arbiter-ai 1.0.0 → 1.0.1
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/dist/index.js +0 -0
- package/package.json +2 -2
- package/dist/tui/layout.d.ts +0 -30
- package/dist/tui/layout.js +0 -200
- package/dist/tui/render.d.ts +0 -57
- package/dist/tui/render.js +0 -266
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arbiter-ai",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Hierarchical AI orchestration system for extending Claude's effective context window",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Hierarchical AI orchestration system for extending Claude's effective context window while staying on task",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
package/dist/tui/layout.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import blessed from 'blessed';
|
|
2
|
-
/**
|
|
3
|
-
* Layout elements interface - exposes all UI components
|
|
4
|
-
*/
|
|
5
|
-
export interface LayoutElements {
|
|
6
|
-
screen: blessed.Widgets.Screen;
|
|
7
|
-
titleBox: blessed.Widgets.BoxElement;
|
|
8
|
-
conversationBox: blessed.Widgets.BoxElement;
|
|
9
|
-
statusBox: blessed.Widgets.BoxElement;
|
|
10
|
-
inputBox: blessed.Widgets.TextboxElement;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Creates the blessed screen layout matching the architecture doc design
|
|
14
|
-
*
|
|
15
|
-
* Layout:
|
|
16
|
-
* - Full terminal takeover
|
|
17
|
-
* - Title area at top with "THE ARBITER" and subtitle
|
|
18
|
-
* - Main conversation area (scrollable)
|
|
19
|
-
* - Status bar showing Arbiter context %, Orchestrator context %, current tool
|
|
20
|
-
* - Input box at bottom with ">" prompt
|
|
21
|
-
*/
|
|
22
|
-
export declare function createLayout(): LayoutElements;
|
|
23
|
-
/**
|
|
24
|
-
* Creates the input prompt line with box drawing characters
|
|
25
|
-
*/
|
|
26
|
-
export declare function createInputPrompt(width: number): string;
|
|
27
|
-
/**
|
|
28
|
-
* Creates a horizontal separator line
|
|
29
|
-
*/
|
|
30
|
-
export declare function createSeparator(width: number): string;
|
package/dist/tui/layout.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// TUI layout configuration using blessed
|
|
2
|
-
// Defines the screen layout, panels, and UI structure
|
|
3
|
-
import blessed from 'blessed';
|
|
4
|
-
/**
|
|
5
|
-
* Box drawing characters for roguelike aesthetic
|
|
6
|
-
*/
|
|
7
|
-
const BOX_CHARS = {
|
|
8
|
-
topLeft: '\u2554', // ╔
|
|
9
|
-
topRight: '\u2557', // ╗
|
|
10
|
-
bottomLeft: '\u255A', // ╚
|
|
11
|
-
bottomRight: '\u255D', // ╝
|
|
12
|
-
horizontal: '\u2550', // ═
|
|
13
|
-
vertical: '\u2551', // ║
|
|
14
|
-
leftT: '\u2560', // ╠
|
|
15
|
-
rightT: '\u2563', // ╣
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Creates the blessed screen layout matching the architecture doc design
|
|
19
|
-
*
|
|
20
|
-
* Layout:
|
|
21
|
-
* - Full terminal takeover
|
|
22
|
-
* - Title area at top with "THE ARBITER" and subtitle
|
|
23
|
-
* - Main conversation area (scrollable)
|
|
24
|
-
* - Status bar showing Arbiter context %, Orchestrator context %, current tool
|
|
25
|
-
* - Input box at bottom with ">" prompt
|
|
26
|
-
*/
|
|
27
|
-
export function createLayout() {
|
|
28
|
-
// Create the main screen
|
|
29
|
-
const screen = blessed.screen({
|
|
30
|
-
smartCSR: true,
|
|
31
|
-
title: 'THE ARBITER',
|
|
32
|
-
fullUnicode: true,
|
|
33
|
-
dockBorders: true,
|
|
34
|
-
autoPadding: false,
|
|
35
|
-
});
|
|
36
|
-
// Title box at the top
|
|
37
|
-
const titleBox = blessed.box({
|
|
38
|
-
parent: screen,
|
|
39
|
-
top: 0,
|
|
40
|
-
left: 0,
|
|
41
|
-
width: '100%',
|
|
42
|
-
height: 4,
|
|
43
|
-
content: '',
|
|
44
|
-
tags: true,
|
|
45
|
-
style: {
|
|
46
|
-
fg: 'white',
|
|
47
|
-
bg: 'black',
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
// Set title content with box drawing characters
|
|
51
|
-
updateTitleContent(titleBox, screen.width);
|
|
52
|
-
// Main conversation area (scrollable)
|
|
53
|
-
const conversationBox = blessed.box({
|
|
54
|
-
parent: screen,
|
|
55
|
-
top: 4,
|
|
56
|
-
left: 0,
|
|
57
|
-
width: '100%',
|
|
58
|
-
height: '100%-10', // Leave room for status (3 lines) and input (3 lines)
|
|
59
|
-
content: '',
|
|
60
|
-
tags: true,
|
|
61
|
-
scrollable: true,
|
|
62
|
-
alwaysScroll: true,
|
|
63
|
-
scrollbar: {
|
|
64
|
-
ch: '\u2588', // █
|
|
65
|
-
style: {
|
|
66
|
-
fg: 'white',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
mouse: true,
|
|
70
|
-
keys: true,
|
|
71
|
-
vi: true,
|
|
72
|
-
style: {
|
|
73
|
-
fg: 'white',
|
|
74
|
-
bg: 'black',
|
|
75
|
-
border: {
|
|
76
|
-
fg: 'white',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
border: {
|
|
80
|
-
type: 'line',
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
// Override border characters to use double-line box drawing
|
|
84
|
-
conversationBox.border = {
|
|
85
|
-
type: 'line',
|
|
86
|
-
ch: ' ',
|
|
87
|
-
top: BOX_CHARS.horizontal,
|
|
88
|
-
bottom: BOX_CHARS.horizontal,
|
|
89
|
-
left: BOX_CHARS.vertical,
|
|
90
|
-
right: BOX_CHARS.vertical,
|
|
91
|
-
};
|
|
92
|
-
// Status bar area (3 lines)
|
|
93
|
-
const statusBox = blessed.box({
|
|
94
|
-
parent: screen,
|
|
95
|
-
bottom: 3,
|
|
96
|
-
left: 0,
|
|
97
|
-
width: '100%',
|
|
98
|
-
height: 4,
|
|
99
|
-
content: '',
|
|
100
|
-
tags: true,
|
|
101
|
-
style: {
|
|
102
|
-
fg: 'white',
|
|
103
|
-
bg: 'black',
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
// Input box at the bottom
|
|
107
|
-
const inputBox = blessed.textbox({
|
|
108
|
-
parent: screen,
|
|
109
|
-
bottom: 0,
|
|
110
|
-
left: 2, // Leave room for "> " prompt
|
|
111
|
-
width: '100%-2',
|
|
112
|
-
height: 3,
|
|
113
|
-
inputOnFocus: true,
|
|
114
|
-
mouse: true,
|
|
115
|
-
keys: true,
|
|
116
|
-
style: {
|
|
117
|
-
fg: 'white',
|
|
118
|
-
bg: 'black',
|
|
119
|
-
border: {
|
|
120
|
-
fg: 'white',
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
border: {
|
|
124
|
-
type: 'line',
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
// Create a fixed prompt label "> " that sits to the left of the input
|
|
128
|
-
const _promptLabel = blessed.text({
|
|
129
|
-
parent: screen,
|
|
130
|
-
bottom: 1,
|
|
131
|
-
left: 0,
|
|
132
|
-
width: 2,
|
|
133
|
-
height: 1,
|
|
134
|
-
content: '> ',
|
|
135
|
-
style: {
|
|
136
|
-
fg: 'white',
|
|
137
|
-
bg: 'black',
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
// Handle screen resize to update title width
|
|
141
|
-
screen.on('resize', () => {
|
|
142
|
-
updateTitleContent(titleBox, screen.width);
|
|
143
|
-
screen.render();
|
|
144
|
-
});
|
|
145
|
-
// Set up quit key bindings
|
|
146
|
-
screen.key(['escape', 'q', 'C-c'], () => {
|
|
147
|
-
return process.exit(0);
|
|
148
|
-
});
|
|
149
|
-
return {
|
|
150
|
-
screen,
|
|
151
|
-
titleBox,
|
|
152
|
-
conversationBox,
|
|
153
|
-
statusBox,
|
|
154
|
-
inputBox,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Updates the title box content with proper width-based formatting
|
|
159
|
-
*/
|
|
160
|
-
function updateTitleContent(titleBox, width) {
|
|
161
|
-
const title = 'THE ARBITER';
|
|
162
|
-
const subtitle = 'OF THAT WHICH WAS, THAT WHICH IS, AND THAT WHICH SHALL COME TO BE';
|
|
163
|
-
// Calculate effective width (accounting for border characters)
|
|
164
|
-
const effectiveWidth = Math.max(width - 2, 80);
|
|
165
|
-
// Create top border
|
|
166
|
-
const topBorder = BOX_CHARS.topLeft + BOX_CHARS.horizontal.repeat(effectiveWidth) + BOX_CHARS.topRight;
|
|
167
|
-
// Center the title and subtitle
|
|
168
|
-
const titlePadding = Math.max(0, Math.floor((effectiveWidth - title.length) / 2));
|
|
169
|
-
const subtitlePadding = Math.max(0, Math.floor((effectiveWidth - subtitle.length) / 2));
|
|
170
|
-
const titleLine = BOX_CHARS.vertical +
|
|
171
|
-
' '.repeat(titlePadding) +
|
|
172
|
-
`{bold}${title}{/bold}` +
|
|
173
|
-
' '.repeat(effectiveWidth - titlePadding - title.length) +
|
|
174
|
-
BOX_CHARS.vertical;
|
|
175
|
-
const subtitleLine = BOX_CHARS.vertical +
|
|
176
|
-
' '.repeat(subtitlePadding) +
|
|
177
|
-
subtitle +
|
|
178
|
-
' '.repeat(Math.max(0, effectiveWidth - subtitlePadding - subtitle.length)) +
|
|
179
|
-
BOX_CHARS.vertical;
|
|
180
|
-
// Create separator
|
|
181
|
-
const separator = BOX_CHARS.leftT + BOX_CHARS.horizontal.repeat(effectiveWidth) + BOX_CHARS.rightT;
|
|
182
|
-
titleBox.setContent(`${topBorder}\n${titleLine}\n${subtitleLine}\n${separator}`);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Creates the input prompt line with box drawing characters
|
|
186
|
-
*/
|
|
187
|
-
export function createInputPrompt(width) {
|
|
188
|
-
const effectiveWidth = Math.max(width - 4, 76);
|
|
189
|
-
const promptLine = `${BOX_CHARS.vertical} > `;
|
|
190
|
-
const endLine = ' '.repeat(effectiveWidth) + BOX_CHARS.vertical;
|
|
191
|
-
const bottomBorder = BOX_CHARS.bottomLeft + BOX_CHARS.horizontal.repeat(width - 2) + BOX_CHARS.bottomRight;
|
|
192
|
-
return `${promptLine + endLine}\n${bottomBorder}`;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Creates a horizontal separator line
|
|
196
|
-
*/
|
|
197
|
-
export function createSeparator(width) {
|
|
198
|
-
const effectiveWidth = Math.max(width - 2, 78);
|
|
199
|
-
return BOX_CHARS.leftT + BOX_CHARS.horizontal.repeat(effectiveWidth) + BOX_CHARS.rightT;
|
|
200
|
-
}
|
package/dist/tui/render.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { LayoutElements } from './layout.js';
|
|
2
|
-
import { type AppState } from '../state.js';
|
|
3
|
-
/**
|
|
4
|
-
* Generates animated dots text based on current animation frame
|
|
5
|
-
* Cycles through: "Working." -> "Working.." -> "Working..."
|
|
6
|
-
* @param baseText - The base text to animate (e.g., "Working" or "Waiting for Arbiter")
|
|
7
|
-
* @returns The text with animated dots appended
|
|
8
|
-
*/
|
|
9
|
-
export declare function getAnimatedDots(baseText: string): string;
|
|
10
|
-
/**
|
|
11
|
-
* Advances the animation frame for the loading dots
|
|
12
|
-
* Should be called by an interval timer
|
|
13
|
-
*/
|
|
14
|
-
export declare function advanceAnimation(): void;
|
|
15
|
-
/**
|
|
16
|
-
* Resets the animation frame to 0
|
|
17
|
-
*/
|
|
18
|
-
export declare function resetAnimation(): void;
|
|
19
|
-
/**
|
|
20
|
-
* Renders the conversation log to the conversation box
|
|
21
|
-
* Formats messages with speakers (You:, Arbiter:, Orchestrator I:, etc.)
|
|
22
|
-
*/
|
|
23
|
-
export declare function renderConversation(elements: LayoutElements, state: AppState): void;
|
|
24
|
-
/**
|
|
25
|
-
* Waiting state enum for different waiting scenarios
|
|
26
|
-
*/
|
|
27
|
-
export type WaitingState = 'none' | 'arbiter' | 'orchestrator';
|
|
28
|
-
/**
|
|
29
|
-
* Renders the status bar with context percentages and current tool
|
|
30
|
-
*
|
|
31
|
-
* When orchestrator is active:
|
|
32
|
-
* ║ Arbiter ─────────────────────────────────────────────────── ██░░░░░░░░ 18% ║
|
|
33
|
-
* ║ Orchestrator I ──────────────────────────────────────────── ████████░░ 74% ║
|
|
34
|
-
* ║ ◈ Edit (12) ║
|
|
35
|
-
*
|
|
36
|
-
* When no orchestrator (Arbiter speaks to human):
|
|
37
|
-
* ║ Arbiter ─────────────────────────────────────────────────── ██░░░░░░░░ 18% ║
|
|
38
|
-
* ║ Awaiting your command. ║
|
|
39
|
-
*
|
|
40
|
-
* @param waitingState - Optional waiting state to show animated dots
|
|
41
|
-
*/
|
|
42
|
-
export declare function renderStatus(elements: LayoutElements, state: AppState, waitingState?: WaitingState): void;
|
|
43
|
-
/**
|
|
44
|
-
* Creates an ASCII progress bar
|
|
45
|
-
* @param percent - Current percentage (0-100)
|
|
46
|
-
* @param width - Total width of the progress bar
|
|
47
|
-
* @returns Progress bar string like "████████░░"
|
|
48
|
-
*/
|
|
49
|
-
export declare function renderProgressBar(percent: number, width: number): string;
|
|
50
|
-
/**
|
|
51
|
-
* Renders the input area with prompt
|
|
52
|
-
*/
|
|
53
|
-
export declare function renderInputArea(elements: LayoutElements): void;
|
|
54
|
-
/**
|
|
55
|
-
* Updates the entire display
|
|
56
|
-
*/
|
|
57
|
-
export declare function renderAll(elements: LayoutElements, state: AppState): void;
|
package/dist/tui/render.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
// TUI rendering logic
|
|
2
|
-
// Handles updating the display with agent outputs and system status
|
|
3
|
-
import { toRoman } from '../state.js';
|
|
4
|
-
/**
|
|
5
|
-
* Box drawing characters for borders
|
|
6
|
-
*/
|
|
7
|
-
const BOX_CHARS = {
|
|
8
|
-
topLeft: '\u2554', // ╔
|
|
9
|
-
topRight: '\u2557', // ╗
|
|
10
|
-
bottomLeft: '\u255A', // ╚
|
|
11
|
-
bottomRight: '\u255D', // ╝
|
|
12
|
-
horizontal: '\u2550', // ═
|
|
13
|
-
vertical: '\u2551', // ║
|
|
14
|
-
leftT: '\u2560', // ╠
|
|
15
|
-
rightT: '\u2563', // ╣
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Progress bar characters
|
|
19
|
-
*/
|
|
20
|
-
const PROGRESS_CHARS = {
|
|
21
|
-
filled: '\u2588', // █
|
|
22
|
-
empty: '\u2591', // ░
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Tool indicator character
|
|
26
|
-
*/
|
|
27
|
-
const TOOL_INDICATOR = '\u25C8'; // ◈
|
|
28
|
-
/**
|
|
29
|
-
* Animation state for loading dots
|
|
30
|
-
*/
|
|
31
|
-
let animationFrame = 0;
|
|
32
|
-
/**
|
|
33
|
-
* Generates animated dots text based on current animation frame
|
|
34
|
-
* Cycles through: "Working." -> "Working.." -> "Working..."
|
|
35
|
-
* @param baseText - The base text to animate (e.g., "Working" or "Waiting for Arbiter")
|
|
36
|
-
* @returns The text with animated dots appended
|
|
37
|
-
*/
|
|
38
|
-
export function getAnimatedDots(baseText) {
|
|
39
|
-
const dotCount = (animationFrame % 3) + 1;
|
|
40
|
-
return baseText + '.'.repeat(dotCount);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Advances the animation frame for the loading dots
|
|
44
|
-
* Should be called by an interval timer
|
|
45
|
-
*/
|
|
46
|
-
export function advanceAnimation() {
|
|
47
|
-
animationFrame = (animationFrame + 1) % 3;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Resets the animation frame to 0
|
|
51
|
-
*/
|
|
52
|
-
export function resetAnimation() {
|
|
53
|
-
animationFrame = 0;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Renders the conversation log to the conversation box
|
|
57
|
-
* Formats messages with speakers (You:, Arbiter:, Orchestrator I:, etc.)
|
|
58
|
-
*/
|
|
59
|
-
export function renderConversation(elements, state) {
|
|
60
|
-
const { conversationBox, screen } = elements;
|
|
61
|
-
const lines = [];
|
|
62
|
-
// Get effective width for text wrapping
|
|
63
|
-
const effectiveWidth = Math.max(screen.width - 6, 74);
|
|
64
|
-
for (const message of state.conversationLog) {
|
|
65
|
-
// Format speaker label
|
|
66
|
-
const speakerLabel = formatSpeakerLabel(message.speaker);
|
|
67
|
-
// Format and wrap the message text
|
|
68
|
-
const wrappedText = wrapText(message.text, effectiveWidth - speakerLabel.length - 2);
|
|
69
|
-
const textLines = wrappedText.split('\n');
|
|
70
|
-
// First line with speaker label
|
|
71
|
-
lines.push(` ${speakerLabel} ${textLines[0]}`);
|
|
72
|
-
// Subsequent lines indented to align with first line
|
|
73
|
-
const indent = ' '.repeat(speakerLabel.length + 3);
|
|
74
|
-
for (let i = 1; i < textLines.length; i++) {
|
|
75
|
-
lines.push(`${indent}${textLines[i]}`);
|
|
76
|
-
}
|
|
77
|
-
// Add empty line between messages
|
|
78
|
-
lines.push('');
|
|
79
|
-
}
|
|
80
|
-
// Join lines and add vertical borders
|
|
81
|
-
const formattedContent = lines
|
|
82
|
-
.map((line) => {
|
|
83
|
-
const paddedLine = line.padEnd(effectiveWidth, ' ');
|
|
84
|
-
return `${BOX_CHARS.vertical}${paddedLine}${BOX_CHARS.vertical}`;
|
|
85
|
-
})
|
|
86
|
-
.join('\n');
|
|
87
|
-
conversationBox.setContent(formattedContent);
|
|
88
|
-
// Auto-scroll to bottom
|
|
89
|
-
conversationBox.setScrollPerc(100);
|
|
90
|
-
screen.render();
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Renders the status bar with context percentages and current tool
|
|
94
|
-
*
|
|
95
|
-
* When orchestrator is active:
|
|
96
|
-
* ║ Arbiter ─────────────────────────────────────────────────── ██░░░░░░░░ 18% ║
|
|
97
|
-
* ║ Orchestrator I ──────────────────────────────────────────── ████████░░ 74% ║
|
|
98
|
-
* ║ ◈ Edit (12) ║
|
|
99
|
-
*
|
|
100
|
-
* When no orchestrator (Arbiter speaks to human):
|
|
101
|
-
* ║ Arbiter ─────────────────────────────────────────────────── ██░░░░░░░░ 18% ║
|
|
102
|
-
* ║ Awaiting your command. ║
|
|
103
|
-
*
|
|
104
|
-
* @param waitingState - Optional waiting state to show animated dots
|
|
105
|
-
*/
|
|
106
|
-
export function renderStatus(elements, state, waitingState = 'none') {
|
|
107
|
-
const { statusBox, screen } = elements;
|
|
108
|
-
const effectiveWidth = Math.max(screen.width - 2, 78);
|
|
109
|
-
// Status separator at top
|
|
110
|
-
const separator = BOX_CHARS.leftT + BOX_CHARS.horizontal.repeat(effectiveWidth) + BOX_CHARS.rightT;
|
|
111
|
-
// Progress bar width (10 characters for the bar)
|
|
112
|
-
const barWidth = 10;
|
|
113
|
-
// Build Arbiter status line
|
|
114
|
-
const arbiterLabel = 'Arbiter';
|
|
115
|
-
const arbiterBar = renderProgressBar(state.arbiterContextPercent, barWidth);
|
|
116
|
-
const arbiterPercent = `${Math.round(state.arbiterContextPercent)}%`.padStart(4);
|
|
117
|
-
const arbiterDashes = createDashLine(effectiveWidth - arbiterLabel.length - barWidth - arbiterPercent.length - 8);
|
|
118
|
-
const arbiterLine = `${BOX_CHARS.vertical} ${arbiterLabel} ${arbiterDashes} ${arbiterBar} ${arbiterPercent} ${BOX_CHARS.vertical}`;
|
|
119
|
-
let orchestratorLine;
|
|
120
|
-
let toolLine;
|
|
121
|
-
if (state.currentOrchestrator) {
|
|
122
|
-
// Orchestrator status line
|
|
123
|
-
const orchLabel = `Orchestrator ${toRoman(state.currentOrchestrator.number)}`;
|
|
124
|
-
const orchBar = renderProgressBar(state.currentOrchestrator.contextPercent, barWidth);
|
|
125
|
-
const orchPercent = `${Math.round(state.currentOrchestrator.contextPercent)}%`.padStart(4);
|
|
126
|
-
const orchDashes = createDashLine(effectiveWidth - orchLabel.length - barWidth - orchPercent.length - 8);
|
|
127
|
-
orchestratorLine = `${BOX_CHARS.vertical} ${orchLabel} ${orchDashes} ${orchBar} ${orchPercent} ${BOX_CHARS.vertical}`;
|
|
128
|
-
// Tool indicator line
|
|
129
|
-
if (state.currentOrchestrator.currentTool) {
|
|
130
|
-
const toolText = `${TOOL_INDICATOR} ${state.currentOrchestrator.currentTool} (${state.currentOrchestrator.toolCallCount})`;
|
|
131
|
-
const toolPadding = ' '.repeat(effectiveWidth - toolText.length - 2);
|
|
132
|
-
toolLine = `${BOX_CHARS.vertical} ${toolText}${toolPadding}${BOX_CHARS.vertical}`;
|
|
133
|
-
}
|
|
134
|
-
else if (waitingState === 'orchestrator') {
|
|
135
|
-
// Show animated dots when waiting for orchestrator response
|
|
136
|
-
const waitingText = getAnimatedDots('Working');
|
|
137
|
-
const waitingPadding = ' '.repeat(effectiveWidth - waitingText.length - 2);
|
|
138
|
-
toolLine = `${BOX_CHARS.vertical} ${waitingText}${waitingPadding}${BOX_CHARS.vertical}`;
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
const waitingText = 'Working...';
|
|
142
|
-
const waitingPadding = ' '.repeat(effectiveWidth - waitingText.length - 2);
|
|
143
|
-
toolLine = `${BOX_CHARS.vertical} ${waitingText}${waitingPadding}${BOX_CHARS.vertical}`;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else if (waitingState === 'arbiter') {
|
|
147
|
-
// Waiting for Arbiter response - show animated dots
|
|
148
|
-
const waitingText = getAnimatedDots('Waiting for Arbiter');
|
|
149
|
-
const waitingPadding = ' '.repeat(effectiveWidth - waitingText.length - 2);
|
|
150
|
-
orchestratorLine = `${BOX_CHARS.vertical} ${waitingText}${waitingPadding}${BOX_CHARS.vertical}`;
|
|
151
|
-
toolLine = `${BOX_CHARS.vertical}${' '.repeat(effectiveWidth)}${BOX_CHARS.vertical}`;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// No orchestrator - show awaiting message
|
|
155
|
-
const awaitingText = 'Awaiting your command.';
|
|
156
|
-
const awaitingPadding = ' '.repeat(effectiveWidth - awaitingText.length - 2);
|
|
157
|
-
orchestratorLine = `${BOX_CHARS.vertical} ${awaitingText}${awaitingPadding}${BOX_CHARS.vertical}`;
|
|
158
|
-
toolLine = `${BOX_CHARS.vertical}${' '.repeat(effectiveWidth)}${BOX_CHARS.vertical}`;
|
|
159
|
-
}
|
|
160
|
-
statusBox.setContent(`${separator}\n${arbiterLine}\n${orchestratorLine}\n${toolLine}`);
|
|
161
|
-
screen.render();
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Creates an ASCII progress bar
|
|
165
|
-
* @param percent - Current percentage (0-100)
|
|
166
|
-
* @param width - Total width of the progress bar
|
|
167
|
-
* @returns Progress bar string like "████████░░"
|
|
168
|
-
*/
|
|
169
|
-
export function renderProgressBar(percent, width) {
|
|
170
|
-
// Clamp percent between 0 and 100
|
|
171
|
-
const clampedPercent = Math.max(0, Math.min(100, percent));
|
|
172
|
-
// Calculate filled width
|
|
173
|
-
const filledWidth = Math.round((clampedPercent / 100) * width);
|
|
174
|
-
const emptyWidth = width - filledWidth;
|
|
175
|
-
// Build progress bar
|
|
176
|
-
return PROGRESS_CHARS.filled.repeat(filledWidth) + PROGRESS_CHARS.empty.repeat(emptyWidth);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Formats a speaker label with appropriate styling
|
|
180
|
-
*/
|
|
181
|
-
function formatSpeakerLabel(speaker) {
|
|
182
|
-
switch (speaker) {
|
|
183
|
-
case 'human':
|
|
184
|
-
return '{bold}You:{/bold}';
|
|
185
|
-
case 'arbiter':
|
|
186
|
-
return '{bold}{yellow-fg}Arbiter:{/yellow-fg}{/bold}';
|
|
187
|
-
default:
|
|
188
|
-
// Orchestrator labels come through as-is (e.g., "Orchestrator I")
|
|
189
|
-
if (speaker.startsWith('Orchestrator')) {
|
|
190
|
-
return `{bold}{cyan-fg}${speaker}:{/cyan-fg}{/bold}`;
|
|
191
|
-
}
|
|
192
|
-
return `{bold}${speaker}:{/bold}`;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Wraps text to fit within a specified width
|
|
197
|
-
*/
|
|
198
|
-
function wrapText(text, maxWidth) {
|
|
199
|
-
if (maxWidth <= 0) {
|
|
200
|
-
return text;
|
|
201
|
-
}
|
|
202
|
-
const words = text.split(' ');
|
|
203
|
-
const lines = [];
|
|
204
|
-
let currentLine = '';
|
|
205
|
-
for (const word of words) {
|
|
206
|
-
// Handle words that are longer than maxWidth
|
|
207
|
-
if (word.length > maxWidth) {
|
|
208
|
-
// If there's content in the current line, push it first
|
|
209
|
-
if (currentLine) {
|
|
210
|
-
lines.push(currentLine.trim());
|
|
211
|
-
currentLine = '';
|
|
212
|
-
}
|
|
213
|
-
// Break the long word
|
|
214
|
-
let remaining = word;
|
|
215
|
-
while (remaining.length > maxWidth) {
|
|
216
|
-
lines.push(remaining.substring(0, maxWidth));
|
|
217
|
-
remaining = remaining.substring(maxWidth);
|
|
218
|
-
}
|
|
219
|
-
currentLine = `${remaining} `;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
// Check if adding this word would exceed the limit
|
|
223
|
-
if (currentLine.length + word.length + 1 > maxWidth) {
|
|
224
|
-
lines.push(currentLine.trim());
|
|
225
|
-
currentLine = `${word} `;
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
currentLine += `${word} `;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Add remaining content
|
|
232
|
-
if (currentLine.trim()) {
|
|
233
|
-
lines.push(currentLine.trim());
|
|
234
|
-
}
|
|
235
|
-
return lines.join('\n');
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Creates a dash line for status bar alignment
|
|
239
|
-
* Uses em-dash (─) for cleaner appearance
|
|
240
|
-
*/
|
|
241
|
-
function createDashLine(length) {
|
|
242
|
-
const dashChar = '\u2500'; // ─
|
|
243
|
-
return dashChar.repeat(Math.max(0, length));
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Renders the input area with prompt
|
|
247
|
-
*/
|
|
248
|
-
export function renderInputArea(elements) {
|
|
249
|
-
const { screen } = elements;
|
|
250
|
-
const effectiveWidth = Math.max(screen.width - 2, 78);
|
|
251
|
-
// Input separator and prompt are handled by the layout
|
|
252
|
-
// This function can be used for additional input area styling if needed
|
|
253
|
-
// Create input separator
|
|
254
|
-
const _inputSeparator = BOX_CHARS.leftT + BOX_CHARS.horizontal.repeat(effectiveWidth) + BOX_CHARS.rightT;
|
|
255
|
-
// The inputBox already has its own styling from layout
|
|
256
|
-
// We can prepend a separator to the status box if needed
|
|
257
|
-
screen.render();
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Updates the entire display
|
|
261
|
-
*/
|
|
262
|
-
export function renderAll(elements, state) {
|
|
263
|
-
renderConversation(elements, state);
|
|
264
|
-
renderStatus(elements, state);
|
|
265
|
-
elements.screen.render();
|
|
266
|
-
}
|