@ekkos/cli 0.2.2 → 0.2.3
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/dist/commands/run.js +102 -20
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -97,6 +97,15 @@ const INTERRUPTED_REGEX = /interrupted.*what should claude do instead/i;
|
|
|
97
97
|
// Command palette indicator - when / is typed, Claude shows a menu
|
|
98
98
|
const PALETTE_INDICATOR_REGEX = /\/(clear|continue|compact|help|bug|config)/i;
|
|
99
99
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// SESSION NAME DETECTION (3-word slug: word-word-word)
|
|
101
|
+
// Claude prints session name in footer: · Turn N · groovy-koala-saves · 📅
|
|
102
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
103
|
+
// Strong signal: session name between dot separators in Claude status/footer line
|
|
104
|
+
// Matches: "· groovy-koala-saves ·" or "· velvet-monk-skips ·"
|
|
105
|
+
const SESSION_NAME_IN_STATUS_REGEX = /·\s*([a-z]+-[a-z]+-[a-z]+)\s*·/i;
|
|
106
|
+
// Weaker signal: any 3-word slug (word-word-word pattern)
|
|
107
|
+
const SESSION_NAME_REGEX = /\b([a-z]+-[a-z]+-[a-z]+)\b/i;
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
100
109
|
// LOGGING (FILE ONLY DURING TUI - NO TERMINAL CORRUPTION)
|
|
101
110
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
102
111
|
let _debugLogPath = path.join(os.homedir(), '.ekkos', 'auto-continue.debug.log');
|
|
@@ -464,6 +473,14 @@ async function run(options) {
|
|
|
464
473
|
let isAutoClearInProgress = false;
|
|
465
474
|
let transcriptPath = null;
|
|
466
475
|
let currentSessionId = null;
|
|
476
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
477
|
+
// SESSION NAME TRACKING (from live TUI output)
|
|
478
|
+
// Claude prints: "· Turn N · groovy-koala-saves · 📅"
|
|
479
|
+
// We parse this to always know the CURRENT session, not stale persisted state
|
|
480
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
481
|
+
let lastSeenSessionName = null;
|
|
482
|
+
let lastSeenSessionAt = 0;
|
|
483
|
+
const RECENCY_WINDOW_MS = 15000; // 15s - session name must be recent to trust
|
|
467
484
|
// Output buffer for pattern detection
|
|
468
485
|
let outputBuffer = '';
|
|
469
486
|
// Debounce tracking to prevent double triggers
|
|
@@ -571,9 +588,24 @@ async function run(options) {
|
|
|
571
588
|
dlog('Already in progress, skipping');
|
|
572
589
|
return;
|
|
573
590
|
}
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
|
|
591
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
592
|
+
// SESSION SELECTION: Prefer most recently SEEN session from TUI output
|
|
593
|
+
// This fixes the "groovy-koala-saves" bug where stale persisted state was used
|
|
594
|
+
// Priority: lastSeenSessionName (if recent) > currentSession > persisted state
|
|
595
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
596
|
+
let sessionToRestore = null;
|
|
597
|
+
// PRIORITY 1: Most recently observed session name from TUI output
|
|
598
|
+
// Only trust if seen within RECENCY_WINDOW_MS (15 seconds)
|
|
599
|
+
if (lastSeenSessionName && (Date.now() - lastSeenSessionAt) < RECENCY_WINDOW_MS) {
|
|
600
|
+
sessionToRestore = lastSeenSessionName;
|
|
601
|
+
dlog(`Using lastSeenSessionName (${Date.now() - lastSeenSessionAt}ms ago): ${sessionToRestore}`);
|
|
602
|
+
}
|
|
603
|
+
// PRIORITY 2: currentSession (in-memory, may be stale)
|
|
604
|
+
if (!sessionToRestore && currentSession) {
|
|
605
|
+
sessionToRestore = currentSession;
|
|
606
|
+
dlog(`Using currentSession (in-memory): ${sessionToRestore}`);
|
|
607
|
+
}
|
|
608
|
+
// PRIORITY 3: Persisted state (fallback)
|
|
577
609
|
if (!sessionToRestore) {
|
|
578
610
|
const state = (0, state_1.getState)();
|
|
579
611
|
sessionToRestore = state?.sessionName || null;
|
|
@@ -582,9 +614,12 @@ async function run(options) {
|
|
|
582
614
|
if (!sessionToRestore && sessionId) {
|
|
583
615
|
sessionToRestore = (0, state_1.uuidToWords)(sessionId);
|
|
584
616
|
}
|
|
617
|
+
if (sessionToRestore) {
|
|
618
|
+
dlog(`Using persisted state (fallback): ${sessionToRestore}`);
|
|
619
|
+
}
|
|
585
620
|
}
|
|
586
621
|
const sessionDisplay = sessionToRestore || 'unknown-session';
|
|
587
|
-
dlog(`Session to restore (
|
|
622
|
+
dlog(`Session to restore (final): ${sessionDisplay}`);
|
|
588
623
|
// CRITICAL: Clear buffer and set flags immediately
|
|
589
624
|
outputBuffer = '';
|
|
590
625
|
lastDetectionTime = now;
|
|
@@ -671,13 +706,53 @@ async function run(options) {
|
|
|
671
706
|
transcriptPath = transcriptMatch[1];
|
|
672
707
|
dlog(`Detected transcript: ${transcriptPath}`);
|
|
673
708
|
}
|
|
674
|
-
// Try to extract session ID from output
|
|
709
|
+
// Try to extract session ID from output (fallback - Claude rarely prints this)
|
|
675
710
|
const sessionMatch = data.match(/session[_\s]?(?:id)?[:\s]+([a-f0-9-]{36})/i);
|
|
676
711
|
if (sessionMatch) {
|
|
677
712
|
currentSessionId = sessionMatch[1];
|
|
678
713
|
currentSession = (0, state_1.uuidToWords)(currentSessionId);
|
|
679
714
|
(0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
|
|
680
|
-
dlog(`Session detected: ${currentSession}`);
|
|
715
|
+
dlog(`Session detected from UUID: ${currentSession}`);
|
|
716
|
+
}
|
|
717
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
718
|
+
// SESSION NAME DETECTION (PRIMARY METHOD)
|
|
719
|
+
// Claude footer: "· Turn N · groovy-koala-saves · 📅 2026-01-17"
|
|
720
|
+
// This is MORE reliable than UUID extraction
|
|
721
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
722
|
+
const plain = stripAnsi(data);
|
|
723
|
+
// Strong signal: session name between dot separators in status/footer line
|
|
724
|
+
const statusMatch = plain.match(SESSION_NAME_IN_STATUS_REGEX);
|
|
725
|
+
if (statusMatch) {
|
|
726
|
+
const detectedSession = statusMatch[1].toLowerCase();
|
|
727
|
+
// Only update if different (avoid log spam)
|
|
728
|
+
if (detectedSession !== lastSeenSessionName) {
|
|
729
|
+
lastSeenSessionName = detectedSession;
|
|
730
|
+
lastSeenSessionAt = Date.now();
|
|
731
|
+
currentSession = lastSeenSessionName;
|
|
732
|
+
(0, state_1.updateState)({ sessionName: currentSession });
|
|
733
|
+
dlog(`Session detected from status line: ${currentSession}`);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
// Same session, just update timestamp
|
|
737
|
+
lastSeenSessionAt = Date.now();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// Weaker signal: any 3-word slug (only if no status match)
|
|
742
|
+
const anyMatch = plain.match(SESSION_NAME_REGEX);
|
|
743
|
+
if (anyMatch) {
|
|
744
|
+
const detectedSession = anyMatch[1].toLowerCase();
|
|
745
|
+
if (detectedSession !== lastSeenSessionName) {
|
|
746
|
+
lastSeenSessionName = detectedSession;
|
|
747
|
+
lastSeenSessionAt = Date.now();
|
|
748
|
+
currentSession = lastSeenSessionName;
|
|
749
|
+
(0, state_1.updateState)({ sessionName: currentSession });
|
|
750
|
+
dlog(`Session detected from generic match: ${currentSession}`);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
lastSeenSessionAt = Date.now();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
681
756
|
}
|
|
682
757
|
// Check for context wall patterns (ANSI-stripped + regex for robustness)
|
|
683
758
|
if (!isAutoClearInProgress) {
|
|
@@ -726,14 +801,22 @@ async function runWithSpawn(claudePath, args, options, state) {
|
|
|
726
801
|
// Debounce tracking
|
|
727
802
|
let lastDetectionTime = 0;
|
|
728
803
|
const DETECTION_COOLDOWN = 30000;
|
|
729
|
-
console.log(chalk_1.default.gray('Using spawn fallback mode'));
|
|
730
|
-
console.log(chalk_1.default.yellow('
|
|
804
|
+
console.log(chalk_1.default.gray('Using spawn fallback mode (node-pty unavailable)'));
|
|
805
|
+
console.log(chalk_1.default.yellow('⚠️ Auto-continue requires PTY - manual /clear + /continue only'));
|
|
806
|
+
console.log(chalk_1.default.gray(' To enable auto-continue: npm rebuild node-pty'));
|
|
731
807
|
console.log('');
|
|
732
808
|
let claude;
|
|
733
809
|
if (isWindows) {
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
810
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
811
|
+
// WINDOWS: Full TTY passthrough (no output monitoring, no auto-inject)
|
|
812
|
+
//
|
|
813
|
+
// Why: Without node-pty/ConPTY, we cannot simultaneously:
|
|
814
|
+
// 1. Keep Claude TUI interactive
|
|
815
|
+
// 2. Read output for context-wall detection
|
|
816
|
+
//
|
|
817
|
+
// Piping stdout triggers Claude's --print mode which breaks the TUI.
|
|
818
|
+
// Solution: stdio: 'inherit' for complete passthrough.
|
|
819
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
737
820
|
if (claudePath === 'npx') {
|
|
738
821
|
console.log('');
|
|
739
822
|
console.log(chalk_1.default.red('═══════════════════════════════════════════════════════════════════'));
|
|
@@ -748,24 +831,24 @@ async function runWithSpawn(claudePath, args, options, state) {
|
|
|
748
831
|
console.log('');
|
|
749
832
|
process.exit(1);
|
|
750
833
|
}
|
|
751
|
-
console.log(chalk_1.default.gray('Windows mode:
|
|
752
|
-
|
|
834
|
+
console.log(chalk_1.default.gray('Windows mode: full TTY passthrough (no auto-inject)'));
|
|
835
|
+
console.log(chalk_1.default.gray('Context wall → manual /clear + /continue <session>'));
|
|
836
|
+
console.log('');
|
|
837
|
+
// Build command for passthrough
|
|
753
838
|
const fullCmd = args.length > 0
|
|
754
839
|
? `"${claudePath}" ${args.join(' ')}`
|
|
755
840
|
: `"${claudePath}"`;
|
|
756
|
-
//
|
|
757
|
-
// This
|
|
841
|
+
// spawnSync with stdio: 'inherit' = full console passthrough
|
|
842
|
+
// This is the ONLY way to keep Claude TUI working on Windows without PTY
|
|
758
843
|
const { spawnSync } = require('child_process');
|
|
759
|
-
// First try: Direct exec replacement
|
|
760
844
|
try {
|
|
761
845
|
const result = spawnSync('cmd.exe', ['/c', fullCmd], {
|
|
762
|
-
stdio: 'inherit',
|
|
846
|
+
stdio: 'inherit', // CRITICAL: Full passthrough, no piping
|
|
763
847
|
cwd: process.cwd(),
|
|
764
848
|
env: process.env,
|
|
765
849
|
windowsHide: false,
|
|
766
850
|
shell: false
|
|
767
851
|
});
|
|
768
|
-
// If we get here, claude exited
|
|
769
852
|
(0, state_1.clearAutoClearFlag)();
|
|
770
853
|
process.exit(result.status || 0);
|
|
771
854
|
}
|
|
@@ -777,8 +860,7 @@ async function runWithSpawn(claudePath, args, options, state) {
|
|
|
777
860
|
console.log('');
|
|
778
861
|
process.exit(1);
|
|
779
862
|
}
|
|
780
|
-
//
|
|
781
|
-
return;
|
|
863
|
+
return; // Unreachable due to spawnSync + process.exit, but explicit
|
|
782
864
|
}
|
|
783
865
|
else {
|
|
784
866
|
// Use script command for PTY on Unix
|