@deepseek-harness-tui/dsh-tui 0.10.0-beta.1 → 0.10.0-beta.3

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.
Files changed (57) hide show
  1. package/lib/types/commands.d.ts.map +1 -1
  2. package/lib/types/commands.js +1 -0
  3. package/lib/types/components/Chat/JobCard.d.ts +23 -0
  4. package/lib/types/components/Chat/JobCard.d.ts.map +1 -0
  5. package/lib/types/components/Chat/JobCard.js +86 -0
  6. package/lib/types/components/JobsPanel.d.ts +17 -0
  7. package/lib/types/components/JobsPanel.d.ts.map +1 -0
  8. package/lib/types/components/JobsPanel.js +111 -0
  9. package/lib/types/components/MessageList.d.ts +7 -1
  10. package/lib/types/components/MessageList.d.ts.map +1 -1
  11. package/lib/types/components/MessageList.js +102 -22
  12. package/lib/types/components/SplitDiffView.d.ts +11 -1
  13. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  14. package/lib/types/components/SplitDiffView.js +9 -2
  15. package/lib/types/components/design-system/Divider.d.ts +10 -5
  16. package/lib/types/components/design-system/Divider.d.ts.map +1 -1
  17. package/lib/types/components/design-system/Divider.js +51 -7
  18. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +6 -1
  19. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  20. package/lib/types/components/messages/AssistantThinkingMessage.js +6 -2
  21. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +15 -1
  22. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  23. package/lib/types/components/messages/AssistantToolUseMessage.js +24 -2
  24. package/lib/types/components/smoothReveal.d.ts +97 -0
  25. package/lib/types/components/smoothReveal.d.ts.map +1 -0
  26. package/lib/types/components/smoothReveal.js +279 -0
  27. package/lib/types/dsh-adapter/channel.d.ts +53 -1
  28. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  29. package/lib/types/dsh-adapter/channel.js +203 -1
  30. package/lib/types/dsh-adapter/index.d.ts +5 -0
  31. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  32. package/lib/types/dsh-adapter/index.js +1 -0
  33. package/lib/types/dsh-adapter/jobs.d.ts +145 -0
  34. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -0
  35. package/lib/types/dsh-adapter/jobs.js +202 -0
  36. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  37. package/lib/types/dsh-adapter/plugin.js +16 -0
  38. package/lib/types/i18n.d.ts +95 -0
  39. package/lib/types/i18n.d.ts.map +1 -1
  40. package/lib/types/i18n.js +25 -0
  41. package/lib/types/ink/ink.d.ts.map +1 -1
  42. package/lib/types/ink/ink.js +12 -5
  43. package/lib/types/ink/parse-keypress.d.ts +18 -0
  44. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  45. package/lib/types/ink/parse-keypress.js +178 -7
  46. package/lib/types/ink/renderer.d.ts.map +1 -1
  47. package/lib/types/ink/renderer.js +8 -1
  48. package/lib/types/ink/selection.d.ts +31 -1
  49. package/lib/types/ink/selection.d.ts.map +1 -1
  50. package/lib/types/ink/selection.js +48 -6
  51. package/lib/types/screens/Chat.d.ts.map +1 -1
  52. package/lib/types/screens/Chat.js +19 -1
  53. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  54. package/lib/types/screens/StatusLine.js +27 -0
  55. package/lib/types/tips.d.ts.map +1 -1
  56. package/lib/types/tips.js +6 -0
  57. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAElF,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,qBAAqB,EAAE,CAAA;AAEpG;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EA2DxC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAElD,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAEpD,CAAA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAIhG;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,YAAY,EAAE,CAKhB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,SAAS,YAAY,EAAmB,EAC/C,QAAQ,GAAE,eAA0B,GACnC,iBAAiB,EAAE,CAuCrB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAElF,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,qBAAqB,EAAE,CAAA;AAEpG;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EA4DxC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAElD,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAEpD,CAAA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAIhG;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,YAAY,EAAE,CAKhB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,SAAS,YAAY,EAAmB,EAC/C,QAAQ,GAAE,eAA0B,GACnC,iBAAiB,EAAE,CAuCrB"}
@@ -43,6 +43,7 @@ export const LOCAL_COMMANDS = [
43
43
  { name: 'doctor', description: 'Run environment checks' },
44
44
  { name: 'init', description: 'Create AGENTS.md in the working directory' },
45
45
  { name: 'agents', description: 'Show subagents of this session' },
46
+ { name: 'jobs', description: 'Show background jobs of this session' },
46
47
  // Model / display
47
48
  { name: 'activity', description: 'Switch the working-activity indicator preset' },
48
49
  { name: 'preset', description: 'Switch the agent preset (including Liangshen mode)' },
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import type { JobRow } from '../../dsh-adapter/channel.js';
3
+ /**
4
+ * Live background-job card embedded in the transcript (`kind: 'job'`),
5
+ * sibling of the subagent card: header (id · kind · label · elapsed ·
6
+ * status) plus a bounded output waterfall (up to three rows) while the job
7
+ * is live — and only when mirrored output exists: background jobs are
8
+ * usually silent, so an outputless card is just its header line, never a
9
+ * row of empty gutters. Settled jobs fold to the header line alone (a
10
+ * failed/killed job keeps one detail line); the `/jobs` panel holds the
11
+ * fuller view the card clicks to.
12
+ *
13
+ * The waterfall is MIRRORED, never polled: the harness job registry's read
14
+ * is consuming and reserved for the owning agent, so the card shows the
15
+ * tail of the agent's own job_output results as they stream through the
16
+ * transcript.
17
+ */
18
+ export declare function JobCard({ job, addMargin, onClick }: {
19
+ job: JobRow;
20
+ addMargin: boolean;
21
+ onClick?(): void;
22
+ }): React.ReactNode;
23
+ //# sourceMappingURL=JobCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobCard.d.ts","sourceRoot":"","sources":["../../../../src/components/Chat/JobCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAkD1D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;IACnD,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB,GAAG,KAAK,CAAC,SAAS,CAuDlB"}
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text, useAnimationFrame, useTerminalSize } from '../../ui.js';
4
+ import { formatJobDuration } from '../../dsh-adapter/jobs.js';
5
+ import { t } from '../../i18n.js';
6
+ import { stringWidth } from '../../ink/stringWidth.js';
7
+ import { isMinimalMode } from '../../minimalMode.js';
8
+ /** The waterfall window mirrors the subagent card: a constant-height region. */
9
+ const WATERFALL_ROWS = 3;
10
+ /** Card left padding + the `│ ` gutter prefix. */
11
+ const WATERFALL_GUTTER = 4;
12
+ /** Static status marker — deliberately NOT the animated activity-indicator
13
+ * preset: a background job is parked work, and reusing the main spinner
14
+ * language for every card reads as clutter. ● = live background work
15
+ * (echoing the status-line chip), ✓/✗ for terminal states. NOTE: no ⚙ —
16
+ * U+2699 is East-Asian Ambiguous: ink measures it 1 cell while CJK
17
+ * terminal fonts paint it 2, so the following text overlaps the glyph. */
18
+ function statusInfo(status) {
19
+ const minimal = isMinimalMode();
20
+ switch (status) {
21
+ case 'completed':
22
+ return { glyph: '✓', label: t('jobs-status-completed'), color: minimal ? undefined : 'success' };
23
+ case 'failed':
24
+ return { glyph: '✗', label: t('jobs-status-failed'), color: minimal ? undefined : 'error' };
25
+ case 'killed':
26
+ return { glyph: '✗', label: t('jobs-status-killed'), color: minimal ? undefined : 'error' };
27
+ case 'stopping':
28
+ return { glyph: '●', label: t('jobs-status-stopping'), color: minimal ? undefined : 'warning' };
29
+ default:
30
+ return { glyph: '●', label: t('jobs-status-running'), color: minimal ? undefined : 'warning' };
31
+ }
32
+ }
33
+ /** Hard single-line clip by display width — a wrapped waterfall row would
34
+ * break the constant-height window. */
35
+ function clipLine(text, maxWidth) {
36
+ if (maxWidth <= 1)
37
+ return '';
38
+ let width = 0;
39
+ let index = 0;
40
+ while (index < text.length) {
41
+ const next = text.codePointAt(index);
42
+ const char = String.fromCodePoint(next);
43
+ const charWidth = stringWidth(char);
44
+ if (width + charWidth > maxWidth - 1)
45
+ break;
46
+ width += charWidth;
47
+ index += char.length;
48
+ }
49
+ return index < text.length ? `${text.slice(0, index)}…` : text;
50
+ }
51
+ /**
52
+ * Live background-job card embedded in the transcript (`kind: 'job'`),
53
+ * sibling of the subagent card: header (id · kind · label · elapsed ·
54
+ * status) plus a bounded output waterfall (up to three rows) while the job
55
+ * is live — and only when mirrored output exists: background jobs are
56
+ * usually silent, so an outputless card is just its header line, never a
57
+ * row of empty gutters. Settled jobs fold to the header line alone (a
58
+ * failed/killed job keeps one detail line); the `/jobs` panel holds the
59
+ * fuller view the card clicks to.
60
+ *
61
+ * The waterfall is MIRRORED, never polled: the harness job registry's read
62
+ * is consuming and reserved for the owning agent, so the card shows the
63
+ * tail of the agent's own job_output results as they stream through the
64
+ * transcript.
65
+ */
66
+ export function JobCard({ job, addMargin, onClick }) {
67
+ const settled = job.status === 'completed' || job.status === 'failed' || job.status === 'killed';
68
+ // 动画订阅仅限存活卡片:settled 后退订共享 clock(同 SubagentMessage 的
69
+ // 约定)。1s tick 只驱动运行时长跳动——状态标是静态的(见 statusInfo)。
70
+ const [viewportRef] = useAnimationFrame(settled ? null : 1000);
71
+ const { columns } = useTerminalSize();
72
+ const info = statusInfo(job.status);
73
+ const [hovered, setHovered] = React.useState(false);
74
+ const clickable = onClick !== undefined;
75
+ const rowWidth = Math.max(20, (columns ?? 80) - WATERFALL_GUTTER);
76
+ const activity = settled ? [] : job.outputLines.slice(-WATERFALL_ROWS);
77
+ // A settled job's terminal detail ('exit code: 0') rides the header; a
78
+ // failed/killed one also keeps it as the explanatory tail line.
79
+ const headerDetail = job.detail !== undefined && job.detail !== '' ? job.detail : undefined;
80
+ // 点击打开 /jobs 面板;hover 不刷整行背景(转录视觉保持安静),只把
81
+ // 状态 glyph 提亮为品牌色作为可点指示。无外层缩进:任务卡是上方工具
82
+ // 调用(run_in_background 卡)的延续,与工具卡通栏左对齐;子代理卡才
83
+ // 是嵌套子实体、保留缩进。瀑布的 ` │ ` 槽自带两格,正好与工具卡正文
84
+ // 的 ` ⎿ ` 槽位一致。
85
+ return _jsxs(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, ref: viewportRef, onClick: onClick, onMouseEnter: clickable ? () => setHovered(true) : undefined, onMouseLeave: clickable ? () => setHovered(false) : undefined, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: hovered && clickable ? 'claude' : info.color, children: info.glyph }), _jsx(Text, { bold: true, color: hovered && clickable ? 'claude' : undefined, children: `${t('jobs-card-prefix')}${job.id}` }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: job.kind }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { children: clipLine(job.label, Math.max(10, rowWidth - 30)) }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: formatJobDuration(job) }), headerDetail !== undefined && _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: headerDetail })] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), !settled && activity.length > 0 && activity.map((line, index) => (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${clipLine(line, rowWidth)}` }, `${job.id}-wf-${index}`))), settled && job.status !== 'completed' && headerDetail !== undefined && (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` └ ${clipLine(headerDetail, rowWidth)}` }))] });
86
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { type BackgroundJobState } from '../dsh-adapter/jobs.js';
3
+ export interface JobsPanelProps {
4
+ jobs: readonly BackgroundJobState[];
5
+ onClose: () => void;
6
+ /** Kill the focused live job (`job_kill` with the session's authority). */
7
+ onKill: (id: string) => void;
8
+ }
9
+ /**
10
+ * `/jobs` overlay panel — every background job of the current session with
11
+ * live status, elapsed/total duration and terminal detail (exit code).
12
+ * Keyboard: ↑/↓ move, k kills the focused live job, Esc closes; the focused
13
+ * row expands a detail block (full label, start/finish times, mirrored
14
+ * output tail). The panel is the deep view behind the transcript job cards.
15
+ */
16
+ export declare function JobsPanel({ jobs, onClose, onKill }: JobsPanelProps): React.ReactNode;
17
+ //# sourceMappingURL=JobsPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobsPanel.d.ts","sourceRoot":"","sources":["../../../src/components/JobsPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAqB,KAAK,kBAAkB,EAA4B,MAAM,wBAAwB,CAAA;AAS7G,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACnC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,2EAA2E;IAC3E,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAkGD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,KAAK,CAAC,SAAS,CA2FpF"}
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text, useInput, ScrollBox, useTerminalSize, useAnimationFrame } from '../ui.js';
4
+ import { formatJobDuration } from '../dsh-adapter/jobs.js';
5
+ import { t } from '../i18n.js';
6
+ import { Divider } from './design-system/Divider.js';
7
+ import { ExitButton } from './SubagentDashboard.js';
8
+ import { isPlainReturnInput } from '../utils/modifiers.js';
9
+ import { isMinimalMode } from '../minimalMode.js';
10
+ import { stringWidth } from '../ink/stringWidth.js';
11
+ function statusInfo(status) {
12
+ const minimal = isMinimalMode();
13
+ switch (status) {
14
+ case 'completed':
15
+ return { glyph: minimal ? '✓' : '●', label: t('jobs-status-completed'), color: minimal ? undefined : 'success' };
16
+ case 'failed':
17
+ return { glyph: minimal ? '×' : '●', label: t('jobs-status-failed'), color: minimal ? undefined : 'error' };
18
+ case 'killed':
19
+ return { glyph: minimal ? '×' : '●', label: t('jobs-status-killed'), color: minimal ? undefined : 'error' };
20
+ case 'stopping':
21
+ return { glyph: minimal ? '·' : '●', label: t('jobs-status-stopping'), color: minimal ? undefined : 'warning' };
22
+ default:
23
+ return { glyph: minimal ? '·' : '●', label: t('jobs-status-running'), color: minimal ? undefined : 'warning' };
24
+ }
25
+ }
26
+ /** Hard single-line clip by display width (shared rule with the job card). */
27
+ function clipLine(text, maxWidth) {
28
+ if (maxWidth <= 1)
29
+ return '';
30
+ let width = 0;
31
+ let index = 0;
32
+ while (index < text.length) {
33
+ const next = text.codePointAt(index);
34
+ const char = String.fromCodePoint(next);
35
+ const charWidth = stringWidth(char);
36
+ if (width + charWidth > maxWidth - 1)
37
+ break;
38
+ width += charWidth;
39
+ index += char.length;
40
+ }
41
+ return index < text.length ? `${text.slice(0, index)}…` : text;
42
+ }
43
+ function JobRowLine({ job, focused }) {
44
+ const { columns } = useTerminalSize();
45
+ const info = statusInfo(job.status);
46
+ const duration = formatJobDuration(job);
47
+ const detail = job.detail !== undefined && job.detail !== '' ? job.detail : undefined;
48
+ // Reserve: glyph(2) id(~9) kind(~7) duration(~7) status(~6) separators(5×2)
49
+ // — the label takes the rest and hard-clips instead of wrapping.
50
+ const labelWidth = Math.max(10, (columns ?? 80) - 46);
51
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: focused ? 'claude' : undefined, children: focused ? '❯' : ' ' }), _jsx(Text, { color: info.color, children: info.glyph }), _jsx(Text, { bold: focused, color: focused ? 'claude' : undefined, children: job.id }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: job.kind }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: focused, children: clipLine(job.label, labelWidth) }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: true, children: duration }), detail !== undefined && _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: detail })] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), focused && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: ` ${t('jobs-card-prefix')}${clipLine(job.label, Math.max(10, labelWidth + 24))}` }), job.command !== undefined && (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` ${t('jobs-panel-command')} ${clipLine(job.command, Math.max(10, labelWidth + 24))}` })), _jsxs(Text, { dimColor: true, wrap: "truncate", children: [` ${t('jobs-panel-started')} ${timeOf(job.startedAt)}`, job.finishedAt !== undefined ? ` · ${t('jobs-panel-finished')} ${timeOf(job.finishedAt)}` : '', job.lastOutputAt !== undefined ? ` · ${t('jobs-panel-output-at')} ${timeOf(job.lastOutputAt)}` : ''] }), job.outputLines.length > 0 ? (job.outputLines.map((line, index) => (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${clipLine(line, Math.max(10, labelWidth + 24))}` }, `${job.id}-detail-${index}`)))) : (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` └ ${t('jobs-panel-no-output-yet')}` }))] }))] }));
52
+ }
53
+ /** HH:MM:SS wall-clock of an epoch ms value (locale-independent). */
54
+ function timeOf(ms) {
55
+ const date = new Date(ms);
56
+ const pad = (value) => String(value).padStart(2, '0');
57
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
58
+ }
59
+ /**
60
+ * `/jobs` overlay panel — every background job of the current session with
61
+ * live status, elapsed/total duration and terminal detail (exit code).
62
+ * Keyboard: ↑/↓ move, k kills the focused live job, Esc closes; the focused
63
+ * row expands a detail block (full label, start/finish times, mirrored
64
+ * output tail). The panel is the deep view behind the transcript job cards.
65
+ */
66
+ export function JobsPanel({ jobs, onClose, onKill }) {
67
+ const [focusIndex, setFocusIndex] = React.useState(0);
68
+ const scrollRef = React.useRef(null);
69
+ const { rows } = useTerminalSize();
70
+ // 1s tick keeps live durations counting while the panel is open.
71
+ const [clockRef] = useAnimationFrame(1000);
72
+ const focus = Math.min(focusIndex, Math.max(0, jobs.length - 1));
73
+ useInput((input, key, event) => {
74
+ if (key.escape || (key.ctrl && input === 'c')) {
75
+ event.stopImmediatePropagation();
76
+ onClose();
77
+ return;
78
+ }
79
+ if (key.upArrow) {
80
+ event.stopImmediatePropagation();
81
+ setFocusIndex(i => Math.max(0, i - 1));
82
+ scrollRef.current?.scrollBy(-1);
83
+ return;
84
+ }
85
+ if (key.downArrow) {
86
+ event.stopImmediatePropagation();
87
+ setFocusIndex(i => Math.min(jobs.length - 1, i + 1));
88
+ scrollRef.current?.scrollBy(1);
89
+ return;
90
+ }
91
+ if (input === 'k') {
92
+ const selected = jobs[focus];
93
+ if (selected !== undefined && (selected.status === 'running' || selected.status === 'stopping')) {
94
+ event.stopImmediatePropagation();
95
+ onKill(selected.id);
96
+ }
97
+ return;
98
+ }
99
+ // Enter on a live job does nothing extra (the card/panel IS the view);
100
+ // keep the key consumed while the panel owns the keyboard.
101
+ if (isPlainReturnInput(input, key)) {
102
+ event.stopImmediatePropagation();
103
+ return;
104
+ }
105
+ event.stopImmediatePropagation();
106
+ });
107
+ const running = jobs.filter(job => job.status === 'running' || job.status === 'stopping').length;
108
+ const completed = jobs.filter(job => job.status === 'completed').length;
109
+ const failed = jobs.filter(job => job.status === 'failed' || job.status === 'killed').length;
110
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, ref: clockRef, children: [_jsx(Divider, { color: "claude", title: t('jobs-panel-title') }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "claude", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-failed')] })] })), _jsx(Box, { flexGrow: 1 }), _jsx(ExitButton, { onClick: onClose })] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: jobs.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('jobs-panel-empty') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-empty-hint') }) })] })) : (jobs.map((job, index) => _jsx(JobRowLine, { job: job, focused: index === focus }, job.id))) }) }), _jsx(Divider, { color: "subtle", title: "" }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-hint') }) })] }));
111
+ }
@@ -4,7 +4,7 @@ import type { ChatRow } from '../dsh-adapter/channel.js';
4
4
  import type { DOMElement } from '../ink/dom.js';
5
5
  import { type TimelineSnapshot } from '../ink/timeline-rail.js';
6
6
  import type { ToolBackground } from '../tuiDisplayPrefs.js';
7
- export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, streamViewToggledRows, onToggleStreamView, model, diffLayout, thinkingFold, toolBackground, foldTerminalCommand, activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible, historyPaintEnabled, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, onTimeline, failureHintRowId, failureHint, onOpenSubagent, onOpenFile, }: {
7
+ export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, streamViewToggledRows, onToggleStreamView, model, diffLayout, thinkingFold, toolBackground, foldTerminalCommand, smoothStreaming, activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible, historyPaintEnabled, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, onTimeline, failureHintRowId, failureHint, onOpenSubagent, onOpenJobs, onOpenFile, }: {
8
8
  rows: readonly ChatRow[];
9
9
  expanded: boolean;
10
10
  expandedRows: ReadonlySet<number>;
@@ -22,6 +22,10 @@ export declare function MessageList({ rows, expanded, expandedRows, selectedId,
22
22
  toolBackground?: ToolBackground;
23
23
  /** Terminal-card header folding from the live channel settings. */
24
24
  foldTerminalCommand?: boolean;
25
+ /** Smooth streaming reveal from the live channel settings (default off at
26
+ * this layer — embedders and verify harnesses keep exact-paint behavior;
27
+ * Chat passes the channel's `dsh-tui.smoothStreaming` value). */
28
+ smoothStreaming?: boolean;
25
29
  /** Working-activity preset name from the channel; drives the subagent
26
30
  * card's running glyph so both indicators follow one setting. */
27
31
  activityFrames?: string;
@@ -84,6 +88,8 @@ export declare function MessageList({ rows, expanded, expandedRows, selectedId,
84
88
  failureHint?: string;
85
89
  /** 打开子代理详情场景(transcript 内点击子代理卡)。 */
86
90
  onOpenSubagent?: (agentId: string) => void;
91
+ /** 打开 /jobs 后台任务面板(transcript 内点击任务卡)。 */
92
+ onOpenJobs?: () => void;
87
93
  /** 点击工具卡内的文件路径(打开文件操作菜单)。 */
88
94
  onOpenFile?: (path: string) => void;
89
95
  }): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ import { AssistantTextMessage } from './messages/AssistantTextMessage.js';
8
8
  import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage.js';
9
9
  import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage.js';
10
10
  import { SubagentMessage } from './Chat/SubagentMessage.js';
11
+ import { JobCard } from './Chat/JobCard.js';
11
12
  import { isMinimalMode } from '../minimalMode.js';
12
13
  import { noteFrameCause, noteListGeometry } from '../ink/geometry-trace.js';
13
14
  import { InterruptedByUser } from './InterruptedByUser.js';
@@ -18,6 +19,7 @@ import { stripNarration } from '../utils/narration.js';
18
19
  import { stringWidth } from '../ink/stringWidth.js';
19
20
  import { truncateToWidth } from '../ink/truncateToWidth.js';
20
21
  import { clipPreview } from '../ink/timeline-rail.js';
22
+ import { getRevealVersion, revealLengthOf, revealTextOf, subscribeReveal } from './smoothReveal.js';
21
23
  /**
22
24
  * Transcript rows rendered in the Claude Code visual language: user prompts
23
25
  * on a grey bubble with a `❯` pointer, assistant text with a `●` bullet and
@@ -55,6 +57,37 @@ const DEFAULT_HEADER_LINES = 14;
55
57
  * toggle callback's deps never churn. */
56
58
  const NO_STREAM_VIEW_TOGGLED = new Set();
57
59
  const NOOP_TOGGLE_STREAM_VIEW = (_rowId) => { };
60
+ // --- smooth-streaming display text -----------------------------------------
61
+ // Render-phase reads of the shared reveal cursors (see smoothReveal.ts for
62
+ // why the cursors live outside React). `active` gates cursor CREATION to
63
+ // live-arrived content (streaming rows, live-settled rows marked `fresh`,
64
+ // replayed history paints complete); once created, a cursor keeps revealing
65
+ // until it catches up — settling mid-reveal must not snap (that is the
66
+ // "non-streaming delivery becomes a smooth flow" contract).
67
+ function assistantRevealText(row, enabled) {
68
+ const stripped = stripNarration(row.text);
69
+ return revealTextOf(`a${row.id}`, stripped, {
70
+ enabled,
71
+ active: row.streaming === true || row.fresh === true,
72
+ });
73
+ }
74
+ function reasoningRevealText(row, enabled) {
75
+ return revealTextOf(`r${row.id}`, row.text, { enabled, active: row.streaming === true });
76
+ }
77
+ /** Display length only (layout signature; no slice allocation). */
78
+ function revealDisplayLen(row, enabled) {
79
+ if (row.kind === 'assistant') {
80
+ const stripped = stripNarration(row.text);
81
+ return revealLengthOf(`a${row.id}`, stripped, {
82
+ enabled,
83
+ active: row.streaming === true || row.fresh === true,
84
+ });
85
+ }
86
+ if (row.kind === 'reasoning') {
87
+ return revealLengthOf(`r${row.id}`, row.text, { enabled, active: row.streaming === true });
88
+ }
89
+ return row.text.length;
90
+ }
58
91
  /**
59
92
  * Per-kind layout signature PARTS: the O(1) identity of every input that
60
93
  * decides a row's rendered HEIGHT (see sigRef in MessageList). Fields are
@@ -76,11 +109,13 @@ const NOOP_TOGGLE_STREAM_VIEW = (_rowId) => { };
76
109
  * (parts.slice()).
77
110
  */
78
111
  const signatureScratch = [];
79
- function signatureParts(row, columns, expanded, expandedRows, streamViewToggledRows, thinkingVisible, thinkingFold, diffLayout, foldTerminalCommand, model, failureHintRowId, failureHint) {
112
+ function signatureParts(row, columns, expanded, expandedRows, streamViewToggledRows, thinkingVisible, thinkingFold, diffLayout, foldTerminalCommand, model, failureHintRowId, failureHint, displayTextLen) {
80
113
  signatureScratch.length = 0;
81
114
  // Universal height inputs: width reflows every row; kind switches height
82
- // semantics wholesale; text length drives wrapping.
83
- signatureScratch.push(columns, row.kind, row.text?.length ?? 0);
115
+ // semantics wholesale; text length drives wrapping. `displayTextLen` is the
116
+ // REVEALED length while smooth streaming is painting (the height follows
117
+ // what is on screen, not what has arrived).
118
+ signatureScratch.push(columns, row.kind, displayTextLen);
84
119
  switch (row.kind) {
85
120
  case 'assistant':
86
121
  // Streaming vs settled swaps renderers; Ctrl+O/per-row expand adds the
@@ -119,7 +154,7 @@ function signatureParts(row, columns, expanded, expandedRows, streamViewToggledR
119
154
  }
120
155
  return signatureScratch;
121
156
  }
122
- export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, streamViewToggledRows = NO_STREAM_VIEW_TOGGLED, onToggleStreamView = NOOP_TOGGLE_STREAM_VIEW, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', foldTerminalCommand = false, activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible = true, historyPaintEnabled = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, onTimeline, failureHintRowId, failureHint, onOpenSubagent, onOpenFile, }) {
157
+ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, streamViewToggledRows = NO_STREAM_VIEW_TOGGLED, onToggleStreamView = NOOP_TOGGLE_STREAM_VIEW, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', foldTerminalCommand = false, smoothStreaming = false, activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible = true, historyPaintEnabled = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, onTimeline, failureHintRowId, failureHint, onOpenSubagent, onOpenJobs, onOpenFile, }) {
123
158
  const hiddenCount = rows.length - MAX_RENDERED_ROWS;
124
159
  // The thinking filter runs BEFORE virtualization so window indices line up.
125
160
  //
@@ -225,6 +260,13 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
225
260
  };
226
261
  // --- layout virtualization ---------------------------------------------
227
262
  const { columns, rows: termRows } = useTerminalSize();
263
+ // Smooth-reveal wakeups: the scheduler's tick bumps this store, re-running
264
+ // MessageList so the revealed `text`/line-count reads below feed fresh
265
+ // values through the same MemoRow-prop pipeline an arriving chunk uses —
266
+ // memo miss → re-render → post-commit height re-measure. Without this
267
+ // subscription a reveal living in child state would change row heights
268
+ // invisibly to the virtualization (stale cached heights → blank bands).
269
+ React.useSyncExternalStore(subscribeReveal, getRevealVersion);
228
270
  // Measured row heights, remembered after a row unmounts so virtualization
229
271
  // can compute total content height. Bounded: row ids grow monotonically
230
272
  // and rows are never removed from the transcript (foldRows keeps the
@@ -259,7 +301,7 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
259
301
  /** True when frame-budgeted history painting still has batches left
260
302
  * (main-screen open): the layout effect schedules the next slice. */
261
303
  const paintPendingRef = React.useRef(false);
262
- const paintQueuedRef = React.useRef(false);
304
+ const paintTimerRef = React.useRef(null);
263
305
  /** Persistent history-paint edge: how far batched painting has advanced
264
306
  * (index into visibleRows). -1 = not painting / reset (list head change:
265
307
  * rewind, new session, loadOlder — must repaint from scratch). */
@@ -275,9 +317,19 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
275
317
  paintedBaseRef.current = listHeadId;
276
318
  /** Content-space offset of visibleRows[0] (header + dividers), measured. */
277
319
  const baseRef = React.useRef(null);
278
- const measureQueuedRef = React.useRef(false);
320
+ const measureTimerRef = React.useRef(null);
279
321
  const [, setMeasureTick] = React.useState(0);
280
322
  const [, setScrollTick] = React.useState(0);
323
+ React.useEffect(() => () => {
324
+ if (measureTimerRef.current !== null) {
325
+ clearTimeout(measureTimerRef.current);
326
+ measureTimerRef.current = null;
327
+ }
328
+ if (paintTimerRef.current !== null) {
329
+ clearTimeout(paintTimerRef.current);
330
+ paintTimerRef.current = null;
331
+ }
332
+ }, []);
281
333
  // A width change reflows every row — all measurements are stale.
282
334
  const lastColumns = React.useRef(columns);
283
335
  if (lastColumns.current !== columns) {
@@ -318,7 +370,7 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
318
370
  // reasoning height at once, remounting the widened window over rows
319
371
  // whose rendering never changed (Yoga spike + measure churn for
320
372
  // nothing). Base parts cover the universal height inputs.
321
- const parts = signatureParts(row, columns, expanded, expandedRows, streamViewToggledRows, thinkingVisible, thinkingFold, diffLayout, foldTerminalCommand, model, failureHintRowId, failureHint);
373
+ const parts = signatureParts(row, columns, expanded, expandedRows, streamViewToggledRows, thinkingVisible, thinkingFold, diffLayout, foldTerminalCommand, model, failureHintRowId, failureHint, revealDisplayLen(row, smoothStreaming));
322
374
  const cachedParts = sigs.get(row.id);
323
375
  let same = false;
324
376
  if (cachedParts !== undefined && cachedParts.length === parts.length) {
@@ -787,15 +839,15 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
787
839
  scrollHandle.setClampBounds(min, bottomPad <= 0 ? undefined : Math.max(min, base + mountedBottom - viewport));
788
840
  }
789
841
  }
790
- if (changed && !measureQueuedRef.current) {
791
- // Layout corrections can cascade for many rows. Yield between commits
792
- // so React does not count the valid convergence as nested updates.
793
- measureQueuedRef.current = true;
794
- queueMicrotask(() => {
795
- measureQueuedRef.current = false;
842
+ if (changed && measureTimerRef.current === null) {
843
+ // Layout corrections can cascade for many rows. Yield to the next
844
+ // macrotask so React does not count the valid convergence as nested
845
+ // updates when a streaming/reveal commit is already in flight.
846
+ measureTimerRef.current = setTimeout(() => {
847
+ measureTimerRef.current = null;
796
848
  noteFrameCause('measure');
797
849
  setMeasureTick(t => t + 1);
798
- });
850
+ }, 0);
799
851
  }
800
852
  // History-paint continuation (main-screen open): more never-painted
801
853
  // batches remain — schedule the next slice on the macrotask queue so
@@ -804,10 +856,9 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
804
856
  // enough: each batch's mount+measure work is bounded (~2 viewports),
805
857
  // unlike the previous single-commit full mount that saturated the main
806
858
  // thread for seconds.
807
- if (paintPendingRef.current && !paintQueuedRef.current) {
808
- paintQueuedRef.current = true;
809
- setTimeout(() => {
810
- paintQueuedRef.current = false;
859
+ if (paintPendingRef.current && paintTimerRef.current === null) {
860
+ paintTimerRef.current = setTimeout(() => {
861
+ paintTimerRef.current = null;
811
862
  if (!paintPendingRef.current)
812
863
  return;
813
864
  noteFrameCause('measure');
@@ -832,7 +883,32 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
832
883
  const addMargin = margins.get(row.id) === true;
833
884
  const tool = row.tool;
834
885
  const subagent = row.kind === 'subagent' ? row.subagent : undefined;
835
- return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, foldTerminalCommand: foldTerminalCommand, activityFrames: activityFrames, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, subagent: subagent, onToggleRow: onToggleRow, onToggleStreamView: onToggleStreamView, streamViewToggled: streamViewToggledRows.has(row.id), onOpenSubagent: onOpenSubagent, onOpenFile: onOpenFile, setRowRef: setRowRef }, row.id));
886
+ const job = row.kind === 'job' ? row.job : undefined;
887
+ const revealVersion = smoothStreaming && row.kind === 'tool' && row.fresh === true &&
888
+ row.tool?.status === 'running' && row.tool.resultView === undefined
889
+ ? getRevealVersion()
890
+ : 0;
891
+ // Smooth reveal feeds the SAME flattened text prop a chunk feeds,
892
+ // and keeps the streaming layout alive until the reveal catches up
893
+ // (settling mid-reveal must not snap — a one-shot non-streaming
894
+ // delivery still paints as a flow).
895
+ let displayText = row.text;
896
+ let displayStreaming = row.streaming === true;
897
+ if (row.kind === 'assistant' && smoothStreaming) {
898
+ const stripped = stripNarration(row.text);
899
+ displayText = revealTextOf(`a${row.id}`, stripped, {
900
+ enabled: true,
901
+ active: displayStreaming || row.fresh === true,
902
+ });
903
+ displayStreaming = displayStreaming || displayText.length !== stripped.length;
904
+ }
905
+ else if (row.kind === 'assistant') {
906
+ displayText = stripNarration(row.text);
907
+ }
908
+ else if (row.kind === 'reasoning' && smoothStreaming) {
909
+ displayText = revealTextOf(`r${row.id}`, row.text, { enabled: true, active: displayStreaming });
910
+ }
911
+ return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: displayText, textFull: row.kind === 'reasoning' ? row.text : undefined, executionTarget: row.executionTarget, streaming: displayStreaming, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, foldTerminalCommand: foldTerminalCommand, smoothStreaming: smoothStreaming, fresh: row.fresh === true, revealVersion: revealVersion, activityFrames: activityFrames, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, subagent: subagent, job: job, onToggleRow: onToggleRow, onToggleStreamView: onToggleStreamView, streamViewToggled: streamViewToggledRows.has(row.id), onOpenSubagent: onOpenSubagent, onOpenJobs: onOpenJobs, onOpenFile: onOpenFile, setRowRef: setRowRef }, row.id));
836
912
  }), bottomPad > 0 && _jsx(Box, { height: bottomPad, flexShrink: 0 })] }));
837
913
  }
838
914
  /** Load-earlier / show-previous divider row with a mouse hover tint — the
@@ -841,7 +917,7 @@ function ClickableDivider({ title, onClick }) {
841
917
  const [hovered, setHovered] = useState(false);
842
918
  return (_jsx(Box, { marginTop: 1, onClick: onClick, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), backgroundColor: hovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Divider, { title: title }) }));
843
919
  }
844
- function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, foldTerminalCommand, activityFrames, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, subagent, onToggleRow, onToggleStreamView, streamViewToggled, onOpenSubagent, onOpenFile, setRowRef, }) {
920
+ function TranscriptRow({ rowId, kind, text, textFull, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, smoothStreaming, fresh, revealVersion, thinkingFold, toolBackground, foldTerminalCommand, activityFrames, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, subagent, job, onToggleRow, onToggleStreamView, streamViewToggled, onOpenSubagent, onOpenJobs, onOpenFile, setRowRef, }) {
845
921
  const ref = React.useCallback((el) => {
846
922
  setRowRef(rowId, el);
847
923
  }, [setRowRef, rowId]);
@@ -878,7 +954,7 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
878
954
  // or per-row transcript expansion always wins and shows the full text.
879
955
  const streamPreview = streaming && !expanded && !isExpanded &&
880
956
  (streamViewToggled ? thinkingFold === 'full' : thinkingFold === 'preview');
881
- return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, addMargin: addMargin, streaming: streaming, preview: streamPreview,
957
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, textFull: textFull, addMargin: addMargin, streaming: streaming, preview: streamPreview,
882
958
  // Settled rows keep the fold-on-settle default and expand via
883
959
  // expandedRows/Ctrl+O; a live row is always preview or full.
884
960
  verbose: isExpanded || expanded || (streaming && !streamPreview), durationMs: durationMs, isSelected: isSelected, onClick: streaming ? streamViewOnClick : foldOnClick }) }));
@@ -907,7 +983,7 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
907
983
  startedAt: toolStartedAt,
908
984
  durationMs: toolDurationMs,
909
985
  };
910
- return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantToolUseMessage, { tool: tool, addMargin: addMargin, verbose: isExpanded || expanded, isSelected: isSelected, isExpanded: isExpanded, footnote: toolFootnote, diffLayout: diffLayout, toolBackground: toolBackground, foldTerminalCommand: foldTerminalCommand, onClick: foldOnClick, onOpenFile: onOpenFile }) }));
986
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantToolUseMessage, { tool: tool, addMargin: addMargin, verbose: isExpanded || expanded, isSelected: isSelected, isExpanded: isExpanded, footnote: toolFootnote, diffLayout: diffLayout, toolBackground: toolBackground, smoothReveal: smoothStreaming, fresh: fresh, foldTerminalCommand: foldTerminalCommand, onClick: foldOnClick, onOpenFile: onOpenFile }) }));
911
987
  }
912
988
  case 'notice':
913
989
  return (_jsx(Box, { marginTop: 1, ref: ref, children: _jsx(Divider, { title: ` ${text} ` }) }));
@@ -927,6 +1003,10 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
927
1003
  if (!subagent)
928
1004
  return null;
929
1005
  return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(SubagentMessage, { subagent: subagent, addMargin: addMargin, activityFrames: activityFrames, isExpanded: isExpanded, onClick: openSubagent }) }));
1006
+ case 'job':
1007
+ if (!job)
1008
+ return null;
1009
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(JobCard, { job: job, addMargin: addMargin, onClick: onOpenJobs }) }));
930
1010
  }
931
1011
  }
932
1012
  /** Folded compact-summary preview: whitespace flattened, capped with an
@@ -3,7 +3,7 @@ import type { ToolFileDiff } from '../dsh-adapter/channel.js';
3
3
  export { chalkFromToken } from '../cc/syntaxTheme.js';
4
4
  export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
5
5
  import type { ToolBackground } from '../tuiDisplayPrefs.js';
6
- export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground, }: {
6
+ export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground, reveal, }: {
7
7
  readonly diffs: readonly ToolFileDiff[];
8
8
  /** Content width available to the whole two-pane block (divider included). */
9
9
  readonly width: number;
@@ -11,5 +11,15 @@ export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBack
11
11
  readonly maxRows: number;
12
12
  readonly verbose: boolean;
13
13
  readonly toolBackground?: ToolBackground;
14
+ /**
15
+ * Smooth-streaming participation (the card owning this view computes
16
+ * eligibility): when present, the capped row list reveals line-by-line at
17
+ * the shared ~30fps cadence instead of painting as one block. The `+N
18
+ * lines` fold hint stays rendered throughout — it describes the cap, not
19
+ * the reveal.
20
+ */
21
+ readonly reveal?: {
22
+ readonly key: string;
23
+ };
14
24
  }): React.ReactNode;
15
25
  //# sourceMappingURL=SplitDiffView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAO7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAuR3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAkFlB"}
1
+ {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAO7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAwR3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,EACvB,MAAM,GACP,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3C,GAAG,KAAK,CAAC,SAAS,CAwFlB"}