parallel-codex-tui 0.2.10 → 0.3.0
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/README.md +21 -6
- package/dist/bootstrap.js +8 -1
- package/dist/cli.js +10 -1
- package/dist/core/role-configuration.js +238 -0
- package/dist/orchestrator/orchestrator.js +135 -11
- package/dist/tui/App.js +397 -4
- package/dist/tui/AppShell.js +7 -1
- package/dist/tui/InputBar.js +27 -3
- package/dist/tui/RoleConfigurationView.js +96 -0
- package/dist/tui/StatusDetailView.js +9 -6
- package/dist/tui/keyboard.js +11 -0
- package/dist/tui/role-configuration-state.js +74 -0
- package/dist/version.js +1 -1
- package/dist/workers/native-attach.js +5 -1
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { CONFIGURABLE_ROLES } from "../core/role-configuration.js";
|
|
4
|
+
import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
|
|
5
|
+
import { roleConfigurationScopeHasOverride, roleConfigurationScopeLabel } from "./role-configuration-state.js";
|
|
6
|
+
import { TUI_THEME } from "./theme.js";
|
|
7
|
+
export function RoleConfigurationView({ height = 20, terminalWidth = process.stdout.columns || 120, ...input }) {
|
|
8
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
9
|
+
const width = Math.max(1, terminalWidth - 2);
|
|
10
|
+
const lines = roleConfigurationDisplayLines(input, width).slice(0, viewportHeight);
|
|
11
|
+
const blanks = Math.max(0, viewportHeight - lines.length);
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(RoleConfigurationRow, { line: line, width: width }, `${line.text}-${index}`))), Array.from({ length: blanks }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `role-config-fill-${index}`)))] }));
|
|
13
|
+
}
|
|
14
|
+
export function roleConfigurationDisplayLines(input, width) {
|
|
15
|
+
const safeWidth = Math.max(1, Math.trunc(width));
|
|
16
|
+
const lines = [
|
|
17
|
+
{ text: "Role & model control", tone: "heading" },
|
|
18
|
+
{
|
|
19
|
+
text: `scope · ${roleConfigurationScopeLabel(input.scope)}`,
|
|
20
|
+
tone: input.scope === "task" && !input.hasActiveTask ? "warning" : "accent"
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
if (input.loading || !input.snapshot || !input.draft) {
|
|
24
|
+
lines.push({ text: "loading role configuration...", tone: "muted" });
|
|
25
|
+
return lines.map((line) => fittedRoleLine(line, safeWidth));
|
|
26
|
+
}
|
|
27
|
+
if (input.scope === "task" && !input.hasActiveTask) {
|
|
28
|
+
lines.push({ text: "No active Task · choose next request or future requests.", tone: "warning" });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
lines.push({
|
|
32
|
+
text: roleConfigurationScopeHasOverride(input.snapshot, input.scope)
|
|
33
|
+
? "saved override · Enter updates it · X resets inheritance"
|
|
34
|
+
: "inheriting defaults · Enter saves this matrix",
|
|
35
|
+
tone: roleConfigurationScopeHasOverride(input.snapshot, input.scope) ? "success" : "muted"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
lines.push({ text: "", tone: "text" });
|
|
39
|
+
for (const [index, role] of CONFIGURABLE_ROLES.entries()) {
|
|
40
|
+
const target = input.draft[role];
|
|
41
|
+
const provider = input.snapshot.providers.find((candidate) => candidate.id === target.engine);
|
|
42
|
+
const remote = provider?.modelProvider.trim();
|
|
43
|
+
const model = target.model.trim() || provider?.model.trim() || "default";
|
|
44
|
+
const prefix = index === input.selectedRoleIndex ? ">" : " ";
|
|
45
|
+
const roleLabel = `${role[0]?.toUpperCase() ?? ""}${role.slice(1)}`.padEnd(7, " ");
|
|
46
|
+
lines.push({
|
|
47
|
+
text: `${prefix} ${roleLabel} ${target.engine} · ${[remote, model].filter(Boolean).join("/")}`,
|
|
48
|
+
tone: index === input.selectedRoleIndex ? "accent" : "text",
|
|
49
|
+
selected: index === input.selectedRoleIndex
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
lines.push({ text: "", tone: "text" });
|
|
53
|
+
const active = input.snapshot.activeTurn;
|
|
54
|
+
if (active) {
|
|
55
|
+
lines.push({
|
|
56
|
+
text: `active turn · ${CONFIGURABLE_ROLES.map((role) => `${role}/${active[role].engine}/${active[role].model || "default"}`).join(" · ")}`,
|
|
57
|
+
tone: "muted"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (input.saving) {
|
|
61
|
+
lines.push({ text: "saving role configuration...", tone: "warning" });
|
|
62
|
+
}
|
|
63
|
+
else if (input.error?.trim()) {
|
|
64
|
+
lines.push({ text: `error · ${input.error.trim()}`, tone: "danger" });
|
|
65
|
+
}
|
|
66
|
+
else if (input.notice?.trim()) {
|
|
67
|
+
lines.push({ text: input.notice.trim(), tone: "success" });
|
|
68
|
+
}
|
|
69
|
+
return lines.map((line) => fittedRoleLine(line, safeWidth));
|
|
70
|
+
}
|
|
71
|
+
function RoleConfigurationRow({ line, width }) {
|
|
72
|
+
const text = compactEndByDisplayWidth(line.text, width);
|
|
73
|
+
const trailing = Math.max(0, width - displayWidth(text));
|
|
74
|
+
const theme = roleConfigurationLineTheme(line.tone, line.selected);
|
|
75
|
+
return (_jsxs(Text, { children: [_jsx(Text, { ...theme, children: text }), trailing > 0 ? _jsx(Text, { backgroundColor: theme.backgroundColor, children: " ".repeat(trailing) }) : null] }));
|
|
76
|
+
}
|
|
77
|
+
export function roleConfigurationLineTheme(tone, selected = false) {
|
|
78
|
+
return {
|
|
79
|
+
backgroundColor: selected ? TUI_THEME.rail : TUI_THEME.surface,
|
|
80
|
+
color: tone === "heading" || tone === "accent"
|
|
81
|
+
? TUI_THEME.accent
|
|
82
|
+
: tone === "success"
|
|
83
|
+
? TUI_THEME.success
|
|
84
|
+
: tone === "warning"
|
|
85
|
+
? TUI_THEME.warning
|
|
86
|
+
: tone === "danger"
|
|
87
|
+
? TUI_THEME.danger
|
|
88
|
+
: tone === "muted"
|
|
89
|
+
? TUI_THEME.muted
|
|
90
|
+
: TUI_THEME.text,
|
|
91
|
+
...(tone === "heading" || tone === "danger" || selected ? { bold: true } : {})
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function fittedRoleLine(line, width) {
|
|
95
|
+
return { ...line, text: compactEndByDisplayWidth(line.text, width) };
|
|
96
|
+
}
|
|
@@ -25,7 +25,7 @@ export function statusDetailDisplayLines(input, width, height) {
|
|
|
25
25
|
tone: "muted"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
text: fitStatusDetailText(rolePairingDetail(input.pairing), safeWidth),
|
|
28
|
+
text: fitStatusDetailText(rolePairingDetail(input.pairing, input.roleSelection), safeWidth),
|
|
29
29
|
tone: "text"
|
|
30
30
|
}
|
|
31
31
|
];
|
|
@@ -59,15 +59,18 @@ export function statusDetailDisplayLines(input, width, height) {
|
|
|
59
59
|
}
|
|
60
60
|
return lines.slice(0, maxLines);
|
|
61
61
|
}
|
|
62
|
-
function rolePairingDetail(pairing) {
|
|
62
|
+
function rolePairingDetail(pairing, selection) {
|
|
63
63
|
return [
|
|
64
64
|
"active roles",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
rolePairingEntry("main", selection?.main.engine ?? pairing.main, selection?.main.model),
|
|
66
|
+
rolePairingEntry("judge", selection?.judge.engine ?? pairing.judge, selection?.judge.model),
|
|
67
|
+
rolePairingEntry("actor", selection?.actor.engine ?? pairing.actor, selection?.actor.model),
|
|
68
|
+
rolePairingEntry("critic", selection?.critic.engine ?? pairing.critic, selection?.critic.model)
|
|
69
69
|
].join(" · ");
|
|
70
70
|
}
|
|
71
|
+
function rolePairingEntry(role, engine, model) {
|
|
72
|
+
return [role, engine, model?.trim()].filter(Boolean).join("/");
|
|
73
|
+
}
|
|
71
74
|
function taskDetail(input) {
|
|
72
75
|
if (!input.taskId) {
|
|
73
76
|
return "task · none";
|
package/dist/tui/keyboard.js
CHANGED
|
@@ -38,6 +38,9 @@ export function isCopyShortcut(input, key) {
|
|
|
38
38
|
export function isDiagnosticsShortcut(input, key) {
|
|
39
39
|
return (key.ctrl === true && input.toLowerCase() === "x") || input === "\u0018";
|
|
40
40
|
}
|
|
41
|
+
export function isRoleConfigurationShortcut(input, key) {
|
|
42
|
+
return (key.ctrl === true && input.toLowerCase() === "e") || input === "\u0005";
|
|
43
|
+
}
|
|
41
44
|
export function workerLogJumpKind(input) {
|
|
42
45
|
if (input === "e" || input === "E") {
|
|
43
46
|
return "error";
|
|
@@ -80,6 +83,14 @@ export function rawPlainArrowDelta(input) {
|
|
|
80
83
|
}
|
|
81
84
|
return delta;
|
|
82
85
|
}
|
|
86
|
+
export function rawHorizontalArrowDelta(input) {
|
|
87
|
+
let delta = 0;
|
|
88
|
+
for (const match of input.matchAll(/\x1b(?:O([CD])|\[[0-9;?]*([CD]))/g)) {
|
|
89
|
+
const direction = match[1] ?? match[2];
|
|
90
|
+
delta += direction === "C" ? 1 : -1;
|
|
91
|
+
}
|
|
92
|
+
return delta;
|
|
93
|
+
}
|
|
83
94
|
export function rawChatScrollArrowDelta(input) {
|
|
84
95
|
const delta = rawPlainArrowDelta(input);
|
|
85
96
|
// Alternate-scroll wheels arrive as arrow bursts; one arrow remains a draft-history keypress.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CONFIGURABLE_ROLES, cloneRoleSelection } from "../core/role-configuration.js";
|
|
2
|
+
const ROLE_CONFIGURATION_SCOPES = ["next", "task", "future"];
|
|
3
|
+
export function roleConfigurationSelectionForScope(snapshot, scope) {
|
|
4
|
+
if (scope === "future") {
|
|
5
|
+
return cloneRoleSelection(snapshot.future);
|
|
6
|
+
}
|
|
7
|
+
if (scope === "task") {
|
|
8
|
+
return cloneRoleSelection(snapshot.task ?? snapshot.future);
|
|
9
|
+
}
|
|
10
|
+
return cloneRoleSelection(snapshot.next ?? snapshot.future);
|
|
11
|
+
}
|
|
12
|
+
export function roleConfigurationScopeHasOverride(snapshot, scope) {
|
|
13
|
+
if (!snapshot) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (scope === "future") {
|
|
17
|
+
return !sameRoleSelection(snapshot.future, snapshot.baseline);
|
|
18
|
+
}
|
|
19
|
+
return scope === "next" ? snapshot.next !== null : snapshot.task !== null;
|
|
20
|
+
}
|
|
21
|
+
export function nextRoleConfigurationScope(scope, delta, hasTask) {
|
|
22
|
+
const available = ROLE_CONFIGURATION_SCOPES.filter((candidate) => candidate !== "task" || hasTask);
|
|
23
|
+
const index = Math.max(0, available.indexOf(scope));
|
|
24
|
+
return available[((index + delta) % available.length + available.length) % available.length] ?? "next";
|
|
25
|
+
}
|
|
26
|
+
export function moveRoleConfigurationSelection(index, delta) {
|
|
27
|
+
const length = CONFIGURABLE_ROLES.length;
|
|
28
|
+
return ((index + delta) % length + length) % length;
|
|
29
|
+
}
|
|
30
|
+
export function selectedConfigurableRole(index) {
|
|
31
|
+
return CONFIGURABLE_ROLES[Math.max(0, Math.min(CONFIGURABLE_ROLES.length - 1, index))] ?? "main";
|
|
32
|
+
}
|
|
33
|
+
export function cycleRoleProvider(roles, role, providers, delta) {
|
|
34
|
+
const currentEngine = roles[role].engine;
|
|
35
|
+
const selectableProviders = providers.filter((provider) => (provider.assignable || provider.id === currentEngine));
|
|
36
|
+
if (selectableProviders.length === 0) {
|
|
37
|
+
return cloneRoleSelection(roles);
|
|
38
|
+
}
|
|
39
|
+
const currentIndex = selectableProviders.findIndex((provider) => provider.id === currentEngine);
|
|
40
|
+
const start = currentIndex >= 0 ? currentIndex : 0;
|
|
41
|
+
const provider = selectableProviders[((start + delta) % selectableProviders.length + selectableProviders.length) % selectableProviders.length] ?? selectableProviders[0];
|
|
42
|
+
if (!provider) {
|
|
43
|
+
return cloneRoleSelection(roles);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
...cloneRoleSelection(roles),
|
|
47
|
+
[role]: {
|
|
48
|
+
engine: provider.id,
|
|
49
|
+
model: provider.model
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function updateRoleModel(roles, role, model) {
|
|
54
|
+
return {
|
|
55
|
+
...cloneRoleSelection(roles),
|
|
56
|
+
[role]: {
|
|
57
|
+
...roles[role],
|
|
58
|
+
model
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function sameRoleSelection(left, right) {
|
|
63
|
+
return CONFIGURABLE_ROLES.every((role) => (left[role].engine === right[role].engine
|
|
64
|
+
&& left[role].model === right[role].model));
|
|
65
|
+
}
|
|
66
|
+
export function roleConfigurationScopeLabel(scope) {
|
|
67
|
+
if (scope === "next") {
|
|
68
|
+
return "next request · one shot";
|
|
69
|
+
}
|
|
70
|
+
if (scope === "task") {
|
|
71
|
+
return "current task · retries and follow-ups";
|
|
72
|
+
}
|
|
73
|
+
return "future requests · persisted default";
|
|
74
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.
|
|
1
|
+
export const version = "0.3.0";
|
|
@@ -18,7 +18,11 @@ export async function buildNativeForkLaunch(input) {
|
|
|
18
18
|
async function buildNativeSessionLaunch(input, mode) {
|
|
19
19
|
const workerConfig = workerProvider(input.config, input.worker.engine).config;
|
|
20
20
|
const nativeSession = await readWorkerNativeSession(input.worker, workerConfig.capabilities.profile);
|
|
21
|
-
const modelConfig =
|
|
21
|
+
const modelConfig = {
|
|
22
|
+
...workerConfig.model,
|
|
23
|
+
name: input.worker.runtimeStatus?.model_name?.trim() || workerConfig.model.name,
|
|
24
|
+
provider: input.worker.runtimeStatus?.model_provider?.trim() || workerConfig.model.provider
|
|
25
|
+
};
|
|
22
26
|
const env = modelEnvironment(modelConfig);
|
|
23
27
|
const interactiveArgs = mode === "fork"
|
|
24
28
|
? workerConfig.interactive.forkArgs
|