arbiter-ai 1.0.3 → 1.0.5
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/assets/sounds/drop.wav +0 -0
- package/dist/index.js +161 -1
- package/dist/router.d.ts +5 -6
- package/dist/router.js +90 -159
- package/dist/tui/animation-loop.d.ts +65 -0
- package/dist/tui/animation-loop.js +123 -0
- package/dist/tui/callbacks.d.ts +52 -0
- package/dist/tui/callbacks.js +164 -0
- package/dist/tui/logViewer.d.ts +36 -0
- package/dist/tui/logViewer.js +158 -0
- package/dist/tui/requirementsOverlay.d.ts +64 -0
- package/dist/tui/requirementsOverlay.js +536 -0
- package/dist/tui/scene.d.ts +37 -38
- package/dist/tui/scene.js +77 -156
- package/dist/tui/screens/CharacterSelect-termkit.js +2 -3
- package/dist/tui/screens/ForestIntro-termkit.js +43 -27
- package/dist/tui/screens/GitignoreCheck-termkit.js +2 -3
- package/dist/tui/screens/TitleScreen-termkit.js +2 -3
- package/dist/tui/sprite.d.ts +251 -0
- package/dist/tui/sprite.js +421 -0
- package/dist/tui/terminal-cleanup.d.ts +19 -0
- package/dist/tui/terminal-cleanup.js +39 -0
- package/dist/tui/tui-termkit.js +275 -1189
- package/package.json +1 -1
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Main entry point for the Arbiter system
|
|
3
3
|
// Ties together state, router, and TUI for the hierarchical AI orchestration system
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import { Router } from './router.js';
|
|
5
6
|
import { loadSession } from './session-persistence.js';
|
|
6
7
|
import { createInitialState } from './state.js';
|
|
7
8
|
import { checkGitignore, createTUI, showCharacterSelect, showForestIntro, showTitleScreen, } from './tui/index.js';
|
|
9
|
+
import { getAllSprites } from './tui/animation-loop.js';
|
|
10
|
+
import { TILE } from './tui/tileset.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get package.json version
|
|
13
|
+
*/
|
|
14
|
+
function getVersion() {
|
|
15
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
return pkg.version;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Print help message and exit
|
|
21
|
+
*/
|
|
22
|
+
function printHelp() {
|
|
23
|
+
console.log(`
|
|
24
|
+
arbiter - Hierarchical AI orchestration system
|
|
25
|
+
|
|
26
|
+
Consult with the Arbiter, a wise overseer who commands a
|
|
27
|
+
council of Orchestrators to tackle complex tasks. Each layer
|
|
28
|
+
extends Claude's context, keeping the work on track. Bring
|
|
29
|
+
a detailed markdown description of your requirements.
|
|
30
|
+
|
|
31
|
+
USAGE
|
|
32
|
+
arbiter [options] [requirements-file]
|
|
33
|
+
|
|
34
|
+
OPTIONS
|
|
35
|
+
-h, --help Show this help message
|
|
36
|
+
-v, --version Show version number
|
|
37
|
+
--resume Resume from saved session (if <24h old)
|
|
38
|
+
--demo-animations Run animation demo (skip intro screens and router)
|
|
39
|
+
|
|
40
|
+
EXAMPLES
|
|
41
|
+
arbiter Start fresh session
|
|
42
|
+
arbiter ./SPEC.md Start with requirements file (skip in-game prompt)
|
|
43
|
+
arbiter --resume Resume previous session
|
|
44
|
+
arbiter --demo-animations Run animation demo
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
8
47
|
/**
|
|
9
48
|
* Outputs session information to stderr for resume capability
|
|
10
49
|
* Format per architecture doc:
|
|
@@ -19,6 +58,89 @@ function outputSessionInfo(state) {
|
|
|
19
58
|
// Output to stderr so it doesn't interfere with TUI output
|
|
20
59
|
process.stderr.write(`${JSON.stringify(sessionInfo)}\n`);
|
|
21
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Helper function to create a delay promise
|
|
63
|
+
*/
|
|
64
|
+
function delay(ms) {
|
|
65
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Runs a demo sequence showing all animations
|
|
69
|
+
* Used when --demo-animations flag is passed
|
|
70
|
+
*/
|
|
71
|
+
async function runDemoSequence() {
|
|
72
|
+
// Wait for TUI to fully initialize and sprites to be registered
|
|
73
|
+
await delay(1500);
|
|
74
|
+
// Get all registered sprites
|
|
75
|
+
const sprites = getAllSprites();
|
|
76
|
+
const human = sprites.find((s) => s.id === 'human');
|
|
77
|
+
const arbiter = sprites.find((s) => s.id === 'arbiter');
|
|
78
|
+
const scroll = sprites.find((s) => s.id === 'scroll');
|
|
79
|
+
const spellbook = sprites.find((s) => s.id === 'spellbook');
|
|
80
|
+
const smoke = sprites.find((s) => s.id === 'smoke');
|
|
81
|
+
const demons = sprites.filter((s) => s.id.startsWith('demon-'));
|
|
82
|
+
if (!human || !arbiter || !scroll || !spellbook || !smoke) {
|
|
83
|
+
process.stderr.write('Demo: Failed to find required sprites\n');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
// Demo entrance sequence - human walks in
|
|
87
|
+
await human.walk({ row: 2, col: 1 });
|
|
88
|
+
await delay(300);
|
|
89
|
+
// Human hops (surprised)
|
|
90
|
+
await human.hop(2);
|
|
91
|
+
await delay(300);
|
|
92
|
+
// Arbiter hops (notices visitor)
|
|
93
|
+
await arbiter.hop(2);
|
|
94
|
+
await delay(500);
|
|
95
|
+
// Demo scroll drop
|
|
96
|
+
await scroll.physicalSpawn();
|
|
97
|
+
await delay(500);
|
|
98
|
+
// Demo arbiter walks to scroll
|
|
99
|
+
await arbiter.walk({ row: 2, col: 3 });
|
|
100
|
+
await delay(300);
|
|
101
|
+
// Arbiter notices scroll (alarmed)
|
|
102
|
+
await arbiter.alarmed(1500);
|
|
103
|
+
await delay(500);
|
|
104
|
+
// Demo summon sequence - arbiter walks to fire for summoning
|
|
105
|
+
await arbiter.walk({ row: 3, col: 4 });
|
|
106
|
+
await delay(300);
|
|
107
|
+
// Spellbook appears
|
|
108
|
+
await spellbook.physicalSpawn();
|
|
109
|
+
await delay(500);
|
|
110
|
+
// Demo cauldron bubbling
|
|
111
|
+
smoke.startBubbling();
|
|
112
|
+
await delay(1000);
|
|
113
|
+
// Spawn demons one by one with magic effect
|
|
114
|
+
for (let i = 0; i < Math.min(3, demons.length); i++) {
|
|
115
|
+
await demons[i].magicSpawn();
|
|
116
|
+
await delay(500);
|
|
117
|
+
}
|
|
118
|
+
await delay(1500);
|
|
119
|
+
// Demo hopping while working
|
|
120
|
+
await arbiter.hop(4);
|
|
121
|
+
await delay(1000);
|
|
122
|
+
// Demo dismiss sequence
|
|
123
|
+
for (const demon of demons.filter((d) => d.visible)) {
|
|
124
|
+
await demon.magicDespawn();
|
|
125
|
+
await delay(300);
|
|
126
|
+
}
|
|
127
|
+
// Stop bubbling
|
|
128
|
+
smoke.stopBubbling();
|
|
129
|
+
await delay(500);
|
|
130
|
+
// Hide spellbook
|
|
131
|
+
spellbook.visible = false;
|
|
132
|
+
await delay(500);
|
|
133
|
+
// Walk back
|
|
134
|
+
await arbiter.walk({ row: 2, col: 3 });
|
|
135
|
+
await delay(500);
|
|
136
|
+
// Demo chat indicator
|
|
137
|
+
await arbiter.chatting(2000);
|
|
138
|
+
await delay(500);
|
|
139
|
+
// Demo complete - wait a moment then exit
|
|
140
|
+
await delay(2000);
|
|
141
|
+
// Exit cleanly
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
22
144
|
/**
|
|
23
145
|
* Main application entry point
|
|
24
146
|
* Creates and wires together all components of the Arbiter system
|
|
@@ -64,6 +186,44 @@ async function main() {
|
|
|
64
186
|
try {
|
|
65
187
|
// Parse CLI arguments
|
|
66
188
|
const args = process.argv.slice(2);
|
|
189
|
+
// Handle --help flag (early exit)
|
|
190
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
191
|
+
printHelp();
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
194
|
+
// Handle --version flag (early exit)
|
|
195
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
196
|
+
console.log(getVersion());
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
// Handle --demo-animations flag (early exit)
|
|
200
|
+
const demoMode = args.includes('--demo-animations');
|
|
201
|
+
if (demoMode) {
|
|
202
|
+
// Demo mode: skip all intro screens and router initialization
|
|
203
|
+
// Go straight to main TUI with default character and run scripted animation demo
|
|
204
|
+
state = createInitialState();
|
|
205
|
+
// Set a requirements path to skip the requirements overlay prompt
|
|
206
|
+
state.requirementsPath = '/dev/null';
|
|
207
|
+
// Create TUI with default character
|
|
208
|
+
tui = createTUI(state, TILE.HUMAN_1);
|
|
209
|
+
// Wire TUI exit to shutdown
|
|
210
|
+
tui.onExit(() => {
|
|
211
|
+
shutdown(0);
|
|
212
|
+
});
|
|
213
|
+
// Start TUI (takes over terminal)
|
|
214
|
+
tui.start();
|
|
215
|
+
// Fire-and-forget the requirements ready callback (skips requirement selection)
|
|
216
|
+
tui.onRequirementsReady(() => {
|
|
217
|
+
// No-op since we're not starting the router
|
|
218
|
+
});
|
|
219
|
+
// Run demo sequence after TUI initializes
|
|
220
|
+
runDemoSequence();
|
|
221
|
+
// Keep the process running until demo completes
|
|
222
|
+
await new Promise(() => {
|
|
223
|
+
// This promise never resolves - demo will exit via process.exit(0)
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
67
227
|
const shouldResume = args.includes('--resume');
|
|
68
228
|
// Handle --resume flag
|
|
69
229
|
let savedSession = null;
|
|
@@ -74,7 +234,7 @@ async function main() {
|
|
|
74
234
|
}
|
|
75
235
|
}
|
|
76
236
|
// Check for positional requirements file argument (first non-flag arg)
|
|
77
|
-
const positionalArgs = args.filter((arg) => !arg.startsWith('--'));
|
|
237
|
+
const positionalArgs = args.filter((arg) => !arg.startsWith('--') && !arg.startsWith('-'));
|
|
78
238
|
const cliRequirementsFile = positionalArgs[0] || null;
|
|
79
239
|
let selectedCharacter;
|
|
80
240
|
if (savedSession) {
|
package/dist/router.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export declare class Router {
|
|
|
63
63
|
private watchdogInterval;
|
|
64
64
|
private arbiterMcpServer;
|
|
65
65
|
private arbiterHooks;
|
|
66
|
+
private arbiterPollContext;
|
|
66
67
|
private contextPollInterval;
|
|
67
68
|
private crashCount;
|
|
68
69
|
constructor(state: AppState, callbacks: RouterCallbacks);
|
|
@@ -77,7 +78,7 @@ export declare class Router {
|
|
|
77
78
|
private startContextPolling;
|
|
78
79
|
/**
|
|
79
80
|
* Poll context for all active sessions
|
|
80
|
-
*
|
|
81
|
+
* Uses paired pollers that share the same options as the sessions
|
|
81
82
|
*/
|
|
82
83
|
private pollAllContexts;
|
|
83
84
|
/**
|
|
@@ -127,17 +128,15 @@ export declare class Router {
|
|
|
127
128
|
private createOrchestratorOptions;
|
|
128
129
|
/**
|
|
129
130
|
* Creates and starts the Arbiter session with MCP tools
|
|
131
|
+
* @param resumeSessionId - Optional session ID for resuming an existing session
|
|
130
132
|
*/
|
|
131
133
|
private startArbiterSession;
|
|
132
134
|
/**
|
|
133
135
|
* Creates and starts an Orchestrator session
|
|
136
|
+
* @param number - The orchestrator number (I, II, III...)
|
|
137
|
+
* @param resumeSessionId - Optional session ID for resuming an existing session
|
|
134
138
|
*/
|
|
135
139
|
private startOrchestratorSession;
|
|
136
|
-
/**
|
|
137
|
-
* Resume an existing Orchestrator session
|
|
138
|
-
* Similar to startOrchestratorSession but uses resume option and skips introduction
|
|
139
|
-
*/
|
|
140
|
-
private resumeOrchestratorSession;
|
|
141
140
|
/**
|
|
142
141
|
* Send a message to the Arbiter
|
|
143
142
|
*/
|
package/dist/router.js
CHANGED
|
@@ -11,6 +11,49 @@ function sleep(ms) {
|
|
|
11
11
|
// Retry constants for crash recovery
|
|
12
12
|
const MAX_RETRIES = 3;
|
|
13
13
|
const RETRY_DELAYS = [1000, 2000, 4000]; // 1s, 2s, 4s exponential backoff
|
|
14
|
+
/**
|
|
15
|
+
* Creates a query and a paired context poller that uses the same options.
|
|
16
|
+
* This ensures context polling always matches the session's actual configuration.
|
|
17
|
+
*
|
|
18
|
+
* @param prompt - Initial prompt for the session
|
|
19
|
+
* @param options - Options used to create the session (will also be used for polling)
|
|
20
|
+
* @returns Tuple of [query generator, context polling function]
|
|
21
|
+
*/
|
|
22
|
+
function createQueryWithPoller(prompt, options) {
|
|
23
|
+
const q = query({ prompt, options });
|
|
24
|
+
const pollContext = async (sessionId) => {
|
|
25
|
+
try {
|
|
26
|
+
const pollQuery = query({
|
|
27
|
+
prompt: '/context',
|
|
28
|
+
options: {
|
|
29
|
+
...options,
|
|
30
|
+
resume: sessionId,
|
|
31
|
+
forkSession: true, // Fork to avoid polluting main session
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
let percent = null;
|
|
35
|
+
for await (const msg of pollQuery) {
|
|
36
|
+
// /context output comes through as user message with the token info
|
|
37
|
+
if (msg.type === 'user') {
|
|
38
|
+
const content = msg.message?.content;
|
|
39
|
+
if (typeof content === 'string') {
|
|
40
|
+
// Match: **Tokens:** 18.4k / 200.0k (9%)
|
|
41
|
+
const match = content.match(/\*\*Tokens:\*\*\s*([0-9.]+)k\s*\/\s*200\.?0?k\s*\((\d+)%\)/i);
|
|
42
|
+
if (match) {
|
|
43
|
+
percent = parseInt(match[2], 10);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return percent;
|
|
49
|
+
}
|
|
50
|
+
catch (_error) {
|
|
51
|
+
// Silently fail - context polling is best-effort
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return [q, pollContext];
|
|
56
|
+
}
|
|
14
57
|
// Arbiter's allowed tools: MCP tools + read-only exploration
|
|
15
58
|
const ARBITER_ALLOWED_TOOLS = [
|
|
16
59
|
'mcp__arbiter-tools__spawn_orchestrator',
|
|
@@ -55,45 +98,6 @@ import { createOrchestratorHooks, ORCHESTRATOR_SYSTEM_PROMPT, } from './orchestr
|
|
|
55
98
|
const MAX_CONTEXT_TOKENS = 200000;
|
|
56
99
|
// Context polling interval (1 minute)
|
|
57
100
|
const CONTEXT_POLL_INTERVAL_MS = 60_000;
|
|
58
|
-
/**
|
|
59
|
-
* Poll context usage by forking a session and running /context
|
|
60
|
-
* Uses forkSession: true to avoid polluting the main conversation
|
|
61
|
-
* Note: This does clutter resume history - no workaround found yet
|
|
62
|
-
*
|
|
63
|
-
* @param sessionId - The session ID to fork and check
|
|
64
|
-
* @returns Context percentage (0-100) or null if polling failed
|
|
65
|
-
*/
|
|
66
|
-
async function pollContextForSession(sessionId) {
|
|
67
|
-
try {
|
|
68
|
-
const q = query({
|
|
69
|
-
prompt: '/context',
|
|
70
|
-
options: {
|
|
71
|
-
resume: sessionId,
|
|
72
|
-
forkSession: true, // Fork to avoid polluting main session
|
|
73
|
-
permissionMode: 'bypassPermissions',
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
let percent = null;
|
|
77
|
-
for await (const msg of q) {
|
|
78
|
-
// /context output comes through as user message with the token info
|
|
79
|
-
if (msg.type === 'user') {
|
|
80
|
-
const content = msg.message?.content;
|
|
81
|
-
if (typeof content === 'string') {
|
|
82
|
-
// Match: **Tokens:** 18.4k / 200.0k (9%)
|
|
83
|
-
const match = content.match(/\*\*Tokens:\*\*\s*([0-9.]+)k\s*\/\s*200\.?0?k\s*\((\d+)%\)/i);
|
|
84
|
-
if (match) {
|
|
85
|
-
percent = parseInt(match[2], 10);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return percent;
|
|
91
|
-
}
|
|
92
|
-
catch (_error) {
|
|
93
|
-
// Silently fail - context polling is best-effort
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
101
|
/**
|
|
98
102
|
* Formats an SDK message for debug logging
|
|
99
103
|
* Returns a human-readable string representation of the message
|
|
@@ -263,6 +267,8 @@ export class Router {
|
|
|
263
267
|
arbiterMcpServer = null;
|
|
264
268
|
// Store Arbiter hooks for session resumption
|
|
265
269
|
arbiterHooks = null;
|
|
270
|
+
// Paired context poller for Arbiter (uses same options as session)
|
|
271
|
+
arbiterPollContext = null;
|
|
266
272
|
// Context polling timer - polls /context once per minute via session forking
|
|
267
273
|
contextPollInterval = null;
|
|
268
274
|
// Track crash recovery attempts for TUI display
|
|
@@ -295,12 +301,12 @@ export class Router {
|
|
|
295
301
|
}
|
|
296
302
|
/**
|
|
297
303
|
* Poll context for all active sessions
|
|
298
|
-
*
|
|
304
|
+
* Uses paired pollers that share the same options as the sessions
|
|
299
305
|
*/
|
|
300
306
|
async pollAllContexts() {
|
|
301
|
-
// Poll Arbiter context
|
|
302
|
-
if (this.state.arbiterSessionId) {
|
|
303
|
-
const arbiterPercent = await
|
|
307
|
+
// Poll Arbiter context using paired poller
|
|
308
|
+
if (this.state.arbiterSessionId && this.arbiterPollContext) {
|
|
309
|
+
const arbiterPercent = await this.arbiterPollContext(this.state.arbiterSessionId);
|
|
304
310
|
if (arbiterPercent !== null) {
|
|
305
311
|
updateArbiterContext(this.state, arbiterPercent);
|
|
306
312
|
this.callbacks.onDebugLog?.({
|
|
@@ -310,10 +316,11 @@ export class Router {
|
|
|
310
316
|
});
|
|
311
317
|
}
|
|
312
318
|
}
|
|
313
|
-
// Poll Orchestrator context
|
|
319
|
+
// Poll Orchestrator context using paired poller
|
|
314
320
|
let orchPercent = null;
|
|
315
|
-
|
|
316
|
-
|
|
321
|
+
const orchSession = this.currentOrchestratorSession;
|
|
322
|
+
if (orchSession?.sessionId && orchSession.pollContext) {
|
|
323
|
+
orchPercent = await orchSession.pollContext(orchSession.sessionId);
|
|
317
324
|
if (orchPercent !== null) {
|
|
318
325
|
updateOrchestratorContext(this.state, orchPercent);
|
|
319
326
|
this.callbacks.onDebugLog?.({
|
|
@@ -330,13 +337,11 @@ export class Router {
|
|
|
330
337
|
* Resume from a previously saved session
|
|
331
338
|
*/
|
|
332
339
|
async resumeFromSavedSession(saved) {
|
|
333
|
-
//
|
|
334
|
-
this.
|
|
335
|
-
// Start arbiter (it will use the session ID for resume)
|
|
336
|
-
await this.startArbiterSession();
|
|
340
|
+
// Start arbiter with resume session ID
|
|
341
|
+
await this.startArbiterSession(saved.arbiterSessionId);
|
|
337
342
|
// If there was an active orchestrator, resume it too
|
|
338
343
|
if (saved.orchestratorSessionId && saved.orchestratorNumber) {
|
|
339
|
-
await this.
|
|
344
|
+
await this.startOrchestratorSession(saved.orchestratorNumber, saved.orchestratorSessionId);
|
|
340
345
|
}
|
|
341
346
|
// Start context polling
|
|
342
347
|
this.startContextPolling();
|
|
@@ -488,6 +493,7 @@ export class Router {
|
|
|
488
493
|
hooks: this.arbiterHooks ?? undefined,
|
|
489
494
|
abortController: this.arbiterAbortController ?? new AbortController(),
|
|
490
495
|
permissionMode: 'bypassPermissions',
|
|
496
|
+
settingSources: ['project'], // Load CLAUDE.md for project context
|
|
491
497
|
allowedTools: [...ARBITER_ALLOWED_TOOLS],
|
|
492
498
|
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
493
499
|
};
|
|
@@ -505,6 +511,7 @@ export class Router {
|
|
|
505
511
|
hooks,
|
|
506
512
|
abortController,
|
|
507
513
|
permissionMode: 'bypassPermissions',
|
|
514
|
+
settingSources: ['project'], // Load CLAUDE.md for project context
|
|
508
515
|
allowedTools: [...ORCHESTRATOR_ALLOWED_TOOLS],
|
|
509
516
|
outputFormat: {
|
|
510
517
|
type: 'json_schema',
|
|
@@ -515,8 +522,9 @@ export class Router {
|
|
|
515
522
|
}
|
|
516
523
|
/**
|
|
517
524
|
* Creates and starts the Arbiter session with MCP tools
|
|
525
|
+
* @param resumeSessionId - Optional session ID for resuming an existing session
|
|
518
526
|
*/
|
|
519
|
-
async startArbiterSession() {
|
|
527
|
+
async startArbiterSession(resumeSessionId) {
|
|
520
528
|
// Notify that we're waiting for Arbiter
|
|
521
529
|
this.callbacks.onWaitingStart?.('arbiter');
|
|
522
530
|
// Create abort controller for this session
|
|
@@ -554,12 +562,12 @@ export class Router {
|
|
|
554
562
|
const hooks = createArbiterHooks(arbiterHooksCallbacks);
|
|
555
563
|
this.arbiterHooks = hooks;
|
|
556
564
|
// Create options using helper
|
|
557
|
-
const options = this.createArbiterOptions(
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
565
|
+
const options = this.createArbiterOptions(resumeSessionId);
|
|
566
|
+
// Choose prompt based on whether resuming or starting fresh
|
|
567
|
+
const prompt = resumeSessionId
|
|
568
|
+
? '[System: Session resumed. Continue where you left off.]'
|
|
569
|
+
: this.state.requirementsPath
|
|
570
|
+
? `@${this.state.requirementsPath}
|
|
563
571
|
|
|
564
572
|
A Scroll of Requirements has been presented.
|
|
565
573
|
|
|
@@ -578,23 +586,28 @@ Your task now is to achieve COMPLETE UNDERSTANDING before any work begins. This
|
|
|
578
586
|
Only when we have achieved 100% alignment on vision, scope, and approach - only when you could explain this task to an Orchestrator with complete confidence - only then do we proceed.
|
|
579
587
|
|
|
580
588
|
Take your time. This phase determines everything that follows.`
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
589
|
+
: 'Speak, mortal.';
|
|
590
|
+
const [arbiterQuery, pollContext] = createQueryWithPoller(prompt, options);
|
|
591
|
+
this.arbiterQuery = arbiterQuery;
|
|
592
|
+
this.arbiterPollContext = pollContext;
|
|
593
|
+
// Store session ID if resuming (will be updated from init message if new)
|
|
594
|
+
if (resumeSessionId) {
|
|
595
|
+
this.state.arbiterSessionId = resumeSessionId;
|
|
596
|
+
}
|
|
586
597
|
// Process the initial response
|
|
587
598
|
await this.processArbiterMessages(this.arbiterQuery);
|
|
588
599
|
}
|
|
589
600
|
/**
|
|
590
601
|
* Creates and starts an Orchestrator session
|
|
602
|
+
* @param number - The orchestrator number (I, II, III...)
|
|
603
|
+
* @param resumeSessionId - Optional session ID for resuming an existing session
|
|
591
604
|
*/
|
|
592
|
-
async startOrchestratorSession(number) {
|
|
593
|
-
// Clean up any existing orchestrator before spawning
|
|
605
|
+
async startOrchestratorSession(number, resumeSessionId) {
|
|
606
|
+
// Clean up any existing orchestrator before spawning/resuming
|
|
594
607
|
this.cleanupOrchestrator();
|
|
595
608
|
// Notify that we're waiting for Orchestrator
|
|
596
609
|
this.callbacks.onWaitingStart?.('orchestrator');
|
|
597
|
-
//
|
|
610
|
+
// Set orchestrator count
|
|
598
611
|
this.orchestratorCount = number;
|
|
599
612
|
// Generate unique ID for this orchestrator
|
|
600
613
|
const orchId = `orch-${Date.now()}`;
|
|
@@ -630,113 +643,31 @@ Take your time. This phase determines everything that follows.`
|
|
|
630
643
|
const hooks = createOrchestratorHooks(orchestratorCallbacks,
|
|
631
644
|
// Context percent getter - returns state value (updated by polling)
|
|
632
645
|
(_sessionId) => this.state.currentOrchestrator?.contextPercent || 0);
|
|
633
|
-
// Create options using helper
|
|
634
|
-
const options = this.createOrchestratorOptions(hooks, abortController);
|
|
635
|
-
//
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
this.currentOrchestratorSession = {
|
|
642
|
-
id: orchId,
|
|
643
|
-
number,
|
|
644
|
-
sessionId: '', // Will be set when we get the init message
|
|
645
|
-
query: orchestratorQuery,
|
|
646
|
-
abortController,
|
|
647
|
-
toolCallCount: 0,
|
|
648
|
-
queue: [],
|
|
649
|
-
lastActivityTime: Date.now(),
|
|
650
|
-
hooks,
|
|
651
|
-
};
|
|
652
|
-
// Set up TUI-facing orchestrator state before processing
|
|
653
|
-
// We'll update the session ID when we get the init message
|
|
654
|
-
setCurrentOrchestrator(this.state, {
|
|
655
|
-
id: orchId,
|
|
656
|
-
sessionId: '', // Will be set when we get the init message
|
|
657
|
-
number,
|
|
658
|
-
});
|
|
659
|
-
// Switch mode
|
|
660
|
-
setMode(this.state, 'arbiter_to_orchestrator');
|
|
661
|
-
this.callbacks.onModeChange('arbiter_to_orchestrator');
|
|
662
|
-
// Notify about orchestrator spawn (for tile scene demon spawning)
|
|
663
|
-
this.callbacks.onOrchestratorSpawn?.(number);
|
|
664
|
-
// Update context display to show orchestrator (initially at 0%)
|
|
665
|
-
this.callbacks.onContextUpdate(this.state.arbiterContextPercent, this.state.currentOrchestrator?.contextPercent ?? null);
|
|
666
|
-
// Start watchdog timer
|
|
667
|
-
this.startWatchdog();
|
|
668
|
-
// Process orchestrator messages
|
|
669
|
-
await this.processOrchestratorMessages(this.currentOrchestratorSession.query);
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Resume an existing Orchestrator session
|
|
673
|
-
* Similar to startOrchestratorSession but uses resume option and skips introduction
|
|
674
|
-
*/
|
|
675
|
-
async resumeOrchestratorSession(sessionId, number) {
|
|
676
|
-
// Clean up any existing orchestrator before resuming
|
|
677
|
-
this.cleanupOrchestrator();
|
|
678
|
-
// Notify that we're waiting for Orchestrator
|
|
679
|
-
this.callbacks.onWaitingStart?.('orchestrator');
|
|
680
|
-
// Restore orchestrator count
|
|
681
|
-
this.orchestratorCount = number;
|
|
682
|
-
// Generate unique ID for this orchestrator
|
|
683
|
-
const orchId = `orch-${Date.now()}`;
|
|
684
|
-
// Create abort controller for this session
|
|
685
|
-
const abortController = new AbortController();
|
|
686
|
-
// Create callbacks for hooks
|
|
687
|
-
const orchestratorCallbacks = {
|
|
688
|
-
onContextUpdate: (_sessionId, _percent) => {
|
|
689
|
-
// Context is now tracked via periodic polling (pollAllContexts)
|
|
690
|
-
// This callback exists for hook compatibility but is unused
|
|
691
|
-
},
|
|
692
|
-
onToolUse: (tool) => {
|
|
693
|
-
// Increment tool count on the session
|
|
694
|
-
if (this.currentOrchestratorSession) {
|
|
695
|
-
this.currentOrchestratorSession.toolCallCount++;
|
|
696
|
-
const newCount = this.currentOrchestratorSession.toolCallCount;
|
|
697
|
-
// Update state and notify callback
|
|
698
|
-
updateOrchestratorTool(this.state, tool, newCount);
|
|
699
|
-
this.callbacks.onToolUse(tool, newCount);
|
|
700
|
-
// Log tool use to debug (logbook) with orchestrator context
|
|
701
|
-
const conjuringLabel = `Conjuring ${toRoman(number)}`;
|
|
702
|
-
this.callbacks.onDebugLog?.({
|
|
703
|
-
type: 'tool',
|
|
704
|
-
speaker: conjuringLabel,
|
|
705
|
-
text: `[Tool] ${tool}`,
|
|
706
|
-
details: { tool, count: newCount },
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
},
|
|
710
|
-
};
|
|
711
|
-
// Create hooks for tool use tracking
|
|
712
|
-
// Context is now tracked via polling, not hooks
|
|
713
|
-
const hooks = createOrchestratorHooks(orchestratorCallbacks,
|
|
714
|
-
// Context percent getter - returns state value (updated by polling)
|
|
715
|
-
(_sessionId) => this.state.currentOrchestrator?.contextPercent || 0);
|
|
716
|
-
// Create options using helper with resume
|
|
717
|
-
const options = this.createOrchestratorOptions(hooks, abortController, sessionId);
|
|
718
|
-
// Create the orchestrator query with a continuation prompt (not introduction)
|
|
719
|
-
const orchestratorQuery = query({
|
|
720
|
-
prompt: '[System: Session resumed. Continue where you left off.]',
|
|
721
|
-
options,
|
|
722
|
-
});
|
|
646
|
+
// Create options using helper
|
|
647
|
+
const options = this.createOrchestratorOptions(hooks, abortController, resumeSessionId);
|
|
648
|
+
// Choose prompt based on whether resuming or starting fresh
|
|
649
|
+
const prompt = resumeSessionId
|
|
650
|
+
? '[System: Session resumed. Continue where you left off.]'
|
|
651
|
+
: 'Introduce yourself and await instructions from the Arbiter.';
|
|
652
|
+
// Create the orchestrator query with paired context poller
|
|
653
|
+
const [orchestratorQuery, pollContext] = createQueryWithPoller(prompt, options);
|
|
723
654
|
// Create the full OrchestratorSession object
|
|
724
|
-
// Note: sessionId is already known from the saved session
|
|
725
655
|
this.currentOrchestratorSession = {
|
|
726
656
|
id: orchId,
|
|
727
657
|
number,
|
|
728
|
-
sessionId:
|
|
658
|
+
sessionId: resumeSessionId ?? '', // Known if resuming, will be set from init message if new
|
|
729
659
|
query: orchestratorQuery,
|
|
730
660
|
abortController,
|
|
731
661
|
toolCallCount: 0,
|
|
732
662
|
queue: [],
|
|
733
663
|
lastActivityTime: Date.now(),
|
|
734
664
|
hooks,
|
|
665
|
+
pollContext,
|
|
735
666
|
};
|
|
736
667
|
// Set up TUI-facing orchestrator state
|
|
737
668
|
setCurrentOrchestrator(this.state, {
|
|
738
669
|
id: orchId,
|
|
739
|
-
sessionId:
|
|
670
|
+
sessionId: resumeSessionId ?? '',
|
|
740
671
|
number,
|
|
741
672
|
});
|
|
742
673
|
// Switch mode
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation loop module for managing sprite animations
|
|
3
|
+
*
|
|
4
|
+
* This module provides a centralized animation tick system that manages
|
|
5
|
+
* a collection of sprites and updates them at ~60fps. It tracks actual
|
|
6
|
+
* delta time between ticks for smooth animations regardless of system load.
|
|
7
|
+
*/
|
|
8
|
+
import { Sprite } from './sprite.js';
|
|
9
|
+
/**
|
|
10
|
+
* Register a sprite with the animation loop
|
|
11
|
+
*
|
|
12
|
+
* Once registered, the sprite's tick() method will be called
|
|
13
|
+
* on each animation frame with the elapsed time delta.
|
|
14
|
+
*
|
|
15
|
+
* @param sprite - The sprite to register
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerSprite(sprite: Sprite): void;
|
|
18
|
+
/**
|
|
19
|
+
* Unregister a sprite from the animation loop
|
|
20
|
+
*
|
|
21
|
+
* The sprite will no longer receive tick updates.
|
|
22
|
+
*
|
|
23
|
+
* @param id - The unique ID of the sprite to unregister
|
|
24
|
+
*/
|
|
25
|
+
export declare function unregisterSprite(id: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get a sprite by ID
|
|
28
|
+
*
|
|
29
|
+
* @param id - The unique ID of the sprite to retrieve
|
|
30
|
+
* @returns The sprite if found, or undefined if not registered
|
|
31
|
+
*/
|
|
32
|
+
export declare function getSprite(id: string): Sprite | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Get all registered sprites
|
|
35
|
+
*
|
|
36
|
+
* @returns An array of all currently registered sprites
|
|
37
|
+
*/
|
|
38
|
+
export declare function getAllSprites(): Sprite[];
|
|
39
|
+
/**
|
|
40
|
+
* Start the animation loop
|
|
41
|
+
*
|
|
42
|
+
* Runs at ~60fps (16ms interval) but tracks actual delta time
|
|
43
|
+
* for smooth animations. If the loop is already running, this
|
|
44
|
+
* function has no effect.
|
|
45
|
+
*/
|
|
46
|
+
export declare function startAnimationLoop(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Stop the animation loop
|
|
49
|
+
*
|
|
50
|
+
* Halts the animation tick. Sprites remain registered and can
|
|
51
|
+
* be resumed by calling startAnimationLoop() again.
|
|
52
|
+
*/
|
|
53
|
+
export declare function stopAnimationLoop(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Check if animation loop is running
|
|
56
|
+
*
|
|
57
|
+
* @returns true if the animation loop is currently active
|
|
58
|
+
*/
|
|
59
|
+
export declare function isAnimationLoopRunning(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Check if any registered sprite has an active animation
|
|
62
|
+
*
|
|
63
|
+
* @returns true if at least one sprite has a non-null animation
|
|
64
|
+
*/
|
|
65
|
+
export declare function hasActiveAnimations(): boolean;
|