dsh-code 1.0.6 → 1.0.7
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 +60 -9
- package/README.md +60 -9
- package/bin/deepseek.mjs +86 -1
- package/cordis.patch.yml +88 -0
- package/lib/index.mjs +1002 -94
- package/lib/session-query.mjs +149 -0
- package/lib/types/app.d.ts +9 -2
- package/lib/types/index.d.ts +27 -0
- package/lib/types/kernel-panels.d.ts +23 -0
- package/lib/types/render/editor.d.ts +4 -3
- package/lib/types/render/ime-cursor.d.ts +60 -0
- package/lib/types/render/projection.d.ts +35 -0
- package/lib/types/render/status.d.ts +1 -1
- package/lib/types/session-query.d.ts +92 -0
- package/lib/types/terminal-title.d.ts +58 -0
- package/lib/types/update-panel.d.ts +49 -0
- package/lib/types/update.d.ts +66 -0
- package/package.json +228 -89
- package/src/app.ts +252 -69
- package/src/index.ts +129 -11
- package/src/internals.ts +5 -0
- package/src/kernel-panels.ts +89 -3
- package/src/render/editor.ts +5 -4
- package/src/render/ime-cursor.ts +147 -0
- package/src/render/projection.ts +144 -3
- package/src/render/status.ts +18 -4
- package/src/session-query.ts +235 -0
- package/src/terminal-title.ts +173 -0
- package/src/update-panel.ts +246 -0
- package/src/update.ts +110 -0
package/src/app.ts
CHANGED
|
@@ -34,10 +34,14 @@ import {
|
|
|
34
34
|
type ThemeName,
|
|
35
35
|
} from './theme.ts'
|
|
36
36
|
import { ThemePanel } from './theme-panel.ts'
|
|
37
|
+
import { UpdatePanel } from './update-panel.ts'
|
|
38
|
+
import type { LauncherUpdateStatus } from './update.ts'
|
|
37
39
|
import { WHALE_GLYPH, WHALE_GLYPH_COLUMNS } from './whale-glyph.ts'
|
|
38
40
|
import { DSH_CODE_VERSION, dshKernelVersion } from './version.ts'
|
|
39
41
|
import type { TranscriptStore } from './store.ts'
|
|
42
|
+
import { DEFAULT_TERMINAL_TITLE, sanitizeTerminalTitle, terminalTitleSequence, useTerminalTitle } from './terminal-title.ts'
|
|
40
43
|
import { settledEntryCount, type TranscriptEntry } from './render/projection.ts'
|
|
44
|
+
import { imeCursorRowsUp, useImeCursorAnchor } from './render/ime-cursor.ts'
|
|
41
45
|
import { type MdSegment, visibleColumns } from './render/markdown.ts'
|
|
42
46
|
import {
|
|
43
47
|
busyChaseFrame,
|
|
@@ -80,7 +84,7 @@ import type { QuestionSnapshot, QuestionStore } from './questions.ts'
|
|
|
80
84
|
import type { SkillsView, SkillRow } from './skills.ts'
|
|
81
85
|
import { isPathLikeMentionQuery, type MentionCandidate } from './mentions.ts'
|
|
82
86
|
import type { SubagentFeedView, SubagentRow } from './subagents.ts'
|
|
83
|
-
import { AgentsPanel, EffortPanel, HistoryPanel, JobsPanel, ModePanel, PermissionPanel, PluginPanel, ResumePanel, StatuslinePanel, runClock, SubagentPanel, type JobRow } from './kernel-panels.ts'
|
|
87
|
+
import { AgentsPanel, editQuery, EffortPanel, HistoryPanel, JobsPanel, ModePanel, PermissionPanel, PluginPanel, ResumePanel, SchedulePanel, StatuslinePanel, runClock, SubagentPanel, type JobRow } from './kernel-panels.ts'
|
|
84
88
|
import type { PresetRow } from './presets.ts'
|
|
85
89
|
import type { PermissionRow } from './permissions.ts'
|
|
86
90
|
import type { PluginRow } from './plugin-inventory.ts'
|
|
@@ -230,7 +234,9 @@ const LOCAL_COMMANDS = [
|
|
|
230
234
|
{ label: '/fork', description: 'fork at the latest completed turn (/fork [event-seq])' },
|
|
231
235
|
{ label: '/resume', description: 'browse or switch root sessions (/resume [id|prefix])' },
|
|
232
236
|
{ label: '/plugin', description: 'inspect the live plugin composition' },
|
|
237
|
+
{ label: '/update', description: 'update dsh-code, the harness host, and profile plugins in one aligned step' },
|
|
233
238
|
{ label: '/jobs', description: 'inspect background jobs' },
|
|
239
|
+
{ label: '/schedule', description: 'inspect active reminders (created through schedule tools)' },
|
|
234
240
|
{ label: '/statusline', description: 'customize the status line items' },
|
|
235
241
|
{ label: '/theme', description: 'switch the color theme' },
|
|
236
242
|
{ label: '/animation', description: 'toggle timed animations (/animation [on|off])' },
|
|
@@ -358,8 +364,10 @@ export interface AppProps {
|
|
|
358
364
|
logoutProviderAuthorization?(row: ProviderAuthorizationRow): Promise<void>
|
|
359
365
|
openAuthorizationUrl?(url: string): boolean
|
|
360
366
|
copyTextValue?(text: string): Promise<void>
|
|
361
|
-
/** Cycle to the next
|
|
362
|
-
|
|
367
|
+
/** Cycle to the next mode station (Shift+Tab): a permission preset or a plan switch; returns the notice label. */
|
|
368
|
+
cycleMode(): string
|
|
369
|
+
/** Pre-session plan choice: shows the plan badge before the first session exists. */
|
|
370
|
+
pendingPlan?: boolean
|
|
363
371
|
/** Select or inspect a permission preset without requiring a pre-existing session. */
|
|
364
372
|
setPermission(id: string): string
|
|
365
373
|
/** Export the transcript to a markdown file (/export [path]); reports via notices. */
|
|
@@ -389,6 +397,10 @@ export interface AppProps {
|
|
|
389
397
|
loadPlugins(): readonly PluginRow[]
|
|
390
398
|
/** Caller-visible background jobs (the host jobs registry, read-only). */
|
|
391
399
|
loadJobs(): readonly JobRow[]
|
|
400
|
+
/** Probe the launcher's aligned update plan (read-only; never installs). */
|
|
401
|
+
probeUpdate(): Promise<LauncherUpdateStatus>
|
|
402
|
+
/** Run the launcher's aligned update; streams sanitized lines; resolves with the exit code. */
|
|
403
|
+
applyUpdate(onLine: (line: string) => void): Promise<number>
|
|
392
404
|
/** Registers the app's notice channel with the runner (called once on mount). */
|
|
393
405
|
onBridgeReady(bridge: { notify(text: string, tone?: NoticeTone): void }): void
|
|
394
406
|
/** Ordered enabled status items (/statusline config); the runner owns persistence. */
|
|
@@ -928,6 +940,10 @@ function statusToneProps(tone: StatusTone): {
|
|
|
928
940
|
return { color: inkColor(getPalette().brand), bold: undefined, dimColor: undefined }
|
|
929
941
|
case 'success':
|
|
930
942
|
return { color: inkColor(getPalette().code), bold: true, dimColor: undefined }
|
|
943
|
+
// The plan station's dedicated green: the status bar otherwise speaks in
|
|
944
|
+
// blues, but the fourth cycle station IS a distinct green mode marker.
|
|
945
|
+
case 'plan':
|
|
946
|
+
return { color: inkColor(getPalette().success), bold: true, dimColor: undefined }
|
|
931
947
|
case 'warn':
|
|
932
948
|
return { color: inkColor(getPalette().warn), bold: true, dimColor: undefined }
|
|
933
949
|
case 'error':
|
|
@@ -969,12 +985,15 @@ function deepseekWaveHues(tier: DeepseekWaveTier): readonly [RgbTriple, RgbTripl
|
|
|
969
985
|
: [palette.brandBright, palette.code, palette.brandMid]
|
|
970
986
|
}
|
|
971
987
|
|
|
972
|
-
function StatusLine({ facts, stats, busy, columns, items }: {
|
|
988
|
+
function StatusLine({ facts, stats, busy, columns, items, onRows }: {
|
|
973
989
|
facts: StatusFacts
|
|
974
990
|
stats: Parameters<typeof layoutStatusBar>[1]
|
|
975
991
|
busy: boolean
|
|
976
992
|
columns: number
|
|
977
993
|
items: readonly string[]
|
|
994
|
+
/** Reports the footer's exact physical row count (1 or 2) so the IME
|
|
995
|
+
* anchor ledger below the composer stays exact. */
|
|
996
|
+
onRows?: (rows: 1 | 2) => void
|
|
978
997
|
}): ReactElement {
|
|
979
998
|
const layout = useMemo(() => layoutStatusBar(facts, stats, Math.max(8, columns - 2), {
|
|
980
999
|
busy,
|
|
@@ -1000,6 +1019,13 @@ function StatusLine({ facts, stats, busy, columns, items }: {
|
|
|
1000
1019
|
columns,
|
|
1001
1020
|
items,
|
|
1002
1021
|
])
|
|
1022
|
+
// The IME anchor below the composer counts every row between the caret and
|
|
1023
|
+
// Ink's parked cursor, so the footer reports its exact row count one-way
|
|
1024
|
+
// (same contract as the composer's row report).
|
|
1025
|
+
const statusRowCount: 1 | 2 = layout.row2.left.length > 0 ? 2 : 1
|
|
1026
|
+
useEffect(() => {
|
|
1027
|
+
onRows?.(statusRowCount)
|
|
1028
|
+
}, [onRows, statusRowCount])
|
|
1003
1029
|
|
|
1004
1030
|
const renderRow = (row: { left: readonly StatusGroup[]; right: readonly StatusSpan[]; hint: boolean }, key: string, indent = 0): ReactElement => {
|
|
1005
1031
|
const leftParts: ReactElement[] = []
|
|
@@ -1638,38 +1664,52 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
1638
1664
|
onRetry(): void
|
|
1639
1665
|
onClose(): void
|
|
1640
1666
|
}): ReactElement {
|
|
1667
|
+
const [query, setQuery] = useState('')
|
|
1641
1668
|
const [cursor, setCursor] = useState(0)
|
|
1642
1669
|
const stdout = useStdout().stdout
|
|
1643
1670
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30)
|
|
1644
1671
|
const rows = directory?.rows ?? []
|
|
1672
|
+
// Direct-typing filter over provider and model names (the /mode contract):
|
|
1673
|
+
// printable keys edit the query, so a long directory is searchable without
|
|
1674
|
+
// a separate search mode. With a query active, q/r/g/G stop acting as
|
|
1675
|
+
// commands and become query text instead.
|
|
1676
|
+
const filtered = useMemo(() => {
|
|
1677
|
+
if (query === '') return rows
|
|
1678
|
+
const needle = query.toLowerCase()
|
|
1679
|
+
return rows.filter(row => `${row.provider} ${row.providerName ?? ''} ${row.model} ${row.modelName}`.toLowerCase().includes(needle))
|
|
1680
|
+
}, [rows, query])
|
|
1645
1681
|
const positioned = useRef(false)
|
|
1646
1682
|
|
|
1647
1683
|
useEffect(() => {
|
|
1648
1684
|
// Open ON the applied model (Codex resumes the previous pick): the first
|
|
1649
1685
|
// non-empty directory positions the cursor once, never on later refreshes.
|
|
1650
1686
|
if (positioned.current || rows.length === 0 || current === undefined) {
|
|
1651
|
-
if (
|
|
1687
|
+
if (filtered.length === 0) {
|
|
1652
1688
|
if (cursor !== 0) setCursor(0)
|
|
1653
1689
|
return
|
|
1654
1690
|
}
|
|
1655
|
-
if (cursor >=
|
|
1691
|
+
if (cursor >= filtered.length) setCursor(filtered.length - 1)
|
|
1656
1692
|
return
|
|
1657
1693
|
}
|
|
1658
1694
|
const index = rows.findIndex(row => `${row.provider}/${row.model}` === current)
|
|
1659
1695
|
if (index >= 0) {
|
|
1660
1696
|
positioned.current = true
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1697
|
+
// Position within the ACTIVE filter: the full-row index means nothing
|
|
1698
|
+
// when the query already narrowed the list while the directory loaded
|
|
1699
|
+
// (a late resolve must not place the cursor outside `filtered`).
|
|
1700
|
+
const filteredIndex = filtered.indexOf(rows[index]!)
|
|
1701
|
+
setCursor(filteredIndex >= 0 ? filteredIndex : 0)
|
|
1702
|
+
} else if (cursor >= filtered.length) {
|
|
1703
|
+
setCursor(Math.max(0, filtered.length - 1))
|
|
1664
1704
|
}
|
|
1665
|
-
}, [rows, cursor, current])
|
|
1705
|
+
}, [rows, filtered, cursor, current])
|
|
1666
1706
|
|
|
1667
1707
|
useInput((input, key) => {
|
|
1668
|
-
if (key.escape || input === 'q') {
|
|
1708
|
+
if (key.escape || (input === 'q' && query === '')) {
|
|
1669
1709
|
onClose()
|
|
1670
1710
|
return
|
|
1671
1711
|
}
|
|
1672
|
-
if (input === 'r') {
|
|
1712
|
+
if (input === 'r' && query === '') {
|
|
1673
1713
|
onRetry()
|
|
1674
1714
|
return
|
|
1675
1715
|
}
|
|
@@ -1682,13 +1722,19 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
1682
1722
|
onClose()
|
|
1683
1723
|
return
|
|
1684
1724
|
}
|
|
1685
|
-
|
|
1725
|
+
const next = editQuery(query, input, key)
|
|
1726
|
+
if (next !== undefined) {
|
|
1727
|
+
setQuery(next)
|
|
1728
|
+
setCursor(0)
|
|
1729
|
+
return
|
|
1730
|
+
}
|
|
1731
|
+
if (filtered.length === 0) return
|
|
1686
1732
|
if (key.upArrow) {
|
|
1687
|
-
setCursor(cursor > 0 ? cursor - 1 :
|
|
1733
|
+
setCursor(cursor > 0 ? cursor - 1 : filtered.length - 1)
|
|
1688
1734
|
return
|
|
1689
1735
|
}
|
|
1690
1736
|
if (key.downArrow) {
|
|
1691
|
-
setCursor(cursor <
|
|
1737
|
+
setCursor(cursor < filtered.length - 1 ? cursor + 1 : 0)
|
|
1692
1738
|
return
|
|
1693
1739
|
}
|
|
1694
1740
|
if (key.pageUp) {
|
|
@@ -1696,32 +1742,27 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
1696
1742
|
return
|
|
1697
1743
|
}
|
|
1698
1744
|
if (key.pageDown) {
|
|
1699
|
-
setCursor(current => Math.min(
|
|
1700
|
-
return
|
|
1701
|
-
}
|
|
1702
|
-
if (input === 'g') {
|
|
1703
|
-
setCursor(0)
|
|
1745
|
+
setCursor(current => Math.min(filtered.length - 1, current + Math.max(1, viewport.bodyRows - 1)))
|
|
1704
1746
|
return
|
|
1705
1747
|
}
|
|
1706
|
-
if (
|
|
1707
|
-
|
|
1708
|
-
return
|
|
1709
|
-
}
|
|
1710
|
-
if (key.return && rows[cursor] !== undefined) {
|
|
1711
|
-
onSelect(rows[cursor])
|
|
1748
|
+
if (key.return && filtered[cursor] !== undefined) {
|
|
1749
|
+
onSelect(filtered[cursor])
|
|
1712
1750
|
}
|
|
1713
1751
|
})
|
|
1714
1752
|
|
|
1715
1753
|
if (viewport.maxHeight === 0 || viewport.compact) {
|
|
1716
1754
|
const providers = onProviders === undefined ? '' : ' · tab providers'
|
|
1717
|
-
const state =
|
|
1755
|
+
const state = filtered.length === 0
|
|
1718
1756
|
? directory === undefined && error === undefined
|
|
1719
1757
|
? 'loading…'
|
|
1720
1758
|
: error !== undefined
|
|
1721
1759
|
? 'error'
|
|
1722
|
-
: 'no models'
|
|
1723
|
-
: `❯ ${
|
|
1724
|
-
|
|
1760
|
+
: query === '' ? 'no models' : `no match for '${singleLineText(query)}'`
|
|
1761
|
+
: `❯ ${filtered[cursor]?.modelName ?? filtered[cursor]?.model ?? ''}`
|
|
1762
|
+
const tail = query === ''
|
|
1763
|
+
? 'type to filter · r retry · esc/q close'
|
|
1764
|
+
: 'backspace edits · esc close'
|
|
1765
|
+
return createElement(Text, { wrap: 'truncate-end' }, truncateColumns(`/model · ${state}${providers} · ${tail}`, viewport.contentColumns))
|
|
1725
1766
|
}
|
|
1726
1767
|
|
|
1727
1768
|
const stateRows: ReactElement[] = directory === undefined && error === undefined
|
|
@@ -1742,23 +1783,27 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
1742
1783
|
)]),
|
|
1743
1784
|
...(rows.length === 0
|
|
1744
1785
|
? [createElement(Text, { key: 'empty', dimColor: true, wrap: 'truncate-end' }, ' no models available')]
|
|
1745
|
-
:
|
|
1786
|
+
: filtered.length === 0
|
|
1787
|
+
? [createElement(Text, { key: 'no-match', dimColor: true, wrap: 'truncate-end' }, truncateColumns(` no models match '${singleLineText(query)}'`, viewport.contentColumns))]
|
|
1788
|
+
: []),
|
|
1746
1789
|
]
|
|
1747
1790
|
// Measurement and rendering share the same physical-row budget: state
|
|
1748
1791
|
// messages consume body rows before selectable entries, as in Codex's
|
|
1749
1792
|
// list-selection views.
|
|
1750
1793
|
const visibleStateRows = stateRows.slice(0, viewport.bodyRows)
|
|
1751
1794
|
const rowBudget = Math.max(0, viewport.bodyRows - visibleStateRows.length)
|
|
1752
|
-
const first = selectionWindow(cursor,
|
|
1753
|
-
const visible = rowBudget === 0 ? [] :
|
|
1795
|
+
const first = selectionWindow(cursor, filtered.length, rowBudget)
|
|
1796
|
+
const visible = rowBudget === 0 ? [] : filtered.slice(first, first + rowBudget)
|
|
1754
1797
|
return createElement(
|
|
1755
1798
|
Box,
|
|
1756
1799
|
{ flexDirection: 'column', width: viewport.outerColumns, paddingX: 1, borderStyle: 'round', borderColor: inkColor(getPalette().brand) },
|
|
1757
|
-
createElement(Text, { color: inkColor(getPalette().brand), bold: true, wrap: 'truncate-end' }, truncateColumns(
|
|
1800
|
+
createElement(Text, { color: inkColor(getPalette().brand), bold: true, wrap: 'truncate-end' }, truncateColumns(query === ''
|
|
1801
|
+
? `/model — select model${rows.length === 0 ? '' : ` · ${cursor + 1}/${rows.length}`}`
|
|
1802
|
+
: `/model — select model · ${filtered.length} of ${rows.length} match '${singleLineText(query)}'`, viewport.contentColumns)),
|
|
1758
1803
|
createElement(PanelGap, { visible: viewport.gapRows > 0 }),
|
|
1759
1804
|
...visibleStateRows,
|
|
1760
1805
|
...visible.map((row) => {
|
|
1761
|
-
const index =
|
|
1806
|
+
const index = filtered.indexOf(row)
|
|
1762
1807
|
const capability = row.inputModalities?.includes('image') === true ? ' · image' : ''
|
|
1763
1808
|
const label = displayText(`${row.providerName} · ${row.modelName}${capability}`)
|
|
1764
1809
|
return createElement(
|
|
@@ -1772,7 +1817,9 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
1772
1817
|
)
|
|
1773
1818
|
}),
|
|
1774
1819
|
createElement(PanelGap, { visible: viewport.gapRows > 0 }),
|
|
1775
|
-
createElement(Text, { dimColor: true, wrap: 'truncate-end' }, dim(truncateColumns(
|
|
1820
|
+
createElement(Text, { dimColor: true, wrap: 'truncate-end' }, dim(truncateColumns(query === ''
|
|
1821
|
+
? `type to filter · ↑↓ move · pgup/pgdn page · enter select${onProviders === undefined ? '' : ' · tab providers'} · r retry · esc/q close`
|
|
1822
|
+
: `↑↓ move · pgup/pgdn page · enter select · backspace edits · esc close`, viewport.contentColumns))),
|
|
1776
1823
|
)
|
|
1777
1824
|
}
|
|
1778
1825
|
|
|
@@ -3187,9 +3234,12 @@ interface DraftFile extends FilePathInspection {
|
|
|
3187
3234
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
3188
3235
|
* box passes every key through untouched.
|
|
3189
3236
|
*/
|
|
3190
|
-
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles,
|
|
3237
|
+
function Input({ active, frozen, frozenHint, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openUpdate, openSchedule, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles, cycleMode, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, animations, applyAnimations, waveTier, waveStyle, maxRows, anchorRowsBelow, tabTitle, onEditorRows, onMenuRows, sessionKey }: {
|
|
3191
3238
|
active: boolean
|
|
3192
3239
|
frozen: boolean
|
|
3240
|
+
/** Frozen-band hint naming the surface that owns the keyboard; an empty
|
|
3241
|
+
* draft otherwise advertises typing that the composer cannot accept. */
|
|
3242
|
+
frozenHint?: string
|
|
3193
3243
|
busy: boolean
|
|
3194
3244
|
descriptors: readonly CommandDescriptor[]
|
|
3195
3245
|
skills: readonly SkillRow[]
|
|
@@ -3206,6 +3256,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3206
3256
|
openPermission(): void
|
|
3207
3257
|
openResume(): void
|
|
3208
3258
|
openPlugin(query?: string): void
|
|
3259
|
+
/** Open the /update panel (aligned upgrade surface). */
|
|
3260
|
+
openUpdate(): void
|
|
3261
|
+
/** Open the /schedule reminder panel (read-only catalog). */
|
|
3262
|
+
openSchedule(): void
|
|
3209
3263
|
openJobs(): void
|
|
3210
3264
|
openStatusline(): void
|
|
3211
3265
|
openTheme(): void
|
|
@@ -3243,7 +3297,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3243
3297
|
prepareImages(paths: readonly string[], signal?: AbortSignal): Promise<readonly ImageBlock[]>
|
|
3244
3298
|
inspectFiles(paths: readonly string[]): Promise<readonly FilePathInspection[]>
|
|
3245
3299
|
prepareFiles(paths: readonly string[], signal?: AbortSignal): Promise<readonly FileBlock[]>
|
|
3246
|
-
|
|
3300
|
+
cycleMode(): string
|
|
3247
3301
|
exportTranscript(argument: string): Promise<void>
|
|
3248
3302
|
renameTitle(argument: string): string
|
|
3249
3303
|
copyLastResponse(): Promise<string>
|
|
@@ -3275,14 +3329,25 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3275
3329
|
waveStyle: DeepseekWaveStyle | null
|
|
3276
3330
|
/** Maximum physical editor rows the composer may occupy (see composerMaxRows). */
|
|
3277
3331
|
maxRows: number
|
|
3332
|
+
/** Terminal rows below the composer the editor does not own: the status
|
|
3333
|
+
* footer and Ink's parked cursor row. The IME anchor adds these to the
|
|
3334
|
+
* caret's in-band offset to reach that parked position. */
|
|
3335
|
+
anchorRowsBelow: number
|
|
3336
|
+
/** The managed terminal tab label; re-asserted on terminal focus-in so a
|
|
3337
|
+
* background process sharing the console cannot keep it overwritten. */
|
|
3338
|
+
tabTitle: string
|
|
3278
3339
|
/** Reports the editor's current physical row count so the live budget stays exact. */
|
|
3279
3340
|
onEditorRows(rows: number): void
|
|
3280
3341
|
/** Reports the open completion menu's physical row count (0 when closed)
|
|
3281
3342
|
* for the same reason: the dynamic budget must reserve it, not overflow. */
|
|
3282
3343
|
onMenuRows(rows: number): void
|
|
3283
3344
|
}): ReactElement {
|
|
3284
|
-
const
|
|
3285
|
-
const
|
|
3345
|
+
const { stdout: inputStdout } = useStdout()
|
|
3346
|
+
const columns = inputStdout?.columns ?? 80
|
|
3347
|
+
const inputTerminalRows = inputStdout?.rows ?? 30
|
|
3348
|
+
// The managed tab label, kept current for the focus-in re-assert below.
|
|
3349
|
+
const tabTitleRef = useRef(tabTitle)
|
|
3350
|
+
tabTitleRef.current = tabTitle
|
|
3286
3351
|
const editorColumns = Math.max(1, columns - 6)
|
|
3287
3352
|
const stdin = useStdin().stdin
|
|
3288
3353
|
const focusReporting = isVsCodeTerminalEnv()
|
|
@@ -3384,6 +3449,14 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3384
3449
|
const input = focusReporting
|
|
3385
3450
|
? stripTerminalFocusEvents(normalized, focused => {
|
|
3386
3451
|
terminalFocusedRef.current = focused
|
|
3452
|
+
// Focus-in re-asserts the managed tab label on both channels: a
|
|
3453
|
+
// background process sharing this console (a test-runner worker,
|
|
3454
|
+
// for example) may have overwritten the console title while the
|
|
3455
|
+
// terminal was unfocused.
|
|
3456
|
+
if (focused && inputStdout !== undefined) {
|
|
3457
|
+
inputStdout.write(terminalTitleSequence(tabTitleRef.current))
|
|
3458
|
+
process.title = sanitizeTerminalTitle(tabTitleRef.current)
|
|
3459
|
+
}
|
|
3387
3460
|
})
|
|
3388
3461
|
: normalized
|
|
3389
3462
|
rawEditorTokens.current = tokenizeRawEditorChunk(input)
|
|
@@ -3732,20 +3805,16 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3732
3805
|
notify('image submission cancelled', 'warning')
|
|
3733
3806
|
}
|
|
3734
3807
|
|
|
3735
|
-
/**
|
|
3808
|
+
/** Cross history while an unchanged recalled draft rests its caret on
|
|
3809
|
+
* either text edge; between the edges (or inside ordinary drafts) the
|
|
3810
|
+
* arrows move through visual rows first. */
|
|
3736
3811
|
const navigateVertical = (direction: -1 | 1): void => {
|
|
3737
3812
|
const currentValue = valueRef.current
|
|
3738
3813
|
const currentCursor = cursorRef.current
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
cursorRef.current = next
|
|
3744
|
-
setCursor(next)
|
|
3745
|
-
resetCursorBlink()
|
|
3746
|
-
preferredColumnRef.current = preferred
|
|
3747
|
-
return
|
|
3748
|
-
}
|
|
3814
|
+
// History owns the arrows while an unchanged recalled draft rests its
|
|
3815
|
+
// caret on either text edge (start or end). Everywhere else - edited
|
|
3816
|
+
// drafts, interior carets, ordinary typing - the arrows move through
|
|
3817
|
+
// visual rows as plain editing.
|
|
3749
3818
|
if (recall.current.entries.length > 0
|
|
3750
3819
|
&& shouldRecallNavigate(currentValue, currentCursor, recall.current.lastRecalled, direction)) {
|
|
3751
3820
|
const step = direction < 0 ? recallOlder(recall.current, currentValue) : recallNewer(recall.current)
|
|
@@ -3757,10 +3826,24 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3757
3826
|
setValue(safe)
|
|
3758
3827
|
setCursor(safe.length)
|
|
3759
3828
|
preferredColumnRef.current = null
|
|
3760
|
-
|
|
3829
|
+
// Suppress the completion menu for the recalled text: a recalled
|
|
3830
|
+
// command would otherwise reopen the menu, whose Up/Down navigation
|
|
3831
|
+
// then traps the walk before it reaches older history entries. Any
|
|
3832
|
+
// edit re-opens the menu; submitting resets the dismissal.
|
|
3833
|
+
setDismissedMenuValue(safe)
|
|
3761
3834
|
}
|
|
3835
|
+
resetCursorBlink()
|
|
3836
|
+
return
|
|
3837
|
+
}
|
|
3838
|
+
const model = editorModel(currentValue, editorColumns)
|
|
3839
|
+
const preferred = preferredColumnRef.current ?? caretSite(model, currentCursor).column
|
|
3840
|
+
const next = moveCursorVertically(model, currentCursor, preferred, direction)
|
|
3841
|
+
if (next !== currentCursor) {
|
|
3842
|
+
cursorRef.current = next
|
|
3843
|
+
setCursor(next)
|
|
3844
|
+
resetCursorBlink()
|
|
3845
|
+
preferredColumnRef.current = preferred
|
|
3762
3846
|
}
|
|
3763
|
-
resetCursorBlink()
|
|
3764
3847
|
}
|
|
3765
3848
|
|
|
3766
3849
|
useStableInput((input, key) => {
|
|
@@ -3787,11 +3870,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3787
3870
|
}
|
|
3788
3871
|
return
|
|
3789
3872
|
}
|
|
3790
|
-
// Shift+Tab cycles the permission
|
|
3873
|
+
// Shift+Tab cycles the mode stations: permission presets, then the
|
|
3874
|
+
// plan station when the composition offers it (Claude-Code convention).
|
|
3791
3875
|
if (key.tab && key.shift) {
|
|
3792
3876
|
try {
|
|
3793
|
-
const
|
|
3794
|
-
if (
|
|
3877
|
+
const label = cycleMode()
|
|
3878
|
+
if (label !== '') notify(label)
|
|
3795
3879
|
} catch (error: unknown) {
|
|
3796
3880
|
notify(`permission change failed: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
|
3797
3881
|
}
|
|
@@ -3958,13 +4042,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
3958
4042
|
setDismissedMenuValue(undefined)
|
|
3959
4043
|
if (trimmed === '') return
|
|
3960
4044
|
dismissNotice()
|
|
3961
|
-
// Global recall records
|
|
3962
|
-
// commands,
|
|
3963
|
-
// the submission resets any active recall
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
}
|
|
4045
|
+
// Global recall records every submission - prompts and typed slash
|
|
4046
|
+
// commands share one history, so Up/Down and /history recall commands
|
|
4047
|
+
// exactly like prompts; the submission resets any active recall
|
|
4048
|
+
// browsing.
|
|
4049
|
+
recordLocal(text)
|
|
4050
|
+
recordHistory(text)
|
|
3968
4051
|
recall.current = beginRecall(recallSpace, '')
|
|
3969
4052
|
if (text === '/quit') {
|
|
3970
4053
|
quit()
|
|
@@ -4056,6 +4139,14 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
4056
4139
|
openPlugin(text.slice(7).trim())
|
|
4057
4140
|
return
|
|
4058
4141
|
}
|
|
4142
|
+
if (text === '/update') {
|
|
4143
|
+
openUpdate()
|
|
4144
|
+
return
|
|
4145
|
+
}
|
|
4146
|
+
if (text === '/schedule') {
|
|
4147
|
+
openSchedule()
|
|
4148
|
+
return
|
|
4149
|
+
}
|
|
4059
4150
|
if (text === '/jobs' || text.startsWith('/jobs ')) {
|
|
4060
4151
|
openJobs()
|
|
4061
4152
|
return
|
|
@@ -4315,6 +4406,17 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
4315
4406
|
useEffect(() => {
|
|
4316
4407
|
onEditorRows(editorRowCount)
|
|
4317
4408
|
}, [editorRowCount, onEditorRows])
|
|
4409
|
+
// IME anchor: park the real terminal cursor on the caret cell while the
|
|
4410
|
+
// composer accepts input. IME composition and candidate windows anchor to
|
|
4411
|
+
// that real cursor cell, which otherwise sits below the status row where
|
|
4412
|
+
// Ink leaves it, so Chinese input never appears at the caret. Frozen bands
|
|
4413
|
+
// release the anchor; the wrapper keeps Ink's relative erase ledger exact.
|
|
4414
|
+
const caretRowInWindow = Math.max(0, Math.min(caret.row - editorWindowStart, editorWindowRows - 1))
|
|
4415
|
+
useImeCursorAnchor(
|
|
4416
|
+
!frozen,
|
|
4417
|
+
imeCursorRowsUp({ editorWindowRows, caretRowInWindow, rowsBelowComposer: anchorRowsBelow }),
|
|
4418
|
+
2 + caret.column,
|
|
4419
|
+
)
|
|
4318
4420
|
// The menu's physical rows ride the same one-way report; the cleanup keeps
|
|
4319
4421
|
// the reserve from outliving the menu (unmount or inactive handoff).
|
|
4320
4422
|
useEffect(() => {
|
|
@@ -4350,7 +4452,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
4350
4452
|
))
|
|
4351
4453
|
}
|
|
4352
4454
|
const frozenLine = value === ''
|
|
4353
|
-
? 'type a message'
|
|
4455
|
+
? frozenHint ?? 'type a message'
|
|
4354
4456
|
: verboseLine(value, Math.max(1, columns - 6))
|
|
4355
4457
|
return band(createElement(
|
|
4356
4458
|
Text,
|
|
@@ -4833,6 +4935,8 @@ export function App(props: AppProps): ReactElement {
|
|
|
4833
4935
|
const [resumeOpen, setResumeOpen] = useState(false)
|
|
4834
4936
|
const [pluginOpen, setPluginOpen] = useState(false)
|
|
4835
4937
|
const [pluginQuery, setPluginQuery] = useState('')
|
|
4938
|
+
const [updateOpen, setUpdateOpen] = useState(false)
|
|
4939
|
+
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
4836
4940
|
const [jobsOpen, setJobsOpen] = useState(false)
|
|
4837
4941
|
const [statuslineOpen, setStatuslineOpen] = useState(false)
|
|
4838
4942
|
const [statuslineItems, setStatuslineItems] = useState<readonly StatusItemId[]>(() => parseStatuslineItems(props.statusline))
|
|
@@ -4912,7 +5016,7 @@ export function App(props: AppProps): ReactElement {
|
|
|
4912
5016
|
// panel keypress.
|
|
4913
5017
|
const inputActive = deleteConfirmId !== undefined
|
|
4914
5018
|
? !approvalPending && !questionPending
|
|
4915
|
-
: !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === undefined && !approvalPending && !questionPending
|
|
5019
|
+
: !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !updateOpen && !scheduleOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === undefined && !approvalPending && !questionPending
|
|
4916
5020
|
|
|
4917
5021
|
// Human questions outrank local inspectors. Close the lower modal instead
|
|
4918
5022
|
// of leaving an approval/question visible but keyboard-locked behind it.
|
|
@@ -4927,6 +5031,8 @@ export function App(props: AppProps): ReactElement {
|
|
|
4927
5031
|
setPermissionOpen(false)
|
|
4928
5032
|
setResumeOpen(false)
|
|
4929
5033
|
setPluginOpen(false)
|
|
5034
|
+
setUpdateOpen(false)
|
|
5035
|
+
setScheduleOpen(false)
|
|
4930
5036
|
setStatuslineOpen(false)
|
|
4931
5037
|
setThemeOpen(false)
|
|
4932
5038
|
setHistoryOpen(false)
|
|
@@ -5029,6 +5135,16 @@ export function App(props: AppProps): ReactElement {
|
|
|
5029
5135
|
const handleMenuRows = useCallback((rows: number): void => {
|
|
5030
5136
|
setMenuRows(current => (current === rows ? current : rows))
|
|
5031
5137
|
}, [])
|
|
5138
|
+
// The status footer's exact row count, reported one-way by StatusLine (the
|
|
5139
|
+
// second row renders only while it has content). The IME cursor anchor
|
|
5140
|
+
// counts every row between the composer caret and Ink's parked cursor: the
|
|
5141
|
+
// status footer plus Ink's own below-frame row. The gutter rows sit ABOVE
|
|
5142
|
+
// the composer and never enter this distance.
|
|
5143
|
+
const [statusBarRows, setStatusBarRows] = useState<1 | 2>(1)
|
|
5144
|
+
const handleStatusRows = useCallback((rows: 1 | 2): void => {
|
|
5145
|
+
setStatusBarRows(current => (current === rows ? current : rows))
|
|
5146
|
+
}, [])
|
|
5147
|
+
const imeRowsBelowComposer = statusBarRows + 1
|
|
5032
5148
|
const composerEditorCap = composerMaxRows(terminalRows)
|
|
5033
5149
|
// Bottom chrome is composer (2 borders + composerRows) + status (up to 2
|
|
5034
5150
|
// rows) + todo/agents/notice (3) = 8 resting rows, plus the historical
|
|
@@ -5040,6 +5156,10 @@ export function App(props: AppProps): ReactElement {
|
|
|
5040
5156
|
const dynamicRows = Math.max(1, terminalRows - 8 - MENU_RESERVE_ROWS - composerGutterRows - (composerRows - 1) - Math.max(0, menuRows - MENU_RESERVE_ROWS))
|
|
5041
5157
|
const streamingActive = view.streaming !== '' || view.streamingReasoning !== ''
|
|
5042
5158
|
const deepDivingVisible = busy && !streamingActive
|
|
5159
|
+
// Terminal tab label: "deepseek" until the session carries a name, then the
|
|
5160
|
+
// session title; cleared on unmount so the host shell regains its default.
|
|
5161
|
+
const tabTitle = view.title === '' ? DEFAULT_TERMINAL_TITLE : view.title
|
|
5162
|
+
useTerminalTitle(tabTitle)
|
|
5043
5163
|
const allLiveLines = useMemo(
|
|
5044
5164
|
() => view.entries.slice(settled).flatMap(
|
|
5045
5165
|
// Width shrinks with the real terminal (no 10-column floor: on a
|
|
@@ -5088,9 +5208,55 @@ export function App(props: AppProps): ReactElement {
|
|
|
5088
5208
|
: visibleLiveLines.slice(-liveAudit.allocation.live)
|
|
5089
5209
|
const auditedReasoningRows = liveAudit.allocation.reasoning
|
|
5090
5210
|
const auditedAnswerRows = liveAudit.allocation.answer
|
|
5091
|
-
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === undefined && !approvalPending && !questionPending
|
|
5211
|
+
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !updateOpen && !scheduleOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === undefined && !approvalPending && !questionPending
|
|
5092
5212
|
const inspectorVisible = verboseOpen && !approvalPending && !questionPending
|
|
5093
|
-
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || jobsOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || todosOpen || inspectorVisible || diffView !== undefined || approvalPending || questionPending
|
|
5213
|
+
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || updateOpen || scheduleOpen || jobsOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || todosOpen || inspectorVisible || diffView !== undefined || approvalPending || questionPending
|
|
5214
|
+
// The surface that currently owns the keyboard, named in the frozen band:
|
|
5215
|
+
// an empty composer under a panel must not advertise typing it cannot
|
|
5216
|
+
// accept — every key actually feeds the panel (which may or may not
|
|
5217
|
+
// filter with it), so the honest hint names the owner and the way out.
|
|
5218
|
+
const keyboardOwner = approvalPending
|
|
5219
|
+
? 'the approval prompt'
|
|
5220
|
+
: questionPending
|
|
5221
|
+
? 'the question'
|
|
5222
|
+
: diffView !== undefined
|
|
5223
|
+
? 'the diff review'
|
|
5224
|
+
: modelOpen
|
|
5225
|
+
? '/model'
|
|
5226
|
+
: helpOpen
|
|
5227
|
+
? '/help'
|
|
5228
|
+
: modeOpen
|
|
5229
|
+
? '/mode'
|
|
5230
|
+
: permissionOpen
|
|
5231
|
+
? '/permission'
|
|
5232
|
+
: resumeOpen
|
|
5233
|
+
? '/resume'
|
|
5234
|
+
: pluginOpen
|
|
5235
|
+
? '/plugin'
|
|
5236
|
+
: updateOpen
|
|
5237
|
+
? '/update'
|
|
5238
|
+
: scheduleOpen
|
|
5239
|
+
? '/schedule'
|
|
5240
|
+
: jobsOpen
|
|
5241
|
+
? '/jobs'
|
|
5242
|
+
: statuslineOpen
|
|
5243
|
+
? '/statusline'
|
|
5244
|
+
: themeOpen
|
|
5245
|
+
? '/theme'
|
|
5246
|
+
: historyOpen
|
|
5247
|
+
? '/history'
|
|
5248
|
+
: agentsOpen
|
|
5249
|
+
? '/agents'
|
|
5250
|
+
: subagentOpen
|
|
5251
|
+
? '/subagent'
|
|
5252
|
+
: todosOpen
|
|
5253
|
+
? '/todos'
|
|
5254
|
+
: inspectorVisible
|
|
5255
|
+
? 'history details'
|
|
5256
|
+
: undefined
|
|
5257
|
+
const frozenHint = keyboardOwner === undefined
|
|
5258
|
+
? undefined
|
|
5259
|
+
: `keys go to ${keyboardOwner} · esc ${approvalPending ? 'rejects' : questionPending ? 'cancels' : 'closes'}`
|
|
5094
5260
|
const closeInspector = useCallback((): void => {
|
|
5095
5261
|
setVerboseOpen(false)
|
|
5096
5262
|
}, [])
|
|
@@ -5492,6 +5658,17 @@ export function App(props: AppProps): ReactElement {
|
|
|
5492
5658
|
pluginOpen && !approvalPending && !questionPending
|
|
5493
5659
|
? createElement(PluginPanel, { load: props.loadPlugins, initialQuery: pluginQuery, close: () => setPluginOpen(false) })
|
|
5494
5660
|
: undefined,
|
|
5661
|
+
updateOpen && !approvalPending && !questionPending
|
|
5662
|
+
? createElement(UpdatePanel, {
|
|
5663
|
+
probe: props.probeUpdate,
|
|
5664
|
+
apply: props.applyUpdate,
|
|
5665
|
+
notify: (text: string, tone?: NoticeTone) => notify(text, tone),
|
|
5666
|
+
close: () => setUpdateOpen(false),
|
|
5667
|
+
})
|
|
5668
|
+
: undefined,
|
|
5669
|
+
scheduleOpen && !approvalPending && !questionPending
|
|
5670
|
+
? createElement(SchedulePanel, { rows: () => view.schedules, close: () => setScheduleOpen(false) })
|
|
5671
|
+
: undefined,
|
|
5495
5672
|
jobsOpen && !approvalPending && !questionPending
|
|
5496
5673
|
? createElement(JobsPanel, { load: props.loadJobs, close: () => setJobsOpen(false) })
|
|
5497
5674
|
: undefined,
|
|
@@ -5577,6 +5754,7 @@ export function App(props: AppProps): ReactElement {
|
|
|
5577
5754
|
createElement(Input, {
|
|
5578
5755
|
active: inputActive,
|
|
5579
5756
|
frozen: modalVisible,
|
|
5757
|
+
frozenHint,
|
|
5580
5758
|
busy,
|
|
5581
5759
|
descriptors,
|
|
5582
5760
|
skills,
|
|
@@ -5644,6 +5822,8 @@ export function App(props: AppProps): ReactElement {
|
|
|
5644
5822
|
openPermission: () => setPermissionOpen(true),
|
|
5645
5823
|
openResume: () => { setResumeDelete({ mode: false }); setResumeOpen(true) },
|
|
5646
5824
|
openPlugin: (query = '') => { setPluginQuery(query); setPluginOpen(true) },
|
|
5825
|
+
openUpdate: () => setUpdateOpen(true),
|
|
5826
|
+
openSchedule: () => setScheduleOpen(true),
|
|
5647
5827
|
openJobs: () => setJobsOpen(true),
|
|
5648
5828
|
openStatusline: () => setStatuslineOpen(true),
|
|
5649
5829
|
openTheme: () => setThemeOpen(true),
|
|
@@ -5698,7 +5878,7 @@ export function App(props: AppProps): ReactElement {
|
|
|
5698
5878
|
inspectFiles: props.inspectFiles,
|
|
5699
5879
|
prepareFiles: props.prepareFiles,
|
|
5700
5880
|
sessionKey: props.sessionKey,
|
|
5701
|
-
|
|
5881
|
+
cycleMode: props.cycleMode,
|
|
5702
5882
|
exportTranscript: props.exportTranscript,
|
|
5703
5883
|
renameTitle: props.renameTitle,
|
|
5704
5884
|
copyLastResponse: props.copyLastResponse,
|
|
@@ -5714,6 +5894,8 @@ export function App(props: AppProps): ReactElement {
|
|
|
5714
5894
|
waveTier,
|
|
5715
5895
|
waveStyle,
|
|
5716
5896
|
maxRows: composerEditorCap,
|
|
5897
|
+
anchorRowsBelow: imeRowsBelowComposer,
|
|
5898
|
+
tabTitle,
|
|
5717
5899
|
onEditorRows: handleEditorRows,
|
|
5718
5900
|
onMenuRows: handleMenuRows,
|
|
5719
5901
|
}),
|
|
@@ -5725,7 +5907,7 @@ export function App(props: AppProps): ReactElement {
|
|
|
5725
5907
|
branch: props.branch,
|
|
5726
5908
|
sessionId: props.sessionId,
|
|
5727
5909
|
title: view.title,
|
|
5728
|
-
plan: view.plan,
|
|
5910
|
+
plan: view.plan || props.pendingPlan === true,
|
|
5729
5911
|
permission: view.permission !== '' ? view.permission : props.permission,
|
|
5730
5912
|
sandbox: view.sandbox,
|
|
5731
5913
|
goal: view.goal === undefined ? undefined : { phase: view.goal.phase, rounds: view.goal.rounds, max: view.goal.max },
|
|
@@ -5734,6 +5916,7 @@ export function App(props: AppProps): ReactElement {
|
|
|
5734
5916
|
busy,
|
|
5735
5917
|
columns: terminalColumns,
|
|
5736
5918
|
items: statuslineItems,
|
|
5919
|
+
onRows: handleStatusRows,
|
|
5737
5920
|
}),
|
|
5738
5921
|
),
|
|
5739
5922
|
)
|