commandmate 0.1.12 → 0.2.1
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 +24 -24
- 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]/capture/route.js +2 -2
- 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/[id]/terminal/route.js +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 +10 -8
- 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/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-8bd88bdc29607413.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/server.js +25 -2
- package/dist/server/src/lib/auto-yes-manager.js +100 -11
- package/dist/server/src/lib/claude-poller.js +341 -0
- package/dist/server/src/lib/claude-session.js +48 -19
- package/dist/server/src/lib/cli-patterns.js +69 -6
- 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-repository.js +482 -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 +297 -109
- package/dist/server/src/lib/response-poller.js +73 -27
- package/dist/server/src/lib/tmux.js +48 -0
- 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/2597.js +0 -1
- package/.next/server/chunks/2648.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-58fcf2e63c056743.js +0 -1
- package/.next/static/chunks/main-a960f4a5e1a2f598.js +0 -1
- package/.next/static/css/376b339640084689.css +0 -3
- /package/.next/static/{564GHwluX5xIv9qpqLJV2 → oUD-A998xeBoez6zsrTH3}/_buildManifest.js +0 -0
- /package/.next/static/{564GHwluX5xIv9qpqLJV2 → oUD-A998xeBoez6zsrTH3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
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
|
+
// TODO [Issue #193]: This code path is unreachable (claude-poller.ts is superseded by response-poller.ts).
|
|
142
|
+
// When refactoring/removing claude-poller.ts, apply stripAnsi() + buildDetectPromptOptions() here.
|
|
143
|
+
if (!isThinking) {
|
|
144
|
+
const fullOutput = lines.join('\n');
|
|
145
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(fullOutput);
|
|
146
|
+
if (promptDetection.isPrompt) {
|
|
147
|
+
// This is an interactive prompt - consider it complete
|
|
148
|
+
return {
|
|
149
|
+
response: fullOutput,
|
|
150
|
+
isComplete: true,
|
|
151
|
+
lineCount: totalLines,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Response not yet complete
|
|
156
|
+
return {
|
|
157
|
+
response: '',
|
|
158
|
+
isComplete: false,
|
|
159
|
+
lineCount: totalLines,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check for Claude response once
|
|
164
|
+
*
|
|
165
|
+
* @param worktreeId - Worktree ID
|
|
166
|
+
* @returns True if response was found and processed
|
|
167
|
+
*/
|
|
168
|
+
async function checkForResponse(worktreeId) {
|
|
169
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
170
|
+
try {
|
|
171
|
+
// Get worktree to retrieve CLI tool ID
|
|
172
|
+
const worktree = (0, db_1.getWorktreeById)(db, worktreeId);
|
|
173
|
+
if (!worktree) {
|
|
174
|
+
console.error(`Worktree ${worktreeId} not found, stopping poller`);
|
|
175
|
+
stopPolling(worktreeId);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const cliToolId = worktree.cliToolId || 'claude';
|
|
179
|
+
// Check if Claude session is running
|
|
180
|
+
const running = await (0, claude_session_1.isClaudeRunning)(worktreeId);
|
|
181
|
+
if (!running) {
|
|
182
|
+
console.log(`Claude session not running for ${worktreeId}, stopping poller`);
|
|
183
|
+
stopPolling(worktreeId);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
// Get session state (last captured line count)
|
|
187
|
+
const sessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
|
|
188
|
+
const lastCapturedLine = sessionState?.lastCapturedLine || 0;
|
|
189
|
+
// Capture current output
|
|
190
|
+
const output = await (0, claude_session_1.captureClaudeOutput)(worktreeId, 10000);
|
|
191
|
+
// Extract response
|
|
192
|
+
const result = extractClaudeResponse(output, lastCapturedLine);
|
|
193
|
+
if (!result) {
|
|
194
|
+
// No new output
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (!result.isComplete) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
// Response is complete! Check if it's a prompt
|
|
201
|
+
// TODO [Issue #193]: This code path is unreachable (claude-poller.ts is superseded by response-poller.ts).
|
|
202
|
+
// When refactoring/removing claude-poller.ts, apply stripAnsi() + buildDetectPromptOptions() here.
|
|
203
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(result.response);
|
|
204
|
+
if (promptDetection.isPrompt) {
|
|
205
|
+
// This is a prompt - save as prompt message
|
|
206
|
+
console.log(`✓ Detected prompt for ${worktreeId}:`, promptDetection.promptData?.question);
|
|
207
|
+
const message = (0, db_1.createMessage)(db, {
|
|
208
|
+
worktreeId,
|
|
209
|
+
role: 'assistant',
|
|
210
|
+
content: promptDetection.cleanContent,
|
|
211
|
+
messageType: 'prompt',
|
|
212
|
+
promptData: promptDetection.promptData,
|
|
213
|
+
timestamp: new Date(),
|
|
214
|
+
cliToolId,
|
|
215
|
+
});
|
|
216
|
+
// Update session state
|
|
217
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
218
|
+
// Broadcast to WebSocket
|
|
219
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
220
|
+
worktreeId,
|
|
221
|
+
message,
|
|
222
|
+
});
|
|
223
|
+
console.log(`✓ Saved prompt message for ${worktreeId}`);
|
|
224
|
+
// Stop polling - waiting for user response
|
|
225
|
+
stopPolling(worktreeId);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
// Normal response (not a prompt)
|
|
229
|
+
console.log(`✓ Detected Claude response for ${worktreeId}`);
|
|
230
|
+
// Validate response content is not empty
|
|
231
|
+
if (!result.response || result.response.trim() === '') {
|
|
232
|
+
console.warn(`⚠ Empty response detected for ${worktreeId}, continuing polling...`);
|
|
233
|
+
// Update session state but don't save the message
|
|
234
|
+
// Continue polling in case a prompt appears next
|
|
235
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
// Create Markdown log file for the conversation pair
|
|
239
|
+
if (result.response) {
|
|
240
|
+
await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, result.response, 'claude');
|
|
241
|
+
}
|
|
242
|
+
// Create Claude message in database
|
|
243
|
+
const message = (0, db_1.createMessage)(db, {
|
|
244
|
+
worktreeId,
|
|
245
|
+
role: 'assistant',
|
|
246
|
+
content: result.response,
|
|
247
|
+
messageType: 'normal',
|
|
248
|
+
timestamp: new Date(),
|
|
249
|
+
cliToolId,
|
|
250
|
+
});
|
|
251
|
+
// Update session state
|
|
252
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
253
|
+
// Broadcast message to WebSocket clients
|
|
254
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
255
|
+
worktreeId,
|
|
256
|
+
message,
|
|
257
|
+
});
|
|
258
|
+
console.log(`✓ Saved Claude response for ${worktreeId}`);
|
|
259
|
+
// Stop polling since we got the response
|
|
260
|
+
stopPolling(worktreeId);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
265
|
+
console.error(`Error checking for response (${worktreeId}):`, errorMessage);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Start polling for Claude response
|
|
271
|
+
*
|
|
272
|
+
* @param worktreeId - Worktree ID
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* startPolling('feature-foo');
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
function startPolling(worktreeId) {
|
|
280
|
+
// Stop existing poller if any
|
|
281
|
+
stopPolling(worktreeId);
|
|
282
|
+
console.log(`Starting poller for ${worktreeId}`);
|
|
283
|
+
// Record start time
|
|
284
|
+
pollingStartTimes.set(worktreeId, Date.now());
|
|
285
|
+
// Start polling
|
|
286
|
+
const interval = setInterval(async () => {
|
|
287
|
+
console.log(`[Poller] Checking for response: ${worktreeId}`);
|
|
288
|
+
const startTime = pollingStartTimes.get(worktreeId);
|
|
289
|
+
// Check if max duration exceeded
|
|
290
|
+
if (startTime && Date.now() - startTime > MAX_POLLING_DURATION) {
|
|
291
|
+
console.log(`Polling timeout for ${worktreeId}, stopping`);
|
|
292
|
+
stopPolling(worktreeId);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Check for response
|
|
296
|
+
try {
|
|
297
|
+
await checkForResponse(worktreeId);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error(`[Poller] Error in checkForResponse:`, error);
|
|
301
|
+
}
|
|
302
|
+
}, POLLING_INTERVAL);
|
|
303
|
+
activePollers.set(worktreeId, interval);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Stop polling for a worktree
|
|
307
|
+
*
|
|
308
|
+
* @param worktreeId - Worktree ID
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```typescript
|
|
312
|
+
* stopPolling('feature-foo');
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
function stopPolling(worktreeId) {
|
|
316
|
+
const interval = activePollers.get(worktreeId);
|
|
317
|
+
if (interval) {
|
|
318
|
+
clearInterval(interval);
|
|
319
|
+
activePollers.delete(worktreeId);
|
|
320
|
+
pollingStartTimes.delete(worktreeId);
|
|
321
|
+
console.log(`Stopped poller for ${worktreeId}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Stop all active pollers
|
|
326
|
+
* Used for cleanup on server shutdown
|
|
327
|
+
*/
|
|
328
|
+
function stopAllPolling() {
|
|
329
|
+
console.log(`Stopping all pollers (${activePollers.size} active)`);
|
|
330
|
+
for (const worktreeId of activePollers.keys()) {
|
|
331
|
+
stopPolling(worktreeId);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get list of active pollers
|
|
336
|
+
*
|
|
337
|
+
* @returns Array of worktree IDs currently being polled
|
|
338
|
+
*/
|
|
339
|
+
function getActivePollers() {
|
|
340
|
+
return Array.from(activePollers.keys());
|
|
341
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
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
|
+
exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
|
|
8
8
|
exports.getSessionName = getSessionName;
|
|
9
9
|
exports.isClaudeInstalled = isClaudeInstalled;
|
|
10
10
|
exports.isClaudeRunning = isClaudeRunning;
|
|
@@ -42,6 +42,9 @@ function getErrorMessage(error) {
|
|
|
42
42
|
* - Authenticate with Anthropic servers (if needed)
|
|
43
43
|
* - Display the interactive prompt
|
|
44
44
|
*
|
|
45
|
+
* This timeout also covers trust dialog auto-response time (typically <1s).
|
|
46
|
+
* When reducing this value, consider dialog response overhead.
|
|
47
|
+
*
|
|
45
48
|
* 15 seconds provides headroom for slower networks or cold starts.
|
|
46
49
|
*/
|
|
47
50
|
exports.CLAUDE_INIT_TIMEOUT = 15000;
|
|
@@ -75,6 +78,24 @@ exports.CLAUDE_POST_PROMPT_DELAY = 500;
|
|
|
75
78
|
* if it's still processing a previous request.
|
|
76
79
|
*/
|
|
77
80
|
exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
|
|
81
|
+
/**
|
|
82
|
+
* Prompt wait timeout before message send (milliseconds).
|
|
83
|
+
*
|
|
84
|
+
* Used exclusively by sendMessageToClaude() to limit how long we wait
|
|
85
|
+
* for Claude to return to a prompt state before sending a user message.
|
|
86
|
+
* This is separate from CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms, the default
|
|
87
|
+
* for waitForPrompt()) because sendMessageToClaude() may be called
|
|
88
|
+
* shortly after session initialization, where Claude CLI needs additional
|
|
89
|
+
* time to become ready.
|
|
90
|
+
*
|
|
91
|
+
* Relationship to other timeout constants:
|
|
92
|
+
* - CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms): Default for waitForPrompt()
|
|
93
|
+
* - CLAUDE_SEND_PROMPT_WAIT_TIMEOUT (10000ms): sendMessageToClaude() specific
|
|
94
|
+
* - CLAUDE_INIT_TIMEOUT (15000ms): Session initialization timeout
|
|
95
|
+
*
|
|
96
|
+
* @see Issue #187 - Constant unification for sendMessageToClaude timeout
|
|
97
|
+
*/
|
|
98
|
+
exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = 10000;
|
|
78
99
|
/**
|
|
79
100
|
* Prompt wait polling interval (milliseconds)
|
|
80
101
|
*
|
|
@@ -227,7 +248,6 @@ async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_T
|
|
|
227
248
|
* await startClaudeSession({
|
|
228
249
|
* worktreeId: 'feature-foo',
|
|
229
250
|
* worktreePath: '/path/to/worktree',
|
|
230
|
-
* baseUrl: 'http://localhost:3000',
|
|
231
251
|
* });
|
|
232
252
|
* ```
|
|
233
253
|
*/
|
|
@@ -262,21 +282,33 @@ async function startClaudeSession(options) {
|
|
|
262
282
|
const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
|
|
263
283
|
const startTime = Date.now();
|
|
264
284
|
let initialized = false;
|
|
285
|
+
let trustDialogHandled = false;
|
|
265
286
|
while (Date.now() - startTime < maxWaitTime) {
|
|
266
287
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
267
288
|
try {
|
|
268
289
|
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
269
|
-
// Claude is ready when we see the prompt
|
|
270
|
-
// Use
|
|
290
|
+
// Claude is ready when we see the prompt (DRY-001)
|
|
291
|
+
// Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts for consistency
|
|
271
292
|
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
293
|
+
// Note: CLAUDE_SEPARATOR_PATTERN was removed from initialization check (Issue #187, P1-1)
|
|
294
|
+
// because separator early-detection caused premature returns before prompt was ready
|
|
272
295
|
const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
|
|
273
|
-
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput)
|
|
296
|
+
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput)) {
|
|
274
297
|
// Wait for stability after prompt detection (CONS-007, DOC-001)
|
|
275
298
|
await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
|
|
276
299
|
console.log(`Claude initialized in ${Date.now() - startTime}ms`);
|
|
277
300
|
initialized = true;
|
|
278
301
|
break;
|
|
279
302
|
}
|
|
303
|
+
// Issue #201: Detect trust dialog and auto-respond with Enter
|
|
304
|
+
// Condition order: CLAUDE_PROMPT_PATTERN (above) is checked first for shortest path
|
|
305
|
+
if (!trustDialogHandled && cli_patterns_1.CLAUDE_TRUST_DIALOG_PATTERN.test(cleanOutput)) {
|
|
306
|
+
await (0, tmux_1.sendKeys)(sessionName, '', true);
|
|
307
|
+
trustDialogHandled = true;
|
|
308
|
+
// TODO: Log output method unification (console.log vs createLogger) to be addressed in a separate Issue (SF-002)
|
|
309
|
+
console.log('Trust dialog detected, sending Enter to confirm');
|
|
310
|
+
// Continue polling to wait for prompt detection
|
|
311
|
+
}
|
|
280
312
|
}
|
|
281
313
|
catch {
|
|
282
314
|
// Ignore capture errors during initialization
|
|
@@ -312,23 +344,21 @@ async function sendMessageToClaude(worktreeId, message) {
|
|
|
312
344
|
throw new Error(`Claude session ${sessionName} does not exist. Start the session first.`);
|
|
313
345
|
}
|
|
314
346
|
// Verify prompt state before sending (CONS-006, DRY-001)
|
|
315
|
-
// Use -50 lines to ensure we capture the prompt even with status bars
|
|
316
347
|
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
317
|
-
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
318
348
|
if (!cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
|
|
319
|
-
//
|
|
320
|
-
|
|
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
|
-
}
|
|
349
|
+
// Path B: Prompt not detected - wait for it (P1: throw on timeout)
|
|
350
|
+
await waitForPrompt(sessionName, exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT);
|
|
328
351
|
}
|
|
352
|
+
// P0: Stability delay after prompt detection (both Path A and Path B)
|
|
353
|
+
// Same delay as startClaudeSession() to ensure Claude CLI input handler is ready
|
|
354
|
+
// NOTE: This 500ms delay also applies to 2nd+ messages. Currently acceptable since
|
|
355
|
+
// Claude CLI response time (seconds to tens of seconds) dwarfs this overhead.
|
|
356
|
+
// If future batch-send use cases arise, this could be optimized to first-message-only,
|
|
357
|
+
// but that optimization is deferred per YAGNI principle. (ref: F-3)
|
|
358
|
+
await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
|
|
329
359
|
// Send message using sendKeys consistently (CONS-001)
|
|
330
|
-
await (0, tmux_1.sendKeys)(sessionName, message, false);
|
|
331
|
-
await (0, tmux_1.sendKeys)(sessionName, '', true);
|
|
360
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false);
|
|
361
|
+
await (0, tmux_1.sendKeys)(sessionName, '', true);
|
|
332
362
|
console.log(`Sent message to Claude session: ${sessionName}`);
|
|
333
363
|
}
|
|
334
364
|
/**
|
|
@@ -403,7 +433,6 @@ async function stopClaudeSession(worktreeId) {
|
|
|
403
433
|
* await restartClaudeSession({
|
|
404
434
|
* worktreeId: 'feature-foo',
|
|
405
435
|
* worktreePath: '/path/to/worktree',
|
|
406
|
-
* baseUrl: 'http://localhost:3000',
|
|
407
436
|
* });
|
|
408
437
|
* ```
|
|
409
438
|
*/
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Shared between response-poller.ts and API routes
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.GEMINI_PROMPT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
7
|
+
exports.GEMINI_PROMPT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
8
8
|
exports.detectThinking = detectThinking;
|
|
9
9
|
exports.getCliToolPatterns = getCliToolPatterns;
|
|
10
10
|
exports.stripAnsi = stripAnsi;
|
|
11
|
+
exports.buildDetectPromptOptions = buildDetectPromptOptions;
|
|
11
12
|
const logger_1 = require("./logger");
|
|
12
13
|
const logger = (0, logger_1.createLogger)('cli-patterns');
|
|
13
14
|
/**
|
|
@@ -23,13 +24,20 @@ exports.CLAUDE_SPINNER_CHARS = [
|
|
|
23
24
|
* Claude thinking pattern
|
|
24
25
|
* Matches spinner character followed by activity text ending with …
|
|
25
26
|
* The text can contain spaces (e.g., "Verifying implementation (dead code detection)…")
|
|
27
|
+
*
|
|
28
|
+
* Alternative 2: "esc to interrupt" status bar text (Issue #XXX)
|
|
29
|
+
* Claude Code shows "esc to interrupt" in the terminal status bar during active processing.
|
|
30
|
+
* Previous pattern required closing paren `to interrupt\)` matching `(esc to interrupt)`,
|
|
31
|
+
* but Claude Code v2.x status bar format uses `· esc to interrupt ·` without parens.
|
|
32
|
+
* Updated to match `esc to interrupt` which covers both formats.
|
|
26
33
|
*/
|
|
27
|
-
exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.join('')}]\\s+.+…|to interrupt
|
|
34
|
+
exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.join('')}]\\s+.+…|esc to interrupt`, 'm');
|
|
28
35
|
/**
|
|
29
36
|
* Codex thinking pattern
|
|
30
37
|
* Matches activity indicators like "• Planning", "• Searching", etc.
|
|
38
|
+
* T1.1: Extended to include "Ran" and "Deciding"
|
|
31
39
|
*/
|
|
32
|
-
exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing)/m;
|
|
40
|
+
exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing|Ran|Deciding)/m;
|
|
33
41
|
/**
|
|
34
42
|
* Claude prompt pattern (waiting for input)
|
|
35
43
|
* Supports both legacy '>' and new '❯' (U+276F) prompt characters
|
|
@@ -44,10 +52,24 @@ exports.CLAUDE_PROMPT_PATTERN = /^[>❯](\s*$|\s+\S)/m;
|
|
|
44
52
|
* Claude separator pattern
|
|
45
53
|
*/
|
|
46
54
|
exports.CLAUDE_SEPARATOR_PATTERN = /^─{10,}$/m;
|
|
55
|
+
/**
|
|
56
|
+
* Claude trust dialog pattern (Issue #201)
|
|
57
|
+
*
|
|
58
|
+
* Matches the "Quick safety check" dialog displayed by Claude CLI v2.x
|
|
59
|
+
* when accessing a workspace for the first time.
|
|
60
|
+
*
|
|
61
|
+
* Intentionally uses partial matching (no line-start anchor ^):
|
|
62
|
+
* Other pattern constants (CLAUDE_PROMPT_PATTERN, CLAUDE_SEPARATOR_PATTERN, etc.)
|
|
63
|
+
* use line-start anchors (^), but this pattern needs to match at any position
|
|
64
|
+
* within the tmux output buffer because the dialog text may appear after
|
|
65
|
+
* tmux padding or other output. (SF-001)
|
|
66
|
+
*/
|
|
67
|
+
exports.CLAUDE_TRUST_DIALOG_PATTERN = /Yes, I trust this folder/m;
|
|
47
68
|
/**
|
|
48
69
|
* Codex prompt pattern
|
|
70
|
+
* T1.2: Improved to detect empty prompts as well
|
|
49
71
|
*/
|
|
50
|
-
exports.CODEX_PROMPT_PATTERN = /^›\s
|
|
72
|
+
exports.CODEX_PROMPT_PATTERN = /^›\s*/m;
|
|
51
73
|
/**
|
|
52
74
|
* Codex separator pattern
|
|
53
75
|
*/
|
|
@@ -114,6 +136,11 @@ function getCliToolPatterns(cliToolId) {
|
|
|
114
136
|
/^\s*for shortcuts$/, // Shortcuts hint
|
|
115
137
|
/╭─+╮/, // Box drawing (top)
|
|
116
138
|
/╰─+╯/, // Box drawing (bottom)
|
|
139
|
+
// T1.3: Additional skip patterns for Codex
|
|
140
|
+
/•\s*Ran\s+/, // Command execution lines
|
|
141
|
+
/^\s*└/, // Tree output (completion indicator)
|
|
142
|
+
/^\s*│/, // Continuation lines
|
|
143
|
+
/\(.*esc to interrupt\)/, // Interrupt hint
|
|
117
144
|
],
|
|
118
145
|
};
|
|
119
146
|
case 'gemini':
|
|
@@ -133,10 +160,46 @@ function getCliToolPatterns(cliToolId) {
|
|
|
133
160
|
}
|
|
134
161
|
}
|
|
135
162
|
/**
|
|
136
|
-
* Strip ANSI escape codes from a string
|
|
137
|
-
* Optimized version at module level for performance
|
|
163
|
+
* Strip ANSI escape codes from a string.
|
|
164
|
+
* Optimized version at module level for performance.
|
|
165
|
+
*
|
|
166
|
+
* Covers:
|
|
167
|
+
* - SGR sequences: ESC[Nm (colors, bold, underline, etc.)
|
|
168
|
+
* - OSC sequences: ESC]...BEL (window title, hyperlinks, etc.)
|
|
169
|
+
* - CSI sequences: ESC[...letter (cursor movement, erase, etc.)
|
|
170
|
+
*
|
|
171
|
+
* Known limitations (SEC-002):
|
|
172
|
+
* - 8-bit CSI (0x9B): C1 control code form of CSI is not covered
|
|
173
|
+
* - DEC private modes: ESC[?25h and similar are not covered
|
|
174
|
+
* - Character set switching: ESC(0, ESC(B are not covered
|
|
175
|
+
* - Some RGB color forms: ESC[38;2;r;g;bm may not be fully matched
|
|
176
|
+
*
|
|
177
|
+
* In practice, tmux capture-pane output rarely contains these sequences,
|
|
178
|
+
* so the risk is low. Future consideration: adopt the `strip-ansi` npm package
|
|
179
|
+
* for more comprehensive coverage.
|
|
138
180
|
*/
|
|
139
181
|
const ANSI_PATTERN = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;
|
|
140
182
|
function stripAnsi(str) {
|
|
141
183
|
return str.replace(ANSI_PATTERN, '');
|
|
142
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Build DetectPromptOptions for a given CLI tool.
|
|
187
|
+
* Centralizes cliToolId-to-options mapping logic (DRY - MF-001).
|
|
188
|
+
*
|
|
189
|
+
* prompt-detector.ts remains CLI tool independent (Issue #161 principle);
|
|
190
|
+
* this function lives in cli-patterns.ts which already depends on CLIToolType.
|
|
191
|
+
*
|
|
192
|
+
* [Future extension memo (C-002)]
|
|
193
|
+
* If CLI tool count grows significantly (currently 3), consider migrating
|
|
194
|
+
* to a CLIToolConfig registry pattern where tool-specific settings
|
|
195
|
+
* (including promptDetectionOptions) are managed in a Record<CLIToolType, CLIToolConfig>.
|
|
196
|
+
*
|
|
197
|
+
* @param cliToolId - CLI tool identifier
|
|
198
|
+
* @returns DetectPromptOptions for the tool, or undefined for default behavior
|
|
199
|
+
*/
|
|
200
|
+
function buildDetectPromptOptions(cliToolId) {
|
|
201
|
+
if (cliToolId === 'claude') {
|
|
202
|
+
return { requireDefaultIndicator: false };
|
|
203
|
+
}
|
|
204
|
+
return undefined; // Default behavior (requireDefaultIndicator = true)
|
|
205
|
+
}
|
|
@@ -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) {
|