dsh-code 1.2.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 +5 -5
- package/README.md +5 -5
- package/lib/index.mjs +6679 -5232
- package/lib/startup.mjs +1 -1
- package/lib/{theme-7u5Qo3dF.mjs → theme-B3orFUYz.mjs} +8 -0
- package/lib/types/app.d.ts +32 -45
- package/lib/types/attachments.d.ts +16 -7
- 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 +65 -5
- 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} +19 -38
- 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} +22 -1
- package/lib/types/provider-settings.d.ts +11 -0
- package/lib/types/render/inspector.d.ts +8 -0
- package/lib/types/render/status.d.ts +4 -1
- 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/{history.d.ts → session/history.d.ts} +10 -0
- package/lib/types/{session-directory.d.ts → session/session-directory.d.ts} +25 -1
- package/lib/types/{session-switch.d.ts → session/session-switch.d.ts} +8 -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/lib/types/version.d.ts +2 -0
- package/package.json +7 -5
- package/src/app.ts +707 -4108
- package/src/attachments.ts +65 -19
- package/src/completion.ts +117 -0
- package/src/composer.ts +1956 -0
- package/src/git-workflow.ts +18 -0
- package/src/index.ts +164 -645
- package/src/input-split.ts +24 -4
- package/src/internals.ts +1 -1
- package/src/locales/en.ts +66 -5
- package/src/locales/zh.ts +66 -5
- package/src/{authorization-panel.ts → panels/authorization-panel.ts} +30 -8
- 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} +142 -90
- 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} +117 -10
- package/src/provider-settings.ts +38 -0
- package/src/rainbow.ts +13 -3
- package/src/render/inspector.ts +23 -0
- package/src/render/status.ts +80 -29
- package/src/render/text.ts +10 -1
- 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/{fork.ts → session/fork.ts} +11 -7
- package/src/{history.ts → session/history.ts} +14 -0
- package/src/{session-directory.ts → session/session-directory.ts} +83 -4
- package/src/{session-switch.ts → session/session-switch.ts} +14 -0
- package/src/{store.ts → session/store.ts} +20 -2
- 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/src/version.ts +5 -0
- /package/lib/types/{fork.d.ts → session/fork.d.ts} +0 -0
|
@@ -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;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Submission vocabulary and the startup input gate for the terminal runner.
|
|
2
|
+
*
|
|
3
|
+
* Pure over the handles they are given: the queue mutations and the session
|
|
4
|
+
* guard are decisions the runner applies, and the gate only orders deliveries,
|
|
5
|
+
* so every branch is testable without composing an Agent.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/submissions
|
|
8
|
+
*/
|
|
9
|
+
import { type ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
10
|
+
import type { Agent, AgentStatus, Inbox } from '@deepseek-ai/dsh-agent';
|
|
11
|
+
import type { UserMessage } from '@deepseek-ai/dsh-session';
|
|
12
|
+
import type { QueueMutation } from '../ui/ui-contract.ts';
|
|
13
|
+
/** One composer submission waiting behind the startup delivery. */
|
|
14
|
+
export interface QueuedSubmission {
|
|
15
|
+
readonly text: string;
|
|
16
|
+
/** `steer` inserts into the running turn; `followup` waits for the next one. */
|
|
17
|
+
readonly mode: 'followup' | 'steer';
|
|
18
|
+
readonly images: readonly ContentBlock[];
|
|
19
|
+
}
|
|
20
|
+
/** What one requested queue mutation did; the runner maps it to one notice. */
|
|
21
|
+
export type QueueMutationOutcome = 'removed' | 'edited' | 'steered' | 'unavailable' | 'empty' | 'steerUnavailable';
|
|
22
|
+
/**
|
|
23
|
+
* Replace one queued message's text while keeping its attachments. A queue
|
|
24
|
+
* edit rewrites what the user typed, not what they attached: image and file
|
|
25
|
+
* blocks ride through in delivery order (text first, then attachments, the
|
|
26
|
+
* shape {@link deliverLine} submits). Dropping them here would silently strip
|
|
27
|
+
* an attachment the user already confirmed, so this is the edit's single
|
|
28
|
+
* definition and the panel's read-only marker only mirrors it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function queueEditContent(content: readonly ContentBlock[], text: string): ContentBlock[];
|
|
31
|
+
/**
|
|
32
|
+
* Apply one terminal queue mutation to the live inbox. The decision and the
|
|
33
|
+
* inbox change are pure over the supplied handles so every branch is testable
|
|
34
|
+
* without an agent; steering itself is injected because it wakes the driver
|
|
35
|
+
* rather than mutating the inbox. The durable inbox splices remain the UI's
|
|
36
|
+
* single source of truth — this helper never reports a state the inbox did not
|
|
37
|
+
* actually reach.
|
|
38
|
+
* @param inbox - the live agent inbox (pending lists plus its mutators).
|
|
39
|
+
* @param status - the agent's lifecycle status; steering needs `running`.
|
|
40
|
+
* @param messageId - identity of the queued message to mutate.
|
|
41
|
+
* @param action - the requested mutation.
|
|
42
|
+
* @param steer - submits the removed message as next-step steering.
|
|
43
|
+
* @returns the outcome the caller reports.
|
|
44
|
+
*/
|
|
45
|
+
export declare function applyQueueMutation(inbox: Pick<Inbox, 'nextTurn' | 'append' | 'remove' | 'replace'>, status: AgentStatus, messageId: string, action: QueueMutation, steer: (message: UserMessage) => void): QueueMutationOutcome;
|
|
46
|
+
/**
|
|
47
|
+
* Cancel the active turn while keeping the next-turn queue, then wake the
|
|
48
|
+
* driver again so the preserved messages actually run. `cancel` clears
|
|
49
|
+
* pending work by default and never wakes the driver on its own, so the queue
|
|
50
|
+
* is captured first and re-submitted afterwards: a waking submission latches
|
|
51
|
+
* the wake while the aborted activity converges to idle, which is what turns
|
|
52
|
+
* "preserved" into "sent next" instead of "parked forever". Next-step
|
|
53
|
+
* steering is deliberately dropped — it belonged to the cancelled turn.
|
|
54
|
+
* @param agent - the live agent handle.
|
|
55
|
+
* @returns how many queued messages were preserved across the abort.
|
|
56
|
+
*/
|
|
57
|
+
export declare function cancelPreservingQueue(agent: Pick<Agent, 'inbox' | 'cancel' | 'followup'>): number;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a tagged submission still belongs to the active session. Attachment
|
|
60
|
+
* prepares resolve on the microtask timeline, while a queued session switch
|
|
61
|
+
* remounts the app asynchronously — the composing instance's unmount cleanup
|
|
62
|
+
* runs too late to abort, so the delivery itself carries the composing
|
|
63
|
+
* session's full id and the runner drops it here when the world moved on.
|
|
64
|
+
* An untagged (synchronous) or pending-session ('') submission always passes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function submissionBelongsToSession(origin: string | undefined, activeSessionId: string | undefined): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Order-preserving gate for composer input while the startup prompt/images
|
|
69
|
+
* are still preparing. Anything submitted before the startup delivery settles
|
|
70
|
+
* queues and flushes afterwards in submit order, so the initial request can
|
|
71
|
+
* never be overtaken by typing that raced a slow image preparation. The flush
|
|
72
|
+
* also runs when the startup delivery fails: user input is never stranded.
|
|
73
|
+
*/
|
|
74
|
+
export declare class StartupInputGate {
|
|
75
|
+
private readonly deliver;
|
|
76
|
+
private readonly queued;
|
|
77
|
+
private pending;
|
|
78
|
+
constructor(deliver: (submission: QueuedSubmission) => void);
|
|
79
|
+
/** Submit one line: delivered now while idle, queued behind the startup delivery otherwise. */
|
|
80
|
+
submit(submission: QueuedSubmission): void;
|
|
81
|
+
/**
|
|
82
|
+
* Run the startup delivery — the callback receives the direct-delivery sink
|
|
83
|
+
* for the startup prompt itself — then flush everything that queued behind
|
|
84
|
+
* it, in order, even when the callback rejects.
|
|
85
|
+
*/
|
|
86
|
+
run(startup: (deliver: (submission: QueuedSubmission) => void) => Promise<void>): Promise<void>;
|
|
87
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One live attachment to a subagent conversation: a transcript store seeded
|
|
3
|
+
* from the child's durable log and then fed by the process-local event and
|
|
4
|
+
* stream buses in real time. The parent's own store is never touched.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-code/session/attach
|
|
7
|
+
*/
|
|
8
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
9
|
+
import { type TranscriptStore } from './store.ts';
|
|
10
|
+
/** The bus surfaces one attachment reads; the runner wires these to ctx. */
|
|
11
|
+
export interface SubagentAttachmentServices {
|
|
12
|
+
/** Seed: the child's full durable event log, oldest first. */
|
|
13
|
+
load(id: string, signal?: AbortSignal): Promise<readonly SessionEvent[]>;
|
|
14
|
+
/** Durable events for exactly this child session, live. */
|
|
15
|
+
subscribeEvents(id: string, onEvent: (event: SessionEvent) => void): () => void;
|
|
16
|
+
/** Token stream frames for exactly this child agent, live. */
|
|
17
|
+
subscribeStream(id: string, onFrame: (frame: Parameters<TranscriptStore['applyStreamFrame']>[0]) => void): () => void;
|
|
18
|
+
}
|
|
19
|
+
/** A live child view handed to the App while attached. */
|
|
20
|
+
export interface SubagentAttachment {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly store: TranscriptStore;
|
|
24
|
+
/** Whether the durable seed has landed (frames are held back until then). */
|
|
25
|
+
seeded(): boolean;
|
|
26
|
+
/** Drop the bus subscriptions; the store freezes at its last state. */
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
/** An inert empty store keeps the App's external-store hook unconditional. */
|
|
30
|
+
export declare const EMPTY_ATTACH_STORE: TranscriptStore;
|
|
31
|
+
/**
|
|
32
|
+
* Attach to one subagent conversation.
|
|
33
|
+
*
|
|
34
|
+
* Ordering contract: durable events carry a per-session sequence, so the seed
|
|
35
|
+
* sets a watermark and live events at or below it are dropped as duplicates;
|
|
36
|
+
* stream frames are held until the seed lands so a frame whose settlement
|
|
37
|
+
* already arrived cannot resurrect a stale tail.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createSubagentAttachment(services: SubagentAttachmentServices, id: string, label: string): SubagentAttachment;
|
|
@@ -78,6 +78,16 @@ export interface RecallState {
|
|
|
78
78
|
}
|
|
79
79
|
/** Fresh navigation state over one recall space. */
|
|
80
80
|
export declare function beginRecall(entries: readonly string[], draft: string): RecallState;
|
|
81
|
+
/**
|
|
82
|
+
* Join a panel-recalled entry onto the draft already in the composer: the
|
|
83
|
+
* draft is extended, never replaced, so picking a history row cannot discard
|
|
84
|
+
* work in progress. An empty draft takes the entry as-is; otherwise the entry
|
|
85
|
+
* starts on its own line unless the draft already ends one.
|
|
86
|
+
* @param draft - the composer text before the recall.
|
|
87
|
+
* @param entry - the sanitized text of the accepted row.
|
|
88
|
+
* @returns the text to place in the composer.
|
|
89
|
+
*/
|
|
90
|
+
export declare function appendRecall(draft: string, entry: string): string;
|
|
81
91
|
/** The outcome of one recall step. */
|
|
82
92
|
export interface RecallStep {
|
|
83
93
|
state: RecallState;
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/** Lightweight session-directory projection for the /resume picker. */
|
|
2
|
-
import { type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import { SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session';
|
|
3
3
|
export interface SessionRecord {
|
|
4
4
|
readonly header: SessionHeader;
|
|
5
5
|
readonly live: boolean;
|
|
6
6
|
readonly persisted: boolean;
|
|
7
7
|
}
|
|
8
|
+
/** Minimal write handle retained while a planned deletion touches artifacts. */
|
|
9
|
+
export interface SessionDeletionLease {
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** Public persistence operation used to acquire the backend's write lease. */
|
|
13
|
+
export interface SessionDeletionPersistence {
|
|
14
|
+
open(id: SessionId, access: 'write'): Promise<SessionDeletionLease>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Acquire every subtree member's cross-process write lease before deleting
|
|
18
|
+
* any artifact. A partial acquisition is rolled back, so callers either hold
|
|
19
|
+
* the whole deletion boundary or touch nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function acquireSessionDeletionLeases(persistence: SessionDeletionPersistence, ids: readonly string[]): Promise<readonly SessionDeletionLease[]>;
|
|
22
|
+
/** Release deletion leases in reverse acquisition order. */
|
|
23
|
+
export declare function releaseSessionDeletionLeases(leases: readonly SessionDeletionLease[]): Promise<void>;
|
|
8
24
|
export interface TitleObservationResult {
|
|
9
25
|
readonly sessionId: string;
|
|
10
26
|
readonly status: 'fulfilled' | 'rejected';
|
|
@@ -78,6 +94,12 @@ export declare function isSubagentSession(header: SessionHeader): boolean;
|
|
|
78
94
|
* @throws when nothing matches or the prefix is ambiguous.
|
|
79
95
|
*/
|
|
80
96
|
export declare function matchSessionId(headers: readonly SessionHeader[], wanted: string): SessionHeader;
|
|
97
|
+
/**
|
|
98
|
+
* Unique picker-row match by exact id, unique prefix, or unique suffix.
|
|
99
|
+
* The resume list shows `id.slice(-12)`, so `/delete` arguments are often
|
|
100
|
+
* that tail rather than a leading prefix.
|
|
101
|
+
*/
|
|
102
|
+
export declare function matchSessionRow(rows: readonly SessionRow[], wanted: string): SessionRow;
|
|
81
103
|
/** The newest persisted ROOT session pinned to this cwd, or undefined. */
|
|
82
104
|
export declare function newestRootForCwd(headers: readonly SessionHeader[], cwd: string): SessionHeader | undefined;
|
|
83
105
|
/**
|
|
@@ -90,6 +112,8 @@ export declare function newestRootForCwd(headers: readonly SessionHeader[], cwd:
|
|
|
90
112
|
* @param updated - per-session last-activity timestamps, when resolved.
|
|
91
113
|
*/
|
|
92
114
|
export declare function projectSessionRows(records: readonly SessionRecord[], options: SessionDirectoryOptions, updated?: ReadonlyMap<string, number>): SessionRow[];
|
|
115
|
+
/** True when the picker query hits id, path, preset, or the displayed title. */
|
|
116
|
+
export declare function sessionRowMatchesQuery(row: Pick<SessionRow, 'id' | 'cwd' | 'workspace' | 'preset' | 'title'>, query: string): boolean;
|
|
93
117
|
/** Merge page-local title observations without disturbing directory order. */
|
|
94
118
|
export declare function mergeSessionTitles(rows: readonly SessionRow[], observations: readonly TitleObservationResult[]): SessionRow[];
|
|
95
119
|
/**
|
|
@@ -8,10 +8,18 @@ export declare class SessionSwitchQueue<T> {
|
|
|
8
8
|
private readonly failed;
|
|
9
9
|
private pending;
|
|
10
10
|
private pumping;
|
|
11
|
+
private running;
|
|
11
12
|
constructor(execute: (value: T) => Promise<void>, failed: (error: unknown) => void);
|
|
12
13
|
/** Queue a request; a later request replaces any request still waiting. */
|
|
13
14
|
request(activity: IdleActivity, value: T): 'queued' | 'started';
|
|
14
15
|
/** Cancel only work that has not begun activation. */
|
|
15
16
|
cancel(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a queued change is being activated right now. Between the idle
|
|
19
|
+
* wait and the handoff the old session is still installed, so a submission
|
|
20
|
+
* made in that window would start a turn the handoff then discards; callers
|
|
21
|
+
* use this to refuse one instead of losing it.
|
|
22
|
+
*/
|
|
23
|
+
get activating(): boolean;
|
|
16
24
|
private pump;
|
|
17
25
|
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import type { AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
|
|
33
33
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
34
|
-
import { type TranscriptView } from '
|
|
34
|
+
import { type TranscriptView } from '../render/projection.ts';
|
|
35
35
|
/** The externally readable, event-fed transcript store for one session. */
|
|
36
36
|
export interface TranscriptStore {
|
|
37
37
|
/** The current view; the same object identity until an event changes it. */
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* microtask notify chained SyncLane rerenders past React's nested update
|
|
19
19
|
* limit; a bare macrotask merge repaints a whole turn's bursts at once).
|
|
20
20
|
*
|
|
21
|
-
* @module @deepseek-ai/dsh-code/subagents
|
|
21
|
+
* @module @deepseek-ai/dsh-code/session/subagents
|
|
22
22
|
*/
|
|
23
23
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
24
24
|
/** Hard row cap: overflow evicts the oldest settled row; a fully busy feed waits. */
|
|
@@ -68,3 +68,10 @@ export declare function createSubagentFeed(): SubagentFeedView & {
|
|
|
68
68
|
apply(sessionId: string, event: SessionEvent): void;
|
|
69
69
|
reset(): void;
|
|
70
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Root-log catalog facts a resumed session must replay into the subagent
|
|
73
|
+
* feed: constructor seeds never fire on the live bus, so without this the
|
|
74
|
+
* children of a resumed session vanish behind a restart. The empty-child
|
|
75
|
+
* placeholder row (childId '') is a placeholder, not a child, and stays out.
|
|
76
|
+
*/
|
|
77
|
+
export declare function subagentCatalogSeed(events: readonly SessionEvent[]): readonly SessionEvent<'subagent/catalog'>[];
|
|
@@ -39,3 +39,13 @@ export interface UserSettingsPersistence {
|
|
|
39
39
|
* @returns the persistence handle.
|
|
40
40
|
*/
|
|
41
41
|
export declare function createUserSettingsPersistence(): UserSettingsPersistence;
|
|
42
|
+
/**
|
|
43
|
+
* Read one user-level settings file as a plain object. The callers all treat a
|
|
44
|
+
* missing file as "unset" and a corrupt one as "warn and fall back", so this
|
|
45
|
+
* helper owns the one distinction they share: readable JSON that is not an
|
|
46
|
+
* object is corruption, not an absent preference, and must not surface as a
|
|
47
|
+
* cryptic property access on `null`.
|
|
48
|
+
* @param path - absolute path of the settings file.
|
|
49
|
+
* @returns the parsed object; the caller narrows each field itself.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readSettingsObject(path: string): Record<string, unknown>;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module @deepseek-ai/dsh-tui/panel-accent
|
|
12
12
|
*/
|
|
13
|
-
import { type RgbTriple } from '
|
|
13
|
+
import { type RgbTriple } from '../theme.ts';
|
|
14
14
|
/** One panel's resolved accent pair (border and title share the ring color in prismatic). */
|
|
15
15
|
export interface PanelAccent {
|
|
16
16
|
readonly border: RgbTriple;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Shared optional spacer row used by bounded panel layouts. */
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
|
+
/** Codex-style panel rhythm that still participates in the row budget. */
|
|
4
|
+
export declare function PanelGap({ visible }: {
|
|
5
|
+
visible: boolean;
|
|
6
|
+
}): ReactElement | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared one-line query editor for keyboard-owned filtering panels. */
|
|
2
|
+
/**
|
|
3
|
+
* Apply one keystroke to a panel search query. IME commits arrive as one
|
|
4
|
+
* multi-character chunk, so the whole printable run is appended; paste
|
|
5
|
+
* markers are stripped and control-laden chunks are ignored.
|
|
6
|
+
*/
|
|
7
|
+
export declare function editQuery(query: string, input: string, key: {
|
|
8
|
+
backspace?: boolean;
|
|
9
|
+
delete?: boolean;
|
|
10
|
+
}): string | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Ink renderer for width-safe styled terminal rows. */
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
|
+
import type { StyledLine } from '../render/lines.ts';
|
|
4
|
+
/** Render width-safe rows; every child is exactly one terminal row. */
|
|
5
|
+
export declare function StyledRows({ lines }: {
|
|
6
|
+
lines: readonly StyledLine[];
|
|
7
|
+
}): ReactElement;
|
|
8
|
+
/** File-oriented, color-coded unified diff viewport. */
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* an empty OSC payload and the terminal falls back to its own default; the
|
|
11
11
|
* previously set title is not portable to read back and is never restored.
|
|
12
12
|
*
|
|
13
|
-
* @module @deepseek-ai/dsh-code/terminal-title
|
|
13
|
+
* @module @deepseek-ai/dsh-code/ui/terminal-title
|
|
14
14
|
*/
|
|
15
15
|
/** Tab label before a session carries a name. */
|
|
16
16
|
export declare const DEFAULT_TERMINAL_TITLE = "deepseek";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shared terminal UI contracts used by the runner, App, and composer. */
|
|
2
|
+
/** Visual priority for one bounded local notice. */
|
|
3
|
+
export type NoticeTone = 'info' | 'warning' | 'error';
|
|
4
|
+
/** One mutation the terminal may request for a pending next-turn inbox item. */
|
|
5
|
+
export type QueueMutation = {
|
|
6
|
+
readonly kind: 'remove';
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'edit';
|
|
9
|
+
readonly text: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'steer';
|
|
12
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Wall-clock animation frame hook shared by independent terminal leaves. */
|
|
2
|
+
/**
|
|
3
|
+
* Derive ticks from elapsed wall time so a stretched interval skips ahead
|
|
4
|
+
* instead of slowing the animation under a busy event loop or remote shell.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useFrames(intervalMs: number, active?: boolean): number;
|