agentboss 0.1.3 → 0.1.4
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/client/dist/assets/{index-CT8rBVfX.js → index-sks7Tuv7.js} +52 -52
- package/client/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/analysis/report-builder.js +28 -1
- package/server/api/execution.js +4 -4
- package/server/api/overview.js +25 -14
- package/server/api/settings.js +139 -119
- package/server/db/queries.js +1108 -1051
- package/server/execution/job.js +63 -12
- package/server/llm/advice.js +15 -7
- package/server/llm/cli-runner.js +316 -265
- package/server/llm/judge.js +149 -123
- package/server/llm/project-advice.js +15 -7
- package/server/llm/session-analyzer.js +141 -131
package/server/execution/job.js
CHANGED
|
@@ -32,6 +32,7 @@ const { loadAdvice } = require('../llm/advice');
|
|
|
32
32
|
const { loadProjectAdvice } = require('../llm/project-advice');
|
|
33
33
|
const { saveDb } = require('../db/connection');
|
|
34
34
|
const { runExecutor, canSpawn } = require('./runner');
|
|
35
|
+
const { detectAllCli } = require('../llm/cli-runner');
|
|
35
36
|
const {
|
|
36
37
|
buildExecutionPrompt,
|
|
37
38
|
buildProjectExecutionPrompt,
|
|
@@ -318,6 +319,56 @@ function cleanupOrphans(db) {
|
|
|
318
319
|
// blocked.
|
|
319
320
|
// ---------------------------------------------------------------------------
|
|
320
321
|
|
|
322
|
+
const VALID_EXECUTORS = new Set(['opencode', 'claude']);
|
|
323
|
+
const VALID_CLI_PREFS = new Set(['auto', 'opencode', 'claude']);
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Read the user's CLI preference from user_settings (no caching — the
|
|
327
|
+
* execution path is low-volume; freshness matters more than micro-perf).
|
|
328
|
+
*/
|
|
329
|
+
function getCliPreference(db) {
|
|
330
|
+
const raw = String(getSetting(db, 'llm_tool_preference') || 'auto').toLowerCase();
|
|
331
|
+
return VALID_CLI_PREFS.has(raw) ? raw : 'auto';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Decide which executor to spawn. Priority:
|
|
336
|
+
* 1. Explicit request override (`executorOpt`) — always wins, even
|
|
337
|
+
* over user preference: an API caller that says "run with claude"
|
|
338
|
+
* gets claude. Validated against the whitelist.
|
|
339
|
+
* 2. User preference (`llm_tool_preference`) — when set to a concrete
|
|
340
|
+
* CLI (opencode / claude), honoured if it's installed; if not
|
|
341
|
+
* installed we fall back to whatever IS, so a stale preference
|
|
342
|
+
* can't silently disable execution.
|
|
343
|
+
* 3. The LLM's recommendation in `item.executor` — used iff the user
|
|
344
|
+
* preference is 'auto' (the default).
|
|
345
|
+
* 4. Hard default `'opencode'`.
|
|
346
|
+
*
|
|
347
|
+
* Returns `null` when no whitelisted CLI is installed at all.
|
|
348
|
+
*/
|
|
349
|
+
async function resolveExecutor(db, executorOpt, item) {
|
|
350
|
+
// 1. Explicit override — accept as-is if whitelisted.
|
|
351
|
+
if (executorOpt && VALID_EXECUTORS.has(executorOpt)) return executorOpt;
|
|
352
|
+
|
|
353
|
+
const pref = getCliPreference(db);
|
|
354
|
+
const itemExec = (item && VALID_EXECUTORS.has(item.executor)) ? item.executor : null;
|
|
355
|
+
|
|
356
|
+
// 2. Concrete user preference: try to honour it, fall back if missing.
|
|
357
|
+
if (pref !== 'auto') {
|
|
358
|
+
const all = await detectAllCli();
|
|
359
|
+
const wanted = all.find((c) => c.name === pref);
|
|
360
|
+
if (wanted && wanted.available) return pref;
|
|
361
|
+
// preferred CLI vanished — fall through to itemExec / first available
|
|
362
|
+
const itemAvail = itemExec && all.find((c) => c.name === itemExec && c.available);
|
|
363
|
+
if (itemAvail) return itemExec;
|
|
364
|
+
const anyAvail = all.find((c) => c.available && VALID_EXECUTORS.has(c.name));
|
|
365
|
+
return anyAvail ? anyAvail.name : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 3 + 4. Auto: trust the LLM's pick, else default to opencode.
|
|
369
|
+
return itemExec || 'opencode';
|
|
370
|
+
}
|
|
371
|
+
|
|
321
372
|
function describeCliCommand(executor, cwd) {
|
|
322
373
|
// What spawnRunAsync → runExecutor will actually invoke. Mirrors
|
|
323
374
|
// server/execution/runner.js (`opencode run` / `claude -p`). The
|
|
@@ -340,7 +391,7 @@ function describeCliCommand(executor, cwd) {
|
|
|
340
391
|
};
|
|
341
392
|
}
|
|
342
393
|
|
|
343
|
-
function previewExecution(db, { sessionId, adviceKey, executor: executorOpt }) {
|
|
394
|
+
async function previewExecution(db, { sessionId, adviceKey, executor: executorOpt }) {
|
|
344
395
|
const session = getSessionById(db, sessionId);
|
|
345
396
|
if (!session) return { ok: false, reason: 'no-session' };
|
|
346
397
|
|
|
@@ -350,7 +401,7 @@ function previewExecution(db, { sessionId, adviceKey, executor: executorOpt }) {
|
|
|
350
401
|
const item = findAdviceItem(advice, adviceKey);
|
|
351
402
|
if (!item) return { ok: false, reason: 'no-advice-item' };
|
|
352
403
|
|
|
353
|
-
const executor = executorOpt
|
|
404
|
+
const executor = (await resolveExecutor(db, executorOpt, item)) || 'opencode';
|
|
354
405
|
// Normalise — session.project may carry an OpenCode-source path like
|
|
355
406
|
// "C//felix/code/X" that won't pass fs.existsSync; canonicalProject
|
|
356
407
|
// re-inserts the missing ":". Mirrors previewProjectExecution.
|
|
@@ -382,7 +433,7 @@ function previewExecution(db, { sessionId, adviceKey, executor: executorOpt }) {
|
|
|
382
433
|
};
|
|
383
434
|
}
|
|
384
435
|
|
|
385
|
-
function previewProjectExecution(db, {
|
|
436
|
+
async function previewProjectExecution(db, {
|
|
386
437
|
project: projectRaw, scope, windowFrom = '', windowTo = '',
|
|
387
438
|
adviceKey, executor: executorOpt,
|
|
388
439
|
}) {
|
|
@@ -402,7 +453,7 @@ function previewProjectExecution(db, {
|
|
|
402
453
|
const item = findAdviceItem(cached.payload, adviceKey);
|
|
403
454
|
if (!item) return { ok: false, reason: 'no-advice-item' };
|
|
404
455
|
|
|
405
|
-
const executor = executorOpt
|
|
456
|
+
const executor = (await resolveExecutor(db, executorOpt, item)) || 'opencode';
|
|
406
457
|
|
|
407
458
|
const prompt = buildProjectExecutionPrompt({
|
|
408
459
|
advice: item,
|
|
@@ -491,10 +542,10 @@ async function startExecution(db, opts) {
|
|
|
491
542
|
return { ok: false, reason: 'not-whitelisted', extra: { project } };
|
|
492
543
|
}
|
|
493
544
|
|
|
494
|
-
// 7. Executor choice & availability.
|
|
495
|
-
const executor = executorOpt
|
|
496
|
-
if (executor
|
|
497
|
-
return { ok: false, reason: 'no-cli', extra: { executor } };
|
|
545
|
+
// 7. Executor choice & availability — honour user preference.
|
|
546
|
+
const executor = await resolveExecutor(db, executorOpt, item);
|
|
547
|
+
if (!executor || !VALID_EXECUTORS.has(executor)) {
|
|
548
|
+
return { ok: false, reason: 'no-cli', extra: { executor: executor || null } };
|
|
498
549
|
}
|
|
499
550
|
const cliOk = await canSpawn(executor);
|
|
500
551
|
if (!cliOk) return { ok: false, reason: 'no-cli', extra: { executor } };
|
|
@@ -614,10 +665,10 @@ async function startProjectExecution(db, opts) {
|
|
|
614
665
|
return { ok: false, reason: 'not-whitelisted', extra: { project } };
|
|
615
666
|
}
|
|
616
667
|
|
|
617
|
-
// 6. Executor.
|
|
618
|
-
const executor = executorOpt
|
|
619
|
-
if (executor
|
|
620
|
-
return { ok: false, reason: 'no-cli', extra: { executor } };
|
|
668
|
+
// 6. Executor — honour user preference.
|
|
669
|
+
const executor = await resolveExecutor(db, executorOpt, item);
|
|
670
|
+
if (!executor || !VALID_EXECUTORS.has(executor)) {
|
|
671
|
+
return { ok: false, reason: 'no-cli', extra: { executor: executor || null } };
|
|
621
672
|
}
|
|
622
673
|
const cliOk = await canSpawn(executor);
|
|
623
674
|
if (!cliOk) return { ok: false, reason: 'no-cli', extra: { executor } };
|
package/server/llm/advice.js
CHANGED
|
@@ -49,21 +49,28 @@ let _settingsCache = null;
|
|
|
49
49
|
let _settingsCacheAt = 0;
|
|
50
50
|
const SETTINGS_TTL_MS = 10_000;
|
|
51
51
|
|
|
52
|
+
const VALID_CLI_PREFS = new Set(['auto', 'opencode', 'claude']);
|
|
53
|
+
|
|
52
54
|
function getSettings(db) {
|
|
53
55
|
const now = Date.now();
|
|
54
56
|
if (_settingsCache && now - _settingsCacheAt < SETTINGS_TTL_MS) {
|
|
55
57
|
return _settingsCache;
|
|
56
58
|
}
|
|
57
59
|
const rows = db.exec(
|
|
58
|
-
"SELECT key, value FROM user_settings WHERE key
|
|
60
|
+
"SELECT key, value FROM user_settings WHERE key IN ('enable_llm_judge', 'llm_tool_preference')"
|
|
59
61
|
);
|
|
60
62
|
let enable = false;
|
|
63
|
+
let pref = 'auto';
|
|
61
64
|
if (rows[0]) {
|
|
62
|
-
for (const [, v] of rows[0].values) {
|
|
63
|
-
enable = String(v) === '1' || String(v).toLowerCase() === 'true';
|
|
65
|
+
for (const [k, v] of rows[0].values) {
|
|
66
|
+
if (k === 'enable_llm_judge') enable = String(v) === '1' || String(v).toLowerCase() === 'true';
|
|
67
|
+
if (k === 'llm_tool_preference') {
|
|
68
|
+
const p = String(v || '').toLowerCase();
|
|
69
|
+
pref = VALID_CLI_PREFS.has(p) ? p : 'auto';
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
|
-
_settingsCache = { enable_llm_judge: enable };
|
|
73
|
+
_settingsCache = { enable_llm_judge: enable, llm_tool_preference: pref };
|
|
67
74
|
_settingsCacheAt = now;
|
|
68
75
|
return _settingsCache;
|
|
69
76
|
}
|
|
@@ -257,8 +264,9 @@ async function generateAdvice(db, sessionId, opts = {}) {
|
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
260
|
-
// 3. CLI detection
|
|
261
|
-
const
|
|
267
|
+
// 3. CLI detection — honour user preference
|
|
268
|
+
const pref = settings.llm_tool_preference || 'auto';
|
|
269
|
+
const cli = await detectAvailableCli(pref);
|
|
262
270
|
if (!cli) { log('no CLI'); return { ok: false, reason: 'no-cli' }; }
|
|
263
271
|
|
|
264
272
|
// 4–5. truncate + build prompt
|
|
@@ -267,7 +275,7 @@ async function generateAdvice(db, sessionId, opts = {}) {
|
|
|
267
275
|
log('spawning', cli.name, 'prompt bytes=', prompt.length, 'truncated=', ctx.truncated);
|
|
268
276
|
|
|
269
277
|
// 6. run under concurrency slot — 90 s is plenty for prompts up to ~80 KB
|
|
270
|
-
const result = await withSlot(() => runJudge({ prompt, timeoutMs: 90_000 }));
|
|
278
|
+
const result = await withSlot(() => runJudge({ prompt, timeoutMs: 90_000, preferredCli: pref }));
|
|
271
279
|
log('result ok=', result.ok, 'reason=', result.reason);
|
|
272
280
|
|
|
273
281
|
if (!result.ok) {
|