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.
- package/.parallel-codex/config.example.toml +90 -3
- package/README.md +240 -21
- package/dist/bootstrap.js +37 -17
- package/dist/cli-args.js +34 -3
- 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 +7 -71
- package/dist/cli.js +221 -23
- package/dist/core/collaboration-timeline.js +261 -0
- package/dist/core/config-errors.js +14 -0
- package/dist/core/config.js +154 -24
- package/dist/core/file-store.js +119 -6
- package/dist/core/lease-finalization.js +22 -0
- package/dist/core/paths.js +7 -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 +462 -35
- package/dist/core/session-index.js +188 -37
- package/dist/core/session-manager.js +1086 -40
- package/dist/core/task-state-machine.js +17 -0
- package/dist/core/workspace-commit-recovery.js +118 -0
- package/dist/core/workspace.js +19 -11
- package/dist/doctor.js +343 -23
- 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 +1749 -202
- 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 +2830 -153
- 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 +13 -1
- 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 +78 -3
- package/dist/workers/process-adapter.js +570 -77
- package/dist/workers/registry.js +4 -2
- package/package.json +5 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const TUI_THEME_MIN_CONTRAST_RATIO = 4.5;
|
|
2
|
+
export const TUI_THEME_RENDERED_CONTRAST_PAIRS = Object.freeze([
|
|
3
|
+
["text", "chrome"],
|
|
4
|
+
["muted", "chrome"],
|
|
5
|
+
["accent", "chrome"],
|
|
6
|
+
["text", "surface"],
|
|
7
|
+
["muted", "surface"],
|
|
8
|
+
["accent", "surface"],
|
|
9
|
+
["warning", "surface"],
|
|
10
|
+
["success", "surface"],
|
|
11
|
+
["text", "rail"],
|
|
12
|
+
["muted", "rail"],
|
|
13
|
+
["accent", "rail"],
|
|
14
|
+
["warning", "rail"],
|
|
15
|
+
["success", "rail"],
|
|
16
|
+
["danger", "rail"],
|
|
17
|
+
["success", "successSurface"],
|
|
18
|
+
["danger", "dangerSurface"]
|
|
19
|
+
]);
|
|
20
|
+
const ANSI_16_RGB = Object.freeze([
|
|
21
|
+
[0, 0, 0],
|
|
22
|
+
[205, 0, 0],
|
|
23
|
+
[0, 205, 0],
|
|
24
|
+
[205, 205, 0],
|
|
25
|
+
[0, 0, 238],
|
|
26
|
+
[205, 0, 205],
|
|
27
|
+
[0, 205, 205],
|
|
28
|
+
[229, 229, 229],
|
|
29
|
+
[127, 127, 127],
|
|
30
|
+
[255, 0, 0],
|
|
31
|
+
[0, 255, 0],
|
|
32
|
+
[255, 255, 0],
|
|
33
|
+
[92, 92, 255],
|
|
34
|
+
[255, 0, 255],
|
|
35
|
+
[0, 255, 255],
|
|
36
|
+
[255, 255, 255]
|
|
37
|
+
]);
|
|
38
|
+
const NAMED_COLOR_INDEX = Object.freeze({
|
|
39
|
+
black: 0,
|
|
40
|
+
red: 1,
|
|
41
|
+
green: 2,
|
|
42
|
+
yellow: 3,
|
|
43
|
+
blue: 4,
|
|
44
|
+
magenta: 5,
|
|
45
|
+
cyan: 6,
|
|
46
|
+
white: 7,
|
|
47
|
+
blackBright: 8,
|
|
48
|
+
gray: 8,
|
|
49
|
+
grey: 8,
|
|
50
|
+
redBright: 9,
|
|
51
|
+
greenBright: 10,
|
|
52
|
+
yellowBright: 11,
|
|
53
|
+
blueBright: 12,
|
|
54
|
+
magentaBright: 13,
|
|
55
|
+
cyanBright: 14,
|
|
56
|
+
whiteBright: 15
|
|
57
|
+
});
|
|
58
|
+
export function auditTuiThemeContrast(theme) {
|
|
59
|
+
const measurements = TUI_THEME_RENDERED_CONTRAST_PAIRS.map(([foreground, background]) => ({
|
|
60
|
+
foreground,
|
|
61
|
+
background,
|
|
62
|
+
ratio: tuiThemeContrastRatio(theme[foreground], theme[background])
|
|
63
|
+
}));
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
measurements: Object.freeze(measurements),
|
|
66
|
+
issues: Object.freeze(measurements.filter(({ ratio }) => ratio < TUI_THEME_MIN_CONTRAST_RATIO)),
|
|
67
|
+
minimumRatio: Math.min(...measurements.map(({ ratio }) => ratio))
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function tuiThemeContrastRatio(foreground, background) {
|
|
71
|
+
const foregroundRgb = tuiThemeColorRgb(foreground);
|
|
72
|
+
const backgroundRgb = tuiThemeColorRgb(background);
|
|
73
|
+
if (!foregroundRgb || !backgroundRgb) {
|
|
74
|
+
throw new Error(`Cannot measure TUI theme contrast: ${foreground} on ${background}`);
|
|
75
|
+
}
|
|
76
|
+
const foregroundLuminance = relativeLuminance(foregroundRgb);
|
|
77
|
+
const backgroundLuminance = relativeLuminance(backgroundRgb);
|
|
78
|
+
const lighter = Math.max(foregroundLuminance, backgroundLuminance);
|
|
79
|
+
const darker = Math.min(foregroundLuminance, backgroundLuminance);
|
|
80
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
81
|
+
}
|
|
82
|
+
export function tuiThemeColorRgb(color) {
|
|
83
|
+
const namedIndex = NAMED_COLOR_INDEX[color];
|
|
84
|
+
if (namedIndex !== undefined) {
|
|
85
|
+
return ANSI_16_RGB[namedIndex] ?? null;
|
|
86
|
+
}
|
|
87
|
+
const ansiMatch = color.match(/^ansi256\(\s*(\d+)\s*\)$/);
|
|
88
|
+
if (ansiMatch) {
|
|
89
|
+
return ansi256Rgb(Number(ansiMatch[1]));
|
|
90
|
+
}
|
|
91
|
+
const rgbMatch = color.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
|
|
92
|
+
if (rgbMatch) {
|
|
93
|
+
const rgb = [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];
|
|
94
|
+
return rgb.every(isByte) ? rgb : null;
|
|
95
|
+
}
|
|
96
|
+
const shortHexMatch = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i);
|
|
97
|
+
if (shortHexMatch) {
|
|
98
|
+
return [
|
|
99
|
+
Number.parseInt(`${shortHexMatch[1]}${shortHexMatch[1]}`, 16),
|
|
100
|
+
Number.parseInt(`${shortHexMatch[2]}${shortHexMatch[2]}`, 16),
|
|
101
|
+
Number.parseInt(`${shortHexMatch[3]}${shortHexMatch[3]}`, 16)
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
const longHexMatch = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
105
|
+
if (!longHexMatch) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return [
|
|
109
|
+
Number.parseInt(longHexMatch[1] ?? "0", 16),
|
|
110
|
+
Number.parseInt(longHexMatch[2] ?? "0", 16),
|
|
111
|
+
Number.parseInt(longHexMatch[3] ?? "0", 16)
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
function ansi256Rgb(index) {
|
|
115
|
+
if (!Number.isInteger(index) || index < 0 || index > 255) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (index < 16) {
|
|
119
|
+
return ANSI_16_RGB[index] ?? null;
|
|
120
|
+
}
|
|
121
|
+
if (index >= 232) {
|
|
122
|
+
const channel = 8 + ((index - 232) * 10);
|
|
123
|
+
return [channel, channel, channel];
|
|
124
|
+
}
|
|
125
|
+
const cubeIndex = index - 16;
|
|
126
|
+
const levels = [0, 95, 135, 175, 215, 255];
|
|
127
|
+
return [
|
|
128
|
+
levels[Math.floor(cubeIndex / 36)] ?? 0,
|
|
129
|
+
levels[Math.floor(cubeIndex / 6) % 6] ?? 0,
|
|
130
|
+
levels[cubeIndex % 6] ?? 0
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
function relativeLuminance([red, green, blue]) {
|
|
134
|
+
const [r, g, b] = [red, green, blue].map((channel) => {
|
|
135
|
+
const normalized = channel / 255;
|
|
136
|
+
return normalized <= 0.03928
|
|
137
|
+
? normalized / 12.92
|
|
138
|
+
: ((normalized + 0.055) / 1.055) ** 2.4;
|
|
139
|
+
});
|
|
140
|
+
return (0.2126 * (r ?? 0)) + (0.7152 * (g ?? 0)) + (0.0722 * (b ?? 0));
|
|
141
|
+
}
|
|
142
|
+
function isByte(value) {
|
|
143
|
+
return Number.isInteger(value) && value >= 0 && value <= 255;
|
|
144
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolveTuiTheme, TUI_THEME_NAMES } from "./theme.js";
|
|
2
|
+
const ANSI_RESET = "\u001b[0m";
|
|
3
|
+
const NAMED_ANSI_FOREGROUND_CODES = Object.freeze({
|
|
4
|
+
black: 30,
|
|
5
|
+
red: 31,
|
|
6
|
+
green: 32,
|
|
7
|
+
yellow: 33,
|
|
8
|
+
blue: 34,
|
|
9
|
+
magenta: 35,
|
|
10
|
+
cyan: 36,
|
|
11
|
+
white: 37,
|
|
12
|
+
gray: 90,
|
|
13
|
+
grey: 90,
|
|
14
|
+
blackBright: 90,
|
|
15
|
+
redBright: 91,
|
|
16
|
+
greenBright: 92,
|
|
17
|
+
yellowBright: 93,
|
|
18
|
+
blueBright: 94,
|
|
19
|
+
magentaBright: 95,
|
|
20
|
+
cyanBright: 96,
|
|
21
|
+
whiteBright: 97
|
|
22
|
+
});
|
|
23
|
+
export function formatTuiThemePreview(theme) {
|
|
24
|
+
return [
|
|
25
|
+
`preview: ${[
|
|
26
|
+
themeSwatch("chrome", theme.chrome, theme.text),
|
|
27
|
+
themeSwatch("surface", theme.surface, theme.text),
|
|
28
|
+
themeSwatch("rail", theme.rail, theme.text)
|
|
29
|
+
].join(" ")}`,
|
|
30
|
+
`semantic: ${[
|
|
31
|
+
themeSwatch("success", theme.successSurface, theme.success),
|
|
32
|
+
themeSwatch("danger", theme.dangerSurface, theme.danger),
|
|
33
|
+
themeSwatch("accent", theme.surface, theme.accent),
|
|
34
|
+
themeSwatch("warning", theme.surface, theme.warning),
|
|
35
|
+
themeSwatch("muted", theme.surface, theme.muted)
|
|
36
|
+
].join(" ")}`
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
export function formatTuiThemeCatalog(themeNames = TUI_THEME_NAMES) {
|
|
40
|
+
const lines = ["parallel-codex-tui themes"];
|
|
41
|
+
for (const themeName of themeNames) {
|
|
42
|
+
const theme = resolveTuiTheme({ theme: themeName });
|
|
43
|
+
lines.push(`${themeName}: ${formatTuiThemePalette(theme)}`);
|
|
44
|
+
lines.push(" palette:");
|
|
45
|
+
lines.push(...formatTuiThemePaletteGroups(theme).map((line) => ` ${line}`));
|
|
46
|
+
lines.push(...formatTuiThemePreview(theme).map((line) => ` ${line}`));
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
export function formatTuiThemePalette(theme) {
|
|
51
|
+
return [
|
|
52
|
+
`chrome=${theme.chrome}`,
|
|
53
|
+
`surface=${theme.surface}`,
|
|
54
|
+
`rail=${theme.rail}`,
|
|
55
|
+
`accent=${theme.accent}`
|
|
56
|
+
].join(", ");
|
|
57
|
+
}
|
|
58
|
+
export function formatTuiThemePaletteGroups(theme) {
|
|
59
|
+
return [
|
|
60
|
+
`chrome=${theme.chrome}, surface=${theme.surface}, rail=${theme.rail}`,
|
|
61
|
+
`text=${theme.text}, muted=${theme.muted}, accent=${theme.accent}`,
|
|
62
|
+
`successSurface=${theme.successSurface}, success=${theme.success}, warning=${theme.warning}`,
|
|
63
|
+
`dangerSurface=${theme.dangerSurface}, danger=${theme.danger}`
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
function themeSwatch(label, backgroundColor, foregroundColor) {
|
|
67
|
+
return `${ansiOpen(backgroundColor, "background")}${ansiOpen(foregroundColor, "foreground")} ${label} ${ANSI_RESET}`;
|
|
68
|
+
}
|
|
69
|
+
function ansiOpen(color, mode) {
|
|
70
|
+
const ansi256Match = color.match(/^ansi256\((\d+)\)$/);
|
|
71
|
+
if (ansi256Match) {
|
|
72
|
+
return `\u001b[${modePrefix(mode)};5;${ansi256Match[1]}m`;
|
|
73
|
+
}
|
|
74
|
+
const rgbMatch = color.match(/^rgb\((\d+),(\d+),(\d+)\)$/);
|
|
75
|
+
if (rgbMatch) {
|
|
76
|
+
return `\u001b[${modePrefix(mode)};2;${rgbMatch[1]};${rgbMatch[2]};${rgbMatch[3]}m`;
|
|
77
|
+
}
|
|
78
|
+
const hexRgb = hexToRgb(color);
|
|
79
|
+
if (hexRgb) {
|
|
80
|
+
return `\u001b[${modePrefix(mode)};2;${hexRgb.join(";")}m`;
|
|
81
|
+
}
|
|
82
|
+
const namedCode = NAMED_ANSI_FOREGROUND_CODES[color];
|
|
83
|
+
if (namedCode !== undefined) {
|
|
84
|
+
return `\u001b[${mode === "background" ? namedCode + 10 : namedCode}m`;
|
|
85
|
+
}
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
function modePrefix(mode) {
|
|
89
|
+
return mode === "background" ? 48 : 38;
|
|
90
|
+
}
|
|
91
|
+
function hexToRgb(color) {
|
|
92
|
+
const shortMatch = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i);
|
|
93
|
+
if (shortMatch) {
|
|
94
|
+
return [
|
|
95
|
+
Number.parseInt(`${shortMatch[1]}${shortMatch[1]}`, 16),
|
|
96
|
+
Number.parseInt(`${shortMatch[2]}${shortMatch[2]}`, 16),
|
|
97
|
+
Number.parseInt(`${shortMatch[3]}${shortMatch[3]}`, 16)
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
const longMatch = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
101
|
+
if (!longMatch) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return [
|
|
105
|
+
Number.parseInt(longMatch[1] ?? "0", 16),
|
|
106
|
+
Number.parseInt(longMatch[2] ?? "0", 16),
|
|
107
|
+
Number.parseInt(longMatch[3] ?? "0", 16)
|
|
108
|
+
];
|
|
109
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { foregroundColorNames } from "chalk";
|
|
2
|
+
const bg = (value) => value;
|
|
3
|
+
const fg = (value) => value;
|
|
4
|
+
export const TUI_THEME_NAMES = Object.freeze(["codex", "graphite", "paper", "aurora", "studio"]);
|
|
5
|
+
export const TUI_THEME_FIELDS = Object.freeze([
|
|
6
|
+
"chrome",
|
|
7
|
+
"surface",
|
|
8
|
+
"rail",
|
|
9
|
+
"successSurface",
|
|
10
|
+
"dangerSurface",
|
|
11
|
+
"text",
|
|
12
|
+
"muted",
|
|
13
|
+
"accent",
|
|
14
|
+
"warning",
|
|
15
|
+
"success",
|
|
16
|
+
"danger"
|
|
17
|
+
]);
|
|
18
|
+
export const TUI_THEME_PRESETS = Object.freeze({
|
|
19
|
+
codex: freezeTuiTheme({
|
|
20
|
+
chrome: bg("ansi256(233)"),
|
|
21
|
+
surface: bg("ansi256(234)"),
|
|
22
|
+
rail: bg("ansi256(236)"),
|
|
23
|
+
successSurface: bg("ansi256(22)"),
|
|
24
|
+
dangerSurface: bg("ansi256(52)"),
|
|
25
|
+
text: fg("ansi256(253)"),
|
|
26
|
+
muted: fg("ansi256(247)"),
|
|
27
|
+
accent: fg("ansi256(81)"),
|
|
28
|
+
warning: fg("ansi256(179)"),
|
|
29
|
+
success: fg("ansi256(114)"),
|
|
30
|
+
danger: fg("ansi256(210)")
|
|
31
|
+
}),
|
|
32
|
+
graphite: freezeTuiTheme({
|
|
33
|
+
chrome: bg("ansi256(236)"),
|
|
34
|
+
surface: bg("ansi256(233)"),
|
|
35
|
+
rail: bg("ansi256(238)"),
|
|
36
|
+
successSurface: bg("ansi256(22)"),
|
|
37
|
+
dangerSurface: bg("ansi256(52)"),
|
|
38
|
+
text: fg("ansi256(255)"),
|
|
39
|
+
muted: fg("ansi256(249)"),
|
|
40
|
+
accent: fg("ansi256(117)"),
|
|
41
|
+
warning: fg("ansi256(214)"),
|
|
42
|
+
success: fg("ansi256(150)"),
|
|
43
|
+
danger: fg("ansi256(217)")
|
|
44
|
+
}),
|
|
45
|
+
paper: freezeTuiTheme({
|
|
46
|
+
chrome: bg("ansi256(254)"),
|
|
47
|
+
surface: bg("ansi256(231)"),
|
|
48
|
+
rail: bg("ansi256(255)"),
|
|
49
|
+
successSurface: bg("ansi256(194)"),
|
|
50
|
+
dangerSurface: bg("ansi256(224)"),
|
|
51
|
+
text: fg("ansi256(235)"),
|
|
52
|
+
muted: fg("ansi256(240)"),
|
|
53
|
+
accent: fg("ansi256(25)"),
|
|
54
|
+
warning: fg("ansi256(94)"),
|
|
55
|
+
success: fg("ansi256(22)"),
|
|
56
|
+
danger: fg("ansi256(124)")
|
|
57
|
+
}),
|
|
58
|
+
aurora: freezeTuiTheme({
|
|
59
|
+
chrome: bg("ansi256(19)"),
|
|
60
|
+
surface: bg("ansi256(233)"),
|
|
61
|
+
rail: bg("ansi256(53)"),
|
|
62
|
+
successSurface: bg("ansi256(22)"),
|
|
63
|
+
dangerSurface: bg("ansi256(52)"),
|
|
64
|
+
text: fg("ansi256(255)"),
|
|
65
|
+
muted: fg("ansi256(109)"),
|
|
66
|
+
accent: fg("ansi256(159)"),
|
|
67
|
+
warning: fg("ansi256(222)"),
|
|
68
|
+
success: fg("ansi256(121)"),
|
|
69
|
+
danger: fg("ansi256(210)")
|
|
70
|
+
}),
|
|
71
|
+
studio: freezeTuiTheme({
|
|
72
|
+
chrome: bg("ansi256(236)"),
|
|
73
|
+
surface: bg("ansi256(235)"),
|
|
74
|
+
rail: bg("ansi256(238)"),
|
|
75
|
+
successSurface: bg("ansi256(22)"),
|
|
76
|
+
dangerSurface: bg("ansi256(52)"),
|
|
77
|
+
text: fg("ansi256(254)"),
|
|
78
|
+
muted: fg("ansi256(249)"),
|
|
79
|
+
accent: fg("ansi256(147)"),
|
|
80
|
+
warning: fg("ansi256(215)"),
|
|
81
|
+
success: fg("ansi256(151)"),
|
|
82
|
+
danger: fg("ansi256(217)")
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
const DEFAULT_TUI_THEME_NAME = "codex";
|
|
86
|
+
export let TUI_THEME = TUI_THEME_PRESETS[DEFAULT_TUI_THEME_NAME];
|
|
87
|
+
export function resolveTuiTheme(options = {}) {
|
|
88
|
+
const theme = normalizeTuiThemeName(options.theme);
|
|
89
|
+
const name = theme ?? DEFAULT_TUI_THEME_NAME;
|
|
90
|
+
return freezeTuiTheme({
|
|
91
|
+
...TUI_THEME_PRESETS[name],
|
|
92
|
+
...normalizeTuiThemeOverrides(options.colors)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export function configureTuiTheme(options = {}) {
|
|
96
|
+
TUI_THEME = resolveTuiTheme(options);
|
|
97
|
+
return TUI_THEME;
|
|
98
|
+
}
|
|
99
|
+
export function resetTuiTheme() {
|
|
100
|
+
TUI_THEME = TUI_THEME_PRESETS[DEFAULT_TUI_THEME_NAME];
|
|
101
|
+
return TUI_THEME;
|
|
102
|
+
}
|
|
103
|
+
export function isTuiThemeColorValue(value) {
|
|
104
|
+
return normalizeTuiThemeColorValue(value) !== null;
|
|
105
|
+
}
|
|
106
|
+
export function normalizeTuiThemeColorValue(value) {
|
|
107
|
+
const color = value?.trim();
|
|
108
|
+
if (!color) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (foregroundColorNames.includes(color)) {
|
|
112
|
+
return color;
|
|
113
|
+
}
|
|
114
|
+
if (/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(color)) {
|
|
115
|
+
return color.toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
const ansiMatch = color.match(/^ansi256\(\s*(\d+)\s*\)$/);
|
|
118
|
+
if (ansiMatch) {
|
|
119
|
+
const value = normalizeByteColorValue(ansiMatch[1] ?? "");
|
|
120
|
+
return value !== null ? `ansi256(${value})` : null;
|
|
121
|
+
}
|
|
122
|
+
const rgbMatch = color.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
|
|
123
|
+
if (rgbMatch) {
|
|
124
|
+
const parts = [rgbMatch[1] ?? "", rgbMatch[2] ?? "", rgbMatch[3] ?? ""].map(normalizeByteColorValue);
|
|
125
|
+
return parts.every((value) => value !== null) ? `rgb(${parts.join(",")})` : null;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
export function isTuiThemeName(value) {
|
|
130
|
+
return TUI_THEME_NAMES.includes(value);
|
|
131
|
+
}
|
|
132
|
+
export function normalizeTuiThemeName(value) {
|
|
133
|
+
const name = value?.trim();
|
|
134
|
+
return isTuiThemeName(name) ? name : null;
|
|
135
|
+
}
|
|
136
|
+
function normalizeByteColorValue(value) {
|
|
137
|
+
if (!/^\d+$/.test(value)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const number = Number(value);
|
|
141
|
+
return Number.isInteger(number) && number >= 0 && number <= 255 ? String(number) : null;
|
|
142
|
+
}
|
|
143
|
+
function normalizeTuiThemeOverrides(colors) {
|
|
144
|
+
const normalized = {};
|
|
145
|
+
if (!colors) {
|
|
146
|
+
return normalized;
|
|
147
|
+
}
|
|
148
|
+
for (const field of TUI_THEME_FIELDS) {
|
|
149
|
+
const value = normalizeTuiThemeColorValue(colors[field]);
|
|
150
|
+
if (value) {
|
|
151
|
+
normalized[field] = value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return normalized;
|
|
155
|
+
}
|
|
156
|
+
function freezeTuiTheme(theme) {
|
|
157
|
+
return Object.freeze(theme);
|
|
158
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.1.
|
|
1
|
+
export const version = "0.1.4";
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
export async function diagnoseAgentCapabilities(config, env, options) {
|
|
4
|
+
const targets = capabilityTargets(config, options);
|
|
5
|
+
const runner = options.runner ?? runCapabilityCommand;
|
|
6
|
+
const timeoutMs = options.timeoutMs ?? 3000;
|
|
7
|
+
const reports = await Promise.all(targets.map(async (target) => {
|
|
8
|
+
if (options.availableCommands && !options.availableCommands.has(target.command)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return diagnoseCapabilityTarget(target, config, env, runner, timeoutMs);
|
|
12
|
+
}));
|
|
13
|
+
const lines = reports.flatMap((report) => report?.lines ?? []);
|
|
14
|
+
const ok = reports.every((report) => report?.ok !== false);
|
|
15
|
+
if (targets.some((target) => target.surfaces.has("native"))) {
|
|
16
|
+
lines.push("native workspace trust: interactive (confirm only workspaces you trust when prompted)");
|
|
17
|
+
}
|
|
18
|
+
return { ok, lines };
|
|
19
|
+
}
|
|
20
|
+
function capabilityTargets(config, options) {
|
|
21
|
+
const targets = new Map();
|
|
22
|
+
const addTarget = (engine, command, capabilities, surface) => {
|
|
23
|
+
const key = `${engine}\0${command}\0${capabilities.profile}`;
|
|
24
|
+
const current = targets.get(key) ?? { engine, command, capabilities, surfaces: new Set() };
|
|
25
|
+
current.surfaces.add(surface);
|
|
26
|
+
targets.set(key, current);
|
|
27
|
+
};
|
|
28
|
+
if (options.includeRouter) {
|
|
29
|
+
addTarget("codex", config.router.codex.command, {
|
|
30
|
+
profile: "codex",
|
|
31
|
+
writableDirArgs: [],
|
|
32
|
+
freshSessionArgs: []
|
|
33
|
+
}, "automated");
|
|
34
|
+
}
|
|
35
|
+
for (const engine of [...new Set(options.workerEngines)]) {
|
|
36
|
+
const worker = config.workers[engine];
|
|
37
|
+
addTarget(engine, worker.command, worker.capabilities, "automated");
|
|
38
|
+
if (worker.nativeSession.enabled) {
|
|
39
|
+
addTarget(engine, worker.command, worker.capabilities, "resume");
|
|
40
|
+
addTarget(engine, worker.interactive.command, worker.capabilities, "native");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...targets.values()];
|
|
44
|
+
}
|
|
45
|
+
async function diagnoseCapabilityTarget(target, config, env, runner, timeoutMs) {
|
|
46
|
+
const configuredIssues = configuredCapabilityIssues(target, config);
|
|
47
|
+
if (configuredIssues.length > 0) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
lines: [`${capabilityTargetLabel(target)}: incompatible (${configuredIssues.join("; ")})`]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (target.capabilities.profile === "generic") {
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
lines: [`${capabilityTargetLabel(target)}: declared (${genericCapabilitySummary(target)})`]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const specs = capabilityProbeSpecs(target);
|
|
60
|
+
const results = await Promise.all(specs.map(async (spec) => ({
|
|
61
|
+
spec,
|
|
62
|
+
result: await runner(target.command, spec.args, env, timeoutMs)
|
|
63
|
+
})));
|
|
64
|
+
const incompatible = [];
|
|
65
|
+
const unverified = [];
|
|
66
|
+
for (const { spec, result } of results) {
|
|
67
|
+
const output = stripTerminalControl(`${result.stdout}\n${result.stderr}`);
|
|
68
|
+
if (result.timedOut) {
|
|
69
|
+
unverified.push(`${spec.label} help timed out`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (result.exitCode !== 0 || !spec.usagePattern.test(output)) {
|
|
73
|
+
unverified.push(`${spec.label} help not recognized`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const missing = spec.requiredOptions.filter((option) => !output.includes(option));
|
|
77
|
+
if (missing.length > 0) {
|
|
78
|
+
incompatible.push(`${spec.label} missing ${missing.join(", ")}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (target.capabilities.profile === "codex" && target.surfaces.has("native")) {
|
|
82
|
+
const sandbox = configuredCodexSandbox(config.workers.codex.interactive.args);
|
|
83
|
+
if (sandbox === "read-only") {
|
|
84
|
+
incompatible.push("native resume uses read-only but feature attach requires writable --add-dir roots");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const label = capabilityTargetLabel(target);
|
|
88
|
+
if (incompatible.length > 0) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
lines: [`${label}: incompatible (${incompatible.join("; ")})`]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (unverified.length > 0) {
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
lines: [`${label}: warning (${unverified.join("; ")}; compatibility unverified)`]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
lines: [`${label}: ok (${specs.map((spec) => spec.label).join(", ")})`]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function capabilityProbeSpecs(target) {
|
|
106
|
+
if (target.capabilities.profile === "claude") {
|
|
107
|
+
return [{
|
|
108
|
+
args: ["--help"],
|
|
109
|
+
label: "print/resume/permissions/add-dir",
|
|
110
|
+
requiredOptions: ["--print", "--resume", "--permission-mode", "--add-dir"],
|
|
111
|
+
usagePattern: /Usage:\s+claude\b/i
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
const specs = [];
|
|
115
|
+
if (target.surfaces.has("automated")) {
|
|
116
|
+
specs.push({
|
|
117
|
+
args: ["exec", "--help"],
|
|
118
|
+
label: "exec sandbox/add-dir",
|
|
119
|
+
requiredOptions: ["--sandbox", "--add-dir", "--skip-git-repo-check"],
|
|
120
|
+
usagePattern: /Usage:\s+codex exec\b/i
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (target.surfaces.has("resume")) {
|
|
124
|
+
specs.push({
|
|
125
|
+
args: ["exec", "resume", "--help"],
|
|
126
|
+
label: "exec resume",
|
|
127
|
+
requiredOptions: ["--skip-git-repo-check"],
|
|
128
|
+
usagePattern: /Usage:\s+codex exec resume\b/i
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (target.surfaces.has("native")) {
|
|
132
|
+
specs.push({
|
|
133
|
+
args: ["resume", "--help"],
|
|
134
|
+
label: "native resume sandbox/add-dir",
|
|
135
|
+
requiredOptions: ["--sandbox", "--add-dir"],
|
|
136
|
+
usagePattern: /Usage:\s+codex resume\b/i
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return specs;
|
|
140
|
+
}
|
|
141
|
+
function configuredCapabilityIssues(target, config) {
|
|
142
|
+
const worker = config.workers[target.engine];
|
|
143
|
+
const issues = [];
|
|
144
|
+
if (target.surfaces.has("resume")
|
|
145
|
+
&& worker.nativeSession.enabled
|
|
146
|
+
&& !worker.nativeSession.resumeArgs.some((arg) => arg.includes("{sessionId}"))) {
|
|
147
|
+
issues.push("nativeSession.resumeArgs missing {sessionId}");
|
|
148
|
+
}
|
|
149
|
+
if (target.surfaces.has("native")
|
|
150
|
+
&& worker.nativeSession.enabled
|
|
151
|
+
&& !worker.interactive.args.some((arg) => arg.includes("{sessionId}"))) {
|
|
152
|
+
issues.push("interactive.args missing {sessionId}");
|
|
153
|
+
}
|
|
154
|
+
return issues;
|
|
155
|
+
}
|
|
156
|
+
function genericCapabilitySummary(target) {
|
|
157
|
+
const writableDirs = target.capabilities.writableDirArgs.length > 0
|
|
158
|
+
? "writable dirs via template"
|
|
159
|
+
: "process-managed writes";
|
|
160
|
+
const freshSession = target.capabilities.freshSessionArgs.length > 0
|
|
161
|
+
? "client-assigned fresh session"
|
|
162
|
+
: "output-detected fresh session";
|
|
163
|
+
const nativeResume = target.surfaces.has("native") || target.surfaces.has("resume")
|
|
164
|
+
? "native resume configured"
|
|
165
|
+
: "fresh runs";
|
|
166
|
+
return `generic CLI, ${writableDirs}, ${freshSession}, ${nativeResume}`;
|
|
167
|
+
}
|
|
168
|
+
function configuredCodexSandbox(args) {
|
|
169
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
170
|
+
const arg = args[index] ?? "";
|
|
171
|
+
if (arg === "--sandbox" || arg === "-s") {
|
|
172
|
+
return args[index + 1]?.trim().toLowerCase() || null;
|
|
173
|
+
}
|
|
174
|
+
const match = arg.match(/^(?:--sandbox|-s)=(.+)$/);
|
|
175
|
+
if (match) {
|
|
176
|
+
return match[1]?.trim().toLowerCase() || null;
|
|
177
|
+
}
|
|
178
|
+
if (arg === "--dangerously-bypass-approvals-and-sandbox") {
|
|
179
|
+
return "danger-full-access";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
function capabilityTargetLabel(target) {
|
|
185
|
+
const executable = basename(target.command);
|
|
186
|
+
return executable === target.engine
|
|
187
|
+
? `${target.engine} capabilities`
|
|
188
|
+
: `${target.engine} capabilities (${executable})`;
|
|
189
|
+
}
|
|
190
|
+
function stripTerminalControl(value) {
|
|
191
|
+
return value
|
|
192
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
193
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
194
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
195
|
+
}
|
|
196
|
+
function runCapabilityCommand(command, args, env, timeoutMs) {
|
|
197
|
+
return new Promise((resolve) => {
|
|
198
|
+
execFile(command, args, {
|
|
199
|
+
env,
|
|
200
|
+
timeout: timeoutMs,
|
|
201
|
+
maxBuffer: 512 * 1024
|
|
202
|
+
}, (error, stdout, stderr) => {
|
|
203
|
+
const processError = error;
|
|
204
|
+
resolve({
|
|
205
|
+
exitCode: typeof processError?.code === "number" ? processError.code : processError ? null : 0,
|
|
206
|
+
stdout,
|
|
207
|
+
stderr,
|
|
208
|
+
timedOut: Boolean(processError?.killed)
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|