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.
- package/.parallel-codex/config.example.toml +90 -3
- package/README.md +269 -12
- package/dist/bootstrap.js +50 -18
- package/dist/cli-args.js +96 -14
- package/dist/cli-help.js +20 -0
- package/dist/cli-startup-recovery.js +70 -0
- package/dist/cli-workspace-picker.js +330 -0
- package/dist/cli-workspace-transition.js +33 -0
- package/dist/cli-workspace.js +40 -0
- package/dist/cli.js +291 -35
- package/dist/core/app-root.js +8 -0
- package/dist/core/collaboration-timeline.js +261 -0
- package/dist/core/config-errors.js +14 -0
- package/dist/core/config.js +191 -23
- package/dist/core/file-store.js +130 -6
- package/dist/core/lease-finalization.js +22 -0
- package/dist/core/paths.js +10 -0
- package/dist/core/process-identity.js +48 -0
- package/dist/core/process-mutation-turn.js +128 -0
- package/dist/core/process-ownership.js +276 -0
- package/dist/core/process-tree.js +90 -0
- package/dist/core/router-audit.js +155 -0
- package/dist/core/router-redaction.js +31 -0
- package/dist/core/router.js +473 -42
- package/dist/core/session-index.js +225 -30
- package/dist/core/session-manager.js +1182 -44
- package/dist/core/task-state-machine.js +17 -0
- package/dist/core/workspace-commit-recovery.js +118 -0
- package/dist/core/workspace.js +126 -0
- package/dist/doctor.js +384 -30
- package/dist/domain/schemas.js +127 -6
- package/dist/orchestrator/collaboration-channel.js +255 -4
- package/dist/orchestrator/feature-plan.js +70 -0
- package/dist/orchestrator/judge-artifacts.js +236 -0
- package/dist/orchestrator/orchestrator.js +1777 -212
- package/dist/orchestrator/prompts.js +126 -2
- package/dist/orchestrator/supervisor-summary.js +56 -2
- package/dist/orchestrator/workspace-sandbox.js +911 -0
- package/dist/tui/App.js +2838 -159
- package/dist/tui/AppShell.js +188 -23
- package/dist/tui/CollaborationTimelineView.js +327 -0
- package/dist/tui/FeatureBoardView.js +227 -0
- package/dist/tui/InputBar.js +514 -57
- package/dist/tui/RouterDiagnosticsView.js +469 -0
- package/dist/tui/StatusBar.js +610 -57
- package/dist/tui/TaskSessionsView.js +207 -0
- package/dist/tui/TerminalOutput.js +53 -9
- package/dist/tui/WorkerOutputView.js +1403 -161
- package/dist/tui/WorkerOverviewView.js +250 -0
- package/dist/tui/chat-history.js +25 -0
- package/dist/tui/chat-input.js +67 -19
- package/dist/tui/chat-paste.js +76 -0
- package/dist/tui/display-width.js +41 -3
- package/dist/tui/incremental-text-file.js +101 -0
- package/dist/tui/keyboard.js +46 -0
- package/dist/tui/markdown-text.js +14 -0
- package/dist/tui/raw-input-decoder.js +3 -0
- package/dist/tui/scrolling.js +2 -1
- package/dist/tui/status-line.js +318 -11
- package/dist/tui/task-memory.js +15 -0
- package/dist/tui/task-result.js +105 -0
- package/dist/tui/terminal-screen.js +13 -1
- package/dist/tui/theme-contrast.js +144 -0
- package/dist/tui/theme-preview.js +109 -0
- package/dist/tui/theme.js +158 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +212 -0
- package/dist/workers/live-probe.js +176 -0
- package/dist/workers/mock-adapter.js +39 -6
- package/dist/workers/native-attach.js +147 -8
- package/dist/workers/native-session-detection.js +17 -0
- package/dist/workers/process-adapter.js +580 -81
- package/dist/workers/registry.js +4 -2
- package/package.json +17 -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
|
-
|
|
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:
|
|
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 (
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
15
|
+
export function terminalOutputEmptyTheme() {
|
|
12
16
|
return {
|
|
13
|
-
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
|
+
}
|