parallel-codex-tui 0.1.3 → 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 (72) hide show
  1. package/.parallel-codex/config.example.toml +90 -3
  2. package/README.md +240 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  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 +7 -71
  10. package/dist/cli.js +221 -23
  11. package/dist/core/collaboration-timeline.js +261 -0
  12. package/dist/core/config-errors.js +14 -0
  13. package/dist/core/config.js +154 -24
  14. package/dist/core/file-store.js +119 -6
  15. package/dist/core/lease-finalization.js +22 -0
  16. package/dist/core/paths.js +7 -0
  17. package/dist/core/process-identity.js +48 -0
  18. package/dist/core/process-mutation-turn.js +128 -0
  19. package/dist/core/process-ownership.js +276 -0
  20. package/dist/core/process-tree.js +90 -0
  21. package/dist/core/router-audit.js +155 -0
  22. package/dist/core/router-redaction.js +31 -0
  23. package/dist/core/router.js +462 -35
  24. package/dist/core/session-index.js +188 -37
  25. package/dist/core/session-manager.js +1086 -40
  26. package/dist/core/task-state-machine.js +17 -0
  27. package/dist/core/workspace-commit-recovery.js +118 -0
  28. package/dist/core/workspace.js +19 -11
  29. package/dist/doctor.js +343 -23
  30. package/dist/domain/schemas.js +127 -6
  31. package/dist/orchestrator/collaboration-channel.js +255 -4
  32. package/dist/orchestrator/feature-plan.js +70 -0
  33. package/dist/orchestrator/judge-artifacts.js +236 -0
  34. package/dist/orchestrator/orchestrator.js +1749 -202
  35. package/dist/orchestrator/prompts.js +126 -2
  36. package/dist/orchestrator/supervisor-summary.js +56 -2
  37. package/dist/orchestrator/workspace-sandbox.js +911 -0
  38. package/dist/tui/App.js +2830 -153
  39. package/dist/tui/AppShell.js +188 -23
  40. package/dist/tui/CollaborationTimelineView.js +327 -0
  41. package/dist/tui/FeatureBoardView.js +227 -0
  42. package/dist/tui/InputBar.js +514 -57
  43. package/dist/tui/RouterDiagnosticsView.js +469 -0
  44. package/dist/tui/StatusBar.js +610 -57
  45. package/dist/tui/TaskSessionsView.js +207 -0
  46. package/dist/tui/TerminalOutput.js +53 -9
  47. package/dist/tui/WorkerOutputView.js +1403 -161
  48. package/dist/tui/WorkerOverviewView.js +250 -0
  49. package/dist/tui/chat-history.js +25 -0
  50. package/dist/tui/chat-input.js +67 -19
  51. package/dist/tui/chat-paste.js +76 -0
  52. package/dist/tui/display-width.js +41 -3
  53. package/dist/tui/incremental-text-file.js +101 -0
  54. package/dist/tui/keyboard.js +46 -0
  55. package/dist/tui/markdown-text.js +14 -0
  56. package/dist/tui/raw-input-decoder.js +3 -0
  57. package/dist/tui/scrolling.js +2 -1
  58. package/dist/tui/status-line.js +318 -11
  59. package/dist/tui/task-memory.js +13 -1
  60. package/dist/tui/task-result.js +105 -0
  61. package/dist/tui/terminal-screen.js +13 -1
  62. package/dist/tui/theme-contrast.js +144 -0
  63. package/dist/tui/theme-preview.js +109 -0
  64. package/dist/tui/theme.js +158 -0
  65. package/dist/version.js +1 -1
  66. package/dist/workers/capabilities.js +212 -0
  67. package/dist/workers/live-probe.js +176 -0
  68. package/dist/workers/mock-adapter.js +39 -6
  69. package/dist/workers/native-attach.js +78 -3
  70. package/dist/workers/process-adapter.js +570 -77
  71. package/dist/workers/registry.js +4 -2
  72. package/package.json +5 -2
@@ -0,0 +1,207 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
4
+ import { TUI_THEME } from "./theme.js";
5
+ export function TaskSessionsView({ tasks, activeTaskId, selectedIndex, includeArchived = false, notice = null, action = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
6
+ const viewportHeight = Math.max(1, height);
7
+ const width = taskSessionsContentWidth(terminalWidth);
8
+ const lines = taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, viewportHeight, terminalWidth, {
9
+ loading,
10
+ error,
11
+ includeArchived,
12
+ notice,
13
+ action
14
+ });
15
+ const blankRows = Math.max(0, viewportHeight - lines.length);
16
+ return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(TaskSessionRow, { line: line, width: width }, `${line.taskIndex ?? line.tone}-${index}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `task-session-fill-${index}`)))] }));
17
+ }
18
+ export function taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, height, terminalWidth, state = {}) {
19
+ const viewportHeight = Math.max(1, Math.trunc(height));
20
+ const width = taskSessionsContentWidth(terminalWidth);
21
+ const lines = [
22
+ {
23
+ text: fitTaskSessionCandidates([
24
+ state.includeArchived ? "Task sessions · archived shown" : "Task sessions",
25
+ state.includeArchived ? "Sessions · all" : "Sessions",
26
+ "Tasks",
27
+ "T"
28
+ ], width),
29
+ tone: "heading"
30
+ }
31
+ ];
32
+ if (viewportHeight >= 3) {
33
+ lines.push({ text: taskSessionSummary(tasks, width), tone: "muted" });
34
+ }
35
+ if (viewportHeight >= 4 && state.action) {
36
+ lines.push({
37
+ text: fitTaskSessionText(state.action.type === "rename"
38
+ ? `rename · ${safeTaskSessionText(state.action.title)} · Enter save · Esc cancel`
39
+ : `delete · ${safeTaskSessionText(state.action.title)} · press D again · Esc cancel`, width),
40
+ tone: state.action.type === "delete" ? "danger" : "active"
41
+ });
42
+ }
43
+ else if (viewportHeight >= 4 && state.notice) {
44
+ lines.push({ text: fitTaskSessionText(state.notice, width), tone: "success" });
45
+ }
46
+ const slots = Math.max(0, viewportHeight - lines.length);
47
+ if (state.loading) {
48
+ if (slots > 0) {
49
+ lines.push({ text: fitTaskSessionText("loading task sessions", width), tone: "muted" });
50
+ }
51
+ return lines;
52
+ }
53
+ if (state.error) {
54
+ if (slots > 0) {
55
+ lines.push({ text: fitTaskSessionText(`error · ${safeTaskSessionText(state.error)}`, width), tone: "danger" });
56
+ }
57
+ return lines;
58
+ }
59
+ if (tasks.length === 0) {
60
+ if (slots > 0) {
61
+ lines.push({ text: fitTaskSessionText("No saved task sessions", width), tone: "muted" });
62
+ }
63
+ return lines;
64
+ }
65
+ const selected = clampTaskIndex(selectedIndex, tasks.length);
66
+ const visibleCount = Math.min(slots, tasks.length);
67
+ const start = taskSessionWindowStart(selected, tasks.length, visibleCount);
68
+ for (let index = start; index < start + visibleCount; index += 1) {
69
+ const task = tasks[index];
70
+ if (!task) {
71
+ continue;
72
+ }
73
+ lines.push({
74
+ text: taskSessionRowText(task, index === selected, task.id === activeTaskId, width),
75
+ tone: taskSessionStatusTone(task),
76
+ taskIndex: index
77
+ });
78
+ }
79
+ return lines;
80
+ }
81
+ export function moveTaskSessionSelection(current, delta, taskCount, wrap = false) {
82
+ if (taskCount <= 0) {
83
+ return 0;
84
+ }
85
+ const normalizedCurrent = clampTaskIndex(current, taskCount);
86
+ const next = normalizedCurrent + Math.trunc(delta);
87
+ if (wrap) {
88
+ return ((next % taskCount) + taskCount) % taskCount;
89
+ }
90
+ return Math.min(taskCount - 1, Math.max(0, next));
91
+ }
92
+ function TaskSessionRow({ line, width }) {
93
+ const trailingWidth = Math.max(0, width - displayWidth(line.text));
94
+ const theme = taskSessionLineTheme(line.tone);
95
+ return (_jsxs(Text, { children: [_jsx(Text, { ...theme, children: line.text }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(trailingWidth) }) : null] }));
96
+ }
97
+ function taskSessionSummary(tasks, width) {
98
+ const counts = new Map();
99
+ let archived = 0;
100
+ for (const task of tasks) {
101
+ if (task.archived_at) {
102
+ archived += 1;
103
+ continue;
104
+ }
105
+ const group = taskSessionStatusGroup(task.status);
106
+ counts.set(group, (counts.get(group) ?? 0) + 1);
107
+ }
108
+ const parts = ["running", "done", "failed", "cancelled"].flatMap((group) => {
109
+ const count = counts.get(group) ?? 0;
110
+ return count > 0 ? [`${count} ${group}`] : [];
111
+ });
112
+ if (archived > 0) {
113
+ parts.push(`${archived} archived`);
114
+ }
115
+ return fitTaskSessionCandidates([
116
+ [`${tasks.length} ${tasks.length === 1 ? "task" : "tasks"}`, ...parts].join(" · "),
117
+ `${tasks.length} tasks · ${counts.get("running") ?? 0} active · ${counts.get("failed") ?? 0} failed`,
118
+ `${tasks.length} tasks`,
119
+ `${tasks.length}t`
120
+ ], width);
121
+ }
122
+ function taskSessionRowText(task, selected, active, width) {
123
+ const marker = `${selected ? ">" : " "} ${active ? "*" : " "} `;
124
+ const title = safeTaskSessionText(task.title);
125
+ const status = task.archived_at
126
+ ? `archived · ${humanizeTaskSessionStatus(task.status)}`
127
+ : humanizeTaskSessionStatus(task.status);
128
+ const date = task.created_at.slice(5, 16).replace("T", " ");
129
+ const turns = `${task.turnCount} ${task.turnCount === 1 ? "turn" : "turns"}`;
130
+ const workers = `${task.workerCount} ${task.workerCount === 1 ? "worker" : "workers"}`;
131
+ const native = `${task.nativeSessionCount} native`;
132
+ const compactId = task.id.replace(/^task-/, "#");
133
+ return fitTaskSessionCandidates([
134
+ [marker + title, status, turns, workers, native, date].join(" · "),
135
+ [marker + title, status, turns, workers, native].join(" · "),
136
+ [marker + title, status, turns, workers].join(" · "),
137
+ [marker + title, status].join(" · "),
138
+ [marker + compactId, status].join(" · "),
139
+ marker.trimEnd()
140
+ ], width);
141
+ }
142
+ function taskSessionWindowStart(selected, count, visibleCount) {
143
+ if (visibleCount <= 0 || count <= visibleCount) {
144
+ return 0;
145
+ }
146
+ return Math.min(count - visibleCount, Math.max(0, selected - Math.floor(visibleCount / 2)));
147
+ }
148
+ function clampTaskIndex(index, count) {
149
+ return Math.min(Math.max(0, count - 1), Math.max(0, Math.trunc(index)));
150
+ }
151
+ function taskSessionStatusGroup(status) {
152
+ if (status === "done" || status === "failed" || status === "cancelled") {
153
+ return status;
154
+ }
155
+ return "running";
156
+ }
157
+ function humanizeTaskSessionStatus(status) {
158
+ return status.replace(/_/g, " ");
159
+ }
160
+ function taskSessionStatusTone(task) {
161
+ if (task.archived_at) {
162
+ return "muted";
163
+ }
164
+ const status = task.status;
165
+ if (status === "done") {
166
+ return "success";
167
+ }
168
+ if (status === "failed") {
169
+ return "danger";
170
+ }
171
+ if (status === "cancelled") {
172
+ return "warning";
173
+ }
174
+ return "active";
175
+ }
176
+ function fitTaskSessionCandidates(candidates, width) {
177
+ return candidates.find((candidate) => displayWidth(candidate) <= width)
178
+ ?? fitTaskSessionText(candidates.at(-1) ?? "", width);
179
+ }
180
+ function fitTaskSessionText(text, width) {
181
+ return compactEndByDisplayWidth(text, Math.max(1, width));
182
+ }
183
+ function safeTaskSessionText(text) {
184
+ return text
185
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
186
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
187
+ .replace(/\s+/g, " ")
188
+ .trim();
189
+ }
190
+ function taskSessionLineTheme(tone) {
191
+ return {
192
+ backgroundColor: TUI_THEME.surface,
193
+ color: tone === "heading" || tone === "active"
194
+ ? TUI_THEME.accent
195
+ : tone === "success"
196
+ ? TUI_THEME.success
197
+ : tone === "warning"
198
+ ? TUI_THEME.warning
199
+ : tone === "danger"
200
+ ? TUI_THEME.danger
201
+ : TUI_THEME.muted,
202
+ ...(tone === "heading" || tone === "danger" ? { bold: true } : {})
203
+ };
204
+ }
205
+ function taskSessionsContentWidth(terminalWidth) {
206
+ return Math.max(1, terminalWidth - 2);
207
+ }
@@ -1,18 +1,36 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- export function TerminalOutput({ lines }) {
3
+ import { displayWidth } from "./display-width.js";
4
+ import { TUI_THEME } from "./theme.js";
5
+ const TERMINAL_OUTPUT_EMPTY_TEXT = "waiting for output";
6
+ export function TerminalOutput({ lines, minLines, width }) {
7
+ const blankTailLineCount = terminalOutputBlankTailLineCount(lines.length === 0 ? 1 : lines.length, minLines);
4
8
  if (lines.length === 0) {
5
- return _jsx(Text, { children: "(no output yet)" });
9
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { ...terminalOutputEmptyTheme(), children: TERMINAL_OUTPUT_EMPTY_TEXT }), _jsx(TerminalOutputEmptyTrailingFill, { width: width })] }), _jsx(TerminalOutputBlankTailLines, { count: blankTailLineCount, width: width })] }));
6
10
  }
7
- return (_jsx(Box, { flexDirection: "column", children: lines.map((line, index) => (_jsx(Text, { children: line.chunks.length === 0
8
- ? " "
9
- : line.chunks.map((chunk, chunkIndex) => (_jsx(Text, { ...textProps(chunk.style), children: chunk.text }, chunkIndex))) }, index))) }));
11
+ return (_jsxs(Box, { flexDirection: "column", children: [lines.map((line, index) => (_jsx(Text, { children: line.chunks.length === 0
12
+ ? _jsx(Text, { ...terminalOutputBlankLineTheme(), children: " ".repeat(terminalOutputBlankLineWidth(width)) })
13
+ : _jsxs(_Fragment, { children: [line.chunks.map((chunk, chunkIndex) => (_jsx(Text, { ...terminalOutputTextProps(chunk.style), children: chunk.text }, chunkIndex))), _jsx(TerminalOutputTrailingFill, { line: line, width: width })] }) }, index))), _jsx(TerminalOutputBlankTailLines, { count: blankTailLineCount, width: width })] }));
10
14
  }
11
- function textProps(style) {
15
+ export function terminalOutputEmptyTheme() {
12
16
  return {
13
- backgroundColor: style.backgroundColor,
17
+ backgroundColor: TUI_THEME.surface,
18
+ color: TUI_THEME.muted
19
+ };
20
+ }
21
+ export function terminalOutputEmptyText() {
22
+ return TERMINAL_OUTPUT_EMPTY_TEXT;
23
+ }
24
+ export function terminalOutputBlankLineTheme() {
25
+ return {
26
+ backgroundColor: TUI_THEME.surface
27
+ };
28
+ }
29
+ export function terminalOutputTextProps(style) {
30
+ return {
31
+ backgroundColor: style.backgroundColor ?? TUI_THEME.surface,
14
32
  bold: style.bold,
15
- color: style.color,
33
+ color: style.color ?? TUI_THEME.text,
16
34
  dimColor: style.dimColor,
17
35
  inverse: style.inverse || style.cursor,
18
36
  italic: style.italic,
@@ -20,3 +38,29 @@ function textProps(style) {
20
38
  underline: style.underline
21
39
  };
22
40
  }
41
+ export function terminalOutputTrailingFillWidth(line, width) {
42
+ if (width === undefined) {
43
+ return 0;
44
+ }
45
+ return Math.max(0, width - terminalOutputLineDisplayWidth(line));
46
+ }
47
+ function TerminalOutputTrailingFill({ line, width }) {
48
+ const fillWidth = terminalOutputTrailingFillWidth(line, width);
49
+ return fillWidth > 0 ? _jsx(Text, { ...terminalOutputBlankLineTheme(), children: " ".repeat(fillWidth) }) : null;
50
+ }
51
+ function TerminalOutputEmptyTrailingFill({ width }) {
52
+ const fillWidth = width === undefined ? 0 : Math.max(0, width - displayWidth(TERMINAL_OUTPUT_EMPTY_TEXT));
53
+ return fillWidth > 0 ? _jsx(Text, { ...terminalOutputBlankLineTheme(), children: " ".repeat(fillWidth) }) : null;
54
+ }
55
+ function TerminalOutputBlankTailLines({ count, width }) {
56
+ return (_jsx(_Fragment, { children: Array.from({ length: count }, (_, index) => (_jsx(Text, { ...terminalOutputBlankLineTheme(), children: " ".repeat(terminalOutputBlankLineWidth(width)) }, `blank-tail-${index}`))) }));
57
+ }
58
+ function terminalOutputBlankTailLineCount(lineCount, minLines) {
59
+ return minLines === undefined ? 0 : Math.max(0, minLines - lineCount);
60
+ }
61
+ function terminalOutputLineDisplayWidth(line) {
62
+ return line.chunks.reduce((sum, chunk) => sum + displayWidth(chunk.text), 0);
63
+ }
64
+ function terminalOutputBlankLineWidth(width) {
65
+ return Math.max(1, width ?? 1);
66
+ }