@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
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Box, Text, useInput, useTerminalSize } from '../ui.js';
|
|
4
|
-
import { Divider } from '../components/design-system/Divider.js';
|
|
5
|
-
import { HintLine } from '../components/design-system/HintLine.js';
|
|
6
|
-
import { SearchBox } from '../components/SearchBox.js';
|
|
7
|
-
import { SessionPreview } from '../components/sessions/SessionPreview.js';
|
|
8
|
-
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
9
|
-
import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
10
|
-
import { isMod, isPlainReturn } from '../utils/modifiers.js';
|
|
11
|
-
import { formatProject, formatWhen, spreadRow, tailWidth, truncateWidth } from '../sessions/format.js';
|
|
12
|
-
import { stringWidth } from '../ink/stringWidth.js';
|
|
13
|
-
import { t } from '../i18n.js';
|
|
14
|
-
import { ApprovalPanel } from '../components/approvals/ApprovalPanel.js';
|
|
15
|
-
import { SpinnerGlyph } from '../components/Spinner/SpinnerGlyph.js';
|
|
16
|
-
/** Rows the layout cannot do without: header, model/cwd line, dispatch
|
|
17
|
-
* input, hints. */
|
|
18
|
-
const MANDATORY_LINES = 4;
|
|
19
|
-
/** Terminal width below which the peek panel replaces the list. */
|
|
20
|
-
const SPLIT_MIN_COLUMNS = 100;
|
|
21
|
-
/** How long the second Ctrl+X stays armed as "delete". */
|
|
22
|
-
const STOP_DELETE_WINDOW_MS = 2000;
|
|
23
|
-
/** State presentation: the glyph and theme color per status. */
|
|
24
|
-
const STATUS_PRESENTATION = {
|
|
25
|
-
'needs-input': { glyph: '✻', color: 'warning' },
|
|
26
|
-
working: { glyph: '✽', color: 'suggestion' },
|
|
27
|
-
completed: { glyph: '✓', color: 'success' },
|
|
28
|
-
failed: { glyph: '✕', color: 'error' },
|
|
29
|
-
idle: { glyph: '∙' },
|
|
30
|
-
stopped: { glyph: '∙' },
|
|
31
|
-
};
|
|
32
|
-
/** The group order, from most to least urgent. */
|
|
33
|
-
const STATUS_ORDER = [
|
|
34
|
-
'needs-input', 'working', 'failed', 'completed', 'idle', 'stopped',
|
|
35
|
-
];
|
|
36
|
-
/** The state group header, resolved through `t` at render time so a
|
|
37
|
-
* `/lang` switch repaints it immediately. */
|
|
38
|
-
function statusLabel(status) {
|
|
39
|
-
switch (status) {
|
|
40
|
-
case 'needs-input': return t('agentview-state-needs-input');
|
|
41
|
-
case 'working': return t('agentview-state-working');
|
|
42
|
-
case 'completed': return t('agentview-state-completed');
|
|
43
|
-
case 'failed': return t('agentview-state-failed');
|
|
44
|
-
case 'idle': return t('agentview-state-idle');
|
|
45
|
-
case 'stopped': return t('agentview-state-stopped');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** A thrown value's message, for a notification that has to say something. */
|
|
49
|
-
function message(error) {
|
|
50
|
-
return error instanceof Error ? error.message : String(error);
|
|
51
|
-
}
|
|
52
|
-
/** Rendered width of a hint: `HintLine` strips the `**` emphasis markers. */
|
|
53
|
-
function hintWidth(text) {
|
|
54
|
-
return stringWidth(text.replace(/\*\*/gu, ''));
|
|
55
|
-
}
|
|
56
|
-
/** The widest hint that fits, cut only when even the shortest will not. */
|
|
57
|
-
function fitHint(candidates, budget) {
|
|
58
|
-
for (const candidate of candidates) {
|
|
59
|
-
if (hintWidth(candidate) <= budget)
|
|
60
|
-
return candidate;
|
|
61
|
-
}
|
|
62
|
-
return truncateWidth((candidates[candidates.length - 1] ?? '').replace(/\*\*/gu, ''), budget);
|
|
63
|
-
}
|
|
64
|
-
/** A minimal SessionSummary-shaped adapter so SessionPreview renders a row. */
|
|
65
|
-
function summaryForRow(row) {
|
|
66
|
-
return {
|
|
67
|
-
id: row.id,
|
|
68
|
-
kind: { kind: 'root' },
|
|
69
|
-
title: { text: row.title, source: 'fallback' },
|
|
70
|
-
cwd: row.cwd,
|
|
71
|
-
createdAt: row.createdAt,
|
|
72
|
-
updatedAt: row.updatedAt,
|
|
73
|
-
bytes: undefined,
|
|
74
|
-
hasPrompt: true,
|
|
75
|
-
agentPreset: undefined,
|
|
76
|
-
model: undefined,
|
|
77
|
-
label: row.summary,
|
|
78
|
-
branch: undefined,
|
|
79
|
-
childCount: 0,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* One row line: a state glyph (an animated spinner for working sessions), a
|
|
84
|
-
* SHORT name, then the session's reply/activity
|
|
85
|
-
* compressed to ONE line at the right of the name, and the age at the far
|
|
86
|
-
* right. The fold already flattens whitespace, so a multi-paragraph reply
|
|
87
|
-
* can never wrap the row; a summary that merely repeats the name (an
|
|
88
|
-
* untitled session whose activity is still its own prompt) is hidden
|
|
89
|
-
* instead of shown twice.
|
|
90
|
-
*/
|
|
91
|
-
function AgentViewRowLine({ row, width, focused, now, spinnerFrame, spinnerTime, }) {
|
|
92
|
-
const presentation = STATUS_PRESENTATION[row.status];
|
|
93
|
-
// The attached session with no title shows as "current
|
|
94
|
-
// session", and a session that never did anything shows
|
|
95
|
-
// "send a prompt to start" instead of a dash.
|
|
96
|
-
const untitled = row.title === 'untitled';
|
|
97
|
-
const title = untitled
|
|
98
|
-
? row.current ? t('agentview-current-session') : t('agentview-untitled')
|
|
99
|
-
: row.title;
|
|
100
|
-
const marker = row.current && !untitled ? ` ${t('agentview-current-marker')}` : '';
|
|
101
|
-
// The name column stays compact: a recorded title may be a long generated
|
|
102
|
-
// sentence, and the row's job is to identify the session, not to quote it.
|
|
103
|
-
const name = truncateWidth(title, Math.max(6, Math.min(28, Math.floor(width * 0.3))));
|
|
104
|
-
const nameWidth = stringWidth(name) + stringWidth(marker);
|
|
105
|
-
// The glyph column is two cells for every state (static glyph + space,
|
|
106
|
-
// spinner glyph + its box padding), so the arithmetic is state-free.
|
|
107
|
-
const leftWidth = 2 + nameWidth;
|
|
108
|
-
const right = formatWhen(row.updatedAt, now);
|
|
109
|
-
const rightWidth = stringWidth(right);
|
|
110
|
-
const summarySource = row.summary.length === 0
|
|
111
|
-
? (row.current || row.title === 'untitled') ? t('agentview-summary-empty') : ''
|
|
112
|
-
: row.summary === row.title ? '' : row.summary;
|
|
113
|
-
const summaryBudget = Math.max(0, width - leftWidth - rightWidth - 3);
|
|
114
|
-
const summary = summarySource.length > 0 && summaryBudget >= 8
|
|
115
|
-
? truncateWidth(summarySource, summaryBudget)
|
|
116
|
-
: '';
|
|
117
|
-
const gap = Math.max(1, width - leftWidth - stringWidth(summary) - rightWidth - 2);
|
|
118
|
-
return (_jsxs(Box, { flexDirection: "row", backgroundColor: focused ? 'userMessageBackgroundHover' : undefined, children: [row.status === 'working' ? (_jsx(SpinnerGlyph, { frame: spinnerFrame, messageColor: "suggestion", reducedMotion: false, time: spinnerTime })) : (_jsx(Text, { color: presentation.color, children: `${presentation.glyph} ` })), _jsx(Text, { color: presentation.color, bold: focused, children: `${name}${marker}` }), _jsx(Text, { dimColor: true, children: `${' '.repeat(gap)}${summary}` }), _jsx(Text, { dimColor: true, children: ` ${right}` })] }));
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* The agent view screen — `/agentview`, `/bg`. Replaces the conversation
|
|
122
|
-
* like the session browser does; every session keeps running behind it.
|
|
123
|
-
*/
|
|
124
|
-
export function AgentView({ channel, home, approval, onApprove, returnSessionId, onClose, }) {
|
|
125
|
-
const { columns, rows } = useTerminalSize();
|
|
126
|
-
const isTerminalFocused = useTerminalFocus();
|
|
127
|
-
const getRows = React.useCallback(() => channel.agentViewRows(), [channel]);
|
|
128
|
-
const subscribe = React.useCallback((listener) => channel.subscribeAgentView(listener), [channel]);
|
|
129
|
-
const agentRows = React.useSyncExternalStore(subscribe, getRows);
|
|
130
|
-
const [focusId, setFocusId] = React.useState(undefined);
|
|
131
|
-
const [mode, setMode] = React.useState('list');
|
|
132
|
-
const [renameText, setRenameText] = React.useState('');
|
|
133
|
-
const [dispatchText, setDispatchText] = React.useState('');
|
|
134
|
-
const [notice, setNotice] = React.useState();
|
|
135
|
-
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
136
|
-
const [peekOpen, setPeekOpen] = React.useState(false);
|
|
137
|
-
const [peek, setPeek] = React.useState([]);
|
|
138
|
-
const [peekLoading, setPeekLoading] = React.useState(false);
|
|
139
|
-
const [replyText, setReplyText] = React.useState('');
|
|
140
|
-
const [now, setNow] = React.useState(Date.now());
|
|
141
|
-
// React batches every parsed key from one stdin chunk. Lock actions in a
|
|
142
|
-
// ref so a repeated Enter cannot start the same async operation twice.
|
|
143
|
-
const actionPendingRef = React.useRef(false);
|
|
144
|
-
/**
|
|
145
|
-
* The delete arm: which session was just stopped and until when the second
|
|
146
|
-
* Ctrl+X still counts as "delete it". Bound to the STOPPED session's id —
|
|
147
|
-
* never to the focused row — because stopping re-sorts the list and the
|
|
148
|
-
* focus coordinate can land on a different row before the confirm lands.
|
|
149
|
-
*/
|
|
150
|
-
const stopArmRef = React.useRef(null);
|
|
151
|
-
// One clock per render pass: every relative time on screen must agree.
|
|
152
|
-
React.useEffect(() => {
|
|
153
|
-
const timer = setInterval(() => setNow(Date.now()), 1000);
|
|
154
|
-
return () => clearInterval(timer);
|
|
155
|
-
}, []);
|
|
156
|
-
// Working rows animate their glyph. The
|
|
157
|
-
// shared clock only runs while at least one row is working and pauses
|
|
158
|
-
// otherwise, so an idle view costs no extra ticks.
|
|
159
|
-
const workingCount = agentRows.filter(row => row.status === 'working').length;
|
|
160
|
-
const [, spinnerTime] = useAnimationFrame(workingCount > 0 ? 120 : null);
|
|
161
|
-
const spinnerFrame = Math.floor(spinnerTime / 120);
|
|
162
|
-
// The delete arm expires after its window without an explicit keystroke;
|
|
163
|
-
// any other way out of confirm-stop clears the arm at the exit site.
|
|
164
|
-
React.useEffect(() => {
|
|
165
|
-
if (mode !== 'confirm-stop')
|
|
166
|
-
return;
|
|
167
|
-
const arm = stopArmRef.current;
|
|
168
|
-
const remaining = (arm?.deadline ?? 0) - Date.now();
|
|
169
|
-
const timer = setTimeout(() => {
|
|
170
|
-
stopArmRef.current = null;
|
|
171
|
-
setMode('list');
|
|
172
|
-
}, Math.max(0, remaining));
|
|
173
|
-
return () => clearTimeout(timer);
|
|
174
|
-
}, [mode]);
|
|
175
|
-
/**
|
|
176
|
-
* Say something, on screen and in the transcript — both, like the session
|
|
177
|
-
* browser: the line below the list is what the user reads now, the
|
|
178
|
-
* transcript entry is what remains after they leave.
|
|
179
|
-
*/
|
|
180
|
-
const report = React.useCallback((text, tone) => {
|
|
181
|
-
setNotice({ text, tone });
|
|
182
|
-
channel.notify(text, tone === 'error' ? { color: 'error' } : {});
|
|
183
|
-
}, [channel]);
|
|
184
|
-
// Group rows by state in the canonical order; the flat id list is the
|
|
185
|
-
// focus coordinate (a session id survives re-sorts, an index does not).
|
|
186
|
-
const groups = React.useMemo(() => {
|
|
187
|
-
const byStatus = new Map();
|
|
188
|
-
for (const row of agentRows) {
|
|
189
|
-
const bucket = byStatus.get(row.status) ?? [];
|
|
190
|
-
bucket.push(row);
|
|
191
|
-
byStatus.set(row.status, bucket);
|
|
192
|
-
}
|
|
193
|
-
return STATUS_ORDER
|
|
194
|
-
.filter(status => byStatus.has(status))
|
|
195
|
-
.map(status => ({ status, rows: byStatus.get(status) }));
|
|
196
|
-
}, [agentRows]);
|
|
197
|
-
const flatIds = React.useMemo(() => groups.flatMap(group => group.rows.map(row => row.id)), [groups]);
|
|
198
|
-
const focusIndex = React.useMemo(() => {
|
|
199
|
-
const found = flatIds.indexOf(focusId ?? '');
|
|
200
|
-
return found >= 0 ? found : 0;
|
|
201
|
-
}, [flatIds, focusId]);
|
|
202
|
-
const focused = flatIds.length > 0 ? agentRows.find(row => row.id === flatIds[focusIndex]) : undefined;
|
|
203
|
-
// Preview follows the cursor. Keyed on the id so arrowing past a row does
|
|
204
|
-
// not refetch the one it came from, and guarded on unmount so a slow read
|
|
205
|
-
// landing after the cursor moved cannot overwrite a newer preview.
|
|
206
|
-
React.useEffect(() => {
|
|
207
|
-
if (!peekOpen || focused === undefined)
|
|
208
|
-
return;
|
|
209
|
-
let live = true;
|
|
210
|
-
setPeekLoading(true);
|
|
211
|
-
void channel
|
|
212
|
-
.peekAgentSession(focused.id)
|
|
213
|
-
.then(entries => (live ? entries : undefined))
|
|
214
|
-
.catch(() => (live ? [] : undefined))
|
|
215
|
-
.then((entries) => {
|
|
216
|
-
if (entries === undefined)
|
|
217
|
-
return;
|
|
218
|
-
setPeek(entries);
|
|
219
|
-
setPeekLoading(false);
|
|
220
|
-
});
|
|
221
|
-
return () => {
|
|
222
|
-
live = false;
|
|
223
|
-
};
|
|
224
|
-
}, [channel, peekOpen, focused?.id]);
|
|
225
|
-
const step = (by, times = 1) => {
|
|
226
|
-
if (flatIds.length === 0)
|
|
227
|
-
return;
|
|
228
|
-
const next = Math.min(flatIds.length - 1, Math.max(0, focusIndex + by * times));
|
|
229
|
-
setFocusId(flatIds[next]);
|
|
230
|
-
};
|
|
231
|
-
const runAction = (action) => {
|
|
232
|
-
if (actionPendingRef.current)
|
|
233
|
-
return;
|
|
234
|
-
actionPendingRef.current = true;
|
|
235
|
-
void action().finally(() => {
|
|
236
|
-
actionPendingRef.current = false;
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
/** Dispatch the input as a new background session; optionally attach. */
|
|
240
|
-
const dispatch = (attach) => {
|
|
241
|
-
const text = dispatchText.trim();
|
|
242
|
-
if (text.length === 0)
|
|
243
|
-
return;
|
|
244
|
-
runAction(async () => {
|
|
245
|
-
const result = await channel.dispatchBackgroundAgent(text);
|
|
246
|
-
if (!result.ok)
|
|
247
|
-
return;
|
|
248
|
-
setDispatchText('');
|
|
249
|
-
report(t('agentview-dispatch-done'), 'info');
|
|
250
|
-
if (attach) {
|
|
251
|
-
const attached = await channel.attachToAgent(result.sessionId);
|
|
252
|
-
if (attached.ok) {
|
|
253
|
-
report(t('agentview-attached'), 'info');
|
|
254
|
-
onClose();
|
|
255
|
-
}
|
|
256
|
-
else if (attached.reason !== 'cancelled') {
|
|
257
|
-
const reason = attached.reason === 'failed' ? attached.error : t('agentview-attach-failed', { err: attached.reason });
|
|
258
|
-
report(t('agentview-attach-failed', { err: reason }), 'error');
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
};
|
|
263
|
-
const attach = (target) => {
|
|
264
|
-
runAction(async () => {
|
|
265
|
-
const result = await channel.attachToAgent(target.id);
|
|
266
|
-
if (result.ok) {
|
|
267
|
-
report(t('agentview-attached'), 'info');
|
|
268
|
-
onClose();
|
|
269
|
-
}
|
|
270
|
-
else if (result.reason !== 'cancelled') {
|
|
271
|
-
const reason = result.reason === 'failed' ? result.error : result.reason;
|
|
272
|
-
report(t('agentview-attach-failed', { err: reason }), 'error');
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
};
|
|
276
|
-
const stop = (target) => {
|
|
277
|
-
if (target.current) {
|
|
278
|
-
report(t('agentview-stop-failed'), 'error');
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
runAction(async () => {
|
|
282
|
-
const stopped = await channel.stopBackgroundAgent(target.id);
|
|
283
|
-
if (stopped) {
|
|
284
|
-
stopArmRef.current = { id: target.id, deadline: Date.now() + STOP_DELETE_WINDOW_MS };
|
|
285
|
-
setMode('confirm-stop');
|
|
286
|
-
report(t('agentview-stopped'), 'info');
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
report(t('agentview-stop-failed'), 'error');
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
const remove = (target) => {
|
|
294
|
-
setMode('list');
|
|
295
|
-
runAction(async () => {
|
|
296
|
-
try {
|
|
297
|
-
const deleted = await channel.deleteSession(target.id);
|
|
298
|
-
report(deleted ? t('agentview-deleted', { name: target.title }) : t('agentview-delete-failed', { name: target.title }), deleted ? 'info' : 'error');
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
report(t('agentview-delete-failed', { name: target.title }) + ` · ${message(error)}`, 'error');
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
const sendReply = (target) => {
|
|
306
|
-
const text = replyText.trim();
|
|
307
|
-
if (text.length === 0)
|
|
308
|
-
return;
|
|
309
|
-
runAction(async () => {
|
|
310
|
-
try {
|
|
311
|
-
const sent = await channel.replyToAgent(target.id, text);
|
|
312
|
-
if (sent) {
|
|
313
|
-
setReplyText('');
|
|
314
|
-
report(t('agentview-reply-sent'), 'info');
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
report(t('agentview-reply-failed', { err: message(error) }), 'error');
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
};
|
|
322
|
-
useInput((input, key) => {
|
|
323
|
-
if (helpOpen) {
|
|
324
|
-
if (key.escape || input === '?')
|
|
325
|
-
setHelpOpen(false);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// The parked approval panel owns the keyboard while it is up (its own
|
|
329
|
-
// useInput answers the ask; Enter/Esc must not attach or exit here).
|
|
330
|
-
if (approval !== null && mode === 'list')
|
|
331
|
-
return;
|
|
332
|
-
if (actionPendingRef.current)
|
|
333
|
-
return;
|
|
334
|
-
if (notice !== undefined)
|
|
335
|
-
setNotice(undefined);
|
|
336
|
-
if (mode === 'confirm-stop') {
|
|
337
|
-
// The arm names the session that was stopped — NOT the focused row.
|
|
338
|
-
// Only a second Ctrl+X deletes it; any other key (Enter included)
|
|
339
|
-
// cancels, exactly as the confirm hint promises. The previous code
|
|
340
|
-
// deleted `focused` on a plain Enter, and focus shifts when the
|
|
341
|
-
// stopped row re-sorts — that is what ate a user's main session.
|
|
342
|
-
const arm = stopArmRef.current;
|
|
343
|
-
if (arm !== null && key.ctrl && input === 'x' && Date.now() <= arm.deadline) {
|
|
344
|
-
const target = agentRows.find(row => row.id === arm.id);
|
|
345
|
-
stopArmRef.current = null;
|
|
346
|
-
if (target !== undefined)
|
|
347
|
-
remove(target);
|
|
348
|
-
else
|
|
349
|
-
setMode('list');
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
stopArmRef.current = null;
|
|
353
|
-
setMode('list');
|
|
354
|
-
}
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (mode === 'rename') {
|
|
358
|
-
if (isPlainReturn(key)) {
|
|
359
|
-
setMode('list');
|
|
360
|
-
const title = renameText.trim();
|
|
361
|
-
if (focused !== undefined && title.length > 0) {
|
|
362
|
-
runAction(async () => {
|
|
363
|
-
try {
|
|
364
|
-
const renamed = await channel.renameSessionTo(focused.id, title);
|
|
365
|
-
report(renamed ? t('agentview-renamed', { title }) : t('agentview-rename-failed'), renamed ? 'info' : 'error');
|
|
366
|
-
}
|
|
367
|
-
catch (error) {
|
|
368
|
-
report(`${t('agentview-rename-failed')} · ${message(error)}`, 'error');
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else if (key.escape) {
|
|
374
|
-
setMode('list');
|
|
375
|
-
}
|
|
376
|
-
else if (key.backspace || key.delete) {
|
|
377
|
-
setRenameText(text => text.slice(0, -1));
|
|
378
|
-
}
|
|
379
|
-
else if (!isMod(key) && !key.meta && input) {
|
|
380
|
-
setRenameText(text => text + input.replace(/[\r\n]+/g, ' '));
|
|
381
|
-
}
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (peekOpen) {
|
|
385
|
-
// The peek panel owns typing: arrows move rows, Enter sends the reply.
|
|
386
|
-
if (key.upArrow) {
|
|
387
|
-
step(-1);
|
|
388
|
-
}
|
|
389
|
-
else if (key.downArrow) {
|
|
390
|
-
step(1);
|
|
391
|
-
}
|
|
392
|
-
else if (key.pageUp || key.pageDown) {
|
|
393
|
-
step(key.pageDown ? 1 : -1, 3);
|
|
394
|
-
}
|
|
395
|
-
else if (isPlainReturn(key)) {
|
|
396
|
-
if (focused !== undefined)
|
|
397
|
-
sendReply(focused);
|
|
398
|
-
}
|
|
399
|
-
else if (key.escape || (input === ' ' && replyText.length === 0)) {
|
|
400
|
-
setPeekOpen(false);
|
|
401
|
-
setReplyText('');
|
|
402
|
-
}
|
|
403
|
-
else if (key.backspace || key.delete) {
|
|
404
|
-
setReplyText(text => text.slice(0, -1));
|
|
405
|
-
}
|
|
406
|
-
else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
|
|
407
|
-
const typed = input.replace(/\p{Cc}/gu, '');
|
|
408
|
-
if (typed.length > 0)
|
|
409
|
-
setReplyText(text => text + typed);
|
|
410
|
-
}
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (key.upArrow) {
|
|
414
|
-
step(-1);
|
|
415
|
-
}
|
|
416
|
-
else if (key.downArrow) {
|
|
417
|
-
step(1);
|
|
418
|
-
}
|
|
419
|
-
else if (key.wheelUp) {
|
|
420
|
-
step(-1);
|
|
421
|
-
}
|
|
422
|
-
else if (key.wheelDown) {
|
|
423
|
-
step(1);
|
|
424
|
-
}
|
|
425
|
-
else if (key.pageUp || key.pageDown) {
|
|
426
|
-
step(key.pageDown ? 1 : -1, Math.max(1, Math.floor(listHeight / 2)));
|
|
427
|
-
}
|
|
428
|
-
else if (isPlainReturn(key) && !key.shift) {
|
|
429
|
-
if (dispatchText.trim().length > 0)
|
|
430
|
-
dispatch(false);
|
|
431
|
-
else if (focused !== undefined)
|
|
432
|
-
attach(focused);
|
|
433
|
-
}
|
|
434
|
-
else if (isPlainReturn(key) && key.shift) {
|
|
435
|
-
if (dispatchText.trim().length > 0)
|
|
436
|
-
dispatch(true);
|
|
437
|
-
else if (focused !== undefined)
|
|
438
|
-
attach(focused);
|
|
439
|
-
}
|
|
440
|
-
else if (key.rightArrow) {
|
|
441
|
-
if (focused !== undefined)
|
|
442
|
-
attach(focused);
|
|
443
|
-
}
|
|
444
|
-
else if (input === ' ' && dispatchText.length === 0) {
|
|
445
|
-
// Space opens the peek panel for the focused row; with text in the
|
|
446
|
-
// input it types a space.
|
|
447
|
-
setPeekOpen(true);
|
|
448
|
-
}
|
|
449
|
-
else if (key.ctrl && input === 'x') {
|
|
450
|
-
if (focused !== undefined)
|
|
451
|
-
stop(focused);
|
|
452
|
-
}
|
|
453
|
-
else if (key.ctrl && input === 'r' && focused !== undefined) {
|
|
454
|
-
setRenameText(focused.title);
|
|
455
|
-
setMode('rename');
|
|
456
|
-
}
|
|
457
|
-
else if (key.ctrl && input === 'c') {
|
|
458
|
-
// Ctrl+C clears the dispatch input, twice exits.
|
|
459
|
-
if (dispatchText.length > 0)
|
|
460
|
-
setDispatchText('');
|
|
461
|
-
else
|
|
462
|
-
onClose();
|
|
463
|
-
}
|
|
464
|
-
else if (input === '?') {
|
|
465
|
-
setHelpOpen(true);
|
|
466
|
-
}
|
|
467
|
-
else if (key.escape) {
|
|
468
|
-
if (dispatchText.length > 0)
|
|
469
|
-
setDispatchText('');
|
|
470
|
-
else if (returnSessionId !== undefined) {
|
|
471
|
-
// The final Esc returns to the conversation that was
|
|
472
|
-
// backgrounded (the view's return target), not just closes.
|
|
473
|
-
runAction(async () => {
|
|
474
|
-
await channel.attachToAgent(returnSessionId);
|
|
475
|
-
onClose();
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
onClose();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
else if (key.backspace || key.delete) {
|
|
483
|
-
setDispatchText(text => text.slice(0, -1));
|
|
484
|
-
}
|
|
485
|
-
else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
|
|
486
|
-
const typed = input.replace(/\p{Cc}/gu, '');
|
|
487
|
-
if (typed.length > 0)
|
|
488
|
-
setDispatchText(text => text + typed);
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
// ── layout ────────────────────────────────────────────────────────────────
|
|
492
|
-
const approvalVisible = approval !== null && mode === 'list';
|
|
493
|
-
const approvalLines = approvalVisible ? 7 : 0;
|
|
494
|
-
const returnLines = returnSessionId !== undefined ? 1 : 0;
|
|
495
|
-
const extraLines = (mode === 'list' ? 0 : 1) + (notice === undefined ? 0 : 1) + approvalLines + returnLines;
|
|
496
|
-
const ruleBudget = Math.max(0, Math.min(2, rows - MANDATORY_LINES - extraLines));
|
|
497
|
-
const listHeight = Math.max(0, rows - MANDATORY_LINES - extraLines - ruleBudget);
|
|
498
|
-
const splitPreview = peekOpen && columns >= SPLIT_MIN_COLUMNS;
|
|
499
|
-
const soloPreview = peekOpen && columns < SPLIT_MIN_COLUMNS;
|
|
500
|
-
const previewWidth = splitPreview ? Math.min(56, Math.floor(columns * 0.42)) : columns;
|
|
501
|
-
const listWidth = Math.max(20, columns - (splitPreview ? previewWidth : 0));
|
|
502
|
-
// Header: title line, then "model · cwd", then the state counts.
|
|
503
|
-
const counts = [t('agentview-count-awaited', { n: agentRows.filter(row => row.status === 'needs-input').length })];
|
|
504
|
-
counts.push(t('agentview-count-working', { n: agentRows.filter(row => row.status === 'working').length }));
|
|
505
|
-
counts.push(t('agentview-count-completed', { n: agentRows.filter(row => row.status === 'completed').length }));
|
|
506
|
-
const failed = agentRows.filter(row => row.status === 'failed').length;
|
|
507
|
-
if (failed > 0)
|
|
508
|
-
counts.push(t('agentview-count-failed', { n: failed }));
|
|
509
|
-
const heading = ` ${t('agentview-title')}`;
|
|
510
|
-
const header = spreadRow(heading, counts.join(' · '), Math.max(0, columns - 1));
|
|
511
|
-
const subtitle = `${channel.model} · ${formatProject(channel.cwd, home)}`;
|
|
512
|
-
const inputBudget = Math.max(0, columns - 2);
|
|
513
|
-
// The confirm hint names the session the arm actually targets — which can
|
|
514
|
-
// differ from the focused row once the stopped row re-sorts.
|
|
515
|
-
const armedId = stopArmRef.current?.id;
|
|
516
|
-
const armedTarget = armedId === undefined
|
|
517
|
-
? undefined
|
|
518
|
-
: agentRows.find(row => row.id === armedId);
|
|
519
|
-
const hint = mode === 'confirm-stop' && armedTarget !== undefined
|
|
520
|
-
? fitHint([t('agentview-stop-confirm', { name: armedTarget.title })], inputBudget)
|
|
521
|
-
: mode === 'rename'
|
|
522
|
-
? fitHint([t('agentview-hint-rename')], inputBudget)
|
|
523
|
-
: fitHint([t('agentview-hint-list')], inputBudget);
|
|
524
|
-
// The help overlay replaces the whole screen while open.
|
|
525
|
-
if (helpOpen) {
|
|
526
|
-
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: "remember", bold: true, children: ` ${t('agentview-help-title')}` }) }), _jsx(Divider, { bleed: true }), _jsx(Box, { flexGrow: 1, flexShrink: 1, children: t('agentview-help').split('\n').map((line, index) => (_jsx(Text, { dimColor: true, children: ` ${line}` }, index))) }), _jsx(Divider, { bleed: true }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: t('agentview-hint-help') }) }) })] }));
|
|
527
|
-
}
|
|
528
|
-
// Visible slice of the grouped rows.
|
|
529
|
-
const windowStart = Math.max(0, Math.min(focusIndex, Math.max(0, flatIds.length - listHeight)));
|
|
530
|
-
const windowEnd = Math.min(flatIds.length, windowStart + listHeight);
|
|
531
|
-
const returnNotice = returnSessionId !== undefined;
|
|
532
|
-
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: truncateWidth(` ${subtitle}`, Math.max(0, columns - 2)) }) }), returnNotice && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: truncateWidth(t('agentview-bg-notice'), Math.max(0, columns - 2)) }) })), ruleBudget > 0 && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { bleed: true }) })), approvalVisible && (_jsx(Box, { flexShrink: 0, flexDirection: "column", height: approvalLines, children: _jsx(ApprovalPanel, { approval: approval, background: approval.agentId !== channel.agentId, onDecide: onApprove }) })), _jsxs(Box, { flexGrow: 1, flexShrink: 1, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [agentRows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('agentview-none'), listWidth - 2)}` })), groups.map((group) => {
|
|
533
|
-
const groupStart = groups
|
|
534
|
-
.slice(0, groups.indexOf(group))
|
|
535
|
-
.reduce((total, g) => total + g.rows.length, 0);
|
|
536
|
-
const groupEnd = groupStart + group.rows.length;
|
|
537
|
-
if (groupEnd <= windowStart || groupStart >= windowEnd)
|
|
538
|
-
return null;
|
|
539
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Text, { color: "planMode", bold: true, children: truncateWidth(` ${statusLabel(group.status)}`, listWidth - 2) }), group.rows.map((row, rowIndex) => {
|
|
540
|
-
const flatIndex = groupStart + rowIndex;
|
|
541
|
-
if (flatIndex < windowStart || flatIndex >= windowEnd)
|
|
542
|
-
return null;
|
|
543
|
-
return (_jsx(AgentViewRowLine, { row: row, width: listWidth - 2, focused: flatIndex === focusIndex, now: now, spinnerFrame: spinnerFrame, spinnerTime: spinnerTime }, row.id));
|
|
544
|
-
})] }, group.status));
|
|
545
|
-
})] })), (splitPreview || soloPreview) && focused !== undefined && (_jsxs(Box, { flexDirection: "column", width: previewWidth, flexShrink: 0, children: [_jsx(SessionPreview, { session: summaryForRow(focused), entries: peek, loading: peekLoading, width: previewWidth, height: Math.max(1, listHeight - (focused.live ? 1 : 0)), home: home, now: now }), focused.live && (_jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(replyText, inputBudget), isFocused: true, isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('agentview-hint-peek'), previewWidth - 4), prefix: "\u276F", width: Math.max(0, previewWidth - 2), borderless: true }) }))] }))] }), notice !== undefined && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: ` ${truncateWidth(notice.text, Math.max(0, columns - 2))}` }) })), mode === 'rename' && (_jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(renameText, inputBudget), isFocused: true, isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('agentview-rename-placeholder'), inputBudget), prefix: "\u270E", width: inputBudget, borderless: true }) })), mode !== 'rename' && (_jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(dispatchText, inputBudget), isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('agentview-input-placeholder'), inputBudget), prefix: "\u276F", width: inputBudget, borderless: true }) })), ruleBudget > 1 && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { bleed: true }) })), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: hint }) }) })] }));
|
|
546
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { ChannelUi as Channel } from '../adapter/channel/ui-policy.js';
|
|
3
|
-
/**
|
|
4
|
-
* The session browser — `/resume` as a screen of its own.
|
|
5
|
-
*
|
|
6
|
-
* The old picker was a panel of eight titles and a timestamp, and the reason
|
|
7
|
-
* it could not be more than that was never layout: the data behind it was five
|
|
8
|
-
* fields wide. With every session now arriving classified, sized, dated and
|
|
9
|
-
* attributed, the surface that shows them can do the job a person actually
|
|
10
|
-
* came for — find one conversation among many.
|
|
11
|
-
*
|
|
12
|
-
* What that means concretely:
|
|
13
|
-
*
|
|
14
|
-
* - Working-directory scope is visible and selectable. Wide terminals keep a
|
|
15
|
-
* directory rail beside the sessions; narrow terminals drill into the same
|
|
16
|
-
* directory list without sacrificing title width.
|
|
17
|
-
* - Search is always live in the active list. There is no mode to enter;
|
|
18
|
-
* typing filters, because the list is the search results.
|
|
19
|
-
* - Delegated sub-agent runs are folded away by default and revealed under
|
|
20
|
-
* their parents on demand. They are not noise to be deleted — they are the
|
|
21
|
-
* model's own work, and it is worth being able to open one — but they are
|
|
22
|
-
* not what "resume a conversation" means, and there are five of them for
|
|
23
|
-
* every conversation.
|
|
24
|
-
* - Sessions that hold no conversation are never listed, only counted, with
|
|
25
|
-
* one action to clear them.
|
|
26
|
-
* - The preview shows the end of a session, so "is this the one I was in the
|
|
27
|
-
* middle of" is answerable without resuming it.
|
|
28
|
-
*
|
|
29
|
-
* Steady-state listings reuse a revision-keyed digest cache. The rare legacy
|
|
30
|
-
* log whose opening prompt sits beyond the cheap window is scanned
|
|
31
|
-
* progressively and cached, so it recovers a real title without making every
|
|
32
|
-
* browser open pay for full history.
|
|
33
|
-
*/
|
|
34
|
-
export declare function SessionBrowser({ channel, home, sameProject, onClose, }: {
|
|
35
|
-
channel: Channel;
|
|
36
|
-
/** Home directory, for collapsing project paths to `~`. */
|
|
37
|
-
home: string;
|
|
38
|
-
/** Whether a stored cwd belongs to the same project as the live session. */
|
|
39
|
-
sameProject: (a: string, b: string) => boolean;
|
|
40
|
-
onClose: () => void;
|
|
41
|
-
}): React.ReactNode;
|
|
42
|
-
//# sourceMappingURL=SessionBrowser.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AA+BzB,OAAO,KAAK,EAAE,SAAS,IAAI,OAAO,EAAE,MAAM,iCAAiC,CAAA;AA+E3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAw/BlB"}
|