commandmate 0.1.11 → 0.2.0
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/.env.example +4 -9
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +25 -25
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +7 -7
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +7 -7
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js +1 -19
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +36 -0
- package/.next/server/app/api/repositories/excluded/route.js.nft.json +1 -0
- package/.next/server/app/api/repositories/excluded.body +1 -0
- package/.next/server/app/api/repositories/excluded.meta +1 -0
- package/.next/server/app/api/repositories/restore/route.js +36 -0
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/repositories/route.js +36 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +36 -1
- package/.next/server/app/api/slash-commands/route.js +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/page.js +7 -7
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +2 -2
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +4 -4
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +12 -10
- package/.next/server/chunks/5488.js +36 -0
- package/.next/server/chunks/6550.js +1 -1
- package/.next/server/chunks/7425.js +53 -50
- package/.next/server/chunks/7536.js +1 -0
- package/.next/server/chunks/8174.js +23 -0
- package/.next/server/chunks/9367.js +19 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +2 -28
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/4327.740cc7fe2d0b5049.js +60 -0
- package/.next/static/chunks/4343-ebe884a2a80eb033.js +1 -0
- package/.next/static/chunks/6568-38a33aa67d82e12b.js +1 -0
- package/.next/static/chunks/816-c254f4e2406e696a.js +1 -0
- package/.next/static/chunks/app/layout-4804cfba519283cf.js +1 -0
- package/.next/static/chunks/app/page-3926224c4cdf315b.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-d64624eb67af57c0.js +1 -0
- package/.next/static/chunks/main-b6d727aa9248d4f2.js +1 -0
- package/.next/static/chunks/{webpack-3fc79fab9bb738d7.js → webpack-4f85dcef6279c6ee.js} +1 -1
- package/.next/static/css/28be35e4727ae7ef.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/repositories/excluded/route.ts +343 -0
- package/.next/types/app/api/repositories/restore/route.ts +343 -0
- package/README.md +2 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -13
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +3 -7
- package/dist/cli/config/security-messages.d.ts +11 -0
- package/dist/cli/config/security-messages.d.ts.map +1 -0
- package/dist/cli/config/security-messages.js +29 -0
- package/dist/cli/types/index.d.ts +0 -1
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +3 -7
- package/dist/cli/utils/env-setup.d.ts +0 -4
- package/dist/cli/utils/env-setup.d.ts.map +1 -1
- package/dist/cli/utils/env-setup.js +0 -14
- package/dist/cli/utils/security-logger.d.ts.map +1 -1
- package/dist/cli/utils/security-logger.js +1 -2
- package/dist/server/src/lib/auto-yes-manager.js +19 -12
- package/dist/server/src/lib/claude-poller.js +337 -0
- package/dist/server/src/lib/claude-session.js +134 -28
- package/dist/server/src/lib/cli-patterns.js +9 -2
- package/dist/server/src/lib/cli-tools/base.js +7 -1
- package/dist/server/src/lib/cli-tools/codex.js +14 -2
- package/dist/server/src/lib/cli-tools/manager.js +27 -0
- package/dist/server/src/lib/cli-tools/types.js +7 -0
- package/dist/server/src/lib/cli-tools/validation.js +41 -0
- package/dist/server/src/lib/db.js +23 -0
- package/dist/server/src/lib/env.js +0 -17
- package/dist/server/src/lib/logger.js +0 -4
- package/dist/server/src/lib/prompt-detector.js +129 -31
- package/dist/server/src/lib/ws-server.js +12 -1
- package/dist/server/src/types/sidebar.js +16 -31
- package/dist/server/src/types/slash-commands.js +2 -0
- package/package.json +1 -1
- package/.next/server/chunks/1318.js +0 -29
- package/.next/server/chunks/1528.js +0 -1
- package/.next/server/chunks/7213.js +0 -1
- package/.next/server/chunks/9703.js +0 -31
- package/.next/server/chunks/9723.js +0 -19
- package/.next/server/edge-runtime-webpack.js +0 -2
- package/.next/server/edge-runtime-webpack.js.map +0 -1
- package/.next/server/src/middleware.js +0 -14
- package/.next/server/src/middleware.js.map +0 -1
- package/.next/static/chunks/2853-d11a80b03c9a1640.js +0 -1
- package/.next/static/chunks/4327.3b84aa049900fdeb.js +0 -60
- package/.next/static/chunks/816-7e340dad784be28c.js +0 -1
- package/.next/static/chunks/9365-733d8c05712d2888.js +0 -1
- package/.next/static/chunks/app/layout-37e55f11dcc8b1bf.js +0 -1
- package/.next/static/chunks/app/page-fe35d61f14b90a51.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-720605c2fb074444.js +0 -1
- package/.next/static/chunks/main-a960f4a5e1a2f598.js +0 -1
- package/.next/static/css/376b339640084689.css +0 -3
- /package/.next/static/{gRNW5YXY43KqCKbCdaJoJ → bdUePCj-b9Gv5okYGp49O}/_buildManifest.js +0 -0
- /package/.next/static/{gRNW5YXY43KqCKbCdaJoJ → bdUePCj-b9Gv5okYGp49O}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude response polling
|
|
4
|
+
* Periodically checks tmux sessions for Claude responses
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.startPolling = startPolling;
|
|
8
|
+
exports.stopPolling = stopPolling;
|
|
9
|
+
exports.stopAllPolling = stopAllPolling;
|
|
10
|
+
exports.getActivePollers = getActivePollers;
|
|
11
|
+
const claude_session_1 = require("./claude-session");
|
|
12
|
+
const db_instance_1 = require("./db-instance");
|
|
13
|
+
const db_1 = require("./db");
|
|
14
|
+
const ws_server_1 = require("./ws-server");
|
|
15
|
+
const prompt_detector_1 = require("./prompt-detector");
|
|
16
|
+
const conversation_logger_1 = require("./conversation-logger");
|
|
17
|
+
/**
|
|
18
|
+
* Polling interval in milliseconds (default: 2 seconds)
|
|
19
|
+
*/
|
|
20
|
+
const POLLING_INTERVAL = 2000;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum polling duration in milliseconds (default: 5 minutes)
|
|
23
|
+
*/
|
|
24
|
+
const MAX_POLLING_DURATION = 5 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Active pollers map: worktreeId -> NodeJS.Timeout
|
|
27
|
+
*/
|
|
28
|
+
const activePollers = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Polling start times map: worktreeId -> timestamp
|
|
31
|
+
*/
|
|
32
|
+
const pollingStartTimes = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Extract Claude response from tmux output
|
|
35
|
+
* Detects when Claude has completed a response by looking for the prompt
|
|
36
|
+
*
|
|
37
|
+
* @param output - Full tmux output
|
|
38
|
+
* @param lastCapturedLine - Number of lines previously captured
|
|
39
|
+
* @returns Extracted response or null if incomplete
|
|
40
|
+
*/
|
|
41
|
+
function extractClaudeResponse(output, lastCapturedLine) {
|
|
42
|
+
// Trim trailing empty lines from the output before processing
|
|
43
|
+
// This prevents the "last 20 lines" from being all empty due to tmux buffer padding
|
|
44
|
+
const rawLines = output.split('\n');
|
|
45
|
+
let trimmedLength = rawLines.length;
|
|
46
|
+
while (trimmedLength > 0 && rawLines[trimmedLength - 1].trim() === '') {
|
|
47
|
+
trimmedLength--;
|
|
48
|
+
}
|
|
49
|
+
const lines = rawLines.slice(0, trimmedLength);
|
|
50
|
+
const totalLines = lines.length;
|
|
51
|
+
// No new output (with buffer to handle newline inconsistencies)
|
|
52
|
+
if (totalLines < lastCapturedLine - 5) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
// Always check the last 20 lines for completion pattern (more robust than tracking line numbers)
|
|
56
|
+
const checkLineCount = 20;
|
|
57
|
+
const startLine = Math.max(0, totalLines - checkLineCount);
|
|
58
|
+
const linesToCheck = lines.slice(startLine);
|
|
59
|
+
const outputToCheck = linesToCheck.join('\n');
|
|
60
|
+
// Check if Claude has returned to prompt (indicated by the prompt symbols)
|
|
61
|
+
// Claude shows "> " or "❯ " or "─────" when waiting for input
|
|
62
|
+
// Supports both legacy '>' and new '❯' (U+276F) prompt characters
|
|
63
|
+
// Issue #132: Also matches prompts with recommended commands (e.g., "❯ /work-plan")
|
|
64
|
+
const promptPattern = /^[>❯](\s*$|\s+\S)/m;
|
|
65
|
+
const separatorPattern = /^─{50,}$/m;
|
|
66
|
+
// Check for thinking/processing indicators
|
|
67
|
+
// Claude shows various animations while thinking: ✻ Herding…, · Choreographing…, ∴ Thinking…, ✢ Doing…, ✳ Cascading…, etc.
|
|
68
|
+
// Match lines that contain: symbol + word + … OR just the symbol alone
|
|
69
|
+
const thinkingPattern = /[✻✽⏺·∴✢✳]/m;
|
|
70
|
+
const hasPrompt = promptPattern.test(outputToCheck);
|
|
71
|
+
const hasSeparator = separatorPattern.test(outputToCheck);
|
|
72
|
+
const isThinking = thinkingPattern.test(outputToCheck);
|
|
73
|
+
// Only consider complete if we have prompt + separator AND Claude is NOT thinking
|
|
74
|
+
if (hasPrompt && hasSeparator && !isThinking) {
|
|
75
|
+
// Claude has completed response
|
|
76
|
+
// Extract the response content from lastCapturedLine to the separator (not just last 20 lines)
|
|
77
|
+
const responseLines = [];
|
|
78
|
+
// Handle tmux buffer scrolling: if lastCapturedLine >= totalLines, the buffer has scrolled
|
|
79
|
+
// In this case, we need to find the response in the current visible buffer
|
|
80
|
+
let startIndex;
|
|
81
|
+
if (lastCapturedLine >= totalLines - 5) {
|
|
82
|
+
// Buffer may have scrolled - look for the start of the new response
|
|
83
|
+
// Find the last user input prompt ("> ...") to identify where the response starts
|
|
84
|
+
let foundUserPrompt = -1;
|
|
85
|
+
for (let i = totalLines - 1; i >= Math.max(0, totalLines - 50); i--) {
|
|
86
|
+
// Look for user input line (starts with "> " or "❯ " followed by content)
|
|
87
|
+
if (/^[>❯]\s+\S/.test(lines[i])) {
|
|
88
|
+
foundUserPrompt = i;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Start extraction from after the user prompt, or from a safe earlier point
|
|
93
|
+
startIndex = foundUserPrompt >= 0 ? foundUserPrompt + 1 : Math.max(0, totalLines - 40);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Normal case: start from lastCapturedLine
|
|
97
|
+
startIndex = Math.max(0, lastCapturedLine);
|
|
98
|
+
}
|
|
99
|
+
for (let i = startIndex; i < totalLines; i++) {
|
|
100
|
+
const line = lines[i];
|
|
101
|
+
// Skip separator lines
|
|
102
|
+
if (/^─{50,}$/.test(line)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Stop at new prompt (supports both '>' and '❯')
|
|
106
|
+
if (/^[>❯]\s*$/.test(line)) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
// Skip thinking/processing status lines (spinner char + activity text ending with …)
|
|
110
|
+
// Note: ⏺ is also used as a response marker, so we only skip if it looks like a thinking line
|
|
111
|
+
// Thinking line example: "✳ UIからジョブ再実行中… (esc to interrupt · 33m 44s · thinking)"
|
|
112
|
+
// Response line example: "⏺ 何かお手伝いできることはありますか?" (should NOT be skipped)
|
|
113
|
+
if (/[✻✽·∴✢✳⦿◉●○◌◎⊙⊚⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*\S+…/.test(line) || /to interrupt\)/.test(line)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Skip tip lines and decoration lines
|
|
117
|
+
if (/^\s*[⎿⏋]\s+Tip:/.test(line) || /^\s*Tip:/.test(line)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
responseLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
const response = responseLines.join('\n').trim();
|
|
123
|
+
// Additional check: ensure response doesn't contain thinking indicators
|
|
124
|
+
// This prevents saving intermediate states as final responses
|
|
125
|
+
if (thinkingPattern.test(response)) {
|
|
126
|
+
console.warn(`[Poller] Response contains thinking indicators, treating as incomplete`);
|
|
127
|
+
return {
|
|
128
|
+
response: '',
|
|
129
|
+
isComplete: false,
|
|
130
|
+
lineCount: totalLines,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
response,
|
|
135
|
+
isComplete: true,
|
|
136
|
+
lineCount: totalLines,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Check if this is an interactive prompt (yes/no or multiple choice)
|
|
140
|
+
// Interactive prompts don't have the ">" prompt and separator, so we need to detect them separately
|
|
141
|
+
if (!isThinking) {
|
|
142
|
+
const fullOutput = lines.join('\n');
|
|
143
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(fullOutput);
|
|
144
|
+
if (promptDetection.isPrompt) {
|
|
145
|
+
// This is an interactive prompt - consider it complete
|
|
146
|
+
return {
|
|
147
|
+
response: fullOutput,
|
|
148
|
+
isComplete: true,
|
|
149
|
+
lineCount: totalLines,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Response not yet complete
|
|
154
|
+
return {
|
|
155
|
+
response: '',
|
|
156
|
+
isComplete: false,
|
|
157
|
+
lineCount: totalLines,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check for Claude response once
|
|
162
|
+
*
|
|
163
|
+
* @param worktreeId - Worktree ID
|
|
164
|
+
* @returns True if response was found and processed
|
|
165
|
+
*/
|
|
166
|
+
async function checkForResponse(worktreeId) {
|
|
167
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
168
|
+
try {
|
|
169
|
+
// Get worktree to retrieve CLI tool ID
|
|
170
|
+
const worktree = (0, db_1.getWorktreeById)(db, worktreeId);
|
|
171
|
+
if (!worktree) {
|
|
172
|
+
console.error(`Worktree ${worktreeId} not found, stopping poller`);
|
|
173
|
+
stopPolling(worktreeId);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const cliToolId = worktree.cliToolId || 'claude';
|
|
177
|
+
// Check if Claude session is running
|
|
178
|
+
const running = await (0, claude_session_1.isClaudeRunning)(worktreeId);
|
|
179
|
+
if (!running) {
|
|
180
|
+
console.log(`Claude session not running for ${worktreeId}, stopping poller`);
|
|
181
|
+
stopPolling(worktreeId);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
// Get session state (last captured line count)
|
|
185
|
+
const sessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
|
|
186
|
+
const lastCapturedLine = sessionState?.lastCapturedLine || 0;
|
|
187
|
+
// Capture current output
|
|
188
|
+
const output = await (0, claude_session_1.captureClaudeOutput)(worktreeId, 10000);
|
|
189
|
+
// Extract response
|
|
190
|
+
const result = extractClaudeResponse(output, lastCapturedLine);
|
|
191
|
+
if (!result) {
|
|
192
|
+
// No new output
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (!result.isComplete) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
// Response is complete! Check if it's a prompt
|
|
199
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(result.response);
|
|
200
|
+
if (promptDetection.isPrompt) {
|
|
201
|
+
// This is a prompt - save as prompt message
|
|
202
|
+
console.log(`✓ Detected prompt for ${worktreeId}:`, promptDetection.promptData?.question);
|
|
203
|
+
const message = (0, db_1.createMessage)(db, {
|
|
204
|
+
worktreeId,
|
|
205
|
+
role: 'assistant',
|
|
206
|
+
content: promptDetection.cleanContent,
|
|
207
|
+
messageType: 'prompt',
|
|
208
|
+
promptData: promptDetection.promptData,
|
|
209
|
+
timestamp: new Date(),
|
|
210
|
+
cliToolId,
|
|
211
|
+
});
|
|
212
|
+
// Update session state
|
|
213
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
214
|
+
// Broadcast to WebSocket
|
|
215
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
216
|
+
worktreeId,
|
|
217
|
+
message,
|
|
218
|
+
});
|
|
219
|
+
console.log(`✓ Saved prompt message for ${worktreeId}`);
|
|
220
|
+
// Stop polling - waiting for user response
|
|
221
|
+
stopPolling(worktreeId);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
// Normal response (not a prompt)
|
|
225
|
+
console.log(`✓ Detected Claude response for ${worktreeId}`);
|
|
226
|
+
// Validate response content is not empty
|
|
227
|
+
if (!result.response || result.response.trim() === '') {
|
|
228
|
+
console.warn(`⚠ Empty response detected for ${worktreeId}, continuing polling...`);
|
|
229
|
+
// Update session state but don't save the message
|
|
230
|
+
// Continue polling in case a prompt appears next
|
|
231
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// Create Markdown log file for the conversation pair
|
|
235
|
+
if (result.response) {
|
|
236
|
+
await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, result.response, 'claude');
|
|
237
|
+
}
|
|
238
|
+
// Create Claude message in database
|
|
239
|
+
const message = (0, db_1.createMessage)(db, {
|
|
240
|
+
worktreeId,
|
|
241
|
+
role: 'assistant',
|
|
242
|
+
content: result.response,
|
|
243
|
+
messageType: 'normal',
|
|
244
|
+
timestamp: new Date(),
|
|
245
|
+
cliToolId,
|
|
246
|
+
});
|
|
247
|
+
// Update session state
|
|
248
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
249
|
+
// Broadcast message to WebSocket clients
|
|
250
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
251
|
+
worktreeId,
|
|
252
|
+
message,
|
|
253
|
+
});
|
|
254
|
+
console.log(`✓ Saved Claude response for ${worktreeId}`);
|
|
255
|
+
// Stop polling since we got the response
|
|
256
|
+
stopPolling(worktreeId);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
261
|
+
console.error(`Error checking for response (${worktreeId}):`, errorMessage);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Start polling for Claude response
|
|
267
|
+
*
|
|
268
|
+
* @param worktreeId - Worktree ID
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* startPolling('feature-foo');
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
function startPolling(worktreeId) {
|
|
276
|
+
// Stop existing poller if any
|
|
277
|
+
stopPolling(worktreeId);
|
|
278
|
+
console.log(`Starting poller for ${worktreeId}`);
|
|
279
|
+
// Record start time
|
|
280
|
+
pollingStartTimes.set(worktreeId, Date.now());
|
|
281
|
+
// Start polling
|
|
282
|
+
const interval = setInterval(async () => {
|
|
283
|
+
console.log(`[Poller] Checking for response: ${worktreeId}`);
|
|
284
|
+
const startTime = pollingStartTimes.get(worktreeId);
|
|
285
|
+
// Check if max duration exceeded
|
|
286
|
+
if (startTime && Date.now() - startTime > MAX_POLLING_DURATION) {
|
|
287
|
+
console.log(`Polling timeout for ${worktreeId}, stopping`);
|
|
288
|
+
stopPolling(worktreeId);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Check for response
|
|
292
|
+
try {
|
|
293
|
+
await checkForResponse(worktreeId);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error(`[Poller] Error in checkForResponse:`, error);
|
|
297
|
+
}
|
|
298
|
+
}, POLLING_INTERVAL);
|
|
299
|
+
activePollers.set(worktreeId, interval);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Stop polling for a worktree
|
|
303
|
+
*
|
|
304
|
+
* @param worktreeId - Worktree ID
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* stopPolling('feature-foo');
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
function stopPolling(worktreeId) {
|
|
312
|
+
const interval = activePollers.get(worktreeId);
|
|
313
|
+
if (interval) {
|
|
314
|
+
clearInterval(interval);
|
|
315
|
+
activePollers.delete(worktreeId);
|
|
316
|
+
pollingStartTimes.delete(worktreeId);
|
|
317
|
+
console.log(`Stopped poller for ${worktreeId}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Stop all active pollers
|
|
322
|
+
* Used for cleanup on server shutdown
|
|
323
|
+
*/
|
|
324
|
+
function stopAllPolling() {
|
|
325
|
+
console.log(`Stopping all pollers (${activePollers.size} active)`);
|
|
326
|
+
for (const worktreeId of activePollers.keys()) {
|
|
327
|
+
stopPolling(worktreeId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get list of active pollers
|
|
332
|
+
*
|
|
333
|
+
* @returns Array of worktree IDs currently being polled
|
|
334
|
+
*/
|
|
335
|
+
function getActivePollers() {
|
|
336
|
+
return Array.from(activePollers.keys());
|
|
337
|
+
}
|
|
@@ -4,19 +4,84 @@
|
|
|
4
4
|
* Manages Claude CLI sessions within tmux for each worktree
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
|
|
7
8
|
exports.getSessionName = getSessionName;
|
|
8
9
|
exports.isClaudeInstalled = isClaudeInstalled;
|
|
9
10
|
exports.isClaudeRunning = isClaudeRunning;
|
|
10
11
|
exports.getClaudeSessionState = getClaudeSessionState;
|
|
12
|
+
exports.waitForPrompt = waitForPrompt;
|
|
11
13
|
exports.startClaudeSession = startClaudeSession;
|
|
12
14
|
exports.sendMessageToClaude = sendMessageToClaude;
|
|
13
15
|
exports.captureClaudeOutput = captureClaudeOutput;
|
|
14
16
|
exports.stopClaudeSession = stopClaudeSession;
|
|
15
17
|
exports.restartClaudeSession = restartClaudeSession;
|
|
16
18
|
const tmux_1 = require("./tmux");
|
|
19
|
+
const cli_patterns_1 = require("./cli-patterns");
|
|
17
20
|
const child_process_1 = require("child_process");
|
|
18
21
|
const util_1 = require("util");
|
|
19
22
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
23
|
+
// ----- Helper Functions -----
|
|
24
|
+
/**
|
|
25
|
+
* Extract error message from unknown error type
|
|
26
|
+
* Provides consistent error message extraction across the module (DRY)
|
|
27
|
+
*
|
|
28
|
+
* @param error - Unknown error object
|
|
29
|
+
* @returns Error message string
|
|
30
|
+
*/
|
|
31
|
+
function getErrorMessage(error) {
|
|
32
|
+
return error instanceof Error ? error.message : String(error);
|
|
33
|
+
}
|
|
34
|
+
// ----- Timeout and Polling Constants (OCP-001) -----
|
|
35
|
+
// These constants are exported to allow configuration and testing.
|
|
36
|
+
// Changing these values affects Claude CLI session startup behavior.
|
|
37
|
+
/**
|
|
38
|
+
* Claude CLI initialization max wait time (milliseconds)
|
|
39
|
+
*
|
|
40
|
+
* This timeout allows sufficient time for Claude CLI to:
|
|
41
|
+
* - Load and initialize its internal state
|
|
42
|
+
* - Authenticate with Anthropic servers (if needed)
|
|
43
|
+
* - Display the interactive prompt
|
|
44
|
+
*
|
|
45
|
+
* 15 seconds provides headroom for slower networks or cold starts.
|
|
46
|
+
*/
|
|
47
|
+
exports.CLAUDE_INIT_TIMEOUT = 15000;
|
|
48
|
+
/**
|
|
49
|
+
* Initialization polling interval (milliseconds)
|
|
50
|
+
*
|
|
51
|
+
* How frequently we check if Claude CLI has finished initializing.
|
|
52
|
+
* 300ms balances responsiveness with avoiding excessive polling overhead.
|
|
53
|
+
*/
|
|
54
|
+
exports.CLAUDE_INIT_POLL_INTERVAL = 300;
|
|
55
|
+
/**
|
|
56
|
+
* Stability delay after prompt detection (milliseconds)
|
|
57
|
+
*
|
|
58
|
+
* This delay is necessary because Claude CLI renders its UI progressively:
|
|
59
|
+
* 1. The prompt character (> or U+276F) appears first
|
|
60
|
+
* 2. Additional UI elements (tips, suggestions) may render afterward
|
|
61
|
+
* 3. Sending input too quickly can interrupt this rendering process
|
|
62
|
+
*
|
|
63
|
+
* The 500ms value was empirically determined to provide sufficient buffer
|
|
64
|
+
* for Claude CLI to complete its initialization rendering while maintaining
|
|
65
|
+
* responsive user experience. (DOC-001)
|
|
66
|
+
*
|
|
67
|
+
* @see Issue #152 - First message not being sent after session start
|
|
68
|
+
*/
|
|
69
|
+
exports.CLAUDE_POST_PROMPT_DELAY = 500;
|
|
70
|
+
/**
|
|
71
|
+
* Prompt wait timeout before message send (milliseconds)
|
|
72
|
+
*
|
|
73
|
+
* When sending a message, we first verify Claude is at a prompt state.
|
|
74
|
+
* This timeout limits how long we wait for Claude to return to prompt
|
|
75
|
+
* if it's still processing a previous request.
|
|
76
|
+
*/
|
|
77
|
+
exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
|
|
78
|
+
/**
|
|
79
|
+
* Prompt wait polling interval (milliseconds)
|
|
80
|
+
*
|
|
81
|
+
* How frequently we check for prompt state before sending messages.
|
|
82
|
+
* 200ms provides quick response while minimizing CPU usage.
|
|
83
|
+
*/
|
|
84
|
+
exports.CLAUDE_PROMPT_POLL_INTERVAL = 200;
|
|
20
85
|
/**
|
|
21
86
|
* Cached Claude CLI path
|
|
22
87
|
*/
|
|
@@ -122,6 +187,35 @@ async function getClaudeSessionState(worktreeId) {
|
|
|
122
187
|
lastActivity: new Date(),
|
|
123
188
|
};
|
|
124
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Wait for session to be at prompt state
|
|
192
|
+
* Polls for prompt detection using CLAUDE_PROMPT_PATTERN (DRY-001)
|
|
193
|
+
*
|
|
194
|
+
* @param sessionName - tmux session name
|
|
195
|
+
* @param timeout - Timeout in milliseconds (default: CLAUDE_PROMPT_WAIT_TIMEOUT)
|
|
196
|
+
* @throws {Error} If prompt is not detected within timeout
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* await waitForPrompt('mcbd-claude-feature-foo');
|
|
201
|
+
* // Session is now ready to receive input
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_TIMEOUT) {
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const pollInterval = exports.CLAUDE_PROMPT_POLL_INTERVAL;
|
|
207
|
+
while (Date.now() - startTime < timeout) {
|
|
208
|
+
// Use -50 lines to capture more context including status bars
|
|
209
|
+
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
210
|
+
// DRY-001: Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts
|
|
211
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
212
|
+
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
|
|
213
|
+
return; // Prompt detected
|
|
214
|
+
}
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`Prompt detection timeout (${timeout}ms)`);
|
|
218
|
+
}
|
|
125
219
|
/**
|
|
126
220
|
* Start a Claude CLI session in tmux
|
|
127
221
|
*
|
|
@@ -162,18 +256,25 @@ async function startClaudeSession(options) {
|
|
|
162
256
|
const claudePath = await getClaudePath();
|
|
163
257
|
// Start Claude CLI in interactive mode using dynamically resolved path
|
|
164
258
|
await (0, tmux_1.sendKeys)(sessionName, claudePath, true);
|
|
165
|
-
// Wait for Claude to initialize with dynamic detection
|
|
166
|
-
//
|
|
167
|
-
const maxWaitTime =
|
|
168
|
-
const pollInterval =
|
|
259
|
+
// Wait for Claude to initialize with dynamic detection (OCP-001)
|
|
260
|
+
// Use constants instead of hardcoded values
|
|
261
|
+
const maxWaitTime = exports.CLAUDE_INIT_TIMEOUT;
|
|
262
|
+
const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
|
|
169
263
|
const startTime = Date.now();
|
|
264
|
+
let initialized = false;
|
|
170
265
|
while (Date.now() - startTime < maxWaitTime) {
|
|
171
266
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
172
267
|
try {
|
|
173
268
|
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
174
|
-
// Claude is ready when we see the prompt
|
|
175
|
-
|
|
176
|
-
|
|
269
|
+
// Claude is ready when we see the prompt or separator line (DRY-001, DRY-002)
|
|
270
|
+
// Use patterns from cli-patterns.ts for consistency
|
|
271
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
272
|
+
const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
|
|
273
|
+
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput) || cli_patterns_1.CLAUDE_SEPARATOR_PATTERN.test(cleanOutput)) {
|
|
274
|
+
// Wait for stability after prompt detection (CONS-007, DOC-001)
|
|
275
|
+
await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
|
|
276
|
+
console.log(`Claude initialized in ${Date.now() - startTime}ms`);
|
|
277
|
+
initialized = true;
|
|
177
278
|
break;
|
|
178
279
|
}
|
|
179
280
|
}
|
|
@@ -181,11 +282,14 @@ async function startClaudeSession(options) {
|
|
|
181
282
|
// Ignore capture errors during initialization
|
|
182
283
|
}
|
|
183
284
|
}
|
|
184
|
-
|
|
285
|
+
// Throw error on timeout instead of silently continuing (CONS-005, IMP-001)
|
|
286
|
+
if (!initialized) {
|
|
287
|
+
throw new Error(`Claude initialization timeout (${exports.CLAUDE_INIT_TIMEOUT}ms)`);
|
|
288
|
+
}
|
|
289
|
+
console.log(`Started Claude session: ${sessionName}`);
|
|
185
290
|
}
|
|
186
291
|
catch (error) {
|
|
187
|
-
|
|
188
|
-
throw new Error(`Failed to start Claude session: ${errorMessage}`);
|
|
292
|
+
throw new Error(`Failed to start Claude session: ${getErrorMessage(error)}`);
|
|
189
293
|
}
|
|
190
294
|
}
|
|
191
295
|
/**
|
|
@@ -207,21 +311,25 @@ async function sendMessageToClaude(worktreeId, message) {
|
|
|
207
311
|
if (!exists) {
|
|
208
312
|
throw new Error(`Claude session ${sessionName} does not exist. Start the session first.`);
|
|
209
313
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
314
|
+
// Verify prompt state before sending (CONS-006, DRY-001)
|
|
315
|
+
// Use -50 lines to ensure we capture the prompt even with status bars
|
|
316
|
+
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
317
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
318
|
+
if (!cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
|
|
319
|
+
// Wait for prompt if not at prompt state
|
|
320
|
+
// Use longer timeout (10s) to handle slow responses
|
|
321
|
+
try {
|
|
322
|
+
await waitForPrompt(sessionName, 10000);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Log warning but don't block - Claude might be in a special state
|
|
326
|
+
console.warn(`[sendMessageToClaude] Prompt not detected, sending anyway`);
|
|
327
|
+
}
|
|
224
328
|
}
|
|
329
|
+
// Send message using sendKeys consistently (CONS-001)
|
|
330
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false); // Message without Enter
|
|
331
|
+
await (0, tmux_1.sendKeys)(sessionName, '', true); // Enter key
|
|
332
|
+
console.log(`Sent message to Claude session: ${sessionName}`);
|
|
225
333
|
}
|
|
226
334
|
/**
|
|
227
335
|
* Capture Claude session output
|
|
@@ -247,8 +355,7 @@ async function captureClaudeOutput(worktreeId, lines = 1000) {
|
|
|
247
355
|
return await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
|
|
248
356
|
}
|
|
249
357
|
catch (error) {
|
|
250
|
-
|
|
251
|
-
throw new Error(`Failed to capture Claude output: ${errorMessage}`);
|
|
358
|
+
throw new Error(`Failed to capture Claude output: ${getErrorMessage(error)}`);
|
|
252
359
|
}
|
|
253
360
|
}
|
|
254
361
|
/**
|
|
@@ -282,8 +389,7 @@ async function stopClaudeSession(worktreeId) {
|
|
|
282
389
|
return killed;
|
|
283
390
|
}
|
|
284
391
|
catch (error) {
|
|
285
|
-
|
|
286
|
-
console.error(`Error stopping Claude session: ${errorMessage}`);
|
|
392
|
+
console.error(`Error stopping Claude session: ${getErrorMessage(error)}`);
|
|
287
393
|
return false;
|
|
288
394
|
}
|
|
289
395
|
}
|
|
@@ -28,8 +28,9 @@ exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.jo
|
|
|
28
28
|
/**
|
|
29
29
|
* Codex thinking pattern
|
|
30
30
|
* Matches activity indicators like "• Planning", "• Searching", etc.
|
|
31
|
+
* T1.1: Extended to include "Ran" and "Deciding"
|
|
31
32
|
*/
|
|
32
|
-
exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing)/m;
|
|
33
|
+
exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing|Ran|Deciding)/m;
|
|
33
34
|
/**
|
|
34
35
|
* Claude prompt pattern (waiting for input)
|
|
35
36
|
* Supports both legacy '>' and new '❯' (U+276F) prompt characters
|
|
@@ -46,8 +47,9 @@ exports.CLAUDE_PROMPT_PATTERN = /^[>❯](\s*$|\s+\S)/m;
|
|
|
46
47
|
exports.CLAUDE_SEPARATOR_PATTERN = /^─{10,}$/m;
|
|
47
48
|
/**
|
|
48
49
|
* Codex prompt pattern
|
|
50
|
+
* T1.2: Improved to detect empty prompts as well
|
|
49
51
|
*/
|
|
50
|
-
exports.CODEX_PROMPT_PATTERN = /^›\s
|
|
52
|
+
exports.CODEX_PROMPT_PATTERN = /^›\s*/m;
|
|
51
53
|
/**
|
|
52
54
|
* Codex separator pattern
|
|
53
55
|
*/
|
|
@@ -114,6 +116,11 @@ function getCliToolPatterns(cliToolId) {
|
|
|
114
116
|
/^\s*for shortcuts$/, // Shortcuts hint
|
|
115
117
|
/╭─+╮/, // Box drawing (top)
|
|
116
118
|
/╰─+╯/, // Box drawing (bottom)
|
|
119
|
+
// T1.3: Additional skip patterns for Codex
|
|
120
|
+
/•\s*Ran\s+/, // Command execution lines
|
|
121
|
+
/^\s*└/, // Tree output (completion indicator)
|
|
122
|
+
/^\s*│/, // Continuation lines
|
|
123
|
+
/\(.*esc to interrupt\)/, // Interrupt hint
|
|
117
124
|
],
|
|
118
125
|
};
|
|
119
126
|
case 'gemini':
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.BaseCLITool = void 0;
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const util_1 = require("util");
|
|
10
|
+
const validation_1 = require("./validation");
|
|
10
11
|
const tmux_1 = require("../tmux");
|
|
11
12
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
12
13
|
/**
|
|
@@ -31,11 +32,16 @@ class BaseCLITool {
|
|
|
31
32
|
* Generate session name for a worktree
|
|
32
33
|
* Format: mcbd-{cli_tool_id}-{worktree_id}
|
|
33
34
|
*
|
|
35
|
+
* T2.3: Added validation to prevent command injection (MF4-001)
|
|
36
|
+
*
|
|
34
37
|
* @param worktreeId - Worktree ID
|
|
35
38
|
* @returns Session name
|
|
39
|
+
* @throws Error if the resulting session name is invalid
|
|
36
40
|
*/
|
|
37
41
|
getSessionName(worktreeId) {
|
|
38
|
-
|
|
42
|
+
const sessionName = `mcbd-${this.id}-${worktreeId}`;
|
|
43
|
+
(0, validation_1.validateSessionName)(sessionName);
|
|
44
|
+
return sessionName;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Interrupt processing by sending Escape key
|
|
@@ -10,6 +10,12 @@ const tmux_1 = require("../tmux");
|
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
11
|
const util_1 = require("util");
|
|
12
12
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
13
|
+
/**
|
|
14
|
+
* Codex initialization timing constants
|
|
15
|
+
* T2.6: Extracted as constants for maintainability
|
|
16
|
+
*/
|
|
17
|
+
const CODEX_INIT_WAIT_MS = 3000; // Wait for Codex to start
|
|
18
|
+
const CODEX_MODEL_SELECT_WAIT_MS = 200; // Wait between model selection keystrokes
|
|
13
19
|
/**
|
|
14
20
|
* Codex CLI tool implementation
|
|
15
21
|
* Manages Codex sessions using tmux
|
|
@@ -59,11 +65,17 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
59
65
|
// Start Codex CLI in interactive mode
|
|
60
66
|
await (0, tmux_1.sendKeys)(sessionName, 'codex', true);
|
|
61
67
|
// Wait for Codex to initialize (and potentially show update notification)
|
|
62
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, CODEX_INIT_WAIT_MS));
|
|
63
69
|
// Auto-skip update notification if present (select option 2: Skip)
|
|
64
70
|
await (0, tmux_1.sendKeys)(sessionName, '2', true);
|
|
65
71
|
// Wait a moment for the selection to process
|
|
66
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
73
|
+
// T2.6: Skip model selection dialog by sending Down arrow + Enter
|
|
74
|
+
// This selects the default model and proceeds to the prompt
|
|
75
|
+
await execAsync(`tmux send-keys -t "${sessionName}" Down`);
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
77
|
+
await execAsync(`tmux send-keys -t "${sessionName}" Enter`);
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
67
79
|
console.log(`✓ Started Codex session: ${sessionName}`);
|
|
68
80
|
}
|
|
69
81
|
catch (error) {
|