commandmate 0.3.3 → 0.3.4

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.
Files changed (54) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +5 -5
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/config.json +3 -3
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/1.pack +0 -0
  9. package/.next/cache/webpack/client-production/2.pack +0 -0
  10. package/.next/cache/webpack/client-production/index.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  13. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/0.pack +0 -0
  15. package/.next/cache/webpack/server-production/index.pack +0 -0
  16. package/.next/next-server.js.nft.json +1 -1
  17. package/.next/prerender-manifest.json +1 -1
  18. package/.next/required-server-files.json +1 -1
  19. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/app/update-check/route.js +1 -1
  21. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  22. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/proxy/[...path]/route.js +1 -1
  25. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app/worktrees/[id]/page.js +4 -4
  27. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  28. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  29. package/.next/server/app-paths-manifest.json +8 -8
  30. package/.next/server/chunks/2314.js +1 -1
  31. package/.next/server/chunks/539.js +1 -1
  32. package/.next/server/chunks/6228.js +1 -1
  33. package/.next/server/chunks/7425.js +41 -35
  34. package/.next/server/chunks/9446.js +1 -1
  35. package/.next/server/middleware-manifest.json +5 -5
  36. package/.next/server/pages/500.html +1 -1
  37. package/.next/server/pages-manifest.json +1 -1
  38. package/.next/server/server-reference-manifest.json +1 -1
  39. package/.next/static/chunks/{8091-274bc0716106e7fc.js → 8091-c0e955616dd86f82.js} +1 -1
  40. package/.next/static/chunks/app/worktrees/[id]/page-9c0c64488c17db3c.js +1 -0
  41. package/.next/static/css/fa3df0e6f437f2ba.css +3 -0
  42. package/.next/trace +5 -5
  43. package/dist/server/src/lib/cli-tools/types.js +35 -1
  44. package/dist/server/src/lib/cli-tools/vibe-local.js +12 -3
  45. package/dist/server/src/lib/db-migrations.js +17 -1
  46. package/dist/server/src/lib/db.js +21 -2
  47. package/dist/server/src/lib/prompt-detector.js +23 -4
  48. package/dist/server/src/lib/response-poller.js +28 -12
  49. package/package.json +4 -4
  50. package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +0 -1
  51. package/.next/static/css/e85de230ef5ddc40.css +0 -3
  52. /package/.next/static/{O7EDFfAYQNe_HRbORxQAC → BiyH3zkbySg7ZWTeZuXqj}/_buildManifest.js +0 -0
  53. /package/.next/static/{O7EDFfAYQNe_HRbORxQAC → BiyH3zkbySg7ZWTeZuXqj}/_ssgManifest.js +0 -0
  54. /package/.next/static/chunks/app/{page-060057e02b841125.js → page-9e523a8f415bc707.js} +0 -0
@@ -3,10 +3,11 @@
3
3
  * Type definitions and interfaces for CLI tools
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OLLAMA_MODEL_PATTERN = exports.CLI_TOOL_DISPLAY_NAMES = exports.CLI_TOOL_IDS = void 0;
6
+ exports.OLLAMA_MODEL_PATTERN = exports.VIBE_LOCAL_CONTEXT_WINDOW_MAX = exports.VIBE_LOCAL_CONTEXT_WINDOW_MIN = exports.CLI_TOOL_DISPLAY_NAMES = exports.CLI_TOOL_IDS = void 0;
7
7
  exports.isCliToolType = isCliToolType;
8
8
  exports.getCliToolDisplayName = getCliToolDisplayName;
9
9
  exports.getCliToolDisplayNameSafe = getCliToolDisplayNameSafe;
10
+ exports.isValidVibeLocalContextWindow = isValidVibeLocalContextWindow;
10
11
  /**
11
12
  * CLI Tool IDs constant array
12
13
  * T2.1: Single source of truth for CLI tool IDs
@@ -64,6 +65,39 @@ function getCliToolDisplayNameSafe(cliToolId, fallback = 'Assistant') {
64
65
  return getCliToolDisplayName(cliToolId);
65
66
  return fallback;
66
67
  }
68
+ /**
69
+ * Minimum context window size for vibe-local.
70
+ * [S1-007] Lower bound rationale: Ollama's actual minimum context window is
71
+ * typically 2048+, but 128 is set as a permissive lower bound to accommodate
72
+ * custom models or future models with smaller contexts. Users are recommended
73
+ * to use practical values (e.g., 2048+).
74
+ * [S1-004] vibe-local specific constant. If more vibe-local constants are added,
75
+ * consider extracting to src/lib/cli-tools/vibe-local-config.ts.
76
+ * [SEC-002] Used to prevent unreasonable values in CLI arguments.
77
+ */
78
+ exports.VIBE_LOCAL_CONTEXT_WINDOW_MIN = 128;
79
+ /**
80
+ * Maximum context window size for vibe-local (2M tokens).
81
+ * Shared between API validation and defense-in-depth (DRY principle).
82
+ * [S1-004] vibe-local specific constant. If more vibe-local constants are added,
83
+ * consider extracting to src/lib/cli-tools/vibe-local-config.ts.
84
+ * [SEC-002] Used to prevent unreasonable values in CLI arguments.
85
+ */
86
+ exports.VIBE_LOCAL_CONTEXT_WINDOW_MAX = 2097152;
87
+ /**
88
+ * Validate vibe-local context window value.
89
+ * Shared between API layer and CLI layer (defense-in-depth).
90
+ * [S1-001] DRY: Single source of truth for context window validation.
91
+ *
92
+ * @param value - Value to validate (accepts unknown for type guard usage)
93
+ * @returns True if value is a valid context window size (integer between MIN and MAX)
94
+ */
95
+ function isValidVibeLocalContextWindow(value) {
96
+ return (typeof value === 'number' &&
97
+ Number.isInteger(value) &&
98
+ value >= exports.VIBE_LOCAL_CONTEXT_WINDOW_MIN &&
99
+ value <= exports.VIBE_LOCAL_CONTEXT_WINDOW_MAX);
100
+ }
67
101
  /**
68
102
  * Ollama model name validation pattern.
69
103
  * Allows: alphanumeric start, followed by alphanumeric, dots, underscores, colons, slashes, hyphens.
@@ -69,18 +69,27 @@ class VibeLocalTool extends base_1.BaseCLITool {
69
69
  });
70
70
  // Wait a moment for the session to be created
71
71
  await new Promise((resolve) => setTimeout(resolve, 100));
72
- // Read Ollama model preference from DB
72
+ // Read Ollama model and context window preferences from DB
73
73
  // [SEC-001] Re-validate model name at point of use (defense-in-depth)
74
+ // [S1-005] DB direct access follows existing vibeLocalModel pattern;
75
+ // future DIP refactoring should pass these as startSession() arguments.
74
76
  let vibeLocalCommand = 'vibe-local -y';
75
77
  try {
76
78
  const db = (0, db_instance_1.getDbInstance)();
77
79
  const wt = (0, db_1.getWorktreeById)(db, worktreeId);
78
80
  if (wt?.vibeLocalModel && types_1.OLLAMA_MODEL_PATTERN.test(wt.vibeLocalModel)) {
79
- vibeLocalCommand = `vibe-local -y -m ${wt.vibeLocalModel}`;
81
+ vibeLocalCommand += ` -m ${wt.vibeLocalModel}`;
82
+ }
83
+ // [C2-008] contextWindow from the same wt object (no additional DB call)
84
+ const ctxWindow = wt?.vibeLocalContextWindow;
85
+ // [SEC-002] Defense-in-depth: re-validate at point of use
86
+ // [S4-001] Number() cast for additional safety in template literal
87
+ if ((0, types_1.isValidVibeLocalContextWindow)(ctxWindow)) {
88
+ vibeLocalCommand += ` --context-window ${Number(ctxWindow)}`;
80
89
  }
81
90
  }
82
91
  catch {
83
- // DB read failure is non-fatal; use default model
92
+ // DB read failure is non-fatal; use defaults
84
93
  }
85
94
  // Start vibe-local in interactive mode with auto-approve (-y)
86
95
  // -y flag skips the permission confirmation prompt
@@ -19,7 +19,7 @@ const db_1 = require("./db");
19
19
  * Current schema version
20
20
  * Increment this when adding new migrations
21
21
  */
22
- exports.CURRENT_SCHEMA_VERSION = 19;
22
+ exports.CURRENT_SCHEMA_VERSION = 20;
23
23
  /**
24
24
  * Migration registry
25
25
  * All migrations should be added to this array in order
@@ -860,6 +860,22 @@ const migrations = [
860
860
  // vibe_local_model is a nullable TEXT column; harmless if unused
861
861
  console.log('No rollback for vibe_local_model column (SQLite limitation)');
862
862
  }
863
+ },
864
+ {
865
+ version: 20,
866
+ name: 'add-vibe-local-context-window-column',
867
+ up: (db) => {
868
+ // Issue #374: Add vibe_local_context_window column for Ollama context window size
869
+ // NULL means use the default (vibe-local CLI decides)
870
+ db.exec(`
871
+ ALTER TABLE worktrees ADD COLUMN vibe_local_context_window INTEGER DEFAULT NULL;
872
+ `);
873
+ console.log('✓ Added vibe_local_context_window column to worktrees table');
874
+ },
875
+ down: () => {
876
+ // vibe_local_context_window is a nullable INTEGER column; harmless if unused
877
+ console.log('No rollback for vibe_local_context_window column (SQLite limitation)');
878
+ }
863
879
  }
864
880
  ];
865
881
  /**
@@ -34,6 +34,7 @@ exports.updateStatus = updateStatus;
34
34
  exports.updateCliToolId = updateCliToolId;
35
35
  exports.updateSelectedAgents = updateSelectedAgents;
36
36
  exports.updateVibeLocalModel = updateVibeLocalModel;
37
+ exports.updateVibeLocalContextWindow = updateVibeLocalContextWindow;
37
38
  exports.getMemosByWorktreeId = getMemosByWorktreeId;
38
39
  exports.getMemoById = getMemoById;
39
40
  exports.createMemo = createMemo;
@@ -193,7 +194,7 @@ function getWorktrees(db, repositoryPath) {
193
194
  w.id, w.name, w.path, w.repository_path, w.repository_name, w.description,
194
195
  w.last_user_message, w.last_user_message_at, w.last_message_summary,
195
196
  w.updated_at, w.favorite, w.status, w.link, w.cli_tool_id, w.last_viewed_at,
196
- w.selected_agents, w.vibe_local_model,
197
+ w.selected_agents, w.vibe_local_model, w.vibe_local_context_window,
197
198
  (SELECT MAX(timestamp) FROM chat_messages
198
199
  WHERE worktree_id = w.id AND role = 'assistant') as last_assistant_message_at
199
200
  FROM worktrees w
@@ -231,6 +232,7 @@ function getWorktrees(db, repositoryPath) {
231
232
  cliToolId: row.cli_tool_id ?? 'claude',
232
233
  selectedAgents: (0, selected_agents_validator_1.parseSelectedAgents)(row.selected_agents),
233
234
  vibeLocalModel: row.vibe_local_model ?? null,
235
+ vibeLocalContextWindow: row.vibe_local_context_window ?? null,
234
236
  };
235
237
  });
236
238
  }
@@ -265,7 +267,7 @@ function getWorktreeById(db, id) {
265
267
  w.id, w.name, w.path, w.repository_path, w.repository_name, w.description,
266
268
  w.last_user_message, w.last_user_message_at, w.last_message_summary,
267
269
  w.updated_at, w.favorite, w.status, w.link, w.cli_tool_id, w.last_viewed_at,
268
- w.selected_agents, w.vibe_local_model,
270
+ w.selected_agents, w.vibe_local_model, w.vibe_local_context_window,
269
271
  (SELECT MAX(timestamp) FROM chat_messages
270
272
  WHERE worktree_id = w.id AND role = 'assistant') as last_assistant_message_at
271
273
  FROM worktrees w
@@ -294,6 +296,7 @@ function getWorktreeById(db, id) {
294
296
  cliToolId: row.cli_tool_id ?? 'claude',
295
297
  selectedAgents: (0, selected_agents_validator_1.parseSelectedAgents)(row.selected_agents),
296
298
  vibeLocalModel: row.vibe_local_model ?? null,
299
+ vibeLocalContextWindow: row.vibe_local_context_window ?? null,
297
300
  };
298
301
  }
299
302
  /**
@@ -728,6 +731,22 @@ function updateVibeLocalModel(db, id, model) {
728
731
  `);
729
732
  stmt.run(model, id);
730
733
  }
734
+ /**
735
+ * Update vibe_local_context_window for a worktree
736
+ * Issue #374: Persists the user's Ollama context window size for vibe-local
737
+ *
738
+ * @param db - Database instance
739
+ * @param id - Worktree ID
740
+ * @param contextWindow - Context window size or null for default
741
+ */
742
+ function updateVibeLocalContextWindow(db, id, contextWindow) {
743
+ const stmt = db.prepare(`
744
+ UPDATE worktrees
745
+ SET vibe_local_context_window = ?
746
+ WHERE id = ?
747
+ `);
748
+ stmt.run(contextWindow, id);
749
+ }
731
750
  /**
732
751
  * Map database row to WorktreeMemo model
733
752
  */
@@ -166,12 +166,12 @@ const TEXT_INPUT_PATTERNS = [
166
166
  /differently/i,
167
167
  ];
168
168
  /**
169
- * Pattern for ❯ (U+276F) / ● (U+25CF) indicator lines used by CLI tools to mark the default selection.
170
- * Claude CLI uses ❯, Gemini CLI uses ●.
169
+ * Pattern for ❯ (U+276F) / ● (U+25CF) / › (U+203A) indicator lines used by CLI tools to mark the default selection.
170
+ * Claude CLI uses ❯, Gemini CLI uses ●, Codex CLI uses › (Issue #372).
171
171
  * Used in Pass 1 (existence check) and Pass 2 (option collection) of the 2-pass detection.
172
172
  * Anchored at both ends -- ReDoS safe (S4-001).
173
173
  */
174
- const DEFAULT_OPTION_PATTERN = /^\s*[\u276F\u25CF]\s*(\d+)\.\s*(.+)$/;
174
+ const DEFAULT_OPTION_PATTERN = /^\s*[\u276F\u25CF\u203A]\s*(\d+)\.\s*(.+)$/;
175
175
  /**
176
176
  * Pattern for normal option lines (no ❯ indicator, just leading whitespace + number).
177
177
  * Only applied in Pass 2 when ❯ indicator existence is confirmed by Pass 1.
@@ -207,6 +207,13 @@ const SEPARATOR_LINE_PATTERN = /^[-─]+$/;
207
207
  * @see Issue #256: multiple_choice prompt detection improvement
208
208
  */
209
209
  const QUESTION_SCAN_RANGE = 3;
210
+ /**
211
+ * Maximum consecutive continuation lines allowed between options and question.
212
+ * Issue #372: Codex TUI indents all output with 2 spaces, causing isContinuationLine()
213
+ * to match body text lines indefinitely. Without this limit, the scanner would traverse
214
+ * through the entire command output, picking up numbered lists as false options.
215
+ */
216
+ const MAX_CONTINUATION_LINES = 5;
210
217
  /**
211
218
  * Creates a "no prompt detected" result.
212
219
  * Centralizes the repeated pattern of returning isPrompt: false with trimmed content.
@@ -544,6 +551,7 @@ function detectMultipleChoicePrompt(output, options) {
544
551
  // ==========================================================================
545
552
  const collectedOptions = [];
546
553
  let questionEndIndex = -1;
554
+ let continuationLineCount = 0;
547
555
  for (let i = effectiveEnd - 1; i >= scanStart; i--) {
548
556
  const line = lines[i].trim();
549
557
  // Try DEFAULT_OPTION_PATTERN first (❯ indicator)
@@ -552,6 +560,7 @@ function detectMultipleChoicePrompt(output, options) {
552
560
  const number = parseInt(defaultMatch[1], 10);
553
561
  const label = defaultMatch[2].trim();
554
562
  collectedOptions.unshift({ number, label, isDefault: true });
563
+ continuationLineCount = 0;
555
564
  continue;
556
565
  }
557
566
  // Try NORMAL_OPTION_PATTERN (no ❯ indicator)
@@ -560,6 +569,7 @@ function detectMultipleChoicePrompt(output, options) {
560
569
  const number = parseInt(normalMatch[1], 10);
561
570
  const label = normalMatch[2].trim();
562
571
  collectedOptions.unshift({ number, label, isDefault: false });
572
+ continuationLineCount = 0;
563
573
  continue;
564
574
  }
565
575
  // [Issue #287 Bug3] User input prompt barrier:
@@ -568,7 +578,7 @@ function detectMultipleChoicePrompt(output, options) {
568
578
  // user input prompt (e.g., "❯ 1", "❯ /command") or idle prompt ("❯").
569
579
  // Anything above this line in the scrollback is historical conversation text,
570
580
  // not an active prompt. Stop scanning to prevent false positives.
571
- if (collectedOptions.length === 0 && (line.startsWith('\u276F') || line.startsWith('\u25CF'))) {
581
+ if (collectedOptions.length === 0 && (line.startsWith('\u276F') || line.startsWith('\u25CF') || line.startsWith('\u203A'))) {
572
582
  return noPromptResult(output);
573
583
  }
574
584
  // Non-option line handling
@@ -591,6 +601,15 @@ function detectMultipleChoicePrompt(output, options) {
591
601
  // or path/filename fragments from terminal width wrapping - Issue #181)
592
602
  const rawLine = lines[i]; // Original line with indentation preserved
593
603
  if (isContinuationLine(rawLine, line)) {
604
+ continuationLineCount++;
605
+ // Issue #372: Codex TUI indents all output with 2 spaces, causing
606
+ // every line to match isContinuationLine(). Limit the scan distance
607
+ // to prevent traversing into body text where numbered lists would be
608
+ // collected as false options.
609
+ if (continuationLineCount > MAX_CONTINUATION_LINES) {
610
+ questionEndIndex = i;
611
+ break;
612
+ }
594
613
  // Skip continuation lines and continue scanning for more options
595
614
  continue;
596
615
  }
@@ -90,13 +90,15 @@ function incompleteResult(lineCount) {
90
90
  * @param findRecentUserPromptIndex - Callback to locate the most recent user prompt
91
91
  * @returns ExtractionResult with isComplete: true and ANSI-stripped response
92
92
  */
93
- function buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex) {
93
+ function buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection) {
94
94
  const startIndex = resolveExtractionStartIndex(lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex);
95
95
  const extractedLines = lines.slice(startIndex);
96
96
  return {
97
97
  response: (0, cli_patterns_1.stripAnsi)(extractedLines.join('\n')),
98
98
  isComplete: true,
99
99
  lineCount: totalLines,
100
+ promptDetection,
101
+ bufferReset,
100
102
  };
101
103
  }
102
104
  /**
@@ -357,14 +359,20 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
357
359
  }
358
360
  return -1;
359
361
  };
360
- // Early check for Claude permission prompts (before extraction logic)
361
- // Permission prompts appear after normal responses and need special handling
362
- if (cliToolId === 'claude') {
362
+ // Early check for interactive prompts (before extraction logic)
363
+ // Permission prompts appear after normal responses and need special handling.
364
+ // Issue #372: Codex command confirmation prompts ( 1. Yes, proceed) match
365
+ // CODEX_PROMPT_PATTERN, causing isCodexOrGeminiComplete to fire prematurely.
366
+ // Early detection ensures prompt options are preserved in the extraction result.
367
+ if (cliToolId === 'claude' || cliToolId === 'codex') {
363
368
  const fullOutput = lines.join('\n');
364
369
  const promptDetection = detectPromptWithOptions(fullOutput, cliToolId);
365
370
  if (promptDetection.isPrompt) {
366
- // Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards
367
- return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex);
371
+ // Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards.
372
+ // Issue #372: Carry promptDetection through ExtractionResult so checkForResponse()
373
+ // can use it directly, avoiding a second detection on the (potentially truncated)
374
+ // extracted portion which may miss the › indicator line.
375
+ return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection);
368
376
  }
369
377
  }
370
378
  // Strip ANSI codes before pattern matching
@@ -466,6 +474,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
466
474
  response,
467
475
  isComplete: true,
468
476
  lineCount: endIndex, // Use endIndex instead of totalLines to track where we actually stopped
477
+ bufferReset,
469
478
  };
470
479
  }
471
480
  // Check if this is an interactive prompt (yes/no or multiple choice)
@@ -475,7 +484,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
475
484
  if (promptDetection.isPrompt) {
476
485
  // Prompt detection uses full buffer for accuracy, but return only lastCapturedLine onwards
477
486
  // stripAnsi is applied inside buildPromptExtractionResult (Stage 4 MF-001: XSS risk mitigation)
478
- return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex);
487
+ return buildPromptExtractionResult(lines, lastCapturedLine, totalLines, bufferReset, cliToolId, findRecentUserPromptIndex, promptDetection);
479
488
  }
480
489
  // Not a prompt, but we may have a partial response in progress (even if Claude shows a spinner)
481
490
  const responseLines = [];
@@ -525,6 +534,7 @@ async function checkForResponse(worktreeId, cliToolId) {
525
534
  // Check if CLI tool session is running
526
535
  const running = await (0, cli_session_1.isSessionRunning)(worktreeId, cliToolId);
527
536
  if (!running) {
537
+ console.log(`[checkForResponse] Session not running for ${worktreeId} (${cliToolId}), stopping poller`);
528
538
  stopPolling(worktreeId, cliToolId);
529
539
  return false;
530
540
  }
@@ -556,17 +566,23 @@ async function checkForResponse(worktreeId, cliToolId) {
556
566
  }
557
567
  // CRITICAL FIX: If lineCount == lastCapturedLine AND there's no in-progress message,
558
568
  // this response has already been saved. Skip to prevent duplicates.
559
- if (result.lineCount === lastCapturedLine && !sessionState?.inProgressMessageId) {
569
+ // Issue #372: Skip when buffer reset detected (TUI redraw may coincidentally match lineCount).
570
+ if (!result.bufferReset && result.lineCount === lastCapturedLine && !sessionState?.inProgressMessageId) {
560
571
  return false;
561
572
  }
562
573
  // Additional duplicate prevention: check if savePendingAssistantResponse
563
- // already saved this content by comparing line counts
564
- if (result.lineCount <= lastCapturedLine) {
574
+ // already saved this content by comparing line counts.
575
+ // Issue #372: Skip this check when buffer reset is detected (TUI redraw, screen clear).
576
+ // Codex TUI redraws cause totalLines to shrink, making lineCount < lastCapturedLine.
577
+ if (!result.bufferReset && result.lineCount <= lastCapturedLine) {
565
578
  console.log(`[checkForResponse] Already saved up to line ${lastCapturedLine}, skipping (result: ${result.lineCount})`);
566
579
  return false;
567
580
  }
568
- // Response is complete! Check if it's a prompt
569
- const promptDetection = detectPromptWithOptions(result.response, cliToolId);
581
+ // Response is complete! Check if it's a prompt.
582
+ // Issue #372: Prefer the prompt detection carried from extractResponse() early check,
583
+ // which uses the full tmux output for accuracy. The extracted portion (result.response)
584
+ // may be truncated and miss the › indicator line when lastCapturedLine falls just before it.
585
+ const promptDetection = result.promptDetection ?? detectPromptWithOptions(result.response, cliToolId);
570
586
  if (promptDetection.isPrompt) {
571
587
  // This is a prompt - save as prompt message
572
588
  (0, db_1.clearInProgressMessageId)(db, worktreeId, cliToolId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commandmate",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Git worktree management with Claude CLI and tmux sessions",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -88,14 +88,14 @@
88
88
  "@types/uuid": "^10.0.0",
89
89
  "@types/ws": "^8.18.1",
90
90
  "@vitejs/plugin-react": "^5.1.1",
91
- "@vitest/coverage-v8": "^4.0.16",
92
- "@vitest/ui": "^4.0.9",
91
+ "@vitest/coverage-v8": "^4.0.18",
92
+ "@vitest/ui": "^4.0.18",
93
93
  "eslint": "^8.57.0",
94
94
  "eslint-config-next": "^14.2.35",
95
95
  "tailwindcss": "^3.4.18",
96
96
  "tsc-alias": "~1.8.16",
97
97
  "tsx": "^4.20.6",
98
98
  "typescript": "^5.5.0",
99
- "vitest": "^4.0.16"
99
+ "vitest": "^4.0.18"
100
100
  }
101
101
  }