commandmate 0.3.3 → 0.3.5
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +13 -13
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +5 -5
- 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 +13 -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/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +2 -2
- package/.next/server/app/api/repositories/route.js.nft.json +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]/execution-logs/[logId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/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]/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]/schedules/[scheduleId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +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/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +1 -1
- 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_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 +4 -4
- package/.next/server/chunks/2314.js +1 -1
- package/.next/server/chunks/3074.js +1 -1
- package/.next/server/chunks/4952.js +1 -0
- package/.next/server/chunks/539.js +3 -3
- package/.next/server/chunks/5795.js +1 -1
- package/.next/server/chunks/6228.js +1 -1
- package/.next/server/chunks/7425.js +52 -43
- package/.next/server/chunks/7566.js +1 -1
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/middleware-react-loadable-manifest.js +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 → 4327.157a4c226d919531.js} +14 -14
- package/.next/static/chunks/5970.0df906ad5a9c9147.js +1 -0
- package/.next/static/chunks/{8091-274bc0716106e7fc.js → 8091-d65d2ab6daed23c6.js} +1 -1
- package/.next/static/chunks/app/login/page-010f02fd4b0dbc48.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-8fb4dc30b58a5681.js +1 -0
- package/.next/static/chunks/webpack-81c97591dd5567ac.js +1 -0
- package/.next/static/css/45b3a41370668314.css +3 -0
- package/.next/trace +5 -5
- package/dist/server/src/lib/claude-executor.js +14 -3
- package/dist/server/src/lib/cli-patterns.js +99 -20
- package/dist/server/src/lib/cli-tools/manager.js +5 -3
- package/dist/server/src/lib/cli-tools/opencode-config.js +236 -0
- package/dist/server/src/lib/cli-tools/opencode.js +188 -0
- package/dist/server/src/lib/cli-tools/types.js +47 -6
- package/dist/server/src/lib/cli-tools/vibe-local.js +12 -3
- package/dist/server/src/lib/db-migrations.js +17 -1
- package/dist/server/src/lib/db.js +39 -2
- package/dist/server/src/lib/prompt-detector.js +23 -4
- package/dist/server/src/lib/response-poller.js +392 -28
- package/package.json +5 -4
- package/.next/server/chunks/9446.js +0 -1
- package/.next/static/chunks/app/login/page-2d42204ba87cd136.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +0 -1
- package/.next/static/chunks/webpack-3c0ee3ce5b546818.js +0 -1
- package/.next/static/css/e85de230ef5ddc40.css +0 -3
- /package/.next/static/chunks/app/{page-060057e02b841125.js → page-9e523a8f415bc707.js} +0 -0
- /package/.next/static/{O7EDFfAYQNe_HRbORxQAC → p3hosTZoJ22r35fWwUoLr}/_buildManifest.js +0 -0
- /package/.next/static/{O7EDFfAYQNe_HRbORxQAC → p3hosTZoJ22r35fWwUoLr}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* CLI Tool response polling.
|
|
4
|
-
* Periodically checks tmux sessions for CLI tool responses (Claude, Codex, Gemini).
|
|
4
|
+
* Periodically checks tmux sessions for CLI tool responses (Claude, Codex, Gemini, Vibe Local, OpenCode).
|
|
5
5
|
*
|
|
6
6
|
* Key responsibilities:
|
|
7
7
|
* - Extract completed responses from tmux output (extractResponse)
|
|
@@ -13,10 +13,23 @@
|
|
|
13
13
|
* - DR-004: Tail-line windowing for thinking detection in extractResponse
|
|
14
14
|
* - MF-001 fix: Same windowing applied to checkForResponse thinking check
|
|
15
15
|
* - SF-003: RESPONSE_THINKING_TAIL_LINE_COUNT constant tracks STATUS_THINKING_LINE_COUNT
|
|
16
|
+
*
|
|
17
|
+
* Issue #379 additions:
|
|
18
|
+
* - OpenCode completion detection via Build summary line (isOpenCodeComplete)
|
|
19
|
+
* - OpenCode response cleaning (cleanOpenCodeResponse)
|
|
20
|
+
* - OpenCode extraction stop conditions (OPENCODE_PROMPT_PATTERN, OPENCODE_PROMPT_AFTER_RESPONSE)
|
|
16
21
|
*/
|
|
17
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.extractTuiContentLines = extractTuiContentLines;
|
|
24
|
+
exports.findOverlapIndex = findOverlapIndex;
|
|
25
|
+
exports.initTuiAccumulator = initTuiAccumulator;
|
|
26
|
+
exports.accumulateTuiContent = accumulateTuiContent;
|
|
27
|
+
exports.getAccumulatedContent = getAccumulatedContent;
|
|
28
|
+
exports.clearTuiAccumulator = clearTuiAccumulator;
|
|
18
29
|
exports.cleanClaudeResponse = cleanClaudeResponse;
|
|
19
30
|
exports.cleanGeminiResponse = cleanGeminiResponse;
|
|
31
|
+
exports.isOpenCodeComplete = isOpenCodeComplete;
|
|
32
|
+
exports.cleanOpenCodeResponse = cleanOpenCodeResponse;
|
|
20
33
|
exports.resolveExtractionStartIndex = resolveExtractionStartIndex;
|
|
21
34
|
exports.startPolling = startPolling;
|
|
22
35
|
exports.stopPolling = stopPolling;
|
|
@@ -90,15 +103,174 @@ function incompleteResult(lineCount) {
|
|
|
90
103
|
* @param findRecentUserPromptIndex - Callback to locate the most recent user prompt
|
|
91
104
|
* @returns ExtractionResult with isComplete: true and ANSI-stripped response
|
|
92
105
|
*/
|
|
93
|
-
function buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex) {
|
|
106
|
+
function buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection) {
|
|
94
107
|
const startIndex = resolveExtractionStartIndex(lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex);
|
|
95
108
|
const extractedLines = lines.slice(startIndex);
|
|
96
109
|
return {
|
|
97
110
|
response: (0, cli_patterns_1.stripAnsi)(extractedLines.join('\n')),
|
|
98
111
|
isComplete: true,
|
|
99
112
|
lineCount: totalLines,
|
|
113
|
+
promptDetection,
|
|
114
|
+
bufferReset,
|
|
100
115
|
};
|
|
101
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Number of lines from the end of accumulated content to use as
|
|
119
|
+
* fingerprint for overlap detection with the next capture.
|
|
120
|
+
*/
|
|
121
|
+
const OVERLAP_FINGERPRINT_SIZE = 10;
|
|
122
|
+
/**
|
|
123
|
+
* Per-session TUI response accumulator storage.
|
|
124
|
+
* Key: pollerKey ("worktreeId:cliToolId")
|
|
125
|
+
*/
|
|
126
|
+
const tuiResponseAccumulator = new Map();
|
|
127
|
+
/**
|
|
128
|
+
* Normalize a single OpenCode TUI line by removing ANSI codes and border glyphs.
|
|
129
|
+
* Returns an empty string when the line has no meaningful content after cleanup.
|
|
130
|
+
*/
|
|
131
|
+
function normalizeOpenCodeLine(line) {
|
|
132
|
+
return (0, cli_patterns_1.stripBoxDrawing)((0, cli_patterns_1.stripAnsi)(line))
|
|
133
|
+
.replace(/^\u2503\s?/, '')
|
|
134
|
+
.replace(/\s*\u2503$/, '')
|
|
135
|
+
.trim();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract meaningful content lines from raw TUI output.
|
|
139
|
+
* Strips ANSI codes, box-drawing characters, and lines matching OPENCODE_SKIP_PATTERNS.
|
|
140
|
+
*
|
|
141
|
+
* @param rawOutput - Raw tmux capture-pane output
|
|
142
|
+
* @returns Array of cleaned, non-empty content lines
|
|
143
|
+
*
|
|
144
|
+
* @internal Exported for unit testing
|
|
145
|
+
*/
|
|
146
|
+
function extractTuiContentLines(rawOutput) {
|
|
147
|
+
const lines = rawOutput.split('\n');
|
|
148
|
+
const contentLines = [];
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const cleaned = normalizeOpenCodeLine(line);
|
|
151
|
+
if (!cleaned)
|
|
152
|
+
continue;
|
|
153
|
+
const shouldSkip = cli_patterns_1.OPENCODE_SKIP_PATTERNS.some(pattern => pattern.test(cleaned));
|
|
154
|
+
if (shouldSkip)
|
|
155
|
+
continue;
|
|
156
|
+
// Also skip the Build summary line (completion indicator)
|
|
157
|
+
if (cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(cleaned))
|
|
158
|
+
continue;
|
|
159
|
+
contentLines.push(cleaned);
|
|
160
|
+
}
|
|
161
|
+
return contentLines;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Find the overlap index between previously accumulated lines and newly captured lines.
|
|
165
|
+
* Searches for the longest suffix of `previous` that matches a prefix of `current`.
|
|
166
|
+
*
|
|
167
|
+
* @param previous - Previously accumulated lines (fingerprint subset)
|
|
168
|
+
* @param current - Newly captured content lines
|
|
169
|
+
* @returns Number of overlapping lines (0 if no overlap found)
|
|
170
|
+
*
|
|
171
|
+
* @internal Exported for unit testing
|
|
172
|
+
*/
|
|
173
|
+
function findOverlapIndex(previous, current) {
|
|
174
|
+
if (previous.length === 0 || current.length === 0)
|
|
175
|
+
return 0;
|
|
176
|
+
// Try decreasing overlap sizes: full fingerprint down to 1 line
|
|
177
|
+
const maxOverlap = Math.min(previous.length, current.length);
|
|
178
|
+
for (let overlapSize = maxOverlap; overlapSize >= 1; overlapSize--) {
|
|
179
|
+
// Check if the last `overlapSize` lines of previous match
|
|
180
|
+
// the first `overlapSize` lines of current
|
|
181
|
+
const prevSlice = previous.slice(-overlapSize);
|
|
182
|
+
const currSlice = current.slice(0, overlapSize);
|
|
183
|
+
let matches = true;
|
|
184
|
+
for (let i = 0; i < overlapSize; i++) {
|
|
185
|
+
if (prevSlice[i] !== currSlice[i]) {
|
|
186
|
+
matches = false;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (matches) {
|
|
191
|
+
return overlapSize;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Initialize the TUI accumulator for a polling session.
|
|
198
|
+
*
|
|
199
|
+
* @param pollerKey - Poller key ("worktreeId:cliToolId")
|
|
200
|
+
*
|
|
201
|
+
* @internal Exported for unit testing
|
|
202
|
+
*/
|
|
203
|
+
function initTuiAccumulator(pollerKey) {
|
|
204
|
+
tuiResponseAccumulator.set(pollerKey, {
|
|
205
|
+
lines: [],
|
|
206
|
+
lastFingerprint: [],
|
|
207
|
+
pollCount: 0,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Accumulate TUI content from a new capture into the session buffer.
|
|
212
|
+
* Detects overlap with previous capture and appends only new lines.
|
|
213
|
+
*
|
|
214
|
+
* @param pollerKey - Poller key ("worktreeId:cliToolId")
|
|
215
|
+
* @param rawOutput - Raw tmux capture-pane output
|
|
216
|
+
*
|
|
217
|
+
* @internal Exported for unit testing
|
|
218
|
+
*/
|
|
219
|
+
function accumulateTuiContent(pollerKey, rawOutput) {
|
|
220
|
+
const state = tuiResponseAccumulator.get(pollerKey);
|
|
221
|
+
if (!state)
|
|
222
|
+
return;
|
|
223
|
+
const contentLines = extractTuiContentLines(rawOutput);
|
|
224
|
+
if (contentLines.length === 0)
|
|
225
|
+
return;
|
|
226
|
+
state.pollCount++;
|
|
227
|
+
if (state.lines.length === 0) {
|
|
228
|
+
// First capture: seed with all content
|
|
229
|
+
state.lines = [...contentLines];
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Subsequent captures: find overlap and append new lines
|
|
233
|
+
const overlapCount = findOverlapIndex(state.lastFingerprint, contentLines);
|
|
234
|
+
if (overlapCount > 0) {
|
|
235
|
+
// Append only lines after the overlap
|
|
236
|
+
const newLines = contentLines.slice(overlapCount);
|
|
237
|
+
state.lines.push(...newLines);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// No overlap found: append all content (completeness over dedup)
|
|
241
|
+
state.lines.push(...contentLines);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Update fingerprint for next poll
|
|
245
|
+
state.lastFingerprint = contentLines.slice(-OVERLAP_FINGERPRINT_SIZE);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get the accumulated content as a single string.
|
|
249
|
+
*
|
|
250
|
+
* @param pollerKey - Poller key ("worktreeId:cliToolId")
|
|
251
|
+
* @returns Accumulated content joined by newlines, or empty string if no accumulator
|
|
252
|
+
*
|
|
253
|
+
* @internal Exported for unit testing
|
|
254
|
+
*/
|
|
255
|
+
function getAccumulatedContent(pollerKey) {
|
|
256
|
+
const state = tuiResponseAccumulator.get(pollerKey);
|
|
257
|
+
if (!state || state.lines.length === 0)
|
|
258
|
+
return '';
|
|
259
|
+
return state.lines.join('\n');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Clear the TUI accumulator for a polling session.
|
|
263
|
+
*
|
|
264
|
+
* @param pollerKey - Poller key ("worktreeId:cliToolId")
|
|
265
|
+
*
|
|
266
|
+
* @internal Exported for unit testing
|
|
267
|
+
*/
|
|
268
|
+
function clearTuiAccumulator(pollerKey) {
|
|
269
|
+
tuiResponseAccumulator.delete(pollerKey);
|
|
270
|
+
}
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Poller State Management
|
|
273
|
+
// ============================================================================
|
|
102
274
|
/**
|
|
103
275
|
* Active pollers map: "worktreeId:cliToolId" -> NodeJS.Timeout
|
|
104
276
|
*/
|
|
@@ -250,13 +422,91 @@ function cleanGeminiResponse(response) {
|
|
|
250
422
|
}
|
|
251
423
|
return cleanedLines.join('\n').trim();
|
|
252
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Check if OpenCode has completed its response.
|
|
427
|
+
* Detects the Build summary line pattern (e.g., "▣ Build . model . 2.5s").
|
|
428
|
+
* [D2-002] Independent completion detection for OpenCode.
|
|
429
|
+
*
|
|
430
|
+
* Unlike Claude (prompt + separator) or Codex/Gemini (prompt + not thinking),
|
|
431
|
+
* OpenCode signals completion via the Build summary line, which includes
|
|
432
|
+
* the model name and generation timing.
|
|
433
|
+
*
|
|
434
|
+
* @param output - Cleaned tmux output to check (ANSI-stripped)
|
|
435
|
+
* @returns True if OpenCode response is complete
|
|
436
|
+
*
|
|
437
|
+
* @internal Exported for unit testing (response-poller-opencode.test.ts)
|
|
438
|
+
*/
|
|
439
|
+
function isOpenCodeComplete(output) {
|
|
440
|
+
// Must have a Build completion marker AND must NOT be actively processing.
|
|
441
|
+
// The "esc interrupt" indicator appears in the TUI footer during model processing.
|
|
442
|
+
// Without this check, old Build markers from previous Q&As cause false completions.
|
|
443
|
+
return cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(output) && !cli_patterns_1.OPENCODE_PROCESSING_INDICATOR.test(output);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Clean OpenCode TUI response by removing decoration characters and status lines,
|
|
447
|
+
* and trimming to only the latest response.
|
|
448
|
+
* [D2-009] Removes box-drawing characters, Build summary, loading indicators,
|
|
449
|
+
* prompt patterns, and processing indicators.
|
|
450
|
+
*
|
|
451
|
+
* Cleaning pipeline:
|
|
452
|
+
* 1. Split response into lines
|
|
453
|
+
* 2. Trim to latest response: find Build markers (▣ Build · model · time)
|
|
454
|
+
* and discard all content before the second-to-last marker.
|
|
455
|
+
* OpenCode TUI accumulates conversation history; each Q&A exchange ends
|
|
456
|
+
* with a Build marker. Without this trimming, savePendingAssistantResponse
|
|
457
|
+
* and Layer 2 accumulator would include previous Q&As in the response.
|
|
458
|
+
* 3. Skip empty lines
|
|
459
|
+
* 4. Skip lines matching any OPENCODE_SKIP_PATTERNS (TUI artifacts)
|
|
460
|
+
* 5. Skip Build summary line (OPENCODE_RESPONSE_COMPLETE, the completion indicator)
|
|
461
|
+
* 6. Join remaining lines
|
|
462
|
+
*
|
|
463
|
+
* @param response - Raw OpenCode response (may contain TUI decoration)
|
|
464
|
+
* @returns Cleaned response with TUI artifacts removed
|
|
465
|
+
*
|
|
466
|
+
* @internal Exported for unit testing (response-poller-opencode.test.ts)
|
|
467
|
+
*/
|
|
468
|
+
function cleanOpenCodeResponse(response) {
|
|
469
|
+
const lines = response.split('\n');
|
|
470
|
+
// Step 2: Trim to latest response by finding Build markers.
|
|
471
|
+
// Each Q&A exchange ends with "▣ Build · model · time".
|
|
472
|
+
// If 2+ markers exist, only include content after the second-to-last marker.
|
|
473
|
+
const buildIndices = [];
|
|
474
|
+
for (let i = 0; i < lines.length; i++) {
|
|
475
|
+
const cleanLine = normalizeOpenCodeLine(lines[i]);
|
|
476
|
+
if (cleanLine && cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(cleanLine)) {
|
|
477
|
+
buildIndices.push(i);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
let startLine = 0;
|
|
481
|
+
if (buildIndices.length >= 2) {
|
|
482
|
+
startLine = buildIndices[buildIndices.length - 2] + 1;
|
|
483
|
+
}
|
|
484
|
+
const cleanedLines = [];
|
|
485
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
486
|
+
// Strip ANSI escape codes and TUI border characters before pattern matching.
|
|
487
|
+
// Without this, embedded ANSI codes and heavy borders can break regex matches.
|
|
488
|
+
const cleanLine = normalizeOpenCodeLine(lines[i]);
|
|
489
|
+
if (!cleanLine)
|
|
490
|
+
continue;
|
|
491
|
+
// Skip lines matching any OpenCode skip pattern
|
|
492
|
+
const shouldSkip = cli_patterns_1.OPENCODE_SKIP_PATTERNS.some(pattern => pattern.test(cleanLine));
|
|
493
|
+
if (shouldSkip)
|
|
494
|
+
continue;
|
|
495
|
+
// Skip the Build summary line (completion indicator)
|
|
496
|
+
if (cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(cleanLine))
|
|
497
|
+
continue;
|
|
498
|
+
cleanedLines.push(cleanLine);
|
|
499
|
+
}
|
|
500
|
+
return cleanedLines.join('\n').trim();
|
|
501
|
+
}
|
|
253
502
|
/**
|
|
254
503
|
* Determine the start index for response extraction based on buffer state.
|
|
255
504
|
* Shared between normal response extraction and prompt detection paths.
|
|
256
505
|
*
|
|
257
|
-
* Implements a
|
|
506
|
+
* Implements a 5-branch decision tree for startIndex determination:
|
|
258
507
|
* 1. bufferWasReset -> findRecentUserPromptIndex(40) + 1, or 0 if not found
|
|
259
|
-
*
|
|
508
|
+
* 2a. cliToolId === 'opencode' -> findRecentUserPromptIndex(totalLines) + 1, or 0
|
|
509
|
+
* 2b. cliToolId === 'codex' -> Math.max(0, lastCapturedLine)
|
|
260
510
|
* 3. lastCapturedLine >= totalLines - 5 (scroll boundary) ->
|
|
261
511
|
* findRecentUserPromptIndex(50) + 1, or totalLines - 40 if not found
|
|
262
512
|
* 4. Normal case -> Math.max(0, lastCapturedLine)
|
|
@@ -289,6 +539,15 @@ function cleanGeminiResponse(response) {
|
|
|
289
539
|
function resolveExtractionStartIndex(lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex) {
|
|
290
540
|
// Defensive validation: clamp negative values to 0 (Stage 4 SF-001)
|
|
291
541
|
lastCapturedLine = Math.max(0, lastCapturedLine);
|
|
542
|
+
// Branch 2a (highest priority for OpenCode): OpenCode runs in alternate screen mode
|
|
543
|
+
// (fixed-size buffer, no scrollback). lastCapturedLine is meaningless because the buffer
|
|
544
|
+
// doesn't grow — it's always ~PANE_HEIGHT lines. bufferWasReset is often true because
|
|
545
|
+
// lastCapturedLine ≈ totalLines. Must execute BEFORE Branch 1 to avoid Branch 1's small
|
|
546
|
+
// window (40 lines) which fails to find the second-to-last Build marker in a 200-line pane.
|
|
547
|
+
if (cliToolId === 'opencode') {
|
|
548
|
+
const foundUserPrompt = findRecentUserPromptIndex(totalLines);
|
|
549
|
+
return foundUserPrompt >= 0 ? foundUserPrompt + 1 : 0;
|
|
550
|
+
}
|
|
292
551
|
// Compute bufferWasReset internally (MF-001: responsibility boundary)
|
|
293
552
|
const bufferWasReset = lastCapturedLine >= totalLines || bufferReset;
|
|
294
553
|
// Branch 1: Buffer was reset - find the most recent user prompt as anchor
|
|
@@ -296,7 +555,7 @@ function resolveExtractionStartIndex(lastCapturedLine, totalLines, bufferReset,
|
|
|
296
555
|
const foundUserPrompt = findRecentUserPromptIndex(40);
|
|
297
556
|
return foundUserPrompt >= 0 ? foundUserPrompt + 1 : 0;
|
|
298
557
|
}
|
|
299
|
-
// Branch
|
|
558
|
+
// Branch 2b: Codex uses lastCapturedLine directly (Codex-specific TUI behavior)
|
|
300
559
|
if (cliToolId === 'codex') {
|
|
301
560
|
return Math.max(0, lastCapturedLine);
|
|
302
561
|
}
|
|
@@ -337,18 +596,47 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
337
596
|
if (!bufferReset && totalLines < lastCapturedLine - 5) {
|
|
338
597
|
return null;
|
|
339
598
|
}
|
|
340
|
-
//
|
|
599
|
+
// Check recent lines for completion pattern.
|
|
600
|
+
// OpenCode TUI: content area + many empty padding lines + status bar at bottom.
|
|
601
|
+
// The trailing-empty-line trim above removes trailing newlines but NOT the internal
|
|
602
|
+
// padding between content and status bar. For OpenCode, the Build completion marker
|
|
603
|
+
// can be far above the last 20 lines, so we must check the full buffer.
|
|
341
604
|
const checkLineCount = 20;
|
|
342
605
|
const startLine = Math.max(0, totalLines - checkLineCount);
|
|
343
606
|
const linesToCheck = lines.slice(startLine);
|
|
344
|
-
const outputToCheck =
|
|
607
|
+
const outputToCheck = cliToolId === 'opencode'
|
|
608
|
+
? (0, cli_patterns_1.stripAnsi)(lines.join('\n'))
|
|
609
|
+
: linesToCheck.join('\n');
|
|
345
610
|
// Get tool-specific patterns from shared module
|
|
346
611
|
const { promptPattern, separatorPattern, thinkingPattern, skipPatterns } = (0, cli_patterns_1.getCliToolPatterns)(cliToolId);
|
|
347
612
|
const findRecentUserPromptIndex = (windowSize = 60) => {
|
|
348
613
|
// User prompt pattern: supports legacy '>' and new '❯' for Claude
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
614
|
+
let userPromptPattern;
|
|
615
|
+
if (cliToolId === 'codex') {
|
|
616
|
+
userPromptPattern = /^›\s+(?!Implement|Find and fix|Type|Summarize)/;
|
|
617
|
+
}
|
|
618
|
+
else if (cliToolId === 'opencode') {
|
|
619
|
+
// OpenCode TUI accumulates conversation history in a single screen.
|
|
620
|
+
// Each Q&A exchange ends with a "▣ Build · model · time" marker.
|
|
621
|
+
// "Ask anything..." does NOT appear in tmux capture content area.
|
|
622
|
+
// Instead, find the SECOND-TO-LAST Build marker (= end of previous exchange)
|
|
623
|
+
// to use as the extraction boundary. The first Build marker found (searching
|
|
624
|
+
// backwards) is the current response's completion; the second is the boundary.
|
|
625
|
+
let buildCount = 0;
|
|
626
|
+
for (let i = totalLines - 1; i >= Math.max(0, totalLines - windowSize); i--) {
|
|
627
|
+
const cleanLine = (0, cli_patterns_1.stripAnsi)(lines[i]);
|
|
628
|
+
if (cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(cleanLine)) {
|
|
629
|
+
buildCount++;
|
|
630
|
+
if (buildCount === 2) {
|
|
631
|
+
return i;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return -1;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
userPromptPattern = /^[>❯]\s+\S/;
|
|
639
|
+
}
|
|
352
640
|
for (let i = totalLines - 1; i >= Math.max(0, totalLines - windowSize); i--) {
|
|
353
641
|
const cleanLine = (0, cli_patterns_1.stripAnsi)(lines[i]);
|
|
354
642
|
if (userPromptPattern.test(cleanLine)) {
|
|
@@ -357,14 +645,20 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
357
645
|
}
|
|
358
646
|
return -1;
|
|
359
647
|
};
|
|
360
|
-
// Early check for
|
|
361
|
-
// Permission prompts appear after normal responses and need special handling
|
|
362
|
-
|
|
648
|
+
// Early check for interactive prompts (before extraction logic)
|
|
649
|
+
// Permission prompts appear after normal responses and need special handling.
|
|
650
|
+
// Issue #372: Codex command confirmation prompts (› 1. Yes, proceed) match
|
|
651
|
+
// CODEX_PROMPT_PATTERN, causing isCodexOrGeminiComplete to fire prematurely.
|
|
652
|
+
// Early detection ensures prompt options are preserved in the extraction result.
|
|
653
|
+
if (cliToolId === 'claude' || cliToolId === 'codex') {
|
|
363
654
|
const fullOutput = lines.join('\n');
|
|
364
655
|
const promptDetection = detectPromptWithOptions(fullOutput, cliToolId);
|
|
365
656
|
if (promptDetection.isPrompt) {
|
|
366
|
-
// Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards
|
|
367
|
-
|
|
657
|
+
// Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards.
|
|
658
|
+
// Issue #372: Carry promptDetection through ExtractionResult so checkForResponse()
|
|
659
|
+
// can use it directly, avoiding a second detection on the (potentially truncated)
|
|
660
|
+
// extracted portion which may miss the › indicator line.
|
|
661
|
+
return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection);
|
|
368
662
|
}
|
|
369
663
|
}
|
|
370
664
|
// Strip ANSI codes before pattern matching
|
|
@@ -379,7 +673,9 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
379
673
|
// Claude: require both prompt and separator
|
|
380
674
|
const isCodexOrGeminiComplete = (cliToolId === 'codex' || cliToolId === 'gemini' || cliToolId === 'vibe-local') && hasPrompt && !isThinking;
|
|
381
675
|
const isClaudeComplete = cliToolId === 'claude' && hasPrompt && hasSeparator && !isThinking;
|
|
382
|
-
|
|
676
|
+
// [D2-002] OpenCode completion: detected via Build summary line pattern (independent of prompt/separator)
|
|
677
|
+
const isOpenCodeDone = cliToolId === 'opencode' && isOpenCodeComplete(cleanOutputToCheck);
|
|
678
|
+
if (isCodexOrGeminiComplete || isClaudeComplete || isOpenCodeDone) {
|
|
383
679
|
// CLI tool has completed response
|
|
384
680
|
// Extract the response content from lastCapturedLine to the separator (not just last 20 lines)
|
|
385
681
|
const responseLines = [];
|
|
@@ -400,6 +696,13 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
400
696
|
endIndex = i;
|
|
401
697
|
break;
|
|
402
698
|
}
|
|
699
|
+
// [D2-003] For OpenCode: stop at prompt or status bar patterns
|
|
700
|
+
if (cliToolId === 'opencode') {
|
|
701
|
+
if (cli_patterns_1.OPENCODE_PROMPT_PATTERN.test(cleanLine) || cli_patterns_1.OPENCODE_PROMPT_AFTER_RESPONSE.test(cleanLine)) {
|
|
702
|
+
endIndex = i;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
403
706
|
// Skip lines matching any skip pattern (check against clean line)
|
|
404
707
|
const shouldSkip = skipPatterns.some(pattern => pattern.test(cleanLine));
|
|
405
708
|
if (shouldSkip) {
|
|
@@ -462,20 +765,42 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
462
765
|
return incompleteResult(totalLines);
|
|
463
766
|
}
|
|
464
767
|
}
|
|
768
|
+
// OpenCode banner defense: initial startup screen should not be treated as a response
|
|
769
|
+
if (cliToolId === 'opencode') {
|
|
770
|
+
const cleanResponse = (0, cli_patterns_1.stripAnsi)(response);
|
|
771
|
+
// If the output is very short and contains only TUI elements, treat as startup banner
|
|
772
|
+
if (cleanResponse.length < 50 || !cli_patterns_1.OPENCODE_RESPONSE_COMPLETE.test(cleanOutputToCheck)) {
|
|
773
|
+
// Check if there's actual content (not just TUI decoration)
|
|
774
|
+
const contentLines = cleanResponse.split('\n').filter(line => {
|
|
775
|
+
const trimmed = line.trim();
|
|
776
|
+
return trimmed && !cli_patterns_1.OPENCODE_SKIP_PATTERNS.some(p => p.test(trimmed));
|
|
777
|
+
});
|
|
778
|
+
if (contentLines.length === 0) {
|
|
779
|
+
return incompleteResult(totalLines);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
465
783
|
return {
|
|
466
784
|
response,
|
|
467
785
|
isComplete: true,
|
|
468
786
|
lineCount: endIndex, // Use endIndex instead of totalLines to track where we actually stopped
|
|
787
|
+
bufferReset,
|
|
469
788
|
};
|
|
470
789
|
}
|
|
471
790
|
// Check if this is an interactive prompt (yes/no or multiple choice)
|
|
472
791
|
// Interactive prompts don't have the ">" prompt and separator, so we need to detect them separately
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
792
|
+
// [Issue #379] Skip general prompt detection for OpenCode when response is incomplete.
|
|
793
|
+
// OpenCode's "Ask anything..." prompt pattern can cause false positive prompt detection
|
|
794
|
+
// when combined with user input text visible in the TUI buffer, leading to duplicate
|
|
795
|
+
// message creation. OpenCode prompts are only relevant when completion is detected above.
|
|
796
|
+
if (cliToolId !== 'opencode') {
|
|
797
|
+
const fullOutput = lines.join('\n');
|
|
798
|
+
const promptDetection = detectPromptWithOptions(fullOutput, cliToolId);
|
|
799
|
+
if (promptDetection.isPrompt) {
|
|
800
|
+
// Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards
|
|
801
|
+
// stripAnsi is applied inside buildPromptExtractionResult (Stage 4 MF-001: XSS risk mitigation)
|
|
802
|
+
return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection);
|
|
803
|
+
}
|
|
479
804
|
}
|
|
480
805
|
// Not a prompt, but we may have a partial response in progress (even if Claude shows a spinner)
|
|
481
806
|
const responseLines = [];
|
|
@@ -525,6 +850,7 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
525
850
|
// Check if CLI tool session is running
|
|
526
851
|
const running = await (0, cli_session_1.isSessionRunning)(worktreeId, cliToolId);
|
|
527
852
|
if (!running) {
|
|
853
|
+
console.log(`[checkForResponse] Session not running for ${worktreeId} (${cliToolId}), stopping poller`);
|
|
528
854
|
stopPolling(worktreeId, cliToolId);
|
|
529
855
|
return false;
|
|
530
856
|
}
|
|
@@ -533,6 +859,11 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
533
859
|
const lastCapturedLine = sessionState?.lastCapturedLine || 0;
|
|
534
860
|
// Capture current output
|
|
535
861
|
const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 10000);
|
|
862
|
+
// Layer 2: Accumulate TUI content for OpenCode (for overlap tracking only).
|
|
863
|
+
if (cliToolId === 'opencode') {
|
|
864
|
+
const pollerKey = getPollerKey(worktreeId, cliToolId);
|
|
865
|
+
accumulateTuiContent(pollerKey, output);
|
|
866
|
+
}
|
|
536
867
|
// Extract response
|
|
537
868
|
const result = extractResponse(output, lastCapturedLine, cliToolId);
|
|
538
869
|
if (!result || !result.isComplete) {
|
|
@@ -554,19 +885,30 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
554
885
|
}
|
|
555
886
|
return false;
|
|
556
887
|
}
|
|
888
|
+
// Issue #379: OpenCode uses a full-screen TUI with fixed buffer size (~200 lines).
|
|
889
|
+
// The tmux pane doesn't grow (no scrollback); each response overwrites the same pane,
|
|
890
|
+
// so lineCount is always approximately equal to lastCapturedLine. Skip line-based
|
|
891
|
+
// duplicate detection entirely for full-screen TUIs.
|
|
892
|
+
const isFullScreenTui = cliToolId === 'opencode';
|
|
557
893
|
// CRITICAL FIX: If lineCount == lastCapturedLine AND there's no in-progress message,
|
|
558
894
|
// this response has already been saved. Skip to prevent duplicates.
|
|
559
|
-
|
|
895
|
+
// Issue #372: Skip when buffer reset detected (TUI redraw may coincidentally match lineCount).
|
|
896
|
+
if (!isFullScreenTui && !result.bufferReset && result.lineCount === lastCapturedLine && !sessionState?.inProgressMessageId) {
|
|
560
897
|
return false;
|
|
561
898
|
}
|
|
562
899
|
// Additional duplicate prevention: check if savePendingAssistantResponse
|
|
563
|
-
// already saved this content by comparing line counts
|
|
564
|
-
|
|
900
|
+
// already saved this content by comparing line counts.
|
|
901
|
+
// Issue #372: Skip this check when buffer reset is detected (TUI redraw, screen clear).
|
|
902
|
+
// Codex TUI redraws cause totalLines to shrink, making lineCount < lastCapturedLine.
|
|
903
|
+
if (!result.bufferReset && !isFullScreenTui && result.lineCount <= lastCapturedLine) {
|
|
565
904
|
console.log(`[checkForResponse] Already saved up to line ${lastCapturedLine}, skipping (result: ${result.lineCount})`);
|
|
566
905
|
return false;
|
|
567
906
|
}
|
|
568
|
-
// Response is complete! Check if it's a prompt
|
|
569
|
-
|
|
907
|
+
// Response is complete! Check if it's a prompt.
|
|
908
|
+
// Issue #372: Prefer the prompt detection carried from extractResponse() early check,
|
|
909
|
+
// which uses the full tmux output for accuracy. The extracted portion (result.response)
|
|
910
|
+
// may be truncated and miss the › indicator line when lastCapturedLine falls just before it.
|
|
911
|
+
const promptDetection = result.promptDetection ?? detectPromptWithOptions(result.response, cliToolId);
|
|
570
912
|
if (promptDetection.isPrompt) {
|
|
571
913
|
// This is a prompt - save as prompt message
|
|
572
914
|
(0, db_1.clearInProgressMessageId)(db, worktreeId, cliToolId);
|
|
@@ -595,6 +937,7 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
595
937
|
? (0, claude_output_1.parseClaudeOutput)(result.response)
|
|
596
938
|
: undefined;
|
|
597
939
|
// Clean up responses (remove shell prompts, setup commands, and errors)
|
|
940
|
+
// [D2-009] Each tool has its own clean function for tool-specific artifacts
|
|
598
941
|
let cleanedResponse = result.response;
|
|
599
942
|
if (cliToolId === 'gemini') {
|
|
600
943
|
cleanedResponse = cleanGeminiResponse(result.response);
|
|
@@ -602,6 +945,14 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
602
945
|
else if (cliToolId === 'claude') {
|
|
603
946
|
cleanedResponse = cleanClaudeResponse(result.response);
|
|
604
947
|
}
|
|
948
|
+
else if (cliToolId === 'opencode') {
|
|
949
|
+
cleanedResponse = cleanOpenCodeResponse(result.response);
|
|
950
|
+
// Clear accumulator for next response cycle (Layer 2 data not used for final content;
|
|
951
|
+
// accumulatedContent includes all past Q&A history from the fixed-size TUI, causing
|
|
952
|
+
// old responses to leak into the saved message even after cleanOpenCodeResponse trimming).
|
|
953
|
+
const pollerKey = getPollerKey(worktreeId, cliToolId);
|
|
954
|
+
clearTuiAccumulator(pollerKey);
|
|
955
|
+
}
|
|
605
956
|
// If cleaned response is empty or just "[No content]", skip saving
|
|
606
957
|
// This prevents creating messages for shell setup commands that get filtered out
|
|
607
958
|
if (!cleanedResponse || cleanedResponse.trim() === '' || cleanedResponse === '[No content]') {
|
|
@@ -621,8 +972,9 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
621
972
|
}
|
|
622
973
|
// Race condition prevention: re-check session state before saving
|
|
623
974
|
// savePendingAssistantResponse may have already saved this content concurrently
|
|
975
|
+
// Issue #379: Skip for OpenCode full-screen TUI (fixed buffer size, lineCount never advances)
|
|
624
976
|
const currentSessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
|
|
625
|
-
if (currentSessionState && result.lineCount <= currentSessionState.lastCapturedLine) {
|
|
977
|
+
if (!isFullScreenTui && currentSessionState && result.lineCount <= currentSessionState.lastCapturedLine) {
|
|
626
978
|
console.log(`[checkForResponse] Race condition detected, skipping save (result: ${result.lineCount}, current: ${currentSessionState.lastCapturedLine})`);
|
|
627
979
|
return false;
|
|
628
980
|
}
|
|
@@ -643,6 +995,12 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
643
995
|
(0, ws_server_1.broadcastMessage)('message', { worktreeId, message });
|
|
644
996
|
// Update session state
|
|
645
997
|
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
998
|
+
// For full-screen TUIs (OpenCode), stop polling after saving the response.
|
|
999
|
+
// Line-count based duplicate prevention doesn't work because the pane size is fixed,
|
|
1000
|
+
// so lineCount never advances. Polling restarts when the user sends the next message.
|
|
1001
|
+
if (isFullScreenTui) {
|
|
1002
|
+
stopPolling(worktreeId, cliToolId);
|
|
1003
|
+
}
|
|
646
1004
|
return true;
|
|
647
1005
|
}
|
|
648
1006
|
catch (error) {
|
|
@@ -668,6 +1026,10 @@ function startPolling(worktreeId, cliToolId) {
|
|
|
668
1026
|
stopPolling(worktreeId, cliToolId);
|
|
669
1027
|
// Record start time
|
|
670
1028
|
pollingStartTimes.set(pollerKey, Date.now());
|
|
1029
|
+
// Initialize TUI accumulator for OpenCode (Layer 2 safety net)
|
|
1030
|
+
if (cliToolId === 'opencode') {
|
|
1031
|
+
initTuiAccumulator(pollerKey);
|
|
1032
|
+
}
|
|
671
1033
|
// Start polling with setTimeout chain to prevent race conditions
|
|
672
1034
|
scheduleNextResponsePoll(worktreeId, cliToolId);
|
|
673
1035
|
}
|
|
@@ -715,6 +1077,8 @@ function stopPolling(worktreeId, cliToolId) {
|
|
|
715
1077
|
activePollers.delete(pollerKey);
|
|
716
1078
|
pollingStartTimes.delete(pollerKey);
|
|
717
1079
|
}
|
|
1080
|
+
// Clean up TUI accumulator if present
|
|
1081
|
+
clearTuiAccumulator(pollerKey);
|
|
718
1082
|
}
|
|
719
1083
|
/**
|
|
720
1084
|
* Stop all active pollers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commandmate",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Git worktree management with Claude CLI and tmux sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"react": "^18.3.0",
|
|
66
66
|
"react-dom": "^18.3.0",
|
|
67
67
|
"react-markdown": "^10.1.0",
|
|
68
|
+
"react-qr-code": "^2.0.18",
|
|
68
69
|
"rehype-highlight": "^7.0.2",
|
|
69
70
|
"rehype-sanitize": "^6.0.0",
|
|
70
71
|
"remark-gfm": "^4.0.1",
|
|
@@ -88,14 +89,14 @@
|
|
|
88
89
|
"@types/uuid": "^10.0.0",
|
|
89
90
|
"@types/ws": "^8.18.1",
|
|
90
91
|
"@vitejs/plugin-react": "^5.1.1",
|
|
91
|
-
"@vitest/coverage-v8": "^4.0.
|
|
92
|
-
"@vitest/ui": "^4.0.
|
|
92
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
93
|
+
"@vitest/ui": "^4.0.18",
|
|
93
94
|
"eslint": "^8.57.0",
|
|
94
95
|
"eslint-config-next": "^14.2.35",
|
|
95
96
|
"tailwindcss": "^3.4.18",
|
|
96
97
|
"tsc-alias": "~1.8.16",
|
|
97
98
|
"tsx": "^4.20.6",
|
|
98
99
|
"typescript": "^5.5.0",
|
|
99
|
-
"vitest": "^4.0.
|
|
100
|
+
"vitest": "^4.0.18"
|
|
100
101
|
}
|
|
101
102
|
}
|