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
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
* Handles provider selection per operation with fallbacks and configuration
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import fs from 'fs/promises';
|
|
7
|
-
import path from 'path';
|
|
8
6
|
import { log } from '../lib/logger.js';
|
|
9
7
|
import { getSettingsService } from '../settings-service.js';
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Operation types that support AI routing
|
|
10
|
+
*/
|
|
11
|
+
const ROUTABLE_OPERATIONS = [
|
|
12
|
+
'execute_task',
|
|
13
|
+
'quality_checks',
|
|
14
|
+
'ai_codereview',
|
|
15
|
+
'ai_merge',
|
|
16
|
+
'improve_task',
|
|
17
|
+
];
|
|
11
18
|
/**
|
|
12
19
|
* Generation options for AI execution
|
|
13
20
|
*/
|
|
@@ -30,67 +37,83 @@ export const OperationConfigSchema = z.object({
|
|
|
30
37
|
*/
|
|
31
38
|
export const RoutingPolicySchema = z.object({
|
|
32
39
|
defaultProvider: z.string(),
|
|
33
|
-
operations: z
|
|
34
|
-
.record(z.enum(['execute_task', 'improve_task', 'ai_codereview', 'ai_merge']), OperationConfigSchema)
|
|
35
|
-
.optional(),
|
|
40
|
+
operations: z.record(z.enum(ROUTABLE_OPERATIONS), OperationConfigSchema).optional(),
|
|
36
41
|
});
|
|
42
|
+
export const ROUTABLE_OPERATION_LIST = ROUTABLE_OPERATIONS;
|
|
37
43
|
/**
|
|
38
44
|
* Routing Policy Manager
|
|
39
|
-
* Manages AI provider routing policies
|
|
45
|
+
* Manages AI provider routing policies stored inside settings.json
|
|
40
46
|
*/
|
|
41
47
|
export class RoutingPolicyManager {
|
|
42
48
|
constructor() {
|
|
43
49
|
this.policy = null;
|
|
44
|
-
this.
|
|
45
|
-
|
|
50
|
+
this.settingsService = getSettingsService();
|
|
51
|
+
// Invalidate cached policy when settings change
|
|
52
|
+
this.settingsService.on('settingsUpdated', () => {
|
|
53
|
+
this.policy = null;
|
|
54
|
+
});
|
|
46
55
|
}
|
|
47
56
|
/**
|
|
48
|
-
* Get current effective policy
|
|
57
|
+
* Get current effective policy (lazy loads from settings)
|
|
49
58
|
*/
|
|
50
59
|
async getPolicy() {
|
|
51
|
-
await this.loadPolicyIfChanged();
|
|
52
|
-
// Return default policy if none exists
|
|
53
60
|
if (!this.policy) {
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
defaultProvider: settings.agents.defaultProvider || 'claude-code',
|
|
57
|
-
operations: {},
|
|
58
|
-
};
|
|
61
|
+
this.policy = this.buildPolicyFromSettings();
|
|
59
62
|
}
|
|
60
63
|
return this.policy;
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
63
|
-
* Update routing policy and persist
|
|
66
|
+
* Update routing policy and persist via settings service
|
|
64
67
|
*/
|
|
65
68
|
async updatePolicy(updates) {
|
|
66
69
|
const currentPolicy = await this.getPolicy();
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
operations
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const mergedOperations = {
|
|
71
|
+
...(currentPolicy.operations ?? {}),
|
|
72
|
+
...(updates.operations ?? {}),
|
|
73
|
+
};
|
|
74
|
+
const operationsKeys = Object.keys(mergedOperations);
|
|
75
|
+
const nextPolicy = {
|
|
76
|
+
defaultProvider: updates.defaultProvider ?? currentPolicy.defaultProvider,
|
|
77
|
+
...(operationsKeys.length > 0 ? { operations: mergedOperations } : {}),
|
|
73
78
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await fs.mkdir(vibemanDir, { recursive: true });
|
|
79
|
-
// Write to file
|
|
80
|
-
await fs.writeFile(this.policyFilePath, JSON.stringify(validated, null, 2), 'utf-8');
|
|
81
|
-
// Update in-memory policy
|
|
79
|
+
const validated = RoutingPolicySchema.parse(nextPolicy);
|
|
80
|
+
await this.settingsService.updateSettings([
|
|
81
|
+
{ path: ['agents', 'routingPolicy'], value: validated },
|
|
82
|
+
]);
|
|
82
83
|
this.policy = validated;
|
|
83
|
-
this.lastModified = Date.now();
|
|
84
84
|
log.info('Updated AI routing policy', { policy: validated }, 'routing-policy');
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
87
|
* Resolve provider for a specific operation
|
|
88
88
|
*/
|
|
89
|
+
async getEffectivePolicy() {
|
|
90
|
+
const base = await this.getPolicy();
|
|
91
|
+
const operations = {
|
|
92
|
+
...base.operations,
|
|
93
|
+
};
|
|
94
|
+
for (const op of ROUTABLE_OPERATIONS) {
|
|
95
|
+
if (!operations[op]) {
|
|
96
|
+
const resolved = await this.resolveProviderForOperation(op);
|
|
97
|
+
operations[op] = {
|
|
98
|
+
provider: resolved.provider,
|
|
99
|
+
...(resolved.model ? { model: resolved.model } : {}),
|
|
100
|
+
...(resolved.options ? { options: resolved.options } : {}),
|
|
101
|
+
...(resolved.fallbacks && resolved.fallbacks.length
|
|
102
|
+
? { fallback: resolved.fallbacks }
|
|
103
|
+
: {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
defaultProvider: base.defaultProvider,
|
|
109
|
+
operations,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
89
112
|
async resolveProviderForOperation(operation, overrides) {
|
|
90
113
|
const policy = await this.getPolicy();
|
|
91
114
|
const operationConfig = policy.operations?.[operation];
|
|
92
115
|
// Pull provider/model defaults from settings per operation
|
|
93
|
-
const settings =
|
|
116
|
+
const settings = this.settingsService.getSettings();
|
|
94
117
|
const coding = settings.agents?.codingAgent;
|
|
95
118
|
const judge = settings.agents?.judgeAgent;
|
|
96
119
|
const settingsProviderModel = (() => {
|
|
@@ -98,61 +121,54 @@ export class RoutingPolicyManager {
|
|
|
98
121
|
case 'ai_codereview':
|
|
99
122
|
return { provider: judge?.provider, model: judge?.model };
|
|
100
123
|
case 'execute_task':
|
|
101
|
-
case '
|
|
124
|
+
case 'quality_checks':
|
|
102
125
|
case 'ai_merge':
|
|
126
|
+
case 'improve_task':
|
|
103
127
|
default:
|
|
104
128
|
return { provider: coding?.provider, model: coding?.model };
|
|
105
129
|
}
|
|
106
130
|
})();
|
|
107
131
|
// Start with operation-specific config or fall back to settings, then default policy
|
|
108
132
|
const baseProvider = operationConfig?.provider || settingsProviderModel.provider || policy.defaultProvider;
|
|
109
|
-
const baseModel = operationConfig?.model
|
|
133
|
+
const baseModel = operationConfig?.model
|
|
134
|
+
? operationConfig.model
|
|
135
|
+
: operationConfig?.provider
|
|
136
|
+
? undefined
|
|
137
|
+
: settingsProviderModel.model;
|
|
110
138
|
const baseOptions = operationConfig?.options;
|
|
111
139
|
const baseFallbacks = operationConfig?.fallback || [];
|
|
112
140
|
// Apply overrides
|
|
141
|
+
const overrideProviderOnly = overrides?.provider && overrides.model === undefined;
|
|
113
142
|
const resolved = {
|
|
114
143
|
provider: overrides?.provider || baseProvider,
|
|
115
|
-
model: overrides?.model || baseModel,
|
|
144
|
+
model: overrideProviderOnly ? undefined : overrides?.model || baseModel,
|
|
116
145
|
options: overrides?.options || baseOptions,
|
|
117
146
|
fallbacks: overrides?.fallbacks || baseFallbacks,
|
|
118
147
|
};
|
|
119
148
|
log.debug(`Resolved provider for ${operation}`, { operation, resolved }, 'routing-policy');
|
|
120
149
|
return resolved;
|
|
121
150
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Set default provider
|
|
124
|
-
*/
|
|
125
151
|
async setDefaultProvider(provider) {
|
|
126
152
|
await this.updatePolicy({ defaultProvider: provider });
|
|
127
153
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Set operation-specific routing
|
|
130
|
-
*/
|
|
131
154
|
async setOperationConfig(operation, config) {
|
|
132
155
|
const currentPolicy = await this.getPolicy();
|
|
133
|
-
|
|
134
|
-
operations
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
156
|
+
const operations = {
|
|
157
|
+
...(currentPolicy.operations ?? {}),
|
|
158
|
+
[operation]: config,
|
|
159
|
+
};
|
|
160
|
+
await this.updatePolicy({ operations });
|
|
139
161
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Validate policy against available providers
|
|
142
|
-
*/
|
|
143
162
|
validatePolicy(policy, availableProviders) {
|
|
144
163
|
const errors = [];
|
|
145
|
-
// Check default provider
|
|
146
164
|
if (!availableProviders.has(policy.defaultProvider)) {
|
|
147
165
|
errors.push(`Default provider '${policy.defaultProvider}' is not available`);
|
|
148
166
|
}
|
|
149
|
-
// Check operation providers
|
|
150
167
|
if (policy.operations) {
|
|
151
168
|
for (const [operation, config] of Object.entries(policy.operations)) {
|
|
152
169
|
if (!availableProviders.has(config.provider)) {
|
|
153
170
|
errors.push(`Provider '${config.provider}' for operation '${operation}' is not available`);
|
|
154
171
|
}
|
|
155
|
-
// Check fallback providers
|
|
156
172
|
if (config.fallback) {
|
|
157
173
|
for (const fallbackProvider of config.fallback) {
|
|
158
174
|
if (!availableProviders.has(fallbackProvider)) {
|
|
@@ -164,83 +180,17 @@ export class RoutingPolicyManager {
|
|
|
164
180
|
}
|
|
165
181
|
return errors;
|
|
166
182
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
const content = await fs.readFile(this.policyFilePath, 'utf-8');
|
|
179
|
-
const parsed = JSON.parse(content);
|
|
180
|
-
const validated = RoutingPolicySchema.parse(parsed);
|
|
181
|
-
this.policy = validated;
|
|
182
|
-
this.lastModified = fileModified;
|
|
183
|
-
log.info('Loaded AI routing policy', { policy: validated }, 'routing-policy');
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
if (error.code === 'ENOENT') {
|
|
187
|
-
// File doesn't exist yet, use default
|
|
188
|
-
this.policy = null;
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
log.warn('Failed to load routing policy, using defaults', error, 'routing-policy');
|
|
192
|
-
this.policy = null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Create example policy file if it doesn't exist
|
|
197
|
-
*/
|
|
198
|
-
async createExamplePolicy() {
|
|
199
|
-
const examplePath = path.join(getVibeDir(), 'ai-routing.example.json');
|
|
200
|
-
// Do not recreate if it already exists
|
|
201
|
-
try {
|
|
202
|
-
await fs.stat(examplePath);
|
|
203
|
-
return; // already exists; avoid noisy logs
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
if (err?.code && err.code !== 'ENOENT') {
|
|
207
|
-
log.warn('Unable to stat example AI routing policy', err, 'routing-policy');
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const settings = getSettingsService().getSettings();
|
|
212
|
-
const example = {
|
|
213
|
-
defaultProvider: settings.agents.defaultProvider || 'claude-code',
|
|
214
|
-
operations: {
|
|
215
|
-
execute_task: {
|
|
216
|
-
provider: settings.agents.defaultProvider || 'claude-code',
|
|
217
|
-
model: 'claude-sonnet-4-20250514',
|
|
218
|
-
fallback: ['codex'],
|
|
219
|
-
},
|
|
220
|
-
improve_task: {
|
|
221
|
-
provider: settings.agents.defaultProvider || 'claude-code',
|
|
222
|
-
model: 'claude-sonnet-4-20250514',
|
|
223
|
-
},
|
|
224
|
-
ai_codereview: {
|
|
225
|
-
provider: 'codex',
|
|
226
|
-
model: 'gpt-5',
|
|
227
|
-
},
|
|
228
|
-
ai_merge: {
|
|
229
|
-
provider: settings.agents.defaultProvider || 'claude-code',
|
|
230
|
-
model: 'claude-sonnet-4-20250514',
|
|
231
|
-
},
|
|
232
|
-
},
|
|
183
|
+
buildPolicyFromSettings() {
|
|
184
|
+
const settings = this.settingsService.getSettings();
|
|
185
|
+
const settingsPolicy = settings.agents?.routingPolicy;
|
|
186
|
+
const defaultProvider = settingsPolicy?.defaultProvider || settings.agents.defaultProvider || 'claude-code';
|
|
187
|
+
const operations = settingsPolicy?.operations
|
|
188
|
+
? { ...settingsPolicy.operations }
|
|
189
|
+
: undefined;
|
|
190
|
+
const policy = {
|
|
191
|
+
defaultProvider,
|
|
192
|
+
...(operations ? { operations } : {}),
|
|
233
193
|
};
|
|
234
|
-
|
|
235
|
-
const vibemanDir = path.dirname(examplePath);
|
|
236
|
-
await fs.mkdir(vibemanDir, { recursive: true });
|
|
237
|
-
await fs.writeFile(examplePath, JSON.stringify(example, null, 2), 'utf-8');
|
|
238
|
-
log.info('Created example AI routing policy', { path: examplePath }, 'routing-policy');
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get policy file path for external access
|
|
242
|
-
*/
|
|
243
|
-
getPolicyFilePath() {
|
|
244
|
-
return this.policyFilePath;
|
|
194
|
+
return RoutingPolicySchema.parse(policy);
|
|
245
195
|
}
|
|
246
196
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
2
|
+
import { RoutingPolicyManager, } from './routing-policy.js';
|
|
3
|
+
import { getSettingsService } from '../settings-service.js';
|
|
4
|
+
const settingsService = getSettingsService();
|
|
5
|
+
const BASE_POLICY = {
|
|
6
|
+
defaultProvider: 'claude-code',
|
|
7
|
+
operations: {},
|
|
8
|
+
};
|
|
9
|
+
let originalRoutingPolicy;
|
|
10
|
+
async function setRoutingPolicy(value) {
|
|
11
|
+
const result = await settingsService.updateSettings([
|
|
12
|
+
{ path: ['agents', 'routingPolicy'], value },
|
|
13
|
+
]);
|
|
14
|
+
if (!result.success) {
|
|
15
|
+
throw new Error(`Failed to set routing policy: ${JSON.stringify(result.errors)}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
describe('RoutingPolicyManager resolve precedence', () => {
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
await settingsService.initialize();
|
|
21
|
+
const manager = new RoutingPolicyManager();
|
|
22
|
+
originalRoutingPolicy = await manager.getPolicy();
|
|
23
|
+
await setRoutingPolicy(BASE_POLICY);
|
|
24
|
+
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await setRoutingPolicy(BASE_POLICY);
|
|
27
|
+
});
|
|
28
|
+
afterAll(async () => {
|
|
29
|
+
await setRoutingPolicy(originalRoutingPolicy);
|
|
30
|
+
});
|
|
31
|
+
it('falls back to settings when no operation config or overrides', async () => {
|
|
32
|
+
const rpm = new RoutingPolicyManager();
|
|
33
|
+
const resolved = await rpm.resolveProviderForOperation('execute_task');
|
|
34
|
+
expect(resolved.provider).toBe('claude-code');
|
|
35
|
+
expect(resolved.model).toBe('claude-sonnet-4-20250514');
|
|
36
|
+
});
|
|
37
|
+
it('uses operation-specific policy when provided', async () => {
|
|
38
|
+
const rpm = new RoutingPolicyManager();
|
|
39
|
+
await rpm.updatePolicy({
|
|
40
|
+
operations: {
|
|
41
|
+
ai_merge: { provider: 'codex', model: 'gpt-5' },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const resolved = await rpm.resolveProviderForOperation('ai_merge');
|
|
45
|
+
expect(resolved.provider).toBe('codex');
|
|
46
|
+
expect(resolved.model).toBe('gpt-5');
|
|
47
|
+
});
|
|
48
|
+
it('when override sets provider without model, does not inherit mismatched model', async () => {
|
|
49
|
+
const rpm = new RoutingPolicyManager();
|
|
50
|
+
await rpm.updatePolicy({ operations: {} });
|
|
51
|
+
const override = { provider: 'codex' };
|
|
52
|
+
const resolved = await rpm.resolveProviderForOperation('execute_task', override);
|
|
53
|
+
expect(resolved.provider).toBe('codex');
|
|
54
|
+
expect(resolved.model).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
it('override with provider+model takes full precedence', async () => {
|
|
57
|
+
const rpm = new RoutingPolicyManager();
|
|
58
|
+
const override = { provider: 'codex', model: 'gpt-5' };
|
|
59
|
+
const resolved = await rpm.resolveProviderForOperation('execute_task', override);
|
|
60
|
+
expect(resolved.provider).toBe('codex');
|
|
61
|
+
expect(resolved.model).toBe('gpt-5');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -8,7 +8,7 @@ import { type RoutingPolicy, type RoutableOperation } from '../../agent/routing-
|
|
|
8
8
|
* Build AI router with required dependencies
|
|
9
9
|
*/
|
|
10
10
|
export declare function buildAIRoutes(options: {
|
|
11
|
-
|
|
11
|
+
agentService: AgentService;
|
|
12
12
|
}): {
|
|
13
13
|
/**
|
|
14
14
|
* List all registered AI providers with health status
|
|
@@ -40,7 +40,19 @@ export declare function buildAIRoutes(options: {
|
|
|
40
40
|
*/
|
|
41
41
|
getRoutingPolicy: import("@trpc/server").TRPCQueryProcedure<{
|
|
42
42
|
input: void;
|
|
43
|
-
output:
|
|
43
|
+
output: {
|
|
44
|
+
defaultProvider: string;
|
|
45
|
+
operations?: Partial<Record<"execute_task" | "quality_checks" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
46
|
+
provider: string;
|
|
47
|
+
options?: {
|
|
48
|
+
temperature?: number | undefined;
|
|
49
|
+
maxTokens?: number | undefined;
|
|
50
|
+
tools?: string[] | undefined;
|
|
51
|
+
} | undefined;
|
|
52
|
+
model?: string | undefined;
|
|
53
|
+
fallback?: string[] | undefined;
|
|
54
|
+
}>> | undefined;
|
|
55
|
+
};
|
|
44
56
|
meta: object;
|
|
45
57
|
}>;
|
|
46
58
|
/**
|
|
@@ -50,12 +62,12 @@ export declare function buildAIRoutes(options: {
|
|
|
50
62
|
input: {
|
|
51
63
|
policy: {
|
|
52
64
|
defaultProvider?: string | undefined;
|
|
53
|
-
operations?: Partial<Record<"execute_task" | "
|
|
65
|
+
operations?: Partial<Record<"execute_task" | "quality_checks" | "improve_task" | "ai_merge" | "ai_codereview", {
|
|
54
66
|
provider: string;
|
|
55
67
|
options?: {
|
|
68
|
+
temperature?: number | undefined;
|
|
56
69
|
maxTokens?: number | undefined;
|
|
57
70
|
tools?: string[] | undefined;
|
|
58
|
-
temperature?: number | undefined;
|
|
59
71
|
} | undefined;
|
|
60
72
|
model?: string | undefined;
|
|
61
73
|
fallback?: string[] | undefined;
|
|
@@ -86,17 +98,17 @@ export declare function buildAIRoutes(options: {
|
|
|
86
98
|
*/
|
|
87
99
|
setOperationConfig: import("@trpc/server").TRPCMutationProcedure<{
|
|
88
100
|
input: {
|
|
101
|
+
operation: "execute_task" | "quality_checks" | "improve_task" | "ai_merge" | "ai_codereview";
|
|
89
102
|
config: {
|
|
90
103
|
provider: string;
|
|
91
104
|
options?: {
|
|
105
|
+
temperature?: number | undefined;
|
|
92
106
|
maxTokens?: number | undefined;
|
|
93
107
|
tools?: string[] | undefined;
|
|
94
|
-
temperature?: number | undefined;
|
|
95
108
|
} | undefined;
|
|
96
109
|
model?: string | undefined;
|
|
97
110
|
fallback?: string[] | undefined;
|
|
98
111
|
};
|
|
99
|
-
operation: "execute_task" | "improve_task" | "ai_codereview" | "ai_merge";
|
|
100
112
|
};
|
|
101
113
|
output: {
|
|
102
114
|
success: boolean;
|
|
@@ -130,7 +142,7 @@ export declare function buildAIRoutes(options: {
|
|
|
130
142
|
* Useful for unit tests that call functions directly.
|
|
131
143
|
*/
|
|
132
144
|
export declare function buildAIRouteHandlers(options: {
|
|
133
|
-
|
|
145
|
+
agentService: AgentService;
|
|
134
146
|
}): {
|
|
135
147
|
readonly listProviders: () => Promise<{
|
|
136
148
|
name: string;
|
|
@@ -20,7 +20,7 @@ const SetDefaultProviderInputSchema = z.object({
|
|
|
20
20
|
provider: z.string(),
|
|
21
21
|
});
|
|
22
22
|
const SetOperationConfigInputSchema = z.object({
|
|
23
|
-
operation: z.enum(['execute_task', '
|
|
23
|
+
operation: z.enum(['execute_task', 'quality_checks', 'ai_codereview', 'ai_merge', 'improve_task']),
|
|
24
24
|
config: OperationConfigSchema,
|
|
25
25
|
});
|
|
26
26
|
const ValidateProvidersInputSchema = z.object({
|
|
@@ -30,14 +30,13 @@ const ValidateProvidersInputSchema = z.object({
|
|
|
30
30
|
* Build AI router with required dependencies
|
|
31
31
|
*/
|
|
32
32
|
export function buildAIRoutes(options) {
|
|
33
|
-
const {
|
|
33
|
+
const { agentService } = options;
|
|
34
34
|
return {
|
|
35
35
|
/**
|
|
36
36
|
* List all registered AI providers with health status
|
|
37
37
|
*/
|
|
38
38
|
listProviders: publicProcedure.query(async () => {
|
|
39
39
|
try {
|
|
40
|
-
const agentService = requireAgentService();
|
|
41
40
|
const providers = agentService.getAvailableProviders();
|
|
42
41
|
const providerStatuses = await agentService.validateProviders();
|
|
43
42
|
const result = Array.from(providers.entries()).map(([name, provider]) => {
|
|
@@ -65,7 +64,6 @@ export function buildAIRoutes(options) {
|
|
|
65
64
|
*/
|
|
66
65
|
listModels: publicProcedure.input(ListModelsInputSchema.optional()).query(async ({ input }) => {
|
|
67
66
|
try {
|
|
68
|
-
const agentService = requireAgentService();
|
|
69
67
|
if (input?.provider) {
|
|
70
68
|
// Get models from specific provider
|
|
71
69
|
const provider = agentService.getAvailableProviders().get(input.provider);
|
|
@@ -94,17 +92,17 @@ export function buildAIRoutes(options) {
|
|
|
94
92
|
*/
|
|
95
93
|
getRoutingPolicy: publicProcedure.query(async () => {
|
|
96
94
|
try {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
if (!policy) {
|
|
100
|
-
// Return default policy if none exists
|
|
95
|
+
const rpm = agentService.getRoutingPolicyManager?.();
|
|
96
|
+
if (!rpm) {
|
|
101
97
|
const settings = getSettingsService().getSettings();
|
|
102
|
-
|
|
98
|
+
return {
|
|
103
99
|
defaultProvider: settings.agents.defaultProvider || 'claude-code',
|
|
104
100
|
operations: {},
|
|
105
101
|
};
|
|
106
|
-
return defaultPolicy;
|
|
107
102
|
}
|
|
103
|
+
const policy = (rpm.getEffectivePolicy
|
|
104
|
+
? await rpm.getEffectivePolicy()
|
|
105
|
+
: await rpm.getPolicy());
|
|
108
106
|
log.debug('Retrieved routing policy', { policy }, 'ai-router');
|
|
109
107
|
return policy;
|
|
110
108
|
}
|
|
@@ -121,7 +119,6 @@ export function buildAIRoutes(options) {
|
|
|
121
119
|
.input(UpdateRoutingPolicyInputSchema)
|
|
122
120
|
.mutation(async ({ input }) => {
|
|
123
121
|
try {
|
|
124
|
-
const agentService = requireAgentService();
|
|
125
122
|
const routingManager = agentService.getRoutingPolicyManager();
|
|
126
123
|
if (!routingManager) {
|
|
127
124
|
throw new Error('Routing policy manager not available');
|
|
@@ -144,7 +141,6 @@ export function buildAIRoutes(options) {
|
|
|
144
141
|
.input(SetDefaultProviderInputSchema)
|
|
145
142
|
.mutation(async ({ input }) => {
|
|
146
143
|
try {
|
|
147
|
-
const agentService = requireAgentService();
|
|
148
144
|
const routingManager = agentService.getRoutingPolicyManager();
|
|
149
145
|
if (!routingManager) {
|
|
150
146
|
throw new Error('Routing policy manager not available');
|
|
@@ -171,7 +167,6 @@ export function buildAIRoutes(options) {
|
|
|
171
167
|
.input(SetOperationConfigInputSchema)
|
|
172
168
|
.mutation(async ({ input }) => {
|
|
173
169
|
try {
|
|
174
|
-
const agentService = requireAgentService();
|
|
175
170
|
const routingManager = agentService.getRoutingPolicyManager();
|
|
176
171
|
if (!routingManager) {
|
|
177
172
|
throw new Error('Routing policy manager not available');
|
|
@@ -206,7 +201,6 @@ export function buildAIRoutes(options) {
|
|
|
206
201
|
.input(ValidateProvidersInputSchema.optional())
|
|
207
202
|
.mutation(async ({ input }) => {
|
|
208
203
|
try {
|
|
209
|
-
const agentService = requireAgentService();
|
|
210
204
|
const results = await agentService.validateProviders();
|
|
211
205
|
const validation = Array.from(results.entries()).map(([name, status]) => ({
|
|
212
206
|
provider: name,
|
|
@@ -239,11 +233,10 @@ export function buildAIRoutes(options) {
|
|
|
239
233
|
* Useful for unit tests that call functions directly.
|
|
240
234
|
*/
|
|
241
235
|
export function buildAIRouteHandlers(options) {
|
|
242
|
-
const {
|
|
236
|
+
const { agentService } = options;
|
|
243
237
|
return {
|
|
244
238
|
async listProviders() {
|
|
245
239
|
try {
|
|
246
|
-
const agentService = requireAgentService();
|
|
247
240
|
const providers = agentService.getAvailableProviders();
|
|
248
241
|
const providerStatuses = await agentService.validateProviders();
|
|
249
242
|
return Array.from(providers.entries()).map(([name, provider]) => {
|
|
@@ -264,7 +257,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
264
257
|
},
|
|
265
258
|
async listModels(input) {
|
|
266
259
|
try {
|
|
267
|
-
const agentService = requireAgentService();
|
|
268
260
|
if (input?.provider) {
|
|
269
261
|
const provider = agentService.getAvailableProviders().get(input.provider);
|
|
270
262
|
if (!provider)
|
|
@@ -279,7 +271,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
279
271
|
},
|
|
280
272
|
async getRoutingPolicy() {
|
|
281
273
|
try {
|
|
282
|
-
const agentService = requireAgentService();
|
|
283
274
|
const rpm = agentService.getRoutingPolicyManager?.() ||
|
|
284
275
|
agentService.routingPolicyManager;
|
|
285
276
|
const policy = await rpm?.getPolicy();
|
|
@@ -298,7 +289,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
298
289
|
},
|
|
299
290
|
async updateRoutingPolicy(input) {
|
|
300
291
|
try {
|
|
301
|
-
const agentService = requireAgentService();
|
|
302
292
|
const routingManager = agentService.getRoutingPolicyManager?.() ||
|
|
303
293
|
agentService.routingPolicyManager;
|
|
304
294
|
if (!routingManager)
|
|
@@ -313,7 +303,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
313
303
|
},
|
|
314
304
|
async setDefaultProvider(input) {
|
|
315
305
|
try {
|
|
316
|
-
const agentService = requireAgentService();
|
|
317
306
|
const routingManager = agentService.getRoutingPolicyManager?.() ||
|
|
318
307
|
agentService.routingPolicyManager;
|
|
319
308
|
if (!routingManager)
|
|
@@ -330,7 +319,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
330
319
|
},
|
|
331
320
|
async setOperationConfig(input) {
|
|
332
321
|
try {
|
|
333
|
-
const agentService = requireAgentService();
|
|
334
322
|
const routingManager = agentService.getRoutingPolicyManager?.() ||
|
|
335
323
|
agentService.routingPolicyManager;
|
|
336
324
|
if (!routingManager)
|
|
@@ -355,7 +343,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
355
343
|
},
|
|
356
344
|
async validateProviders() {
|
|
357
345
|
try {
|
|
358
|
-
const agentService = requireAgentService();
|
|
359
346
|
const results = await agentService.validateProviders();
|
|
360
347
|
const validation = Array.from(results.entries()).map(([name, status]) => ({
|
|
361
348
|
provider: name,
|
|
@@ -377,7 +364,6 @@ export function buildAIRouteHandlers(options) {
|
|
|
377
364
|
},
|
|
378
365
|
async getProviderHealth() {
|
|
379
366
|
try {
|
|
380
|
-
const agentService = requireAgentService();
|
|
381
367
|
const providers = agentService.getAvailableProviders();
|
|
382
368
|
const statuses = await agentService.validateProviders();
|
|
383
369
|
const health = {
|
|
@@ -2,10 +2,9 @@ import type { TaskService } from '../../tasks/task-service.js';
|
|
|
2
2
|
import type { VibingOrchestrator } from '../../workflows/vibing-orchestrator.js';
|
|
3
3
|
type Deps = {
|
|
4
4
|
taskService: TaskService;
|
|
5
|
-
|
|
6
|
-
requireOrchestratorSync: () => VibingOrchestrator;
|
|
5
|
+
vibingOrchestrator: VibingOrchestrator;
|
|
7
6
|
};
|
|
8
|
-
export declare function buildExecutionRoutes({ taskService,
|
|
7
|
+
export declare function buildExecutionRoutes({ taskService, vibingOrchestrator }: Deps): {
|
|
9
8
|
readonly executeTask: import("@trpc/server").TRPCMutationProcedure<{
|
|
10
9
|
input: {
|
|
11
10
|
taskId: string;
|
|
@@ -66,9 +65,9 @@ export declare function buildExecutionRoutes({ taskService, requireOrchestrator,
|
|
|
66
65
|
}>;
|
|
67
66
|
readonly improveTask: import("@trpc/server").TRPCMutationProcedure<{
|
|
68
67
|
input: {
|
|
68
|
+
title: string;
|
|
69
69
|
type: "feature" | "bug" | "chore" | "refactor" | "test" | "doc";
|
|
70
70
|
priority: "low" | "medium" | "high";
|
|
71
|
-
title: string;
|
|
72
71
|
content: string;
|
|
73
72
|
taskId: string;
|
|
74
73
|
executionId?: string | undefined;
|
|
@@ -77,6 +76,7 @@ export declare function buildExecutionRoutes({ taskService, requireOrchestrator,
|
|
|
77
76
|
type: string;
|
|
78
77
|
priority: string;
|
|
79
78
|
content: string;
|
|
79
|
+
title?: string;
|
|
80
80
|
executionId: string;
|
|
81
81
|
selectedModel?: string;
|
|
82
82
|
};
|