vibeman 0.0.1 → 0.0.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/dist/index.js +5 -7
- package/dist/runtime/api/.tsbuildinfo +1 -1
- package/dist/runtime/api/agent/agent-service.d.ts +18 -19
- package/dist/runtime/api/agent/agent-service.js +61 -58
- 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.d.ts +2 -0
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
- package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
- 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.js +230 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
- package/dist/runtime/api/agent/routing-policy.js +82 -132
- package/dist/runtime/api/agent/routing-policy.test.js +63 -0
- package/dist/runtime/api/api/routers/ai.d.ts +19 -7
- package/dist/runtime/api/api/routers/ai.js +9 -23
- package/dist/runtime/api/api/routers/executions.d.ts +4 -4
- 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 +9 -9
- package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
- package/dist/runtime/api/api/routers/workflows.js +30 -27
- 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 +16 -16
- 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 +143 -30
- 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 +144 -31
- package/dist/runtime/api/router.js +9 -31
- package/dist/runtime/api/settings-service.js +51 -1
- package/dist/runtime/api/types/index.d.ts +8 -1
- package/dist/runtime/api/types/settings.d.ts +15 -2
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
- package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
- package/dist/runtime/web/.next/BUILD_ID +1 -1
- package/dist/runtime/web/.next/app-build-manifest.json +19 -12
- package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
- package/dist/runtime/web/.next/build-manifest.json +2 -2
- package/dist/runtime/web/.next/prerender-manifest.json +10 -10
- package/dist/runtime/web/.next/routes-manifest.json +8 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
- 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.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.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 +21 -21
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -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/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
- package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
- 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-9fe7d75095b4ccec.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
- package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
- package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
- package/dist/runtime/api/lib/markdown-utils.js +0 -282
- package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
- package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
- package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
- package/dist/runtime/api/lib/tiptap-utils.js +0 -327
- package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
- /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
- /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
|
@@ -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();
|
|
@@ -30,32 +37,67 @@ export class CodexCliProvider {
|
|
|
30
37
|
// Default to binary name (resolve via PATH)
|
|
31
38
|
return 'codex';
|
|
32
39
|
}
|
|
40
|
+
normalizeReasoningEffort(value) {
|
|
41
|
+
if (!value)
|
|
42
|
+
return undefined;
|
|
43
|
+
const normalized = value.toLowerCase();
|
|
44
|
+
if (normalized === 'minimal')
|
|
45
|
+
return 'low';
|
|
46
|
+
const allowed = ['low', 'medium', 'high'];
|
|
47
|
+
return allowed.includes(normalized)
|
|
48
|
+
? normalized
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
51
|
+
resolveModelSpec(model, fallbackEffort) {
|
|
52
|
+
const fallback = this.normalizeReasoningEffort(fallbackEffort);
|
|
53
|
+
if (!model) {
|
|
54
|
+
return { reasoning: fallback };
|
|
55
|
+
}
|
|
56
|
+
const trimmed = model.trim();
|
|
57
|
+
const normalized = trimmed.toLowerCase();
|
|
58
|
+
const match = normalized.match(/^(gpt-5(?:-codex)?)(?:-(minimal|low|medium|high))?$/);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return {
|
|
61
|
+
cliModel: trimmed,
|
|
62
|
+
requestedModel: trimmed,
|
|
63
|
+
reasoning: fallback,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const baseModel = match[1];
|
|
67
|
+
const reasoning = this.normalizeReasoningEffort(match[2]);
|
|
68
|
+
return {
|
|
69
|
+
cliModel: baseModel,
|
|
70
|
+
requestedModel: trimmed,
|
|
71
|
+
reasoning: reasoning ?? fallback,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
33
74
|
async *execute(prompt, options) {
|
|
34
75
|
// Effective options
|
|
35
76
|
const cwd = options?.workingDirectory || this.config.defaultWorkingDirectory || process.cwd();
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
return m;
|
|
39
|
-
// Normalize reasoning-effort variants to the canonical model id
|
|
40
|
-
// Users reported only `gpt-5` is selectable; variants are effort levels in the TUI.
|
|
41
|
-
const lower = m.toLowerCase();
|
|
42
|
-
if (lower === 'gpt-5-minimal' ||
|
|
43
|
-
lower === 'gpt-5-low' ||
|
|
44
|
-
lower === 'gpt-5-medium' ||
|
|
45
|
-
lower === 'gpt-5-high') {
|
|
46
|
-
return 'gpt-5';
|
|
47
|
-
}
|
|
48
|
-
return m;
|
|
49
|
-
};
|
|
50
|
-
const model = mapModel(options?.model || this.config.defaultModel);
|
|
77
|
+
const resolvedModel = this.resolveModelSpec(options?.model || this.config.defaultModel, options?.effort);
|
|
78
|
+
const model = resolvedModel.cliModel;
|
|
51
79
|
const images = (options?.images || []).filter(Boolean);
|
|
52
|
-
const
|
|
80
|
+
const reasoningEffort = resolvedModel.reasoning;
|
|
53
81
|
const timeoutMs = options?.timeout ?? this.config.defaultTimeoutMs ?? 10 * 60 * 1000; // 10m
|
|
82
|
+
const systemPrompt = options?.systemPrompt?.trim();
|
|
83
|
+
const appendSystemPrompt = options?.appendSystemPrompt?.trim();
|
|
84
|
+
const promptSegments = [];
|
|
85
|
+
if (systemPrompt) {
|
|
86
|
+
promptSegments.push(systemPrompt);
|
|
87
|
+
}
|
|
88
|
+
if (appendSystemPrompt) {
|
|
89
|
+
promptSegments.push(appendSystemPrompt);
|
|
90
|
+
}
|
|
91
|
+
promptSegments.push(prompt);
|
|
92
|
+
const effectivePrompt = promptSegments.join('\n\n');
|
|
54
93
|
// Build argv for `codex exec` (non-interactive automation mode)
|
|
55
|
-
const argv = ['exec',
|
|
94
|
+
const argv = ['exec', effectivePrompt];
|
|
56
95
|
if (model) {
|
|
57
96
|
argv.push('--model', model);
|
|
58
97
|
}
|
|
98
|
+
if (reasoningEffort) {
|
|
99
|
+
argv.push('-c', `model_reasoning_effort=${reasoningEffort}`);
|
|
100
|
+
}
|
|
59
101
|
// Prefer spawning with cwd, but also set --cd to make Codex aware of root
|
|
60
102
|
if (cwd) {
|
|
61
103
|
argv.push('--cd', cwd);
|
|
@@ -63,7 +105,10 @@ export class CodexCliProvider {
|
|
|
63
105
|
if (images.length) {
|
|
64
106
|
argv.push('--image', images.join(','));
|
|
65
107
|
}
|
|
66
|
-
|
|
108
|
+
if (options?.dangerouslyBypassApprovalsAndSandbox) {
|
|
109
|
+
argv.push('--dangerously-bypass-approvals-and-sandbox');
|
|
110
|
+
}
|
|
111
|
+
const cmd = await this.resolveExecutable();
|
|
67
112
|
const child = spawn(cmd, argv, {
|
|
68
113
|
cwd,
|
|
69
114
|
env: { ...process.env },
|
|
@@ -74,15 +119,24 @@ export class CodexCliProvider {
|
|
|
74
119
|
type: 'init',
|
|
75
120
|
timestamp: new Date().toISOString(),
|
|
76
121
|
sessionId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
77
|
-
model: model || 'default',
|
|
122
|
+
model: resolvedModel.requestedModel || model || 'default',
|
|
78
123
|
provider: this.name,
|
|
79
124
|
};
|
|
80
125
|
yield init;
|
|
81
126
|
yield {
|
|
82
127
|
type: 'system',
|
|
83
128
|
timestamp: new Date().toISOString(),
|
|
84
|
-
content: `Running: ${cmd} ${argv
|
|
85
|
-
|
|
129
|
+
content: `Running: ${cmd} ${argv
|
|
130
|
+
.map((a) => (a.includes(' ') ? '"' + a + '"' : a))
|
|
131
|
+
.join(' ')} (cwd=${cwd})`,
|
|
132
|
+
metadata: {
|
|
133
|
+
provider: this.name,
|
|
134
|
+
images,
|
|
135
|
+
effort: reasoningEffort || options?.effort,
|
|
136
|
+
systemPrompt,
|
|
137
|
+
appendSystemPrompt,
|
|
138
|
+
dangerouslyBypassApprovalsAndSandbox: !!options?.dangerouslyBypassApprovalsAndSandbox,
|
|
139
|
+
},
|
|
86
140
|
};
|
|
87
141
|
let stdoutBuf = '';
|
|
88
142
|
// let stderrBuf = '';
|
|
@@ -212,6 +266,7 @@ export class CodexCliProvider {
|
|
|
212
266
|
// Convenience: collect from execute()
|
|
213
267
|
let content = '';
|
|
214
268
|
const start = Date.now();
|
|
269
|
+
const resolvedModel = this.resolveModelSpec(options?.model || this.config.defaultModel, options?.effort);
|
|
215
270
|
for await (const msg of this.execute(prompt, options)) {
|
|
216
271
|
if (msg.type === 'assistant' && typeof msg.content === 'string') {
|
|
217
272
|
content += (content ? '\n' : '') + msg.content;
|
|
@@ -220,36 +275,47 @@ export class CodexCliProvider {
|
|
|
220
275
|
return {
|
|
221
276
|
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
222
277
|
provider: this.name,
|
|
223
|
-
model:
|
|
278
|
+
model: resolvedModel.requestedModel || resolvedModel.cliModel || 'default',
|
|
224
279
|
content,
|
|
225
280
|
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
226
281
|
duration: Date.now() - start,
|
|
227
282
|
};
|
|
228
283
|
}
|
|
229
284
|
async detectAvailableModels() {
|
|
230
|
-
|
|
285
|
+
const capabilities = ['code', 'analysis', 'tools', 'vision', 'image', 'reasoning'];
|
|
286
|
+
const makeModel = (id, displayName) => ({
|
|
287
|
+
id,
|
|
288
|
+
name: id,
|
|
289
|
+
displayName,
|
|
290
|
+
provider: this.name,
|
|
291
|
+
contextWindow: 256000,
|
|
292
|
+
maxOutputTokens: 16384,
|
|
293
|
+
capabilities,
|
|
294
|
+
});
|
|
231
295
|
return [
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
capabilities: ['code', 'analysis', 'tools', 'vision', 'image', 'reasoning'],
|
|
241
|
-
},
|
|
296
|
+
makeModel('gpt-5', 'gpt‑5'),
|
|
297
|
+
makeModel('gpt-5-low', 'gpt‑5 · low reasoning'),
|
|
298
|
+
makeModel('gpt-5-medium', 'gpt‑5 · medium reasoning'),
|
|
299
|
+
makeModel('gpt-5-high', 'gpt‑5 · high reasoning'),
|
|
300
|
+
makeModel('gpt-5-codex', 'gpt‑5 Codex'),
|
|
301
|
+
makeModel('gpt-5-codex-low', 'gpt‑5 Codex · low reasoning'),
|
|
302
|
+
makeModel('gpt-5-codex-medium', 'gpt‑5 Codex · medium reasoning'),
|
|
303
|
+
makeModel('gpt-5-codex-high', 'gpt‑5 Codex · high reasoning'),
|
|
242
304
|
];
|
|
243
305
|
}
|
|
244
306
|
async validateSetup() {
|
|
245
307
|
try {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
308
|
+
const detectionService = getProviderDetectionService();
|
|
309
|
+
const result = await detectionService.detectProvider('codex');
|
|
310
|
+
if (!result.found) {
|
|
311
|
+
return {
|
|
312
|
+
available: false,
|
|
313
|
+
error: result.error ||
|
|
314
|
+
'Codex CLI not found. Install from https://github.com/openai/codex (see Getting Started: CLI usage).',
|
|
315
|
+
models: [],
|
|
316
|
+
capabilities: this.getCapabilities(),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
253
319
|
return {
|
|
254
320
|
available: true,
|
|
255
321
|
models: await this.detectAvailableModels(),
|
|
@@ -260,7 +326,7 @@ export class CodexCliProvider {
|
|
|
260
326
|
logger.error(error);
|
|
261
327
|
return {
|
|
262
328
|
available: false,
|
|
263
|
-
error:
|
|
329
|
+
error: error instanceof Error ? error.message : String(error),
|
|
264
330
|
models: [],
|
|
265
331
|
capabilities: this.getCapabilities(),
|
|
266
332
|
};
|
|
@@ -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
|
|
@@ -24,10 +24,12 @@ describe('CodexCliProvider (mocked)', () => {
|
|
|
24
24
|
setTimeout(() => proc.emit('exit', 0, null), 20);
|
|
25
25
|
return proc;
|
|
26
26
|
},
|
|
27
|
+
exec: vi.fn(),
|
|
28
|
+
execFile: vi.fn(),
|
|
27
29
|
};
|
|
28
30
|
});
|
|
29
31
|
const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
|
|
30
|
-
const provider = new CodexCliProvider({ defaultTimeoutMs: 1000 });
|
|
32
|
+
const provider = new CodexCliProvider({ defaultTimeoutMs: 1000, codexBinPath: 'codex' });
|
|
31
33
|
const messages = [];
|
|
32
34
|
const iter = provider.execute('What is this project about?', {
|
|
33
35
|
workingDirectory: process.cwd(),
|
|
@@ -54,6 +56,86 @@ describe('CodexCliProvider (mocked)', () => {
|
|
|
54
56
|
const modelIdx = captured[1].indexOf('--model');
|
|
55
57
|
expect(modelIdx).toBeGreaterThan(-1);
|
|
56
58
|
expect(captured[1][modelIdx + 1]).toBe('gpt-5');
|
|
59
|
+
const configIdx = captured[1].indexOf('-c');
|
|
60
|
+
expect(configIdx).toBeGreaterThan(-1);
|
|
61
|
+
expect(captured[1][configIdx + 1]).toBe('model_reasoning_effort=low');
|
|
62
|
+
});
|
|
63
|
+
it('maps reasoning level encoded in model name to CLI flags', async () => {
|
|
64
|
+
vi.resetModules();
|
|
65
|
+
vi.doMock('child_process', () => {
|
|
66
|
+
let captured = [];
|
|
67
|
+
return {
|
|
68
|
+
__captured: () => captured,
|
|
69
|
+
spawn: (cmd, args, opts) => {
|
|
70
|
+
captured = [cmd, args, opts];
|
|
71
|
+
const { EventEmitter } = require('events');
|
|
72
|
+
const stdout = new EventEmitter();
|
|
73
|
+
const proc = new EventEmitter();
|
|
74
|
+
proc.stdout = stdout;
|
|
75
|
+
setTimeout(() => stdout.emit('data', Buffer.from('Reasoned\n')), 10);
|
|
76
|
+
setTimeout(() => proc.emit('exit', 0, null), 20);
|
|
77
|
+
return proc;
|
|
78
|
+
},
|
|
79
|
+
exec: vi.fn(),
|
|
80
|
+
execFile: vi.fn(),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
|
|
84
|
+
const provider = new CodexCliProvider({ codexBinPath: 'codex' });
|
|
85
|
+
const messages = [];
|
|
86
|
+
const iter = provider.execute('Solve with depth', {
|
|
87
|
+
workingDirectory: process.cwd(),
|
|
88
|
+
model: 'gpt-5-codex-high',
|
|
89
|
+
});
|
|
90
|
+
for await (const m of iter)
|
|
91
|
+
messages.push(m);
|
|
92
|
+
const mockSpawn = await import('child_process');
|
|
93
|
+
const captured = mockSpawn.__captured();
|
|
94
|
+
const modelIdx = captured[1].indexOf('--model');
|
|
95
|
+
expect(modelIdx).toBeGreaterThan(-1);
|
|
96
|
+
expect(captured[1][modelIdx + 1]).toBe('gpt-5-codex');
|
|
97
|
+
const configIdx = captured[1].indexOf('-c');
|
|
98
|
+
expect(configIdx).toBeGreaterThan(-1);
|
|
99
|
+
expect(captured[1][configIdx + 1]).toBe('model_reasoning_effort=high');
|
|
100
|
+
});
|
|
101
|
+
it('prepends system prompts and toggles sandbox bypass flag', async () => {
|
|
102
|
+
vi.resetModules();
|
|
103
|
+
vi.doMock('child_process', () => {
|
|
104
|
+
let captured = [];
|
|
105
|
+
return {
|
|
106
|
+
__captured: () => captured,
|
|
107
|
+
spawn: (cmd, args, opts) => {
|
|
108
|
+
captured = [cmd, args, opts];
|
|
109
|
+
const { EventEmitter } = require('events');
|
|
110
|
+
const stdout = new EventEmitter();
|
|
111
|
+
const proc = new EventEmitter();
|
|
112
|
+
proc.stdout = stdout;
|
|
113
|
+
setTimeout(() => stdout.emit('data', Buffer.from('Done\n')), 5);
|
|
114
|
+
setTimeout(() => proc.emit('exit', 0, null), 10);
|
|
115
|
+
return proc;
|
|
116
|
+
},
|
|
117
|
+
exec: vi.fn(),
|
|
118
|
+
execFile: vi.fn(),
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
|
|
122
|
+
const provider = new CodexCliProvider({ codexBinPath: 'codex' });
|
|
123
|
+
const iter = provider.execute('Implement feature', {
|
|
124
|
+
workingDirectory: '/tmp/project',
|
|
125
|
+
systemPrompt: 'Base system prompt',
|
|
126
|
+
appendSystemPrompt: 'Additional guidance',
|
|
127
|
+
dangerouslyBypassApprovalsAndSandbox: true,
|
|
128
|
+
});
|
|
129
|
+
for await (const _ of iter) {
|
|
130
|
+
// drain iterator
|
|
131
|
+
}
|
|
132
|
+
const mockSpawn = await import('child_process');
|
|
133
|
+
const captured = mockSpawn.__captured();
|
|
134
|
+
expect(captured[1][1]).toContain('Base system prompt');
|
|
135
|
+
expect(captured[1][1]).toContain('Additional guidance');
|
|
136
|
+
const bypassIdx = captured[1].indexOf('--dangerously-bypass-approvals-and-sandbox');
|
|
137
|
+
expect(bypassIdx).toBeGreaterThan(-1);
|
|
138
|
+
expect(captured[1][bypassIdx + 1]).toBeUndefined();
|
|
57
139
|
});
|
|
58
140
|
});
|
|
59
141
|
// Real integration (opt-in)
|
|
@@ -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 {
|
|
@@ -4,13 +4,26 @@ export declare class PromptService {
|
|
|
4
4
|
private projectRoot;
|
|
5
5
|
private taskService;
|
|
6
6
|
constructor(projectRoot: string, taskService: TaskService);
|
|
7
|
+
/**
|
|
8
|
+
* Generate absolute path to task file
|
|
9
|
+
*/
|
|
10
|
+
private generateTaskFilePath;
|
|
11
|
+
/**
|
|
12
|
+
* Validate that task file exists at the given path
|
|
13
|
+
*/
|
|
14
|
+
private validateTaskFile;
|
|
7
15
|
generateImprovementPrompt(task: Task, taskData: {
|
|
8
16
|
title: string;
|
|
9
17
|
type: string;
|
|
10
18
|
priority: string;
|
|
11
19
|
content: string;
|
|
12
20
|
}): Promise<string>;
|
|
13
|
-
generateTaskPrompt(task: Task): Promise<string>;
|
|
21
|
+
generateTaskPrompt(task: Task, workflowConfig?: import('../types/index.js').VibingConfig): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a prominent, clearly formatted instruction section derived from workflow options.
|
|
24
|
+
* If no options are provided or none apply, returns a minimal guidance line.
|
|
25
|
+
*/
|
|
26
|
+
private buildWorkflowInstructions;
|
|
14
27
|
generateAIMergePrompt(task: Task, worktree: WorktreeInfo, baseBranch?: string): Promise<string>;
|
|
15
28
|
generateReviewPrompt(task: Task, worktree: WorktreeInfo, reviewContext?: string): Promise<string>;
|
|
16
29
|
}
|