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.
@@ -20,7 +20,7 @@ const DEFAULTS = {
20
20
  engine: {
21
21
  skill: 'yuri',
22
22
  tmux_session: 'yuri-gateway',
23
- startup_timeout: 30000, // ms to wait for Claude Code to initialize
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
- * @returns {'idle'|'processing'|'approval'|'complete'|'unknown'}
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: Approval promptneeds immediate response
218
- if (APPROVAL_RE.test(tail)) {
219
- return 'approval';
220
- }
221
-
222
- // Priority 3: Idle indicator — waiting for input
220
+ // Priority 2: Idle indicatorwaiting for input
223
221
  if (IDLE_RE.test(tail)) {
224
222
  return 'idle';
225
223
  }
226
224
 
227
- // Priority 4: Processing indicator — still working
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
- // Kill existing stale session
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
- const startupTimeout = engineConfig.startup_timeout || 30000;
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
- throw new Error(`Claude Code did not become idle within ${startupTimeout}ms`);
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 (mirrors monitor-agent.sh):
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: Approval prompt detected, auto-approve with 'y'
438
- * P4: Content stability — 3 consecutive polls with identical MD5 hash
428
+ * P3: Content stability3 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) throw err;
581
- // Brief pause before retry
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Yuri — Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {