vibeman 0.0.1 → 0.0.2
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/dist/index.js +5 -7
- package/dist/runtime/api/.tsbuildinfo +1 -1
- package/dist/runtime/api/agent/agent-service.d.ts +11 -13
- package/dist/runtime/api/agent/agent-service.js +25 -31
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +48 -14
- package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +37 -0
- package/dist/runtime/api/agent/parsers.d.ts +1 -0
- package/dist/runtime/api/agent/parsers.js +75 -8
- package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
- package/dist/runtime/api/agent/prompt-service.js +123 -14
- package/dist/runtime/api/agent/prompt-service.test.d.ts +1 -0
- package/dist/runtime/api/agent/prompt-service.test.js +230 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +14 -14
- package/dist/runtime/api/api/routers/ai.d.ts +6 -6
- package/dist/runtime/api/api/routers/ai.js +2 -17
- package/dist/runtime/api/api/routers/executions.d.ts +5 -5
- package/dist/runtime/api/api/routers/executions.js +12 -21
- package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
- package/dist/runtime/api/api/routers/provider-config.js +252 -0
- package/dist/runtime/api/api/routers/tasks.d.ts +10 -10
- package/dist/runtime/api/api/routers/workflows.d.ts +15 -16
- package/dist/runtime/api/api/routers/workflows.js +28 -26
- package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
- package/dist/runtime/api/api/routers/worktrees.js +11 -11
- package/dist/runtime/api/api/trpc.d.ts +18 -18
- package/dist/runtime/api/index.js +2 -10
- package/dist/runtime/api/lib/local-config.d.ts +245 -0
- package/dist/runtime/api/lib/local-config.js +288 -0
- package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
- package/dist/runtime/api/lib/provider-detection.js +244 -0
- package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
- package/dist/runtime/api/lib/server/bootstrap.js +197 -0
- package/dist/runtime/api/lib/server/project-root.js +24 -1
- package/dist/runtime/api/lib/trpc/server.d.ts +124 -31
- package/dist/runtime/api/lib/trpc/server.js +8 -8
- package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
- package/dist/runtime/api/router.d.ts +125 -32
- package/dist/runtime/api/router.js +9 -31
- package/dist/runtime/api/settings-service.js +2 -0
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
- package/dist/runtime/api/workflows/vibing-orchestrator.js +182 -183
- package/dist/runtime/web/.next/BUILD_ID +1 -1
- package/dist/runtime/web/.next/app-build-manifest.json +2 -2
- package/dist/runtime/web/.next/build-manifest.json +2 -2
- package/dist/runtime/web/.next/prerender-manifest.json +3 -3
- package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
- package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
- package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
- package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/index.html +2 -2
- package/dist/runtime/web/.next/server/app/index.rsc +6 -6
- package/dist/runtime/web/.next/server/app/page.js +3 -3
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/chunks/458.js +1 -1
- package/dist/runtime/web/.next/server/pages/404.html +2 -2
- package/dist/runtime/web/.next/server/pages/500.html +1 -1
- package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
- package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/page-8c3ba579efc6f918.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → mRpNgPfbYR_0wrODzlg_4}/_buildManifest.js +0 -0
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → mRpNgPfbYR_0wrODzlg_4}/_ssgManifest.js +0 -0
|
@@ -64,6 +64,7 @@ export declare class AgentService extends EventEmitter {
|
|
|
64
64
|
attempt?: number;
|
|
65
65
|
};
|
|
66
66
|
providerOverride?: Partial<ResolvedProvider>;
|
|
67
|
+
workflowConfig?: import('../types/index.js').VibingConfig;
|
|
67
68
|
}): Promise<string>;
|
|
68
69
|
/**
|
|
69
70
|
* Build an appended system prompt with rerun context (reason, logs, failed checks)
|
|
@@ -97,13 +98,18 @@ export declare class AgentService extends EventEmitter {
|
|
|
97
98
|
type: string;
|
|
98
99
|
priority: string;
|
|
99
100
|
content: string;
|
|
101
|
+
title?: string;
|
|
100
102
|
executionId: string;
|
|
101
103
|
selectedModel?: string;
|
|
102
104
|
}>;
|
|
103
105
|
/**
|
|
104
106
|
* Perform AI code review of changes in a worktree
|
|
105
107
|
*/
|
|
106
|
-
aiReviewCode(taskId: string, reviewContext?: string,
|
|
108
|
+
aiReviewCode(taskId: string, reviewContext?: string, options?: {
|
|
109
|
+
overrides?: Partial<ResolvedProvider>;
|
|
110
|
+
executionId?: string;
|
|
111
|
+
workflowId?: string;
|
|
112
|
+
}): Promise<{
|
|
107
113
|
executionId: string;
|
|
108
114
|
reviewSummary: string;
|
|
109
115
|
recommendations: string[];
|
|
@@ -114,19 +120,11 @@ export declare class AgentService extends EventEmitter {
|
|
|
114
120
|
*/
|
|
115
121
|
aiMerge(taskId: string, options?: {
|
|
116
122
|
baseBranch?: string;
|
|
117
|
-
|
|
118
|
-
executionId
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* Compatibility alias for aiReviewCode
|
|
122
|
-
* @deprecated Use aiReviewCode instead
|
|
123
|
-
* TODO: migrate UI to use aiReviewCode directly and remove this
|
|
124
|
-
*/
|
|
125
|
-
reviewCode(taskId: string, reviewContext?: string, overrides?: Partial<ResolvedProvider>): Promise<{
|
|
123
|
+
overrides?: Partial<ResolvedProvider>;
|
|
124
|
+
executionId?: string;
|
|
125
|
+
workflowId?: string;
|
|
126
|
+
}): Promise<{
|
|
126
127
|
executionId: string;
|
|
127
|
-
reviewSummary: string;
|
|
128
|
-
recommendations: string[];
|
|
129
|
-
qualityScore: number;
|
|
130
128
|
}>;
|
|
131
129
|
/**
|
|
132
130
|
* Get persistent execution logs
|
|
@@ -325,8 +325,8 @@ export class AgentService extends EventEmitter {
|
|
|
325
325
|
catch (error) {
|
|
326
326
|
log.error('Failed to create worktree, using main directory', error, 'agent-service');
|
|
327
327
|
}
|
|
328
|
-
// Generate task prompt
|
|
329
|
-
const prompt = await this.promptService.generateTaskPrompt(task);
|
|
328
|
+
// Generate task prompt with workflow configuration for dynamic instructions
|
|
329
|
+
const prompt = await this.promptService.generateTaskPrompt(task, options?.workflowConfig);
|
|
330
330
|
// Build system prompt - use rerun context if available, otherwise base prompt
|
|
331
331
|
let appendSystemPrompt;
|
|
332
332
|
if (options?.rerunContext) {
|
|
@@ -349,6 +349,7 @@ export class AgentService extends EventEmitter {
|
|
|
349
349
|
tools: this.defaultTools.execute_task,
|
|
350
350
|
timeout: 30 * 60 * 1000,
|
|
351
351
|
appendSystemPrompt,
|
|
352
|
+
dangerouslyBypassApprovalsAndSandbox: true,
|
|
352
353
|
executionId,
|
|
353
354
|
metadata: {
|
|
354
355
|
operation: 'execute_task',
|
|
@@ -538,6 +539,7 @@ export class AgentService extends EventEmitter {
|
|
|
538
539
|
appendSystemPrompt: this.systemPrompts.improve_task,
|
|
539
540
|
tools: this.defaultTools.improve_task,
|
|
540
541
|
timeout: 5 * 60 * 1000,
|
|
542
|
+
dangerouslyBypassApprovalsAndSandbox: false,
|
|
541
543
|
executionId: finalExecutionId,
|
|
542
544
|
metadata: {
|
|
543
545
|
operation: 'improve_task',
|
|
@@ -559,7 +561,7 @@ export class AgentService extends EventEmitter {
|
|
|
559
561
|
/**
|
|
560
562
|
* Perform AI code review of changes in a worktree
|
|
561
563
|
*/
|
|
562
|
-
async aiReviewCode(taskId, reviewContext,
|
|
564
|
+
async aiReviewCode(taskId, reviewContext, options) {
|
|
563
565
|
const task = this.taskService.getTask(taskId);
|
|
564
566
|
if (!task) {
|
|
565
567
|
throw new Error(`Task ${taskId} not found`);
|
|
@@ -568,7 +570,7 @@ export class AgentService extends EventEmitter {
|
|
|
568
570
|
if (!worktree) {
|
|
569
571
|
throw new Error(`No worktree found for task ${taskId}`);
|
|
570
572
|
}
|
|
571
|
-
const executionId = generateId('review');
|
|
573
|
+
const executionId = options?.executionId ?? generateId('review');
|
|
572
574
|
try {
|
|
573
575
|
// Pre-register execution for streaming/persistence
|
|
574
576
|
if (!this.executionRegistry.has(executionId)) {
|
|
@@ -586,7 +588,7 @@ export class AgentService extends EventEmitter {
|
|
|
586
588
|
logs: [],
|
|
587
589
|
workingDirectory: this.projectRoot,
|
|
588
590
|
};
|
|
589
|
-
await this.logPersistence.startExecution(execRec);
|
|
591
|
+
await this.logPersistence.startExecution(execRec, options?.workflowId);
|
|
590
592
|
}
|
|
591
593
|
// Generate review prompt
|
|
592
594
|
const reviewPrompt = await this.promptService.generateReviewPrompt(task, worktree, reviewContext);
|
|
@@ -606,7 +608,7 @@ export class AgentService extends EventEmitter {
|
|
|
606
608
|
operation: 'ai_codereview',
|
|
607
609
|
taskId,
|
|
608
610
|
},
|
|
609
|
-
}, overrides);
|
|
611
|
+
}, options?.overrides);
|
|
610
612
|
// Parse review result
|
|
611
613
|
const review = this.parseReviewResult(result);
|
|
612
614
|
const payload = { executionId, ...review };
|
|
@@ -627,7 +629,7 @@ export class AgentService extends EventEmitter {
|
|
|
627
629
|
/**
|
|
628
630
|
* Perform AI-assisted merge of a task's worktree into the base project
|
|
629
631
|
*/
|
|
630
|
-
async aiMerge(taskId, options
|
|
632
|
+
async aiMerge(taskId, options) {
|
|
631
633
|
const task = this.taskService.getTask(taskId);
|
|
632
634
|
if (!task) {
|
|
633
635
|
throw new Error(`Task ${taskId} not found`);
|
|
@@ -636,14 +638,15 @@ export class AgentService extends EventEmitter {
|
|
|
636
638
|
if (!worktree) {
|
|
637
639
|
throw new Error(`No worktree found for task ${taskId}`);
|
|
638
640
|
}
|
|
639
|
-
const executionId = generateId('merge');
|
|
641
|
+
const executionId = options?.executionId ?? generateId('merge');
|
|
642
|
+
const workingDirectory = worktree.path || this.projectRoot;
|
|
640
643
|
try {
|
|
641
644
|
// Pre-register execution for streaming/persistence
|
|
642
645
|
if (!this.executionRegistry.has(executionId)) {
|
|
643
646
|
const startTime = new Date().toISOString();
|
|
644
647
|
this.executionRegistry.set(executionId, {
|
|
645
648
|
taskId,
|
|
646
|
-
workingDirectory
|
|
649
|
+
workingDirectory,
|
|
647
650
|
startTime,
|
|
648
651
|
});
|
|
649
652
|
const execRec = {
|
|
@@ -652,21 +655,21 @@ export class AgentService extends EventEmitter {
|
|
|
652
655
|
status: 'pending',
|
|
653
656
|
startTime,
|
|
654
657
|
logs: [],
|
|
655
|
-
workingDirectory
|
|
658
|
+
workingDirectory,
|
|
656
659
|
};
|
|
657
|
-
await this.logPersistence.startExecution(execRec);
|
|
660
|
+
await this.logPersistence.startExecution(execRec, options?.workflowId);
|
|
658
661
|
}
|
|
659
662
|
// Generate merge prompt
|
|
660
663
|
const mergePrompt = await this.promptService.generateAIMergePrompt(task, worktree, options?.baseBranch);
|
|
661
664
|
void this.logPersistence.logMessage(executionId, 'Merge prompt prepared', 'info', {
|
|
662
665
|
prompt: mergePrompt,
|
|
663
|
-
options: { workingDirectory
|
|
666
|
+
options: { workingDirectory, tools: this.defaultTools.ai_merge },
|
|
664
667
|
taskId,
|
|
665
668
|
baseBranch: options?.baseBranch,
|
|
666
669
|
});
|
|
667
670
|
// Execute merge with AI
|
|
668
671
|
await this.executeWithRouting('ai_merge', mergePrompt, {
|
|
669
|
-
workingDirectory
|
|
672
|
+
workingDirectory,
|
|
670
673
|
appendSystemPrompt: this.systemPrompts.ai_merge,
|
|
671
674
|
tools: this.defaultTools.ai_merge,
|
|
672
675
|
timeout: 15 * 60 * 1000,
|
|
@@ -676,29 +679,13 @@ export class AgentService extends EventEmitter {
|
|
|
676
679
|
taskId,
|
|
677
680
|
baseBranch: options?.baseBranch,
|
|
678
681
|
},
|
|
679
|
-
}, overrides);
|
|
680
|
-
// Ensure registry exists for lookups (no-op otherwise)
|
|
681
|
-
if (!this.executionRegistry.has(executionId)) {
|
|
682
|
-
this.executionRegistry.set(executionId, {
|
|
683
|
-
taskId,
|
|
684
|
-
workingDirectory: this.projectRoot,
|
|
685
|
-
startTime: new Date().toISOString(),
|
|
686
|
-
});
|
|
687
|
-
}
|
|
682
|
+
}, options?.overrides);
|
|
688
683
|
return { executionId };
|
|
689
684
|
}
|
|
690
685
|
catch (error) {
|
|
691
686
|
throw new Error(`AI merge failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
692
687
|
}
|
|
693
688
|
}
|
|
694
|
-
/**
|
|
695
|
-
* Compatibility alias for aiReviewCode
|
|
696
|
-
* @deprecated Use aiReviewCode instead
|
|
697
|
-
* TODO: migrate UI to use aiReviewCode directly and remove this
|
|
698
|
-
*/
|
|
699
|
-
async reviewCode(taskId, reviewContext, overrides) {
|
|
700
|
-
return this.aiReviewCode(taskId, reviewContext, overrides);
|
|
701
|
-
}
|
|
702
689
|
/**
|
|
703
690
|
* Get persistent execution logs
|
|
704
691
|
*/
|
|
@@ -843,8 +830,15 @@ export class AgentService extends EventEmitter {
|
|
|
843
830
|
catch {
|
|
844
831
|
// best effort; ignore if settings unavailable
|
|
845
832
|
}
|
|
846
|
-
let lastError = null;
|
|
847
833
|
const providersToTry = [resolved.provider, ...(resolved.fallbacks || [])];
|
|
834
|
+
// Ensure Codex executes with write permissions when it is in the routing set.
|
|
835
|
+
// Some overrides may omit the flag, so default it on for task execution.
|
|
836
|
+
if (operation === 'execute_task' &&
|
|
837
|
+
providersToTry.includes('codex') &&
|
|
838
|
+
executionOptions.dangerouslyBypassApprovalsAndSandbox === undefined) {
|
|
839
|
+
executionOptions.dangerouslyBypassApprovalsAndSandbox = true;
|
|
840
|
+
}
|
|
841
|
+
let lastError = null;
|
|
848
842
|
for (let i = 0; i < providersToTry.length; i++) {
|
|
849
843
|
const provider = providersToTry[i];
|
|
850
844
|
const isLastProvider = i === providersToTry.length - 1;
|
|
@@ -43,7 +43,7 @@ export declare class ClaudeCodeAdapter implements AIProvider {
|
|
|
43
43
|
*/
|
|
44
44
|
detectAvailableModels(): Promise<ModelInfo[]>;
|
|
45
45
|
/**
|
|
46
|
-
* Validate Claude Code setup
|
|
46
|
+
* Validate Claude Code setup using enhanced detection
|
|
47
47
|
*/
|
|
48
48
|
validateSetup(): Promise<ProviderStatus>;
|
|
49
49
|
/**
|
|
@@ -55,7 +55,7 @@ export declare class ClaudeCodeAdapter implements AIProvider {
|
|
|
55
55
|
*/
|
|
56
56
|
private mapToClaudeOptions;
|
|
57
57
|
/**
|
|
58
|
-
* Resolve Claude CLI executable path
|
|
58
|
+
* Resolve Claude CLI executable path using enhanced detection
|
|
59
59
|
*/
|
|
60
60
|
private getClaudeExecutablePath;
|
|
61
61
|
}
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Implements AIProvider interface for Claude Code SDK
|
|
4
4
|
*/
|
|
5
5
|
import { query } from '@anthropic-ai/claude-code';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import os from 'os';
|
|
8
6
|
import { generateId } from '../../lib/id-generator.js';
|
|
9
7
|
import { getSettingsService } from '../../settings-service.js';
|
|
8
|
+
import { getProviderDetectionService } from '../../lib/provider-detection.js';
|
|
10
9
|
/**
|
|
11
10
|
* Claude Code Adapter
|
|
12
11
|
* Wraps the Claude Code SDK to implement the AIProvider interface
|
|
@@ -21,7 +20,7 @@ export class ClaudeCodeAdapter {
|
|
|
21
20
|
* Execute a prompt using Claude Code SDK
|
|
22
21
|
*/
|
|
23
22
|
async *execute(prompt, options) {
|
|
24
|
-
const claudeOptions = this.mapToClaudeOptions(options);
|
|
23
|
+
const claudeOptions = await this.mapToClaudeOptions(options);
|
|
25
24
|
const co = claudeOptions;
|
|
26
25
|
const startTime = Date.now();
|
|
27
26
|
let sessionId;
|
|
@@ -249,30 +248,19 @@ export class ClaudeCodeAdapter {
|
|
|
249
248
|
];
|
|
250
249
|
}
|
|
251
250
|
/**
|
|
252
|
-
* Validate Claude Code setup
|
|
251
|
+
* Validate Claude Code setup using enhanced detection
|
|
253
252
|
*/
|
|
254
253
|
async validateSetup() {
|
|
255
254
|
try {
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
execSync('claude --version', { stdio: 'ignore' });
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return {
|
|
270
|
-
available: false,
|
|
271
|
-
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
272
|
-
models: [],
|
|
273
|
-
capabilities: this.getCapabilities(),
|
|
274
|
-
};
|
|
275
|
-
}
|
|
255
|
+
const detectionService = getProviderDetectionService();
|
|
256
|
+
const result = await detectionService.detectProvider('claude-code');
|
|
257
|
+
if (!result.found) {
|
|
258
|
+
return {
|
|
259
|
+
available: false,
|
|
260
|
+
error: result.error || 'Claude CLI not found. Please install Claude Code CLI.',
|
|
261
|
+
models: [],
|
|
262
|
+
capabilities: this.getCapabilities(),
|
|
263
|
+
};
|
|
276
264
|
}
|
|
277
265
|
const models = await this.detectAvailableModels();
|
|
278
266
|
return {
|
|
@@ -307,11 +295,11 @@ export class ClaudeCodeAdapter {
|
|
|
307
295
|
/**
|
|
308
296
|
* Map ExecutionOptions to Claude Code Options
|
|
309
297
|
*/
|
|
310
|
-
mapToClaudeOptions(options) {
|
|
298
|
+
async mapToClaudeOptions(options) {
|
|
311
299
|
const claudeOptions = {
|
|
312
300
|
cwd: options?.workingDirectory || this.config.defaultWorkingDirectory || process.cwd(),
|
|
313
301
|
model: options?.model || this.config.defaultModel,
|
|
314
|
-
pathToClaudeCodeExecutable: this.getClaudeExecutablePath(),
|
|
302
|
+
pathToClaudeCodeExecutable: await this.getClaudeExecutablePath(),
|
|
315
303
|
};
|
|
316
304
|
// Map temperature
|
|
317
305
|
if (options?.temperature !== undefined) {
|
|
@@ -342,18 +330,20 @@ export class ClaudeCodeAdapter {
|
|
|
342
330
|
return claudeOptions;
|
|
343
331
|
}
|
|
344
332
|
/**
|
|
345
|
-
* Resolve Claude CLI executable path
|
|
333
|
+
* Resolve Claude CLI executable path using enhanced detection
|
|
346
334
|
*/
|
|
347
|
-
getClaudeExecutablePath() {
|
|
348
|
-
// Highest precedence: explicit
|
|
349
|
-
const envPath = process.env.VIBEMAN_CLAUDE_BIN;
|
|
350
|
-
if (envPath && envPath.trim()) {
|
|
351
|
-
return envPath.trim();
|
|
352
|
-
}
|
|
335
|
+
async getClaudeExecutablePath() {
|
|
336
|
+
// Highest precedence: explicit config option
|
|
353
337
|
if (this.config.claudeBinPath) {
|
|
354
338
|
return this.config.claudeBinPath;
|
|
355
339
|
}
|
|
356
|
-
//
|
|
340
|
+
// Use enhanced detection service
|
|
341
|
+
const detectionService = getProviderDetectionService();
|
|
342
|
+
const result = await detectionService.detectProvider('claude-code');
|
|
343
|
+
if (result.found && result.path) {
|
|
344
|
+
return result.path;
|
|
345
|
+
}
|
|
346
|
+
// Fallback: try old settings-based approach for backwards compatibility
|
|
357
347
|
const settingsBinPath = (() => {
|
|
358
348
|
try {
|
|
359
349
|
const svc = getSettingsService();
|
|
@@ -367,7 +357,6 @@ export class ClaudeCodeAdapter {
|
|
|
367
357
|
if (settingsBinPath?.trim()) {
|
|
368
358
|
return settingsBinPath.trim();
|
|
369
359
|
}
|
|
370
|
-
|
|
371
|
-
return path.join(os.homedir(), '.claude', 'local', 'claude');
|
|
360
|
+
return 'claude';
|
|
372
361
|
}
|
|
373
362
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import { getSettingsService } from '../../settings-service.js';
|
|
7
|
+
import { getProviderDetectionService } from '../../lib/provider-detection.js';
|
|
7
8
|
import { logger } from '../../lib/logger.js';
|
|
8
9
|
export class CodexCliProvider {
|
|
9
10
|
constructor(config = {}) {
|
|
@@ -11,10 +12,16 @@ export class CodexCliProvider {
|
|
|
11
12
|
this.name = 'codex';
|
|
12
13
|
this.displayName = 'Codex CLI';
|
|
13
14
|
}
|
|
14
|
-
resolveExecutable() {
|
|
15
|
+
async resolveExecutable() {
|
|
15
16
|
if (this.config.codexBinPath)
|
|
16
17
|
return this.config.codexBinPath;
|
|
17
|
-
//
|
|
18
|
+
// Use enhanced detection service
|
|
19
|
+
const detectionService = getProviderDetectionService();
|
|
20
|
+
const result = await detectionService.detectProvider('codex');
|
|
21
|
+
if (result.found && result.path) {
|
|
22
|
+
return result.path;
|
|
23
|
+
}
|
|
24
|
+
// Fallback: try old settings-based approach for backwards compatibility
|
|
18
25
|
const settingsBinPath = (() => {
|
|
19
26
|
try {
|
|
20
27
|
const svc = getSettingsService();
|
|
@@ -51,8 +58,19 @@ export class CodexCliProvider {
|
|
|
51
58
|
const images = (options?.images || []).filter(Boolean);
|
|
52
59
|
const effort = options?.effort;
|
|
53
60
|
const timeoutMs = options?.timeout ?? this.config.defaultTimeoutMs ?? 10 * 60 * 1000; // 10m
|
|
61
|
+
const systemPrompt = options?.systemPrompt?.trim();
|
|
62
|
+
const appendSystemPrompt = options?.appendSystemPrompt?.trim();
|
|
63
|
+
const promptSegments = [];
|
|
64
|
+
if (systemPrompt) {
|
|
65
|
+
promptSegments.push(systemPrompt);
|
|
66
|
+
}
|
|
67
|
+
if (appendSystemPrompt) {
|
|
68
|
+
promptSegments.push(appendSystemPrompt);
|
|
69
|
+
}
|
|
70
|
+
promptSegments.push(prompt);
|
|
71
|
+
const effectivePrompt = promptSegments.join('\n\n');
|
|
54
72
|
// Build argv for `codex exec` (non-interactive automation mode)
|
|
55
|
-
const argv = ['exec',
|
|
73
|
+
const argv = ['exec', effectivePrompt];
|
|
56
74
|
if (model) {
|
|
57
75
|
argv.push('--model', model);
|
|
58
76
|
}
|
|
@@ -63,7 +81,10 @@ export class CodexCliProvider {
|
|
|
63
81
|
if (images.length) {
|
|
64
82
|
argv.push('--image', images.join(','));
|
|
65
83
|
}
|
|
66
|
-
|
|
84
|
+
if (options?.dangerouslyBypassApprovalsAndSandbox) {
|
|
85
|
+
argv.push('--dangerously-bypass-approvals-and-sandbox');
|
|
86
|
+
}
|
|
87
|
+
const cmd = await this.resolveExecutable();
|
|
67
88
|
const child = spawn(cmd, argv, {
|
|
68
89
|
cwd,
|
|
69
90
|
env: { ...process.env },
|
|
@@ -81,8 +102,17 @@ export class CodexCliProvider {
|
|
|
81
102
|
yield {
|
|
82
103
|
type: 'system',
|
|
83
104
|
timestamp: new Date().toISOString(),
|
|
84
|
-
content: `Running: ${cmd} ${argv
|
|
85
|
-
|
|
105
|
+
content: `Running: ${cmd} ${argv
|
|
106
|
+
.map((a) => (a.includes(' ') ? '"' + a + '"' : a))
|
|
107
|
+
.join(' ')} (cwd=${cwd})`,
|
|
108
|
+
metadata: {
|
|
109
|
+
provider: this.name,
|
|
110
|
+
images,
|
|
111
|
+
effort,
|
|
112
|
+
systemPrompt,
|
|
113
|
+
appendSystemPrompt,
|
|
114
|
+
dangerouslyBypassApprovalsAndSandbox: !!options?.dangerouslyBypassApprovalsAndSandbox,
|
|
115
|
+
},
|
|
86
116
|
};
|
|
87
117
|
let stdoutBuf = '';
|
|
88
118
|
// let stderrBuf = '';
|
|
@@ -243,13 +273,17 @@ export class CodexCliProvider {
|
|
|
243
273
|
}
|
|
244
274
|
async validateSetup() {
|
|
245
275
|
try {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
276
|
+
const detectionService = getProviderDetectionService();
|
|
277
|
+
const result = await detectionService.detectProvider('codex');
|
|
278
|
+
if (!result.found) {
|
|
279
|
+
return {
|
|
280
|
+
available: false,
|
|
281
|
+
error: result.error ||
|
|
282
|
+
'Codex CLI not found. Install from https://github.com/openai/codex (see Getting Started: CLI usage).',
|
|
283
|
+
models: [],
|
|
284
|
+
capabilities: this.getCapabilities(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
253
287
|
return {
|
|
254
288
|
available: true,
|
|
255
289
|
models: await this.detectAvailableModels(),
|
|
@@ -260,7 +294,7 @@ export class CodexCliProvider {
|
|
|
260
294
|
logger.error(error);
|
|
261
295
|
return {
|
|
262
296
|
available: false,
|
|
263
|
-
error:
|
|
297
|
+
error: error instanceof Error ? error.message : String(error),
|
|
264
298
|
models: [],
|
|
265
299
|
capabilities: this.getCapabilities(),
|
|
266
300
|
};
|
|
@@ -45,6 +45,8 @@ export interface ExecutionOptions {
|
|
|
45
45
|
images?: string[];
|
|
46
46
|
/** Optional effort hint for reasoning presets (e.g., 'minimal'|'low'|'medium'|'high'); informational only */
|
|
47
47
|
effort?: string;
|
|
48
|
+
/** Enable Codex CLI sandbox bypass flag when running automation that must edit files */
|
|
49
|
+
dangerouslyBypassApprovalsAndSandbox?: boolean;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Message types for streaming execution
|
|
@@ -55,6 +55,43 @@ describe('CodexCliProvider (mocked)', () => {
|
|
|
55
55
|
expect(modelIdx).toBeGreaterThan(-1);
|
|
56
56
|
expect(captured[1][modelIdx + 1]).toBe('gpt-5');
|
|
57
57
|
});
|
|
58
|
+
it('prepends system prompts and toggles sandbox bypass flag', async () => {
|
|
59
|
+
vi.resetModules();
|
|
60
|
+
vi.doMock('child_process', () => {
|
|
61
|
+
let captured = [];
|
|
62
|
+
return {
|
|
63
|
+
__captured: () => captured,
|
|
64
|
+
spawn: (cmd, args, opts) => {
|
|
65
|
+
captured = [cmd, args, opts];
|
|
66
|
+
const { EventEmitter } = require('events');
|
|
67
|
+
const stdout = new EventEmitter();
|
|
68
|
+
const proc = new EventEmitter();
|
|
69
|
+
proc.stdout = stdout;
|
|
70
|
+
setTimeout(() => stdout.emit('data', Buffer.from('Done\n')), 5);
|
|
71
|
+
setTimeout(() => proc.emit('exit', 0, null), 10);
|
|
72
|
+
return proc;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
|
|
77
|
+
const provider = new CodexCliProvider();
|
|
78
|
+
const iter = provider.execute('Implement feature', {
|
|
79
|
+
workingDirectory: '/tmp/project',
|
|
80
|
+
systemPrompt: 'Base system prompt',
|
|
81
|
+
appendSystemPrompt: 'Additional guidance',
|
|
82
|
+
dangerouslyBypassApprovalsAndSandbox: true,
|
|
83
|
+
});
|
|
84
|
+
for await (const _ of iter) {
|
|
85
|
+
// drain iterator
|
|
86
|
+
}
|
|
87
|
+
const mockSpawn = await import('child_process');
|
|
88
|
+
const captured = mockSpawn.__captured();
|
|
89
|
+
expect(captured[1][1]).toContain('Base system prompt');
|
|
90
|
+
expect(captured[1][1]).toContain('Additional guidance');
|
|
91
|
+
const bypassIdx = captured[1].indexOf('--dangerously-bypass-approvals-and-sandbox');
|
|
92
|
+
expect(bypassIdx).toBeGreaterThan(-1);
|
|
93
|
+
expect(captured[1][bypassIdx + 1]).toBeUndefined();
|
|
94
|
+
});
|
|
58
95
|
});
|
|
59
96
|
// Real integration (opt-in)
|
|
60
97
|
(USE_REAL ? describe : describe.skip)('CodexCliProvider (real CLI)', () => {
|
|
@@ -60,9 +60,10 @@ function extractLastValidJson(text) {
|
|
|
60
60
|
*/
|
|
61
61
|
function parseJsonManually(jsonStr) {
|
|
62
62
|
try {
|
|
63
|
-
// Extract type and
|
|
63
|
+
// Extract type, priority and title with simple regex (these are usually well-formed)
|
|
64
64
|
const typeMatch = jsonStr.match(/"type":\s*"([^"]+)"/);
|
|
65
65
|
const priorityMatch = jsonStr.match(/"priority":\s*"([^"]+)"/);
|
|
66
|
+
const titleMatch = jsonStr.match(/"title":\s*"([^"]+)"/);
|
|
66
67
|
// For content, find the start and extract everything until the closing brace
|
|
67
68
|
const contentStart = jsonStr.indexOf('"content":');
|
|
68
69
|
if (contentStart === -1)
|
|
@@ -103,11 +104,16 @@ function parseJsonManually(jsonStr) {
|
|
|
103
104
|
.replace(/\\t/g, '\t')
|
|
104
105
|
.replace(/\\"/g, '"')
|
|
105
106
|
.replace(/\\\\/g, '\\');
|
|
106
|
-
|
|
107
|
+
const result = {
|
|
107
108
|
type: typeMatch[1],
|
|
108
109
|
priority: priorityMatch[1],
|
|
109
110
|
content: content,
|
|
110
111
|
};
|
|
112
|
+
// Add title if found
|
|
113
|
+
if (titleMatch) {
|
|
114
|
+
result.title = titleMatch[1];
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
111
117
|
}
|
|
112
118
|
return null;
|
|
113
119
|
}
|
|
@@ -117,6 +123,24 @@ function parseJsonManually(jsonStr) {
|
|
|
117
123
|
return null;
|
|
118
124
|
}
|
|
119
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract title from markdown content and remove it from content
|
|
128
|
+
* Returns { title: string | null, cleanedContent: string }
|
|
129
|
+
*/
|
|
130
|
+
function extractTitleFromContent(content) {
|
|
131
|
+
if (!content || typeof content !== 'string') {
|
|
132
|
+
return { title: null, cleanedContent: content };
|
|
133
|
+
}
|
|
134
|
+
// Look for H1 heading at the start of content (with optional whitespace)
|
|
135
|
+
const h1Match = content.match(/^\s*#\s+([^\n\r]+)/);
|
|
136
|
+
if (h1Match) {
|
|
137
|
+
const title = h1Match[1].trim();
|
|
138
|
+
// Remove the H1 heading from content
|
|
139
|
+
const cleanedContent = content.replace(/^\s*#\s+[^\n\r]+\s*\n?/, '').trim();
|
|
140
|
+
return { title, cleanedContent };
|
|
141
|
+
}
|
|
142
|
+
return { title: null, cleanedContent: content };
|
|
143
|
+
}
|
|
120
144
|
export function parseImprovementResult(result, originalData) {
|
|
121
145
|
try {
|
|
122
146
|
const jsonBlock = extractLastValidJson(result);
|
|
@@ -130,13 +154,31 @@ export function parseImprovementResult(result, originalData) {
|
|
|
130
154
|
const validPriorities = ['high', 'medium', 'low'];
|
|
131
155
|
const normalizedType = String(parsed.type).toLowerCase();
|
|
132
156
|
const normalizedPriority = String(parsed.priority).toLowerCase();
|
|
133
|
-
|
|
157
|
+
// Extract title from JSON if present, otherwise from content
|
|
158
|
+
let finalTitle;
|
|
159
|
+
let finalContent = String(parsed.content).trim();
|
|
160
|
+
if (parsed.title && typeof parsed.title === 'string' && parsed.title.trim()) {
|
|
161
|
+
finalTitle = parsed.title.trim();
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Try to extract title from content
|
|
165
|
+
const { title, cleanedContent } = extractTitleFromContent(finalContent);
|
|
166
|
+
if (title) {
|
|
167
|
+
finalTitle = title;
|
|
168
|
+
finalContent = cleanedContent;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const result = {
|
|
134
172
|
type: validTypes.includes(normalizedType) ? normalizedType : originalData.type,
|
|
135
173
|
priority: validPriorities.includes(normalizedPriority)
|
|
136
174
|
? normalizedPriority
|
|
137
175
|
: originalData.priority,
|
|
138
|
-
content:
|
|
176
|
+
content: finalContent,
|
|
139
177
|
};
|
|
178
|
+
if (finalTitle) {
|
|
179
|
+
result.title = finalTitle;
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
140
182
|
}
|
|
141
183
|
}
|
|
142
184
|
catch (parseError) {
|
|
@@ -151,13 +193,31 @@ export function parseImprovementResult(result, originalData) {
|
|
|
151
193
|
const validPriorities = ['high', 'medium', 'low'];
|
|
152
194
|
const normalizedType = String(parsed.type).toLowerCase();
|
|
153
195
|
const normalizedPriority = String(parsed.priority).toLowerCase();
|
|
154
|
-
|
|
196
|
+
// Extract title from parsed JSON if present, otherwise from content
|
|
197
|
+
let finalTitle;
|
|
198
|
+
let finalContent = String(parsed.content).trim();
|
|
199
|
+
if (parsed.title && typeof parsed.title === 'string' && parsed.title.trim()) {
|
|
200
|
+
finalTitle = parsed.title.trim();
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Try to extract title from content
|
|
204
|
+
const { title, cleanedContent } = extractTitleFromContent(finalContent);
|
|
205
|
+
if (title) {
|
|
206
|
+
finalTitle = title;
|
|
207
|
+
finalContent = cleanedContent;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const result = {
|
|
155
211
|
type: validTypes.includes(normalizedType) ? normalizedType : originalData.type,
|
|
156
212
|
priority: validPriorities.includes(normalizedPriority)
|
|
157
213
|
? normalizedPriority
|
|
158
214
|
: originalData.priority,
|
|
159
|
-
content:
|
|
215
|
+
content: finalContent,
|
|
160
216
|
};
|
|
217
|
+
if (finalTitle) {
|
|
218
|
+
result.title = finalTitle;
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
161
221
|
}
|
|
162
222
|
}
|
|
163
223
|
catch (secondError) {
|
|
@@ -171,11 +231,18 @@ export function parseImprovementResult(result, originalData) {
|
|
|
171
231
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
172
232
|
log.error('Failed to parse improvement result', { error: errMsg, result }, 'result-parsers');
|
|
173
233
|
}
|
|
174
|
-
|
|
234
|
+
// Fallback: try to extract title from the raw result content
|
|
235
|
+
const fallbackContent = result.trim() || originalData.content;
|
|
236
|
+
const { title: extractedTitle, cleanedContent } = extractTitleFromContent(fallbackContent);
|
|
237
|
+
const fallbackResult = {
|
|
175
238
|
type: originalData.type,
|
|
176
239
|
priority: originalData.priority,
|
|
177
|
-
content:
|
|
240
|
+
content: cleanedContent,
|
|
178
241
|
};
|
|
242
|
+
if (extractedTitle) {
|
|
243
|
+
fallbackResult.title = extractedTitle;
|
|
244
|
+
}
|
|
245
|
+
return fallbackResult;
|
|
179
246
|
}
|
|
180
247
|
export function parseReviewResult(result) {
|
|
181
248
|
try {
|