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 CHANGED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "arbiter-ai",
3
- "version": "1.0.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": {
@@ -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;
@@ -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
- }
@@ -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;
@@ -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
- }