parallel-codex-tui 0.1.0 → 0.1.4

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 (74) hide show
  1. package/.parallel-codex/config.example.toml +90 -3
  2. package/README.md +269 -12
  3. package/dist/bootstrap.js +50 -18
  4. package/dist/cli-args.js +96 -14
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-recovery.js +70 -0
  7. package/dist/cli-workspace-picker.js +330 -0
  8. package/dist/cli-workspace-transition.js +33 -0
  9. package/dist/cli-workspace.js +40 -0
  10. package/dist/cli.js +291 -35
  11. package/dist/core/app-root.js +8 -0
  12. package/dist/core/collaboration-timeline.js +261 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +191 -23
  15. package/dist/core/file-store.js +130 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +10 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +473 -42
  25. package/dist/core/session-index.js +225 -30
  26. package/dist/core/session-manager.js +1182 -44
  27. package/dist/core/task-state-machine.js +17 -0
  28. package/dist/core/workspace-commit-recovery.js +118 -0
  29. package/dist/core/workspace.js +126 -0
  30. package/dist/doctor.js +384 -30
  31. package/dist/domain/schemas.js +127 -6
  32. package/dist/orchestrator/collaboration-channel.js +255 -4
  33. package/dist/orchestrator/feature-plan.js +70 -0
  34. package/dist/orchestrator/judge-artifacts.js +236 -0
  35. package/dist/orchestrator/orchestrator.js +1777 -212
  36. package/dist/orchestrator/prompts.js +126 -2
  37. package/dist/orchestrator/supervisor-summary.js +56 -2
  38. package/dist/orchestrator/workspace-sandbox.js +911 -0
  39. package/dist/tui/App.js +2838 -159
  40. package/dist/tui/AppShell.js +188 -23
  41. package/dist/tui/CollaborationTimelineView.js +327 -0
  42. package/dist/tui/FeatureBoardView.js +227 -0
  43. package/dist/tui/InputBar.js +514 -57
  44. package/dist/tui/RouterDiagnosticsView.js +469 -0
  45. package/dist/tui/StatusBar.js +610 -57
  46. package/dist/tui/TaskSessionsView.js +207 -0
  47. package/dist/tui/TerminalOutput.js +53 -9
  48. package/dist/tui/WorkerOutputView.js +1403 -161
  49. package/dist/tui/WorkerOverviewView.js +250 -0
  50. package/dist/tui/chat-history.js +25 -0
  51. package/dist/tui/chat-input.js +67 -19
  52. package/dist/tui/chat-paste.js +76 -0
  53. package/dist/tui/display-width.js +41 -3
  54. package/dist/tui/incremental-text-file.js +101 -0
  55. package/dist/tui/keyboard.js +46 -0
  56. package/dist/tui/markdown-text.js +14 -0
  57. package/dist/tui/raw-input-decoder.js +3 -0
  58. package/dist/tui/scrolling.js +2 -1
  59. package/dist/tui/status-line.js +318 -11
  60. package/dist/tui/task-memory.js +15 -0
  61. package/dist/tui/task-result.js +105 -0
  62. package/dist/tui/terminal-screen.js +13 -1
  63. package/dist/tui/theme-contrast.js +144 -0
  64. package/dist/tui/theme-preview.js +109 -0
  65. package/dist/tui/theme.js +158 -0
  66. package/dist/version.js +1 -1
  67. package/dist/workers/capabilities.js +212 -0
  68. package/dist/workers/live-probe.js +176 -0
  69. package/dist/workers/mock-adapter.js +39 -6
  70. package/dist/workers/native-attach.js +147 -8
  71. package/dist/workers/native-session-detection.js +17 -0
  72. package/dist/workers/process-adapter.js +580 -81
  73. package/dist/workers/registry.js +4 -2
  74. package/package.json +17 -2
@@ -1,39 +1,110 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { compactTailByDisplayWidth, displayWidth } from "./display-width.js";
4
- const INPUT_BAR_BACKGROUND = "ansi256(234)";
5
- export function InputBar({ mode, busy = false, hasWorkers = false, nativeClosed = false, value, terminalWidth = process.stdout.columns || 120, onChange, onSubmit }) {
3
+ import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
4
+ import { TUI_THEME } from "./theme.js";
5
+ export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCancelConfirm = false, taskSessionAction = null, taskSessionsIncludeArchived = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
6
+ const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
7
+ const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
8
+ if (mode === "worker-search") {
9
+ const suffix = workerSearchInputSuffix(terminalWidth, searchMatchIndex, searchMatchCount);
10
+ const prefix = terminalWidth < 4 ? "/" : "/ ";
11
+ const textBudget = Math.max(1, terminalWidth - (terminalWidth > 1 ? 2 : 0));
12
+ const valueWidth = Math.max(1, textBudget - displayWidth(prefix) - 1 - displayWidth(suffix));
13
+ const display = chatInputDisplayParts(value, cursor ?? Array.from(value).length, valueWidth + 6);
14
+ const textWidth = displayWidth(`${prefix}${display.before}|${display.after}${suffix}`);
15
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: textWidth, fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prefix }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after }), suffix ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: suffix }) : null] }));
16
+ }
17
+ if (mode === "sessions") {
18
+ if (taskSessionAction?.type === "rename") {
19
+ const prefix = terminalWidth < 12 ? "> " : "rename > ";
20
+ const valueWidth = Math.max(1, terminalWidth - displayWidth(prefix) - 3);
21
+ const display = chatInputDisplayParts(taskSessionAction.value, taskSessionAction.cursor, valueWidth);
22
+ const textWidth = displayWidth(`${prefix}${display.before}|${display.after}`);
23
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: textWidth, fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prefix }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after })] }));
24
+ }
25
+ if (taskSessionAction?.type === "delete") {
26
+ const hints = taskSessionDeleteInputHints(terminalWidth, taskSessionAction.title);
27
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.danger, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
28
+ }
29
+ const hints = taskSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
30
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
31
+ }
32
+ if (mode === "collaboration") {
33
+ const hints = collaborationDetail
34
+ ? collaborationDetailInputHints(terminalWidth)
35
+ : collaborationTimelineInputHints(terminalWidth, collaborationUnresolved, collaborationBack);
36
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
37
+ }
38
+ if (mode === "features") {
39
+ const hints = featureBoardInputHints(terminalWidth, {
40
+ canCancel: featureCanCancel,
41
+ confirmCancel: featureCancelConfirm,
42
+ canRetry
43
+ });
44
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
45
+ }
46
+ if (mode === "workers") {
47
+ const hints = workerOverviewInputHints(terminalWidth);
48
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
49
+ }
6
50
  if (mode === "worker") {
7
51
  const hints = workerInputHints(terminalWidth);
8
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: hints.detail }) : null] }));
52
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
9
53
  }
10
54
  if (mode === "native") {
11
55
  const hints = nativeInputHints(terminalWidth, nativeClosed);
12
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: hints.detail }) : null] }));
56
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
57
+ }
58
+ if (mode === "router") {
59
+ const hints = routerInputHints(terminalWidth);
60
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
61
+ }
62
+ if (!ready) {
63
+ const starting = chatStartingDisplayValue(terminalWidth);
64
+ return (_jsx(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(starting), fill: fillRail, children: _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: starting }) }));
65
+ }
66
+ if (routeFallback) {
67
+ const hints = routeFallbackInputHints(terminalWidth);
68
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: hints.detail }) : null] }));
13
69
  }
14
70
  const busyText = chatBusyDisplayValue(terminalWidth);
15
71
  const prompt = busy ? "run" : ">";
16
72
  if (busy) {
17
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(busyText ? `${prompt} ${busyText}` : prompt), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "yellow", bold: true, children: prompt }), busyText ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "yellow", children: busyText })] })) : null] }));
73
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(busyText ? `${prompt} ${busyText}` : prompt), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: prompt }), busyText ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, children: busyText })] })) : null] }));
18
74
  }
19
75
  if (value) {
20
- const displayValue = chatInputDisplayValue(value, terminalWidth);
21
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt} ${displayValue}|`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: prompt }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }), _jsxs(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: [displayValue, "|"] })] }));
76
+ const display = chatInputDisplayParts(value, cursor ?? Array.from(value).length, terminalWidth);
77
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt} ${display.before}|${display.after}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prompt }), _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after })] }));
22
78
  }
23
79
  const hasLeadingPromptSpace = terminalWidth >= 10;
24
- const placeholder = chatPlaceholderDisplayText(terminalWidth, { hasWorkers }, { leadingSpace: hasLeadingPromptSpace });
25
- return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt}${hasLeadingPromptSpace ? " " : ""}|${placeholder}`), children: [_jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, color: "cyan", bold: true, children: prompt }), hasLeadingPromptSpace ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " " }) : null, _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: "|" }), _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, dimColor: true, children: placeholder })] }));
80
+ const placeholder = chatPlaceholderDisplayText(terminalWidth, {
81
+ hasWorkers,
82
+ hasActiveTask,
83
+ hasTaskResult,
84
+ taskResultExpanded,
85
+ canRetry,
86
+ scrollOffset: chatScrollOffset,
87
+ maxScrollOffset: chatMaxScrollOffset
88
+ }, { leadingSpace: hasLeadingPromptSpace });
89
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${prompt}${hasLeadingPromptSpace ? " " : ""}|${placeholder}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prompt }), hasLeadingPromptSpace ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }) : null, _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: canRetry ? TUI_THEME.warning : hasTaskResult ? TUI_THEME.accent : TUI_THEME.muted, children: placeholder })] }));
90
+ }
91
+ function InputRail({ fill, terminalWidth, textWidth, children }) {
92
+ const { leadingWidth, trailingWidth } = inputRailLayout(terminalWidth, textWidth, { fill });
93
+ return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(leadingWidth) }) : null, children, trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.rail, children: " ".repeat(trailingWidth) }) : null] }));
26
94
  }
27
- function InputRail({ terminalWidth, textWidth, children }) {
95
+ export function inputRailLayout(terminalWidth, textWidth, options = {}) {
28
96
  const leadingWidth = terminalWidth > 1 ? 1 : 0;
97
+ if (options.fill === false) {
98
+ return { leadingWidth, trailingWidth: 0 };
99
+ }
29
100
  const renderWidth = typeof process.stdout.columns === "number"
30
101
  ? Math.max(1, Math.min(terminalWidth, process.stdout.columns))
31
- : null;
32
- const barWidth = renderWidth === null ? null : Math.max(1, renderWidth - 1);
33
- const trailingWidth = barWidth === null
34
- ? 0
35
- : Math.max(0, barWidth - leadingWidth - textWidth);
36
- return (_jsxs(Box, { children: [leadingWidth > 0 ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " ".repeat(leadingWidth) }) : null, children, trailingWidth > 0 ? _jsx(Text, { backgroundColor: INPUT_BAR_BACKGROUND, children: " ".repeat(trailingWidth) }) : null] }));
102
+ : Math.max(1, terminalWidth);
103
+ const barWidth = Math.max(1, renderWidth - 1);
104
+ return {
105
+ leadingWidth,
106
+ trailingWidth: Math.max(0, barWidth - leadingWidth - Math.max(0, textWidth))
107
+ };
37
108
  }
38
109
  export function chatPlaceholderDisplayText(terminalWidth, options = {}, display = {}) {
39
110
  const placeholder = chatPlaceholderDisplayValue(terminalWidth, options);
@@ -44,37 +115,211 @@ export function chatPlaceholderDisplayText(terminalWidth, options = {}, display
44
115
  }
45
116
  export function chatInputDisplayValue(value, terminalWidth) {
46
117
  const valueWidth = Math.max(1, terminalWidth - 6);
47
- return compactTailByDisplayWidth(value, valueWidth);
118
+ return compactTailByDisplayWidth(chatInputVisibleValue(value), valueWidth);
119
+ }
120
+ export function chatInputDisplayParts(value, cursor, terminalWidth) {
121
+ const sourceChars = Array.from(value);
122
+ const chars = sourceChars.map(chatInputVisibleCharacter);
123
+ const clampedCursor = Math.min(sourceChars.length, Math.max(0, Math.trunc(cursor)));
124
+ const valueWidth = Math.max(1, terminalWidth - 6);
125
+ if (displayWidth(chars.join("")) <= valueWidth) {
126
+ return {
127
+ before: chars.slice(0, clampedCursor).join(""),
128
+ after: chars.slice(clampedCursor).join("")
129
+ };
130
+ }
131
+ const beforeCursor = chars.slice(0, clampedCursor).join("");
132
+ const afterCursor = chars.slice(clampedCursor).join("");
133
+ if (valueWidth < 6) {
134
+ return clampedCursor > 0
135
+ ? { before: compactTailByDisplayWidth(beforeCursor, valueWidth), after: "" }
136
+ : { before: "", after: compactEndByDisplayWidth(afterCursor, valueWidth) };
137
+ }
138
+ let start = clampedCursor;
139
+ let end = clampedCursor;
140
+ const renderedWidth = (nextStart, nextEnd) => displayWidth([
141
+ nextStart > 0 ? "..." : "",
142
+ chars.slice(nextStart, nextEnd).join(""),
143
+ nextEnd < chars.length ? "..." : ""
144
+ ].join(""));
145
+ while (true) {
146
+ const canExpandLeft = start > 0 && renderedWidth(start - 1, end) <= valueWidth;
147
+ const canExpandRight = end < chars.length && renderedWidth(start, end + 1) <= valueWidth;
148
+ if (!canExpandLeft && !canExpandRight) {
149
+ break;
150
+ }
151
+ if (canExpandLeft && canExpandRight) {
152
+ const leftWidth = displayWidth(chars.slice(start, clampedCursor).join(""));
153
+ const rightWidth = displayWidth(chars.slice(clampedCursor, end).join(""));
154
+ if (leftWidth <= rightWidth) {
155
+ start -= 1;
156
+ }
157
+ else {
158
+ end += 1;
159
+ }
160
+ }
161
+ else if (canExpandLeft) {
162
+ start -= 1;
163
+ }
164
+ else {
165
+ end += 1;
166
+ }
167
+ }
168
+ return {
169
+ before: `${start > 0 ? "..." : ""}${chars.slice(start, clampedCursor).join("")}`,
170
+ after: `${chars.slice(clampedCursor, end).join("")}${end < chars.length ? "..." : ""}`
171
+ };
172
+ }
173
+ function chatInputVisibleValue(value) {
174
+ return Array.from(value).map(chatInputVisibleCharacter).join("");
175
+ }
176
+ function chatInputVisibleCharacter(char) {
177
+ if (char === "\n" || char === "\r") {
178
+ return "↵";
179
+ }
180
+ if (char === "\t") {
181
+ return "⇥";
182
+ }
183
+ return char;
48
184
  }
49
185
  export function chatPlaceholderDisplayValue(terminalWidth, options = {}) {
50
- if (options.hasWorkers) {
51
- return chatTaskPlaceholderDisplayValue(terminalWidth);
186
+ if (options.canRetry) {
187
+ return selectChatPlaceholder(terminalWidth, options.hasActiveTask
188
+ ? ["message · ^R retry · ^N new", "^R retry · ^N", "^R retry", "retry"]
189
+ : ["message · ^R retry", "^R retry", "retry"]);
52
190
  }
53
- if (terminalWidth < 14) {
54
- return "msg";
191
+ const maxScrollOffset = Math.max(0, options.maxScrollOffset ?? 0);
192
+ const scrollOffset = Math.min(Math.max(0, options.scrollOffset ?? 0), maxScrollOffset);
193
+ if (options.hasTaskResult) {
194
+ return chatTaskResultPlaceholderDisplayValue(terminalWidth, Boolean(options.taskResultExpanded), Boolean(options.hasWorkers), Boolean(options.hasActiveTask), scrollOffset, maxScrollOffset);
55
195
  }
56
- if (terminalWidth < 22) {
57
- return "message";
196
+ if (scrollOffset > 0) {
197
+ return chatHistoryPlaceholderDisplayValue(terminalWidth, scrollOffset, maxScrollOffset);
58
198
  }
59
- return chatInputDisplayValue("Type a message", terminalWidth);
199
+ if (options.hasActiveTask && !options.hasWorkers) {
200
+ return selectChatPlaceholder(terminalWidth, [
201
+ "message · ^N new · ^P project · ^T tasks · ^G routes",
202
+ "message · ^N new · ^P project · ^G routes",
203
+ "message · ^N new · ^P project",
204
+ "message · ^N new",
205
+ "msg · ^N",
206
+ "msg"
207
+ ]);
208
+ }
209
+ if (options.hasWorkers) {
210
+ return chatTaskPlaceholderDisplayValue(terminalWidth, maxScrollOffset > 0, options.hasActiveTask);
211
+ }
212
+ if (maxScrollOffset > 0 && terminalWidth >= 22) {
213
+ return selectChatPlaceholder(terminalWidth, ["message · scroll", "message", "msg"]);
214
+ }
215
+ return selectChatPlaceholder(terminalWidth, [
216
+ "message · ^P project · ^T tasks · ^G routes",
217
+ "message · ^P project · ^G routes",
218
+ "message · ^P project",
219
+ "message",
220
+ "msg"
221
+ ]);
222
+ }
223
+ function chatTaskResultPlaceholderDisplayValue(terminalWidth, expanded, hasWorkers, hasActiveTask, scrollOffset, maxScrollOffset) {
224
+ const toggle = expanded ? "^D compact" : "^D details";
225
+ const position = expanded && maxScrollOffset > 0
226
+ ? scrollOffset > 0 ? `result ${scrollOffset}/${maxScrollOffset}` : "result · scroll"
227
+ : "message";
228
+ const candidates = hasWorkers
229
+ ? [
230
+ `${position} · ${toggle} · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach`,
231
+ `${position} · ${toggle} · ^N new · ^W logs · ^T tasks · Tab · ^O attach`,
232
+ `${position} · ${toggle} · ^W logs · ^B workers · ^T tasks · Tab · ^O attach`,
233
+ `${position} · ${toggle} · ^W logs · ^T tasks · Tab · ^O attach`,
234
+ `${position} · ${toggle} · ^W logs · Tab · ^O attach`,
235
+ `${position} · ${toggle} · ^W logs`,
236
+ `${toggle} · ^W logs`,
237
+ toggle,
238
+ "^D"
239
+ ]
240
+ : [
241
+ `${position} · ${toggle}${hasActiveTask ? " · ^N new" : ""}`,
242
+ `${position} · ${toggle}`,
243
+ toggle,
244
+ "^D"
245
+ ];
246
+ return selectChatPlaceholder(terminalWidth, candidates);
60
247
  }
61
- function chatTaskPlaceholderDisplayValue(terminalWidth) {
248
+ function chatHistoryPlaceholderDisplayValue(terminalWidth, offset, maxOffset) {
62
249
  if (terminalWidth < 14) {
63
- return "msg";
250
+ return "back";
64
251
  }
65
- if (terminalWidth < 19) {
66
- return "msg · ^W";
67
- }
68
- if (terminalWidth < 24) {
69
- return "msg · ^W · ^O";
252
+ if (terminalWidth < 20) {
253
+ return `back ${offset}`;
70
254
  }
71
255
  if (terminalWidth < 38) {
72
- return chatInputDisplayValue("Message · ^W · ^O", terminalWidth);
256
+ return selectChatPlaceholder(terminalWidth, [
257
+ `back ${offset}/${maxOffset} · PgDn`,
258
+ `back ${offset}/${maxOffset}`,
259
+ `back ${offset}`,
260
+ "back"
261
+ ]);
73
262
  }
74
- if (terminalWidth < 64) {
75
- return chatInputDisplayValue("Message · ^W logs · ^O attach", terminalWidth);
263
+ return selectChatPlaceholder(terminalWidth, [
264
+ `message · back ${offset}/${maxOffset} · PgDn latest`,
265
+ `back ${offset}/${maxOffset} · PgDn latest`,
266
+ `back ${offset}/${maxOffset} · PgDn`,
267
+ `back ${offset}/${maxOffset}`,
268
+ `back ${offset}`,
269
+ "back"
270
+ ]);
271
+ }
272
+ function chatTaskPlaceholderDisplayValue(terminalWidth, scrollable = false, activeTask = false) {
273
+ if (activeTask && terminalWidth >= 72) {
274
+ const activeCandidates = scrollable
275
+ ? [
276
+ "message · scroll · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
277
+ "message · scroll · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
278
+ ]
279
+ : [
280
+ "message · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
281
+ "message · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
282
+ ];
283
+ const active = activeCandidates.find((candidate) => displayWidth(candidate) <= chatPlaceholderValueWidth(terminalWidth));
284
+ if (active) {
285
+ return active;
286
+ }
76
287
  }
77
- return chatInputDisplayValue("Type a message · ^W logs · ^O attach", terminalWidth);
288
+ return selectChatPlaceholder(terminalWidth, scrollable
289
+ ? [
290
+ "message · scroll · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
291
+ "message · scroll · ^W logs · ^B workers · Tab · ^O attach · ^G routes",
292
+ "message · scroll · ^W logs · Tab · ^O attach · ^G routes",
293
+ "message · scroll · ^W logs · Tab · ^O attach",
294
+ "scroll · ^W logs · Tab · ^O attach",
295
+ "scroll · ^W logs · ^O attach",
296
+ "^W logs · ^O attach",
297
+ "message · ^W logs",
298
+ "msg · ^W logs",
299
+ "^W logs",
300
+ "msg"
301
+ ]
302
+ : [
303
+ "message · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
304
+ "message · ^W logs · ^B workers · Tab · ^O attach · ^G routes",
305
+ "message · ^W logs · Tab · ^O attach · ^G routes",
306
+ "message · ^W logs · Tab · ^O attach",
307
+ "message · ^W logs · ^O attach",
308
+ "^W logs · ^O attach",
309
+ "message · ^W logs",
310
+ "msg · ^W logs",
311
+ "^W logs",
312
+ "msg"
313
+ ]);
314
+ }
315
+ function selectChatPlaceholder(terminalWidth, candidates) {
316
+ const valueWidth = chatPlaceholderValueWidth(terminalWidth);
317
+ return candidates.find((candidate) => displayWidth(candidate) <= valueWidth)
318
+ ?? candidates.at(-1)
319
+ ?? "";
320
+ }
321
+ function chatPlaceholderValueWidth(terminalWidth) {
322
+ return Math.max(1, terminalWidth - (terminalWidth >= 10 ? 6 : 4));
78
323
  }
79
324
  export function chatBusyDisplayValue(terminalWidth) {
80
325
  if (terminalWidth < 14) {
@@ -83,28 +328,228 @@ export function chatBusyDisplayValue(terminalWidth) {
83
328
  if (terminalWidth < 22) {
84
329
  return "busy";
85
330
  }
86
- return "Running...";
331
+ if (terminalWidth >= 34) {
332
+ return "working · Esc stop";
333
+ }
334
+ return "working";
335
+ }
336
+ function routeFallbackInputHints(width) {
337
+ if (width < 10) {
338
+ return { label: "route", detail: "" };
339
+ }
340
+ if (width < 14) {
341
+ return { label: "choose", detail: "" };
342
+ }
343
+ if (width < 20) {
344
+ return { label: "1M", detail: " · 2P" };
345
+ }
346
+ if (width < 28) {
347
+ return { label: "1 Main", detail: " · 2 Pair" };
348
+ }
349
+ if (width < 40) {
350
+ return { label: "1 Main", detail: " · 2 Parallel" };
351
+ }
352
+ if (width < 54) {
353
+ return { label: "route", detail: " · 1 Main · 2 Parallel · Esc" };
354
+ }
355
+ if (width < 75) {
356
+ return { label: "route failed", detail: " · 1 Main · 2 Parallel · R · Esc" };
357
+ }
358
+ return { label: "route failed", detail: " · 1 Main · 2 Parallel · R retry · Esc cancel" };
359
+ }
360
+ export function chatStartingDisplayValue(terminalWidth) {
361
+ if (terminalWidth < 8) {
362
+ return "";
363
+ }
364
+ return terminalWidth < 12 ? "start" : "starting";
87
365
  }
88
366
  function workerInputHints(width) {
89
- if (width < 12) {
367
+ if (width < 10) {
90
368
  return { label: "log", detail: "" };
91
369
  }
92
- if (width < 15) {
93
- return { label: "log", detail: " · ^O" };
370
+ if (width < 16) {
371
+ return { label: "Esc chat", detail: "" };
372
+ }
373
+ if (width < 17) {
374
+ return { label: "log", detail: " · Esc chat" };
375
+ }
376
+ if (width < 22) {
377
+ return { label: "logs", detail: " · Esc chat" };
94
378
  }
95
- if (width < 18) {
96
- return { label: "log", detail: " · Pg · ^O" };
379
+ if (width < 28) {
380
+ return { label: "logs", detail: " · Pg · Esc chat" };
381
+ }
382
+ if (width < 32) {
383
+ return { label: "logs", detail: " · Pg · Tab · Esc chat" };
384
+ }
385
+ if (width < 44) {
386
+ return { label: "logs", detail: " · scroll · Tab · Esc chat" };
387
+ }
388
+ if (width < 54) {
389
+ return { label: "logs", detail: " · scroll · Tab · ^O attach · Esc chat" };
390
+ }
391
+ if (width < 71) {
392
+ return { label: "logs", detail: " · scroll · ^F find · Tab · ^O attach · Esc chat" };
393
+ }
394
+ if (width < 84) {
395
+ return { label: "logs", detail: " · scroll · ^F find · E err · D diff · Tab · ^O attach · Esc chat" };
396
+ }
397
+ return { label: "logs", detail: " · scroll · ^F find · E err · D diff · Tab · ^B workers · ^O attach · Esc chat" };
398
+ }
399
+ function workerOverviewInputHints(width) {
400
+ if (width < 16) {
401
+ return { label: "wrk", detail: "" };
402
+ }
403
+ if (width < 20) {
404
+ return { label: "wrk", detail: " · Esc back" };
97
405
  }
98
406
  if (width < 28) {
99
- return { label: "logs", detail: " · Pg · ^O" };
407
+ return { label: "workers", detail: " · Esc back" };
100
408
  }
101
- if (width < 56) {
102
- return { label: "logs", detail: " · Pg · Tab · ^O · Esc" };
409
+ if (width < 41) {
410
+ return { label: "workers", detail: " · Up/Dn · Esc back" };
411
+ }
412
+ if (width < 53) {
413
+ return { label: "workers", detail: " · Up/Dn · Enter logs · Esc back" };
103
414
  }
104
415
  if (width < 72) {
105
- return { label: "logs", detail: " · Pg/wheel · Tab · ^O · Esc" };
416
+ return { label: "workers", detail: " · Up/Dn · Enter logs · ^O attach · Esc back" };
417
+ }
418
+ if (width < 86) {
419
+ return { label: "workers", detail: " · Up/Dn · Enter logs · F board · C flow · ^O attach · Esc back" };
106
420
  }
107
- return { label: "logs", detail: " · read · wheel/Pg · Tab worker · ^O attach · Esc chat" };
421
+ return { label: "workers", detail: " · Up/Dn select · Enter logs · F features · C timeline · ^O attach · Esc back" };
422
+ }
423
+ function featureBoardInputHints(width, options) {
424
+ if (options.confirmCancel) {
425
+ return selectInputHints(width, [
426
+ { label: "cancel feature?", detail: " · X confirm · Esc keep" },
427
+ { label: "cancel?", detail: " · X confirm · Esc keep" },
428
+ { label: "cancel?", detail: " · Esc keep" },
429
+ { label: "Esc keep", detail: "" },
430
+ { label: "stop?", detail: "" },
431
+ { label: "?", detail: "" }
432
+ ]);
433
+ }
434
+ if (options.canCancel) {
435
+ return selectInputHints(width, [
436
+ { label: "features", detail: " · Up/Dn select · Enter timeline · X cancel · R refresh · Esc workers" },
437
+ { label: "features", detail: " · Up/Dn select · X cancel · R refresh · Esc workers" },
438
+ { label: "features", detail: " · Up/Dn select · X cancel · Esc workers" },
439
+ { label: "features", detail: " · X cancel · Esc workers" },
440
+ { label: "features", detail: " · Esc workers" },
441
+ { label: "features", detail: "" },
442
+ { label: "ft", detail: "" },
443
+ { label: "f", detail: "" }
444
+ ]);
445
+ }
446
+ if (options.canRetry) {
447
+ return selectInputHints(width, [
448
+ { label: "features", detail: " · Up/Dn select · Enter timeline · ^R retry task · R refresh · Esc workers" },
449
+ { label: "features", detail: " · Up/Dn select · ^R retry task · R refresh · Esc workers" },
450
+ { label: "features", detail: " · Up/Dn select · ^R retry · R refresh · Esc workers" },
451
+ { label: "features", detail: " · Up/Dn select · ^R retry · Esc workers" },
452
+ { label: "features", detail: " · ^R retry · Esc workers" },
453
+ { label: "features", detail: " · Esc workers" },
454
+ { label: "features", detail: "" },
455
+ { label: "ft", detail: "" },
456
+ { label: "f", detail: "" }
457
+ ]);
458
+ }
459
+ return selectInputHints(width, [
460
+ { label: "features", detail: " · Up/Dn select · Enter timeline · R refresh · Esc workers" },
461
+ { label: "features", detail: " · Up/Dn select · Enter timeline · Esc workers" },
462
+ { label: "features", detail: " · Up/Dn select · Esc workers" },
463
+ { label: "features", detail: " · Esc workers" },
464
+ { label: "features", detail: "" },
465
+ { label: "ft", detail: "" },
466
+ { label: "f", detail: "" }
467
+ ]);
468
+ }
469
+ function collaborationTimelineInputHints(width, unresolvedOnly, back) {
470
+ const backAction = `Esc ${back}`;
471
+ const filterAction = unresolvedOnly ? "U all" : "U unresolved";
472
+ const compactFilterAction = unresolvedOnly ? "U all" : "U open";
473
+ return selectInputHints(width, [
474
+ {
475
+ label: "timeline",
476
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${filterAction} · R refresh · ${backAction}`
477
+ },
478
+ {
479
+ label: "timeline",
480
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${compactFilterAction} · R refresh · ${backAction}`
481
+ },
482
+ {
483
+ label: "timeline",
484
+ detail: ` · Up/Dn event · Enter detail · Tab feature · ${compactFilterAction} · ${backAction}`
485
+ },
486
+ { label: "timeline", detail: ` · Up/Dn event · Enter detail · Tab feature · ${backAction}` },
487
+ { label: "timeline", detail: ` · Up/Dn event · Enter detail · ${backAction}` },
488
+ { label: "timeline", detail: ` · Enter detail · ${backAction}` },
489
+ { label: "timeline", detail: ` · ${backAction}` },
490
+ { label: backAction, detail: "" },
491
+ { label: "flow", detail: "" },
492
+ { label: "tl", detail: "" },
493
+ { label: "t", detail: "" }
494
+ ]);
495
+ }
496
+ function collaborationDetailInputHints(width) {
497
+ return selectInputHints(width, [
498
+ { label: "event detail", detail: " · scroll · Enter/Esc timeline" },
499
+ { label: "event", detail: " · Pg scroll · Enter/Esc timeline" },
500
+ { label: "event", detail: " · Pg scroll · Esc timeline" },
501
+ { label: "event", detail: " · Esc timeline" },
502
+ { label: "Esc timeline", detail: "" },
503
+ { label: "event", detail: "" },
504
+ { label: "e", detail: "" }
505
+ ]);
506
+ }
507
+ function taskSessionsInputHints(width, includeArchived) {
508
+ const archivedAction = includeArchived ? "H hide archived" : "H archived";
509
+ return selectInputHints(width, [
510
+ { label: "sessions", detail: ` · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
511
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · Esc back" },
512
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · Esc back" },
513
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · Esc back" },
514
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · Esc back" },
515
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · Esc back" },
516
+ { label: "sessions", detail: " · Up/Dn select · Esc back" },
517
+ { label: "sessions", detail: " · Esc back" },
518
+ { label: "Esc back", detail: "" },
519
+ { label: "sessions", detail: "" },
520
+ { label: "ses", detail: "" },
521
+ { label: "s", detail: "" }
522
+ ]);
523
+ }
524
+ function taskSessionDeleteInputHints(width, title) {
525
+ const safeTitle = title.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
526
+ return selectInputHints(width, [
527
+ { label: "delete", detail: ` · ${safeTitle} · D confirm · Esc cancel` },
528
+ { label: "delete", detail: " · D confirm · Esc cancel" },
529
+ { label: "D confirm", detail: " · Esc cancel" },
530
+ { label: "D confirm", detail: "" },
531
+ { label: "D", detail: "" }
532
+ ]);
533
+ }
534
+ function workerSearchInputSuffix(width, matchIndex, matchCount) {
535
+ const count = Math.max(0, Math.trunc(matchCount));
536
+ const index = count > 0
537
+ ? Math.min(count - 1, Math.max(0, Math.trunc(matchIndex))) + 1
538
+ : 0;
539
+ const position = `${index}/${count}`;
540
+ if (width < 16) {
541
+ return "";
542
+ }
543
+ if (width < 24) {
544
+ return ` · ${position}`;
545
+ }
546
+ if (width < 36) {
547
+ return ` · ${position} · Esc`;
548
+ }
549
+ if (width < 52) {
550
+ return ` · ${position} · Enter · Esc`;
551
+ }
552
+ return ` · ${position} · Enter next · Up/Dn · Esc logs`;
108
553
  }
109
554
  function nativeInputHints(width, closed = false) {
110
555
  if (closed) {
@@ -117,13 +562,10 @@ function nativeInputHints(width, closed = false) {
117
562
  if (width < 24) {
118
563
  return { label: "closed", detail: " · ^]" };
119
564
  }
120
- if (width < 36) {
565
+ if (width < 27) {
121
566
  return { label: "closed", detail: " · Pg · ^]" };
122
567
  }
123
- if (width < 56) {
124
- return { label: "closed", detail: " · Pg/wheel · ^]" };
125
- }
126
- return { label: "closed", detail: " · wheel/Pg · ^] back" };
568
+ return { label: "closed", detail: " · scroll · ^] logs" };
127
569
  }
128
570
  if (width < 12) {
129
571
  return { label: "nat", detail: " ^]" };
@@ -131,11 +573,26 @@ function nativeInputHints(width, closed = false) {
131
573
  if (width < 24) {
132
574
  return { label: "native", detail: " · ^]" };
133
575
  }
134
- if (width < 36) {
576
+ if (width < 27) {
135
577
  return { label: "native", detail: " · Pg · ^]" };
136
578
  }
137
- if (width < 56) {
138
- return { label: "native", detail: " · Pg/wheel · ^]" };
139
- }
140
- return { label: "native", detail: " · wheel/Pg · ^] detach" };
579
+ return { label: "native", detail: " · scroll · ^] logs" };
580
+ }
581
+ function routerInputHints(width) {
582
+ return selectInputHints(width, [
583
+ { label: "routes", detail: " · scroll · Tab scope · ^G refresh · Esc chat" },
584
+ { label: "routes", detail: " · scroll · ^G refresh · Esc chat" },
585
+ { label: "routes", detail: " · Pg scroll · Esc chat" },
586
+ { label: "routes", detail: " · Esc chat" },
587
+ { label: "Esc chat", detail: "" },
588
+ { label: "routes", detail: "" },
589
+ { label: "rt", detail: "" },
590
+ { label: "r", detail: "" }
591
+ ]);
592
+ }
593
+ function selectInputHints(width, candidates) {
594
+ const contentWidth = Math.max(1, Math.trunc(width) - (width > 1 ? 2 : 0));
595
+ return candidates.find((candidate) => displayWidth(`${candidate.label}${candidate.detail}`) <= contentWidth)
596
+ ?? candidates.at(-1)
597
+ ?? { label: "", detail: "" };
141
598
  }