@in-the-loop-labs/pair-review 3.0.5 → 3.1.0
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/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/references/level1-balanced.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level1-fast.md +7 -0
- package/plugin-code-critic/skills/analyze/references/level1-thorough.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level2-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level3-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-fast.md +5 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-thorough.md +9 -0
- package/public/css/analysis-config.css +83 -0
- package/public/css/pr.css +191 -4
- package/public/index.html +20 -0
- package/public/js/components/AIPanel.js +1 -1
- package/public/js/components/AdvancedConfigTab.js +83 -8
- package/public/js/components/AnalysisConfigModal.js +155 -5
- package/public/js/components/ChatPanel.js +22 -5
- package/public/js/components/CouncilProgressModal.js +239 -22
- package/public/js/components/TimeoutSelect.js +2 -0
- package/public/js/components/VoiceCentricConfigTab.js +179 -12
- package/public/js/index.js +119 -1
- package/public/js/local.js +141 -47
- package/public/js/modules/suggestion-manager.js +2 -1
- package/public/js/pr.js +71 -12
- package/public/js/repo-settings.js +2 -2
- package/public/local.html +32 -11
- package/public/pr.html +2 -0
- package/src/ai/analyzer.js +371 -111
- package/src/ai/claude-provider.js +2 -0
- package/src/ai/codex-provider.js +1 -1
- package/src/ai/copilot-provider.js +2 -0
- package/src/ai/executable-provider.js +534 -0
- package/src/ai/gemini-provider.js +2 -0
- package/src/ai/index.js +9 -1
- package/src/ai/pi-provider.js +10 -8
- package/src/ai/prompts/baseline/consolidation/balanced.js +54 -2
- package/src/ai/prompts/baseline/consolidation/fast.js +31 -1
- package/src/ai/prompts/baseline/consolidation/thorough.js +46 -3
- package/src/ai/prompts/baseline/level1/balanced.js +12 -0
- package/src/ai/prompts/baseline/level1/fast.js +11 -0
- package/src/ai/prompts/baseline/level1/thorough.js +12 -0
- package/src/ai/prompts/baseline/level2/balanced.js +13 -0
- package/src/ai/prompts/baseline/level2/fast.js +12 -0
- package/src/ai/prompts/baseline/level2/thorough.js +13 -0
- package/src/ai/prompts/baseline/level3/balanced.js +13 -0
- package/src/ai/prompts/baseline/level3/fast.js +12 -0
- package/src/ai/prompts/baseline/level3/thorough.js +13 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +15 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +11 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +15 -0
- package/src/ai/prompts/render-for-skill.js +3 -0
- package/src/ai/prompts/shared/output-schema.js +8 -0
- package/src/ai/provider.js +89 -4
- package/src/chat/prompt-builder.js +17 -1
- package/src/chat/session-manager.js +32 -28
- package/src/config.js +15 -2
- package/src/database.js +59 -15
- package/src/git/base-branch.js +133 -52
- package/src/local-review.js +15 -9
- package/src/main.js +3 -2
- package/src/routes/analyses.js +34 -8
- package/src/routes/chat.js +15 -8
- package/src/routes/config.js +3 -120
- package/src/routes/councils.js +15 -6
- package/src/routes/executable-analysis.js +494 -0
- package/src/routes/local.js +160 -26
- package/src/routes/mcp.js +9 -4
- package/src/routes/pr.js +166 -29
- package/src/routes/reviews.js +31 -5
- package/src/routes/shared.js +72 -5
- package/src/routes/worktrees.js +4 -2
- package/src/utils/comment-formatter.js +28 -11
- package/src/utils/instructions.js +22 -8
- package/src/utils/logger.js +20 -10
package/src/routes/analyses.js
CHANGED
|
@@ -300,6 +300,25 @@ router.post('/api/analyses/results', async (req, res) => {
|
|
|
300
300
|
}
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* List currently active (running) analyses across all reviews.
|
|
305
|
+
* Returns lightweight projections from the in-memory activeAnalyses map.
|
|
306
|
+
*/
|
|
307
|
+
router.get('/api/analyses/active', (req, res) => {
|
|
308
|
+
const active = [];
|
|
309
|
+
for (const status of activeAnalyses.values()) {
|
|
310
|
+
if (status.status !== 'running') continue;
|
|
311
|
+
active.push({
|
|
312
|
+
analysisId: status.id,
|
|
313
|
+
reviewId: status.reviewId,
|
|
314
|
+
reviewType: status.reviewType || null,
|
|
315
|
+
repository: status.repository || null,
|
|
316
|
+
prNumber: status.prNumber || null
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
res.json({ active });
|
|
320
|
+
});
|
|
321
|
+
|
|
303
322
|
// ==========================================================================
|
|
304
323
|
// Parameterised :id routes — registered AFTER static paths
|
|
305
324
|
// ==========================================================================
|
|
@@ -396,7 +415,10 @@ router.post('/api/analyses/:id/cancel', async (req, res) => {
|
|
|
396
415
|
: analysis.levels?.[3],
|
|
397
416
|
4: analysis.levels?.[4]?.status === 'running'
|
|
398
417
|
? { status: 'cancelled', progress: 'Cancelled' }
|
|
399
|
-
: analysis.levels?.[4]
|
|
418
|
+
: analysis.levels?.[4],
|
|
419
|
+
exec: analysis.levels?.exec?.status === 'running'
|
|
420
|
+
? { status: 'cancelled', progress: 'Cancelled' }
|
|
421
|
+
: analysis.levels?.exec
|
|
400
422
|
}
|
|
401
423
|
};
|
|
402
424
|
|
|
@@ -446,7 +468,7 @@ router.post('/api/analyses/:id/cancel', async (req, res) => {
|
|
|
446
468
|
* @param {Object} modeContext - Mode-specific values
|
|
447
469
|
* @param {Object} councilConfig - Validated council configuration
|
|
448
470
|
* @param {string} councilId - Council ID (for the model field in analysis_runs), or null for inline config
|
|
449
|
-
* @param {Object} instructions - { repoInstructions, requestInstructions }
|
|
471
|
+
* @param {Object} instructions - { globalInstructions, repoInstructions, requestInstructions }
|
|
450
472
|
* @param {string} [configType='advanced'] - Config type
|
|
451
473
|
* @returns {{ analysisId: string, runId: string }}
|
|
452
474
|
*/
|
|
@@ -469,10 +491,12 @@ async function launchCouncilAnalysis(db, modeContext, councilConfig, councilId,
|
|
|
469
491
|
onSuccess,
|
|
470
492
|
runUpdateExtra,
|
|
471
493
|
config: modeConfig,
|
|
494
|
+
excludePrevious,
|
|
495
|
+
serverPort,
|
|
472
496
|
hookContext = {},
|
|
473
497
|
} = modeContext;
|
|
474
498
|
|
|
475
|
-
const { repoInstructions, requestInstructions } = instructions;
|
|
499
|
+
const { globalInstructions, repoInstructions, requestInstructions } = instructions;
|
|
476
500
|
|
|
477
501
|
const isVoiceCentric = configType === 'council';
|
|
478
502
|
|
|
@@ -496,6 +520,7 @@ async function launchCouncilAnalysis(db, modeContext, councilConfig, councilId,
|
|
|
496
520
|
provider: 'council',
|
|
497
521
|
model: councilId || 'inline-config',
|
|
498
522
|
tier: null,
|
|
523
|
+
globalInstructions,
|
|
499
524
|
repoInstructions,
|
|
500
525
|
requestInstructions,
|
|
501
526
|
headSha: headSha || null,
|
|
@@ -521,7 +546,8 @@ async function launchCouncilAnalysis(db, modeContext, councilConfig, councilId,
|
|
|
521
546
|
1: isLevelEnabled(councilConfig, '1') ? { status: 'running', progress: 'Starting...' } : { status: 'skipped', progress: 'Skipped' },
|
|
522
547
|
2: isLevelEnabled(councilConfig, '2') ? { status: 'running', progress: 'Starting...' } : { status: 'skipped', progress: 'Skipped' },
|
|
523
548
|
3: isLevelEnabled(councilConfig, '3') ? { status: 'running', progress: 'Starting...' } : { status: 'skipped', progress: 'Skipped' },
|
|
524
|
-
4: { status: 'pending', progress: 'Pending' }
|
|
549
|
+
4: { status: 'pending', progress: 'Pending' },
|
|
550
|
+
exec: { status: 'pending', progress: 'Pending' }
|
|
525
551
|
},
|
|
526
552
|
isCouncil: true,
|
|
527
553
|
councilConfig,
|
|
@@ -563,12 +589,12 @@ async function launchCouncilAnalysis(db, modeContext, councilConfig, councilId,
|
|
|
563
589
|
worktreePath,
|
|
564
590
|
prMetadata,
|
|
565
591
|
changedFiles,
|
|
566
|
-
instructions: { repoInstructions, requestInstructions }
|
|
592
|
+
instructions: { globalInstructions, repoInstructions, requestInstructions }
|
|
567
593
|
};
|
|
568
594
|
|
|
569
595
|
const analysisPromise = isVoiceCentric
|
|
570
|
-
? analyzer.runReviewerCentricCouncil(reviewContext, councilConfig, { analysisId, runId, progressCallback })
|
|
571
|
-
: analyzer.runCouncilAnalysis(reviewContext, councilConfig, { analysisId, runId, progressCallback });
|
|
596
|
+
? analyzer.runReviewerCentricCouncil(reviewContext, councilConfig, { analysisId, runId, progressCallback, excludePrevious, serverPort })
|
|
597
|
+
: analyzer.runCouncilAnalysis(reviewContext, councilConfig, { analysisId, runId, progressCallback, excludePrevious, serverPort });
|
|
572
598
|
|
|
573
599
|
analysisPromise
|
|
574
600
|
.then(async result => {
|
|
@@ -607,7 +633,7 @@ async function launchCouncilAnalysis(db, modeContext, councilConfig, councilId,
|
|
|
607
633
|
4: { status: 'completed', progress: 'Results finalized' }
|
|
608
634
|
}
|
|
609
635
|
};
|
|
610
|
-
for (const levelKey of ['1', '2', '3']) {
|
|
636
|
+
for (const levelKey of ['1', '2', '3', 'exec']) {
|
|
611
637
|
if (currentStatus.levels?.[levelKey]?.status === 'running') {
|
|
612
638
|
completedStatus.levels[levelKey] = { status: 'completed', progress: 'Complete' };
|
|
613
639
|
}
|
package/src/routes/chat.js
CHANGED
|
@@ -20,6 +20,7 @@ const logger = require('../utils/logger');
|
|
|
20
20
|
const ws = require('../ws');
|
|
21
21
|
const { fireHooks, hasHooks } = require('../hooks/hook-runner');
|
|
22
22
|
const { buildChatStartedPayload, buildChatResumedPayload, buildChatHookContext, getCachedUser } = require('../hooks/payloads');
|
|
23
|
+
const { resolveFormat } = require('../utils/comment-formatter');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Fire a chat hook event (non-blocking). Skips async work when no hooks are configured.
|
|
@@ -259,8 +260,10 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
259
260
|
|
|
260
261
|
const chatInstructions = await getChatInstructions(db, review);
|
|
261
262
|
const prData = await fetchPrData(db, review);
|
|
263
|
+
const config = req.app.get('config') || {};
|
|
264
|
+
const formatConfig = resolveFormat(config.comment_format);
|
|
262
265
|
|
|
263
|
-
finalSystemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
266
|
+
finalSystemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: formatConfig.template });
|
|
264
267
|
|
|
265
268
|
if (!skipAnalysisContext) {
|
|
266
269
|
// Fetch all AI suggestions from the latest analysis run
|
|
@@ -268,7 +271,7 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
268
271
|
SELECT
|
|
269
272
|
id, ai_run_id, ai_level, ai_confidence,
|
|
270
273
|
file, line_start, line_end, type, title, body,
|
|
271
|
-
reasoning, status, is_file_level
|
|
274
|
+
reasoning, status, is_file_level, severity
|
|
272
275
|
FROM comments
|
|
273
276
|
WHERE review_id = ?
|
|
274
277
|
AND source = 'ai'
|
|
@@ -323,7 +326,7 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
323
326
|
initialContext: initialContextWithPort
|
|
324
327
|
});
|
|
325
328
|
|
|
326
|
-
logger.info(`Chat session created: ${session.id} (provider=${provider}
|
|
329
|
+
logger.info(`Chat session created: ${session.id} (provider=${provider})`);
|
|
327
330
|
|
|
328
331
|
// Register broadcast listeners so events reach all connected clients
|
|
329
332
|
registerChatBroadcast(chatSessionManager, session.id, serverPort);
|
|
@@ -390,8 +393,10 @@ router.post('/api/chat/session/:id/message', async (req, res) => {
|
|
|
390
393
|
}
|
|
391
394
|
const chatInstructions = await getChatInstructions(db, review);
|
|
392
395
|
const prData = await fetchPrData(db, review);
|
|
396
|
+
const config = req.app.get('config') || {};
|
|
397
|
+
const fmtConfig = resolveFormat(config.comment_format);
|
|
393
398
|
|
|
394
|
-
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
399
|
+
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: fmtConfig.template });
|
|
395
400
|
const cwd = await resolveReviewCwd(db, review);
|
|
396
401
|
|
|
397
402
|
try {
|
|
@@ -405,7 +410,7 @@ router.post('/api/chat/session/:id/message', async (req, res) => {
|
|
|
405
410
|
// Inject port correction so the agent knows the current server address,
|
|
406
411
|
// even if the conversational history has a stale port from session creation.
|
|
407
412
|
const serverPort = req.socket.localPort;
|
|
408
|
-
portCorrectionContext = `[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}`;
|
|
413
|
+
portCorrectionContext = `[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}\n\n[Comment format template: ${fmtConfig.template}]`;
|
|
409
414
|
// Note: we intentionally do NOT re-inject the API cheat sheet on resume.
|
|
410
415
|
// The agent already has the endpoint shapes from the original session context —
|
|
411
416
|
// it only needs the updated port to adjust its curl calls.
|
|
@@ -530,8 +535,10 @@ router.post('/api/chat/session/:id/resume', async (req, res) => {
|
|
|
530
535
|
// initial session's context.
|
|
531
536
|
const chatInstructions = await getChatInstructions(db, review);
|
|
532
537
|
const prData = await fetchPrData(db, review);
|
|
538
|
+
const config = req.app.get('config') || {};
|
|
539
|
+
const fmtConfig = resolveFormat(config.comment_format);
|
|
533
540
|
|
|
534
|
-
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
541
|
+
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: fmtConfig.template });
|
|
535
542
|
const cwd = await resolveReviewCwd(db, review);
|
|
536
543
|
|
|
537
544
|
await chatSessionManager.resumeSession(sessionId, { systemPrompt, cwd });
|
|
@@ -544,7 +551,7 @@ router.post('/api/chat/session/:id/resume', async (req, res) => {
|
|
|
544
551
|
// Uses resumeContext (consumed on next sendMessage) instead of saveContextMessage
|
|
545
552
|
// (which only writes to DB and never reaches the agent process).
|
|
546
553
|
chatSessionManager.setResumeContext(sessionId,
|
|
547
|
-
`[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}`
|
|
554
|
+
`[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}\n\n[Comment format template: ${fmtConfig.template}]`
|
|
548
555
|
);
|
|
549
556
|
|
|
550
557
|
logger.info(`[ChatRoute] Explicitly resumed session ${sessionId}`);
|
|
@@ -622,7 +629,7 @@ router.get('/api/chat/analysis-context/:runId', async (req, res) => {
|
|
|
622
629
|
SELECT
|
|
623
630
|
id, ai_run_id, ai_level, ai_confidence,
|
|
624
631
|
file, line_start, line_end, type, title, body,
|
|
625
|
-
reasoning, status, is_file_level
|
|
632
|
+
reasoning, status, is_file_level, severity
|
|
626
633
|
FROM comments
|
|
627
634
|
WHERE review_id = ?
|
|
628
635
|
AND source = 'ai'
|
package/src/routes/config.js
CHANGED
|
@@ -20,10 +20,9 @@ const {
|
|
|
20
20
|
isCheckInProgress
|
|
21
21
|
} = require('../ai');
|
|
22
22
|
const { normalizeRepository } = require('../utils/paths');
|
|
23
|
-
const { isRunningViaNpx,
|
|
23
|
+
const { isRunningViaNpx, getGitHubToken } = require('../config');
|
|
24
24
|
const { version } = require('../../package.json');
|
|
25
25
|
const { getAllChatProviders, getAllCachedChatAvailability } = require('../chat/chat-providers');
|
|
26
|
-
const { PRESETS } = require('../utils/comment-formatter');
|
|
27
26
|
const logger = require('../utils/logger');
|
|
28
27
|
|
|
29
28
|
const router = express.Router();
|
|
@@ -45,6 +44,7 @@ router.get('/api/config', (req, res) => {
|
|
|
45
44
|
res.json({
|
|
46
45
|
version,
|
|
47
46
|
theme: config.theme || 'light',
|
|
47
|
+
has_github_token: Boolean(getGitHubToken(config)),
|
|
48
48
|
comment_button_action: config.comment_button_action || 'submit',
|
|
49
49
|
comment_format: config.comment_format || 'legacy',
|
|
50
50
|
// Include npx detection for frontend command examples
|
|
@@ -53,6 +53,7 @@ router.get('/api/config', (req, res) => {
|
|
|
53
53
|
chat_provider: config.chat_provider || 'pi',
|
|
54
54
|
chat_providers: chatProviders,
|
|
55
55
|
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false,
|
|
56
|
+
chat_enter_to_send: config.chat?.enter_to_send !== false,
|
|
56
57
|
pi_available: getCachedAvailability('pi')?.available || false,
|
|
57
58
|
assisted_by_url: config.assisted_by_url || 'https://github.com/in-the-loop-labs/pair-review',
|
|
58
59
|
enable_graphite: config.enable_graphite === true,
|
|
@@ -74,124 +75,6 @@ router.get('/api/config', (req, res) => {
|
|
|
74
75
|
});
|
|
75
76
|
});
|
|
76
77
|
|
|
77
|
-
/**
|
|
78
|
-
* Update user configuration
|
|
79
|
-
* Updates safe configuration values
|
|
80
|
-
*/
|
|
81
|
-
router.patch('/api/config', async (req, res) => {
|
|
82
|
-
try {
|
|
83
|
-
const { comment_button_action, chat_enable_shortcuts, comment_format } = req.body;
|
|
84
|
-
|
|
85
|
-
// Validate comment_button_action if provided
|
|
86
|
-
if (comment_button_action !== undefined) {
|
|
87
|
-
if (!['submit', 'preview'].includes(comment_button_action)) {
|
|
88
|
-
return res.status(400).json({
|
|
89
|
-
error: 'Invalid comment_button_action. Must be "submit" or "preview"'
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (chat_enable_shortcuts !== undefined) {
|
|
95
|
-
if (typeof chat_enable_shortcuts !== 'boolean') {
|
|
96
|
-
return res.status(400).json({
|
|
97
|
-
error: 'Invalid chat_enable_shortcuts. Must be a boolean'
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Validate comment_format if provided
|
|
103
|
-
if (comment_format !== undefined) {
|
|
104
|
-
const validPresets = Object.keys(PRESETS);
|
|
105
|
-
if (typeof comment_format === 'string') {
|
|
106
|
-
if (!validPresets.includes(comment_format)) {
|
|
107
|
-
return res.status(400).json({
|
|
108
|
-
error: `Invalid comment_format preset. Must be one of: ${validPresets.join(', ')}`
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
} else if (typeof comment_format === 'object' && comment_format !== null) {
|
|
112
|
-
if (!comment_format.template || typeof comment_format.template !== 'string') {
|
|
113
|
-
return res.status(400).json({
|
|
114
|
-
error: 'Custom comment_format must have a "template" string'
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
// Validate categoryOverrides if provided (must be a string->string mapping)
|
|
118
|
-
if (comment_format.categoryOverrides !== undefined) {
|
|
119
|
-
if (typeof comment_format.categoryOverrides !== 'object' || comment_format.categoryOverrides === null || Array.isArray(comment_format.categoryOverrides)) {
|
|
120
|
-
return res.status(400).json({
|
|
121
|
-
error: 'categoryOverrides must be an object mapping category names to replacement strings'
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
for (const [, value] of Object.entries(comment_format.categoryOverrides)) {
|
|
125
|
-
if (typeof value !== 'string') {
|
|
126
|
-
return res.status(400).json({
|
|
127
|
-
error: 'categoryOverrides must be a string-to-string mapping'
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// Validate emojiOverrides if provided (must be a string->string mapping)
|
|
133
|
-
if (comment_format.emojiOverrides !== undefined) {
|
|
134
|
-
if (typeof comment_format.emojiOverrides !== 'object' || comment_format.emojiOverrides === null || Array.isArray(comment_format.emojiOverrides)) {
|
|
135
|
-
return res.status(400).json({
|
|
136
|
-
error: 'emojiOverrides must be an object mapping category names to emoji strings'
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
for (const [, value] of Object.entries(comment_format.emojiOverrides)) {
|
|
140
|
-
if (typeof value !== 'string') {
|
|
141
|
-
return res.status(400).json({
|
|
142
|
-
error: 'emojiOverrides must be a string-to-string mapping'
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
return res.status(400).json({
|
|
149
|
-
error: 'comment_format must be a preset name string or a custom template object'
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Get current config
|
|
155
|
-
const config = req.app.get('config') || {};
|
|
156
|
-
|
|
157
|
-
// Update allowed fields
|
|
158
|
-
if (comment_button_action !== undefined) {
|
|
159
|
-
config.comment_button_action = comment_button_action;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (chat_enable_shortcuts !== undefined) {
|
|
163
|
-
if (!config.chat) config.chat = {};
|
|
164
|
-
config.chat.enable_shortcuts = chat_enable_shortcuts;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (comment_format !== undefined) {
|
|
168
|
-
config.comment_format = comment_format;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Save config to file
|
|
172
|
-
await saveConfig(config);
|
|
173
|
-
|
|
174
|
-
// Update app config
|
|
175
|
-
req.app.set('config', config);
|
|
176
|
-
|
|
177
|
-
res.json({
|
|
178
|
-
success: true,
|
|
179
|
-
config: {
|
|
180
|
-
theme: config.theme || 'light',
|
|
181
|
-
comment_button_action: config.comment_button_action || 'submit',
|
|
182
|
-
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false,
|
|
183
|
-
comment_format: config.comment_format || 'legacy'
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
} catch (error) {
|
|
188
|
-
logger.error('Error updating config:', error);
|
|
189
|
-
res.status(500).json({
|
|
190
|
-
error: 'Failed to update configuration'
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
78
|
/**
|
|
196
79
|
* Get repository-specific settings
|
|
197
80
|
* Returns default_instructions, default_provider, and default_model for the repository
|
package/src/routes/councils.js
CHANGED
|
@@ -11,6 +11,7 @@ const express = require('express');
|
|
|
11
11
|
const { v4: uuidv4 } = require('uuid');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
13
|
const { CouncilRepository } = require('../database');
|
|
14
|
+
const { getProviderClass } = require('../ai/provider');
|
|
14
15
|
|
|
15
16
|
const router = express.Router();
|
|
16
17
|
|
|
@@ -129,12 +130,20 @@ function validateCouncilFormat(config) {
|
|
|
129
130
|
return 'config.levels is required and must be an object';
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
// Skip level requirement when all voices are executable providers
|
|
134
|
+
const allExecutable = config.voices.every(v => {
|
|
135
|
+
const ProviderClass = getProviderClass(v.provider);
|
|
136
|
+
return ProviderClass?.isExecutable;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!allExecutable) {
|
|
140
|
+
const validLevels = ['1', '2', '3'];
|
|
141
|
+
const hasEnabled = Object.entries(config.levels).some(([key, val]) =>
|
|
142
|
+
validLevels.includes(key) && val === true
|
|
143
|
+
);
|
|
144
|
+
if (!hasEnabled) {
|
|
145
|
+
return 'At least one level (1, 2, or 3) must be enabled for non-executable providers';
|
|
146
|
+
}
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Validate consolidation (optional)
|