dsh-code 0.8.0 → 0.9.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.
@@ -25,6 +25,7 @@ import type { ProviderSettingsDirectory, ProviderTargetView } from './provider-s
25
25
  import type { QuestionStore } from './questions.ts';
26
26
  import type { SkillsView, SkillRow } from './skills.ts';
27
27
  import type { MentionCandidate } from './mentions.ts';
28
+ import type { SubagentFeedView } from './subagents.ts';
28
29
  import type { PresetRow } from './presets.ts';
29
30
  import type { PermissionRow } from './permissions.ts';
30
31
  import type { PluginRow } from './plugin-inventory.ts';
@@ -39,6 +40,8 @@ export interface AppProps {
39
40
  approval: ApprovalStore;
40
41
  /** ask_user_question store fed by the single UI provider. */
41
42
  questions: QuestionStore;
43
+ /** Live subagent activity feed (child sessions of the current root). */
44
+ subagents: SubagentFeedView;
42
45
  /** Live slash-command descriptor list (completion candidates). */
43
46
  commands: CommandsView;
44
47
  /** Live user-invocable skill catalog (completion candidates). */
@@ -75,6 +78,14 @@ export interface AppProps {
75
78
  loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
76
79
  /** Apply one /model selection (with an advertised reasoning effort, when picked); returns the display label. */
77
80
  selectModel(row: ModelRow, effortId?: string): string;
81
+ /** The /subagent override label, '' when delegated agents follow the current model. */
82
+ subagentModel: string;
83
+ /** Apply one /subagent model pick; returns the override label. */
84
+ setSubagentModel(row: ModelRow, effortId?: string): string;
85
+ /** Drop the /subagent override (delegated agents follow the current model). */
86
+ clearSubagentModel(): void;
87
+ /** Delete one session subtree; resolves with the outcome line. */
88
+ deleteSession(id: string): Promise<string>;
78
89
  /** Load provider/settings/credential facts for the optional /model provider stage. */
79
90
  loadModelProviders?(): Promise<ProviderSettingsDirectory>;
80
91
  /** Subscribe to Harness credential/settings/adapter invalidations while /model is open. */
@@ -101,6 +112,8 @@ export interface AppProps {
101
112
  createSession(mode?: string): void;
102
113
  loadSessions(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
103
114
  loadSessionTranscript(id: string, signal?: AbortSignal): Promise<string>;
115
+ /** Load this session's subagent conversations (children by lineage). */
116
+ loadSubagents(): Promise<readonly SessionRow[]>;
104
117
  switchSession(row: SessionRow): void;
105
118
  cancelSessionSwitch(): boolean;
106
119
  loadPlugins(): readonly PluginRow[];
@@ -29,10 +29,12 @@ export interface PendingApproval {
29
29
  }
30
30
  /** The pending-question snapshot the renderer subscribes to. */
31
31
  export interface ApprovalSnapshot {
32
- /** The pending question, or undefined when none is being asked. */
32
+ /** The question on screen (queue head), or undefined when none is asked. */
33
33
  pending: PendingApproval | undefined;
34
34
  /** Presentational: an answer was submitted, the ask has not settled yet. */
35
35
  answered: boolean;
36
+ /** Further asks waiting behind the on-screen one (FIFO, Codex-style). */
37
+ queued: number;
36
38
  }
37
39
  /** Store the pending question lands in; the renderer reads, the answerer writes. */
38
40
  export interface ApprovalStore {
@@ -1,6 +1,7 @@
1
1
  /** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
2
2
  import { type ReactElement } from 'react';
3
- import type { ModelRow } from './models.ts';
3
+ import type { ModelDirectory, ModelRow } from './models.ts';
4
+ import type { SubagentRow } from './subagents.ts';
4
5
  import type { PermissionRow } from './permissions.ts';
5
6
  import type { PresetRow } from './presets.ts';
6
7
  import type { PluginRow } from './plugin-inventory.ts';
@@ -23,11 +24,19 @@ export declare function PluginPanel({ load, close, initialQuery }: {
23
24
  close(): void;
24
25
  initialQuery?: string;
25
26
  }): ReactElement;
26
- export declare function ResumePanel({ currentCwd, load, readTranscript, select, close }: {
27
+ export declare function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete, deleteConfirmId, reloadToken, deleteMode, close }: {
27
28
  currentCwd: string;
28
29
  load(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
29
30
  readTranscript(id: string, signal?: AbortSignal): Promise<string>;
30
31
  select(row: SessionRow): void;
32
+ /** Arm the composer-based delete confirm for one row (App owns the keys). */
33
+ requestDelete?(row: SessionRow): void;
34
+ /** The row id awaiting y/n in the composer, when any (App-owned). */
35
+ deleteConfirmId?: string;
36
+ /** Bump to reload the listing (e.g. after a deletion). */
37
+ reloadToken?: number;
38
+ /** Opened via /delete: hint-first delete mode. */
39
+ deleteMode?: boolean;
31
40
  close(): void;
32
41
  }): ReactElement;
33
42
  /**
@@ -57,12 +66,15 @@ export declare function StatuslinePanel({ enabled, change, close }: {
57
66
  /**
58
67
  * The `/model` reasoning-effort stage (the Codex model → reasoning popup
59
68
  * contract): one bounded list over the selected model's adapter-advertised
60
- * effort levels, with the effective effort and the model default marked.
61
- * A model WITHOUT an adapter-declared default leads with a "Default"
62
- * (provider-default) row the web effort pane's first entry so the user
63
- * can clear a picked level back to provider behavior instead of being forced
64
- * to choose an advertised one. Enter applies one level; Esc returns to the
65
- * model list without applying.
69
+ * effort levels in the adapter's own display order, ids verbatim (the
70
+ * kernel treats them as opaque and rejects anything else) — with the
71
+ * effective effort and the model default marked. A model WITHOUT an
72
+ * adapter-declared default leads with a "Default" (provider-default) row
73
+ * the web effort pane's first entry so the user can clear a picked level
74
+ * back to provider behavior. A model advertising no levels opens the same
75
+ * stage with an explicit empty state (the web pane's "no levels" copy)
76
+ * instead of a bare failure notice. Enter applies one level; Esc returns to
77
+ * the model list without applying.
66
78
  */
67
79
  export declare function EffortPanel({ row, current, select, back }: {
68
80
  /** The model row whose advertised levels this stage lists. */
@@ -74,3 +86,41 @@ export declare function EffortPanel({ row, current, select, back }: {
74
86
  /** Return to the model list without applying. */
75
87
  back(): void;
76
88
  }): ReactElement;
89
+ /**
90
+ * The /agents panel (the Codex agent-picker contract, read-only): this
91
+ * conversation's subagent conversations — live rows from the activity feed
92
+ * first, persisted children the feed has not seen this process after — with
93
+ * Enter/t opening the child's full transcript in the shared read-only
94
+ * document view (the same projection the exporter uses).
95
+ */
96
+ export declare function AgentsPanel({ live, load, readTranscript, close }: {
97
+ /** Live feed rows (child sessions observed this process). */
98
+ live: readonly SubagentRow[];
99
+ /** Load this session's persisted child sessions by lineage. */
100
+ load(): Promise<readonly SessionRow[]>;
101
+ /** Read one child session's full transcript as markdown. */
102
+ readTranscript(id: string, signal?: AbortSignal): Promise<string>;
103
+ close(): void;
104
+ }): ReactElement;
105
+ /**
106
+ * The /subagent model panel: which model configuration delegated subagents
107
+ * run on. The kernel seeds child agents from the parent's CREATE-TIME
108
+ * AgentOptions, so a mid-session /model switch would otherwise leave them on
109
+ * the launch-time route; the TUI mirrors the selection onto subagent-origin
110
+ * requests (or an explicit override picked here) via an agent/request
111
+ * listener. The leading "inherit" row restores follow-the-current-model
112
+ * behavior; picking a model with several advertised efforts opens the same
113
+ * effort stage /model uses. Effort overrides are not offered separately —
114
+ * the kernel's AgentOptions has no effort channel for children, so the level
115
+ * rides the selected model exactly as /model applies it.
116
+ */
117
+ export declare function SubagentPanel({ current, load, pick, inherit, close }: {
118
+ /** Display label of the override in force, '' when following the current model. */
119
+ current: string;
120
+ load(): Promise<ModelDirectory>;
121
+ /** Apply one model (with an advertised effort, when picked) as the override. */
122
+ pick(row: ModelRow, effortId?: string): void;
123
+ /** Drop the override: subagents follow the current model again. */
124
+ inherit(): void;
125
+ close(): void;
126
+ }): ReactElement;
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import type { Context } from '@deepseek-ai/cordis';
11
11
  import type { ModelSelection } from '@deepseek-ai/dsh-agent';
12
- import { type LlmModelReasoningInfo } from '@deepseek-ai/dsh-llm';
12
+ import { type LlmCallConfig, type LlmModelReasoningInfo } from '@deepseek-ai/dsh-llm';
13
13
  /** Display metadata for one adapter-owned reasoning effort (mirrors `LlmReasoningEffortInfo`). */
14
14
  export interface ModelReasoningEffort {
15
15
  /** Opaque value accepted by the model's `GenerateOptions.reasoningEffort`. */
@@ -87,6 +87,20 @@ export declare function resolveEffectiveSelection(picked: ModelSelection | undef
87
87
  export declare function buildModelSelection(row: ModelRow, effortId?: string): ModelSelection;
88
88
  /** Display label for one applied selection: `provider/model` or `provider/model@effort`. */
89
89
  export declare function modelSelectionLabel(selection: ModelSelection): string;
90
+ /**
91
+ * Apply one model selection onto a resolved request config — the exact
92
+ * semantics of the kernel's `installModelSelection` request listener,
93
+ * extracted so the TUI can mirror it for subagent-origin requests: children
94
+ * spawned by the subagent tool inherit the parent's CREATE-TIME AgentOptions,
95
+ * which a mid-session /model switch never touches, so delegated work would
96
+ * otherwise keep running on the launch-time route. An absent effort strips
97
+ * any inherited effort (restoring the selected model's provider default),
98
+ * matching the kernel listener field-for-field.
99
+ * @param resolved - the config the inner chain produced.
100
+ * @param selection - the selection to enforce.
101
+ * @returns the overridden config.
102
+ */
103
+ export declare function applyModelSelectionToConfig(resolved: LlmCallConfig, selection: ModelSelection): LlmCallConfig;
90
104
  /**
91
105
  * Load the selectable model directory from the live `ctx.llm` registry.
92
106
  * Providers are listed synchronously; each provider's models are discovered
@@ -47,6 +47,8 @@ export interface ToolEntry {
47
47
  arguments: string;
48
48
  /** Bounded human-meaningful arguments preview for the tool card. */
49
49
  preview: string;
50
+ /** Bounded delegation prompt (subagent cards' second row), '' when none. */
51
+ prompt: string;
50
52
  /** Execution state; `running` until the paired result lands. */
51
53
  state: 'running' | 'done' | 'error';
52
54
  /** Bounded first text block of the result, empty until it lands. */
@@ -16,3 +16,13 @@
16
16
  * @returns the preview line; empty when nothing useful resolves.
17
17
  */
18
18
  export declare function toolArgumentsPreview(args: string, toolName: string): string;
19
+ /**
20
+ * Bounded prompt preview for delegation-style tools (`subagent`): the
21
+ * `prompt` argument rendered as the card's second row, so the transcript
22
+ * shows what the child agent was asked — not just its description label —
23
+ * while it runs (Codex's SpawnAgent card preview). Anything else returns ''.
24
+ * @param toolName - the tool the arguments belong to.
25
+ * @param args - raw JSON arguments string as the model produced it.
26
+ * @returns the one-line prompt preview, or '' when none applies.
27
+ */
28
+ export declare function toolPromptPreview(toolName: string, args: string): string;
@@ -38,6 +38,8 @@ export interface SessionDirectoryOptions {
38
38
  export interface SessionRow {
39
39
  readonly id: string;
40
40
  readonly createdAt: number;
41
+ /** Last-activity timestamp: artifact mtime when known, else createdAt. */
42
+ readonly updatedAt: number;
41
43
  readonly cwd: string;
42
44
  readonly workspace: string;
43
45
  readonly parent?: string;
@@ -63,7 +65,49 @@ export declare function samePath(left: string | undefined, right: string): boole
63
65
  export declare function matchSessionId(headers: readonly SessionHeader[], wanted: string): SessionHeader;
64
66
  /** The newest persisted ROOT session pinned to this cwd, or undefined. */
65
67
  export declare function newestRootForCwd(headers: readonly SessionHeader[], cwd: string): SessionHeader | undefined;
66
- /** Filter/sort header-only records. No session log is loaded here. */
67
- export declare function projectSessionRows(records: readonly SessionRecord[], options: SessionDirectoryOptions): SessionRow[];
68
+ /**
69
+ * Filter/sort header-only records. No session log is loaded here. Sorting is
70
+ * by LAST ACTIVITY (`updated` — artifact mtime when the caller resolved one,
71
+ * else createdAt), matching the codex resume picker's default UpdatedAt
72
+ * ordering: a session you kept talking in outranks one created later but idle.
73
+ * @param records - the header-only records.
74
+ * @param options - filter/sort options.
75
+ * @param updated - per-session last-activity timestamps, when resolved.
76
+ */
77
+ export declare function projectSessionRows(records: readonly SessionRecord[], options: SessionDirectoryOptions, updated?: ReadonlyMap<string, number>): SessionRow[];
68
78
  /** Merge page-local title observations without disturbing directory order. */
69
79
  export declare function mergeSessionTitles(rows: readonly SessionRow[], observations: readonly TitleObservationResult[]): SessionRow[];
80
+ /**
81
+ * Encode a session id the way the JSONL backend does for its on-disk layout
82
+ * (`encodeSegment`: safe units literal, everything else `~XXXX`). Used ONLY to
83
+ * validate that a `locate()` path really is this session's directory before
84
+ * any deletion touches the filesystem — a local copy of the pure upstream
85
+ * contract, kept in sync with `session-persistence-jsonl/src/format.ts`.
86
+ */
87
+ export declare function encodeSessionSegment(raw: string): string;
88
+ /** The session-log artifact names the JSONL backend may create. */
89
+ export declare const SESSION_ARTIFACT_NAMES: readonly string[];
90
+ /**
91
+ * Guard one `locate()` artifact path before deletion (codex's scoped-path
92
+ * check, adapted to the JSONL layout): the file must be a `session.jsonl`
93
+ * artifact sitting in the directory named exactly `encodeSegment(id)`.
94
+ * @param artifact - the path the persistence backend located.
95
+ * @param id - the session id the artifact claims to belong to.
96
+ * @returns the owning session directory, or undefined when the layout is unexpected.
97
+ */
98
+ export declare function sessionArtifactDirectory(artifact: string, id: string): string | undefined;
99
+ /**
100
+ * Collect one session's deletion subtree: the id plus every record whose
101
+ * parent chain leads to it (codex deletes subagent threads with their root).
102
+ * @param records - the full directory listing.
103
+ * @param id - the root session id to delete.
104
+ * @returns the ids to delete, root first.
105
+ */
106
+ export declare function collectDeletionSubtree(records: readonly SessionRecord[], id: string): string[];
107
+ /**
108
+ * Codex-style relative time for session rows ("now", "5m ago", "3h ago",
109
+ * "2d ago"; older than a week falls back to the local date).
110
+ * @param timestamp - epoch milliseconds of the last activity.
111
+ * @param now - the pinned reference clock (one value per list render).
112
+ */
113
+ export declare function formatRelativeTime(timestamp: number, now: number): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Live subagent activity feed: a bounded, display-only projection of CHILD
3
+ * session events. The transcript store folds only the root session (the
4
+ * durable truth this TUI renders); subagent conversations are their own
5
+ * sessions, and before this module their events were dropped entirely —
6
+ * a running subagent was invisible until its parent tool call settled.
7
+ *
8
+ * This is NOT a second transcript: each child folds to ONE row (label,
9
+ * running state, bounded last-activity text), capped at
10
+ * {@link MAX_SUBAGENT_ROWS}. Rows are advisory display state, rebuilt from
11
+ * live events; nothing here persists or replays. Notification is coalesced
12
+ * to one microtask per delivery burst, mirroring the transcript store's
13
+ * contract (per-token synchronous notify once cascaded past React's nested
14
+ * update limit on the GLM thinking path).
15
+ *
16
+ * @module @deepseek-ai/dsh-code/subagents
17
+ */
18
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
19
+ /** Hard row cap: a fan-out larger than this stays summarized by the head. */
20
+ export declare const MAX_SUBAGENT_ROWS = 8;
21
+ /** One live subagent row in the feed. */
22
+ export interface SubagentRow {
23
+ /** Child session id. */
24
+ readonly id: string;
25
+ /** Display label (session title when observed, else a short id form). */
26
+ readonly label: string;
27
+ /** Coarse lifecycle state folded from the child's events. */
28
+ readonly state: 'running' | 'idle' | 'done';
29
+ /** Bounded last-activity text for the status line. */
30
+ readonly activity: string;
31
+ /** Last fold time (ms, event clock) — newest-first ordering key. */
32
+ readonly updatedAt: number;
33
+ }
34
+ /** The read-only snapshot surface the renderer subscribes to. */
35
+ export interface SubagentFeedView {
36
+ /** Subscribe to feed changes; returns the unsubscribe function. */
37
+ subscribe(listener: () => void): () => void;
38
+ /** Read the current rows (identity-stable between changes). */
39
+ getSnapshot(): readonly SubagentRow[];
40
+ }
41
+ /**
42
+ * Fold one child-session event into its feed row (pure).
43
+ * Unknown event kinds leave the row untouched.
44
+ * @param previous - the row's current state, when any.
45
+ * @param sessionId - the child session id.
46
+ * @param event - the child session event.
47
+ * @returns the next row state.
48
+ */
49
+ export declare function foldSubagentRow(previous: SubagentRow | undefined, sessionId: string, event: SessionEvent): SubagentRow;
50
+ /**
51
+ * Create one subagent feed. `apply` folds a child event (the caller gates
52
+ * which sessions are children); `reset` clears on a session switch. Row
53
+ * order is first-seen; the snapshot array is frozen and only replaced when
54
+ * a row actually changed.
55
+ * @returns the mutable feed handle plus its `SubagentFeedView`.
56
+ */
57
+ export declare function createSubagentFeed(): SubagentFeedView & {
58
+ apply(sessionId: string, event: SessionEvent): void;
59
+ reset(): void;
60
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code",
3
3
  "description": "Claude-Code-style interactive TUI bundle for DeepSeek Harness (dsh): DeepSeek-blue whale banner, live session transcript, and a blended status line",
4
- "version": "0.8.0",
4
+ "version": "0.9.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "deepseek": "./bin/deepseek.mjs",