dsh-ssh-tui 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +92 -8
- package/README.md +64 -8
- package/cordis.patch.yml +15 -0
- package/docs/terminals.md +113 -0
- package/lib/approval-cache.js +12 -9
- package/lib/approval-cache.js.map +1 -1
- package/lib/color-depth.js +10 -4
- package/lib/color-depth.js.map +1 -1
- package/lib/diag.js +76 -17
- package/lib/diag.js.map +1 -1
- package/lib/dialogs.js.map +1 -1
- package/lib/display-sock.js +304 -29
- package/lib/display-sock.js.map +1 -1
- package/lib/doctor.js +71 -37
- package/lib/doctor.js.map +1 -1
- package/lib/dsh-compat.js +206 -88
- package/lib/dsh-compat.js.map +1 -1
- package/lib/footer.js +39 -9
- package/lib/footer.js.map +1 -1
- package/lib/gateway-protocol.js +202 -0
- package/lib/gateway-protocol.js.map +1 -0
- package/lib/i18n/en.js +79 -41
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/index.js +13 -7
- package/lib/i18n/index.js.map +1 -1
- package/lib/i18n/zh.js +79 -41
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +132 -31
- package/lib/index.js.map +1 -1
- package/lib/job-label.js +99 -17
- package/lib/job-label.js.map +1 -1
- package/lib/paint.js +5 -2
- package/lib/paint.js.map +1 -1
- package/lib/picker.js +5 -3
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +269 -17
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +373 -0
- package/lib/platform.js.map +1 -0
- package/lib/preset-authoring.js +10 -14
- package/lib/preset-authoring.js.map +1 -1
- package/lib/preset-compat.js +100 -0
- package/lib/preset-compat.js.map +1 -0
- package/lib/preset-picker.js +5 -1
- package/lib/preset-picker.js.map +1 -1
- package/lib/preset-rows.js +83 -13
- package/lib/preset-rows.js.map +1 -1
- package/lib/provider-catalog.js +7 -4
- package/lib/provider-catalog.js.map +1 -1
- package/lib/route-memory.js +11 -4
- package/lib/route-memory.js.map +1 -1
- package/lib/session-index.js +5 -0
- package/lib/session-index.js.map +1 -1
- package/lib/session-list.js +18 -1
- package/lib/session-list.js.map +1 -1
- package/lib/session-lock.js +79 -24
- package/lib/session-lock.js.map +1 -1
- package/lib/session-route.js +334 -0
- package/lib/session-route.js.map +1 -0
- package/lib/settings-routes.js +10 -0
- package/lib/settings-routes.js.map +1 -0
- package/lib/settings-subagent.js +10 -0
- package/lib/settings-subagent.js.map +1 -0
- package/lib/subagent-model.js +84 -6
- package/lib/subagent-model.js.map +1 -1
- package/lib/supergrok-token.js +4 -0
- package/lib/supergrok-token.js.map +1 -1
- package/lib/term-text.js +7 -4
- package/lib/term-text.js.map +1 -1
- package/lib/terminal-caps.js +358 -0
- package/lib/terminal-caps.js.map +1 -0
- package/lib/tool-present.js +46 -14
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +1949 -371
- package/lib/tui.js.map +1 -1
- package/lib/types/color-depth.d.ts +3 -3
- package/lib/types/diag.d.ts +18 -1
- package/lib/types/dialogs.d.ts +4 -0
- package/lib/types/display-sock.d.ts +94 -2
- package/lib/types/doctor.d.ts +8 -1
- package/lib/types/dsh-compat.d.ts +141 -44
- package/lib/types/footer.d.ts +21 -5
- package/lib/types/gateway-protocol.d.ts +88 -0
- package/lib/types/i18n/index.d.ts +18 -12
- package/lib/types/index.d.ts +45 -0
- package/lib/types/job-label.d.ts +38 -7
- package/lib/types/picker.d.ts +3 -0
- package/lib/types/plan.d.ts +94 -4
- package/lib/types/platform.d.ts +215 -0
- package/lib/types/preset-authoring.d.ts +8 -14
- package/lib/types/preset-compat.d.ts +58 -0
- package/lib/types/preset-picker.d.ts +1 -1
- package/lib/types/preset-rows.d.ts +51 -9
- package/lib/types/route-memory.d.ts +17 -22
- package/lib/types/session-list.d.ts +5 -0
- package/lib/types/session-lock.d.ts +9 -0
- package/lib/types/session-route.d.ts +191 -0
- package/lib/types/settings-routes.d.ts +16 -0
- package/lib/types/settings-subagent.d.ts +24 -0
- package/lib/types/subagent-model.d.ts +60 -7
- package/lib/types/term-text.d.ts +3 -2
- package/lib/types/terminal-caps.d.ts +105 -0
- package/lib/types/tool-present.d.ts +2 -17
- package/lib/types/transcript-types.d.ts +44 -1
- package/lib/types/tui.d.ts +333 -9
- package/package.json +82 -55
|
@@ -18,10 +18,10 @@ export type ColorDepth = 'truecolor' | '256' | '8' | 'none';
|
|
|
18
18
|
* Decide the palette from the environment.
|
|
19
19
|
*
|
|
20
20
|
* `DSH_TUI_COLOR_DEPTH` wins outright so a user can correct a wrong guess.
|
|
21
|
-
* Otherwise: no colour at all when `NO_COLOR` is set or `TERM`
|
|
22
|
-
*
|
|
21
|
+
* Otherwise: no colour at all when `NO_COLOR` is set or `TERM` is `dumb`
|
|
22
|
+
* (or empty on POSIX); truecolor when `COLORTERM` says so; 256 for a
|
|
23
23
|
* `*256color` terminal and for tmux/screen (their default palette is 256 and
|
|
24
|
-
* they translate truecolor poorly); 8 for everything else.
|
|
24
|
+
* they translate truecolor poorly); 8 for Linux/VT consoles and everything else.
|
|
25
25
|
* @param env - the environment to read (tests pass their own).
|
|
26
26
|
* @returns the palette to paint with.
|
|
27
27
|
*/
|
package/lib/types/diag.d.ts
CHANGED
|
@@ -21,6 +21,24 @@ export interface DiagSnapshot {
|
|
|
21
21
|
hostVersion: string;
|
|
22
22
|
nodeVersion: string;
|
|
23
23
|
platform: string;
|
|
24
|
+
/**
|
|
25
|
+
* Which terminal we classified, and the capabilities we then claimed. A
|
|
26
|
+
* Windows report that says "the mouse does nothing" or "copy did nothing" is
|
|
27
|
+
* otherwise guesswork: conhost and Windows Terminal look identical from the
|
|
28
|
+
* transcript, and the claimed flags are what the TUI actually acted on.
|
|
29
|
+
*/
|
|
30
|
+
terminal?: {
|
|
31
|
+
family: string;
|
|
32
|
+
label: string;
|
|
33
|
+
mouse: boolean;
|
|
34
|
+
bracketedPaste: boolean;
|
|
35
|
+
alternateScreen: boolean;
|
|
36
|
+
osc52: boolean;
|
|
37
|
+
osc8: boolean;
|
|
38
|
+
title: boolean;
|
|
39
|
+
/** `DSH_TUI_TERM_CAPS` tokens that were rejected (typos), for the row. */
|
|
40
|
+
ignoredOverrides: readonly string[];
|
|
41
|
+
};
|
|
24
42
|
/**
|
|
25
43
|
* What the palette resolved to and which hints decided it. A "no colour on
|
|
26
44
|
* Windows" report is otherwise guesswork: `TERM` is unset there by default.
|
|
@@ -72,7 +90,6 @@ export declare function readErrTail(sessionId: string, dshHome?: string): Promis
|
|
|
72
90
|
* The first entry is the actionable one; the rest is supporting context.
|
|
73
91
|
*/
|
|
74
92
|
export declare function diagVerdicts(snapshot: DiagSnapshot): string[];
|
|
75
|
-
/** The whole report as transcript lines. Pure. */
|
|
76
93
|
export declare function formatDiag(snapshot: DiagSnapshot): string[];
|
|
77
94
|
/** Gather everything the report needs. Every probe is best-effort. */
|
|
78
95
|
export declare function collectDiag(options: {
|
package/lib/types/dialogs.d.ts
CHANGED
|
@@ -51,6 +51,10 @@ export interface InspectDialog {
|
|
|
51
51
|
title: string;
|
|
52
52
|
lines: DiffDisplayLine[];
|
|
53
53
|
offset: number;
|
|
54
|
+
/** Child session whose live log should refresh this overlay in place. */
|
|
55
|
+
subagentSessionId?: string;
|
|
56
|
+
/** `/find` already scrolled to its hit here; later repaints keep the offset. */
|
|
57
|
+
searchRevealed?: boolean;
|
|
54
58
|
}
|
|
55
59
|
export type Dialog = ConfirmDialog | QuestionDialog | OnboardingDialog | InspectDialog;
|
|
56
60
|
export interface DialogAnswer {
|
|
@@ -33,12 +33,36 @@ export declare function resolveDshHome(env?: NodeJS.ProcessEnv, home?: string):
|
|
|
33
33
|
export declare function sessionSockDir(dshHome?: string): string;
|
|
34
34
|
/** Filesystem/pipe-safe form of a session id, as used for locks and channels. */
|
|
35
35
|
export declare function safeSessionId(sessionId: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Readable, collision-free short label for one session: a sanitized head plus a
|
|
38
|
+
* digest of the raw id. The digest is always appended because sanitizing alone
|
|
39
|
+
* collapses distinct ids (`foo/bar` and `foo_bar`, `abc` and `abc.`) into one
|
|
40
|
+
* name. On Windows the pipe namespace is machine-wide; on POSIX the same
|
|
41
|
+
* collapse would share a socket file and a lock, so a relay could attach to
|
|
42
|
+
* the wrong session.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sessionLabel(sessionId: string, maxLength: number): string;
|
|
36
45
|
/**
|
|
37
46
|
* Address of the per-session display channel: a filesystem path on POSIX, a
|
|
38
47
|
* named pipe on Windows. `platform` is injectable so the Windows shape stays
|
|
39
48
|
* testable from a POSIX test run.
|
|
40
49
|
*/
|
|
41
50
|
export declare function sessionSockPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string;
|
|
51
|
+
/**
|
|
52
|
+
* Pre-digest POSIX socket path (`tui-socks/<safeId>.sock`).
|
|
53
|
+
*
|
|
54
|
+
* 0.7.1 Hosts still listen here. A newer build's attach must find that channel
|
|
55
|
+
* instead of spawning a second Host that dies on the session write handle.
|
|
56
|
+
* Distinct ids that collapsed under sanitizing (`foo/bar` vs `foo_bar`)
|
|
57
|
+
* share this name — that is why the digest form exists — so a leftover
|
|
58
|
+
* file is only an attach target, never the path a new Host binds.
|
|
59
|
+
*/
|
|
60
|
+
export declare function legacySessionSockPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Channel a leftover Host may still be listening on: the digested path first,
|
|
63
|
+
* then the 0.7.1 name when it is different.
|
|
64
|
+
*/
|
|
65
|
+
export declare function sessionSockLookupPaths(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string[];
|
|
42
66
|
/**
|
|
43
67
|
* Host stderr log for one session. POSIX keeps the historical `<sock>.err`
|
|
44
68
|
* next to the socket; a Windows pipe name is not a file path, so the log lives
|
|
@@ -47,6 +71,16 @@ export declare function sessionSockPath(sessionId: string, dshHome?: string, pla
|
|
|
47
71
|
* device names (`CON`, `NUL`, …) from becoming the file stem.
|
|
48
72
|
*/
|
|
49
73
|
export declare function sessionErrPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string;
|
|
74
|
+
/**
|
|
75
|
+
* Where the hidden-console bootstrap writes the Host's pid.
|
|
76
|
+
*
|
|
77
|
+
* On `\\.\pipe\` Windows there is no socket file to derive a name from, and the
|
|
78
|
+
* state directory already holds the per-session lock and stderr log, so the pid
|
|
79
|
+
* file lives beside them. It is removed as soon as it has been read.
|
|
80
|
+
*/
|
|
81
|
+
export declare function sessionBootstrapPidPath(sessionId: string, dshHome?: string): string;
|
|
82
|
+
/** Pre-digest Host stderr log next to the 0.7.1 socket, when that name differs. */
|
|
83
|
+
export declare function legacySessionErrPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string | undefined;
|
|
50
84
|
export declare function encodeFrame(type: number, payload?: Buffer): Buffer;
|
|
51
85
|
export declare function encodeResize(columns: number, rows: number): Buffer;
|
|
52
86
|
export declare function decodeResize(payload: Buffer): {
|
|
@@ -115,6 +149,8 @@ export interface HostExitWatch {
|
|
|
115
149
|
/** Stop watching; call once the channel is confirmed up. */
|
|
116
150
|
dispose(): void;
|
|
117
151
|
}
|
|
152
|
+
/** Exported for the test that pins its loop-ref behaviour; not public API. */
|
|
153
|
+
export declare function watchHostPid(pid: number): HostExitWatch;
|
|
118
154
|
/**
|
|
119
155
|
* How long a dead-pid report waits for the child's `exit` event before giving
|
|
120
156
|
* up on its code. The event is normally delivered within a tick; the bound only
|
|
@@ -165,8 +201,64 @@ export declare function quietTerminalInput(stdin?: NodeJS.ReadStream): number;
|
|
|
165
201
|
* written into a link that may already be dead.
|
|
166
202
|
*/
|
|
167
203
|
export declare function restoreTerminalInput(stdin?: NodeJS.ReadStream): void;
|
|
168
|
-
/**
|
|
169
|
-
export
|
|
204
|
+
/** Test seam for {@link spawnDetachedHost}; production passes nothing. */
|
|
205
|
+
export interface SpawnHostOptions {
|
|
206
|
+
/**
|
|
207
|
+
* Start the Host through this command instead of resolving the real one, or
|
|
208
|
+
* `null` to spawn it directly even where a bootstrap exists.
|
|
209
|
+
*
|
|
210
|
+
* `null` is for the tests that are about the *direct* path — the fixture Hosts
|
|
211
|
+
* in `tests/display-host-e2e.test.mjs` assert on the child's exit code, which
|
|
212
|
+
* only a real child handle can report (a pid watch resolves `null`). The
|
|
213
|
+
* bootstrap has its own coverage: the builders on every platform, and the real
|
|
214
|
+
* Windows probes end to end.
|
|
215
|
+
*/
|
|
216
|
+
bootstrap?: {
|
|
217
|
+
command: string;
|
|
218
|
+
args: string[];
|
|
219
|
+
} | null | undefined;
|
|
220
|
+
/** How long the bootstrap may take to report a pid (Windows PowerShell start). */
|
|
221
|
+
bootstrapTimeoutMs?: number;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Start the Host through the hidden-console bootstrap and return its pid, or
|
|
225
|
+
* `undefined` when the bootstrap could not report one.
|
|
226
|
+
*
|
|
227
|
+
* `spawnSync` on purpose. The pid has to be in hand before this function
|
|
228
|
+
* returns (the caller watches it, and the fallback must never leave two Hosts
|
|
229
|
+
* for one session), and the cost is one bounded wait while the boot splash is
|
|
230
|
+
* already on screen. PowerShell exits as soon as `Start-Process` has created the
|
|
231
|
+
* Host, so the wait is its own start-up, not the Host's.
|
|
232
|
+
*
|
|
233
|
+
* Falling back is safe exactly when nothing was printed: `Start-Process -PassThru`
|
|
234
|
+
* either starts the Host and prints its id, or throws before starting anything
|
|
235
|
+
* (`$ErrorActionPreference = 'Stop'`). A *timeout* is the one case where a Host
|
|
236
|
+
* might exist and the pid was lost, so it does not fall back — it reports.
|
|
237
|
+
*/
|
|
238
|
+
export declare function spawnHostThroughBootstrap(bootstrap: {
|
|
239
|
+
command: string;
|
|
240
|
+
args: string[];
|
|
241
|
+
}, options: {
|
|
242
|
+
env: NodeJS.ProcessEnv;
|
|
243
|
+
platform: NodeJS.Platform;
|
|
244
|
+
timeoutMs: number;
|
|
245
|
+
/** File the bootstrap writes the Host's pid to. */
|
|
246
|
+
pidFile: string;
|
|
247
|
+
}): {
|
|
248
|
+
pid: number;
|
|
249
|
+
} | undefined;
|
|
250
|
+
/**
|
|
251
|
+
* Spawn a detached Host copy of this `dsh` invocation and return its sock path.
|
|
252
|
+
*
|
|
253
|
+
* On Windows the Host goes through {@link hostBootstrapCommand} when the OS
|
|
254
|
+
* PowerShell is available: a direct spawn there cannot both survive the
|
|
255
|
+
* launcher (libuv's `KILL_ON_JOB_CLOSE` job takes a non-detached child with it)
|
|
256
|
+
* and avoid flashing console windows (`detached` is DETACHED_PROCESS, which makes
|
|
257
|
+
* Windows ignore `CREATE_NO_WINDOW`). The bootstrap gives the Host a console of
|
|
258
|
+
* its own, hidden — see `docs/platform.md`. Without it, the direct spawn below
|
|
259
|
+
* is still what runs, with the old semantics.
|
|
260
|
+
*/
|
|
261
|
+
export declare function spawnDetachedHost(sessionId: string, platform?: NodeJS.Platform, options?: SpawnHostOptions): SpawnedHost;
|
|
170
262
|
export declare function probeDisplaySock(path: string, timeoutMs?: number): Promise<boolean>;
|
|
171
263
|
export interface RelayResult {
|
|
172
264
|
/** Host sent goodbye — user exited from the attached session. */
|
package/lib/types/doctor.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `collectDoctor` and every probe is best-effort. Nothing leaves the machine.
|
|
15
15
|
* @module dsh-ssh-tui/doctor
|
|
16
16
|
*/
|
|
17
|
-
import { type PatchAnalysis, type PatchRowRef, type RosterRow } from './preset-rows.js';
|
|
17
|
+
import { type HostGeneration, type PatchAnalysis, type PatchRowRef, type RosterRow } from './preset-rows.js';
|
|
18
18
|
export type DoctorStatus = 'ok' | 'warn' | 'fail';
|
|
19
19
|
export interface DoctorCheck {
|
|
20
20
|
id: string;
|
|
@@ -64,6 +64,12 @@ export interface DoctorFacts {
|
|
|
64
64
|
range?: string;
|
|
65
65
|
releases: Record<string, string>;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Which settings protocol the host speaks; decides which rows a terminal
|
|
69
|
+
* profile has to mount itself. Absent means the 0.1.5 line, so a snapshot
|
|
70
|
+
* built before this field existed keeps its verdicts.
|
|
71
|
+
*/
|
|
72
|
+
generation?: HostGeneration;
|
|
67
73
|
/** Distinct installs of `@deepseek-ai/dsh-scope` found from the anchors. */
|
|
68
74
|
scopeCopies: readonly string[];
|
|
69
75
|
/** Absent when routing could not be read (no settings service). */
|
|
@@ -101,6 +107,7 @@ export declare function collectDoctor(options: {
|
|
|
101
107
|
services: DoctorFacts['services'];
|
|
102
108
|
anchors: ReadonlyArray<string | undefined>;
|
|
103
109
|
routing?: DoctorRouting;
|
|
110
|
+
generation?: HostGeneration;
|
|
104
111
|
}): Promise<DoctorFacts>;
|
|
105
112
|
/** Rows `/doctor --fix` would mount, given the services the composition registered. */
|
|
106
113
|
export declare function rowsToRepair(facts: DoctorFacts): RosterRow[];
|
|
@@ -1,35 +1,136 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dual-stack shims for dsh 0.1.
|
|
3
|
-
* handle API that landed with
|
|
2
|
+
* Dual-stack shims for the dsh 0.1.5-rc and 0.1.7-rc lines (including the
|
|
3
|
+
* 0.1.5-alpha handle API that landed with the former).
|
|
4
4
|
*
|
|
5
|
-
* 0.1.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* runs on either host.
|
|
5
|
+
* 0.1.5 resolves settings through `SettingsProvider.get`/`installSection` and
|
|
6
|
+
* exposes session persistence as snapshot `list` plus per-session `open`
|
|
7
|
+
* handles; live tokens arrive on the process-local `agent/assistant-stream`.
|
|
8
|
+
* 0.1.7 replaced the settings service with schema-projected forms. Every shim
|
|
9
|
+
* here picks the API that is actually present so one build runs on either
|
|
10
|
+
* host line.
|
|
12
11
|
*/
|
|
13
12
|
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
14
14
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
15
|
-
import type { SettingsNamespace
|
|
15
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
16
16
|
import type z from '@deepseek-ai/schemastery';
|
|
17
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
18
|
+
/**
|
|
19
|
+
* The TUI's own notice/steering messages, which it commits to the durable log
|
|
20
|
+
* with `source.kind === 'plugin'`.
|
|
21
|
+
*
|
|
22
|
+
* 0.1.5 shipped this member; 0.1.7 removed the catch-all and documents the
|
|
23
|
+
* intended pattern instead — "each producer declares its own `kind` in its
|
|
24
|
+
* own module". This is that declaration, and it keeps the committed log shape
|
|
25
|
+
* identical on both lines.
|
|
26
|
+
*/
|
|
27
|
+
interface MessageSourceMap {
|
|
28
|
+
plugin: {
|
|
29
|
+
kind: 'plugin';
|
|
30
|
+
plugin: string;
|
|
31
|
+
} & ContextFormed;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
17
34
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
35
|
+
* Settings namespaces are branded strings at the type level on both supported
|
|
36
|
+
* lines; this cast supplies the brand from a plain literal.
|
|
20
37
|
*/
|
|
21
38
|
export declare function settingsNamespace(value: string): SettingsNamespace;
|
|
22
39
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
40
|
+
* Hooks a settings consumer hands to {@link installSettingsSection}.
|
|
41
|
+
*
|
|
42
|
+
* Spelled out locally instead of imported: 0.1.7 deleted the
|
|
43
|
+
* `SettingsSectionHooks` export, and this shape is the whole contract the three
|
|
44
|
+
* call sites use.
|
|
45
|
+
*/
|
|
46
|
+
export interface SettingsSectionHooks<T> {
|
|
47
|
+
/**
|
|
48
|
+
* Receive the active configuration source: the resolved settings value while
|
|
49
|
+
* a section is attached. Called at attach and again after every change.
|
|
50
|
+
* @param current - thunk returning the currently authoritative value.
|
|
51
|
+
*/
|
|
52
|
+
setSource(current: () => T): void;
|
|
53
|
+
/**
|
|
54
|
+
* Re-judge anything derived from the source — registration-level facts,
|
|
55
|
+
* memoized resolutions — after an attach or a committed change.
|
|
56
|
+
*/
|
|
57
|
+
onChange(): void;
|
|
58
|
+
/** Reject a resolved section this consumer could not act on. */
|
|
59
|
+
validate?(value: T): void;
|
|
60
|
+
}
|
|
61
|
+
/** Which settings protocol the running host speaks. */
|
|
62
|
+
export type SettingsGeneration = 'legacy' | 'forms';
|
|
63
|
+
/**
|
|
64
|
+
* The host's settings generation, as feature detection rather than a version.
|
|
65
|
+
*
|
|
66
|
+
* `legacy` (0.1.5) resolves a namespace through `settings.get`; `forms` (0.1.7)
|
|
67
|
+
* has no `get` and projects a form per loader entry. Callers that differ by
|
|
68
|
+
* generation — which profile rows a terminal profile must mount, for one — read
|
|
69
|
+
* it here instead of sniffing package versions.
|
|
70
|
+
*
|
|
71
|
+
* Before the service exists (a plugin applies before it is mounted) the same
|
|
72
|
+
* split shows up as the roster package's absence, and the answer is memoized as
|
|
73
|
+
* soon as the service is seen so a later call cannot disagree with an earlier
|
|
74
|
+
* one.
|
|
75
|
+
*/
|
|
76
|
+
export declare function hostSettingsGeneration(ctx: Context): SettingsGeneration;
|
|
77
|
+
/** Forget the memoized descriptors so the next read re-walks the forms. */
|
|
78
|
+
export declare function invalidateSettingsCache(ctx: Context): void;
|
|
79
|
+
/**
|
|
80
|
+
* Read one settings section.
|
|
81
|
+
*
|
|
82
|
+
* 0.1.5 resolves it through `get(ns)`. 0.1.7 has no `get`, so the value comes
|
|
83
|
+
* from the entry's descriptor, which projects the live config — volatile fields
|
|
84
|
+
* only, i.e. exactly the fields its form exposes — and returns `undefined` when
|
|
85
|
+
* no entry carries that id.
|
|
86
|
+
*/
|
|
87
|
+
export declare function readSettingsSection(ctx: Context, ns: SettingsNamespace): unknown;
|
|
88
|
+
/**
|
|
89
|
+
* The user's own settings document, keyed by namespace, on either line.
|
|
90
|
+
*
|
|
91
|
+
* 0.1.5 publishes it as `settings.document`. 0.1.7 dropped the property — the
|
|
92
|
+
* document is the profile patch now — but each descriptor still carries the
|
|
93
|
+
* user layer it was built from, so the same view is reconstructible. Callers
|
|
94
|
+
* that ask "did the user configure this?" must use this, not
|
|
95
|
+
* {@link readSettingsSection}: a resolved read also carries the composition
|
|
96
|
+
* base and schema defaults, which is exactly what such a caller must not
|
|
97
|
+
* mistake for a user choice.
|
|
98
|
+
*/
|
|
99
|
+
export declare function settingsDocument(ctx: Context): Record<string, unknown> | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Mark one schema field as form-writable on the 0.1.7 line.
|
|
102
|
+
*
|
|
103
|
+
* 0.1.7 projects only fields whose schema node carries the `volatile` meta, and
|
|
104
|
+
* refuses a settings write to any other path. The 0.1.5 schemastery (3.18.2)
|
|
105
|
+
* has no such builder, so the call is feature-detected rather than typed: on
|
|
106
|
+
* that line the marker means nothing and the schema is returned untouched.
|
|
107
|
+
*/
|
|
108
|
+
export declare function liveField<T>(schema: z<T>): z<T>;
|
|
109
|
+
/**
|
|
110
|
+
* Register a settings section.
|
|
111
|
+
*
|
|
112
|
+
* 0.1.5 publishes the `settings` service with `installSection`, callable only
|
|
113
|
+
* once that service is injected (plugins apply before it, so `ctx.inject` must
|
|
114
|
+
* defer — same pattern the harness's own packages use).
|
|
115
|
+
*
|
|
116
|
+
* 0.1.7 removed it. The section is now the loader entry's own `Config`
|
|
117
|
+
* schema — this plugin's is `ssh-tui`, and the two auxiliary namespaces are
|
|
118
|
+
* carried by the `dsh-ssh-tui/settings-*` rows in `cordis.patch.yml` — so the
|
|
119
|
+
* only thing left for a consumer to wire is the live read (`setSource`) and the
|
|
120
|
+
* change notification (`onChange`).
|
|
28
121
|
*/
|
|
29
122
|
export declare function installSettingsSection<T>(ctx: Context, ns: SettingsNamespace, schema: z<T>, entry: T, hooks: SettingsSectionHooks<T>): void;
|
|
30
123
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
124
|
+
* Whether a tool result reports failure.
|
|
125
|
+
*
|
|
126
|
+
* 0.1.5 carries `isError` on the `tool-result` content block; 0.1.7 removed
|
|
127
|
+
* that block from `ContentBlockMap` and moved the flag onto the message
|
|
128
|
+
* itself. Both are read, so one build understands either host.
|
|
129
|
+
*/
|
|
130
|
+
export declare function toolResultFailed(message: unknown): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Read the full durable event log. Both supported lines read it on demand
|
|
133
|
+
* through `snapshotEvents()`.
|
|
33
134
|
*/
|
|
34
135
|
export declare function sessionEvents(session: object): readonly SessionEvent[];
|
|
35
136
|
/**
|
|
@@ -60,7 +161,6 @@ export interface SessionHeaderLike {
|
|
|
60
161
|
/** Logical log plus the header it belongs to. */
|
|
61
162
|
export interface SessionInspectionLike {
|
|
62
163
|
events: readonly unknown[];
|
|
63
|
-
meta?: SessionHeaderLike;
|
|
64
164
|
header?: SessionHeaderLike;
|
|
65
165
|
/**
|
|
66
166
|
* Backend state for the slice. `detached` means the backend never
|
|
@@ -71,27 +171,28 @@ export interface SessionInspectionLike {
|
|
|
71
171
|
eventState?: string;
|
|
72
172
|
}
|
|
73
173
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
174
|
+
* Both supported lines return `{ header, revision, … }` snapshots from
|
|
175
|
+
* `list()`; normalize to the header so the picker does not care which host
|
|
76
176
|
* it is talking to.
|
|
77
177
|
*/
|
|
78
178
|
export declare function listPersistenceHeaders(persistence: object): Promise<SessionHeaderLike[]>;
|
|
79
179
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
180
|
+
* Read one session through `open(id, 'read')` + `handle.read()`, the access
|
|
181
|
+
* both supported lines expose. Close the handle so a listing pass does not pin
|
|
182
|
+
* write ownership.
|
|
83
183
|
*/
|
|
84
184
|
export declare function inspectPersistenceSession(persistence: object, id: unknown): Promise<SessionInspectionLike>;
|
|
85
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* A session's artifact path from its header. Both supported lines still
|
|
187
|
+
* implement `locate()` on the JSONL backend, but their typings keep it
|
|
188
|
+
* private, so the call stays feature-detected.
|
|
189
|
+
*/
|
|
86
190
|
export declare function persistenceLocate(persistence: object, meta: object): {
|
|
87
191
|
path?: string;
|
|
88
192
|
} | undefined;
|
|
89
|
-
/**
|
|
90
|
-
* 0.1.2 command input advertised `images`; 0.1.5 renamed the flag to
|
|
91
|
-
* `attachments`. Either true means the slash command accepts composer files.
|
|
92
|
-
*/
|
|
193
|
+
/** Whether a command's input admits the composer's attachments. */
|
|
93
194
|
export declare function commandAcceptsAttachments(input: unknown): boolean;
|
|
94
|
-
/** One
|
|
195
|
+
/** One chunk from a live `agent/assistant-stream` frame. */
|
|
95
196
|
export interface StreamChunkLike {
|
|
96
197
|
type: string;
|
|
97
198
|
text?: string;
|
|
@@ -133,12 +234,12 @@ export declare function streamFrameAttemptId(frame: unknown): unknown;
|
|
|
133
234
|
*/
|
|
134
235
|
export declare function streamFirstTokenTime(stream: unknown): number | undefined;
|
|
135
236
|
/**
|
|
136
|
-
*
|
|
237
|
+
* A live `agent/assistant-stream` chunk frame's inner chunk plus its framing.
|
|
137
238
|
*
|
|
138
|
-
* `fallback` supplies the turn/step
|
|
139
|
-
*
|
|
239
|
+
* `fallback` supplies the turn/step, which chunk frames do not carry (see
|
|
240
|
+
* {@link streamFrameOwner}).
|
|
140
241
|
*/
|
|
141
|
-
export declare function streamChunkOf(
|
|
242
|
+
export declare function streamChunkOf(frame: unknown, fallback?: {
|
|
142
243
|
turn: number;
|
|
143
244
|
step: number;
|
|
144
245
|
}): {
|
|
@@ -147,20 +248,16 @@ export declare function streamChunkOf(eventOrFrame: unknown, fallback?: {
|
|
|
147
248
|
step: number;
|
|
148
249
|
time: number;
|
|
149
250
|
/**
|
|
150
|
-
* False when neither the
|
|
251
|
+
* False when neither the frame nor a fallback carried a real turn/step.
|
|
151
252
|
* Usage folded under such a chunk would be filed under a bogus key (0:0)
|
|
152
253
|
* that `step/end` never clears, inflating the session totals forever.
|
|
153
254
|
*/
|
|
154
255
|
stepKnown: boolean;
|
|
155
256
|
} | undefined;
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Subscribe to a host event that may not exist on the compile-time Events
|
|
162
|
-
* map. 0.1.5 emits `agent/assistant-stream`; 0.1.2 never does. Cordis
|
|
163
|
-
* still accepts the string; the listener is simply never called on 0.1.2.
|
|
257
|
+
/**
|
|
258
|
+
* Subscribe to a host event whose scoped payload type does not match this
|
|
259
|
+
* build's `Events` map. Both supported lines emit `agent/assistant-stream`,
|
|
260
|
+
* and Cordis accepts the plain event name at runtime.
|
|
164
261
|
*/
|
|
165
262
|
export declare function listenHostEvent(ctx: {
|
|
166
263
|
on: (event: never, handler: never) => unknown;
|
package/lib/types/footer.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Footer stats, status identity, context-pressure chips, and `/status` report.
|
|
3
3
|
*/
|
|
4
|
+
import { type ColorDepth } from './color-depth.js';
|
|
4
5
|
import { type QuotaPeriod, type QuotaSnapshot, type QuotaSource } from './quota.js';
|
|
5
6
|
import type { DisconnectPolicyName } from './transcript-types.js';
|
|
6
7
|
export declare const WAIT_INDICATOR_MS = 8000;
|
|
@@ -104,7 +105,9 @@ export declare function fitFooterChips(chips: readonly FooterChip[], width: numb
|
|
|
104
105
|
* the preset-owned tools are missing. It leads the strip and keeps its glyph
|
|
105
106
|
* longest, because it is the one group that reports a broken install.
|
|
106
107
|
*/
|
|
107
|
-
export declare function footerHealthChip(missing: boolean, color?: boolean
|
|
108
|
+
export declare function footerHealthChip(missing: boolean, color?: boolean,
|
|
109
|
+
/** Which rows are missing: the 0.1.5 roster, or the 0.1.7 agent plane. */
|
|
110
|
+
kind?: 'roster' | 'agent-plane'): FooterChip | undefined;
|
|
108
111
|
export declare function fitFooterStatsLine(chip: string, groups: readonly string[], width: number): string;
|
|
109
112
|
export type FooterActivityKind = 'plan-review' | 'waiting' | 'compacting' | 'retry' | 'subagents' | 'tools' | 'plan-open' | 'plan-pending' | 'goal' | 'waiting-llm' | 'idle';
|
|
110
113
|
export interface FooterStatusInput {
|
|
@@ -161,12 +164,25 @@ export declare function formatQuotaBar(remainingPercent: number, width?: number)
|
|
|
161
164
|
* `sub:<model>` chip for the identity row: `sub:grok-4.5`, or
|
|
162
165
|
* `sub:grok-4.5(xhigh)` when an explicit `/subeffort` is set.
|
|
163
166
|
*
|
|
164
|
-
* It shows the model name only, like the parent's own chip
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* and the child's model name is the part that differs from the parent's.
|
|
167
|
+
* It shows the model name only, like the parent's own chip, unless `/submodel`
|
|
168
|
+
* pinned a different provider — then `sub:xai/grok-4.5` so the foreign route
|
|
169
|
+
* is visible on the identity row as well as the recolored chip.
|
|
168
170
|
*/
|
|
169
171
|
export declare function subagentRouteLabel(model: string, provider?: string, effort?: string): string;
|
|
172
|
+
/**
|
|
173
|
+
* Paint the `sub:` chip on an otherwise muted status line.
|
|
174
|
+
*
|
|
175
|
+
* Only a *foreign* route is accented: cyan when `/submodel` pinned a supplier
|
|
176
|
+
* the parent is not on. A child that follows the parent (or that was pinned
|
|
177
|
+
* back onto the parent's own provider) keeps the identity row's mute — the
|
|
178
|
+
* accent means "this route is not your parent's", and spending it on every
|
|
179
|
+
* child made the following case look pinned.
|
|
180
|
+
*
|
|
181
|
+
* The mute (`90`) is reopened after the chip so the rest of the identity row
|
|
182
|
+
* stays dim.
|
|
183
|
+
*/
|
|
184
|
+
export declare function paintFooterSubagentChip(line: string, chip: string, foreign: boolean, muteSgr?: string, depth?: ColorDepth): string;
|
|
185
|
+
export declare function footerSubagentForeign(input: Pick<FooterStatusInput, 'provider' | 'subProvider'>): boolean;
|
|
170
186
|
/**
|
|
171
187
|
* The model as the footer shows it: the model's own name, without a
|
|
172
188
|
* `provider/model` route prefix.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One gateway, several wire protocols.
|
|
3
|
+
*
|
|
4
|
+
* `llm-pi-ai` puts `api` on the *provider* entry, not on a model entry, so a
|
|
5
|
+
* gateway whose catalogue spans protocols (Command Code: 55 of 71 models answer
|
|
6
|
+
* `/responses`, 8 only `/chat/completions`, the Claude family only `/messages`)
|
|
7
|
+
* cannot be one entry. The wizard therefore keeps one row per gateway and
|
|
8
|
+
* expands it into sibling entries — `command-code`, `command-code-responses`,
|
|
9
|
+
* `command-code-messages` — with each model filed under the protocol it
|
|
10
|
+
* actually speaks. Everything here is pure so the split can be tested without
|
|
11
|
+
* a settings file or a network.
|
|
12
|
+
*/
|
|
13
|
+
export type GatewayProtocol = 'openai-responses' | 'openai-completions' | 'anthropic-messages';
|
|
14
|
+
/** Preference order for a model a gateway lists on more than one route. */
|
|
15
|
+
export declare const GATEWAY_PROTOCOL_PREFERENCE: readonly GatewayProtocol[];
|
|
16
|
+
/** Id suffix per protocol; the base entry keeps the plain gateway id. */
|
|
17
|
+
export declare const GATEWAY_PROTOCOL_SUFFIX: Record<GatewayProtocol, string>;
|
|
18
|
+
/** The endpoint path each protocol speaks, for messages and hints. */
|
|
19
|
+
export declare const GATEWAY_PROTOCOL_ENDPOINT: Record<GatewayProtocol, string>;
|
|
20
|
+
/** The endpoint strings gateways publish; `null` for anything unrecognised. */
|
|
21
|
+
export declare function endpointProtocol(endpoint: string): GatewayProtocol | null;
|
|
22
|
+
/** `command-code` + `openai-responses` → `command-code-responses`. */
|
|
23
|
+
export declare function siblingProviderId(baseId: string, protocol: GatewayProtocol): string;
|
|
24
|
+
/** The protocol a sibling id carries, or null when it is not one of ours. */
|
|
25
|
+
export declare function siblingProtocolOf(id: string): GatewayProtocol | null;
|
|
26
|
+
/** True when `id` is a sibling of `baseId` (and not the base itself). */
|
|
27
|
+
export declare function isSiblingOf(baseId: string, id: string): boolean;
|
|
28
|
+
/** The gateway id a sibling row belongs to; a base id comes back unchanged. */
|
|
29
|
+
export declare function baseProviderIdOf(id: string): string;
|
|
30
|
+
/** The protocol a provider entry's `api` names, when it is one of the three we model. */
|
|
31
|
+
export declare function declaredProtocol(value: unknown): GatewayProtocol | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Whether an entry that speaks `protocol` may serve `model`, according to the
|
|
34
|
+
* gateway's own `supported_endpoints`. A gateway that publishes nothing about
|
|
35
|
+
* the model — or no protocol to check against — never blocks it: silence is not
|
|
36
|
+
* a refusal, only an explicit list that omits the protocol is.
|
|
37
|
+
*/
|
|
38
|
+
export declare function gatewayServesModel(endpoints: ReadonlyMap<string, readonly string[]> | undefined, model: string, protocol: GatewayProtocol | undefined): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The protocols a gateway lists for `model`, in preference order. Empty when the
|
|
41
|
+
* gateway says nothing, which callers read as "no opinion".
|
|
42
|
+
*/
|
|
43
|
+
export declare function advertisedProtocols(endpoints: ReadonlyMap<string, readonly string[]> | undefined, model: string): GatewayProtocol[];
|
|
44
|
+
/**
|
|
45
|
+
* The protocol one model should be filed under. The gateway's own
|
|
46
|
+
* `supported_endpoints` decide when it publishes them; a model already
|
|
47
|
+
* configured on a route the gateway still lists keeps that route, and a model
|
|
48
|
+
* the gateway does not describe keeps the route it is already on rather than
|
|
49
|
+
* being relocated by a hand-maintained table. Otherwise the earliest entry in
|
|
50
|
+
* `preference` wins, then the table, then the gateway's own primary protocol.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveModelProtocol(input: {
|
|
53
|
+
advertised?: readonly string[];
|
|
54
|
+
/** The provider protocol this model is already configured on, if any. */
|
|
55
|
+
existing?: GatewayProtocol;
|
|
56
|
+
table?: GatewayProtocol;
|
|
57
|
+
fallback: GatewayProtocol;
|
|
58
|
+
preference?: readonly GatewayProtocol[];
|
|
59
|
+
}): GatewayProtocol;
|
|
60
|
+
/**
|
|
61
|
+
* Every model of one gateway, filed by protocol. `existing` carries the
|
|
62
|
+
* protocol each model is already configured on, so re-running setup keeps a
|
|
63
|
+
* working placement instead of re-homing it. Models the gateway does not
|
|
64
|
+
* describe, that are not configured yet, and that the table does not know land
|
|
65
|
+
* on `fallback`, which is the gateway's own pinned protocol — the one its
|
|
66
|
+
* default models were verified on.
|
|
67
|
+
*/
|
|
68
|
+
export declare function splitModelsByProtocol(input: {
|
|
69
|
+
models: readonly string[];
|
|
70
|
+
advertised?: ReadonlyMap<string, readonly string[]>;
|
|
71
|
+
existing?: ReadonlyMap<string, GatewayProtocol>;
|
|
72
|
+
table?: Readonly<Record<string, GatewayProtocol>>;
|
|
73
|
+
fallback: GatewayProtocol;
|
|
74
|
+
preference?: readonly GatewayProtocol[];
|
|
75
|
+
}): Map<GatewayProtocol, string[]>;
|
|
76
|
+
/**
|
|
77
|
+
* OpenCode Go's published endpoint table (https://opencode.ai/docs/go/), because
|
|
78
|
+
* `GET /zen/go/v1/models` carries no `supported_endpoints` field at all.
|
|
79
|
+
*
|
|
80
|
+
* The whole DeepSeek family answers `/responses` (verified against the live
|
|
81
|
+
* gateway, and what this plugin's pinned template has shipped since 0.7.0) even
|
|
82
|
+
* though the page still lists those models under chat. A route that works is not
|
|
83
|
+
* downgraded because a document lags. Zen's own (non-Go) route publishes no
|
|
84
|
+
* table here — unknown models there take the gateway's primary protocol.
|
|
85
|
+
*/
|
|
86
|
+
export declare const ZEN_GO_MODEL_PROTOCOLS: Readonly<Record<string, GatewayProtocol>>;
|
|
87
|
+
/** The built-in table for a gateway base URL, when we have one. */
|
|
88
|
+
export declare function gatewayModelTable(baseURL: string): Readonly<Record<string, GatewayProtocol>> | undefined;
|
|
@@ -10,22 +10,28 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
10
10
|
export type Locale = 'zh' | 'en';
|
|
11
11
|
export type MessageVars = Record<string, string | number>;
|
|
12
12
|
export declare const UI_LOCALE_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
13
|
+
/**
|
|
14
|
+
* Fields `/language`, `/view`, `/disconnect` and `/autoapproval` persist.
|
|
15
|
+
*
|
|
16
|
+
* Every field is live: on 0.1.7 the section *is* this form, and only volatile
|
|
17
|
+
* paths may be written (see {@link liveField}).
|
|
18
|
+
*/
|
|
13
19
|
export declare const UI_LOCALE_SCHEMA: z<Schemastery.ObjectS<{
|
|
14
|
-
language: z<string
|
|
15
|
-
skipUpdate: z<string
|
|
16
|
-
view: z<string
|
|
17
|
-
disconnect: z<string
|
|
18
|
-
autoApproval: z<string
|
|
20
|
+
language: z<string>;
|
|
21
|
+
skipUpdate: z<string>;
|
|
22
|
+
view: z<string>;
|
|
23
|
+
disconnect: z<string>;
|
|
24
|
+
autoApproval: z<string>;
|
|
19
25
|
/** Milliseconds a leftover, finished Host waits before exiting; 0 = never. */
|
|
20
|
-
idleExit: z<number
|
|
26
|
+
idleExit: z<number>;
|
|
21
27
|
}>, Schemastery.ObjectT<{
|
|
22
|
-
language: z<string
|
|
23
|
-
skipUpdate: z<string
|
|
24
|
-
view: z<string
|
|
25
|
-
disconnect: z<string
|
|
26
|
-
autoApproval: z<string
|
|
28
|
+
language: z<string>;
|
|
29
|
+
skipUpdate: z<string>;
|
|
30
|
+
view: z<string>;
|
|
31
|
+
disconnect: z<string>;
|
|
32
|
+
autoApproval: z<string>;
|
|
27
33
|
/** Milliseconds a leftover, finished Host waits before exiting; 0 = never. */
|
|
28
|
-
idleExit: z<number
|
|
34
|
+
idleExit: z<number>;
|
|
29
35
|
}>>;
|
|
30
36
|
export declare function localeFromTag(tag: string): Locale | undefined;
|
|
31
37
|
/** Pick zh/en from env, optionally after a saved settings value. */
|