arbiter-ai 1.0.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.
Files changed (51) hide show
  1. package/README.md +41 -0
  2. package/assets/jerom_16x16.png +0 -0
  3. package/dist/arbiter.d.ts +43 -0
  4. package/dist/arbiter.js +486 -0
  5. package/dist/context-analyzer.d.ts +15 -0
  6. package/dist/context-analyzer.js +603 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +165 -0
  9. package/dist/orchestrator.d.ts +31 -0
  10. package/dist/orchestrator.js +227 -0
  11. package/dist/router.d.ts +187 -0
  12. package/dist/router.js +1135 -0
  13. package/dist/router.test.d.ts +15 -0
  14. package/dist/router.test.js +95 -0
  15. package/dist/session-persistence.d.ts +9 -0
  16. package/dist/session-persistence.js +63 -0
  17. package/dist/session-persistence.test.d.ts +1 -0
  18. package/dist/session-persistence.test.js +165 -0
  19. package/dist/sound.d.ts +31 -0
  20. package/dist/sound.js +50 -0
  21. package/dist/state.d.ts +72 -0
  22. package/dist/state.js +107 -0
  23. package/dist/state.test.d.ts +1 -0
  24. package/dist/state.test.js +194 -0
  25. package/dist/test-headless.d.ts +1 -0
  26. package/dist/test-headless.js +155 -0
  27. package/dist/tui/index.d.ts +14 -0
  28. package/dist/tui/index.js +17 -0
  29. package/dist/tui/layout.d.ts +30 -0
  30. package/dist/tui/layout.js +200 -0
  31. package/dist/tui/render.d.ts +57 -0
  32. package/dist/tui/render.js +266 -0
  33. package/dist/tui/scene.d.ts +64 -0
  34. package/dist/tui/scene.js +366 -0
  35. package/dist/tui/screens/CharacterSelect-termkit.d.ts +18 -0
  36. package/dist/tui/screens/CharacterSelect-termkit.js +216 -0
  37. package/dist/tui/screens/ForestIntro-termkit.d.ts +15 -0
  38. package/dist/tui/screens/ForestIntro-termkit.js +856 -0
  39. package/dist/tui/screens/GitignoreCheck-termkit.d.ts +14 -0
  40. package/dist/tui/screens/GitignoreCheck-termkit.js +185 -0
  41. package/dist/tui/screens/TitleScreen-termkit.d.ts +14 -0
  42. package/dist/tui/screens/TitleScreen-termkit.js +132 -0
  43. package/dist/tui/screens/index.d.ts +9 -0
  44. package/dist/tui/screens/index.js +10 -0
  45. package/dist/tui/tileset.d.ts +97 -0
  46. package/dist/tui/tileset.js +237 -0
  47. package/dist/tui/tui-termkit.d.ts +34 -0
  48. package/dist/tui/tui-termkit.js +2602 -0
  49. package/dist/tui/types.d.ts +41 -0
  50. package/dist/tui/types.js +4 -0
  51. package/package.json +71 -0
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Router Integration Tests
3
+ *
4
+ * Note: The Router class is tightly coupled to the Claude Agent SDK's `query()` function,
5
+ * which returns an async generator. Full integration testing would require:
6
+ * - Mocking the SDK's `query()` function with async generators
7
+ * - Mocking message sequences (SDKMessage types)
8
+ * - Mocking abort controllers and timers for watchdog behavior
9
+ *
10
+ * These tests cover what's feasible without deep SDK mocking:
11
+ * - Type exports are correct
12
+ * - Router can be instantiated with proper callbacks
13
+ * - Basic structural validation
14
+ */
15
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Router Integration Tests
3
+ *
4
+ * Note: The Router class is tightly coupled to the Claude Agent SDK's `query()` function,
5
+ * which returns an async generator. Full integration testing would require:
6
+ * - Mocking the SDK's `query()` function with async generators
7
+ * - Mocking message sequences (SDKMessage types)
8
+ * - Mocking abort controllers and timers for watchdog behavior
9
+ *
10
+ * These tests cover what's feasible without deep SDK mocking:
11
+ * - Type exports are correct
12
+ * - Router can be instantiated with proper callbacks
13
+ * - Basic structural validation
14
+ */
15
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
16
+ import { Router } from './router.js';
17
+ import { createInitialState } from './state.js';
18
+ // Mock the SDK query function to prevent actual API calls
19
+ vi.mock('@anthropic-ai/claude-agent-sdk', () => ({
20
+ query: vi.fn(),
21
+ }));
22
+ // Mock session persistence to prevent file I/O
23
+ vi.mock('./session-persistence.js', () => ({
24
+ saveSession: vi.fn(),
25
+ loadSession: vi.fn(() => null),
26
+ clearSession: vi.fn(),
27
+ }));
28
+ describe('router', () => {
29
+ let state;
30
+ let callbacks;
31
+ beforeEach(() => {
32
+ state = createInitialState();
33
+ // Create mock callbacks matching actual RouterCallbacks interface
34
+ callbacks = {
35
+ onHumanMessage: vi.fn(),
36
+ onArbiterMessage: vi.fn(),
37
+ onOrchestratorMessage: vi.fn(),
38
+ onContextUpdate: vi.fn(),
39
+ onToolUse: vi.fn(),
40
+ onModeChange: vi.fn(),
41
+ onDebugLog: vi.fn(),
42
+ };
43
+ });
44
+ describe('Router instantiation', () => {
45
+ it('should create a Router instance with state and callbacks', () => {
46
+ const router = new Router(state, callbacks);
47
+ expect(router).toBeInstanceOf(Router);
48
+ });
49
+ it('should have required public methods', () => {
50
+ const router = new Router(state, callbacks);
51
+ // Check that required methods exist
52
+ expect(typeof router.start).toBe('function');
53
+ expect(typeof router.stop).toBe('function');
54
+ expect(typeof router.sendHumanMessage).toBe('function');
55
+ expect(typeof router.resumeFromSavedSession).toBe('function');
56
+ });
57
+ });
58
+ describe('Type exports', () => {
59
+ it('should export RouterCallbacks type with correct structure', () => {
60
+ // This test validates at compile time that the types are correct
61
+ // Using vi.fn() satisfies both type checking and linting (no empty blocks)
62
+ const validCallbacks = {
63
+ onHumanMessage: vi.fn(),
64
+ onArbiterMessage: vi.fn(),
65
+ onOrchestratorMessage: vi.fn(),
66
+ onContextUpdate: vi.fn(),
67
+ onToolUse: vi.fn(),
68
+ onModeChange: vi.fn(),
69
+ };
70
+ expect(validCallbacks.onHumanMessage).toBeDefined();
71
+ expect(validCallbacks.onArbiterMessage).toBeDefined();
72
+ expect(validCallbacks.onOrchestratorMessage).toBeDefined();
73
+ expect(validCallbacks.onContextUpdate).toBeDefined();
74
+ expect(validCallbacks.onToolUse).toBeDefined();
75
+ expect(validCallbacks.onModeChange).toBeDefined();
76
+ });
77
+ it('should export DebugLogEntry type', () => {
78
+ const entry = {
79
+ type: 'message',
80
+ speaker: 'arbiter',
81
+ text: 'test content',
82
+ };
83
+ expect(entry.type).toBe('message');
84
+ expect(entry.speaker).toBe('arbiter');
85
+ expect(entry.text).toBe('test content');
86
+ });
87
+ });
88
+ describe('Router.stop()', () => {
89
+ it('should be safe to call stop on a router that was never started', async () => {
90
+ const router = new Router(state, callbacks);
91
+ // Should not throw
92
+ await expect(router.stop()).resolves.toBeUndefined();
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,9 @@
1
+ export interface PersistedSession {
2
+ arbiterSessionId: string;
3
+ orchestratorSessionId: string | null;
4
+ orchestratorNumber: number | null;
5
+ savedAt: string;
6
+ }
7
+ export declare function saveSession(arbiterSessionId: string, orchestratorSessionId: string | null, orchestratorNumber: number | null): void;
8
+ export declare function loadSession(): PersistedSession | null;
9
+ export declare function clearSession(): void;
@@ -0,0 +1,63 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const SESSION_FILE = '.claude/.arbiter-session.json';
4
+ function getSessionFilePath() {
5
+ return path.join(process.cwd(), SESSION_FILE);
6
+ }
7
+ export function saveSession(arbiterSessionId, orchestratorSessionId, orchestratorNumber) {
8
+ try {
9
+ const sessionData = {
10
+ arbiterSessionId,
11
+ orchestratorSessionId,
12
+ orchestratorNumber,
13
+ savedAt: new Date().toISOString(),
14
+ };
15
+ const filePath = getSessionFilePath();
16
+ const dirPath = path.dirname(filePath);
17
+ // Create .claude directory if it doesn't exist
18
+ if (!fs.existsSync(dirPath)) {
19
+ fs.mkdirSync(dirPath, { recursive: true });
20
+ }
21
+ fs.writeFileSync(filePath, JSON.stringify(sessionData, null, 2), 'utf-8');
22
+ }
23
+ catch {
24
+ // Silent on errors (best-effort save)
25
+ }
26
+ }
27
+ export function loadSession() {
28
+ try {
29
+ const filePath = getSessionFilePath();
30
+ // Return null if file doesn't exist
31
+ if (!fs.existsSync(filePath)) {
32
+ return null;
33
+ }
34
+ const content = fs.readFileSync(filePath, 'utf-8');
35
+ const session = JSON.parse(content);
36
+ // Validate savedAt exists and is not stale (more than 24 hours old)
37
+ if (!session.savedAt) {
38
+ return null;
39
+ }
40
+ const savedTime = new Date(session.savedAt).getTime();
41
+ const now = Date.now();
42
+ const twentyFourHours = 24 * 60 * 60 * 1000;
43
+ if (now - savedTime > twentyFourHours) {
44
+ return null;
45
+ }
46
+ return session;
47
+ }
48
+ catch {
49
+ // Return null if file is invalid JSON or any other error
50
+ return null;
51
+ }
52
+ }
53
+ export function clearSession() {
54
+ try {
55
+ const filePath = getSessionFilePath();
56
+ if (fs.existsSync(filePath)) {
57
+ fs.unlinkSync(filePath);
58
+ }
59
+ }
60
+ catch {
61
+ // Silent on errors
62
+ }
63
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,165 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { clearSession, loadSession, saveSession, } from './session-persistence.js';
5
+ // Mock the fs module
6
+ vi.mock('node:fs');
7
+ vi.mock('node:path');
8
+ describe('session-persistence', () => {
9
+ const mockCwd = '/test/workspace';
10
+ const mockSessionPath = '/test/workspace/.claude/.arbiter-session.json';
11
+ const mockDirPath = '/test/workspace/.claude';
12
+ beforeEach(() => {
13
+ vi.resetAllMocks();
14
+ vi.spyOn(process, 'cwd').mockReturnValue(mockCwd);
15
+ vi.mocked(path.join).mockReturnValue(mockSessionPath);
16
+ vi.mocked(path.dirname).mockReturnValue(mockDirPath);
17
+ });
18
+ afterEach(() => {
19
+ vi.restoreAllMocks();
20
+ });
21
+ describe('saveSession', () => {
22
+ it('should create directory and save session data', () => {
23
+ vi.mocked(fs.existsSync).mockReturnValue(false);
24
+ vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
25
+ vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
26
+ saveSession('arbiter-123', 'orch-456', 2);
27
+ expect(fs.existsSync).toHaveBeenCalledWith(mockDirPath);
28
+ expect(fs.mkdirSync).toHaveBeenCalledWith(mockDirPath, { recursive: true });
29
+ expect(fs.writeFileSync).toHaveBeenCalledWith(mockSessionPath, expect.stringContaining('"arbiterSessionId": "arbiter-123"'), 'utf-8');
30
+ });
31
+ it('should not create directory if it already exists', () => {
32
+ vi.mocked(fs.existsSync).mockReturnValue(true);
33
+ vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
34
+ saveSession('arbiter-123', null, null);
35
+ expect(fs.existsSync).toHaveBeenCalledWith(mockDirPath);
36
+ expect(fs.mkdirSync).not.toHaveBeenCalled();
37
+ });
38
+ it('should save session with null orchestrator values', () => {
39
+ vi.mocked(fs.existsSync).mockReturnValue(true);
40
+ vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
41
+ saveSession('arbiter-789', null, null);
42
+ const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0];
43
+ const savedData = JSON.parse(writeCall[1]);
44
+ expect(savedData.arbiterSessionId).toBe('arbiter-789');
45
+ expect(savedData.orchestratorSessionId).toBeNull();
46
+ expect(savedData.orchestratorNumber).toBeNull();
47
+ expect(savedData.savedAt).toBeDefined();
48
+ });
49
+ it('should silently handle write errors', () => {
50
+ vi.mocked(fs.existsSync).mockReturnValue(true);
51
+ vi.mocked(fs.writeFileSync).mockImplementation(() => {
52
+ throw new Error('Write error');
53
+ });
54
+ // Should not throw
55
+ expect(() => saveSession('arbiter-123', null, null)).not.toThrow();
56
+ });
57
+ it('should include ISO timestamp in savedAt', () => {
58
+ vi.mocked(fs.existsSync).mockReturnValue(true);
59
+ vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
60
+ const beforeSave = new Date();
61
+ saveSession('arbiter-123', 'orch-456', 1);
62
+ const afterSave = new Date();
63
+ const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0];
64
+ const savedData = JSON.parse(writeCall[1]);
65
+ const savedTime = new Date(savedData.savedAt);
66
+ expect(savedTime.getTime()).toBeGreaterThanOrEqual(beforeSave.getTime());
67
+ expect(savedTime.getTime()).toBeLessThanOrEqual(afterSave.getTime());
68
+ });
69
+ });
70
+ describe('loadSession', () => {
71
+ it('should return null if file does not exist', () => {
72
+ vi.mocked(fs.existsSync).mockReturnValue(false);
73
+ const result = loadSession();
74
+ expect(result).toBeNull();
75
+ expect(fs.readFileSync).not.toHaveBeenCalled();
76
+ });
77
+ it('should load and return valid session data', () => {
78
+ const sessionData = {
79
+ arbiterSessionId: 'arbiter-123',
80
+ orchestratorSessionId: 'orch-456',
81
+ orchestratorNumber: 2,
82
+ savedAt: new Date().toISOString(),
83
+ };
84
+ vi.mocked(fs.existsSync).mockReturnValue(true);
85
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(sessionData));
86
+ const result = loadSession();
87
+ expect(result).toEqual(sessionData);
88
+ });
89
+ it('should return null for stale session (>24 hours old)', () => {
90
+ const staleTime = new Date(Date.now() - 25 * 60 * 60 * 1000); // 25 hours ago
91
+ const sessionData = {
92
+ arbiterSessionId: 'arbiter-123',
93
+ orchestratorSessionId: null,
94
+ orchestratorNumber: null,
95
+ savedAt: staleTime.toISOString(),
96
+ };
97
+ vi.mocked(fs.existsSync).mockReturnValue(true);
98
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(sessionData));
99
+ const result = loadSession();
100
+ expect(result).toBeNull();
101
+ });
102
+ it('should return session at exactly 24 hours', () => {
103
+ const exactlyOneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
104
+ const sessionData = {
105
+ arbiterSessionId: 'arbiter-123',
106
+ orchestratorSessionId: null,
107
+ orchestratorNumber: null,
108
+ savedAt: exactlyOneDayAgo.toISOString(),
109
+ };
110
+ vi.mocked(fs.existsSync).mockReturnValue(true);
111
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(sessionData));
112
+ const result = loadSession();
113
+ // At exactly 24 hours, it should still be valid (not > 24 hours)
114
+ expect(result).toEqual(sessionData);
115
+ });
116
+ it('should return null for session missing savedAt', () => {
117
+ const sessionData = {
118
+ arbiterSessionId: 'arbiter-123',
119
+ orchestratorSessionId: null,
120
+ orchestratorNumber: null,
121
+ // savedAt is missing
122
+ };
123
+ vi.mocked(fs.existsSync).mockReturnValue(true);
124
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(sessionData));
125
+ const result = loadSession();
126
+ expect(result).toBeNull();
127
+ });
128
+ it('should return null for invalid JSON', () => {
129
+ vi.mocked(fs.existsSync).mockReturnValue(true);
130
+ vi.mocked(fs.readFileSync).mockReturnValue('not valid json');
131
+ const result = loadSession();
132
+ expect(result).toBeNull();
133
+ });
134
+ it('should return null on read error', () => {
135
+ vi.mocked(fs.existsSync).mockReturnValue(true);
136
+ vi.mocked(fs.readFileSync).mockImplementation(() => {
137
+ throw new Error('Read error');
138
+ });
139
+ const result = loadSession();
140
+ expect(result).toBeNull();
141
+ });
142
+ });
143
+ describe('clearSession', () => {
144
+ it('should delete file if it exists', () => {
145
+ vi.mocked(fs.existsSync).mockReturnValue(true);
146
+ vi.mocked(fs.unlinkSync).mockReturnValue(undefined);
147
+ clearSession();
148
+ expect(fs.existsSync).toHaveBeenCalledWith(mockSessionPath);
149
+ expect(fs.unlinkSync).toHaveBeenCalledWith(mockSessionPath);
150
+ });
151
+ it('should not attempt delete if file does not exist', () => {
152
+ vi.mocked(fs.existsSync).mockReturnValue(false);
153
+ clearSession();
154
+ expect(fs.unlinkSync).not.toHaveBeenCalled();
155
+ });
156
+ it('should silently handle delete errors', () => {
157
+ vi.mocked(fs.existsSync).mockReturnValue(true);
158
+ vi.mocked(fs.unlinkSync).mockImplementation(() => {
159
+ throw new Error('Delete error');
160
+ });
161
+ // Should not throw
162
+ expect(() => clearSession()).not.toThrow();
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Sound effects module for the Arbiter TUI
3
+ *
4
+ * Provides simple fire-and-forget sound effect playback.
5
+ * Uses play-sound package for cross-platform support (Linux, Mac, Windows).
6
+ */
7
+ /**
8
+ * Sound effect filename mappings
9
+ */
10
+ export declare const SFX: {
11
+ readonly footstep: "footstep.wav";
12
+ readonly jump: "jump.wav";
13
+ readonly magic: "magic.wav";
14
+ readonly death: "death.wav";
15
+ readonly menuLeft: "menu-left.wav";
16
+ readonly menuRight: "menu-right.wav";
17
+ readonly menuSelect: "menu-select.wav";
18
+ readonly quickNotice: "quick-notice.wav";
19
+ };
20
+ /**
21
+ * Type for valid sound effect names
22
+ */
23
+ export type SfxName = keyof typeof SFX;
24
+ /**
25
+ * Play a sound effect by name
26
+ *
27
+ * Fire-and-forget: does not await, catches errors silently
28
+ *
29
+ * @param name - The name of the sound effect to play
30
+ */
31
+ export declare function playSfx(name: SfxName): void;
package/dist/sound.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Sound effects module for the Arbiter TUI
3
+ *
4
+ * Provides simple fire-and-forget sound effect playback.
5
+ * Uses play-sound package for cross-platform support (Linux, Mac, Windows).
6
+ */
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import playSound from 'play-sound';
10
+ // Get package root directory (works when installed globally or locally)
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const PACKAGE_ROOT = path.resolve(__dirname, '..'); // From dist/ to package root
14
+ // Create player instance
15
+ // play-sound auto-detects available audio players:
16
+ // - Mac: afplay
17
+ // - Linux: aplay, mpg123, mpg321, play, mplayer, etc.
18
+ // - Windows: powershell, cmdmp3
19
+ const player = playSound();
20
+ /**
21
+ * Sound effect filename mappings
22
+ */
23
+ export const SFX = {
24
+ footstep: 'footstep.wav',
25
+ jump: 'jump.wav',
26
+ magic: 'magic.wav',
27
+ death: 'death.wav',
28
+ menuLeft: 'menu-left.wav',
29
+ menuRight: 'menu-right.wav',
30
+ menuSelect: 'menu-select.wav',
31
+ quickNotice: 'quick-notice.wav',
32
+ };
33
+ /**
34
+ * Play a sound effect by name
35
+ *
36
+ * Fire-and-forget: does not await, catches errors silently
37
+ *
38
+ * @param name - The name of the sound effect to play
39
+ */
40
+ export function playSfx(name) {
41
+ const filename = SFX[name];
42
+ const filepath = path.join(PACKAGE_ROOT, 'sounds', filename);
43
+ player.play(filepath, (err) => {
44
+ if (err) {
45
+ // Silently ignore errors - sound is non-critical
46
+ // Uncomment below for debugging:
47
+ // console.error(`Failed to play sound "${name}":`, err);
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Message type for conversation log entries
3
+ */
4
+ export interface Message {
5
+ speaker: 'human' | 'arbiter' | string;
6
+ text: string;
7
+ timestamp: Date;
8
+ }
9
+ /**
10
+ * Current orchestrator state
11
+ */
12
+ export interface OrchestratorState {
13
+ id: string;
14
+ sessionId: string;
15
+ number: number;
16
+ contextPercent: number;
17
+ currentTool: string | null;
18
+ toolCallCount: number;
19
+ }
20
+ /**
21
+ * Main application state interface
22
+ */
23
+ export interface AppState {
24
+ mode: 'human_to_arbiter' | 'arbiter_to_orchestrator';
25
+ arbiterSessionId: string | null;
26
+ arbiterContextPercent: number;
27
+ currentOrchestrator: OrchestratorState | null;
28
+ conversationLog: Message[];
29
+ crashCount: number;
30
+ requirementsPath: string | null;
31
+ }
32
+ /**
33
+ * Creates the initial application state
34
+ */
35
+ export declare function createInitialState(): AppState;
36
+ /**
37
+ * Updates the Arbiter's context percentage
38
+ */
39
+ export declare function updateArbiterContext(state: AppState, percent: number): void;
40
+ /**
41
+ * Updates the current Orchestrator's context percentage
42
+ */
43
+ export declare function updateOrchestratorContext(state: AppState, percent: number): void;
44
+ /**
45
+ * Sets the current orchestrator
46
+ */
47
+ export declare function setCurrentOrchestrator(state: AppState, orch: {
48
+ id: string;
49
+ sessionId: string;
50
+ number: number;
51
+ }): void;
52
+ /**
53
+ * Clears the current orchestrator (sets to null)
54
+ */
55
+ export declare function clearCurrentOrchestrator(state: AppState): void;
56
+ /**
57
+ * Sets the routing mode
58
+ */
59
+ export declare function setMode(state: AppState, mode: AppState['mode']): void;
60
+ /**
61
+ * Adds a message to the conversation log
62
+ */
63
+ export declare function addMessage(state: AppState, speaker: string, text: string): void;
64
+ /**
65
+ * Updates the current orchestrator's tool info
66
+ */
67
+ export declare function updateOrchestratorTool(state: AppState, tool: string | null, count: number): void;
68
+ /**
69
+ * Converts a number to Roman numerals
70
+ * Supports numbers 1-3999
71
+ */
72
+ export declare function toRoman(num: number): string;
package/dist/state.js ADDED
@@ -0,0 +1,107 @@
1
+ // Global state management for the Arbiter system
2
+ // Tracks agent states, conversation history, and system configuration
3
+ /**
4
+ * Creates the initial application state
5
+ */
6
+ export function createInitialState() {
7
+ return {
8
+ mode: 'human_to_arbiter',
9
+ arbiterSessionId: null,
10
+ arbiterContextPercent: 0,
11
+ currentOrchestrator: null,
12
+ conversationLog: [],
13
+ crashCount: 0,
14
+ requirementsPath: null,
15
+ };
16
+ }
17
+ /**
18
+ * Updates the Arbiter's context percentage
19
+ */
20
+ export function updateArbiterContext(state, percent) {
21
+ state.arbiterContextPercent = percent;
22
+ }
23
+ /**
24
+ * Updates the current Orchestrator's context percentage
25
+ */
26
+ export function updateOrchestratorContext(state, percent) {
27
+ if (state.currentOrchestrator) {
28
+ state.currentOrchestrator.contextPercent = percent;
29
+ }
30
+ }
31
+ /**
32
+ * Sets the current orchestrator
33
+ */
34
+ export function setCurrentOrchestrator(state, orch) {
35
+ state.currentOrchestrator = {
36
+ id: orch.id,
37
+ sessionId: orch.sessionId,
38
+ number: orch.number,
39
+ contextPercent: 0,
40
+ currentTool: null,
41
+ toolCallCount: 0,
42
+ };
43
+ }
44
+ /**
45
+ * Clears the current orchestrator (sets to null)
46
+ */
47
+ export function clearCurrentOrchestrator(state) {
48
+ state.currentOrchestrator = null;
49
+ }
50
+ /**
51
+ * Sets the routing mode
52
+ */
53
+ export function setMode(state, mode) {
54
+ state.mode = mode;
55
+ }
56
+ /**
57
+ * Adds a message to the conversation log
58
+ */
59
+ export function addMessage(state, speaker, text) {
60
+ state.conversationLog.push({
61
+ speaker,
62
+ text,
63
+ timestamp: new Date(),
64
+ });
65
+ }
66
+ /**
67
+ * Updates the current orchestrator's tool info
68
+ */
69
+ export function updateOrchestratorTool(state, tool, count) {
70
+ if (state.currentOrchestrator) {
71
+ state.currentOrchestrator.currentTool = tool;
72
+ state.currentOrchestrator.toolCallCount = count;
73
+ }
74
+ }
75
+ /**
76
+ * Converts a number to Roman numerals
77
+ * Supports numbers 1-3999
78
+ */
79
+ export function toRoman(num) {
80
+ if (num < 1 || num > 3999) {
81
+ throw new Error('Number must be between 1 and 3999');
82
+ }
83
+ const romanNumerals = [
84
+ [1000, 'M'],
85
+ [900, 'CM'],
86
+ [500, 'D'],
87
+ [400, 'CD'],
88
+ [100, 'C'],
89
+ [90, 'XC'],
90
+ [50, 'L'],
91
+ [40, 'XL'],
92
+ [10, 'X'],
93
+ [9, 'IX'],
94
+ [5, 'V'],
95
+ [4, 'IV'],
96
+ [1, 'I'],
97
+ ];
98
+ let result = '';
99
+ let remaining = num;
100
+ for (const [value, symbol] of romanNumerals) {
101
+ while (remaining >= value) {
102
+ result += symbol;
103
+ remaining -= value;
104
+ }
105
+ }
106
+ return result;
107
+ }
@@ -0,0 +1 @@
1
+ export {};