crewly 1.2.1 → 1.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/config/roles/architect/prompt.md +7 -5
- package/config/roles/backend-developer/prompt.md +8 -4
- package/config/roles/content-strategist/prompt.md +12 -4
- package/config/roles/designer/prompt.md +8 -4
- package/config/roles/developer/prompt.md +7 -4
- package/config/roles/frontend-developer/prompt.md +8 -4
- package/config/roles/fullstack-dev/prompt.md +8 -4
- package/config/roles/generalist/prompt.md +7 -4
- package/config/roles/ops/prompt.md +7 -4
- package/config/roles/orchestrator/prompt.md +22 -1
- package/config/roles/product-manager/prompt.md +8 -4
- package/config/roles/qa/prompt.md +8 -4
- package/config/roles/qa-engineer/prompt.md +8 -4
- package/config/roles/sales/prompt.md +8 -4
- package/config/roles/support/prompt.md +8 -4
- package/config/roles/tpm/prompt.md +8 -4
- package/config/skills/orchestrator/delegate-task/execute.sh +2 -2
- package/config/templates/agent-claude-md.md +10 -5
- package/dist/backend/backend/src/constants.d.ts +7 -69
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +7 -74
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +39 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +2 -13
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +85 -133
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts +1 -1
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js +8 -8
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +0 -7
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +0 -31
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts +28 -4
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +111 -58
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js +0 -3
- package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/session-command-helper.js +16 -4
- package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -1
- package/dist/backend/backend/src/utils/terminal-output.utils.d.ts +2 -1
- package/dist/backend/backend/src/utils/terminal-output.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/terminal-output.utils.js +2 -28
- package/dist/backend/backend/src/utils/terminal-output.utils.js.map +1 -1
- package/dist/backend/backend/src/utils/terminal-string-ops.d.ts +183 -0
- package/dist/backend/backend/src/utils/terminal-string-ops.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/terminal-string-ops.js +717 -0
- package/dist/backend/backend/src/utils/terminal-string-ops.js.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js +22 -27
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +7 -69
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +7 -74
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/utils/terminal-output.utils.d.ts +2 -1
- package/dist/cli/backend/src/utils/terminal-output.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/utils/terminal-output.utils.js +2 -28
- package/dist/cli/backend/src/utils/terminal-output.utils.js.map +1 -1
- package/dist/cli/backend/src/utils/terminal-string-ops.d.ts +183 -0
- package/dist/cli/backend/src/utils/terminal-string-ops.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/terminal-string-ops.js +717 -0
- package/dist/cli/backend/src/utils/terminal-string-ops.js.map +1 -0
- package/package.json +1 -1
|
@@ -14,6 +14,8 @@ import { ContextWindowMonitorService } from './context-window-monitor.service.js
|
|
|
14
14
|
import { SubAgentMessageQueue } from '../messaging/sub-agent-message-queue.service.js';
|
|
15
15
|
import { AgentSuspendService } from './agent-suspend.service.js';
|
|
16
16
|
import { stripAnsiCodes } from '../../utils/terminal-output.utils.js';
|
|
17
|
+
import { isPromptLine, containsSpinnerOrWorkingIndicator, containsProcessingIndicator, containsBusyStatusBar, containsRewindMode, containsGeminiProcessingKeywords, extractChatPrefix, stripTuiLineBorders, matchTuiPromptLine, } from '../../utils/terminal-string-ops.js';
|
|
18
|
+
import { PtyActivityTrackerService } from './pty-activity-tracker.service.js';
|
|
17
19
|
/**
|
|
18
20
|
* Service responsible for the complex, multi-step process of agent initialization and registration.
|
|
19
21
|
* Isolates the complex state management of agent startup with progressive escalation.
|
|
@@ -47,16 +49,10 @@ export class AgentRegistrationService {
|
|
|
47
49
|
// which causes multiple Ctrl+C presses that can crash the runtime.
|
|
48
50
|
sessionDeliveryMutex = new Map();
|
|
49
51
|
// Terminal patterns are now centralized in TERMINAL_PATTERNS constant
|
|
50
|
-
// Keeping
|
|
52
|
+
// Keeping prompt chars as static getter for backwards compatibility within the class
|
|
51
53
|
static get CLAUDE_PROMPT_INDICATORS() {
|
|
52
54
|
return TERMINAL_PATTERNS.PROMPT_CHARS;
|
|
53
55
|
}
|
|
54
|
-
static get CLAUDE_PROMPT_STREAM_PATTERN() {
|
|
55
|
-
return TERMINAL_PATTERNS.PROMPT_STREAM;
|
|
56
|
-
}
|
|
57
|
-
static get CLAUDE_PROCESSING_INDICATORS() {
|
|
58
|
-
return TERMINAL_PATTERNS.PROCESSING_INDICATORS;
|
|
59
|
-
}
|
|
60
56
|
constructor(_legacyTmuxService, // Legacy parameter for backwards compatibility
|
|
61
57
|
projectRoot, storageService) {
|
|
62
58
|
this.logger = LoggerService.getInstance().createComponentLogger('AgentRegistrationService');
|
|
@@ -1508,6 +1504,14 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
1508
1504
|
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.CREWLY_SESSION_NAME, sessionName);
|
|
1509
1505
|
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.CREWLY_ROLE, role);
|
|
1510
1506
|
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.CREWLY_API_URL, `http://localhost:${WEB_CONSTANTS.PORTS.BACKEND}`);
|
|
1507
|
+
// Pass Gemini API key to gemini-cli agents so they authenticate
|
|
1508
|
+
// with the paid API key instead of the free-tier Google login.
|
|
1509
|
+
if (runtimeType === RUNTIME_TYPES.GEMINI_CLI) {
|
|
1510
|
+
const geminiApiKey = process.env[ENV_CONSTANTS.GEMINI_API_KEY];
|
|
1511
|
+
if (geminiApiKey) {
|
|
1512
|
+
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.GEMINI_API_KEY, geminiApiKey);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1511
1515
|
this.logger.info('Agent session created and environment variables set, initializing with registration', {
|
|
1512
1516
|
sessionName,
|
|
1513
1517
|
role,
|
|
@@ -1701,9 +1705,9 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
1701
1705
|
if (!delivered) {
|
|
1702
1706
|
// Check if the agent is actively processing (busy) — the queue
|
|
1703
1707
|
// processor can re-queue instead of permanently failing the message.
|
|
1704
|
-
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1708
|
+
// Use PTY idle time instead of regex patterns for robust cross-runtime detection.
|
|
1709
|
+
const idleMs = PtyActivityTrackerService.getInstance().getIdleTimeMs(sessionName);
|
|
1710
|
+
const isBusy = idleMs < SESSION_COMMAND_DELAYS.AGENT_BUSY_IDLE_THRESHOLD_MS;
|
|
1707
1711
|
return {
|
|
1708
1712
|
success: false,
|
|
1709
1713
|
error: isBusy
|
|
@@ -1768,9 +1772,6 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
1768
1772
|
if (!session) {
|
|
1769
1773
|
return false;
|
|
1770
1774
|
}
|
|
1771
|
-
// Use runtime-specific pattern for stream detection to avoid false positives
|
|
1772
|
-
// (e.g. Gemini's `> ` pattern matching markdown blockquotes in Claude Code output)
|
|
1773
|
-
const streamPattern = this.getPromptPatternForRuntime(runtimeType);
|
|
1774
1775
|
return new Promise((resolve) => {
|
|
1775
1776
|
let resolved = false;
|
|
1776
1777
|
const pollInterval = EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL;
|
|
@@ -1817,9 +1818,11 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
1817
1818
|
if (resolved)
|
|
1818
1819
|
return;
|
|
1819
1820
|
// Strip ANSI escape sequences before testing — raw PTY data contains
|
|
1820
|
-
// cursor positioning, color codes, etc. that break
|
|
1821
|
+
// cursor positioning, color codes, etc. that break pattern matching (#106)
|
|
1821
1822
|
const cleanData = stripAnsiCodes(data);
|
|
1822
|
-
|
|
1823
|
+
// Check each line for prompt pattern (string-based, no regex)
|
|
1824
|
+
const hasPromptInStream = cleanData.split('\n').some(line => line.trim().length > 0 && isPromptLine(line, runtimeType));
|
|
1825
|
+
if (hasPromptInStream) {
|
|
1823
1826
|
// Double-check with capturePane to avoid false positives from partial data
|
|
1824
1827
|
const output = sessionHelper.capturePane(sessionName);
|
|
1825
1828
|
if (this.isClaudeAtPrompt(output, runtimeType)) {
|
|
@@ -2052,7 +2055,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2052
2055
|
}
|
|
2053
2056
|
// Phase 1: Wait for Claude to be at prompt before sending
|
|
2054
2057
|
if (!messageSent) {
|
|
2055
|
-
const isAtPrompt =
|
|
2058
|
+
const isAtPrompt = buffer.split('\n').some(line => line.trim().length > 0 && isPromptLine(line));
|
|
2056
2059
|
if (isAtPrompt) {
|
|
2057
2060
|
sendMessageNow();
|
|
2058
2061
|
}
|
|
@@ -2063,9 +2066,9 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2063
2066
|
return; // Wait for Enter to be sent
|
|
2064
2067
|
}
|
|
2065
2068
|
// Look for processing indicators confirming delivery
|
|
2066
|
-
const hasProcessingIndicator =
|
|
2069
|
+
const hasProcessingIndicator = containsProcessingIndicator(buffer);
|
|
2067
2070
|
// Also check if prompt disappeared (Claude is working)
|
|
2068
|
-
const promptStillVisible =
|
|
2071
|
+
const promptStillVisible = buffer.split('\n').some(line => line.trim().length > 0 && isPromptLine(line));
|
|
2069
2072
|
// Use constant for minimum buffer check (P3.2 fix)
|
|
2070
2073
|
if (hasProcessingIndicator || (!promptStillVisible && buffer.length > EVENT_DELIVERY_CONSTANTS.MIN_BUFFER_FOR_PROCESSING_DETECTION)) {
|
|
2071
2074
|
this.logger.debug('Message delivery confirmed (event-driven)', {
|
|
@@ -2114,12 +2117,10 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2114
2117
|
if (!this.isClaudeAtPrompt(output, runtimeType)) {
|
|
2115
2118
|
if (attempt === maxAttempts) {
|
|
2116
2119
|
// On the final attempt, check if the agent is DEFINITELY busy
|
|
2117
|
-
// before force-delivering.
|
|
2118
|
-
//
|
|
2119
|
-
|
|
2120
|
-
const
|
|
2121
|
-
const isBusy = TERMINAL_PATTERNS.BUSY_STATUS_BAR.test(tailForBusyCheck) ||
|
|
2122
|
-
TERMINAL_PATTERNS.PROCESSING_WITH_TEXT.test(tailForBusyCheck);
|
|
2120
|
+
// before force-delivering. Use PTY idle time for robust
|
|
2121
|
+
// cross-runtime detection instead of fragile regex patterns.
|
|
2122
|
+
const idleMs = PtyActivityTrackerService.getInstance().getIdleTimeMs(sessionName);
|
|
2123
|
+
const isBusy = idleMs < SESSION_COMMAND_DELAYS.AGENT_BUSY_IDLE_THRESHOLD_MS;
|
|
2123
2124
|
if (isBusy) {
|
|
2124
2125
|
this.logger.warn('Agent is busy (processing indicators detected), skipping force delivery', {
|
|
2125
2126
|
sessionName,
|
|
@@ -2182,8 +2183,30 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2182
2183
|
// for focus cycling and overlay dismissal.
|
|
2183
2184
|
if (isClaudeCode) {
|
|
2184
2185
|
if (attempt > 1) {
|
|
2186
|
+
// On retry: Ctrl+C to cancel stale input, then PTY resize to force
|
|
2187
|
+
// TUI re-render (SIGWINCH), then Tab to cycle Ink focus.
|
|
2185
2188
|
await sessionHelper.sendCtrlC(sessionName);
|
|
2186
2189
|
await delay(300);
|
|
2190
|
+
try {
|
|
2191
|
+
const session = sessionHelper.getSession(sessionName);
|
|
2192
|
+
if (session) {
|
|
2193
|
+
session.resize(81, 25);
|
|
2194
|
+
await delay(200);
|
|
2195
|
+
session.resize(80, 24);
|
|
2196
|
+
await delay(300);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
catch { /* non-fatal */ }
|
|
2200
|
+
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
2201
|
+
await delay(300);
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
// First attempt: lightweight focus nudge — Tab key cycles Ink's
|
|
2205
|
+
// focusNext() to ensure the InputPrompt is active. This prevents
|
|
2206
|
+
// the "write succeeds but TUI ignores it" failure mode without
|
|
2207
|
+
// the overhead of Ctrl+C or PTY resize.
|
|
2208
|
+
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
2209
|
+
await delay(200);
|
|
2187
2210
|
}
|
|
2188
2211
|
}
|
|
2189
2212
|
else {
|
|
@@ -2318,7 +2341,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2318
2341
|
// definitive proof the agent accepted and is working.
|
|
2319
2342
|
// Only check spinner/⏺ chars — NOT text words like
|
|
2320
2343
|
// "thinking" which appear in historical response text.
|
|
2321
|
-
if (
|
|
2344
|
+
if (containsSpinnerOrWorkingIndicator(currentOutput)) {
|
|
2322
2345
|
this.logger.debug('Processing indicators detected — message accepted', {
|
|
2323
2346
|
sessionName,
|
|
2324
2347
|
attempt,
|
|
@@ -2353,7 +2376,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2353
2376
|
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
2354
2377
|
// Verify recovery
|
|
2355
2378
|
const postEnterOutput = sessionHelper.capturePane(sessionName);
|
|
2356
|
-
if (
|
|
2379
|
+
if (containsSpinnerOrWorkingIndicator(postEnterOutput) ||
|
|
2357
2380
|
!this.isClaudeAtPrompt(postEnterOutput, runtimeType)) {
|
|
2358
2381
|
this.logger.info('Enter recovery from prompt successful', {
|
|
2359
2382
|
sessionName,
|
|
@@ -2400,7 +2423,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2400
2423
|
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
2401
2424
|
// Verify recovery: check if processing started
|
|
2402
2425
|
const recoveryOutput = sessionHelper.capturePane(sessionName);
|
|
2403
|
-
if (
|
|
2426
|
+
if (containsSpinnerOrWorkingIndicator(recoveryOutput)) {
|
|
2404
2427
|
this.logger.info('Enter recovery successful — processing started', {
|
|
2405
2428
|
sessionName,
|
|
2406
2429
|
attempt,
|
|
@@ -2487,9 +2510,9 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2487
2510
|
.filter((line) => !beforeLines.has(line))
|
|
2488
2511
|
.join('\n');
|
|
2489
2512
|
}
|
|
2490
|
-
const hasProcessingIndicators =
|
|
2513
|
+
const hasProcessingIndicators = containsProcessingIndicator(newContent || afterOutput.slice(-500));
|
|
2491
2514
|
const hasGeminiIndicators = newContent.length > 0
|
|
2492
|
-
&&
|
|
2515
|
+
&& containsGeminiProcessingKeywords(newContent);
|
|
2493
2516
|
const significantLengthChange = Math.abs(lengthDiff) > 10;
|
|
2494
2517
|
// For Gemini CLI, contentChanged alone is sufficient evidence of
|
|
2495
2518
|
// delivery. The TUI redraws minimally (lengthDiff can be as low as
|
|
@@ -2568,19 +2591,11 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2568
2591
|
}
|
|
2569
2592
|
}
|
|
2570
2593
|
}
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
if (childAlive !== false) {
|
|
2577
|
-
this.logger.warn('Message delivery verification inconclusive but session alive — assuming success', {
|
|
2578
|
-
sessionName,
|
|
2579
|
-
maxAttempts,
|
|
2580
|
-
messageLength: message.length,
|
|
2581
|
-
});
|
|
2582
|
-
return true;
|
|
2583
|
-
}
|
|
2594
|
+
this.logger.warn('Message delivery verification failed — all attempts exhausted', {
|
|
2595
|
+
sessionName,
|
|
2596
|
+
maxAttempts,
|
|
2597
|
+
messageLength: message.length,
|
|
2598
|
+
});
|
|
2584
2599
|
this.logger.error('Message delivery failed after all retry attempts', {
|
|
2585
2600
|
sessionName,
|
|
2586
2601
|
maxAttempts,
|
|
@@ -2608,13 +2623,13 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2608
2623
|
}
|
|
2609
2624
|
// Extract a search token from the message:
|
|
2610
2625
|
// Strip [CHAT:uuid] prefix if present, then take the first 40 chars
|
|
2611
|
-
const
|
|
2612
|
-
const contentAfterPrefix =
|
|
2613
|
-
? message.slice(
|
|
2626
|
+
const { prefixLength } = extractChatPrefix(message);
|
|
2627
|
+
const contentAfterPrefix = prefixLength > 0
|
|
2628
|
+
? message.slice(prefixLength)
|
|
2614
2629
|
: message;
|
|
2615
2630
|
const searchToken = contentAfterPrefix.slice(0, 40).trim();
|
|
2616
2631
|
// Also use [CHAT: as a secondary token if message has a CHAT prefix
|
|
2617
|
-
const chatToken =
|
|
2632
|
+
const chatToken = prefixLength > 0 ? '[CHAT:' : null;
|
|
2618
2633
|
// Check last 20 non-empty lines for either token.
|
|
2619
2634
|
// Gemini CLI TUI has status bars at the bottom (branch, sandbox, model info)
|
|
2620
2635
|
// that push input content further up. 5 lines was insufficient.
|
|
@@ -2622,7 +2637,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2622
2637
|
const linesToCheck = lines.slice(-20);
|
|
2623
2638
|
const isStuck = linesToCheck.some((line) => {
|
|
2624
2639
|
// Strip TUI box-drawing borders before checking (Gemini CLI wraps content in │...│)
|
|
2625
|
-
const stripped = line
|
|
2640
|
+
const stripped = stripTuiLineBorders(line);
|
|
2626
2641
|
if (searchToken && (line.includes(searchToken) || stripped.includes(searchToken)))
|
|
2627
2642
|
return true;
|
|
2628
2643
|
if (chatToken && (line.includes(chatToken) || stripped.includes(chatToken)))
|
|
@@ -2672,9 +2687,9 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2672
2687
|
return false;
|
|
2673
2688
|
// Extract search token: first 30 chars of the message content
|
|
2674
2689
|
// (after stripping any [CHAT:uuid] prefix)
|
|
2675
|
-
const
|
|
2676
|
-
const contentAfterPrefix =
|
|
2677
|
-
? message.slice(
|
|
2690
|
+
const { prefixLength } = extractChatPrefix(message);
|
|
2691
|
+
const contentAfterPrefix = prefixLength > 0
|
|
2692
|
+
? message.slice(prefixLength)
|
|
2678
2693
|
: message;
|
|
2679
2694
|
const searchToken = contentAfterPrefix.slice(0, 30).trim();
|
|
2680
2695
|
if (!searchToken)
|
|
@@ -2684,14 +2699,13 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2684
2699
|
// Or without borders: > text
|
|
2685
2700
|
// The prompt line is the line with `> ` followed by actual content.
|
|
2686
2701
|
const lines = output.split('\n');
|
|
2687
|
-
const promptLineRegex = /^[│┃║|\s]*>\s+(.+)/;
|
|
2688
2702
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 25); i--) {
|
|
2689
2703
|
const line = lines[i];
|
|
2690
|
-
const
|
|
2691
|
-
if (
|
|
2692
|
-
const
|
|
2704
|
+
const promptContent = matchTuiPromptLine(line);
|
|
2705
|
+
if (promptContent !== null) {
|
|
2706
|
+
const trimmedContent = stripTuiLineBorders(promptContent).trim();
|
|
2693
2707
|
// Check if the prompt line content contains our message text
|
|
2694
|
-
if (
|
|
2708
|
+
if (trimmedContent.length > 5 && trimmedContent.includes(searchToken)) {
|
|
2695
2709
|
this.logger.warn('Text stuck at TUI prompt — Enter was not pressed', {
|
|
2696
2710
|
sessionName,
|
|
2697
2711
|
searchToken: searchToken.slice(0, 20),
|
|
@@ -2797,7 +2811,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2797
2811
|
for (const sessionName of sessionHelper.listSessions()) {
|
|
2798
2812
|
try {
|
|
2799
2813
|
const output = sessionHelper.capturePane(sessionName);
|
|
2800
|
-
if (
|
|
2814
|
+
if (containsRewindMode(output)) {
|
|
2801
2815
|
this.logger.warn('Rewind mode detected, sending q to exit', { sessionName });
|
|
2802
2816
|
sessionHelper.writeRaw(sessionName, 'q');
|
|
2803
2817
|
await delay(500);
|
|
@@ -2820,19 +2834,19 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2820
2834
|
continue;
|
|
2821
2835
|
// Look for any text sitting on the prompt line
|
|
2822
2836
|
const lines = output.split('\n');
|
|
2823
|
-
const promptLineRegex = /^[│┃║|\s]*>\s+(.+)/;
|
|
2824
2837
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 25); i--) {
|
|
2825
|
-
const
|
|
2826
|
-
if (
|
|
2827
|
-
const
|
|
2838
|
+
const promptContent = matchTuiPromptLine(lines[i]);
|
|
2839
|
+
if (promptContent !== null) {
|
|
2840
|
+
const trimmedContent = stripTuiLineBorders(promptContent).trim();
|
|
2828
2841
|
// Only act on substantial text (> 10 chars) to avoid false positives
|
|
2829
2842
|
// from TUI rendering artifacts or short status text
|
|
2830
|
-
if (
|
|
2843
|
+
if (trimmedContent.length > 10) {
|
|
2831
2844
|
// Skip known Gemini CLI idle placeholder text that sits at
|
|
2832
2845
|
// the `> ` prompt when no user input is present. These are
|
|
2833
2846
|
// NOT stuck messages — they are TUI decoration.
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2847
|
+
const lowerContent = trimmedContent.toLowerCase();
|
|
2848
|
+
const isPlaceholder = lowerContent.startsWith('type your message') ||
|
|
2849
|
+
trimmedContent.startsWith('@'); // e.g., "@path/to/file"
|
|
2836
2850
|
if (isPlaceholder) {
|
|
2837
2851
|
break;
|
|
2838
2852
|
}
|
|
@@ -2927,8 +2941,8 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2927
2941
|
*/
|
|
2928
2942
|
trackSentMessage(sessionName, message) {
|
|
2929
2943
|
// Extract a search snippet: skip [CHAT:uuid] prefix, take first 80 chars
|
|
2930
|
-
const
|
|
2931
|
-
const contentStart =
|
|
2944
|
+
const { prefixLength } = extractChatPrefix(message);
|
|
2945
|
+
const contentStart = prefixLength;
|
|
2932
2946
|
// Normalize whitespace: messages may contain \n from enhanced templates.
|
|
2933
2947
|
// Terminal bottom text is join(' '), so \n in snippet would never match.
|
|
2934
2948
|
const snippet = message.slice(contentStart, contentStart + 80).replace(/\s+/g, ' ').trim();
|
|
@@ -2942,27 +2956,10 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2942
2956
|
// Ensure the background scanner is running
|
|
2943
2957
|
this.startStuckMessageDetector();
|
|
2944
2958
|
}
|
|
2945
|
-
/**
|
|
2946
|
-
* Get the runtime-specific prompt regex pattern.
|
|
2947
|
-
* Avoids false positives by using narrow patterns when runtime is known.
|
|
2948
|
-
*
|
|
2949
|
-
* @param runtimeType - The runtime type (claude-code, gemini-cli, etc.)
|
|
2950
|
-
* @returns The appropriate prompt detection regex
|
|
2951
|
-
*/
|
|
2952
|
-
getPromptPatternForRuntime(runtimeType) {
|
|
2953
|
-
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE)
|
|
2954
|
-
return TERMINAL_PATTERNS.CLAUDE_CODE_PROMPT;
|
|
2955
|
-
if (runtimeType === RUNTIME_TYPES.GEMINI_CLI)
|
|
2956
|
-
return TERMINAL_PATTERNS.GEMINI_CLI_PROMPT;
|
|
2957
|
-
if (runtimeType === RUNTIME_TYPES.CODEX_CLI)
|
|
2958
|
-
return TERMINAL_PATTERNS.CODEX_CLI_PROMPT;
|
|
2959
|
-
return TERMINAL_PATTERNS.PROMPT_STREAM;
|
|
2960
|
-
}
|
|
2961
2959
|
/**
|
|
2962
2960
|
* Check if the agent appears to be at an input prompt.
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
2965
|
-
* to avoid false negatives when the agent is processing.
|
|
2961
|
+
* Delegates to the regex-free isAgentAtPrompt() from terminal-string-ops,
|
|
2962
|
+
* with additional logging and per-line prompt detection using isPromptLine().
|
|
2966
2963
|
*
|
|
2967
2964
|
* @param terminalOutput - The terminal output to check
|
|
2968
2965
|
* @param runtimeType - The runtime type for pattern selection
|
|
@@ -2979,57 +2976,12 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
2979
2976
|
// Use 5000 chars to accommodate large tool outputs that push the prompt
|
|
2980
2977
|
// further back in the buffer (#106).
|
|
2981
2978
|
const tailSection = terminalOutput.slice(-5000);
|
|
2982
|
-
const isGemini = runtimeType === RUNTIME_TYPES.GEMINI_CLI;
|
|
2983
|
-
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
2984
|
-
const isCodex = runtimeType === RUNTIME_TYPES.CODEX_CLI;
|
|
2985
|
-
const streamPattern = this.getPromptPatternForRuntime(runtimeType);
|
|
2986
2979
|
// Check for prompt FIRST. Processing indicators like "thinking" or "analyzing"
|
|
2987
2980
|
// can appear in the agent's previous response text and persist in the terminal
|
|
2988
2981
|
// scroll buffer, causing false negatives if checked before the prompt.
|
|
2989
|
-
if (streamPattern.test(tailSection)) {
|
|
2990
|
-
return true;
|
|
2991
|
-
}
|
|
2992
|
-
// Fallback: check last several lines for prompt indicators.
|
|
2993
|
-
// The prompt may not be on the very last line due to status bars,
|
|
2994
|
-
// notifications, or terminal wrapping below the prompt.
|
|
2995
2982
|
const lines = tailSection.split('\n').filter((line) => line.trim().length > 0);
|
|
2996
2983
|
const linesToCheck = lines.slice(-10);
|
|
2997
|
-
const hasPrompt = linesToCheck.some(
|
|
2998
|
-
const trimmed = line.trim();
|
|
2999
|
-
// Strip TUI box-drawing borders that Gemini CLI and other TUI frameworks
|
|
3000
|
-
// wrap around prompts. Covers full Unicode box-drawing range (#106).
|
|
3001
|
-
const stripped = trimmed
|
|
3002
|
-
.replace(/^[\u2500-\u257F|+\-═║╭╮╰╯]+\s*/, '')
|
|
3003
|
-
.replace(/\s*[\u2500-\u257F|+\-═║╭╮╰╯]+$/, '');
|
|
3004
|
-
// Claude Code prompts: ❯, ⏵, $ alone on a line
|
|
3005
|
-
if (!isGemini && !isCodex) {
|
|
3006
|
-
if (['❯', '⏵', '$'].some(ch => trimmed === ch || stripped === ch)) {
|
|
3007
|
-
return true;
|
|
3008
|
-
}
|
|
3009
|
-
// ❯❯ = bypass permissions prompt (idle).
|
|
3010
|
-
// Matches "❯❯", "❯❯ ", and "❯❯ bypass permissions on (shift+tab to cycle)".
|
|
3011
|
-
// Note: ⏵⏵ appears in the status bar but is visible both when idle AND
|
|
3012
|
-
// busy, so it cannot be used as a reliable prompt indicator.
|
|
3013
|
-
if (trimmed.startsWith('❯❯')) {
|
|
3014
|
-
return true;
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
// Gemini CLI prompts: > or ! followed by space
|
|
3018
|
-
if (!isClaudeCode) {
|
|
3019
|
-
if (isCodex) {
|
|
3020
|
-
// Codex prompt uses `›`; avoid plain `> ` to prevent false-positives
|
|
3021
|
-
// from markdown blockquotes in agent output.
|
|
3022
|
-
if (trimmed.startsWith('› ') || stripped.startsWith('› ')) {
|
|
3023
|
-
return true;
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
else if (trimmed.startsWith('> ') || trimmed.startsWith('! ') ||
|
|
3027
|
-
stripped.startsWith('> ') || stripped.startsWith('! ')) {
|
|
3028
|
-
return true;
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
return false;
|
|
3032
|
-
});
|
|
2984
|
+
const hasPrompt = linesToCheck.some(line => isPromptLine(line, runtimeType));
|
|
3033
2985
|
if (hasPrompt) {
|
|
3034
2986
|
return true;
|
|
3035
2987
|
}
|
|
@@ -3037,14 +2989,14 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
3037
2989
|
// Check last 10 lines (not just 5) because tool output can push processing
|
|
3038
2990
|
// indicators further up while the status bar stays at the bottom.
|
|
3039
2991
|
const recentLines = linesToCheck.join('\n');
|
|
3040
|
-
if (
|
|
2992
|
+
if (containsProcessingIndicator(recentLines)) {
|
|
3041
2993
|
this.logger.debug('Processing indicators present near bottom of output');
|
|
3042
2994
|
return false;
|
|
3043
2995
|
}
|
|
3044
2996
|
// Check for "esc to interrupt" in the status bar — this is a definitive
|
|
3045
2997
|
// busy signal. Claude Code only shows this text while actively processing.
|
|
3046
2998
|
// It disappears when the agent returns to idle at the prompt.
|
|
3047
|
-
if (
|
|
2999
|
+
if (containsBusyStatusBar(recentLines)) {
|
|
3048
3000
|
this.logger.debug('Busy status bar detected (esc to interrupt)');
|
|
3049
3001
|
return false;
|
|
3050
3002
|
}
|
|
@@ -3302,7 +3254,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
|
|
|
3302
3254
|
return false;
|
|
3303
3255
|
const currentOutput = sessionHelper.capturePane(sessionName);
|
|
3304
3256
|
// Processing indicators (spinners) = definitive success
|
|
3305
|
-
if (
|
|
3257
|
+
if (containsSpinnerOrWorkingIndicator(currentOutput)) {
|
|
3306
3258
|
this.logger.debug('Kickoff delivered — processing indicators detected', {
|
|
3307
3259
|
sessionName, checkIndex: i,
|
|
3308
3260
|
});
|