parallel-codex-tui 0.2.9 → 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 +22 -7
- package/dist/bootstrap.js +8 -1
- package/dist/cli.js +18 -3
- package/dist/core/role-configuration.js +238 -0
- package/dist/core/session-manager.js +287 -4
- package/dist/domain/schemas.js +3 -1
- package/dist/orchestrator/orchestrator.js +135 -11
- package/dist/tui/App.js +615 -16
- package/dist/tui/AppShell.js +7 -1
- package/dist/tui/InputBar.js +35 -5
- package/dist/tui/MainConversationsView.js +28 -8
- 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
package/dist/tui/AppShell.js
CHANGED
|
@@ -236,6 +236,9 @@ function viewLabel(view) {
|
|
|
236
236
|
return shortViewLabel(view);
|
|
237
237
|
}
|
|
238
238
|
function shortcutHint(view) {
|
|
239
|
+
if (view === "roles") {
|
|
240
|
+
return "^E back";
|
|
241
|
+
}
|
|
239
242
|
if (view === "status") {
|
|
240
243
|
return "^S back";
|
|
241
244
|
}
|
|
@@ -245,6 +248,9 @@ function shortcutHint(view) {
|
|
|
245
248
|
return "^S status · ^C exit";
|
|
246
249
|
}
|
|
247
250
|
function shortViewLabel(view) {
|
|
251
|
+
if (view === "roles") {
|
|
252
|
+
return "roles";
|
|
253
|
+
}
|
|
248
254
|
if (view === "status") {
|
|
249
255
|
return "status";
|
|
250
256
|
}
|
|
@@ -272,7 +278,7 @@ function shortViewLabel(view) {
|
|
|
272
278
|
return "chat";
|
|
273
279
|
}
|
|
274
280
|
function shortShortcutHint(view) {
|
|
275
|
-
return view === "native" ? "^]" : view === "status" ? "^S" : "^C";
|
|
281
|
+
return view === "native" ? "^]" : view === "status" ? "^S" : view === "roles" ? "^E" : "^C";
|
|
276
282
|
}
|
|
277
283
|
function compactHeaderProject(cwd, maxLength) {
|
|
278
284
|
const project = basename(cwd) || cwd;
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
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, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, mainConversationSessions = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
5
|
+
export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, mainConversationSessions = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, roleScope = "next", roleEditingModel = null, roleCanApply = true, roleSaving = false, roleHasOverride = false, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
6
6
|
const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
|
|
7
7
|
const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
|
|
8
8
|
if (clipboardNotice) {
|
|
@@ -19,6 +19,17 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
19
19
|
const textWidth = displayWidth(`${prefix}${display.before}|${display.after}${suffix}`);
|
|
20
20
|
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] }));
|
|
21
21
|
}
|
|
22
|
+
if (mode === "roles") {
|
|
23
|
+
if (roleEditingModel) {
|
|
24
|
+
const prefix = `${roleEditingModel.role} model > `;
|
|
25
|
+
const valueWidth = Math.max(1, terminalWidth - displayWidth(prefix) - 3);
|
|
26
|
+
const display = chatInputDisplayParts(roleEditingModel.value, roleEditingModel.cursor, valueWidth);
|
|
27
|
+
const textWidth = displayWidth(`${prefix}${display.before}|${display.after}`);
|
|
28
|
+
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 })] }));
|
|
29
|
+
}
|
|
30
|
+
const hints = roleConfigurationInputHints(terminalWidth, roleScope, roleCanApply, roleSaving, roleHasOverride);
|
|
31
|
+
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: roleSaving ? TUI_THEME.warning : TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
32
|
+
}
|
|
22
33
|
if (mode === "status") {
|
|
23
34
|
const hints = statusDetailInputHints(terminalWidth);
|
|
24
35
|
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] }));
|
|
@@ -40,7 +51,7 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
40
51
|
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] }));
|
|
41
52
|
}
|
|
42
53
|
if (mainConversationSessions) {
|
|
43
|
-
const hints = mainConversationSessionsInputHints(terminalWidth);
|
|
54
|
+
const hints = mainConversationSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
|
|
44
55
|
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
56
|
}
|
|
46
57
|
const hints = taskSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
|
|
@@ -422,14 +433,27 @@ function workerInputHints(width) {
|
|
|
422
433
|
}
|
|
423
434
|
function statusDetailInputHints(width) {
|
|
424
435
|
return selectInputHints(width, [
|
|
425
|
-
{ label: "status", detail: " · ^X diagnostics · ^S/Esc back · ^C exit" },
|
|
426
|
-
{ label: "status", detail: " · ^X diag · ^S/Esc back · ^C exit" },
|
|
436
|
+
{ label: "status", detail: " · ^E roles · ^X diagnostics · ^S/Esc back · ^C exit" },
|
|
437
|
+
{ label: "status", detail: " · ^E roles · ^X diag · ^S/Esc back · ^C exit" },
|
|
427
438
|
{ label: "status", detail: " · ^S back · ^C exit" },
|
|
428
439
|
{ label: "status", detail: " · ^S back" },
|
|
429
440
|
{ label: "status", detail: "" },
|
|
430
441
|
{ label: "st", detail: "" }
|
|
431
442
|
]);
|
|
432
443
|
}
|
|
444
|
+
function roleConfigurationInputHints(width, scope, canApply, saving, hasOverride) {
|
|
445
|
+
const label = saving ? "roles · saving" : `roles · ${scope}`;
|
|
446
|
+
const apply = canApply ? "Enter apply" : "task unavailable";
|
|
447
|
+
const reset = hasOverride ? " · X reset" : "";
|
|
448
|
+
return selectInputHints(width, [
|
|
449
|
+
{ label, detail: ` · Tab scope · Up/Dn role · Left/Right provider · M model · ${apply}${reset} · ^E/Esc back` },
|
|
450
|
+
{ label, detail: ` · Tab scope · Up/Dn · Left/Right provider · M model · ${apply}${reset} · Esc back` },
|
|
451
|
+
{ label, detail: ` · Tab · Up/Dn · Left/Right · M model · ${apply}${reset}` },
|
|
452
|
+
{ label, detail: ` · Tab · arrows · M · ${canApply ? "Enter" : "no task"}` },
|
|
453
|
+
{ label: "roles", detail: " · Tab · arrows · M · Enter" },
|
|
454
|
+
{ label: "roles", detail: "" }
|
|
455
|
+
]);
|
|
456
|
+
}
|
|
433
457
|
function workerOverviewInputHints(width) {
|
|
434
458
|
if (width < 16) {
|
|
435
459
|
return { label: "wrk", detail: "" };
|
|
@@ -588,8 +612,14 @@ function taskSessionsInputHints(width, includeArchived) {
|
|
|
588
612
|
{ label: "s", detail: "" }
|
|
589
613
|
]);
|
|
590
614
|
}
|
|
591
|
-
function mainConversationSessionsInputHints(width) {
|
|
615
|
+
export function mainConversationSessionsInputHints(width, includeArchived) {
|
|
616
|
+
const archivedAction = includeArchived ? "H hide archived" : "H archived";
|
|
592
617
|
return selectInputHints(width, [
|
|
618
|
+
{ label: "conversations", detail: ` · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · ${archivedAction} · N new · T tasks · Esc back` },
|
|
619
|
+
{ label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · N new · T tasks · Esc back" },
|
|
620
|
+
{ label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · N new · T tasks · Esc back" },
|
|
621
|
+
{ label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · N new · T tasks · Esc back" },
|
|
622
|
+
{ label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · N new · T tasks · Esc back" },
|
|
593
623
|
{ label: "conversations", detail: " · Up/Dn select · Enter restore · N new · T tasks · Esc back" },
|
|
594
624
|
{ label: "conversations", detail: " · Up/Dn select · Enter restore · T tasks · Esc back" },
|
|
595
625
|
{ label: "conversations", detail: " · Up/Dn select · Enter restore · Esc back" },
|
|
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
4
|
import { TUI_THEME } from "./theme.js";
|
|
5
|
-
export function MainConversationsView({ conversations, selectedIndex, notice = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
|
|
5
|
+
export function MainConversationsView({ conversations, selectedIndex, includeArchived = false, notice = null, action = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
|
|
6
6
|
const viewportHeight = Math.max(1, height);
|
|
7
7
|
const width = mainConversationsContentWidth(terminalWidth);
|
|
8
|
-
const lines = mainConversationsDisplayLines(conversations, selectedIndex, viewportHeight, terminalWidth, { notice, loading, error });
|
|
8
|
+
const lines = mainConversationsDisplayLines(conversations, selectedIndex, viewportHeight, terminalWidth, { includeArchived, notice, action, loading, error });
|
|
9
9
|
const blankRows = Math.max(0, viewportHeight - lines.length);
|
|
10
10
|
return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(MainConversationRow, { line: line, width: width }, `${line.conversationIndex ?? line.tone}-${index}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `main-conversation-fill-${index}`)))] }));
|
|
11
11
|
}
|
|
@@ -13,15 +13,26 @@ export function mainConversationsDisplayLines(conversations, selectedIndex, heig
|
|
|
13
13
|
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
14
14
|
const width = mainConversationsContentWidth(terminalWidth);
|
|
15
15
|
const lines = [{
|
|
16
|
-
text: fitMainConversationCandidates([
|
|
16
|
+
text: fitMainConversationCandidates([
|
|
17
|
+
state.includeArchived ? "Main conversations · archived shown" : "Main conversations",
|
|
18
|
+
state.includeArchived ? "Conversations · all" : "Conversations",
|
|
19
|
+
"Chats",
|
|
20
|
+
"C"
|
|
21
|
+
], width),
|
|
17
22
|
tone: "heading"
|
|
18
23
|
}];
|
|
19
24
|
if (viewportHeight >= 3) {
|
|
20
25
|
const messages = conversations.reduce((sum, conversation) => sum + conversation.messageCount, 0);
|
|
21
26
|
const nativeSessions = conversations.reduce((sum, conversation) => sum + conversation.nativeSessionCount, 0);
|
|
27
|
+
const archived = conversations.filter((conversation) => conversation.archivedAt).length;
|
|
22
28
|
lines.push({
|
|
23
29
|
text: fitMainConversationCandidates([
|
|
24
|
-
|
|
30
|
+
[
|
|
31
|
+
`${conversations.length} ${conversations.length === 1 ? "conversation" : "conversations"}`,
|
|
32
|
+
`${messages} messages`,
|
|
33
|
+
`${nativeSessions} native`,
|
|
34
|
+
...(archived > 0 ? [`${archived} archived`] : [])
|
|
35
|
+
].join(" · "),
|
|
25
36
|
`${conversations.length} conversations · ${messages} messages`,
|
|
26
37
|
`${conversations.length} conversations`,
|
|
27
38
|
`${conversations.length} chats`
|
|
@@ -29,7 +40,15 @@ export function mainConversationsDisplayLines(conversations, selectedIndex, heig
|
|
|
29
40
|
tone: "muted"
|
|
30
41
|
});
|
|
31
42
|
}
|
|
32
|
-
if (viewportHeight >= 4 && state.
|
|
43
|
+
if (viewportHeight >= 4 && state.action) {
|
|
44
|
+
lines.push({
|
|
45
|
+
text: fitMainConversationText(state.action.type === "rename"
|
|
46
|
+
? `rename · ${safeMainConversationText(state.action.title)} · Enter save · Esc cancel`
|
|
47
|
+
: `delete · ${safeMainConversationText(state.action.title)} · press D again · Esc cancel`, width),
|
|
48
|
+
tone: state.action.type === "delete" ? "danger" : "active"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else if (viewportHeight >= 4 && state.notice) {
|
|
33
52
|
lines.push({ text: fitMainConversationText(state.notice, width), tone: "success" });
|
|
34
53
|
}
|
|
35
54
|
const slots = Math.max(0, viewportHeight - lines.length);
|
|
@@ -88,14 +107,15 @@ function mainConversationRowText(conversation, selected, width) {
|
|
|
88
107
|
const title = safeMainConversationText(conversation.title);
|
|
89
108
|
const messages = `${conversation.messageCount} ${conversation.messageCount === 1 ? "message" : "messages"}`;
|
|
90
109
|
const native = `${conversation.nativeSessionCount} native`;
|
|
110
|
+
const status = conversation.archivedAt ? "archived" : null;
|
|
91
111
|
const date = conversation.lastActivityAt.slice(5, 16).replace("T", " ");
|
|
92
112
|
const scope = conversation.id
|
|
93
113
|
? `#${conversation.id.replace(/^conversation-/, "")}`
|
|
94
114
|
: "legacy";
|
|
95
115
|
return fitMainConversationCandidates([
|
|
96
|
-
[marker + title, messages, native, date].join(" · "),
|
|
97
|
-
[marker + title, messages, native].join(" · "),
|
|
98
|
-
[marker + title, messages].join(" · "),
|
|
116
|
+
[marker + title, status, messages, native, date].filter(Boolean).join(" · "),
|
|
117
|
+
[marker + title, status, messages, native].filter(Boolean).join(" · "),
|
|
118
|
+
[marker + title, status, messages].filter(Boolean).join(" · "),
|
|
99
119
|
[marker + title, date].join(" · "),
|
|
100
120
|
[marker + scope, messages].join(" · "),
|
|
101
121
|
marker.trimEnd()
|
|
@@ -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
|