orchestrix-yuri 2.3.2 → 2.3.4
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/lib/gateway/config.js
CHANGED
|
@@ -20,7 +20,7 @@ const DEFAULTS = {
|
|
|
20
20
|
engine: {
|
|
21
21
|
skill: 'yuri',
|
|
22
22
|
tmux_session: 'yuri-gateway',
|
|
23
|
-
startup_timeout:
|
|
23
|
+
startup_timeout: 60000, // ms to wait for Claude Code to initialize
|
|
24
24
|
poll_interval: 2000, // ms between capture-pane polls
|
|
25
25
|
stable_count: 3, // consecutive stable polls before declaring done
|
|
26
26
|
max_retries: 3, // session restart retries before error
|
|
@@ -162,7 +162,6 @@ const BRAILLE_SPINNER = /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/;
|
|
|
162
162
|
const COMPLETION_RE = /[A-Z][a-z]*ed for \d+/;
|
|
163
163
|
const IDLE_RE = /○/;
|
|
164
164
|
const PROCESSING_RE = /●/;
|
|
165
|
-
const APPROVAL_RE = /◐/;
|
|
166
165
|
|
|
167
166
|
/**
|
|
168
167
|
* Strip TUI chrome from captured pane output.
|
|
@@ -203,7 +202,11 @@ function paneTail(name, n) {
|
|
|
203
202
|
/**
|
|
204
203
|
* Detect Claude Code's current state from pane output.
|
|
205
204
|
*
|
|
206
|
-
*
|
|
205
|
+
* Note: We launch with --dangerously-skip-permissions, so approval prompts (◐)
|
|
206
|
+
* should never appear. The detection is kept for robustness but no auto-approve
|
|
207
|
+
* action is taken — sending blind 'y' keystrokes is dangerous.
|
|
208
|
+
*
|
|
209
|
+
* @returns {'idle'|'processing'|'complete'|'unknown'}
|
|
207
210
|
*/
|
|
208
211
|
function detectState(name) {
|
|
209
212
|
const tail = paneTail(name, 15);
|
|
@@ -214,17 +217,12 @@ function detectState(name) {
|
|
|
214
217
|
return 'complete';
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
// Priority 2:
|
|
218
|
-
if (APPROVAL_RE.test(tail)) {
|
|
219
|
-
return 'approval';
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Priority 3: Idle indicator — waiting for input
|
|
220
|
+
// Priority 2: Idle indicator — waiting for input
|
|
223
221
|
if (IDLE_RE.test(tail)) {
|
|
224
222
|
return 'idle';
|
|
225
223
|
}
|
|
226
224
|
|
|
227
|
-
// Priority
|
|
225
|
+
// Priority 3: Processing indicator — still working
|
|
228
226
|
if (PROCESSING_RE.test(tail) || BRAILLE_SPINNER.test(tail)) {
|
|
229
227
|
return 'processing';
|
|
230
228
|
}
|
|
@@ -241,20 +239,6 @@ function isIdle(name) {
|
|
|
241
239
|
return state === 'idle' || state === 'complete';
|
|
242
240
|
}
|
|
243
241
|
|
|
244
|
-
/**
|
|
245
|
-
* Detect if Claude Code is showing an approval prompt (◐).
|
|
246
|
-
*/
|
|
247
|
-
function isApprovalPrompt(name) {
|
|
248
|
-
return detectState(name) === 'approval';
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Detect if Claude Code is actively processing (● or spinner).
|
|
253
|
-
*/
|
|
254
|
-
function isProcessing(name) {
|
|
255
|
-
return detectState(name) === 'processing';
|
|
256
|
-
}
|
|
257
|
-
|
|
258
242
|
// ── Context Management ─────────────────────────────────────────────────────────
|
|
259
243
|
//
|
|
260
244
|
// Claude Code has built-in auto-compact that triggers at ~95% context capacity.
|
|
@@ -341,32 +325,43 @@ async function createSession(engineConfig) {
|
|
|
341
325
|
|
|
342
326
|
const binary = getClaudeBinary();
|
|
343
327
|
const projectRoot = resolveProjectRoot() || os.homedir();
|
|
328
|
+
log.tmux(`Binary: ${binary}`);
|
|
329
|
+
log.tmux(`Project root: ${projectRoot}`);
|
|
344
330
|
|
|
345
331
|
// Ensure CLAUDE.md has channel mode instructions (survives compact)
|
|
346
332
|
ensureClaudeMd(projectRoot);
|
|
347
333
|
|
|
348
|
-
//
|
|
334
|
+
// Only kill existing session if it exists — don't kill on retry
|
|
335
|
+
// so the user can `tmux attach -t yuri-gateway` to debug
|
|
349
336
|
if (hasSession(sessionName)) {
|
|
337
|
+
log.tmux(`Killing existing session "${sessionName}"`);
|
|
350
338
|
tmuxSafe(`kill-session -t ${sessionName}`);
|
|
351
339
|
}
|
|
352
340
|
|
|
353
341
|
// Create session with generous scrollback
|
|
354
342
|
tmux(`new-session -d -s ${sessionName} -n claude -c "${projectRoot}"`);
|
|
355
343
|
tmux(`set-option -t ${sessionName} history-limit ${HISTORY_LIMIT}`);
|
|
344
|
+
log.tmux(`Session "${sessionName}" created, launching Claude Code...`);
|
|
356
345
|
|
|
357
346
|
// Set auto-compact threshold to 80% (default is 95%)
|
|
358
|
-
// This gives comfortable buffer before context pressure
|
|
359
347
|
const compactPct = engineConfig.autocompact_pct || 80;
|
|
360
348
|
tmux(`send-keys -t ${sessionName}:0 'export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=${compactPct}' Enter`);
|
|
361
349
|
|
|
362
350
|
// Launch Claude Code in interactive mode
|
|
363
351
|
tmux(`send-keys -t ${sessionName}:0 '"${binary}" --dangerously-skip-permissions' Enter`);
|
|
364
352
|
|
|
365
|
-
// Wait for Claude Code to initialize (detect idle indicator)
|
|
366
|
-
|
|
353
|
+
// Wait for Claude Code to initialize (detect idle indicator ○)
|
|
354
|
+
// Default 60s — Claude Code needs time to load CLAUDE.md, connect MCP servers, etc.
|
|
355
|
+
const startupTimeout = engineConfig.startup_timeout || 60000;
|
|
356
|
+
log.tmux(`Waiting for Claude Code to become idle (timeout: ${startupTimeout / 1000}s)...`);
|
|
367
357
|
const started = await waitForIdle(sessionName, startupTimeout);
|
|
368
358
|
if (!started) {
|
|
369
|
-
|
|
359
|
+
// Don't kill session on failure — let user debug with tmux attach
|
|
360
|
+
const tail = paneTail(sessionName, 10);
|
|
361
|
+
log.error(`Claude Code did not become idle within ${startupTimeout / 1000}s`);
|
|
362
|
+
log.error(`Last pane output:\n${tail}`);
|
|
363
|
+
log.info(`Debug: tmux attach -t ${sessionName}`);
|
|
364
|
+
throw new Error(`Claude Code did not become idle within ${startupTimeout / 1000}s`);
|
|
370
365
|
}
|
|
371
366
|
|
|
372
367
|
// Send L1 context as the initial system message.
|
|
@@ -374,6 +369,7 @@ async function createSession(engineConfig) {
|
|
|
374
369
|
// so we only inject L1 global memory here to prime the session.
|
|
375
370
|
const l1 = loadL1Context();
|
|
376
371
|
if (l1) {
|
|
372
|
+
log.tmux('Injecting L1 context...');
|
|
377
373
|
await injectMessage(sessionName, l1);
|
|
378
374
|
await waitForIdle(sessionName, 120000); // allow up to 2min for L1 processing
|
|
379
375
|
}
|
|
@@ -398,11 +394,6 @@ function waitForIdle(name, timeoutMs) {
|
|
|
398
394
|
return resolve(false);
|
|
399
395
|
}
|
|
400
396
|
|
|
401
|
-
// Auto-approve any permission prompts
|
|
402
|
-
if (isApprovalPrompt(name)) {
|
|
403
|
-
tmuxSafe(`send-keys -t ${name}:0 'y' Enter`);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
397
|
if (isIdle(name)) {
|
|
407
398
|
return resolve(true);
|
|
408
399
|
}
|
|
@@ -431,11 +422,10 @@ function injectMessage(name, text) {
|
|
|
431
422
|
/**
|
|
432
423
|
* Capture the response after injecting a message.
|
|
433
424
|
*
|
|
434
|
-
* Detection priority
|
|
425
|
+
* Detection priority:
|
|
435
426
|
* P1: Completion message — "[Verb]ed for [N]s/m" (e.g. "Baked for 31s")
|
|
436
427
|
* P2: Idle indicator — ○ appears in pane tail
|
|
437
|
-
* P3:
|
|
438
|
-
* P4: Content stability — 3 consecutive polls with identical MD5 hash
|
|
428
|
+
* P3: Content stability — 3 consecutive polls with identical MD5 hash
|
|
439
429
|
*/
|
|
440
430
|
async function captureResponse(name, marker, engineConfig) {
|
|
441
431
|
const timeout = engineConfig.timeout || 300000;
|
|
@@ -475,16 +465,6 @@ async function captureResponse(name, marker, engineConfig) {
|
|
|
475
465
|
return setTimeout(poll, pollInterval);
|
|
476
466
|
}
|
|
477
467
|
|
|
478
|
-
// P3: Auto-approve permission prompts (◐)
|
|
479
|
-
if (state === 'approval') {
|
|
480
|
-
tmuxSafe(`send-keys -t ${name}:0 'y' Enter`);
|
|
481
|
-
sawProcessing = true; // approval implies processing started
|
|
482
|
-
stableCount = 0;
|
|
483
|
-
lastHash = hash;
|
|
484
|
-
// Brief pause after approval before next poll
|
|
485
|
-
return setTimeout(poll, 2000);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
468
|
// P1: Completion message — most reliable done signal
|
|
489
469
|
if (state === 'complete' && sawProcessing) {
|
|
490
470
|
return resolve(extractResponse(raw, marker));
|
|
@@ -577,8 +557,14 @@ async function ensureSession(engineConfig) {
|
|
|
577
557
|
return;
|
|
578
558
|
} catch (err) {
|
|
579
559
|
log.warn(`Session init attempt ${attempt}/${maxRetries} failed: ${err.message}`);
|
|
580
|
-
if (attempt === maxRetries)
|
|
581
|
-
|
|
560
|
+
if (attempt === maxRetries) {
|
|
561
|
+
log.error('All init attempts failed. Check Claude Code installation and tmux.');
|
|
562
|
+
log.info(`Debug: tmux attach -t ${engineConfig.tmux_session || DEFAULT_SESSION}`);
|
|
563
|
+
throw err;
|
|
564
|
+
}
|
|
565
|
+
// Kill session before retry so createSession starts fresh
|
|
566
|
+
const sn = engineConfig.tmux_session || DEFAULT_SESSION;
|
|
567
|
+
if (hasSession(sn)) tmuxSafe(`kill-session -t ${sn}`);
|
|
582
568
|
await new Promise((r) => setTimeout(r, 3000));
|
|
583
569
|
}
|
|
584
570
|
}
|