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.
- package/README.md +41 -0
- package/assets/jerom_16x16.png +0 -0
- package/dist/arbiter.d.ts +43 -0
- package/dist/arbiter.js +486 -0
- package/dist/context-analyzer.d.ts +15 -0
- package/dist/context-analyzer.js +603 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +165 -0
- package/dist/orchestrator.d.ts +31 -0
- package/dist/orchestrator.js +227 -0
- package/dist/router.d.ts +187 -0
- package/dist/router.js +1135 -0
- package/dist/router.test.d.ts +15 -0
- package/dist/router.test.js +95 -0
- package/dist/session-persistence.d.ts +9 -0
- package/dist/session-persistence.js +63 -0
- package/dist/session-persistence.test.d.ts +1 -0
- package/dist/session-persistence.test.js +165 -0
- package/dist/sound.d.ts +31 -0
- package/dist/sound.js +50 -0
- package/dist/state.d.ts +72 -0
- package/dist/state.js +107 -0
- package/dist/state.test.d.ts +1 -0
- package/dist/state.test.js +194 -0
- package/dist/test-headless.d.ts +1 -0
- package/dist/test-headless.js +155 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.js +17 -0
- package/dist/tui/layout.d.ts +30 -0
- package/dist/tui/layout.js +200 -0
- package/dist/tui/render.d.ts +57 -0
- package/dist/tui/render.js +266 -0
- package/dist/tui/scene.d.ts +64 -0
- package/dist/tui/scene.js +366 -0
- package/dist/tui/screens/CharacterSelect-termkit.d.ts +18 -0
- package/dist/tui/screens/CharacterSelect-termkit.js +216 -0
- package/dist/tui/screens/ForestIntro-termkit.d.ts +15 -0
- package/dist/tui/screens/ForestIntro-termkit.js +856 -0
- package/dist/tui/screens/GitignoreCheck-termkit.d.ts +14 -0
- package/dist/tui/screens/GitignoreCheck-termkit.js +185 -0
- package/dist/tui/screens/TitleScreen-termkit.d.ts +14 -0
- package/dist/tui/screens/TitleScreen-termkit.js +132 -0
- package/dist/tui/screens/index.d.ts +9 -0
- package/dist/tui/screens/index.js +10 -0
- package/dist/tui/tileset.d.ts +97 -0
- package/dist/tui/tileset.js +237 -0
- package/dist/tui/tui-termkit.d.ts +34 -0
- package/dist/tui/tui-termkit.js +2602 -0
- package/dist/tui/types.d.ts +41 -0
- package/dist/tui/types.js +4 -0
- 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
|
+
});
|
package/dist/sound.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/state.d.ts
ADDED
|
@@ -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 {};
|