commandmate 0.3.2 → 0.3.3
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 +4 -4
- 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/required-server-files.json +1 -1
- package/.next/routes-manifest.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/ollama/models/route.js +1 -0
- package/.next/server/app/api/ollama/models/route.js.nft.json +1 -0
- package/.next/server/app/api/ollama/models.body +1 -0
- package/.next/server/app/api/ollama/models.meta +1 -0
- package/.next/server/app/api/repositories/route.js +3 -3
- 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]/cli-tool/route.js +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]/messages/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]/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 +2 -2
- 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_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.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 +7 -6
- package/.next/server/chunks/2314.js +1 -1
- package/.next/server/chunks/4559.js +1 -1
- package/.next/server/chunks/539.js +10 -10
- package/.next/server/chunks/5853.js +1 -1
- package/.next/server/chunks/6228.js +1 -1
- package/.next/server/chunks/7425.js +59 -39
- package/.next/server/chunks/7566.js +1 -1
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/chunks/9446.js +1 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/8091-274bc0716106e7fc.js +1 -0
- package/.next/static/chunks/app/page-060057e02b841125.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +1 -0
- package/.next/static/chunks/{main-db79434ee4a6c931.js → main-2feda12a4d321111.js} +1 -1
- package/.next/static/css/{bd6065b03ddb3efd.css → e85de230ef5ddc40.css} +1 -1
- package/.next/trace +5 -5
- package/.next/types/app/api/ollama/models/route.ts +343 -0
- package/README.md +74 -76
- package/dist/server/src/config/schedule-config.js +7 -1
- package/dist/server/src/lib/auto-yes-manager.js +2 -2
- package/dist/server/src/lib/claude-executor.js +15 -4
- package/dist/server/src/lib/cli-patterns.js +73 -9
- package/dist/server/src/lib/cli-tools/gemini.js +81 -22
- package/dist/server/src/lib/cli-tools/manager.js +4 -2
- package/dist/server/src/lib/cli-tools/types.js +64 -2
- package/dist/server/src/lib/cli-tools/vibe-local.js +163 -0
- package/dist/server/src/lib/cmate-parser.js +25 -3
- package/dist/server/src/lib/db-migrations.js +50 -1
- package/dist/server/src/lib/db.js +51 -1
- package/dist/server/src/lib/prompt-detector.js +4 -3
- package/dist/server/src/lib/response-poller.js +22 -11
- package/dist/server/src/lib/schedule-manager.js +6 -2
- package/dist/server/src/lib/selected-agents-validator.js +99 -0
- package/dist/server/src/types/sidebar.js +9 -4
- package/package.json +1 -1
- package/.next/server/chunks/7536.js +0 -1
- package/.next/static/chunks/8091-925542bdfc843dce.js +0 -1
- package/.next/static/chunks/app/page-238b5a70d8c101e9.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-0c889ab3f30d5af7.js +0 -1
- /package/.next/static/{j8HFvzDZj7tHjAnhpXUno → O7EDFfAYQNe_HRbORxQAC}/_buildManifest.js +0 -0
- /package/.next/static/{j8HFvzDZj7tHjAnhpXUno → O7EDFfAYQNe_HRbORxQAC}/_ssgManifest.js +0 -0
|
@@ -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.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_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;
|
|
7
|
+
exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_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.stripBoxDrawing = stripBoxDrawing;
|
|
11
12
|
exports.buildDetectPromptOptions = buildDetectPromptOptions;
|
|
12
13
|
const logger_1 = require("./logger");
|
|
13
14
|
const logger = (0, logger_1.createLogger)('cli-patterns');
|
|
@@ -113,9 +114,29 @@ exports.PASTED_TEXT_DETECT_DELAY = 500;
|
|
|
113
114
|
*/
|
|
114
115
|
exports.MAX_PASTED_TEXT_RETRIES = 3;
|
|
115
116
|
/**
|
|
116
|
-
* Gemini
|
|
117
|
+
* Gemini interactive REPL prompt pattern
|
|
118
|
+
* Gemini CLI shows a `>` or `❯` prompt when waiting for user input in interactive mode.
|
|
119
|
+
* Also matches shell prompts as fallback for session initialization phase.
|
|
117
120
|
*/
|
|
118
|
-
exports.GEMINI_PROMPT_PATTERN = /^
|
|
121
|
+
exports.GEMINI_PROMPT_PATTERN = /^[>❯]\s*$/m;
|
|
122
|
+
/**
|
|
123
|
+
* Gemini thinking/processing pattern
|
|
124
|
+
* Gemini CLI shows braille spinner characters and status text while processing.
|
|
125
|
+
*/
|
|
126
|
+
exports.GEMINI_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking\.\.\./;
|
|
127
|
+
/**
|
|
128
|
+
* Vibe Local prompt pattern
|
|
129
|
+
* vibe-local (vibe-coder) shows `ctx:N% ❯` prompt when waiting for user input.
|
|
130
|
+
* The prompt line includes a context usage percentage prefix.
|
|
131
|
+
* Examples: "ctx:9% ❯", "ctx:30% ❯", "ctx:9% ❯ /model"
|
|
132
|
+
*/
|
|
133
|
+
exports.VIBE_LOCAL_PROMPT_PATTERN = /ctx:\d+%\s*[>❯]/m;
|
|
134
|
+
/**
|
|
135
|
+
* Vibe Local thinking/processing pattern
|
|
136
|
+
* vibe-local shows spinner characters and status text while processing.
|
|
137
|
+
* Matches braille spinners, "Thinking", and tool execution indicators.
|
|
138
|
+
*/
|
|
139
|
+
exports.VIBE_LOCAL_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking|⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏|Running|Executing/;
|
|
119
140
|
/**
|
|
120
141
|
* Detect if CLI tool is showing "thinking" indicator
|
|
121
142
|
*/
|
|
@@ -131,8 +152,10 @@ function detectThinking(cliToolId, content) {
|
|
|
131
152
|
result = exports.CODEX_THINKING_PATTERN.test(content);
|
|
132
153
|
break;
|
|
133
154
|
case 'gemini':
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
result = exports.GEMINI_THINKING_PATTERN.test(content);
|
|
156
|
+
break;
|
|
157
|
+
case 'vibe-local':
|
|
158
|
+
result = exports.VIBE_LOCAL_THINKING_PATTERN.test(content);
|
|
136
159
|
break;
|
|
137
160
|
default:
|
|
138
161
|
result = exports.CLAUDE_THINKING_PATTERN.test(content);
|
|
@@ -186,12 +209,36 @@ function getCliToolPatterns(cliToolId) {
|
|
|
186
209
|
case 'gemini':
|
|
187
210
|
return {
|
|
188
211
|
promptPattern: exports.GEMINI_PROMPT_PATTERN,
|
|
189
|
-
separatorPattern: /^
|
|
190
|
-
thinkingPattern:
|
|
212
|
+
separatorPattern: /^[─━]{3,}$/m,
|
|
213
|
+
thinkingPattern: exports.GEMINI_THINKING_PATTERN,
|
|
214
|
+
skipPatterns: [
|
|
215
|
+
/^[>❯]\s*$/, // Prompt line
|
|
216
|
+
exports.GEMINI_THINKING_PATTERN, // Thinking indicators
|
|
217
|
+
/^\s*$/, // Empty lines
|
|
218
|
+
/Gemini\s+\d+\.\d+/, // Version line
|
|
219
|
+
exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
case 'vibe-local':
|
|
223
|
+
return {
|
|
224
|
+
promptPattern: exports.VIBE_LOCAL_PROMPT_PATTERN,
|
|
225
|
+
separatorPattern: /^[·]{10,}$/m, // vibe-local uses middle dot separators
|
|
226
|
+
thinkingPattern: exports.VIBE_LOCAL_THINKING_PATTERN,
|
|
191
227
|
skipPatterns: [
|
|
192
|
-
|
|
193
|
-
exports.
|
|
228
|
+
exports.VIBE_LOCAL_PROMPT_PATTERN, // Prompt line (ctx:N% ❯)
|
|
229
|
+
exports.VIBE_LOCAL_THINKING_PATTERN, // Thinking indicators
|
|
194
230
|
/^\s*$/, // Empty lines
|
|
231
|
+
/vibe-local|vibe-coder/, // Version/banner lines
|
|
232
|
+
/ctx:\s*\d+%/, // Context usage indicator
|
|
233
|
+
/Model\s+\w/, // Model info line
|
|
234
|
+
/Engine\s+\w/, // Engine info line
|
|
235
|
+
/Mode\s+/, // Mode info line
|
|
236
|
+
/RAM\s+/, // RAM info line
|
|
237
|
+
/CWD\s+/, // Working directory line
|
|
238
|
+
/^[·]{10,}$/, // Middle dot separator lines
|
|
239
|
+
/✦\s*Ready/, // Status bar "Ready" indicator
|
|
240
|
+
/ESC:\s*stop/, // Status bar "ESC: stop" hint
|
|
241
|
+
exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
|
|
195
242
|
],
|
|
196
243
|
};
|
|
197
244
|
default:
|
|
@@ -222,6 +269,23 @@ const ANSI_PATTERN = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;
|
|
|
222
269
|
function stripAnsi(str) {
|
|
223
270
|
return str.replace(ANSI_PATTERN, '');
|
|
224
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Strip box-drawing border characters from CLI output.
|
|
274
|
+
* Gemini CLI wraps Action Required prompts in ╭─╮│╰─╯ borders.
|
|
275
|
+
* Removes │ (U+2502) prefix/suffix and border-only lines (╭╮╰╯─).
|
|
276
|
+
*
|
|
277
|
+
* @param str - Input string (typically after stripAnsi())
|
|
278
|
+
* @returns String with box-drawing borders removed
|
|
279
|
+
*/
|
|
280
|
+
function stripBoxDrawing(str) {
|
|
281
|
+
return str.split('\n').map(line => {
|
|
282
|
+
// Remove border-only lines (╭──╮, ╰──╯, │ only, etc.)
|
|
283
|
+
if (/^[\u2502\u256D\u256E\u256F\u2570\u2500\s]+$/.test(line))
|
|
284
|
+
return '';
|
|
285
|
+
// Strip leading │ + optional space, trailing space + │
|
|
286
|
+
return line.replace(/^\u2502\s?/, '').replace(/\s*\u2502$/, '');
|
|
287
|
+
}).join('\n');
|
|
288
|
+
}
|
|
225
289
|
/**
|
|
226
290
|
* Build DetectPromptOptions for a given CLI tool.
|
|
227
291
|
* Centralizes cliToolId-to-options mapping logic (DRY - MF-001).
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Gemini CLI tool implementation
|
|
4
|
-
* Provides integration with Google's Gemini CLI
|
|
4
|
+
* Provides integration with Google's Gemini CLI in interactive mode
|
|
5
|
+
*
|
|
6
|
+
* @remarks Issue #368: Rewritten from non-interactive pipe mode to interactive REPL mode.
|
|
7
|
+
* Previous implementation used `echo 'msg' | gemini` which caused the process to exit
|
|
8
|
+
* immediately, making response polling impossible. Now launches `gemini` in interactive
|
|
9
|
+
* mode within tmux (same approach as Claude/Codex).
|
|
5
10
|
*/
|
|
6
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
12
|
exports.GeminiTool = void 0;
|
|
8
13
|
const base_1 = require("./base");
|
|
9
14
|
const tmux_1 = require("../tmux");
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const pasted_text_helper_1 = require("../pasted-text-helper");
|
|
16
|
+
/**
|
|
17
|
+
* Extract error message from unknown error type (DRY)
|
|
18
|
+
*/
|
|
19
|
+
function getErrorMessage(error) {
|
|
20
|
+
return error instanceof Error ? error.message : String(error);
|
|
21
|
+
}
|
|
22
|
+
/** Wait for Gemini CLI to initialize after launch (banner + auth + dialog) */
|
|
23
|
+
const GEMINI_INIT_WAIT_MS = 6000;
|
|
24
|
+
/** Interval for polling trust dialog detection */
|
|
25
|
+
const TRUST_DIALOG_POLL_INTERVAL_MS = 1000;
|
|
26
|
+
/** Max attempts to detect trust dialog (10 * 1000ms = 10s polling window) */
|
|
27
|
+
const TRUST_DIALOG_MAX_ATTEMPTS = 10;
|
|
13
28
|
/**
|
|
14
29
|
* Gemini CLI tool implementation
|
|
15
|
-
* Manages Gemini sessions using tmux
|
|
30
|
+
* Manages Gemini interactive sessions using tmux
|
|
16
31
|
*/
|
|
17
32
|
class GeminiTool extends base_1.BaseCLITool {
|
|
18
33
|
id = 'gemini';
|
|
@@ -30,8 +45,7 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
32
47
|
* Start a new Gemini session for a worktree
|
|
33
|
-
*
|
|
34
|
-
* for running one-shot commands
|
|
48
|
+
* Launches `gemini` in interactive REPL mode within tmux
|
|
35
49
|
*
|
|
36
50
|
* @param worktreeId - Worktree ID
|
|
37
51
|
* @param worktreePath - Worktree path
|
|
@@ -50,22 +64,59 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
50
64
|
return;
|
|
51
65
|
}
|
|
52
66
|
try {
|
|
53
|
-
// Create tmux session
|
|
67
|
+
// Create tmux session with large history buffer
|
|
54
68
|
await (0, tmux_1.createSession)({
|
|
55
69
|
sessionName,
|
|
56
70
|
workingDirectory: worktreePath,
|
|
57
71
|
historyLimit: 50000,
|
|
58
72
|
});
|
|
73
|
+
// Wait a moment for the session to be created
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
75
|
+
// Start Gemini CLI in interactive mode (no flags = interactive REPL)
|
|
76
|
+
await (0, tmux_1.sendKeys)(sessionName, 'gemini', true);
|
|
77
|
+
// Wait for Gemini to initialize
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, GEMINI_INIT_WAIT_MS));
|
|
79
|
+
// Auto-handle "Do you trust this folder?" dialog on first run
|
|
80
|
+
await this.handleTrustDialog(sessionName);
|
|
59
81
|
console.log(`✓ Started Gemini session: ${sessionName}`);
|
|
60
82
|
}
|
|
61
83
|
catch (error) {
|
|
62
|
-
const errorMessage =
|
|
84
|
+
const errorMessage = getErrorMessage(error);
|
|
63
85
|
throw new Error(`Failed to start Gemini session: ${errorMessage}`);
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
89
|
+
* Handle Gemini "Do you trust this folder?" dialog
|
|
90
|
+
* On first run in a new directory, Gemini shows a trust confirmation.
|
|
91
|
+
* Auto-selects "1. Trust folder" to allow execution.
|
|
92
|
+
*/
|
|
93
|
+
async handleTrustDialog(sessionName) {
|
|
94
|
+
for (let i = 0; i < TRUST_DIALOG_MAX_ATTEMPTS; i++) {
|
|
95
|
+
try {
|
|
96
|
+
const output = await (0, tmux_1.capturePane)(sessionName, 50);
|
|
97
|
+
if (output.includes('Do you trust this folder?')) {
|
|
98
|
+
// Option 1 "Trust folder" is pre-selected (● marker).
|
|
99
|
+
// Send Enter to confirm the selection.
|
|
100
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'Enter');
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
102
|
+
console.log('✓ Auto-trusted folder for Gemini session');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Check if Gemini interactive prompt is already showing (no dialog needed)
|
|
106
|
+
if (output.match(/^[>❯]\s*$/m)) {
|
|
107
|
+
console.log('✓ Gemini prompt detected - no trust dialog needed');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Capture may fail during initialization - continue polling
|
|
113
|
+
}
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, TRUST_DIALOG_POLL_INTERVAL_MS));
|
|
115
|
+
}
|
|
116
|
+
console.log('⚠ Trust dialog detection timed out - proceeding anyway');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Send a message to Gemini interactive session
|
|
69
120
|
*
|
|
70
121
|
* @param worktreeId - Worktree ID
|
|
71
122
|
* @param message - Message to send
|
|
@@ -78,15 +129,22 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
78
129
|
throw new Error(`Gemini session ${sessionName} does not exist. Start the session first.`);
|
|
79
130
|
}
|
|
80
131
|
try {
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
132
|
+
// Send message to Gemini (without Enter)
|
|
133
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false);
|
|
134
|
+
// Wait a moment for the text to be typed
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136
|
+
// Send Enter key separately
|
|
137
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
|
|
138
|
+
// Wait a moment for the message to be processed
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
140
|
+
// Detect [Pasted text] and resend Enter for multi-line messages
|
|
141
|
+
if (message.includes('\n')) {
|
|
142
|
+
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
143
|
+
}
|
|
86
144
|
console.log(`✓ Sent message to Gemini session: ${sessionName}`);
|
|
87
145
|
}
|
|
88
146
|
catch (error) {
|
|
89
|
-
const errorMessage =
|
|
147
|
+
const errorMessage = getErrorMessage(error);
|
|
90
148
|
throw new Error(`Failed to send message to Gemini: ${errorMessage}`);
|
|
91
149
|
}
|
|
92
150
|
}
|
|
@@ -98,12 +156,13 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
98
156
|
async killSession(worktreeId) {
|
|
99
157
|
const sessionName = this.getSessionName(worktreeId);
|
|
100
158
|
try {
|
|
101
|
-
// Send Ctrl+D to exit Gemini gracefully
|
|
102
159
|
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
103
160
|
if (exists) {
|
|
104
|
-
// Send Ctrl+
|
|
105
|
-
await
|
|
106
|
-
|
|
161
|
+
// Send Ctrl+C to interrupt any running operation
|
|
162
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-c');
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
164
|
+
// Send /quit to exit Gemini gracefully
|
|
165
|
+
await (0, tmux_1.sendKeys)(sessionName, '/quit', true);
|
|
107
166
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
108
167
|
}
|
|
109
168
|
// Kill the tmux session
|
|
@@ -113,7 +172,7 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
113
172
|
}
|
|
114
173
|
}
|
|
115
174
|
catch (error) {
|
|
116
|
-
const errorMessage =
|
|
175
|
+
const errorMessage = getErrorMessage(error);
|
|
117
176
|
console.error(`Error stopping Gemini session: ${errorMessage}`);
|
|
118
177
|
throw error;
|
|
119
178
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* CLI Tool Manager
|
|
4
|
-
* Singleton class to manage multiple CLI tools (Claude, Codex, Gemini)
|
|
4
|
+
* Singleton class to manage multiple CLI tools (Claude, Codex, Gemini, Vibe Local)
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.CLIToolManager = void 0;
|
|
8
8
|
const claude_1 = require("./claude");
|
|
9
9
|
const codex_1 = require("./codex");
|
|
10
10
|
const gemini_1 = require("./gemini");
|
|
11
|
+
const vibe_local_1 = require("./vibe-local");
|
|
11
12
|
const response_poller_1 = require("../response-poller");
|
|
12
13
|
/**
|
|
13
14
|
* CLI Tool Manager (Singleton)
|
|
14
|
-
* Provides centralized access to all CLI tools
|
|
15
|
+
* Provides centralized access to all CLI tools (Issue #368: includes Vibe Local)
|
|
15
16
|
*/
|
|
16
17
|
class CLIToolManager {
|
|
17
18
|
static instance;
|
|
@@ -25,6 +26,7 @@ class CLIToolManager {
|
|
|
25
26
|
this.tools.set('claude', new claude_1.ClaudeTool());
|
|
26
27
|
this.tools.set('codex', new codex_1.CodexTool());
|
|
27
28
|
this.tools.set('gemini', new gemini_1.GeminiTool());
|
|
29
|
+
this.tools.set('vibe-local', new vibe_local_1.VibeLocalTool());
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Get singleton instance
|
|
@@ -3,10 +3,72 @@
|
|
|
3
3
|
* Type definitions and interfaces for CLI tools
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CLI_TOOL_IDS = void 0;
|
|
6
|
+
exports.OLLAMA_MODEL_PATTERN = exports.CLI_TOOL_DISPLAY_NAMES = exports.CLI_TOOL_IDS = void 0;
|
|
7
|
+
exports.isCliToolType = isCliToolType;
|
|
8
|
+
exports.getCliToolDisplayName = getCliToolDisplayName;
|
|
9
|
+
exports.getCliToolDisplayNameSafe = getCliToolDisplayNameSafe;
|
|
7
10
|
/**
|
|
8
11
|
* CLI Tool IDs constant array
|
|
9
12
|
* T2.1: Single source of truth for CLI tool IDs
|
|
10
13
|
* CLIToolType is derived from this constant (DRY principle)
|
|
11
14
|
*/
|
|
12
|
-
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini'];
|
|
15
|
+
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini', 'vibe-local'];
|
|
16
|
+
/**
|
|
17
|
+
* CLI tool display names for UI rendering
|
|
18
|
+
* Issue #368: Centralized display name mapping
|
|
19
|
+
*
|
|
20
|
+
* Usage: UI display (tab headers, message lists, settings).
|
|
21
|
+
* For internal logs/debug, use tool.name (BaseCLITool.name) instead.
|
|
22
|
+
*/
|
|
23
|
+
exports.CLI_TOOL_DISPLAY_NAMES = {
|
|
24
|
+
claude: 'Claude',
|
|
25
|
+
codex: 'Codex',
|
|
26
|
+
gemini: 'Gemini',
|
|
27
|
+
'vibe-local': 'Vibe Local',
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string is a valid CLIToolType
|
|
31
|
+
* Issue #368: Type guard for safe casting of untrusted CLI tool ID strings
|
|
32
|
+
*
|
|
33
|
+
* @param value - String to check
|
|
34
|
+
* @returns True if value is a valid CLIToolType
|
|
35
|
+
*/
|
|
36
|
+
function isCliToolType(value) {
|
|
37
|
+
return exports.CLI_TOOL_IDS.includes(value);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the display name for a CLI tool ID
|
|
41
|
+
* Issue #368: Centralized display name function for DRY compliance
|
|
42
|
+
*
|
|
43
|
+
* @param id - CLI tool type identifier
|
|
44
|
+
* @returns Human-readable display name
|
|
45
|
+
*/
|
|
46
|
+
function getCliToolDisplayName(id) {
|
|
47
|
+
return exports.CLI_TOOL_DISPLAY_NAMES[id] ?? id;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the display name for a CLI tool ID string, with fallback for unknown IDs
|
|
51
|
+
* Issue #368: Safe wrapper for UI components receiving untyped cliToolId strings
|
|
52
|
+
*
|
|
53
|
+
* Unlike getCliToolDisplayName(), this accepts optional/untyped strings and
|
|
54
|
+
* returns a fallback value ('Assistant') for null, undefined, or unknown IDs.
|
|
55
|
+
*
|
|
56
|
+
* @param cliToolId - Optional CLI tool ID string (may be untyped)
|
|
57
|
+
* @param fallback - Fallback display name for missing/unknown IDs (default: 'Assistant')
|
|
58
|
+
* @returns Human-readable display name or fallback
|
|
59
|
+
*/
|
|
60
|
+
function getCliToolDisplayNameSafe(cliToolId, fallback = 'Assistant') {
|
|
61
|
+
if (!cliToolId)
|
|
62
|
+
return fallback;
|
|
63
|
+
if (isCliToolType(cliToolId))
|
|
64
|
+
return getCliToolDisplayName(cliToolId);
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ollama model name validation pattern.
|
|
69
|
+
* Allows: alphanumeric start, followed by alphanumeric, dots, underscores, colons, slashes, hyphens.
|
|
70
|
+
* Max 100 characters. Used for defense-in-depth validation at point of use.
|
|
71
|
+
*
|
|
72
|
+
* [SEC-001] Shared between API route validation and CLI command construction
|
|
73
|
+
*/
|
|
74
|
+
exports.OLLAMA_MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vibe Local CLI tool implementation
|
|
4
|
+
* Provides integration with vibe-local (vibe-coder) in interactive mode
|
|
5
|
+
*
|
|
6
|
+
* @remarks Issue #368: Rewritten from non-interactive pipe mode to interactive REPL mode.
|
|
7
|
+
* Previous implementation used `echo 'msg' | vibe-local` which caused the process to exit
|
|
8
|
+
* immediately with "(Cancelled)" + "Goodbye!", making response polling impossible.
|
|
9
|
+
* Now launches `vibe-local -y` in interactive mode within tmux (same approach as Claude/Codex/Gemini).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.VibeLocalTool = void 0;
|
|
13
|
+
const base_1 = require("./base");
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
const tmux_1 = require("../tmux");
|
|
16
|
+
const pasted_text_helper_1 = require("../pasted-text-helper");
|
|
17
|
+
const db_instance_1 = require("../db-instance");
|
|
18
|
+
const db_1 = require("../db");
|
|
19
|
+
/**
|
|
20
|
+
* Extract error message from unknown error type (DRY)
|
|
21
|
+
*/
|
|
22
|
+
function getErrorMessage(error) {
|
|
23
|
+
return error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wait for vibe-local to initialize after launch.
|
|
27
|
+
* vibe-local shows a permission check prompt, banner, and model loading.
|
|
28
|
+
*/
|
|
29
|
+
const VIBE_LOCAL_INIT_WAIT_MS = 5000;
|
|
30
|
+
/**
|
|
31
|
+
* Vibe Local CLI tool implementation
|
|
32
|
+
* Manages vibe-local interactive sessions using tmux
|
|
33
|
+
*/
|
|
34
|
+
class VibeLocalTool extends base_1.BaseCLITool {
|
|
35
|
+
id = 'vibe-local';
|
|
36
|
+
name = 'Vibe Local';
|
|
37
|
+
command = 'vibe-local';
|
|
38
|
+
/**
|
|
39
|
+
* Check if vibe-local session is running for a worktree
|
|
40
|
+
*/
|
|
41
|
+
async isRunning(worktreeId) {
|
|
42
|
+
const sessionName = this.getSessionName(worktreeId);
|
|
43
|
+
return await (0, tmux_1.hasSession)(sessionName);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Start a new vibe-local session for a worktree
|
|
47
|
+
* Launches `vibe-local -y` in interactive mode within tmux
|
|
48
|
+
*
|
|
49
|
+
* @param worktreeId - Worktree ID
|
|
50
|
+
* @param worktreePath - Worktree path
|
|
51
|
+
*/
|
|
52
|
+
async startSession(worktreeId, worktreePath) {
|
|
53
|
+
const vibeLocalAvailable = await this.isInstalled();
|
|
54
|
+
if (!vibeLocalAvailable) {
|
|
55
|
+
throw new Error('vibe-local is not installed or not in PATH');
|
|
56
|
+
}
|
|
57
|
+
const sessionName = this.getSessionName(worktreeId);
|
|
58
|
+
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
59
|
+
if (exists) {
|
|
60
|
+
console.log(`Vibe Local session ${sessionName} already exists`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Create tmux session with large history buffer
|
|
65
|
+
await (0, tmux_1.createSession)({
|
|
66
|
+
sessionName,
|
|
67
|
+
workingDirectory: worktreePath,
|
|
68
|
+
historyLimit: 50000,
|
|
69
|
+
});
|
|
70
|
+
// Wait a moment for the session to be created
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
72
|
+
// Read Ollama model preference from DB
|
|
73
|
+
// [SEC-001] Re-validate model name at point of use (defense-in-depth)
|
|
74
|
+
let vibeLocalCommand = 'vibe-local -y';
|
|
75
|
+
try {
|
|
76
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
77
|
+
const wt = (0, db_1.getWorktreeById)(db, worktreeId);
|
|
78
|
+
if (wt?.vibeLocalModel && types_1.OLLAMA_MODEL_PATTERN.test(wt.vibeLocalModel)) {
|
|
79
|
+
vibeLocalCommand = `vibe-local -y -m ${wt.vibeLocalModel}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// DB read failure is non-fatal; use default model
|
|
84
|
+
}
|
|
85
|
+
// Start vibe-local in interactive mode with auto-approve (-y)
|
|
86
|
+
// -y flag skips the permission confirmation prompt
|
|
87
|
+
await (0, tmux_1.sendKeys)(sessionName, vibeLocalCommand, true);
|
|
88
|
+
// Wait for vibe-local to initialize (banner + model loading)
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, VIBE_LOCAL_INIT_WAIT_MS));
|
|
90
|
+
console.log(`✓ Started Vibe Local session: ${sessionName}`);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const errorMessage = getErrorMessage(error);
|
|
94
|
+
throw new Error(`Failed to start Vibe Local session: ${errorMessage}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Send a message to vibe-local interactive session
|
|
99
|
+
*
|
|
100
|
+
* @param worktreeId - Worktree ID
|
|
101
|
+
* @param message - Message to send
|
|
102
|
+
*/
|
|
103
|
+
async sendMessage(worktreeId, message) {
|
|
104
|
+
const sessionName = this.getSessionName(worktreeId);
|
|
105
|
+
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
106
|
+
if (!exists) {
|
|
107
|
+
throw new Error(`Vibe Local session ${sessionName} does not exist. Start the session first.`);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
// Send message to vibe-local (without Enter)
|
|
111
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false);
|
|
112
|
+
// Wait a moment for the text to be typed
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
114
|
+
// vibe-local uses IME mode: first Enter creates a new line,
|
|
115
|
+
// second Enter on empty line submits the message.
|
|
116
|
+
// Send Enter twice with a short delay between.
|
|
117
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
119
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
|
|
120
|
+
// Wait a moment for the message to be processed
|
|
121
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
122
|
+
// Detect [Pasted text] and resend Enter for multi-line messages
|
|
123
|
+
if (message.includes('\n')) {
|
|
124
|
+
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
125
|
+
}
|
|
126
|
+
console.log(`✓ Sent message to Vibe Local session: ${sessionName}`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const errorMessage = getErrorMessage(error);
|
|
130
|
+
throw new Error(`Failed to send message to Vibe Local: ${errorMessage}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Kill vibe-local session
|
|
135
|
+
*
|
|
136
|
+
* @param worktreeId - Worktree ID
|
|
137
|
+
*/
|
|
138
|
+
async killSession(worktreeId) {
|
|
139
|
+
const sessionName = this.getSessionName(worktreeId);
|
|
140
|
+
try {
|
|
141
|
+
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
142
|
+
if (exists) {
|
|
143
|
+
// Send Ctrl+C to interrupt any running operation
|
|
144
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-c');
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
146
|
+
// Send Ctrl+C again to ensure exit
|
|
147
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-c');
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
149
|
+
}
|
|
150
|
+
// Kill the tmux session
|
|
151
|
+
const killed = await (0, tmux_1.killSession)(sessionName);
|
|
152
|
+
if (killed) {
|
|
153
|
+
console.log(`✓ Stopped Vibe Local session: ${sessionName}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const errorMessage = getErrorMessage(error);
|
|
158
|
+
console.error(`Error stopping Vibe Local session: ${errorMessage}`);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.VibeLocalTool = VibeLocalTool;
|
|
@@ -24,6 +24,7 @@ exports.parseSchedulesSection = parseSchedulesSection;
|
|
|
24
24
|
exports.readCmateFile = readCmateFile;
|
|
25
25
|
const fs_1 = require("fs");
|
|
26
26
|
const path_1 = __importDefault(require("path"));
|
|
27
|
+
const types_1 = require("../lib/cli-tools/types");
|
|
27
28
|
const schedule_config_1 = require("../config/schedule-config");
|
|
28
29
|
const cmate_constants_1 = require("../config/cmate-constants");
|
|
29
30
|
Object.defineProperty(exports, "CMATE_FILENAME", { enumerable: true, get: function () { return cmate_constants_1.CMATE_FILENAME; } });
|
|
@@ -193,13 +194,34 @@ function parseSchedulesSection(rows) {
|
|
|
193
194
|
const enabled = enabledStr === undefined ||
|
|
194
195
|
enabledStr === '' ||
|
|
195
196
|
enabledStr.toLowerCase() === 'true';
|
|
196
|
-
// Parse
|
|
197
|
+
// Parse and validate CLI tool ID [SEC-002]
|
|
197
198
|
const resolvedCliToolId = cliToolId?.trim() || 'claude';
|
|
199
|
+
if (!(0, types_1.isCliToolType)(resolvedCliToolId)) {
|
|
200
|
+
console.warn(`[cmate-parser] Skipping entry "${sanitizedName}" with invalid CLI tool: "${resolvedCliToolId}"`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
198
203
|
const defaultPermission = schedule_config_1.DEFAULT_PERMISSIONS[resolvedCliToolId] ?? '';
|
|
199
204
|
let permission = permissionStr?.trim() || defaultPermission;
|
|
200
205
|
// Validate permission against allowed values
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
let allowedValues;
|
|
207
|
+
switch (resolvedCliToolId) {
|
|
208
|
+
case 'codex':
|
|
209
|
+
allowedValues = schedule_config_1.CODEX_SANDBOXES;
|
|
210
|
+
break;
|
|
211
|
+
case 'gemini':
|
|
212
|
+
case 'vibe-local':
|
|
213
|
+
// No permission flags for gemini/vibe-local; only empty string is valid
|
|
214
|
+
allowedValues = [];
|
|
215
|
+
if (permission) {
|
|
216
|
+
console.warn(`[cmate-parser] Permission "${permission}" ignored for ${resolvedCliToolId} in entry "${sanitizedName}" (no permission flags supported)`);
|
|
217
|
+
permission = '';
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
allowedValues = schedule_config_1.CLAUDE_PERMISSIONS;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (allowedValues.length > 0 && permission && !allowedValues.includes(permission)) {
|
|
203
225
|
console.warn(`[cmate-parser] Invalid permission "${permission}" for ${resolvedCliToolId} in entry "${sanitizedName}", using default "${defaultPermission}"`);
|
|
204
226
|
permission = defaultPermission;
|
|
205
227
|
}
|
|
@@ -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 = 19;
|
|
23
23
|
/**
|
|
24
24
|
* Migration registry
|
|
25
25
|
* All migrations should be added to this array in order
|
|
@@ -811,6 +811,55 @@ const migrations = [
|
|
|
811
811
|
db.exec('DROP TABLE IF EXISTS scheduled_executions');
|
|
812
812
|
console.log('✓ Dropped scheduled_executions and execution_logs tables');
|
|
813
813
|
}
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
version: 18,
|
|
817
|
+
name: 'add-selected-agents-column',
|
|
818
|
+
up: (db) => {
|
|
819
|
+
// Issue #368: Add selected_agents column for agent selection persistence
|
|
820
|
+
// NOTE (R1-010): The literal values 'claude', 'codex' in the SQL CASE below
|
|
821
|
+
// are fixed at migration time and do NOT sync with TypeScript CLI_TOOL_IDS.
|
|
822
|
+
// Changes to CLI_TOOL_IDS will not retroactively affect already-migrated data.
|
|
823
|
+
// Migration tests cover all CLIToolType values to catch sync issues.
|
|
824
|
+
// Step 1: Add column
|
|
825
|
+
db.exec(`
|
|
826
|
+
ALTER TABLE worktrees ADD COLUMN selected_agents TEXT;
|
|
827
|
+
`);
|
|
828
|
+
// Step 2: Initialize existing data based on cli_tool_id
|
|
829
|
+
// - If cli_tool_id is 'claude' or 'codex' -> default ["claude","codex"]
|
|
830
|
+
// - Otherwise (e.g. 'gemini', 'vibe-local') -> [cli_tool_id, "claude"]
|
|
831
|
+
db.exec(`
|
|
832
|
+
UPDATE worktrees SET selected_agents =
|
|
833
|
+
CASE
|
|
834
|
+
WHEN cli_tool_id NOT IN ('claude', 'codex')
|
|
835
|
+
THEN json_array(cli_tool_id, 'claude')
|
|
836
|
+
ELSE '["claude","codex"]'
|
|
837
|
+
END;
|
|
838
|
+
`);
|
|
839
|
+
console.log('✓ Added selected_agents column to worktrees table');
|
|
840
|
+
console.log('✓ Initialized selected_agents based on cli_tool_id');
|
|
841
|
+
},
|
|
842
|
+
down: () => {
|
|
843
|
+
// selected_agents is a nullable TEXT column; dropping it requires table recreation
|
|
844
|
+
// which is disproportionate for a rollback. The column is harmless if unused.
|
|
845
|
+
console.log('No rollback for selected_agents column (SQLite limitation)');
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
version: 19,
|
|
850
|
+
name: 'add-vibe-local-model-column',
|
|
851
|
+
up: (db) => {
|
|
852
|
+
// Issue #368: Add vibe_local_model column for Ollama model selection
|
|
853
|
+
// NULL means use the default model (vibe-local decides)
|
|
854
|
+
db.exec(`
|
|
855
|
+
ALTER TABLE worktrees ADD COLUMN vibe_local_model TEXT DEFAULT NULL;
|
|
856
|
+
`);
|
|
857
|
+
console.log('✓ Added vibe_local_model column to worktrees table');
|
|
858
|
+
},
|
|
859
|
+
down: () => {
|
|
860
|
+
// vibe_local_model is a nullable TEXT column; harmless if unused
|
|
861
|
+
console.log('No rollback for vibe_local_model column (SQLite limitation)');
|
|
862
|
+
}
|
|
814
863
|
}
|
|
815
864
|
];
|
|
816
865
|
/**
|