dsh-ssh-tui 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +76 -7
- package/README.md +50 -7
- 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 +41 -17
- package/lib/diag.js.map +1 -1
- package/lib/dialogs.js.map +1 -1
- package/lib/display-sock.js +68 -10
- package/lib/display-sock.js.map +1 -1
- package/lib/footer.js +35 -7
- 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 +61 -41
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +61 -41
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +99 -23
- package/lib/index.js.map +1 -1
- package/lib/job-label.js +99 -17
- package/lib/job-label.js.map +1 -1
- package/lib/plan.js +269 -17
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +88 -0
- package/lib/platform.js.map +1 -0
- package/lib/provider-catalog.js +3 -0
- package/lib/provider-catalog.js.map +1 -1
- package/lib/route-memory.js +8 -1
- package/lib/route-memory.js.map +1 -1
- package/lib/session-list.js +18 -1
- package/lib/session-list.js.map +1 -1
- package/lib/session-lock.js +75 -23
- package/lib/session-lock.js.map +1 -1
- package/lib/session-route.js +331 -0
- package/lib/session-route.js.map +1 -0
- package/lib/subagent-model.js +79 -1
- package/lib/subagent-model.js.map +1 -1
- package/lib/term-text.js +7 -4
- package/lib/term-text.js.map +1 -1
- package/lib/tool-present.js +46 -14
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +1789 -275
- package/lib/tui.js.map +1 -1
- package/lib/types/color-depth.d.ts +3 -3
- package/lib/types/dialogs.d.ts +4 -0
- package/lib/types/display-sock.d.ts +52 -1
- package/lib/types/footer.d.ts +18 -4
- package/lib/types/gateway-protocol.d.ts +88 -0
- package/lib/types/job-label.d.ts +38 -7
- package/lib/types/plan.d.ts +94 -4
- package/lib/types/platform.d.ts +50 -0
- 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/subagent-model.d.ts +53 -0
- package/lib/types/term-text.d.ts +3 -2
- package/lib/types/tool-present.d.ts +2 -17
- package/lib/types/transcript-types.d.ts +44 -1
- package/lib/types/tui.d.ts +292 -2
- package/package.json +1 -1
|
@@ -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/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,8 @@ 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
|
+
/** Pre-digest Host stderr log next to the 0.7.1 socket, when that name differs. */
|
|
75
|
+
export declare function legacySessionErrPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string | undefined;
|
|
50
76
|
export declare function encodeFrame(type: number, payload?: Buffer): Buffer;
|
|
51
77
|
export declare function encodeResize(columns: number, rows: number): Buffer;
|
|
52
78
|
export declare function decodeResize(payload: Buffer): {
|
|
@@ -165,8 +191,33 @@ export declare function quietTerminalInput(stdin?: NodeJS.ReadStream): number;
|
|
|
165
191
|
* written into a link that may already be dead.
|
|
166
192
|
*/
|
|
167
193
|
export declare function restoreTerminalInput(stdin?: NodeJS.ReadStream): void;
|
|
194
|
+
/**
|
|
195
|
+
* How to start the background Host so it outlives this process *and* does not
|
|
196
|
+
* make its own children flash console windows on Windows.
|
|
197
|
+
*
|
|
198
|
+
* POSIX wants `detached: true` (setsid) so the Host survives the launcher and a
|
|
199
|
+
* hung-up terminal.
|
|
200
|
+
*
|
|
201
|
+
* Windows is the opposite: `detached: true` maps to DETACHED_PROCESS, which
|
|
202
|
+
* gives the Host **no console at all**, and Windows ignores CREATE_NO_WINDOW
|
|
203
|
+
* (what `windowsHide` sets) when DETACHED_PROCESS is present. Every console
|
|
204
|
+
* child the Host then starts — each tool call, every shell, node, git — has to
|
|
205
|
+
* allocate its own console, which is a visible window flashing over the TUI.
|
|
206
|
+
* Dropping `detached` there lets `windowsHide` do its job: the Host gets its
|
|
207
|
+
* own invisible console, and descendants inherit it instead of creating one.
|
|
208
|
+
* The Host still outlives the launcher: Windows does not kill children with
|
|
209
|
+
* their parent, and its console is its own, so closing the user's terminal does
|
|
210
|
+
* not reach it either.
|
|
211
|
+
*
|
|
212
|
+
* Pure and platform-parameterised so the Windows branch can be asserted from
|
|
213
|
+
* Linux (see docs/platform.md).
|
|
214
|
+
*/
|
|
215
|
+
export declare function hostSpawnOptions(platform?: NodeJS.Platform): {
|
|
216
|
+
detached: boolean;
|
|
217
|
+
windowsHide: boolean;
|
|
218
|
+
};
|
|
168
219
|
/** Spawn a detached Host copy of this `dsh` invocation and return its sock path. */
|
|
169
|
-
export declare function spawnDetachedHost(sessionId: string): SpawnedHost;
|
|
220
|
+
export declare function spawnDetachedHost(sessionId: string, platform?: NodeJS.Platform): SpawnedHost;
|
|
170
221
|
export declare function probeDisplaySock(path: string, timeoutMs?: number): Promise<boolean>;
|
|
171
222
|
export interface RelayResult {
|
|
172
223
|
/** Host sent goodbye — user exited from the attached session. */
|
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;
|
|
@@ -161,12 +162,25 @@ export declare function formatQuotaBar(remainingPercent: number, width?: number)
|
|
|
161
162
|
* `sub:<model>` chip for the identity row: `sub:grok-4.5`, or
|
|
162
163
|
* `sub:grok-4.5(xhigh)` when an explicit `/subeffort` is set.
|
|
163
164
|
*
|
|
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.
|
|
165
|
+
* It shows the model name only, like the parent's own chip, unless `/submodel`
|
|
166
|
+
* pinned a different provider — then `sub:xai/grok-4.5` so the foreign route
|
|
167
|
+
* is visible on the identity row as well as the recolored chip.
|
|
168
168
|
*/
|
|
169
169
|
export declare function subagentRouteLabel(model: string, provider?: string, effort?: string): string;
|
|
170
|
+
/**
|
|
171
|
+
* Paint the `sub:` chip on an otherwise muted status line.
|
|
172
|
+
*
|
|
173
|
+
* Only a *foreign* route is accented: cyan when `/submodel` pinned a supplier
|
|
174
|
+
* the parent is not on. A child that follows the parent (or that was pinned
|
|
175
|
+
* back onto the parent's own provider) keeps the identity row's mute — the
|
|
176
|
+
* accent means "this route is not your parent's", and spending it on every
|
|
177
|
+
* child made the following case look pinned.
|
|
178
|
+
*
|
|
179
|
+
* The mute (`90`) is reopened after the chip so the rest of the identity row
|
|
180
|
+
* stays dim.
|
|
181
|
+
*/
|
|
182
|
+
export declare function paintFooterSubagentChip(line: string, chip: string, foreign: boolean, muteSgr?: string, depth?: ColorDepth): string;
|
|
183
|
+
export declare function footerSubagentForeign(input: Pick<FooterStatusInput, 'provider' | 'subProvider'>): boolean;
|
|
170
184
|
/**
|
|
171
185
|
* The model as the footer shows it: the model's own name, without a
|
|
172
186
|
* `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;
|
package/lib/types/job-label.d.ts
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Friendly display names for background jobs.
|
|
2
|
+
* Friendly display names for background jobs and subagent chips.
|
|
3
3
|
*
|
|
4
|
-
* `job_*` cards otherwise read as the raw tool vocabulary
|
|
5
|
-
* `job_id: bash-1`)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* keeps one name across its call card, its result card, and every later
|
|
4
|
+
* Background `job_*` cards otherwise read as the raw tool vocabulary
|
|
5
|
+
* (`job_output`, `job_id: bash-1`). Each job id gets a two-token alias
|
|
6
|
+
* (hour + weather: 昏风 / twilight wind) derived from the id itself rather
|
|
7
|
+
* than from a random draw, so the same
|
|
8
|
+
* job keeps one name across its call card, its result card, and every later
|
|
9
9
|
* `job_output` / `job_kill` mention. The model-facing id stays authoritative;
|
|
10
10
|
* the alias is presentation only.
|
|
11
11
|
*
|
|
12
|
+
* Subagent chips use a different scheme: a role distilled from the parent
|
|
13
|
+
* spawn description, joined to one of the four directional beasts
|
|
14
|
+
* (青龙 / 白虎 / 朱雀 / 玄武) hashed from the child session id.
|
|
15
|
+
*
|
|
12
16
|
* @module dsh-ssh-tui/job-label
|
|
13
17
|
*/
|
|
18
|
+
/** FNV-1a over the job id: stable, dependency-free, and well spread for short ids. */
|
|
19
|
+
export declare function hashJobId(id: string): number;
|
|
14
20
|
/**
|
|
15
21
|
* One stable alias for a background job, or `undefined` when the id is empty
|
|
16
22
|
* or the locale carries no vocabulary.
|
|
17
23
|
* @param jobId - the native job id (`bash-1`, `pwsh-2`, …).
|
|
18
|
-
* @returns the locale-formatted alias, e.g.
|
|
24
|
+
* @returns the locale-formatted alias, e.g. `昏风` / `twilight wind`.
|
|
19
25
|
*/
|
|
20
26
|
export declare function jobAlias(jobId: string): string | undefined;
|
|
27
|
+
/** The four directional beasts, hashed from the child session id. */
|
|
28
|
+
export declare const SUBAGENT_BEASTS: readonly ["azure-dragon", "white-tiger", "vermilion-bird", "black-tortoise"];
|
|
29
|
+
export type SubagentBeastId = (typeof SUBAGENT_BEASTS)[number];
|
|
30
|
+
export type SubagentRoleId = 'scout' | 'scribe' | 'artisan' | 'envoy' | 'inquirer' | 'sentinel' | 'steward' | 'courier';
|
|
31
|
+
/** Distill a parent spawn description into one role id. */
|
|
32
|
+
export declare function subagentRoleId(task: string): SubagentRoleId;
|
|
33
|
+
/** Hash the child session onto one of the four beasts. */
|
|
34
|
+
export declare function subagentBeastId(sessionId: string): SubagentBeastId;
|
|
35
|
+
/** Localized role label (`探路` / `scout`). */
|
|
36
|
+
export declare function subagentRoleLabel(role: SubagentRoleId): string;
|
|
37
|
+
/** Localized beast label (`青龙` / `Azure Dragon`). */
|
|
38
|
+
export declare function subagentBeastLabel(beast: SubagentBeastId): string;
|
|
39
|
+
/**
|
|
40
|
+
* Chip title: `探路·青龙` / `Scout · Azure Dragon`.
|
|
41
|
+
* Falls back to the spawn-time label when neither task nor id can be named.
|
|
42
|
+
*/
|
|
43
|
+
export declare function subagentCourtesyName(input: {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
task?: string;
|
|
46
|
+
fallback: string;
|
|
47
|
+
}): string;
|
|
48
|
+
/** True when a string looks like a session / tool-call id, not a display name. */
|
|
49
|
+
export declare function looksLikeOpaqueId(value: string): boolean;
|
|
50
|
+
/** Prefer a recorded tool name; never surface `call-<uuid>` as a title. */
|
|
51
|
+
export declare function displayToolName(name: string | undefined): string;
|
package/lib/types/plan.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan dock, todo lists, /find, prompt-injection cards, and compact errors.
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { type TextSegment } from './term-text.js';
|
|
5
|
+
import type { DiffDisplayLine, DisplayKind, PlanTodoItem, Row, SubagentLogEntry } from './transcript-types.js';
|
|
5
6
|
export declare const MAX_SUBAGENT_LOGS = 80;
|
|
6
7
|
export declare const TODO_STATUS_MARK: Record<PlanTodoItem['status'], string>;
|
|
7
8
|
/** True while a plan still belongs in the dock (latest incomplete work). */
|
|
@@ -78,10 +79,99 @@ export declare function parsePlanTodos(value: unknown): PlanTodoItem[];
|
|
|
78
79
|
export declare function todoSummary(value: unknown): string;
|
|
79
80
|
/** Compact ask_user_question summary from tool arguments. */
|
|
80
81
|
export declare function askSummary(value: unknown): string;
|
|
81
|
-
/**
|
|
82
|
-
export declare function
|
|
82
|
+
/** First non-empty line, collapsed to a single scan line. */
|
|
83
|
+
export declare function firstDisplayLine(text: string): string;
|
|
84
|
+
/** Collapse a child-session blob to one short chip/wait-card line. */
|
|
85
|
+
export declare function clipSubagentActivity(text: string, maxChars?: number): string;
|
|
86
|
+
/** Running / ok / aborted / error → ANSI for the status dot and status word. */
|
|
87
|
+
export declare function subagentStateColor(status: Extract<Row, {
|
|
83
88
|
kind: 'subagent';
|
|
84
|
-
}
|
|
89
|
+
}>['status']): '33' | '32' | '31' | '90';
|
|
90
|
+
/**
|
|
91
|
+
* Rebuild a subagent chip from the parent spawn tool call that survives in the
|
|
92
|
+
* session log. Live `subagent/start` is not replayed, so resume would otherwise
|
|
93
|
+
* show a generic tool card titled from the English description ("probe").
|
|
94
|
+
*/
|
|
95
|
+
export declare function subagentRowFromSpawnTool(input: {
|
|
96
|
+
callId: string;
|
|
97
|
+
task: string;
|
|
98
|
+
provider?: string;
|
|
99
|
+
/** Model route for the child, when the caller knows it. */
|
|
100
|
+
modelProvider?: string;
|
|
101
|
+
local?: boolean;
|
|
102
|
+
status?: Extract<Row, {
|
|
103
|
+
kind: 'subagent';
|
|
104
|
+
}>['status'];
|
|
105
|
+
startedAt?: number;
|
|
106
|
+
endedAt?: number;
|
|
107
|
+
output?: string;
|
|
108
|
+
}): Extract<Row, {
|
|
109
|
+
kind: 'subagent';
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Courtesy title: distilled role plus a directional beast.
|
|
113
|
+
*
|
|
114
|
+
* The beast comes from the child's own session id, never the parent call id a
|
|
115
|
+
* replayed chip was built with: the same child must keep its symbol across a
|
|
116
|
+
* `--resume`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function subagentDisplayName(row: Extract<Row, {
|
|
119
|
+
kind: 'subagent';
|
|
120
|
+
}>): string;
|
|
121
|
+
/**
|
|
122
|
+
* Short activity for the collapsed chip and wait card: prefer the parent
|
|
123
|
+
* task name, else a clipped last log line. Never the full child transcript.
|
|
124
|
+
*/
|
|
125
|
+
export declare function subagentChipSummary(row: Extract<Row, {
|
|
126
|
+
kind: 'subagent';
|
|
127
|
+
}>): string;
|
|
128
|
+
/** Header + SGR spans: identity color, status dot/word, muted summary. */
|
|
129
|
+
export declare function buildSubagentHeader(input: {
|
|
130
|
+
focused: boolean;
|
|
131
|
+
title: string;
|
|
132
|
+
status: Extract<Row, {
|
|
133
|
+
kind: 'subagent';
|
|
134
|
+
}>['status'];
|
|
135
|
+
elapsedLabel: string;
|
|
136
|
+
summary: string;
|
|
137
|
+
spinner?: string;
|
|
138
|
+
inspectHint?: string;
|
|
139
|
+
/** Different provider from the parent: paint the title cyan, not violet. */
|
|
140
|
+
foreign?: boolean;
|
|
141
|
+
}): {
|
|
142
|
+
plain: string;
|
|
143
|
+
segments: TextSegment[];
|
|
144
|
+
};
|
|
145
|
+
/** Map a folded child-session event onto an existing display role. */
|
|
146
|
+
export declare function subagentLogDisplayKind(entry: SubagentLogEntry, status: Extract<Row, {
|
|
147
|
+
kind: 'subagent';
|
|
148
|
+
}>['status']): DisplayKind;
|
|
149
|
+
/**
|
|
150
|
+
* Overlay body: session line, stop reason, then the clipped child log.
|
|
151
|
+
*
|
|
152
|
+
* The child's session id is the one `/subagents kill` takes, so it belongs on
|
|
153
|
+
* this line — the collapsed chip stays free of opaque ids.
|
|
154
|
+
*/
|
|
155
|
+
export declare function subagentInspectLines(row: Extract<Row, {
|
|
156
|
+
kind: 'subagent';
|
|
157
|
+
}>): DiffDisplayLine[];
|
|
158
|
+
/**
|
|
159
|
+
* Classify a child-session error so the chip can say *why* it died, not just
|
|
160
|
+
* that it ended. Quota and expired auth get a command that actually helps;
|
|
161
|
+
* everything else keeps a clipped diagnostic.
|
|
162
|
+
*/
|
|
163
|
+
export declare function describeSubagentFailure(input: {
|
|
164
|
+
stopReason?: string;
|
|
165
|
+
message?: string;
|
|
166
|
+
provider?: string;
|
|
167
|
+
}): {
|
|
168
|
+
hint: string;
|
|
169
|
+
kind: 'quota' | 'auth' | 'effort' | 'error';
|
|
170
|
+
} | undefined;
|
|
85
171
|
export declare function appendSubagentLog(row: Extract<Row, {
|
|
86
172
|
kind: 'subagent';
|
|
87
173
|
}>, entry: SubagentLogEntry): void;
|
|
174
|
+
/** Fold a child user/plugin blob: reminders become one inject chip, not raw XML. */
|
|
175
|
+
export declare function foldSubagentUserLog(row: Extract<Row, {
|
|
176
|
+
kind: 'subagent';
|
|
177
|
+
}>, text: string, sourceKind?: string, plugin?: string): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** This process is running on Windows. */
|
|
2
|
+
export declare const IS_WINDOWS: boolean;
|
|
3
|
+
/** Windows delivers resizes on the stream; everyone else raises SIGWINCH. */
|
|
4
|
+
export declare function usesSigwinch(platform?: NodeJS.Platform): boolean;
|
|
5
|
+
/** The launcher's environment file: a shell fragment or a `cmd` script. */
|
|
6
|
+
export declare function envFileName(platform?: NodeJS.Platform): string;
|
|
7
|
+
/** The shell a Windows user has: PowerShell, where POSIX code would say bash. */
|
|
8
|
+
export declare function shellName(platform?: NodeJS.Platform): string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether lock liveness is decided by asking the OS about the process (Windows:
|
|
11
|
+
* `Get-Process` + creation time) rather than by reading `/proc` (Linux) or the
|
|
12
|
+
* command line (darwin).
|
|
13
|
+
*
|
|
14
|
+
* `lockOwnerIsAlive` returns early into `windowsProcessMatchesLock` when this is
|
|
15
|
+
* true; the POSIX body below it has no meaning there (`/proc` does not exist).
|
|
16
|
+
*/
|
|
17
|
+
export declare function usesProcessIdentity(platform?: NodeJS.Platform): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* How to start the background Host so it outlives this process *and* does not
|
|
20
|
+
* make its own children flash console windows on Windows.
|
|
21
|
+
*
|
|
22
|
+
* POSIX wants `detached: true` (setsid) so the Host survives the launcher and a
|
|
23
|
+
* hung-up terminal.
|
|
24
|
+
*
|
|
25
|
+
* Windows is the opposite: `detached: true` maps to DETACHED_PROCESS, which
|
|
26
|
+
* gives the Host **no console at all**, and Windows ignores CREATE_NO_WINDOW
|
|
27
|
+
* (what `windowsHide` sets) when DETACHED_PROCESS is present. Every console
|
|
28
|
+
* child the Host then starts — each tool call, every shell, node, git — has to
|
|
29
|
+
* allocate its own console, which is a visible window flashing over the TUI.
|
|
30
|
+
* Dropping `detached` there lets `windowsHide` do its job: the Host gets its
|
|
31
|
+
* own invisible console, and descendants inherit it instead of creating one.
|
|
32
|
+
* The Host still outlives the launcher: Windows does not kill children with
|
|
33
|
+
* their parent, and its console is its own, so closing the user's terminal does
|
|
34
|
+
* not reach it either.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hostSpawnOptions(platform?: NodeJS.Platform): {
|
|
37
|
+
detached: boolean;
|
|
38
|
+
windowsHide: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A path a human can read, with the platform's own shorthand.
|
|
42
|
+
*
|
|
43
|
+
* `~/.dsh/env.sh` is the POSIX form; Windows users know `%USERPROFILE%`, and a
|
|
44
|
+
* `C:\Users\...` prefix spelled out is noise in a one-line hint.
|
|
45
|
+
*/
|
|
46
|
+
export declare function displayHomePath(home: string, file: string, options?: {
|
|
47
|
+
platform?: NodeJS.Platform;
|
|
48
|
+
env?: NodeJS.ProcessEnv;
|
|
49
|
+
userHome?: string;
|
|
50
|
+
}): string;
|
|
@@ -5,28 +5,23 @@
|
|
|
5
5
|
import z from '@deepseek-ai/schemastery';
|
|
6
6
|
import type { Context } from '@deepseek-ai/cordis';
|
|
7
7
|
export declare const ROUTE_MEMORY_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
8
|
-
/**
|
|
9
|
-
export
|
|
10
|
-
providers:
|
|
11
|
-
model
|
|
12
|
-
reasoningEffort
|
|
13
|
-
updatedAt
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
model: z<string, string>;
|
|
26
|
-
reasoningEffort: z<string, string>;
|
|
27
|
-
updatedAt: z<number, number>;
|
|
28
|
-
}>, string>>;
|
|
29
|
-
}>>;
|
|
8
|
+
/** The shape `ssh-tui-routes` takes in `$DSH_HOME/settings.yaml`. */
|
|
9
|
+
export interface RouteMemorySettings {
|
|
10
|
+
providers: Record<string, {
|
|
11
|
+
model: string;
|
|
12
|
+
reasoningEffort: string;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Settings schema for `$DSH_HOME/settings.yaml` under ssh-tui-routes.
|
|
18
|
+
*
|
|
19
|
+
* The type argument is spelled out rather than inferred: `schemastery`'s
|
|
20
|
+
* builder names its own package internally, so an inferred schema makes `tsc`
|
|
21
|
+
* emit a declaration that references a path inside `node_modules` and fails
|
|
22
|
+
* with TS2742 on any layout that does not match this machine's.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ROUTE_MEMORY_SCHEMA: z<RouteMemorySettings>;
|
|
30
25
|
export interface RememberedRoute {
|
|
31
26
|
model: string;
|
|
32
27
|
reasoningEffort?: string;
|
|
@@ -10,10 +10,15 @@ export declare function formatFooterCwd(cwd: string): string;
|
|
|
10
10
|
/**
|
|
11
11
|
* Switch the process into a persisted session working directory. Returns the
|
|
12
12
|
* directory actually used; missing/invalid paths stay put and are reported.
|
|
13
|
+
*
|
|
14
|
+
* The target must be an absolute directory. A regular file that happens to
|
|
15
|
+
* exist at the recorded path used to pass `existsSync` and then fail inside
|
|
16
|
+
* `chdir` with a platform errno; resume now refuses it before moving.
|
|
13
17
|
*/
|
|
14
18
|
export declare function enterSessionCwd(cwd: string | undefined, options?: {
|
|
15
19
|
current?: string;
|
|
16
20
|
exists?: (path: string) => boolean;
|
|
21
|
+
isDirectory?: (path: string) => boolean;
|
|
17
22
|
chdir?: (path: string) => void;
|
|
18
23
|
}): {
|
|
19
24
|
cwd: string;
|
|
@@ -21,6 +21,15 @@ export declare class SessionLockHeldError extends Error {
|
|
|
21
21
|
constructor(lock: SessionLockInfo, path: string);
|
|
22
22
|
}
|
|
23
23
|
export declare function sessionLockPath(sessionId: string, dshHome?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Pre-digest lock path (`tui-locks/<safeId>.json`).
|
|
26
|
+
*
|
|
27
|
+
* 0.7.1 Hosts still write here. Lookup must find that file; a new Host must
|
|
28
|
+
* not bind a second lock beside a live one just because the filename changed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function legacySessionLockPath(sessionId: string, dshHome?: string): string;
|
|
31
|
+
/** Digested lock first, then the 0.7.1 name when it is different. */
|
|
32
|
+
export declare function sessionLockLookupPaths(sessionId: string, dshHome?: string): string[];
|
|
24
33
|
export declare function parseSessionLock(raw: string): SessionLockInfo | undefined;
|
|
25
34
|
/** True when `pid` still exists on this machine (best-effort). */
|
|
26
35
|
export declare function processIsAlive(pid: number): boolean;
|