diff --git a/ui-tui/src/__tests__/agentsCompact.test.tsx b/ui-tui/src/__tests__/agentsCompact.test.tsx index 0507f37805..59e2cf2c05 100644 --- a/ui-tui/src/__tests__/agentsCompact.test.tsx +++ b/ui-tui/src/__tests__/agentsCompact.test.tsx @@ -27,34 +27,48 @@ it('keeps collapsed live chrome to one row without losing count or restore contr expect(text).toContain(`${rows.running} live agents`) expect(text).toContain('Ctrl+T expand') expect(text).toContain('F7 restore') - expect(renderToScreen(, cols).height).toBeGreaterThan(view.height) + expect(renderToScreen(, cols).height).toBeGreaterThan( + view.height + ) } }) it('opens the selected live transcript on Enter while details remain independently accessible', async () => { patchUiState({ sid: 'owner' }) - applyAgentSnapshot('owner', { subagents: [{ subagent_id: 'child', goal: 'Inspect ownership', status: 'running' }], delegations: [] }) + applyAgentSnapshot('owner', { + subagents: [{ subagent_id: 'child', goal: 'Inspect ownership', status: 'running' }], + delegations: [] + }) - const request = vi.fn(async (method: string) => method === 'subagent.tail' - ? { available: true, text: 'CHILD_TOOL_OUTPUT', truncated: false } - : {}) + const request = vi.fn(async (method: string) => + method === 'subagent.tail' ? { available: true, text: 'CHILD_TOOL_OUTPUT', truncated: false } : {} + ) const stdout = Object.assign(new PassThrough(), { columns: 80, rows: 20, isTTY: false }) const stdin = Object.assign(new PassThrough(), { isTTY: true, setRawMode: () => {}, ref: () => {}, unref: () => {} }) let output = '' - stdout.on('data', chunk => { output += stripAnsi(chunk.toString()) }) - - const view = renderSync( {}} t={DEFAULT_THEME} />, { - stdout: stdout as unknown as NodeJS.WriteStream, - stdin: stdin as unknown as NodeJS.ReadStream, - stderr: new PassThrough() as unknown as NodeJS.WriteStream, - patchConsole: false + stdout.on('data', chunk => { + output += stripAnsi(chunk.toString()) }) + const view = renderSync( + + {}} t={DEFAULT_THEME} /> + , + { + stdout: stdout as unknown as NodeJS.WriteStream, + stdin: stdin as unknown as NodeJS.ReadStream, + stderr: new PassThrough() as unknown as NodeJS.WriteStream, + patchConsole: false + } + ) + try { await vi.waitFor(() => expect(output).toContain('Inspect ownership')) stdin.write('\r') - await vi.waitFor(() => expect(request).toHaveBeenCalledWith('subagent.tail', { session_id: 'owner', subagent_id: 'child' })) + await vi.waitFor(() => + expect(request).toHaveBeenCalledWith('subagent.tail', { session_id: 'owner', subagent_id: 'child' }) + ) await vi.waitFor(() => expect(output).toContain('CHILD_TOOL_OUTPUT')) output = '' stdin.write('d') diff --git a/ui-tui/src/components/agentsOverlay.tsx b/ui-tui/src/components/agentsOverlay.tsx index e0467dc699..f7221bf450 100644 --- a/ui-tui/src/components/agentsOverlay.tsx +++ b/ui-tui/src/components/agentsOverlay.tsx @@ -772,9 +772,13 @@ export function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }: Agent return setMode('steer') } - if (ch === 't' && !key.ctrl && selected) {return setMode('tail')} + if (ch === 't' && !key.ctrl && selected) { + return setMode('tail') + } - if (ch === 'd' && !key.ctrl && selected) {return setMode('detail')} + if (ch === 'd' && !key.ctrl && selected) { + return setMode('detail') + } if (ch === 'q') { return closeWithCleanup() @@ -980,12 +984,19 @@ export function AgentsOverlay({ gw, initialHistoryIndex = 0, onClose, t }: Agent )} - {replayMode ? 'Enter/d detail' : 'Enter/t tail · d detail'} · e steer · x stop · Esc back - {flash ? {flash} : null} + + {replayMode ? 'Enter/d detail' : 'Enter/t tail · d detail'} · e steer · x stop · Esc back + + {flash ? ( + + {flash} + + ) : null} {mode === 'list' ? ( - ↑↓/jk move · g/G top/bottom · {replayMode ? 'Enter/→ detail' : 'Enter tail · d/→ detail'}{controlsHint} · s sort:{SORT_LABEL[sort]} · f filter: + ↑↓/jk move · g/G top/bottom · {replayMode ? 'Enter/→ detail' : 'Enter tail · d/→ detail'} + {controlsHint} · s sort:{SORT_LABEL[sort]} · f filter: {FILTER_LABEL[filter]} {history.length > 0 ? ` · [ / ] history ${historyIndex}/${history.length}` : ''} {' · q close'} diff --git a/ui-tui/src/components/agentsPanel.tsx b/ui-tui/src/components/agentsPanel.tsx index 7bf5fa6b3c..00f4e07aaf 100644 --- a/ui-tui/src/components/agentsPanel.tsx +++ b/ui-tui/src/components/agentsPanel.tsx @@ -11,8 +11,17 @@ import { fmtDuration } from '../lib/subagentTree.js' import { compactPreview } from '../lib/text.js' import type { Theme } from '../theme.js' -export function AgentsPanelView({ collapsed = false, cols, hidden, rows, running, t }: AgentRows & { collapsed?: boolean; cols: number; t: Theme }) { - if (!running) {return null} +export function AgentsPanelView({ + collapsed = false, + cols, + hidden, + rows, + running, + t +}: AgentRows & { collapsed?: boolean; cols: number; t: Theme }) { + if (!running) { + return null + } const summary = `▸ ${running} live agents` const hints = ' · Ctrl+T expand · F7 restore' @@ -27,18 +36,21 @@ export function AgentsPanelView({ collapsed = false, cols, hidden, rows, running width={cols} > - {collapsed ? summary + activity + hints : `▾ ${running} live agents${hidden ? ` · +${hidden} more` : ''} · Ctrl+T expand · F7 collapse`} + {collapsed + ? summary + activity + hints + : `▾ ${running} live agents${hidden ? ` · +${hidden} more` : ''} · Ctrl+T expand · F7 collapse`} - {!collapsed && rows.map(row => ( - - - {statusGlyph(row.status, t).glyph} - {compactPreview(row.goal, Math.max(8, cols - 18))} - {row.elapsedSeconds == null ? '' : fmtDuration(row.elapsedSeconds)} - - {` ↳ ${compactPreview(row.detail, cols - 4)}`} - - ))} + {!collapsed && + rows.map(row => ( + + + {statusGlyph(row.status, t).glyph} + {compactPreview(row.goal, Math.max(8, cols - 18))} + {row.elapsedSeconds == null ? '' : fmtDuration(row.elapsedSeconds)} + + {` ↳ ${compactPreview(row.detail, cols - 4)}`} + + ))} ) } @@ -61,6 +73,11 @@ export function LiveAgentsPanel({ cols }: { cols: number }) { }, [live]) return ( - + ) } diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx index 79e3eb3faf..09fbb075a2 100644 --- a/ui-tui/src/components/appLayout.tsx +++ b/ui-tui/src/components/appLayout.tsx @@ -277,7 +277,9 @@ const ComposerPane = memo(function ComposerPane({ composer, cursorSnapshotRef, status -}: Pick & { cursorSnapshotRef: MutableRefObject }) { +}: Pick & { + cursorSnapshotRef: MutableRefObject +}) { const ui = useStore($uiState) const isBlocked = useStore($isBlocked) const sh = (composer.inputBuf[0] ?? composer.input).startsWith('!') @@ -534,7 +536,9 @@ export const AppLayout = memo(function AppLayout({ const ui = useStore($uiState) const cursorSnapshotRef = useRef(null) - useEffect(() => { cursorSnapshotRef.current = null }, [ui.sid]) + useEffect(() => { + cursorSnapshotRef.current = null + }, [ui.sid]) // Inline mode skips AlternateScreen so the host terminal's native // scrollback captures rows scrolled off the top; composer + progress @@ -577,7 +581,12 @@ export const AppLayout = memo(function AppLayout({ - + {SHOW_FPS && ( diff --git a/ui-tui/src/components/textInput.tsx b/ui-tui/src/components/textInput.tsx index 29264205a1..a24100169c 100644 --- a/ui-tui/src/components/textInput.tsx +++ b/ui-tui/src/components/textInput.tsx @@ -790,7 +790,9 @@ export function TextInput({ color, focus = true }: TextInputProps) { - const [cur, setCur] = useState(() => cursorSnapshotRef?.current?.value === value ? cursorSnapshotRef.current.cursor : value.length) + const [cur, setCur] = useState(() => + cursorSnapshotRef?.current?.value === value ? cursorSnapshotRef.current.cursor : value.length + ) const [sel, setSel] = useState(null) const fwdDel = useFwdDelete(focus) const termFocus = useTerminalFocus() @@ -939,9 +941,14 @@ export function TextInput({ // The composer unmounts while full-screen monitors own input. Keep its // insertion point with the shell, not with transient steer/secret inputs. - useEffect(() => () => { - if (cursorSnapshotRef) {cursorSnapshotRef.current = { cursor: curRef.current, value: vRef.current }} - }, [cursorSnapshotRef]) + useEffect( + () => () => { + if (cursorSnapshotRef) { + cursorSnapshotRef.current = { cursor: curRef.current, value: vRef.current } + } + }, + [cursorSnapshotRef] + ) useEffect(() => { if (!focus) { diff --git a/website/docs/user-guide/cli.md b/website/docs/user-guide/cli.md index df128820e0..61ad61687e 100644 --- a/website/docs/user-guide/cli.md +++ b/website/docs/user-guide/cli.md @@ -187,6 +187,7 @@ When resuming a previous session (`hermes -c` or `hermes --resume `), a "Pre | `Ctrl+S` | **Stash the prompt.** Parks the current draft and clears the composer so you can send something else first. Press `Ctrl+S` again on an empty composer to bring the draft back (cursor at the end, attached images restored). Repeated presses build a stack rather than overwriting, so an earlier draft is never silently lost — with two or more stashed, `Ctrl+S` opens a browse panel (`↑`/`↓` to navigate, `Enter` to restore, `D` to discard, `Esc` or `Ctrl+S` to close). A `📌 N` badge in the status bar shows how many drafts are parked. Multi-line drafts round-trip exactly, including blank lines. The stash lives in memory for the session only — nothing is written to disk, since drafts often contain secrets. | | `Ctrl+C` | Interrupt agent (double-press within 2s to force exit) | | `F6` | Open the full-screen live subagent monitor without losing the composer draft. The live dock appears automatically above the status bar; arrows select a worker, `Enter` shows its recent log, `s` steers, and `x` requests stop with confirmation. See [Monitoring subagents](/user-guide/features/delegation#monitoring-running-subagents-agents). | +| `F7` | Toggle the live subagent dock between its multi-row preview and a single summary line without moving composer focus. | | `Ctrl+D` | Exit | | `Ctrl+Z` | Suspend Hermes to background (Unix only). Run `fg` in the shell to resume. | | `Tab` | Accept auto-suggestion (ghost text) or autocomplete slash commands | diff --git a/website/docs/user-guide/features/delegation.md b/website/docs/user-guide/features/delegation.md index ef97852ffd..3e0e19eb53 100644 --- a/website/docs/user-guide/features/delegation.md +++ b/website/docs/user-guide/features/delegation.md @@ -392,11 +392,13 @@ The classic CLI, TUI, and Desktop automatically show live subagents above the co | Surface | Expand and inspect | Control a selected worker | |---|---|---| | Classic CLI | **F6** opens the full-screen live roster; arrows select, **Enter** opens the transcript tail, **PgUp/PgDn** scroll | **s** opens a separate steering input; **x**, then **y** requests stop | -| TUI | **Ctrl+T** or `/agents` opens the full-height tree; **Enter** opens detail; **t** opens the live transcript tail | **e** opens steering; **x** stops the selected worker; **X** stops its subtree | +| TUI | **Ctrl+T** or `/agents` opens the full-height tree; **Enter/t** opens the live transcript tail; **d** opens rich detail (archived/replay Enter still opens detail) | **e** opens steering; **x** stops the selected worker; **X** stops its subtree | | Desktop | Expand **Subagents** above the composer, then select a worker to inspect its activity and details | **Steer** queues guidance; **Stop** requests interruption for that worker | Closing the terminal monitor returns to your existing composer draft. Steering uses its own input and acknowledges **queued**, not delivery: the child consumes guidance at a checkpoint. Stop does not interrupt unrelated siblings. +Press **F7** in the Classic CLI or TUI composer to toggle the dock between its multi-row preview and a single shaded summary line. The summary retains the live count and expand/restore hints, adding activity when space permits. Typing and sending remain available; opening and closing the monitor preserves your draft and insertion point. This is a local presentation choice, not a saved config change. + The live transcript tail is a bounded recent excerpt, not an unlimited conversation browser. A child leaving the live registry leaves the dock; completion messages and the TUI/Desktop history views remain the place to review finished work. Latest activity is an observation, not a percentage-complete estimate. The classic CLI's `/agents` and `/tasks` commands still print a text summary; **F6** is the immediate interactive monitor, including while the parent is busy. See [TUI — Slash commands](/user-guide/tui#slash-commands). diff --git a/website/docs/user-guide/tui.md b/website/docs/user-guide/tui.md index e601b3a085..87bed9a239 100644 --- a/website/docs/user-guide/tui.md +++ b/website/docs/user-guide/tui.md @@ -102,7 +102,8 @@ The directory must contain `dist/entry.js`. Keybindings match the [Classic CLI](cli.md#keybindings) exactly. The only behavioral differences: -- **`Ctrl+T`** expands the automatic live-subagent dock into the full-height `/agents` roster. Select a worker to inspect details, press **`t`** for its recent transcript, **`e`** to steer, or **`x`** to stop it. The dock fits its row count to terminal height and preserves your composer draft. See [Monitoring subagents](/user-guide/features/delegation#monitoring-running-subagents-agents). +- **`Ctrl+T`** expands the automatic live-subagent dock into the full-height `/agents` roster. Select a worker and press **Enter** (or **`t`**) for its live transcript, **`d`** for rich details, **`e`** to steer, or **`x`** to stop it. The dock fits its row count to terminal height and preserves your composer draft. See [Monitoring subagents](/user-guide/features/delegation#monitoring-running-subagents-agents). +- **`F7`** toggles the live dock between its default preview and one summary line. This does not open the monitor or move composer focus; the choice lasts for this TUI process without changing config. - **Mouse drag** highlights text with a uniform selection background. - **`Cmd+V` / `Ctrl+V`** first tries normal text paste, then falls back to OSC52/native clipboard reads, and finally image attach when the clipboard or pasted payload resolves to an image. - **`/terminal-setup`** installs local VS Code / Cursor / Windsurf terminal bindings for better `Cmd+Enter` and undo/redo parity on macOS. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/cli.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/cli.md index 6ff86a5b24..6ff9cb00a0 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/cli.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/cli.md @@ -103,6 +103,7 @@ hermes -w -z "Fix issue #123" # 在 worktree 中以单次查询模式运行 | `Ctrl+X Ctrl+E` | 外部编辑器的 Emacs 风格备用绑定(与 `Ctrl+G` 行为相同)。 | | `Ctrl+C` | 中断 agent(2 秒内双击强制退出) | | `F6` | 打开全屏实时子智能体监视器,保留输入草稿。方向键选择,`Enter` 查看近期日志,`s` 引导,`x` 请求停止并确认。 | +| `F7` | 将实时子智能体栏切换为单行摘要或恢复多行预览,不改变输入焦点。 | | `Ctrl+D` | 退出 | | `Ctrl+Z` | 将 Hermes 挂起到后台(仅 Unix)。在 shell 中运行 `fg` 恢复。 | | `Tab` | 接受自动建议(ghost text)或自动补全斜杠命令 | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/delegation.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/delegation.md index 0971ef374a..ee35ae5e81 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/delegation.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/delegation.md @@ -257,11 +257,13 @@ TUI 提供 `/agents` 浮层(别名 `/tasks`),将递归 `delegate_task` 扇 经典 CLI、TUI 和 Desktop 会在输入框上方自动显示正在运行的子智能体,包括总数、任务名称、已运行时间和最近活动。终端根据屏幕高度限制可见行数,并显示隐藏数量;Desktop 最多预览三个工作者。 - **经典 CLI:F6** 打开全屏实时列表;方向键选择,**Enter** 查看近期日志,**PgUp/PgDn** 滚动,**s** 输入引导,**x** 后按 **y** 确认停止。关闭后保留原有输入草稿。 -- **TUI:Ctrl+T** 或 `/agents` 打开完整树状列表;**t** 查看实时日志,**e** 输入引导,**x** 停止选中的工作者,**X** 停止其子树。 +- **TUI:Ctrl+T** 或 `/agents` 打开完整树状列表;**Enter/t** 查看实时日志,**d** 查看详情(历史回放中 Enter 仍打开详情),**e** 输入引导,**x** 停止选中的工作者,**X** 停止其子树。 - **Desktop:** 展开输入框上方的 **Subagents**,选择工作者查看详情并使用 **Steer** / **Stop**。 引导的“已排队”确认不代表子智能体已经读取;它会在检查点接收。日志预览只包含有大小限制的近期内容。工作者结束后离开实时列表,完成消息和已有历史视图仍可用于回顾。 +在经典 CLI 和 TUI 中按 **F7**,可将实时栏折叠为单行摘要,再按一次恢复多行预览。单行保留运行数量和展开/恢复提示,空间允许时显示活动。输入和发送不受影响;关闭监视器后保留草稿及光标位置。此选项不写入配置。 + 经典 CLI 的 `/agents` 和 `/tasks` 仍打印文本摘要;父智能体忙碌时可直接按 **F6** 打开交互式监视器。参见 [TUI — 斜杠命令](/user-guide/tui#slash-commands)。 ## 深度限制与嵌套编排 {#depth-limit-and-nested-orchestration} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/tui.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/tui.md index c79b3d95d0..3c0becd661 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/tui.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/tui.md @@ -85,7 +85,8 @@ hermes --tui 快捷键与 [Classic CLI](cli.md#keybindings) 完全一致。仅有以下行为差异: -- **`Ctrl+T`** — 将输入框上方的实时子智能体栏展开为完整 `/agents` 列表;**`t`** 查看近期日志,**`e`** 引导,**`x`** 停止选中的工作者。可见行数随终端高度调整,关闭后保留输入草稿。 +- **`Ctrl+T`** — 将输入框上方的实时子智能体栏展开为完整 `/agents` 列表;**Enter/t** 查看实时日志,**`d`** 查看详细信息,**`e`** 引导,**`x`** 停止选中的工作者。可见行数随终端高度调整,关闭后保留输入草稿。 +- **`F7`** — 在多行预览和单行摘要之间切换,保留输入焦点,不写入配置。 - **鼠标拖拽** — 以统一选区背景色高亮文本。 - **`Cmd+V` / `Ctrl+V`** — 优先尝试普通文本粘贴,然后回退到 OSC52/原生剪贴板读取,最后在剪贴板或粘贴内容解析为图片时进行图片附件操作。 - **`/terminal-setup`** — 安装本地 VS Code / Cursor / Windsurf 终端绑定,以在 macOS 上获得更好的 `Cmd+Enter` 和撤销/重做一致性。