dsh-code 1.3.0 → 1.5.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 +10 -8
- package/README.md +10 -8
- package/lib/index.mjs +6980 -5942
- package/lib/session-query.mjs +18 -1
- package/lib/types/app.d.ts +38 -65
- 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 +20 -8
- 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 +16 -0
- package/lib/types/render/projection-events.d.ts +33 -0
- package/lib/types/render/text.d.ts +4 -0
- package/lib/types/render/transcript-viewport.d.ts +32 -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 +57 -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/session-query.d.ts +11 -0
- 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 +3 -2
- package/src/app.ts +802 -4162
- 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 +20 -8
- package/src/locales/zh.ts +20 -8
- 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 +35 -0
- package/src/render/lines.ts +45 -18
- package/src/render/projection-events.ts +109 -0
- package/src/render/projection.ts +11 -2
- package/src/render/text.ts +8 -0
- package/src/render/tool-preview.ts +35 -2
- package/src/render/transcript-viewport.ts +61 -0
- package/src/render/usage.ts +1 -2
- 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 +245 -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/session-query.ts +22 -0
- 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
|
@@ -2,25 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import { createElement, useCallback, useEffect, useMemo, useRef, useState, type ReactElement } from 'react'
|
|
4
4
|
import { Box, Text, useInput, useStdout } from 'ink'
|
|
5
|
-
import type { ModelDirectory, ModelRow } from '
|
|
6
|
-
import type { SubagentRow } from '
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import {
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
5
|
+
import type { ModelDirectory, ModelRow } from '../models.ts'
|
|
6
|
+
import type { SubagentRow } from '../session/subagents.ts'
|
|
7
|
+
import type { SearchRow } from '../runner/search-rows.ts'
|
|
8
|
+
export type { SearchRow } from '../runner/search-rows.ts'
|
|
9
|
+
import type { ScheduleRow } from '../render/projection.ts'
|
|
10
|
+
import type { PermissionRow } from '../permissions.ts'
|
|
11
|
+
import type { PresetRow } from '../presets.ts'
|
|
12
|
+
import type { PluginRow } from '../plugin-inventory.ts'
|
|
13
|
+
import type { SessionDirectoryOptions, SessionRow } from '../session/session-directory.ts'
|
|
14
|
+
import { formatRelativeTime, matchSessionRow } from '../session/session-directory.ts'
|
|
15
|
+
import type { ReviewBranch, ReviewCommit, ReviewSelection } from '../git-workflow.ts'
|
|
16
|
+
import { expandedDocumentViewport, panelViewport, revealRow } from '../render/inspector.ts'
|
|
17
|
+
import { markdownLines, textLines, type LineStyle, type StyledLine } from '../render/lines.ts'
|
|
18
|
+
import { usageLines, type UsageView } from '../render/usage.ts'
|
|
19
|
+
import { deleteLastGrapheme } from '../render/editor.ts'
|
|
20
|
+
import { stripPasteMarkers } from '../keyboard.ts'
|
|
21
|
+
import { DEFAULT_STATUSLINE_ITEMS, localizedStatusItems, type StatusItemId } from '../render/status.ts'
|
|
22
|
+
import { singleLineText, truncateColumns } from '../render/text.ts'
|
|
23
|
+
import { panelAccent } from '../ui/panel-accent.ts'
|
|
24
|
+
import { editQuery } from '../ui/query-editor.ts'
|
|
25
|
+
export { editQuery } from '../ui/query-editor.ts'
|
|
26
|
+
import { t } from '../i18n.ts'
|
|
27
|
+
import { getPalette, inkColor } from '../theme.ts'
|
|
24
28
|
|
|
25
29
|
interface ListFrameProps {
|
|
26
30
|
readonly title: string
|
|
@@ -96,18 +100,6 @@ function ListFrame(props: ListFrameProps): ReactElement {
|
|
|
96
100
|
)
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
/**
|
|
100
|
-
* Apply one keystroke to a panel search query. IME commits arrive as one
|
|
101
|
-
* multi-character chunk, so the whole printable run is appended; paste
|
|
102
|
-
* markers are stripped and control-laden chunks are ignored.
|
|
103
|
-
*/
|
|
104
|
-
export function editQuery(query: string, input: string, key: { backspace?: boolean; delete?: boolean }): string | undefined {
|
|
105
|
-
if (key.backspace || key.delete) return deleteLastGrapheme(query)
|
|
106
|
-
const text = stripPasteMarkers(input)
|
|
107
|
-
if (text !== '' && !/[\u0000-\u001f\u007f]/u.test(text)) return query + text
|
|
108
|
-
return undefined
|
|
109
|
-
}
|
|
110
|
-
|
|
111
103
|
export function ModePanel({ current, load, select, close }: {
|
|
112
104
|
current: string
|
|
113
105
|
load: () => Promise<readonly PresetRow[]>
|
|
@@ -472,24 +464,50 @@ function DocumentRows({ lines }: { lines: readonly StyledLine[] }): ReactElement
|
|
|
472
464
|
)
|
|
473
465
|
}
|
|
474
466
|
|
|
475
|
-
function DocumentPanel({ title, text, error, close }: {
|
|
467
|
+
function DocumentPanel({ title, text, error, close, onRefresh, onExpand }: {
|
|
476
468
|
title: string
|
|
477
469
|
text?: string
|
|
478
470
|
error?: string
|
|
479
471
|
close: () => void
|
|
472
|
+
/** Re-read the document; offered as `r` (running subagents keep moving). */
|
|
473
|
+
onRefresh?: () => void
|
|
474
|
+
/** Replace this preview with a fuller surface (subagent attachment). */
|
|
475
|
+
onExpand?: () => void
|
|
480
476
|
}): ReactElement {
|
|
481
477
|
const stdout = useStdout().stdout
|
|
482
|
-
|
|
478
|
+
// Expanded mode: the document replaces the transcript area (near the whole
|
|
479
|
+
// terminal minus the bottom live region), entered from the preview with
|
|
480
|
+
// Enter/f. Esc leaves for the caller's list directly — the preview is a
|
|
481
|
+
// peek step, not a level to back through.
|
|
482
|
+
const [expanded, setExpanded] = useState(false)
|
|
483
|
+
const viewport = expanded
|
|
484
|
+
? expandedDocumentViewport(stdout?.columns ?? 80, stdout?.rows ?? 30)
|
|
485
|
+
: panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30)
|
|
483
486
|
const [scroll, setScroll] = useState(0)
|
|
484
487
|
const lines = useMemo(() => text === undefined ? [] : markdownLines(text, viewport.contentColumns), [text, viewport.contentColumns])
|
|
488
|
+
const maxScroll = Math.max(0, lines.length - viewport.bodyRows)
|
|
485
489
|
useInput((input, key) => {
|
|
486
490
|
if (key.escape || input === 'q' || input === 't') return close()
|
|
491
|
+
// Ctrl+D keeps its step-out meaning: empty-draft exit in the composer,
|
|
492
|
+
// leave-the-document here.
|
|
493
|
+
if (expanded && key.ctrl && input === 'd') return close()
|
|
494
|
+
if (!expanded && (key.return || input === 'f')) {
|
|
495
|
+
if (onExpand !== undefined) {
|
|
496
|
+
close()
|
|
497
|
+
onExpand()
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
setExpanded(true)
|
|
501
|
+
setScroll(value => Math.min(value, Math.max(0, lines.length - expandedDocumentViewport(stdout?.columns ?? 80, stdout?.rows ?? 30).bodyRows)))
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
if (expanded && input === 'r' && onRefresh !== undefined) return onRefresh()
|
|
487
505
|
if (key.upArrow) return setScroll(value => Math.max(0, value - 1))
|
|
488
|
-
if (key.downArrow) return setScroll(value => Math.min(
|
|
506
|
+
if (key.downArrow) return setScroll(value => Math.min(maxScroll, value + 1))
|
|
489
507
|
if (key.pageUp) return setScroll(value => Math.max(0, value - Math.max(1, viewport.bodyRows - 1)))
|
|
490
|
-
if (key.pageDown) return setScroll(value => Math.min(
|
|
508
|
+
if (key.pageDown) return setScroll(value => Math.min(maxScroll, value + Math.max(1, viewport.bodyRows - 1)))
|
|
491
509
|
if (input === 'g') return setScroll(0)
|
|
492
|
-
if (input === 'G') return setScroll(
|
|
510
|
+
if (input === 'G') return setScroll(maxScroll)
|
|
493
511
|
})
|
|
494
512
|
if (viewport.compact) return createElement(Text, { wrap: 'truncate-end' }, truncateColumns(t('document.compact'), viewport.contentColumns))
|
|
495
513
|
const body: readonly StyledLine[] = error !== undefined
|
|
@@ -500,10 +518,12 @@ function DocumentPanel({ title, text, error, close }: {
|
|
|
500
518
|
const accent = panelAccent('kernel-transcript', getPalette().dim, getPalette().brandBright)
|
|
501
519
|
return createElement(
|
|
502
520
|
Box,
|
|
503
|
-
{ width: viewport.outerColumns, borderStyle: 'round', borderColor: inkColor(accent.border), flexDirection: 'column', paddingX: 1 },
|
|
504
|
-
createElement(Text, { color: inkColor(accent.title), wrap: 'truncate-end' }, truncateColumns(singleLineText(title), viewport.contentColumns)),
|
|
521
|
+
{ width: viewport.outerColumns, borderStyle: 'round', borderColor: expanded ? inkColor(accent.title) : inkColor(accent.border), flexDirection: 'column', paddingX: 1 },
|
|
522
|
+
createElement(Text, { color: inkColor(accent.title), wrap: 'truncate-end' }, truncateColumns(singleLineText(title) + (expanded ? ` · ${t('document.expanded')}` : ''), viewport.contentColumns)),
|
|
505
523
|
createElement(DocumentRows, { lines: body }),
|
|
506
|
-
createElement(Text, { dimColor: true, wrap: 'truncate-end' }, truncateColumns(
|
|
524
|
+
createElement(Text, { dimColor: true, wrap: 'truncate-end' }, truncateColumns(expanded
|
|
525
|
+
? t('document.footerExpanded', { from: lines.length === 0 ? 0 : scroll + 1, to: Math.min(lines.length, scroll + viewport.bodyRows), total: lines.length, refresh: onRefresh === undefined ? '' : ` · ${t('document.refreshHint')}` })
|
|
526
|
+
: t('document.footer', { from: lines.length === 0 ? 0 : scroll + 1, to: Math.min(lines.length, scroll + viewport.bodyRows), total: lines.length }), viewport.contentColumns)),
|
|
507
527
|
)
|
|
508
528
|
}
|
|
509
529
|
|
|
@@ -747,24 +767,6 @@ export function ReviewPickerPanel({ loadBranches, loadCommits, choose, close }:
|
|
|
747
767
|
)
|
|
748
768
|
}
|
|
749
769
|
|
|
750
|
-
/** One cross-session full-text search hit mapped from the session-query engine. */
|
|
751
|
-
export interface SearchRow {
|
|
752
|
-
/** Session id (Enter resumes it through the switch machinery). */
|
|
753
|
-
readonly id: string
|
|
754
|
-
/** Display label: session title or the short id form. */
|
|
755
|
-
readonly label: string
|
|
756
|
-
/** Secondary facts line (workspace · preset markers). */
|
|
757
|
-
readonly detail: string
|
|
758
|
-
/** Bounded plain-text excerpt around the strongest match. */
|
|
759
|
-
readonly snippet: string
|
|
760
|
-
/** Match timestamp (relative labels derive from it). */
|
|
761
|
-
readonly updatedAt: number
|
|
762
|
-
/** Whether the hit is a delegated subagent conversation (not resumable). */
|
|
763
|
-
readonly subagent: boolean
|
|
764
|
-
/** Whether Enter may switch into it. */
|
|
765
|
-
readonly resumable: boolean
|
|
766
|
-
}
|
|
767
|
-
|
|
768
770
|
/**
|
|
769
771
|
* The /search panel: full-text search over every persisted session through
|
|
770
772
|
* the in-process session-query engine (the same corpus the model's
|
|
@@ -1092,13 +1094,15 @@ interface AgentsEntry {
|
|
|
1092
1094
|
* Enter/t opening the child's full transcript in the shared read-only
|
|
1093
1095
|
* document view (the same projection the exporter uses).
|
|
1094
1096
|
*/
|
|
1095
|
-
export function AgentsPanel({ live, load, readTranscript, close }: {
|
|
1097
|
+
export function AgentsPanel({ live, load, readTranscript, attach, close }: {
|
|
1096
1098
|
/** Live feed rows (child sessions observed this process). */
|
|
1097
1099
|
live: readonly SubagentRow[]
|
|
1098
1100
|
/** Load this session's persisted child sessions by lineage. */
|
|
1099
1101
|
load: () => Promise<readonly SessionRow[]>
|
|
1100
1102
|
/** Read one child session's full transcript as markdown. */
|
|
1101
1103
|
readTranscript: (id: string, signal?: AbortSignal) => Promise<string>
|
|
1104
|
+
/** Attach to one child as the whole view (the runner's live buses). */
|
|
1105
|
+
attach: ((id: string, label: string) => void) | undefined
|
|
1102
1106
|
close: () => void
|
|
1103
1107
|
}): ReactElement {
|
|
1104
1108
|
const [dirRows, setDirRows] = useState<readonly SessionRow[] | undefined>(undefined)
|
|
@@ -1145,22 +1149,26 @@ export function AgentsPanel({ live, load, readTranscript, close }: {
|
|
|
1145
1149
|
return [...feedRows, ...persisted]
|
|
1146
1150
|
}, [live, dirRows])
|
|
1147
1151
|
useEffect(() => setCursor(value => Math.min(value, Math.max(0, rows.length - 1))), [rows.length])
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
if (row === undefined) return
|
|
1152
|
+
/** (Re-)read one child's transcript; the id is captured, not re-derived. */
|
|
1153
|
+
const readTranscriptOf = (id: string): void => {
|
|
1151
1154
|
transcriptLoad.current?.abort()
|
|
1152
|
-
setTranscript({ id
|
|
1155
|
+
setTranscript({ id })
|
|
1153
1156
|
const controller = new AbortController()
|
|
1154
1157
|
transcriptLoad.current = controller
|
|
1155
|
-
Promise.resolve().then(() => readTranscript(
|
|
1158
|
+
Promise.resolve().then(() => readTranscript(id, controller.signal)).then(
|
|
1156
1159
|
text => {
|
|
1157
|
-
if (!controller.signal.aborted) setTranscript({ id
|
|
1160
|
+
if (!controller.signal.aborted) setTranscript({ id, text })
|
|
1158
1161
|
},
|
|
1159
1162
|
reason => {
|
|
1160
|
-
if (!controller.signal.aborted) setTranscript({ id
|
|
1163
|
+
if (!controller.signal.aborted) setTranscript({ id, error: reason instanceof Error ? reason.message : String(reason) })
|
|
1161
1164
|
},
|
|
1162
1165
|
)
|
|
1163
1166
|
}
|
|
1167
|
+
const openTranscript = (): void => {
|
|
1168
|
+
const row = rows[cursor]
|
|
1169
|
+
if (row === undefined) return
|
|
1170
|
+
readTranscriptOf(row.id)
|
|
1171
|
+
}
|
|
1164
1172
|
useInput((input, key) => {
|
|
1165
1173
|
if (key.escape || input === 'q') return close()
|
|
1166
1174
|
if (input === 'r') return refresh()
|
|
@@ -1177,6 +1185,17 @@ export function AgentsPanel({ live, load, readTranscript, close }: {
|
|
|
1177
1185
|
transcriptLoad.current?.abort()
|
|
1178
1186
|
setTranscript(undefined)
|
|
1179
1187
|
},
|
|
1188
|
+
// Running subagents keep moving: `r` re-reads the same child.
|
|
1189
|
+
onRefresh: () => readTranscriptOf(transcript.id),
|
|
1190
|
+
// Enter/f in the preview attaches the child as the whole view when the
|
|
1191
|
+
// runner wired the live buses; without them the bordered expansion
|
|
1192
|
+
// keeps serving the same document.
|
|
1193
|
+
...(attach === undefined ? {} : {
|
|
1194
|
+
onExpand: () => {
|
|
1195
|
+
const label = rows.find(row => row.id === transcript.id)?.label ?? transcript.id.slice(-12)
|
|
1196
|
+
attach(transcript.id, label)
|
|
1197
|
+
},
|
|
1198
|
+
}),
|
|
1180
1199
|
})
|
|
1181
1200
|
}
|
|
1182
1201
|
return createElement(ListFrame, {
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import { createElement, useState, type ReactElement } from 'react'
|
|
8
8
|
import { Box, Text, useInput, useStdout } from 'ink'
|
|
9
|
-
import { panelViewport } from '
|
|
10
|
-
import { truncateColumns } from '
|
|
11
|
-
import { LANGUAGES, t, type LanguageName } from '
|
|
12
|
-
import { getPalette, inkColor } from '
|
|
9
|
+
import { panelViewport } from '../render/inspector.ts'
|
|
10
|
+
import { truncateColumns } from '../render/text.ts'
|
|
11
|
+
import { LANGUAGES, t, type LanguageName } from '../i18n.ts'
|
|
12
|
+
import { getPalette, inkColor } from '../theme.ts'
|
|
13
13
|
|
|
14
14
|
export function LanguagePanel({ current, select, close }: {
|
|
15
15
|
current: LanguageName
|