dsh-code 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/index.mjs +5115 -4390
- package/lib/types/app.d.ts +18 -51
- package/lib/types/completion.d.ts +29 -0
- package/lib/types/composer.d.ts +150 -0
- package/lib/types/git-workflow.d.ts +6 -0
- package/lib/types/index.d.ts +6 -188
- package/lib/types/locales/en.d.ts +16 -4
- package/lib/types/{authorization-panel.d.ts → panels/authorization-panel.d.ts} +6 -1
- package/lib/types/panels/completion-panel.d.ts +13 -0
- package/lib/types/panels/interaction-bars.d.ts +38 -0
- package/lib/types/{kernel-panels.d.ts → panels/kernel-panels.d.ts} +16 -37
- package/lib/types/{language-panel.d.ts → panels/language-panel.d.ts} +1 -1
- package/lib/types/panels/model-panels.d.ts +86 -0
- package/lib/types/{theme-panel.d.ts → panels/theme-panel.d.ts} +1 -1
- package/lib/types/{update-panel.d.ts → panels/update-panel.d.ts} +1 -1
- package/lib/types/provider-settings.d.ts +11 -0
- package/lib/types/render/inspector.d.ts +8 -0
- package/lib/types/render/text.d.ts +4 -0
- package/lib/types/runner/harness-gate.d.ts +83 -0
- package/lib/types/runner/input-history.d.ts +31 -0
- package/lib/types/runner/mode-cycle.d.ts +44 -0
- package/lib/types/runner/preferences.d.ts +40 -0
- package/lib/types/runner/quit.d.ts +27 -0
- package/lib/types/runner/search-rows.d.ts +38 -0
- package/lib/types/runner/session-io.d.ts +46 -0
- package/lib/types/runner/session-target.d.ts +42 -0
- package/lib/types/runner/startup-config.d.ts +33 -0
- package/lib/types/runner/submissions.d.ts +87 -0
- package/lib/types/session/attach.d.ts +39 -0
- package/lib/types/{store.d.ts → session/store.d.ts} +1 -1
- package/lib/types/{subagents.d.ts → session/subagents.d.ts} +8 -1
- package/lib/types/settings-file.d.ts +10 -0
- package/lib/types/{panel-accent.d.ts → ui/panel-accent.d.ts} +1 -1
- package/lib/types/ui/panel-gap.d.ts +6 -0
- package/lib/types/ui/query-editor.d.ts +10 -0
- package/lib/types/ui/styled-rows.d.ts +8 -0
- package/lib/types/{terminal-title.d.ts → ui/terminal-title.d.ts} +1 -1
- package/lib/types/ui/ui-contract.d.ts +12 -0
- package/lib/types/ui/use-frames.d.ts +6 -0
- package/lib/types/ui/use-stable-input.d.ts +7 -0
- package/package.json +1 -1
- package/src/app.ts +543 -4066
- package/src/completion.ts +117 -0
- package/src/composer.ts +1956 -0
- package/src/git-workflow.ts +18 -0
- package/src/index.ts +113 -633
- package/src/internals.ts +1 -1
- package/src/locales/en.ts +16 -4
- package/src/locales/zh.ts +16 -4
- package/src/{authorization-panel.ts → panels/authorization-panel.ts} +25 -6
- package/src/panels/completion-panel.ts +79 -0
- package/src/panels/interaction-bars.ts +567 -0
- package/src/{kernel-panels.ts → panels/kernel-panels.ts} +84 -65
- package/src/{language-panel.ts → panels/language-panel.ts} +4 -4
- package/src/panels/model-panels.ts +1021 -0
- package/src/{theme-panel.ts → panels/theme-panel.ts} +5 -5
- package/src/{update-panel.ts → panels/update-panel.ts} +6 -6
- package/src/provider-settings.ts +38 -0
- package/src/render/inspector.ts +23 -0
- package/src/render/text.ts +8 -0
- package/src/runner/harness-gate.ts +168 -0
- package/src/runner/input-history.ts +77 -0
- package/src/runner/mode-cycle.ts +49 -0
- package/src/runner/preferences.ts +67 -0
- package/src/runner/quit.ts +53 -0
- package/src/runner/search-rows.ts +55 -0
- package/src/runner/session-io.ts +206 -0
- package/src/runner/session-target.ts +81 -0
- package/src/runner/startup-config.ts +54 -0
- package/src/runner/submissions.ts +157 -0
- package/src/session/attach.ts +87 -0
- package/src/{session-directory.ts → session/session-directory.ts} +1 -1
- package/src/{store.ts → session/store.ts} +1 -1
- package/src/{subagents.ts → session/subagents.ts} +12 -1
- package/src/settings-file.ts +19 -1
- package/src/{panel-accent.ts → ui/panel-accent.ts} +1 -1
- package/src/ui/panel-gap.ts +9 -0
- package/src/ui/query-editor.ts +16 -0
- package/src/ui/styled-rows.ts +124 -0
- package/src/{terminal-title.ts → ui/terminal-title.ts} +1 -1
- package/src/ui/ui-contract.ts +10 -0
- package/src/ui/use-frames.ts +23 -0
- package/src/ui/use-stable-input.ts +17 -0
- /package/lib/types/{fork.d.ts → session/fork.d.ts} +0 -0
- /package/lib/types/{history.d.ts → session/history.d.ts} +0 -0
- /package/lib/types/{session-directory.d.ts → session/session-directory.d.ts} +0 -0
- /package/lib/types/{session-switch.d.ts → session/session-switch.d.ts} +0 -0
- /package/src/{fork.ts → session/fork.ts} +0 -0
- /package/src/{history.ts → session/history.ts} +0 -0
- /package/src/{session-switch.ts → session/session-switch.ts} +0 -0
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
2
2
|
import { type ReactElement } from 'react';
|
|
3
|
-
import type { ModelDirectory, ModelRow } from '
|
|
4
|
-
import type { SubagentRow } from '
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* markers are stripped and control-laden chunks are ignored.
|
|
17
|
-
*/
|
|
18
|
-
export declare function editQuery(query: string, input: string, key: {
|
|
19
|
-
backspace?: boolean;
|
|
20
|
-
delete?: boolean;
|
|
21
|
-
}): string | undefined;
|
|
3
|
+
import type { ModelDirectory, ModelRow } from '../models.ts';
|
|
4
|
+
import type { SubagentRow } from '../session/subagents.ts';
|
|
5
|
+
import type { SearchRow } from '../runner/search-rows.ts';
|
|
6
|
+
export type { SearchRow } from '../runner/search-rows.ts';
|
|
7
|
+
import type { ScheduleRow } from '../render/projection.ts';
|
|
8
|
+
import type { PermissionRow } from '../permissions.ts';
|
|
9
|
+
import type { PresetRow } from '../presets.ts';
|
|
10
|
+
import type { PluginRow } from '../plugin-inventory.ts';
|
|
11
|
+
import type { SessionDirectoryOptions, SessionRow } from '../session/session-directory.ts';
|
|
12
|
+
import type { ReviewBranch, ReviewCommit, ReviewSelection } from '../git-workflow.ts';
|
|
13
|
+
import { type UsageView } from '../render/usage.ts';
|
|
14
|
+
import { type StatusItemId } from '../render/status.ts';
|
|
15
|
+
export { editQuery } from '../ui/query-editor.ts';
|
|
22
16
|
export declare function ModePanel({ current, load, select, close }: {
|
|
23
17
|
current: string;
|
|
24
18
|
load: () => Promise<readonly PresetRow[]>;
|
|
@@ -110,23 +104,6 @@ export declare function ReviewPickerPanel({ loadBranches, loadCommits, choose, c
|
|
|
110
104
|
choose: (selection: ReviewSelection) => void;
|
|
111
105
|
close: () => void;
|
|
112
106
|
}): ReactElement;
|
|
113
|
-
/** One cross-session full-text search hit mapped from the session-query engine. */
|
|
114
|
-
export interface SearchRow {
|
|
115
|
-
/** Session id (Enter resumes it through the switch machinery). */
|
|
116
|
-
readonly id: string;
|
|
117
|
-
/** Display label: session title or the short id form. */
|
|
118
|
-
readonly label: string;
|
|
119
|
-
/** Secondary facts line (workspace · preset markers). */
|
|
120
|
-
readonly detail: string;
|
|
121
|
-
/** Bounded plain-text excerpt around the strongest match. */
|
|
122
|
-
readonly snippet: string;
|
|
123
|
-
/** Match timestamp (relative labels derive from it). */
|
|
124
|
-
readonly updatedAt: number;
|
|
125
|
-
/** Whether the hit is a delegated subagent conversation (not resumable). */
|
|
126
|
-
readonly subagent: boolean;
|
|
127
|
-
/** Whether Enter may switch into it. */
|
|
128
|
-
readonly resumable: boolean;
|
|
129
|
-
}
|
|
130
107
|
/**
|
|
131
108
|
* The /search panel: full-text search over every persisted session through
|
|
132
109
|
* the in-process session-query engine (the same corpus the model's
|
|
@@ -182,13 +159,15 @@ export declare function EffortPanel({ row, current, select, back, onExit }: {
|
|
|
182
159
|
* Enter/t opening the child's full transcript in the shared read-only
|
|
183
160
|
* document view (the same projection the exporter uses).
|
|
184
161
|
*/
|
|
185
|
-
export declare function AgentsPanel({ live, load, readTranscript, close }: {
|
|
162
|
+
export declare function AgentsPanel({ live, load, readTranscript, attach, close }: {
|
|
186
163
|
/** Live feed rows (child sessions observed this process). */
|
|
187
164
|
live: readonly SubagentRow[];
|
|
188
165
|
/** Load this session's persisted child sessions by lineage. */
|
|
189
166
|
load: () => Promise<readonly SessionRow[]>;
|
|
190
167
|
/** Read one child session's full transcript as markdown. */
|
|
191
168
|
readTranscript: (id: string, signal?: AbortSignal) => Promise<string>;
|
|
169
|
+
/** Attach to one child as the whole view (the runner's live buses). */
|
|
170
|
+
attach: ((id: string, label: string) => void) | undefined;
|
|
192
171
|
close: () => void;
|
|
193
172
|
}): ReactElement;
|
|
194
173
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* repainting the whole screen so Static-region text switches too).
|
|
5
5
|
*/
|
|
6
6
|
import { type ReactElement } from 'react';
|
|
7
|
-
import { type LanguageName } from '
|
|
7
|
+
import { type LanguageName } from '../i18n.ts';
|
|
8
8
|
export declare function LanguagePanel({ current, select, close }: {
|
|
9
9
|
current: LanguageName;
|
|
10
10
|
select: (name: LanguageName) => void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Model selection and provider-management panels for the terminal app. */
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
|
+
import type { ModelDirectory, ModelRow } from '../models.ts';
|
|
4
|
+
import { type DiscoveredModelView, type ProviderConfiguration, type ProviderSettingsDirectory, type ProviderTargetView } from '../provider-settings.ts';
|
|
5
|
+
import { type ProviderAuthorizationDirectory, type ProviderAuthorizationRow } from '../authorization.ts';
|
|
6
|
+
export declare function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry, onClose }: {
|
|
7
|
+
directory: ModelDirectory | undefined;
|
|
8
|
+
error: string | undefined;
|
|
9
|
+
/** `provider/model` label of the applied model: the cursor lands on it once. */
|
|
10
|
+
current?: string;
|
|
11
|
+
onSelect: (row: ModelRow) => void;
|
|
12
|
+
onProviders?: () => void;
|
|
13
|
+
onRetry: () => void;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
}): ReactElement;
|
|
16
|
+
/** The provider-management stage reached from /model with `a`. */
|
|
17
|
+
export declare function ProviderPanel({ directory, error, authorizations, authorizationError, onConfigure, onUnset, onRemove, onRetry, onBack, onExit }: {
|
|
18
|
+
directory: ProviderSettingsDirectory | undefined;
|
|
19
|
+
error: string | undefined;
|
|
20
|
+
authorizations: ProviderAuthorizationDirectory | undefined;
|
|
21
|
+
authorizationError: string | undefined;
|
|
22
|
+
onConfigure: (target: ProviderTargetView) => void;
|
|
23
|
+
onUnset: (target: ProviderTargetView) => void;
|
|
24
|
+
onRemove: (target: ProviderTargetView) => void;
|
|
25
|
+
onRetry: () => void;
|
|
26
|
+
onBack: () => void;
|
|
27
|
+
/** Leave the whole /model flow (Ctrl+C), not just this stage. */
|
|
28
|
+
onExit: () => void;
|
|
29
|
+
}): ReactElement;
|
|
30
|
+
/** Provider configuration editor: only explicit models are written to settings. */
|
|
31
|
+
/**
|
|
32
|
+
* The unified provider setup page: API key, endpoint, and the explicit model
|
|
33
|
+
* list (with per-model context/output capacities) on ONE screen — the deep
|
|
34
|
+
* Tab menu and the separate key panel merged into a single discoverable
|
|
35
|
+
* surface. A saved key rides the same Enter as the endpoint and models; an
|
|
36
|
+
* empty endpoint keeps the provider's official default. Tab moves to the
|
|
37
|
+
* discovery page, which interrogates the real endpoint and returns checkable
|
|
38
|
+
* models for adoption; the last row also accepts hand-typed model ids.
|
|
39
|
+
*/
|
|
40
|
+
/** One declarable donor the setup page can copy reasoning efforts from verbatim. */
|
|
41
|
+
export interface EffortDonor {
|
|
42
|
+
/** Provider route the declaration lives on. */
|
|
43
|
+
readonly provider: string;
|
|
44
|
+
/** Model id the declaration belongs to. */
|
|
45
|
+
readonly id: string;
|
|
46
|
+
/** The stored display-level to wire-value map, copied verbatim. */
|
|
47
|
+
readonly efforts: Record<string, string | null>;
|
|
48
|
+
}
|
|
49
|
+
export declare function ProviderSetupPanel({ target, authorization, onSubscribe, save, saveCredential, discover, effortDonors, done, back, onExit }: {
|
|
50
|
+
target: ProviderTargetView;
|
|
51
|
+
/**
|
|
52
|
+
* The provider's sign-in flow, when it offers a web (OAuth) channel: its
|
|
53
|
+
* presence adds the subscription/key mode layer at the top of the page.
|
|
54
|
+
*/
|
|
55
|
+
authorization: ProviderAuthorizationRow | undefined;
|
|
56
|
+
/** Open the subscription channel: sign in when logged out, confirm sign-out when logged in. */
|
|
57
|
+
onSubscribe: (() => void) | undefined;
|
|
58
|
+
/** Models with declared efforts (settings first, catalog-advertised after) a model row can copy from. */
|
|
59
|
+
effortDonors: readonly EffortDonor[];
|
|
60
|
+
save: (target: ProviderTargetView, configuration: ProviderConfiguration) => Promise<void>;
|
|
61
|
+
saveCredential: ((target: ProviderTargetView, key: string) => Promise<void>) | undefined;
|
|
62
|
+
discover: (target: ProviderTargetView, request: {
|
|
63
|
+
readonly apiKey?: string;
|
|
64
|
+
readonly baseURL?: string;
|
|
65
|
+
}, signal?: AbortSignal) => Promise<readonly DiscoveredModelView[]>;
|
|
66
|
+
/** Report a successful save so the surface can notice the key rotation. */
|
|
67
|
+
done: (result: {
|
|
68
|
+
readonly key: boolean;
|
|
69
|
+
}) => void;
|
|
70
|
+
back: () => void;
|
|
71
|
+
/** Leave the whole /model flow (Ctrl+C), not just this page. */
|
|
72
|
+
onExit: () => void;
|
|
73
|
+
}): ReactElement;
|
|
74
|
+
/** Bounded destructive-action confirmation for credential or provider removal. */
|
|
75
|
+
export declare function ProviderConfirmPanel({ target, kind, confirm, done, back }: {
|
|
76
|
+
target: ProviderTargetView;
|
|
77
|
+
kind: 'credential' | 'provider';
|
|
78
|
+
confirm: (target: ProviderTargetView) => Promise<void>;
|
|
79
|
+
done: () => void;
|
|
80
|
+
back: () => void;
|
|
81
|
+
}): ReactElement;
|
|
82
|
+
/**
|
|
83
|
+
* The /help overlay: one scrolling card with the keyboard map, the TUI-local
|
|
84
|
+
* commands, the live registry commands, and the user-invocable skills — the
|
|
85
|
+
* real command surface, replacing the one-line notice.
|
|
86
|
+
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module @deepseek-ai/dsh-tui/theme-panel
|
|
8
8
|
*/
|
|
9
9
|
import { type ReactElement } from 'react';
|
|
10
|
-
import { type ThemeName } from '
|
|
10
|
+
import { type ThemeName } from '../theme.ts';
|
|
11
11
|
/**
|
|
12
12
|
* The /theme list: one row per theme, the current one marked with ●, the
|
|
13
13
|
* focused one with ›. Enter applies the focused theme (the runner persists
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* here.
|
|
10
10
|
*/
|
|
11
11
|
import { type ReactElement } from 'react';
|
|
12
|
-
import type { LauncherUpdatePlan, LauncherUpdateStatus } from '
|
|
12
|
+
import type { LauncherUpdatePlan, LauncherUpdateStatus } from '../update.ts';
|
|
13
13
|
/** Retained apply-progress lines (ring tail; npm output is ephemeral). */
|
|
14
14
|
export declare const UPDATE_OUTPUT_CAP = 800;
|
|
15
15
|
/** Keep the newest UPDATE_OUTPUT_CAP lines of streamed update output. */
|
|
@@ -197,6 +197,17 @@ export declare function loadProviderSettings(ctx: Context): Promise<ProviderSett
|
|
|
197
197
|
export declare function saveProviderCredential(ctx: Context, target: ProviderTargetView, rawKey: string): Promise<void>;
|
|
198
198
|
/** Save the endpoint and an explicit model allow-list without rebuilding the profile. */
|
|
199
199
|
export declare function saveProviderConfiguration(ctx: Context, target: ProviderTargetView, configuration: ProviderConfiguration): Promise<void>;
|
|
200
|
+
/**
|
|
201
|
+
* Switch one provider route to its subscription (plan sign-in) channel: the
|
|
202
|
+
* profile keeps neither a key reference nor an explicit model list, so the
|
|
203
|
+
* official catalog endpoint serves requests and the stored OAuth record — not
|
|
204
|
+
* a settings-named key override — authenticates them. Enabling a route that
|
|
205
|
+
* has no profile yet creates the empty profile that registers it.
|
|
206
|
+
* @param ctx - context carrying the `settings` service.
|
|
207
|
+
* @param target - provider row whose settings namespace owns the profile.
|
|
208
|
+
* @throws when the route is unmanaged or settings are read-only.
|
|
209
|
+
*/
|
|
210
|
+
export declare function enableProviderSubscription(ctx: Context, target: ProviderTargetView): Promise<void>;
|
|
200
211
|
/**
|
|
201
212
|
* Interrogate a provider endpoint for the models it really serves, through
|
|
202
213
|
* the model-discovery capability the provider's settings namespace
|
|
@@ -48,6 +48,14 @@ export declare function liveRegionBudget(chrome: LiveRegionChrome): number;
|
|
|
48
48
|
* accumulated `<Static>` output on every frame.
|
|
49
49
|
*/
|
|
50
50
|
export declare function panelViewport(columns: number, rows: number): InspectorViewport;
|
|
51
|
+
/**
|
|
52
|
+
* Viewport for an expanded (near-fullscreen) document overlay: the document
|
|
53
|
+
* replaces the transcript area entirely, so the half-screen overlay cap and
|
|
54
|
+
* the inter-section gaps drop away while the bottom live region (composer,
|
|
55
|
+
* status) and the Static-rewrite margin stay reserved. The result stays
|
|
56
|
+
* below terminal height, which is what keeps Ink off its full-rewrite path.
|
|
57
|
+
*/
|
|
58
|
+
export declare function expandedDocumentViewport(columns: number, rows: number): InspectorViewport;
|
|
51
59
|
/** Backward-compatible name for the Ctrl+O-specific caller and tests. */
|
|
52
60
|
export declare function inspectorViewport(columns: number, rows: number): InspectorViewport;
|
|
53
61
|
/** Clamp a first-visible row to the range representable by one viewport. */
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module @deepseek-ai/dsh-code/render/text
|
|
13
13
|
*/
|
|
14
|
+
import { stringWidth } from './width.ts';
|
|
15
|
+
export { stringWidth };
|
|
14
16
|
/**
|
|
15
17
|
* Compact token count: exact below 1K, then one-decimal-ish K/M.
|
|
16
18
|
* @param n - token count.
|
|
@@ -35,6 +37,8 @@ export declare function singleLineText(text: string): string;
|
|
|
35
37
|
* combining sequences never split mid-cluster.
|
|
36
38
|
*/
|
|
37
39
|
export declare function truncateColumns(text: string, columns: number): string;
|
|
40
|
+
/** Sanitize, clip, and right-pad one cell to an exact terminal-column width. */
|
|
41
|
+
export declare function padColumns(text: string, columns: number): string;
|
|
38
42
|
/** A display-safe suffix bounded by terminal rows and columns. */
|
|
39
43
|
export interface DisplayTail {
|
|
40
44
|
/** Sanitized suffix suitable for direct terminal rendering. */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load-time DeepSeek Harness version gate.
|
|
3
|
+
*
|
|
4
|
+
* The terminal driver is validated against exactly one Harness snapshot, and
|
|
5
|
+
* the host resolves every bare `@deepseek-ai/*` import the plugin makes
|
|
6
|
+
* against ITS OWN installed copies (the profile's `HostResolvedRootInclude`
|
|
7
|
+
* redirects bare names to the installed-host base). No install-time or
|
|
8
|
+
* load-time check exists anywhere on that path, so a user who updates `dsh`
|
|
9
|
+
* would otherwise run this build against unvetted upstream code silently.
|
|
10
|
+
*
|
|
11
|
+
* This gate mirrors how the host itself reads its identity: the running CLI
|
|
12
|
+
* entry (`process.argv[1]`) sits inside the `@deepseek-ai/dsh` package, whose
|
|
13
|
+
* manifest version is released in lockstep with the Harness packages bundled
|
|
14
|
+
* beside it. When the probe can identify the host it compares the running
|
|
15
|
+
* versions against the one this build declares and fails fast with an
|
|
16
|
+
* actionable message; when it cannot identify the host it stays silent rather
|
|
17
|
+
* than brick a supported embedding on a false positive.
|
|
18
|
+
*
|
|
19
|
+
* @module @deepseek-ai/dsh-code/runner/harness-gate
|
|
20
|
+
*/
|
|
21
|
+
/** The one Harness release this build was validated against. */
|
|
22
|
+
export declare const EXPECTED_HARNESS_VERSION = "0.1.5-rc.2";
|
|
23
|
+
/** Host packages whose bundled copy the plugin binds to at runtime. */
|
|
24
|
+
export declare const HARNESS_GATE_PACKAGES: readonly ["@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-session"];
|
|
25
|
+
/** Filesystem slice the probe needs; injectable so tests stay pure. */
|
|
26
|
+
export interface HarnessFs {
|
|
27
|
+
/** Resolve a symlinked entry to its real path. */
|
|
28
|
+
realpathSync(path: string): string;
|
|
29
|
+
/** Read and parse one package.json; undefined when absent or unreadable. */
|
|
30
|
+
readManifest(path: string): {
|
|
31
|
+
name?: unknown;
|
|
32
|
+
version?: unknown;
|
|
33
|
+
} | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** What the probe learned about the running host. */
|
|
36
|
+
export interface HarnessProbe {
|
|
37
|
+
/** Installed-host package root, when identified. */
|
|
38
|
+
readonly hostRoot?: string;
|
|
39
|
+
/** Installed-host CLI version (`@deepseek-ai/dsh`). */
|
|
40
|
+
readonly hostVersion?: string;
|
|
41
|
+
/** Version of each gate package bundled beside the host, when present. */
|
|
42
|
+
readonly packages: Readonly<Record<string, string | undefined>>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Identify the running host and the Harness versions it provides.
|
|
46
|
+
* @param argv1 - `process.argv[1]`, the running CLI entry path.
|
|
47
|
+
* @param fs - filesystem slice; defaults to the real one.
|
|
48
|
+
* @returns the probe result; `packages` is empty when the host is unknown.
|
|
49
|
+
*/
|
|
50
|
+
export declare function probeRunningHarness(argv1: string | undefined, fs?: HarnessFs): HarnessProbe;
|
|
51
|
+
/** One host fact that disagrees with the version this build declares. */
|
|
52
|
+
export interface HarnessMismatch {
|
|
53
|
+
/** What was compared: the host CLI or one bundled package. */
|
|
54
|
+
readonly source: string;
|
|
55
|
+
/** The version this build was validated against. */
|
|
56
|
+
readonly expected: string;
|
|
57
|
+
/** The version the running host actually provides. */
|
|
58
|
+
readonly provided: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compare one probe against the expected snapshot. Unknown facts never
|
|
62
|
+
* mismatch: the gate must stay silent when it cannot judge, not brick a
|
|
63
|
+
* supported embedding on missing evidence.
|
|
64
|
+
* @param expected - the Harness version this build declares.
|
|
65
|
+
* @param probe - the running host's identified versions.
|
|
66
|
+
* @returns every concrete disagreement, empty when compatible or unknown.
|
|
67
|
+
*/
|
|
68
|
+
export declare function harnessVersionMismatches(expected: string, probe: HarnessProbe): readonly HarnessMismatch[];
|
|
69
|
+
/**
|
|
70
|
+
* Render the gate failure the user sees on stderr.
|
|
71
|
+
* @param expected - the Harness version this build declares.
|
|
72
|
+
* @param mismatches - the disagreements to report.
|
|
73
|
+
* @param hostRoot - installed-host root, when identified, for the fix hint.
|
|
74
|
+
* @returns the multi-line diagnostic.
|
|
75
|
+
*/
|
|
76
|
+
export declare function harnessGateMessage(expected: string, mismatches: readonly HarnessMismatch[], hostRoot?: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Refuse to run against an identified-but-incompatible host.
|
|
79
|
+
* @param expected - the Harness version this build declares.
|
|
80
|
+
* @param probe - the running host's identified versions.
|
|
81
|
+
* @throws an Error carrying the gate diagnostic when a version disagrees.
|
|
82
|
+
*/
|
|
83
|
+
export declare function requireHarnessVersion(expected: string, probe: HarnessProbe): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Global input recall: the appended prompts and their durable JSONL file.
|
|
2
|
+
*
|
|
3
|
+
* One JSONL file under the DSH home. A missing file means an empty history and
|
|
4
|
+
* unreadable or corrupt content degrades to the valid lines it could parse,
|
|
5
|
+
* silently — recall is a convenience surface, never a gate.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/input-history
|
|
8
|
+
*/
|
|
9
|
+
/** Live recall state plus its serialized durable writes. */
|
|
10
|
+
export interface InputHistoryStore {
|
|
11
|
+
/** The recall list, newest last; read live by the composer. */
|
|
12
|
+
readonly entries: () => readonly string[];
|
|
13
|
+
/** Append one submitted line and persist it; '' is ignored. */
|
|
14
|
+
readonly record: (text: string) => void;
|
|
15
|
+
/** Await the pending writes (the quit flush). */
|
|
16
|
+
readonly flush: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load the recall history and return its live store.
|
|
20
|
+
*
|
|
21
|
+
* Serialized history writes: each submission appends one JSON line at the end
|
|
22
|
+
* of the file, so concurrent terminals add entries after each other instead of
|
|
23
|
+
* overwriting snapshots they read at their own boot. A multi-line draft still
|
|
24
|
+
* occupies one physical line (JSON escapes the newline), and a regular-length
|
|
25
|
+
* line reaches the disk as one positioned write; an oversized paste may
|
|
26
|
+
* interleave mid-line, which the next parse simply drops.
|
|
27
|
+
*
|
|
28
|
+
* @param path - absolute path of the JSONL recall file.
|
|
29
|
+
* @param onFailure - receives the write failure message for a bounded notice.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createInputHistory(path: string, onFailure: (message: string) => void): InputHistoryStore;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Shift+Tab mode-cycle stations for the terminal runner.
|
|
2
|
+
*
|
|
3
|
+
* Pure decision over the preset table and the committed plan fold: the runner
|
|
4
|
+
* applies the returned station, so the whole cycle is testable without a
|
|
5
|
+
* session.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/mode-cycle
|
|
8
|
+
*/
|
|
9
|
+
/** One Shift+Tab station decision for the mode cycle. */
|
|
10
|
+
export type ModeCycleDecision = {
|
|
11
|
+
readonly kind: 'permission';
|
|
12
|
+
readonly preset: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'plan-on';
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'plan-off';
|
|
17
|
+
readonly preset: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Decide the next Shift+Tab station. The cycle keeps the preset table's
|
|
21
|
+
* own order (most restrictive first) and inserts ONE plan station between
|
|
22
|
+
* the most restrictive preset and the wrap target: with the shipped three
|
|
23
|
+
* presets the user sees workspace-write → danger-full-access → read-only
|
|
24
|
+
* → plan → workspace-write. Plan IS the most restrictive preset plus the
|
|
25
|
+
* plan prompt layer — entering it switches nothing (the cycle is already
|
|
26
|
+
* parked on read-only), and leaving it lands on the next preset after the
|
|
27
|
+
* most restrictive one. Without the /plan command the cycle is exactly the
|
|
28
|
+
* preset table.
|
|
29
|
+
*
|
|
30
|
+
* `planIntent` covers the committed fold's commit lag: upstream queues a
|
|
31
|
+
* plan switch during an open turn (and the command pipeline is async even
|
|
32
|
+
* idle), so the durable plan/mode event lands AFTER the press that chose
|
|
33
|
+
* it. While an intent from an earlier press is in flight it — not the
|
|
34
|
+
* stale committed fold — decides the station, so repeated presses advance
|
|
35
|
+
* the cycle instead of re-issuing the same plan transition (the stuck
|
|
36
|
+
* plan-on/plan-off toggle). Undefined falls back to the committed fold.
|
|
37
|
+
*/
|
|
38
|
+
export declare function planCycleDecision(input: {
|
|
39
|
+
readonly names: readonly string[];
|
|
40
|
+
readonly current: string;
|
|
41
|
+
readonly inPlan: boolean;
|
|
42
|
+
readonly planAvailable: boolean;
|
|
43
|
+
readonly planIntent?: boolean;
|
|
44
|
+
}): ModeCycleDecision | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** User-level preference files under the DSH home.
|
|
2
|
+
*
|
|
3
|
+
* The statusline, theme, language, and animations customizations each live in
|
|
4
|
+
* one small JSON file. They share exactly two policies: a missing file is the
|
|
5
|
+
* default and stays silent while a corrupt one degrades to the default with a
|
|
6
|
+
* surfaced warning (a user-authored customization must never fail silently),
|
|
7
|
+
* and every save goes through the serialized crash-atomic writer.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/preferences
|
|
10
|
+
*/
|
|
11
|
+
import { type UserSettingsPersistence } from '../settings-file.ts';
|
|
12
|
+
/** Absolute path of one preference file under the DSH home. */
|
|
13
|
+
export declare function preferencePath(fileName: string): string;
|
|
14
|
+
/** Outcome of reading one preference file. */
|
|
15
|
+
export interface PreferenceRead<T> {
|
|
16
|
+
/** Parsed value; undefined when the file is missing or corrupt (keep the default). */
|
|
17
|
+
readonly value?: T;
|
|
18
|
+
/** Corruption message to surface; undefined for a missing or clean file. */
|
|
19
|
+
readonly warning?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read one field of a preference file. A missing file is silence — the default
|
|
23
|
+
* stands and nothing is reported — while unreadable JSON or a non-object
|
|
24
|
+
* document both warn and keep the default.
|
|
25
|
+
* @param path - absolute path of the preference file.
|
|
26
|
+
* @param field - the JSON field the file carries.
|
|
27
|
+
* @param parse - narrows the raw field to the usable value.
|
|
28
|
+
* @returns the parsed value and/or the warning to surface.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readPreference<T>(path: string, field: string, parse: (raw: unknown) => T): PreferenceRead<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Persist one preference field through the serialized crash-atomic writer.
|
|
33
|
+
* A failed write is reported through `onFailure` instead of rejecting.
|
|
34
|
+
* @param persistence - the shared user-settings writer.
|
|
35
|
+
* @param path - absolute path of the preference file.
|
|
36
|
+
* @param field - the JSON field the file carries.
|
|
37
|
+
* @param value - the value to store under `field`.
|
|
38
|
+
* @param onFailure - receives the failure message for a bounded notice.
|
|
39
|
+
*/
|
|
40
|
+
export declare function savePreference(persistence: UserSettingsPersistence, path: string, field: string, value: unknown, onFailure: (message: string) => void): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Ordered terminal quit cleanup.
|
|
2
|
+
*
|
|
3
|
+
* Pure sequencing: every step rejection is contained and the exit request is
|
|
4
|
+
* always reached exactly once, so a failing flush never strands the process.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-code/quit
|
|
7
|
+
*/
|
|
8
|
+
/** One ordered step of the terminal quit cleanup. */
|
|
9
|
+
export interface QuitCleanupStep {
|
|
10
|
+
/** Step label used in diagnostics and tests. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** The step's async work; a rejection is contained by the sequence. */
|
|
13
|
+
readonly run: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run the ordered quit cleanup, then request exit. Every step rejection is
|
|
17
|
+
* contained (reported through `onError`) so a failed flush or dispose never
|
|
18
|
+
* skips the remaining cleanup; the exit request is always reached exactly
|
|
19
|
+
* once.
|
|
20
|
+
* @param steps - the cleanup steps in dependency order (settle the visible
|
|
21
|
+
* session, await the final in-flight composition, await durable recall).
|
|
22
|
+
* @param exit - the terminal exit request (code 0).
|
|
23
|
+
* @param onError - optional failure sink; called once per failing step and
|
|
24
|
+
* itself contained, so a throwing sink cannot abort the sequence.
|
|
25
|
+
* @returns the names of the steps that started, in order (for tests).
|
|
26
|
+
*/
|
|
27
|
+
export declare function runQuitSequence(steps: readonly QuitCleanupStep[], exit: (code: number) => void, onError?: (name: string, error: unknown) => void): Promise<readonly string[]>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Cross-session search rows for the /search panel.
|
|
2
|
+
*
|
|
3
|
+
* Owns the panel row shape and the pure mapping from a session-query hit, so
|
|
4
|
+
* the row contract does not live inside the panel component that renders it
|
|
5
|
+
* and the runner can build rows without importing the panel module.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/search-rows
|
|
8
|
+
*/
|
|
9
|
+
import type { SessionHeader } from '@deepseek-ai/dsh-session';
|
|
10
|
+
/** One cross-session full-text search hit mapped from the session-query engine. */
|
|
11
|
+
export interface SearchRow {
|
|
12
|
+
/** Session id (Enter resumes it through the switch machinery). */
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/** Display label: session title or the short id form. */
|
|
15
|
+
readonly label: string;
|
|
16
|
+
/** Secondary facts line (workspace · preset markers). */
|
|
17
|
+
readonly detail: string;
|
|
18
|
+
/** Bounded plain-text excerpt around the strongest match. */
|
|
19
|
+
readonly snippet: string;
|
|
20
|
+
/** Match timestamp (relative labels derive from it). */
|
|
21
|
+
readonly updatedAt: number;
|
|
22
|
+
/** Whether the hit is a delegated subagent conversation (not resumable). */
|
|
23
|
+
readonly subagent: boolean;
|
|
24
|
+
/** Whether Enter may switch into it. */
|
|
25
|
+
readonly resumable: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Map one cross-session full-text hit onto the /search panel's row (pure).
|
|
29
|
+
* Labels fall back to the short id form — the engine's hit carries the
|
|
30
|
+
* strongest matching event, not the title observation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function searchHitToRow(hit: {
|
|
33
|
+
header: SessionHeader;
|
|
34
|
+
bestMatch: {
|
|
35
|
+
snippet: string;
|
|
36
|
+
time: number;
|
|
37
|
+
};
|
|
38
|
+
}): SearchRow;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Session directory IO exposed to the picker, /delete, and /export.
|
|
2
|
+
*
|
|
3
|
+
* The kernel persistence seam has NO deletion API by design — logs accumulate
|
|
4
|
+
* "until removed externally" — so removal is planned, layout-checked, and
|
|
5
|
+
* lease-guarded here before any file is touched. Every read goes through the
|
|
6
|
+
* in-process session-query engine, so this module stays free of `app.ts` and
|
|
7
|
+
* of the Ink tree.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/runner/session-io
|
|
10
|
+
*/
|
|
11
|
+
import { type SessionDeletionPersistence, type SessionDirectoryOptions, type SessionQueryService, type SessionRow } from '../session/session-directory.ts';
|
|
12
|
+
/**
|
|
13
|
+
* The persistence surface this module needs: the JSONL backend's public
|
|
14
|
+
* session root plus its per-session write handles. Typing the narrow shape
|
|
15
|
+
* instead of the upstream service keeps the IO injectable from a test double.
|
|
16
|
+
*/
|
|
17
|
+
export interface SessionIoPersistence extends SessionDeletionPersistence {
|
|
18
|
+
/** JSONL backend plugin config carrying the session root, when exposed. */
|
|
19
|
+
readonly config?: {
|
|
20
|
+
readonly root?: unknown;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Services the session IO closes over. */
|
|
24
|
+
export interface SessionIoServices {
|
|
25
|
+
/** In-process session-query engine; absent in profiles without one. */
|
|
26
|
+
readonly sessionQuery?: SessionQueryService;
|
|
27
|
+
/** The durable persistence service; absent in profiles without one. */
|
|
28
|
+
readonly persistence?: SessionIoPersistence;
|
|
29
|
+
/** The session currently visible in the UI (self-deletion guard). */
|
|
30
|
+
readonly activeSessionId: () => string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Session directory reads and the guarded /delete operation. */
|
|
33
|
+
export interface SessionIo {
|
|
34
|
+
/** Project the session directory for the picker (query-filtered). */
|
|
35
|
+
readonly loadSessions: (options: SessionDirectoryOptions, signal?: AbortSignal) => Promise<readonly SessionRow[]>;
|
|
36
|
+
/** Delete one session subtree, returning the outcome line. */
|
|
37
|
+
readonly deleteSession: (id: string) => Promise<string>;
|
|
38
|
+
/** Render one session's whole transcript as export Markdown. */
|
|
39
|
+
readonly loadSessionTranscript: (id: string, signal?: AbortSignal) => Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bind the session IO to one runner's services.
|
|
43
|
+
* @param services - the query engine, persistence, and the live-session probe.
|
|
44
|
+
* @returns the picker/delete/export reads used by the app bridge.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createSessionIo(services: SessionIoServices): SessionIo;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Session identity resolution for one terminal runner invocation.
|
|
2
|
+
*
|
|
3
|
+
* Turns parsed startup flags into the session this process will run, and maps
|
|
4
|
+
* a session id onto a filename-safe export default. Resolution reads persisted
|
|
5
|
+
* headers, so it is the runner's only pre-composition persistence IO; the
|
|
6
|
+
* remaining helpers are pure.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-code/session-target
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionId, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
11
|
+
import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
|
|
12
|
+
import type { TuiStartup } from '../startup.ts';
|
|
13
|
+
/** The session identity this invocation will run, plus whether it is resumed. */
|
|
14
|
+
export interface Target {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
resume: boolean;
|
|
17
|
+
mode?: string;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
seed?: readonly SessionEvent[];
|
|
20
|
+
parentSession?: SessionId;
|
|
21
|
+
/** Marks the session as a subagent conversation in the durable header. */
|
|
22
|
+
origin?: 'subagent';
|
|
23
|
+
seedLength?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Reduce a session id to a filename-safe /export default-name suffix. Session
|
|
27
|
+
* ids are normally minted `session-<uuid>`, but `--session` accepts arbitrary
|
|
28
|
+
* user text: path separators must never leak into the default export filename
|
|
29
|
+
* (which would escape the session cwd).
|
|
30
|
+
* @param id - the session id.
|
|
31
|
+
* @returns at most the last 8 filename-safe characters.
|
|
32
|
+
*/
|
|
33
|
+
export declare function exportSessionIdSuffix(id: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the invocation's target session against the persisted headers.
|
|
36
|
+
* @param startup - the parsed startup flags.
|
|
37
|
+
* @param persistence - the persistence service; required for resume/latest.
|
|
38
|
+
* @param cwd - the working directory `--continue` filters by.
|
|
39
|
+
* @returns the target identity.
|
|
40
|
+
* @throws with a user-facing message when the flags name nothing resolvable.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveTarget(startup: TuiStartup, persistence: SessionPersistence | undefined, cwd: string): Promise<Target>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the runner's startup flags from the loose plugin config.
|
|
3
|
+
*
|
|
4
|
+
* The Cordis config schema is deliberately loose (`kind` is an unvalidated
|
|
5
|
+
* string, narrowed here), so this is the single place that maps a config row
|
|
6
|
+
* onto the `TuiStartup` the runner consumes — pure, and unit-testable without
|
|
7
|
+
* a plugin context.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/runner/startup-config
|
|
10
|
+
*/
|
|
11
|
+
import type { TuiStartup } from '../startup.ts';
|
|
12
|
+
/** The config row shape this resolver narrows; satisfied by the plugin schema. */
|
|
13
|
+
export interface StartupConfigRow {
|
|
14
|
+
/** How this invocation obtains its session identity. */
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
readonly sessionId?: string;
|
|
17
|
+
readonly mode?: string;
|
|
18
|
+
readonly theme?: string;
|
|
19
|
+
readonly prompt?: string;
|
|
20
|
+
readonly images?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Narrow one config row to a startup.
|
|
24
|
+
*
|
|
25
|
+
* `resume`/`named` without a session id degrade to a fresh launch, an invalid
|
|
26
|
+
* theme string narrows to the dark default (and is therefore still present),
|
|
27
|
+
* and `mode` only survives on the kinds that can pre-compose a session.
|
|
28
|
+
* @param config - the validated plugin config.
|
|
29
|
+
* @returns the startup the runner will resolve a target from.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveStartupConfig(config: {
|
|
32
|
+
readonly startup: StartupConfigRow;
|
|
33
|
+
}): TuiStartup;
|