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
|
@@ -3,16 +3,17 @@
|
|
|
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
|
|
13
14
|
* CLIToolType is derived from this constant (DRY principle)
|
|
14
15
|
*/
|
|
15
|
-
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini', 'vibe-local'];
|
|
16
|
+
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini', 'vibe-local', 'opencode'];
|
|
16
17
|
/**
|
|
17
18
|
* CLI tool display names for UI rendering
|
|
18
19
|
* Issue #368: Centralized display name mapping
|
|
@@ -25,6 +26,7 @@ exports.CLI_TOOL_DISPLAY_NAMES = {
|
|
|
25
26
|
codex: 'Codex',
|
|
26
27
|
gemini: 'Gemini',
|
|
27
28
|
'vibe-local': 'Vibe Local',
|
|
29
|
+
opencode: 'OpenCode',
|
|
28
30
|
};
|
|
29
31
|
/**
|
|
30
32
|
* Check if a string is a valid CLIToolType
|
|
@@ -65,10 +67,49 @@ function getCliToolDisplayNameSafe(cliToolId, fallback = 'Assistant') {
|
|
|
65
67
|
return fallback;
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
70
|
+
* Minimum context window size for vibe-local.
|
|
71
|
+
* [S1-007] Lower bound rationale: Ollama's actual minimum context window is
|
|
72
|
+
* typically 2048+, but 128 is set as a permissive lower bound to accommodate
|
|
73
|
+
* custom models or future models with smaller contexts. Users are recommended
|
|
74
|
+
* to use practical values (e.g., 2048+).
|
|
75
|
+
* [S1-004] vibe-local specific constant. If more vibe-local constants are added,
|
|
76
|
+
* consider extracting to src/lib/cli-tools/vibe-local-config.ts.
|
|
77
|
+
* [SEC-002] Used to prevent unreasonable values in CLI arguments.
|
|
78
|
+
*/
|
|
79
|
+
exports.VIBE_LOCAL_CONTEXT_WINDOW_MIN = 128;
|
|
80
|
+
/**
|
|
81
|
+
* Maximum context window size for vibe-local (2M tokens).
|
|
82
|
+
* Shared between API validation and defense-in-depth (DRY principle).
|
|
83
|
+
* [S1-004] vibe-local specific constant. If more vibe-local constants are added,
|
|
84
|
+
* consider extracting to src/lib/cli-tools/vibe-local-config.ts.
|
|
85
|
+
* [SEC-002] Used to prevent unreasonable values in CLI arguments.
|
|
86
|
+
*/
|
|
87
|
+
exports.VIBE_LOCAL_CONTEXT_WINDOW_MAX = 2097152;
|
|
88
|
+
/**
|
|
89
|
+
* Validate vibe-local context window value.
|
|
90
|
+
* Shared between API layer and CLI layer (defense-in-depth).
|
|
91
|
+
* [S1-001] DRY: Single source of truth for context window validation.
|
|
92
|
+
*
|
|
93
|
+
* @param value - Value to validate (accepts unknown for type guard usage)
|
|
94
|
+
* @returns True if value is a valid context window size (integer between MIN and MAX)
|
|
95
|
+
*/
|
|
96
|
+
function isValidVibeLocalContextWindow(value) {
|
|
97
|
+
return (typeof value === 'number' &&
|
|
98
|
+
Number.isInteger(value) &&
|
|
99
|
+
value >= exports.VIBE_LOCAL_CONTEXT_WINDOW_MIN &&
|
|
100
|
+
value <= exports.VIBE_LOCAL_CONTEXT_WINDOW_MAX);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Ollama model name validation pattern (API/DB layer).
|
|
104
|
+
* Requires alphanumeric first character, followed by alphanumeric, dots, underscores,
|
|
105
|
+
* colons, slashes, hyphens. No explicit length limit (DB schema handles storage limits).
|
|
106
|
+
*
|
|
107
|
+
* [SEC-001] Shared between API route validation and CLI command construction.
|
|
71
108
|
*
|
|
72
|
-
*
|
|
109
|
+
* Note: opencode-config.ts has a separate OLLAMA_MODEL_PATTERN with a 100-character
|
|
110
|
+
* length limit (`{1,100}`) for DoS protection when parsing Ollama API responses.
|
|
111
|
+
* The patterns are intentionally different: this one enforces first-character constraints
|
|
112
|
+
* for user-facing validation, while the opencode-config version adds length limits
|
|
113
|
+
* for untrusted external API data.
|
|
73
114
|
*/
|
|
74
115
|
exports.OLLAMA_MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/;
|
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
/**
|
|
@@ -19,6 +19,7 @@ exports.getMessages = getMessages;
|
|
|
19
19
|
exports.getLastUserMessage = getLastUserMessage;
|
|
20
20
|
exports.getLastMessage = getLastMessage;
|
|
21
21
|
exports.deleteAllMessages = deleteAllMessages;
|
|
22
|
+
exports.deleteMessageById = deleteMessageById;
|
|
22
23
|
exports.deleteMessagesByCliTool = deleteMessagesByCliTool;
|
|
23
24
|
exports.getSessionState = getSessionState;
|
|
24
25
|
exports.updateSessionState = updateSessionState;
|
|
@@ -34,6 +35,7 @@ exports.updateStatus = updateStatus;
|
|
|
34
35
|
exports.updateCliToolId = updateCliToolId;
|
|
35
36
|
exports.updateSelectedAgents = updateSelectedAgents;
|
|
36
37
|
exports.updateVibeLocalModel = updateVibeLocalModel;
|
|
38
|
+
exports.updateVibeLocalContextWindow = updateVibeLocalContextWindow;
|
|
37
39
|
exports.getMemosByWorktreeId = getMemosByWorktreeId;
|
|
38
40
|
exports.getMemoById = getMemoById;
|
|
39
41
|
exports.createMemo = createMemo;
|
|
@@ -193,7 +195,7 @@ function getWorktrees(db, repositoryPath) {
|
|
|
193
195
|
w.id, w.name, w.path, w.repository_path, w.repository_name, w.description,
|
|
194
196
|
w.last_user_message, w.last_user_message_at, w.last_message_summary,
|
|
195
197
|
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,
|
|
198
|
+
w.selected_agents, w.vibe_local_model, w.vibe_local_context_window,
|
|
197
199
|
(SELECT MAX(timestamp) FROM chat_messages
|
|
198
200
|
WHERE worktree_id = w.id AND role = 'assistant') as last_assistant_message_at
|
|
199
201
|
FROM worktrees w
|
|
@@ -231,6 +233,7 @@ function getWorktrees(db, repositoryPath) {
|
|
|
231
233
|
cliToolId: row.cli_tool_id ?? 'claude',
|
|
232
234
|
selectedAgents: (0, selected_agents_validator_1.parseSelectedAgents)(row.selected_agents),
|
|
233
235
|
vibeLocalModel: row.vibe_local_model ?? null,
|
|
236
|
+
vibeLocalContextWindow: row.vibe_local_context_window ?? null,
|
|
234
237
|
};
|
|
235
238
|
});
|
|
236
239
|
}
|
|
@@ -265,7 +268,7 @@ function getWorktreeById(db, id) {
|
|
|
265
268
|
w.id, w.name, w.path, w.repository_path, w.repository_name, w.description,
|
|
266
269
|
w.last_user_message, w.last_user_message_at, w.last_message_summary,
|
|
267
270
|
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,
|
|
271
|
+
w.selected_agents, w.vibe_local_model, w.vibe_local_context_window,
|
|
269
272
|
(SELECT MAX(timestamp) FROM chat_messages
|
|
270
273
|
WHERE worktree_id = w.id AND role = 'assistant') as last_assistant_message_at
|
|
271
274
|
FROM worktrees w
|
|
@@ -294,6 +297,7 @@ function getWorktreeById(db, id) {
|
|
|
294
297
|
cliToolId: row.cli_tool_id ?? 'claude',
|
|
295
298
|
selectedAgents: (0, selected_agents_validator_1.parseSelectedAgents)(row.selected_agents),
|
|
296
299
|
vibeLocalModel: row.vibe_local_model ?? null,
|
|
300
|
+
vibeLocalContextWindow: row.vibe_local_context_window ?? null,
|
|
297
301
|
};
|
|
298
302
|
}
|
|
299
303
|
/**
|
|
@@ -477,6 +481,23 @@ function deleteAllMessages(db, worktreeId) {
|
|
|
477
481
|
stmt.run(worktreeId);
|
|
478
482
|
console.log(`[deleteAllMessages] Deleted all messages for worktree: ${worktreeId}`);
|
|
479
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Delete a single message by its ID
|
|
486
|
+
* Used to clean up orphaned user messages (e.g., when a user re-sends a message
|
|
487
|
+
* after the previous one received no response).
|
|
488
|
+
*
|
|
489
|
+
* @param db - Database instance
|
|
490
|
+
* @param messageId - ID of the message to delete
|
|
491
|
+
* @returns True if a message was deleted, false otherwise
|
|
492
|
+
*/
|
|
493
|
+
function deleteMessageById(db, messageId) {
|
|
494
|
+
const stmt = db.prepare(`
|
|
495
|
+
DELETE FROM chat_messages
|
|
496
|
+
WHERE id = ?
|
|
497
|
+
`);
|
|
498
|
+
const result = stmt.run(messageId);
|
|
499
|
+
return result.changes > 0;
|
|
500
|
+
}
|
|
480
501
|
/**
|
|
481
502
|
* Delete messages for a specific CLI tool in a worktree
|
|
482
503
|
* Issue #4: T4.2 - Individual CLI tool session termination (MF3-001)
|
|
@@ -728,6 +749,22 @@ function updateVibeLocalModel(db, id, model) {
|
|
|
728
749
|
`);
|
|
729
750
|
stmt.run(model, id);
|
|
730
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* Update vibe_local_context_window for a worktree
|
|
754
|
+
* Issue #374: Persists the user's Ollama context window size for vibe-local
|
|
755
|
+
*
|
|
756
|
+
* @param db - Database instance
|
|
757
|
+
* @param id - Worktree ID
|
|
758
|
+
* @param contextWindow - Context window size or null for default
|
|
759
|
+
*/
|
|
760
|
+
function updateVibeLocalContextWindow(db, id, contextWindow) {
|
|
761
|
+
const stmt = db.prepare(`
|
|
762
|
+
UPDATE worktrees
|
|
763
|
+
SET vibe_local_context_window = ?
|
|
764
|
+
WHERE id = ?
|
|
765
|
+
`);
|
|
766
|
+
stmt.run(contextWindow, id);
|
|
767
|
+
}
|
|
731
768
|
/**
|
|
732
769
|
* Map database row to WorktreeMemo model
|
|
733
770
|
*/
|
|
@@ -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
|
}
|