@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.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.md +171 -124
- package/bin/dsh-tui.js +748 -22
- package/cordis.patch.yml +38 -10
- package/cordis.yml +2 -0
- package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
- package/lib/types/adapter/channel/session-projection.js +2 -0
- package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui-policy.js +5 -1
- package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
- package/lib/types/adapter/kernel/host-facade.js +1 -0
- package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
- package/lib/types/adapter/kernel/replay.js +3 -0
- package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
- package/lib/types/adapter/kernel/runtime.js +2 -0
- package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
- package/lib/types/adapter/kernel/slices/workspace.js +1 -0
- package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +66 -0
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
- package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
- package/lib/types/adapter/ports/workspace.d.ts +2 -0
- package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
- package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
- package/lib/types/adapter/upstream/workspace-driver.js +10 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +1 -0
- package/lib/types/components/ContextBarView.d.ts +15 -7
- package/lib/types/components/ContextBarView.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.js +26 -22
- package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
- package/lib/types/components/ImagePreviewOverlay.js +11 -5
- package/lib/types/components/Markdown.d.ts +11 -1
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +20 -2
- package/lib/types/components/MermaidDiagram.d.ts +13 -0
- package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
- package/lib/types/components/MermaidDiagram.js +61 -0
- package/lib/types/components/MessageList.d.ts +26 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +72 -7
- package/lib/types/components/PageMargin.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts +37 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +243 -28
- package/lib/types/components/ScrollbarGutter.d.ts +20 -5
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
- package/lib/types/components/ScrollbarGutter.js +88 -10
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +30 -28
- package/lib/types/components/Tooltip.d.ts.map +1 -1
- package/lib/types/components/Tooltip.js +45 -7
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +5 -7
- package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
- package/lib/types/components/messages/useImageInspection.js +19 -9
- package/lib/types/components/promptDraftCache.d.ts +102 -0
- package/lib/types/components/promptDraftCache.d.ts.map +1 -0
- package/lib/types/components/promptDraftCache.js +76 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
- package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
- package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +52 -5
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/bundled-presets.js +80 -0
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/background-action.js +52 -29
- package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
- package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding.js +78 -18
- package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
- package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
- package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
- package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
- package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
- package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +29 -19
- package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/reports.js +4 -12
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
- package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
- package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
- package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +1 -1
- package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/transcript.js +6 -12
- package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
- package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +67 -6
- package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/messages.js +15 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/settings.js +43 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/shell.js +11 -0
- package/lib/types/dsh-adapter/contract.d.ts +4 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +15 -6
- package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
- package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/ide-channel.js +556 -0
- package/lib/types/dsh-adapter/index.d.ts +18 -3
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +32 -5
- package/lib/types/dsh-adapter/plugin.d.ts +5 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +173 -42
- package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/preset-resolution.js +10 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessionTree.js +3 -2
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +6 -5
- package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
- package/lib/types/dsh-adapter/workspaces.js +116 -0
- package/lib/types/effortPrefs.d.ts +15 -3
- package/lib/types/effortPrefs.d.ts.map +1 -1
- package/lib/types/effortPrefs.js +37 -3
- package/lib/types/homePrefs.d.ts +15 -0
- package/lib/types/homePrefs.d.ts.map +1 -0
- package/lib/types/homePrefs.js +53 -0
- package/lib/types/i18n.d.ts +257 -296
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +78 -84
- package/lib/types/ink/colorize.d.ts +10 -0
- package/lib/types/ink/colorize.d.ts.map +1 -1
- package/lib/types/ink/colorize.js +21 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +12 -1
- package/lib/types/ink/dom.d.ts +1 -0
- package/lib/types/ink/dom.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
- package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +36 -1
- package/lib/types/ink/output.d.ts +10 -1
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +109 -17
- package/lib/types/ink/render-border.d.ts +6 -1
- package/lib/types/ink/render-border.d.ts.map +1 -1
- package/lib/types/ink/render-border.js +25 -9
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +48 -6
- package/lib/types/ink/renderer.d.ts +6 -0
- package/lib/types/ink/renderer.d.ts.map +1 -1
- package/lib/types/ink/renderer.js +2 -1
- package/lib/types/ink/selection.d.ts +60 -0
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +129 -0
- package/lib/types/ink/sixel-graphics.d.ts +19 -1
- package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
- package/lib/types/ink/sixel-graphics.js +124 -12
- package/lib/types/ink/terminal-image.d.ts +18 -1
- package/lib/types/ink/terminal-image.d.ts.map +1 -1
- package/lib/types/ink/terminal.d.ts +17 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +20 -1
- package/lib/types/screens/Chat.d.ts +17 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +284 -72
- package/lib/types/screens/SessionSupervisor.d.ts +84 -0
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/SessionSupervisor.js +350 -0
- package/lib/types/screens/SessionTree.js +2 -2
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +32 -9
- package/lib/types/screens/StatusLine.d.ts +13 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +36 -17
- package/lib/types/screens/StatusMetrics.d.ts +79 -22
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +153 -88
- package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/model.js +69 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
- package/lib/types/sessionMounts.d.ts +272 -0
- package/lib/types/sessionMounts.d.ts.map +1 -0
- package/lib/types/sessionMounts.js +706 -0
- package/lib/types/sessions/format.d.ts.map +1 -1
- package/lib/types/sessions/format.js +4 -1
- package/lib/types/sessions/resumeFailure.d.ts +38 -0
- package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
- package/lib/types/sessions/resumeFailure.js +58 -0
- package/lib/types/terminal-utils/mermaid.d.ts +44 -0
- package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
- package/lib/types/terminal-utils/mermaid.js +83 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +175 -19
- package/lib/types/tuiDisplayPrefs.d.ts +7 -9
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +31 -28
- package/lib/types/update.d.ts +61 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +194 -0
- package/lib/types/utils/imageResize.d.ts +90 -0
- package/lib/types/utils/imageResize.d.ts.map +1 -0
- package/lib/types/utils/imageResize.js +272 -0
- package/lib/types/utils/keymap.d.ts +5 -1
- package/lib/types/utils/keymap.d.ts.map +1 -1
- package/lib/types/utils/keymap.js +23 -0
- package/lib/types/workspaces.d.ts +2 -1
- package/lib/types/workspaces.d.ts.map +1 -1
- package/lib/types/workspaces.js +1 -1
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
- package/package.json +120 -103
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/agent.cordis.yml +15 -0
- package/lib/types/screens/AgentView.d.ts +0 -23
- package/lib/types/screens/AgentView.d.ts.map +0 -1
- package/lib/types/screens/AgentView.js +0 -546
- package/lib/types/screens/SessionBrowser.d.ts +0 -42
- package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
- package/lib/types/screens/SessionBrowser.js +0 -911
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { ChannelUi as Channel } from '../adapter/channel/ui-policy.js';
|
|
3
3
|
import type { ComposerImageRef } from '../dsh-adapter/channel.js';
|
|
4
4
|
import type { TranscriptImage } from '../dsh-adapter/transcript-images.js';
|
|
5
|
+
import { type PromptDraftCache } from './promptDraftCache.js';
|
|
5
6
|
/** Capabilities referenced by `text`, in first occurrence order. A raw token
|
|
6
7
|
* restored from disk/history has no sidecar entry and therefore stays inert. */
|
|
7
8
|
export declare function composerImageRefsForText(text: string, stagedByToken: ReadonlyMap<string, string>): ComposerImageRef[];
|
|
@@ -13,6 +14,8 @@ export declare function composerImageRefsForText(text: string, stagedByToken: Re
|
|
|
13
14
|
*/
|
|
14
15
|
export interface PromptController {
|
|
15
16
|
hasText(): boolean;
|
|
17
|
+
/** The draft text, for a caller that must not lose it (see {@link PromptDraftCache}). */
|
|
18
|
+
text(): string;
|
|
16
19
|
/** Current capability-backed draft images in token order, without reads. */
|
|
17
20
|
previewImages?(): readonly {
|
|
18
21
|
image: TranscriptImage;
|
|
@@ -39,10 +42,33 @@ export interface PromptController {
|
|
|
39
42
|
* Chat's working-turn Esc interrupt must yield in BOTH submodes. */
|
|
40
43
|
vimActive(): boolean;
|
|
41
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Owner-held slot for the composer draft, so a screen that unmounts the
|
|
47
|
+
* prompt (every early return in Chat) does not discard what the user typed.
|
|
48
|
+
*
|
|
49
|
+
* The composer writes ONE complete snapshot as it unmounts and consumes it on
|
|
50
|
+
* the way back; the shape and the reasoning live in `promptDraftCache.ts`.
|
|
51
|
+
*/
|
|
42
52
|
export interface PromptInputProps {
|
|
43
53
|
channel: Channel;
|
|
44
54
|
/** Keep the draft mounted while another prompt-slot panel owns the UI. */
|
|
45
55
|
suspended?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Owner-held slot for the unsent draft.
|
|
58
|
+
*
|
|
59
|
+
* The prompt owns its text in local state, and several screens REPLACE the
|
|
60
|
+
* conversation (the session screen, the session tree, settings, the jobs and
|
|
61
|
+
* subagent panels, the trajectory scene) — early returns that unmount this
|
|
62
|
+
* component and would take a half-written prompt down with it. Chat owns the
|
|
63
|
+
* slot, so the text, its caret AND its image bindings survive that unmount
|
|
64
|
+
* and come back when the composer does. The owner also drops it on a session
|
|
65
|
+
* change, so a draft can never leak into a different conversation.
|
|
66
|
+
*
|
|
67
|
+
* Nothing is written from render: a commit-time assignment would run before
|
|
68
|
+
* the restore effect has read the slot and overwrite the draft with the
|
|
69
|
+
* empty first value.
|
|
70
|
+
*/
|
|
71
|
+
draftCache?: PromptDraftCache;
|
|
46
72
|
/** Whether the `?` help menu is open (state lives in the Chat screen). */
|
|
47
73
|
helpOpen: boolean;
|
|
48
74
|
onToggleHelp(): void;
|
|
@@ -67,6 +93,16 @@ export interface PromptInputProps {
|
|
|
67
93
|
* opens the agent view (with text, ← moves the caret as usual).
|
|
68
94
|
*/
|
|
69
95
|
onBackgroundRequest?(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Open the session screen (the one `/resume`, `/agentview` and `/home`
|
|
98
|
+
* share) from the `⌂` entry at the head of the input row.
|
|
99
|
+
*
|
|
100
|
+
* Optional on purpose: the entry is rendered ONLY when this is provided, so
|
|
101
|
+
* hosts that mount the prompt without a session screen — and the layout
|
|
102
|
+
* regressions that pin this row's column budget — keep exactly the row they
|
|
103
|
+
* had before.
|
|
104
|
+
*/
|
|
105
|
+
onOpenSessions?(): void;
|
|
70
106
|
/**
|
|
71
107
|
* Background sessions waiting on the user (agent view "needs input" rows
|
|
72
108
|
* excluding this session); the prompt footer shows the
|
|
@@ -123,5 +159,5 @@ export interface PromptInputProps {
|
|
|
123
159
|
* working) interrupts the turn and delivers them right away; Ctrl+Enter
|
|
124
160
|
* aborts the turn and sends the current input immediately.
|
|
125
161
|
*/
|
|
126
|
-
export declare function PromptInput({ channel, suspended, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen, onDismissCaretPreview, }: PromptInputProps): React.JSX.Element | null;
|
|
162
|
+
export declare function PromptInput({ channel, suspended, draftCache, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, onOpenSessions, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen, onDismissCaretPreview, }: PromptInputProps): React.JSX.Element | null;
|
|
127
163
|
//# sourceMappingURL=PromptInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AA0BzB,OAAO,KAAK,EAAE,SAAS,IAAI,OAAO,EAAE,MAAM,iCAAiC,CAAA;AAC3E,OAAO,KAAK,EACV,gBAAgB,EAGjB,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;
|
|
1
|
+
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AA0BzB,OAAO,KAAK,EAAE,SAAS,IAAI,OAAO,EAAE,MAAM,iCAAiC,CAAA;AAC3E,OAAO,KAAK,EACV,gBAAgB,EAGjB,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAY1E,OAAO,EAIL,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAA;AA8H9B;gFACgF;AAChF,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACzC,gBAAgB,EAAE,CAWpB;AA+MD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,yFAAyF;IACzF,IAAI,IAAI,MAAM,CAAA;IACd,4EAA4E;IAC5E,aAAa,CAAC,IAAI,SAAS;QAAE,KAAK,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACtE,KAAK,IAAI,IAAI,CAAA;IACb;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO,CAAA;IAC/B,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAA;IACpB;yEACqE;IACrE,SAAS,IAAI,OAAO,CAAA;CAAC;AAEvB;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,kEAAkE;IAClE,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB;;;OAGG;IACH,mBAAmB,CAAC,IAAI,IAAI,CAAA;IAC5B;;;;;;;;OAQG;IACH,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB;;;;OAIG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IACxD;;;;;;;OAOG;IACH,YAAY,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAA;IAC7G;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,qBAAqB,CAAC,IAAI,IAAI,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,SAAiB,EACjB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,4BAA4B,EAC5B,aAAa,EACb,YAAY,EACZ,gBAAwB,EACxB,qBAAqB,GACtB,EAAE,gBAAgB,4BAivGlB"}
|
|
@@ -34,7 +34,26 @@ import { FileSuggestions } from './FileSuggestions.js';
|
|
|
34
34
|
import { HelpMenu } from './HelpMenu.js';
|
|
35
35
|
import { OverlayAbove } from './OverlayAbove.js';
|
|
36
36
|
import { SuggestionCard, cardContentWidth } from './SuggestionCard.js';
|
|
37
|
+
import { filterLiveImageBindings, isUsableDraftSnapshot, resolveBindingGeneration, } from './promptDraftCache.js';
|
|
37
38
|
const HISTORY_LIMIT = 50;
|
|
39
|
+
/**
|
|
40
|
+
* Visible text of the session-entry control at the head of the input row:
|
|
41
|
+
* U+2338 APL FUNCTIONAL SYMBOL QUAD COLON plus a separator space.
|
|
42
|
+
*
|
|
43
|
+
* The glyph is chosen for ONE property above all others: the layout model and
|
|
44
|
+
* the terminal cell table must agree on how many columns it occupies. `\u2338`
|
|
45
|
+
* is a plain single-cell symbol with no emoji presentation, so
|
|
46
|
+
* `stringWidth` and the terminal both say 1 + 1 = 2 and
|
|
47
|
+
* {@link HOME_BUTTON_COLS} can be derived rather than guessed.
|
|
48
|
+
*
|
|
49
|
+
* The emoji house (`U+1F3E0`) was tried and rejected on this exact ground: it
|
|
50
|
+
* costs the terminal ONE cell while `stringWidth` charges the row TWO, and
|
|
51
|
+
* appending VARIATION SELECTOR-15 (text presentation) does not change either
|
|
52
|
+
* number. That standing one-column disagreement put every caret-relative
|
|
53
|
+
* column in the composer off by one, so no offset compensation made it work.
|
|
54
|
+
*/
|
|
55
|
+
const HOME_BUTTON_TEXT = '⌸ ';
|
|
56
|
+
const HOME_BUTTON_COLS = stringWidth(HOME_BUTTON_TEXT);
|
|
38
57
|
/**
|
|
39
58
|
* Paste fold with a visible preview and no black box:
|
|
40
59
|
* a paste that leaves the input this big folds into a one-line chip
|
|
@@ -67,6 +86,9 @@ function sanitizeEditableText(text) {
|
|
|
67
86
|
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '');
|
|
68
87
|
}
|
|
69
88
|
const COMPOSER_IMAGE_TOKEN = /\[Image #\d+\]/gu;
|
|
89
|
+
/** Format label for one image media type, matching the image preview card's
|
|
90
|
+
* title (`JPEG`, `PNG`, `WEBP`, `GIF`). */
|
|
91
|
+
const mediaTypeLabel = (mediaType) => mediaType.replace(/^image\//u, '').replace(/\+xml$/u, '').toUpperCase();
|
|
70
92
|
/** Every `[Image #N]` in `text`, in order. */
|
|
71
93
|
function imageTokenSpans(text) {
|
|
72
94
|
const spans = [];
|
|
@@ -352,13 +374,70 @@ const DOUBLE_CLICK_MS = 500;
|
|
|
352
374
|
* working) interrupts the turn and delivers them right away; Ctrl+Enter
|
|
353
375
|
* aborts the turn and sends the current input immediately.
|
|
354
376
|
*/
|
|
355
|
-
export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, }) {
|
|
377
|
+
export function PromptInput({ channel, suspended = false, draftCache, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, onOpenSessions, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, }) {
|
|
356
378
|
const [themeName] = useTheme();
|
|
357
379
|
// Raw stdout writer for OSC 52 clipboard writes (selection copy) — must
|
|
358
380
|
// bypass the frame pipeline; null outside a mounted Ink App.
|
|
359
381
|
const writeRaw = React.useContext(TerminalWriteContext);
|
|
382
|
+
// The composer owns its text in local state, so it starts EMPTY and adopts
|
|
383
|
+
// whatever draft the owner stored in a MOUNT-TIME effect below. Reading the
|
|
384
|
+
// store in this initializer instead looks equivalent and is not: a non-empty
|
|
385
|
+
// first frame leaves `repro-resume-position` red — resuming a session parks
|
|
386
|
+
// the transcript mid-history instead of pinning it to the newest message.
|
|
387
|
+
// Adopting after mount keeps the first frame identical to a fresh composer
|
|
388
|
+
// while still handing the draft back.
|
|
360
389
|
const [value, setValue] = React.useState('');
|
|
361
390
|
const [cursor, setCursor] = React.useState(0);
|
|
391
|
+
/**
|
|
392
|
+
* Adopt the owner's draft ONCE, after mount.
|
|
393
|
+
*
|
|
394
|
+
* This is where a screen swap gives the draft back: the slot outlives this
|
|
395
|
+
* component, so a remount picks up what the user had written — text, caret,
|
|
396
|
+
* the image bindings behind the visible `[Image #N]` tokens, and the edit
|
|
397
|
+
* state around them (fold chip, fullscreen editor, vim mode).
|
|
398
|
+
*
|
|
399
|
+
* It runs as an effect rather than as the `useState` initial value on
|
|
400
|
+
* purpose. A composer whose FIRST frame is already non-empty moves the
|
|
401
|
+
* transcript's restored scroll position (`repro-resume-position`); adopting
|
|
402
|
+
* after the first commit keeps that frame identical to a fresh composer. The
|
|
403
|
+
* slot is consumed here, and the composer never writes to it while mounted,
|
|
404
|
+
* so the empty first value cannot overwrite the draft before this reads it.
|
|
405
|
+
*/
|
|
406
|
+
const adoptDraft = React.useRef(true);
|
|
407
|
+
React.useEffect(() => {
|
|
408
|
+
if (draftCache === undefined || !adoptDraft.current)
|
|
409
|
+
return;
|
|
410
|
+
adoptDraft.current = false;
|
|
411
|
+
const snapshot = draftCache.current;
|
|
412
|
+
draftCache.current = null;
|
|
413
|
+
if (!isUsableDraftSnapshot(snapshot, String(channel.agentId), resolveBindingGeneration(channel)))
|
|
414
|
+
return;
|
|
415
|
+
const text = snapshot.value;
|
|
416
|
+
// Carried edit state, restored ahead of the text: the chip, the
|
|
417
|
+
// fullscreen editor and the vim mode/submode come back with the draft —
|
|
418
|
+
// and they come back even with NO text, because they are modes rather
|
|
419
|
+
// than content. Fold ranges are safe by the CAPTURE invariant, not by
|
|
420
|
+
// restore order: a snapshot's block, when set, always sits inside the
|
|
421
|
+
// snapshot's own text (setInput keeps or drops it atomically), and at
|
|
422
|
+
// mount the caret is 0 so updateFoldBlock's caret-drag clamp cannot fire
|
|
423
|
+
// here. The transient state around them — editor scroll, vim undo stack,
|
|
424
|
+
// selection — does not come back.
|
|
425
|
+
updateFoldBlock(snapshot.foldBlock);
|
|
426
|
+
expandedRef.current = snapshot.expanded;
|
|
427
|
+
setExpanded(snapshot.expanded);
|
|
428
|
+
vimEnabledRef.current = snapshot.vimEnabled;
|
|
429
|
+
setVimEnabled(snapshot.vimEnabled);
|
|
430
|
+
vimInsertRef.current = snapshot.vimInsert;
|
|
431
|
+
setVimInsert(snapshot.vimInsert);
|
|
432
|
+
if (text === '')
|
|
433
|
+
return;
|
|
434
|
+
const restoredCursor = normalizeCursorOffset(text, snapshot.cursor);
|
|
435
|
+
replaceDraftImages(filterLiveImageBindings(snapshot.images, stageId => channel.hasStagedImage?.(stageId) === true).map(([token, stageId]) => ({ token, stageId })));
|
|
436
|
+
valueRef.current = text;
|
|
437
|
+
cursorRef.current = restoredCursor;
|
|
438
|
+
setValue(text);
|
|
439
|
+
setCursor(restoredCursor);
|
|
440
|
+
}, [draftCache]);
|
|
362
441
|
/**
|
|
363
442
|
* Mouse text selection: UTF-16 offsets [start, end) in `value`, snapped
|
|
364
443
|
* to grapheme boundaries, start ≤ end. Null = no selection. Created by
|
|
@@ -403,7 +482,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
403
482
|
const foldBlockRef = React.useRef(null);
|
|
404
483
|
/**
|
|
405
484
|
* Fullscreen draft editor (`expandEditor`, default Ctrl+Shift+E, or the
|
|
406
|
-
*
|
|
485
|
+
* ✎ affordance at the end of the input row). While expanded the SAME
|
|
407
486
|
* editing state renders into the PromptEditorLayer cover (published via
|
|
408
487
|
* setPromptEditorNode each render): Enter inserts a newline, Ctrl+Enter
|
|
409
488
|
* submits, Esc collapses. The fold chip is bypassed (full text shown).
|
|
@@ -424,14 +503,16 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
424
503
|
const prevExpandedRef = React.useRef(false);
|
|
425
504
|
/**
|
|
426
505
|
* Feature gate (settings `dsh-tui.expandEditor`, on by default; a mock
|
|
427
|
-
* channel without the field also reads as on). Off hides the
|
|
506
|
+
* channel without the field also reads as on). Off hides the ✎
|
|
428
507
|
* affordance and refuses the shortcut — the editor cannot open.
|
|
429
508
|
*/
|
|
430
509
|
const expandEnabled = channel.expandEditor !== false;
|
|
431
510
|
/** Latest expanded viewport metrics for the useInput wheel branch. */
|
|
432
511
|
const editorViewportRef = React.useRef(null);
|
|
433
|
-
/** Hover state of the
|
|
512
|
+
/** Hover state of the ⤢/✎ expand affordance in the input row. */
|
|
434
513
|
const [expandHovered, setExpandHovered] = React.useState(false);
|
|
514
|
+
/** Hover state of the ⌂ session-list affordance at the head of the row. */
|
|
515
|
+
const [homeHovered, setHomeHovered] = React.useState(false);
|
|
435
516
|
/** Pointer over the input box (drives the hover peek card). */
|
|
436
517
|
const [hovered, setHovered] = React.useState(false);
|
|
437
518
|
/** 120ms grace so the pointer crossing the input border row from the
|
|
@@ -550,6 +631,10 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
550
631
|
syncImageGeneration();
|
|
551
632
|
valueRef.current = value;
|
|
552
633
|
cursorRef.current = cursor;
|
|
634
|
+
// Nothing is written to the owner's draft slot here — see the unmount
|
|
635
|
+
// hand-off below. A commit-time write would run BEFORE the restore effect
|
|
636
|
+
// has consumed the slot, so the composer's own first (empty) value would
|
|
637
|
+
// erase the draft it just came back for.
|
|
553
638
|
// Publish the live controller (fresh closure over `value` every render).
|
|
554
639
|
// A prompt-slot panel withdraws the handle in the same commit: external
|
|
555
640
|
// injection must not append/submit a hidden command draft while it waits
|
|
@@ -563,6 +648,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
563
648
|
}
|
|
564
649
|
controllerRef.current = {
|
|
565
650
|
hasText: () => value.length > 0,
|
|
651
|
+
text: () => valueRef.current,
|
|
566
652
|
previewImages: () => composerImageRefsForText(valueRef.current, draftImagesRef.current).flatMap(ref => {
|
|
567
653
|
const image = channel.stagedImage(ref.stageId);
|
|
568
654
|
return image === undefined ? [] : [{ image, title: ref.token.slice(1, -1) }];
|
|
@@ -660,9 +746,56 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
660
746
|
clearTimeout(escTimerRef.current);
|
|
661
747
|
if (hoverLeaveTimerRef.current)
|
|
662
748
|
clearTimeout(hoverLeaveTimerRef.current);
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
749
|
+
/**
|
|
750
|
+
* Hand the draft to the owner's slot as this component goes away.
|
|
751
|
+
*
|
|
752
|
+
* This is the ONLY write to that slot, and it belongs here: it happens at
|
|
753
|
+
* the instant a screen replaces the composer, so it can neither race the
|
|
754
|
+
* restore effect nor change the transcript's first frame.
|
|
755
|
+
*
|
|
756
|
+
* `advanceDraftRevision` invalidates any image read/stage still in
|
|
757
|
+
* flight, so a late continuation cannot bind a capability into a composer
|
|
758
|
+
* that no longer exists. `clearVimUndo` releases the capabilities that
|
|
759
|
+
* only the undo stack was holding: they are not part of the draft, and
|
|
760
|
+
* nothing will ever restore them once this composer is gone.
|
|
761
|
+
*
|
|
762
|
+
* The stageIds the DRAFT holds are deliberately NOT revoked — they are
|
|
763
|
+
* part of the snapshot the slot now keeps, and the restore filters out
|
|
764
|
+
* whatever the channel revoked in the meantime. Revoking them here is why
|
|
765
|
+
* an image draft used to come back as inert text.
|
|
766
|
+
*/
|
|
767
|
+
advanceDraftRevision();
|
|
768
|
+
clearVimUndo();
|
|
769
|
+
const images = [...draftImagesRef.current.entries()]
|
|
770
|
+
.map(([token, stageId]) => [token, stageId]);
|
|
771
|
+
draftImagesRef.current.clear();
|
|
772
|
+
if (draftCache === undefined) {
|
|
773
|
+
discardUnretainedImages(images.map(image => image[1]));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const text = valueRef.current;
|
|
777
|
+
// The edit state rides along even with nothing typed: vim mode and the
|
|
778
|
+
// fullscreen editor are MODES the user turned on, not content, and an
|
|
779
|
+
// empty composer must not drop them (`text === ''` alone used to skip
|
|
780
|
+
// the snapshot entirely, losing the vim badge on a round trip with an
|
|
781
|
+
// empty composer).
|
|
782
|
+
const editState = foldBlockRef.current !== null || expandedRef.current
|
|
783
|
+
|| vimEnabledRef.current;
|
|
784
|
+
if (text === '' && images.length === 0 && !editState) {
|
|
785
|
+
draftCache.current = null;
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
draftCache.current = {
|
|
789
|
+
ownerAgentId: String(channel.agentId),
|
|
790
|
+
bindingGeneration: resolveBindingGeneration(channel),
|
|
791
|
+
value: text,
|
|
792
|
+
cursor: cursorRef.current,
|
|
793
|
+
foldBlock: foldBlockRef.current,
|
|
794
|
+
expanded: expandedRef.current,
|
|
795
|
+
vimEnabled: vimEnabledRef.current,
|
|
796
|
+
vimInsert: vimInsertRef.current,
|
|
797
|
+
images,
|
|
798
|
+
};
|
|
666
799
|
};
|
|
667
800
|
}, []);
|
|
668
801
|
const { columns, rows: terminalRows } = useTerminalSize();
|
|
@@ -768,14 +901,17 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
768
901
|
onCaretImageRef.current = onCaretImage;
|
|
769
902
|
const lastCaretImageRef = React.useRef({ image: undefined, title: undefined });
|
|
770
903
|
/** Tell the caller which staged image the caret is on. `'caret'` reports
|
|
771
|
-
* only changes; `'click'` always reports (see onCaretImage).
|
|
904
|
+
* only changes; `'click'` always reports (see onCaretImage). "Same image"
|
|
905
|
+
* is the attachment id plus the token, not facade identity: this runs
|
|
906
|
+
* after every commit and the caller stores what it reports, so an
|
|
907
|
+
* identity-only difference would re-render the caller forever (#885). */
|
|
772
908
|
const reportCaretImage = (reason) => {
|
|
773
909
|
const report = onCaretImageRef.current;
|
|
774
910
|
if (report === undefined)
|
|
775
911
|
return;
|
|
776
912
|
const found = suspended ? undefined : caretImageAt(valueRef.current, cursorRef.current);
|
|
777
913
|
const last = lastCaretImageRef.current;
|
|
778
|
-
if (reason === 'caret' && last.image === found?.image && last.title === found?.title)
|
|
914
|
+
if (reason === 'caret' && last.image?.id === found?.image.id && last.title === found?.title)
|
|
779
915
|
return;
|
|
780
916
|
lastCaretImageRef.current = { image: found?.image, title: found?.title };
|
|
781
917
|
report(found?.image, found?.title, reason);
|
|
@@ -1231,7 +1367,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1231
1367
|
if (!tryRunCommand(value))
|
|
1232
1368
|
submitText(value);
|
|
1233
1369
|
};
|
|
1234
|
-
/** Expand/collapse the fullscreen editor (shortcut +
|
|
1370
|
+
/** Expand/collapse the fullscreen editor (shortcut + ✎ affordance).
|
|
1235
1371
|
* Expanding also DROPS any fold block: the fullscreen view shows and
|
|
1236
1372
|
* edits the full text, and the block's atomic-clamp semantics (caret
|
|
1237
1373
|
* pushed to its edges, selection clamped to one side) would contradict
|
|
@@ -1324,6 +1460,35 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1324
1460
|
imageStageChainRef.current = queued.then(() => undefined, () => undefined);
|
|
1325
1461
|
return queued;
|
|
1326
1462
|
};
|
|
1463
|
+
/** Success notice for one staged image. An adapted paste says what the user
|
|
1464
|
+
* actually got (stored dimensions, stored format, filled alpha) — naming it
|
|
1465
|
+
* is the difference between a reported adaptation and a silently rewritten
|
|
1466
|
+
* image. */
|
|
1467
|
+
const stagedImageNotice = (token, handle) => {
|
|
1468
|
+
const adjustment = handle.adjustment;
|
|
1469
|
+
if (adjustment === undefined)
|
|
1470
|
+
return t('input-image-pasted', { token });
|
|
1471
|
+
const details = [];
|
|
1472
|
+
if (adjustment.resized) {
|
|
1473
|
+
details.push(t('input-image-detail-resized', {
|
|
1474
|
+
width: adjustment.width,
|
|
1475
|
+
height: adjustment.height,
|
|
1476
|
+
}));
|
|
1477
|
+
}
|
|
1478
|
+
if (adjustment.mediaType !== adjustment.sourceMediaType) {
|
|
1479
|
+
const from = mediaTypeLabel(adjustment.sourceMediaType);
|
|
1480
|
+
const to = mediaTypeLabel(adjustment.mediaType);
|
|
1481
|
+
details.push(adjustment.flattened
|
|
1482
|
+
? t('input-image-detail-converted-flattened', { from, to })
|
|
1483
|
+
: t('input-image-detail-converted', { from, to }));
|
|
1484
|
+
}
|
|
1485
|
+
// An adjustment always describes a resize or a conversion; anything else
|
|
1486
|
+
// keeps the plain notice rather than an empty parenthetical. ' · ' joins
|
|
1487
|
+
// the clauses in both shipped languages.
|
|
1488
|
+
if (details.length === 0)
|
|
1489
|
+
return t('input-image-pasted', { token });
|
|
1490
|
+
return t('input-image-pasted-adjusted', { token, detail: details.join(' · ') });
|
|
1491
|
+
};
|
|
1327
1492
|
const discardStagedHandles = (handles) => {
|
|
1328
1493
|
for (const stageId of new Set(handles.map(handle => handle.stageId))) {
|
|
1329
1494
|
channel.discardStagedImage(stageId);
|
|
@@ -1411,7 +1576,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1411
1576
|
setFileSelected(0);
|
|
1412
1577
|
return;
|
|
1413
1578
|
}
|
|
1414
|
-
// ── 全屏草稿编辑(expandEditor,默认 Ctrl+Shift+E / 输入行
|
|
1579
|
+
// ── 全屏草稿编辑(expandEditor,默认 Ctrl+Shift+E / 输入行 ✎)─────
|
|
1415
1580
|
// 展开态拥有屏幕;Esc 收起(有选区时上面的 selection 分支已先行只清
|
|
1416
1581
|
// 选区)。滚轮不经此——编辑区的 onWheel 位置路由直接驱动滚动窗口。
|
|
1417
1582
|
if (key.escape && expandedRef.current) {
|
|
@@ -1497,7 +1662,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1497
1662
|
// Bind and insert share this synchronous continuation: setInput's
|
|
1498
1663
|
// sidecar pruning can never observe a bound-but-not-visible token.
|
|
1499
1664
|
insertClipboardAtCaret(`${token} `);
|
|
1500
|
-
channel.notify(
|
|
1665
|
+
channel.notify(stagedImageNotice(token, handle), { timeoutMs: 2500 });
|
|
1501
1666
|
})
|
|
1502
1667
|
.catch(() => {
|
|
1503
1668
|
if (!draftImageLeaseIsCurrent(lease))
|
|
@@ -1561,7 +1726,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1561
1726
|
const handle = await stageImagePath(content.path, lease);
|
|
1562
1727
|
const token = bindStagedImage(handle, lease);
|
|
1563
1728
|
insertClipboardAtCaret(`${token} `);
|
|
1564
|
-
channel.notify(
|
|
1729
|
+
channel.notify(stagedImageNotice(token, handle), { timeoutMs: 2500 });
|
|
1565
1730
|
});
|
|
1566
1731
|
}
|
|
1567
1732
|
catch (error) {
|
|
@@ -1605,9 +1770,14 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
1605
1770
|
// All bindings and their visible labels enter together;
|
|
1606
1771
|
// typing while an earlier file saves cannot prune one.
|
|
1607
1772
|
insertClipboardAtCaret(`${rendered.join(' ')} `);
|
|
1773
|
+
// A batch cannot itemise every image in one line, but it must
|
|
1774
|
+
// still say that some of them were not stored as pasted.
|
|
1775
|
+
const adapted = staged.filter(handle => handle.adjustment !== undefined).length;
|
|
1608
1776
|
channel.notify(boundTokens.length === 1
|
|
1609
|
-
?
|
|
1610
|
-
:
|
|
1777
|
+
? stagedImageNotice(boundTokens[0], staged[0])
|
|
1778
|
+
: adapted > 0
|
|
1779
|
+
? t('input-images-staged-adapted', { count: boundTokens.length, adapted })
|
|
1780
|
+
: t('input-images-staged', { count: boundTokens.length }), { timeoutMs: 2500 });
|
|
1611
1781
|
return true;
|
|
1612
1782
|
}
|
|
1613
1783
|
catch (error) {
|
|
@@ -2463,6 +2633,11 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
2463
2633
|
// the same row, so the wrap budget must shrink by its width or long lines
|
|
2464
2634
|
// would be clipped at the value box's right edge.
|
|
2465
2635
|
const vimBadgeCols = vimEnabled ? 7 : 0;
|
|
2636
|
+
// 行首会话入口占列:未传 onOpenSessions 时按钮整体不渲染,预算同步归还
|
|
2637
|
+
// (既有调用方的可用列数逐列不变)。用实测常量而不是 stringWidth:该字符
|
|
2638
|
+
// 是 U+1F3E0+VS15,stringWidth 按码点算 2(含 VS15 仍是 2),终端只给 1,
|
|
2639
|
+
// 见 HOME_BUTTON_COLS 的说明。
|
|
2640
|
+
const homeButtonCols = onOpenSessions === undefined ? 0 : HOME_BUTTON_COLS;
|
|
2466
2641
|
// 补全卡片边框与输入框 idle 边框同色(plan 模式下整套面板一起变 sage
|
|
2467
2642
|
// 绿)。`/color` 会话强调色优先于主题 promptBorder(plan 模式仍整体走
|
|
2468
2643
|
// sage 绿)。`?? ''` 防御最小 mock channel(只声明用到的字段的回归脚
|
|
@@ -2470,14 +2645,15 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
2470
2645
|
const sessionAccent = sessionColorHex(channel.sessionColor ?? '');
|
|
2471
2646
|
const promptAccent = channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'promptBorder');
|
|
2472
2647
|
// 展开态布局参数:编辑器独占整屏 —— 行号槽(宽度随逻辑行数伸缩)+
|
|
2473
|
-
// 圆角边框 2 + 两侧 padding 各 1
|
|
2474
|
-
//
|
|
2648
|
+
// 圆角边框 2 + 两侧 padding 各 1 占列,⌸ 入口 / ❯ 前缀 / vim 徽标 /
|
|
2649
|
+
// ⛶ 按钮全部让位;收起态额外扣掉行首 ⌸ 入口(未渲染时 0 列)与行尾
|
|
2650
|
+
// ⛶ 按钮的 2 列。
|
|
2475
2651
|
const editorLogicalLines = expanded ? value.split('\n').length : 1;
|
|
2476
2652
|
const editorNoWidth = Math.max(2, String(editorLogicalLines).length);
|
|
2477
2653
|
const editorGutterCols = editorNoWidth + 3;
|
|
2478
2654
|
const inputWidth = expanded
|
|
2479
2655
|
? Math.max(1, columns - 4 - editorGutterCols)
|
|
2480
|
-
: Math.max(1, columns - 3 - vimBadgeCols - (expandEnabled ? 2 : 0));
|
|
2656
|
+
: Math.max(1, columns - 3 - vimBadgeCols - homeButtonCols - (expandEnabled ? 2 : 0));
|
|
2481
2657
|
// 展开态无视折叠块:全屏编辑就是为了看全文(foldBlock 状态保留,
|
|
2482
2658
|
// 收起后折叠显示恢复)。
|
|
2483
2659
|
const block = expanded ? null : foldBlock;
|
|
@@ -2546,22 +2722,50 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
2546
2722
|
visibleCount = MAX_VISIBLE_LINES;
|
|
2547
2723
|
}
|
|
2548
2724
|
const visibleLines = visualLines.slice(windowStart, windowStart + visibleCount);
|
|
2725
|
+
// Caret-window jump: when the caret leaves MAX_VISIBLE_LINES (typing past
|
|
2726
|
+
// it, arrow-key walks through a long prompt) the whole visible band is
|
|
2727
|
+
// replaced in place — same-height rows, completely different text. The
|
|
2728
|
+
// per-cell diff that repaints that band is exactly the path that goes
|
|
2729
|
+
// haywire when the inline viewport is even one row out of sync with the
|
|
2730
|
+
// terminal's scrollback ("重叠变花": old rows bleeding through the new
|
|
2731
|
+
// ones). Same one-shot reanchor family as the shrink/floaters/editor
|
|
2732
|
+
// patches above — the window jump is a viewport-level discontinuity, not
|
|
2733
|
+
// an ordinary content edit.
|
|
2734
|
+
const prevWindowStartRef = React.useRef(windowStart);
|
|
2735
|
+
React.useLayoutEffect(() => {
|
|
2736
|
+
if (windowStart !== prevWindowStartRef.current) {
|
|
2737
|
+
prevWindowStartRef.current = windowStart;
|
|
2738
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
2739
|
+
ink?.invalidatePrevFrame();
|
|
2740
|
+
ink?.reanchorViewport();
|
|
2741
|
+
}
|
|
2742
|
+
}, [windowStart]);
|
|
2549
2743
|
// useInput 的滚轮分支需要这份几何(它在这些派生之前注册)。
|
|
2550
2744
|
if (expanded) {
|
|
2551
2745
|
editorViewportRef.current = { maxRows: editorMaxRows, total: visualLines.length };
|
|
2552
2746
|
}
|
|
2553
2747
|
// Folded chip content: block stats + first-line preview + hover hint,
|
|
2554
2748
|
// all pre-truncated to the input width (the row is one line, always).
|
|
2749
|
+
// `·` separators are U+30FB KATAKANA MIDDLE DOT, NOT U+00B7: the latter
|
|
2750
|
+
// is East-Asian-ambiguous (model 1 cell, CJK terminal fonts paint 2) and
|
|
2751
|
+
// this row's stats→preview→hint truncation arithmetic all runs through
|
|
2752
|
+
// stringWidth — a painted-wide separator shifts every segment right by
|
|
2753
|
+
// one column per separator and the row reads as overlapping text.
|
|
2754
|
+
// U+30FB is officially Ambiguous too, but get-east-asian-width hardcodes
|
|
2755
|
+
// it Wide and mainstream terminals (Western included) paint it 2 cells —
|
|
2756
|
+
// the model and the painted width agree in practice. A wcwidth-strict
|
|
2757
|
+
// Western terminal painting it 1 cell would leave a spare column, which
|
|
2758
|
+
// the `- 6` slack below absorbs.
|
|
2555
2759
|
const foldBadge = `▸ ${stats}`;
|
|
2556
2760
|
const foldHint = t('input-fold-hover');
|
|
2557
|
-
const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(
|
|
2761
|
+
const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(`・${foldHint}`) - 6;
|
|
2558
2762
|
const foldPreview = foldPreviewWidth >= 8
|
|
2559
2763
|
? truncateToWidth(foldText.split('\n')[0] ?? '', foldPreviewWidth)
|
|
2560
2764
|
: '';
|
|
2561
2765
|
// Expanded-state fold affordance: a `▾` prefix at the start of the FIRST
|
|
2562
2766
|
// row (only while the window is at the top and no block exists); its
|
|
2563
2767
|
// cells fold the whole input into a block again on click.
|
|
2564
|
-
const prefixLabel = `▾ ${stats}
|
|
2768
|
+
const prefixLabel = `▾ ${stats}・`;
|
|
2565
2769
|
const prefixCols = !block && !expanded && big && windowStart === 0 ? stringWidth(prefixLabel) : 0;
|
|
2566
2770
|
// Offset range [start, end) of every visual row — the SAME break rule as
|
|
2567
2771
|
// wrapToWidth — so the selection highlight can intersect each row. With a
|
|
@@ -2639,7 +2843,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
2639
2843
|
return (_jsxs(Box, { flexDirection: "row", onClick: (event) => {
|
|
2640
2844
|
event.stopImmediatePropagation();
|
|
2641
2845
|
updateFoldBlock(null);
|
|
2642
|
-
}, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: "
|
|
2846
|
+
}, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: "\u30FB" }), foldPreview !== '' && _jsx(Text, { wrap: "truncate-end", children: foldPreview }), _jsx(Text, { dimColor: true, children: `・${foldHint}` })] }, `fold-${absoluteLine}`));
|
|
2643
2847
|
}
|
|
2644
2848
|
const withPrefix = absoluteLine === 0 && prefixCols > 0;
|
|
2645
2849
|
const text = withPrefix ? truncateToWidth(line, inputWidth - prefixCols) : line;
|
|
@@ -2723,12 +2927,16 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
2723
2927
|
// In the expanded editor the declared box starts at its outer edge
|
|
2724
2928
|
// (padding + gutter included), so those columns ride along and the
|
|
2725
2929
|
// clamp grows with them.
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2930
|
+
//
|
|
2931
|
+
// The declared column is relative to the VALUE BOX, and `caretVisualCol`
|
|
2932
|
+
// is already measured in that box's cell space: the text box IS the text
|
|
2933
|
+
// run, so nothing that renders before it — the session entry, the `❯ `
|
|
2934
|
+
// glyph, the fold prefix — shifts a cell inside it. Adding any of those
|
|
2935
|
+
// back double-counts them and parks the hardware cursor to the RIGHT of
|
|
2936
|
+
// the inverted caret cell (a 2-column miss shows up as a displaced IME
|
|
2937
|
+
// preedit target). Only the expanded editor declares against a wider box
|
|
2938
|
+
// (gutter + padding), so only that branch adds columns.
|
|
2939
|
+
column: Math.min(caretVisualCol + (expanded ? editorGutterCols + 1 : 0), expanded ? editorGutterCols + 1 + inputWidth : inputWidth),
|
|
2732
2940
|
active: !suspended && !selectionActive,
|
|
2733
2941
|
});
|
|
2734
2942
|
/**
|
|
@@ -3063,7 +3271,14 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
|
|
|
3063
3271
|
: undefined, rows: peekVisualLines.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, index))), onRowPick: () => {
|
|
3064
3272
|
updateFoldBlock(null);
|
|
3065
3273
|
setHovered(false);
|
|
3066
|
-
} }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [
|
|
3274
|
+
} }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [onOpenSessions !== undefined && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
|
|
3275
|
+
event.stopImmediatePropagation();
|
|
3276
|
+
onOpenSessions?.();
|
|
3277
|
+
}, onMouseEnter: () => {
|
|
3278
|
+
setHomeHovered(true);
|
|
3279
|
+
}, onMouseLeave: () => {
|
|
3280
|
+
setHomeHovered(false);
|
|
3281
|
+
}, children: _jsx(Text, { dimColor: !homeHovered, bold: homeHovered, color: homeHovered ? promptAccent : undefined, children: HOME_BUTTON_TEXT }) })), _jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), vimEnabled && (_jsxs(Text, { bold: true, color: vimInsert ? 'success' : 'warning', children: [vimInsert ? 'INSERT' : 'NORMAL', " "] })), _jsx(Box, { ref: expanded ? undefined : valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, onDragStart: handleDragStart, onDragMove: handleDragMove, onDragEnd: handleDragEnd, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 + homeButtonCols })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) }), expandEnabled && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
|
|
3067
3282
|
event.stopImmediatePropagation();
|
|
3068
3283
|
toggleExpand();
|
|
3069
3284
|
}, onMouseEnter: () => {
|
|
@@ -10,16 +10,31 @@ import { type ScrollBoxHandle } from '../ui.js';
|
|
|
10
10
|
* - clicking the track scrolls the clicked position to the viewport top
|
|
11
11
|
* (classic scrollbar semantics — the thumb centers under the click
|
|
12
12
|
* through the follow-up renders);
|
|
13
|
+
* - dragging the track scrubs the transcript: the drag protocol's
|
|
14
|
+
* per-target localRow maps through the SAME trackScrollTop as a click
|
|
15
|
+
* (absolute mapping — drag to point, no grabbed thumb offset), so a
|
|
16
|
+
* drag is a continuous run of click-to-position jumps. The mapping only
|
|
17
|
+
* reads the render-time maxScroll/trackH and never re-reads scrollTop
|
|
18
|
+
* mid-gesture, so a throttled/clamped scrollTo cannot feed back into the
|
|
19
|
+
* pointer mapping (no jitter). Dragging the thumb therefore lands the
|
|
20
|
+
* pointer's row at the viewport top, exactly like clicking that row;
|
|
21
|
+
* - a landing (drag or the back-to-bottom affordance) breaks and re-pins
|
|
22
|
+
* sticky, which unmounts the chrome that had shrunk the transcript row;
|
|
23
|
+
* the re-pinning Ink pass follows the commit and fires no subscriber
|
|
24
|
+
* notify, so the gutter re-reads the handle once that frame lands (see
|
|
25
|
+
* the subscribe effect). Without it the thumb stays painted from the
|
|
26
|
+
* shrunken viewport and floats a few rows above the bottom until an
|
|
27
|
+
* unrelated render (hover) re-reads the handle;
|
|
13
28
|
* - the gutter is permanent while scrollable (Qwen's rule: an
|
|
14
29
|
* auto-hiding gutter that changes content width rewraps everything);
|
|
15
30
|
* hidden below 60 terminal columns or when the content fits (inline
|
|
16
31
|
* mode keeps the terminal's native scrollback);
|
|
17
32
|
* - NoSelect fences the glyphs out of click-drag text selection; the
|
|
18
|
-
* wheel over the gutter scrolls the transcript.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
33
|
+
* wheel over the gutter scrolls the transcript. Because the track is a
|
|
34
|
+
* drag target, an unmodified left drag scrubs instead of selecting, and
|
|
35
|
+
* multi-clicks on the track no longer feed the select-line chain;
|
|
36
|
+
* Shift/Alt/Ctrl drags never open a drag session and keep the
|
|
37
|
+
* selection path.
|
|
23
38
|
*/
|
|
24
39
|
export declare function ScrollbarGutter({ handle, terminalWidth, }: {
|
|
25
40
|
handle: ScrollBoxHandle | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollbarGutter.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollbarGutter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"ScrollbarGutter.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollbarGutter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAqBpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,aAAa,GACd,EAAE;IACD,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,KAAK,CAAC,SAAS,CA6LlB"}
|