copilot-liku-cli 0.0.4 → 0.0.8
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/QUICKSTART.md +24 -0
- package/README.md +85 -33
- package/package.json +23 -14
- package/scripts/postinstall.js +63 -0
- package/src/cli/commands/window.js +66 -0
- package/src/main/agents/base-agent.js +15 -7
- package/src/main/agents/builder.js +211 -0
- package/src/main/agents/index.js +7 -4
- package/src/main/agents/orchestrator.js +13 -0
- package/src/main/agents/producer.js +891 -0
- package/src/main/agents/researcher.js +78 -0
- package/src/main/agents/state-manager.js +134 -2
- package/src/main/agents/verifier.js +201 -0
- package/src/main/ai-service.js +349 -35
- package/src/main/index.js +680 -110
- package/src/main/inspect-service.js +24 -1
- package/src/main/python-bridge.js +395 -0
- package/src/main/system-automation.js +849 -131
- package/src/main/ui-automation/core/ui-provider.js +99 -0
- package/src/main/ui-automation/core/uia-host.js +214 -0
- package/src/main/ui-automation/index.js +30 -0
- package/src/main/ui-automation/interactions/element-click.js +6 -6
- package/src/main/ui-automation/interactions/high-level.js +28 -6
- package/src/main/ui-automation/interactions/index.js +21 -0
- package/src/main/ui-automation/interactions/pattern-actions.js +236 -0
- package/src/main/ui-automation/window/index.js +6 -0
- package/src/main/ui-automation/window/manager.js +173 -26
- package/src/main/ui-watcher.js +401 -58
- package/src/main/visual-awareness.js +18 -1
- package/src/native/windows-uia/Program.cs +89 -0
- package/src/native/windows-uia/build.ps1 +24 -0
- package/src/native/windows-uia-dotnet/Program.cs +920 -0
- package/src/native/windows-uia-dotnet/WindowsUIA.csproj +11 -0
- package/src/native/windows-uia-dotnet/build.ps1 +24 -0
- package/src/renderer/chat/chat.js +915 -671
- package/src/renderer/chat/index.html +2 -4
- package/src/renderer/chat/preload.js +8 -1
- package/src/renderer/overlay/overlay.js +157 -8
- package/src/renderer/overlay/preload.js +4 -0
- package/src/shared/inspect-types.js +82 -6
- package/ARCHITECTURE.md +0 -411
- package/CONFIGURATION.md +0 -302
- package/CONTRIBUTING.md +0 -225
- package/ELECTRON_README.md +0 -121
- package/PROJECT_STATUS.md +0 -229
- package/TESTING.md +0 -274
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { BaseAgent, AgentRole, AgentCapabilities } = require('./base-agent');
|
|
15
|
+
const { PythonBridge } = require('../python-bridge');
|
|
15
16
|
const fs = require('fs');
|
|
16
17
|
const path = require('path');
|
|
17
18
|
|
|
@@ -41,6 +42,9 @@ class ResearcherAgent extends BaseAgent {
|
|
|
41
42
|
this.researchCache = new Map();
|
|
42
43
|
this.cacheMaxAge = options.cacheMaxAge || 3600000; // 1 hour
|
|
43
44
|
this.sourceCredibility = new Map();
|
|
45
|
+
|
|
46
|
+
// PythonBridge for genre intelligence (lazy init via shared singleton)
|
|
47
|
+
this.pythonBridge = null;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
getSystemPrompt() {
|
|
@@ -506,6 +510,80 @@ Provide comprehensive findings with:
|
|
|
506
510
|
this.researchCache.clear();
|
|
507
511
|
this.sourceCredibility.clear();
|
|
508
512
|
}
|
|
513
|
+
|
|
514
|
+
// ===== Genre Intelligence Methods (Sprint 3 — Task 3.4) =====
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Lazily initialise and start the shared PythonBridge.
|
|
518
|
+
* @returns {Promise<PythonBridge>}
|
|
519
|
+
*/
|
|
520
|
+
async ensurePythonBridge() {
|
|
521
|
+
if (!this.pythonBridge) {
|
|
522
|
+
this.pythonBridge = PythonBridge.getShared();
|
|
523
|
+
}
|
|
524
|
+
if (!this.pythonBridge.isRunning) {
|
|
525
|
+
this.log('info', 'Starting PythonBridge for genre intelligence');
|
|
526
|
+
await this.pythonBridge.start();
|
|
527
|
+
}
|
|
528
|
+
return this.pythonBridge;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Look up the 10-dimensional DNA vector for a given genre.
|
|
533
|
+
*
|
|
534
|
+
* Results are cached in ``researchCache`` to avoid repeated RPCs.
|
|
535
|
+
*
|
|
536
|
+
* @param {string} genre Genre identifier (e.g. "trap_soul").
|
|
537
|
+
* @returns {Promise<object>} { genre, found, vector, dimensions }
|
|
538
|
+
*/
|
|
539
|
+
async queryGenreDNA(genre) {
|
|
540
|
+
// Check cache first
|
|
541
|
+
const cacheKey = `genre_dna::${genre}`;
|
|
542
|
+
const cached = this.researchCache.get(cacheKey);
|
|
543
|
+
if (cached && (Date.now() - cached.timestamp) < this.cacheMaxAge) {
|
|
544
|
+
this.log('info', 'Returning cached genre DNA', { genre });
|
|
545
|
+
return { ...cached.result, fromCache: true };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
await this.ensurePythonBridge();
|
|
549
|
+
this.log('info', 'Querying genre DNA', { genre });
|
|
550
|
+
|
|
551
|
+
const result = await this.pythonBridge.call('genre_dna_lookup', { genre });
|
|
552
|
+
|
|
553
|
+
// Cache the result
|
|
554
|
+
this.researchCache.set(cacheKey, {
|
|
555
|
+
result,
|
|
556
|
+
timestamp: Date.now(),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Blend multiple genre DNA vectors with weights.
|
|
564
|
+
*
|
|
565
|
+
* @param {Array<{genre: string, weight: number}>} genres
|
|
566
|
+
* @returns {Promise<object>} { vector, sources, description, suggested_tempo, dimensions }
|
|
567
|
+
*/
|
|
568
|
+
async blendGenres(genres) {
|
|
569
|
+
await this.ensurePythonBridge();
|
|
570
|
+
this.log('info', 'Blending genres', { count: genres.length });
|
|
571
|
+
|
|
572
|
+
const result = await this.pythonBridge.call('genre_blend', { genres });
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Stop and release the PythonBridge.
|
|
578
|
+
* @returns {Promise<void>}
|
|
579
|
+
*/
|
|
580
|
+
async disposePythonBridge() {
|
|
581
|
+
if (this.pythonBridge) {
|
|
582
|
+
this.log('info', 'Disposing PythonBridge');
|
|
583
|
+
await this.pythonBridge.stop();
|
|
584
|
+
this.pythonBridge = null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
509
587
|
}
|
|
510
588
|
|
|
511
589
|
module.exports = { ResearcherAgent };
|
|
@@ -9,6 +9,7 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const { nowIso, nowFilenameSafe } = require('../utils/time');
|
|
12
|
+
const { PythonBridge } = require('../python-bridge');
|
|
12
13
|
|
|
13
14
|
class AgentStateManager {
|
|
14
15
|
constructor(statePath = null) {
|
|
@@ -48,7 +49,10 @@ class AgentStateManager {
|
|
|
48
49
|
purpose: null,
|
|
49
50
|
parentSessionId: null
|
|
50
51
|
},
|
|
51
|
-
checkpoints: []
|
|
52
|
+
checkpoints: [],
|
|
53
|
+
sessionGraph: null,
|
|
54
|
+
generations: [],
|
|
55
|
+
lastSync: null
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -69,6 +73,13 @@ class AgentStateManager {
|
|
|
69
73
|
state.schemaVersion = 2;
|
|
70
74
|
state.version = '1.1.0';
|
|
71
75
|
}
|
|
76
|
+
if (!state.schemaVersion || state.schemaVersion < 3) {
|
|
77
|
+
state.sessionGraph = state.sessionGraph || null;
|
|
78
|
+
state.generations = state.generations || [];
|
|
79
|
+
state.lastSync = state.lastSync || null;
|
|
80
|
+
state.schemaVersion = 3;
|
|
81
|
+
state.version = '1.2.0';
|
|
82
|
+
}
|
|
72
83
|
return state;
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -335,10 +346,131 @@ class AgentStateManager {
|
|
|
335
346
|
purpose: null,
|
|
336
347
|
parentSessionId: null
|
|
337
348
|
},
|
|
338
|
-
checkpoints: []
|
|
349
|
+
checkpoints: [],
|
|
350
|
+
sessionGraph: null,
|
|
351
|
+
generations: [],
|
|
352
|
+
lastSync: null
|
|
339
353
|
};
|
|
340
354
|
this._saveState();
|
|
341
355
|
}
|
|
356
|
+
|
|
357
|
+
// ===== SessionGraph Integration =====
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Fetch the current SessionGraph from the Python backend.
|
|
361
|
+
* Caches locally in state for offline access.
|
|
362
|
+
* @returns {Promise<object|null>} The SessionGraph dict or null
|
|
363
|
+
*/
|
|
364
|
+
async fetchSessionGraph() {
|
|
365
|
+
try {
|
|
366
|
+
const bridge = PythonBridge.getShared();
|
|
367
|
+
const graph = await bridge.call('session_state', {});
|
|
368
|
+
this.state.sessionGraph = graph;
|
|
369
|
+
this.state.lastSync = nowIso();
|
|
370
|
+
this._saveState();
|
|
371
|
+
return graph;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.warn(`[StateManager] Failed to fetch SessionGraph: ${error.message}`);
|
|
374
|
+
return this.state.sessionGraph || null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get the cached SessionGraph (no network call).
|
|
380
|
+
* @returns {object|null}
|
|
381
|
+
*/
|
|
382
|
+
getCachedSessionGraph() {
|
|
383
|
+
return this.state.sessionGraph || null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get summary of the session graph (track count, section count, etc.)
|
|
388
|
+
* @returns {object} Summary stats
|
|
389
|
+
*/
|
|
390
|
+
getSessionSummary() {
|
|
391
|
+
const graph = this.state.sessionGraph;
|
|
392
|
+
if (!graph) {
|
|
393
|
+
return { available: false };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const tracks = graph.tracks || [];
|
|
397
|
+
const sections = graph.sections || [];
|
|
398
|
+
const totalBars = sections.reduce((sum, s) => sum + (s.bars || s.length_bars || 0), 0);
|
|
399
|
+
const hasMidi = tracks.some(t => (t.clips || []).some(c => c.midi_path || c.midi));
|
|
400
|
+
const hasAudio = tracks.some(t => (t.clips || []).some(c => c.audio_path || c.audio));
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
available: true,
|
|
404
|
+
session_id: graph.session_id || null,
|
|
405
|
+
bpm: graph.bpm || null,
|
|
406
|
+
key: graph.key || null,
|
|
407
|
+
genre: graph.genre || null,
|
|
408
|
+
trackCount: tracks.length,
|
|
409
|
+
sectionCount: sections.length,
|
|
410
|
+
totalBars,
|
|
411
|
+
hasMidi,
|
|
412
|
+
hasAudio
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Record a generation event in state with the resulting SessionGraph.
|
|
418
|
+
* @param {string} prompt - The original user prompt
|
|
419
|
+
* @param {object} result - The GenerationResult from generate_sync
|
|
420
|
+
* @param {object} sessionGraph - The SessionGraph from session_state
|
|
421
|
+
*/
|
|
422
|
+
recordGeneration(prompt, result, sessionGraph) {
|
|
423
|
+
if (!this.state.generations) {
|
|
424
|
+
this.state.generations = [];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.state.generations.push({
|
|
428
|
+
timestamp: nowIso(),
|
|
429
|
+
prompt,
|
|
430
|
+
result: {
|
|
431
|
+
success: result?.success ?? null,
|
|
432
|
+
session_id: result?.session_id ?? null,
|
|
433
|
+
tracks: result?.tracks ?? [],
|
|
434
|
+
error: result?.error ?? null
|
|
435
|
+
},
|
|
436
|
+
sessionGraph: sessionGraph || null
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Keep only last 10 generations
|
|
440
|
+
if (this.state.generations.length > 10) {
|
|
441
|
+
this.state.generations = this.state.generations.slice(-10);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this._saveState();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get history of past generations.
|
|
449
|
+
* @param {number} [limit=5]
|
|
450
|
+
* @returns {Array}
|
|
451
|
+
*/
|
|
452
|
+
getGenerationHistory(limit = 5) {
|
|
453
|
+
return (this.state.generations || []).slice(-limit);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Sync session state between Python and Electron.
|
|
458
|
+
* Fetches graph, records in state, returns summary.
|
|
459
|
+
* @returns {Promise<object>} { synced: true/false, summary, timestamp }
|
|
460
|
+
*/
|
|
461
|
+
async syncSessionState() {
|
|
462
|
+
const timestamp = nowIso();
|
|
463
|
+
try {
|
|
464
|
+
const graph = await this.fetchSessionGraph();
|
|
465
|
+
if (!graph) {
|
|
466
|
+
return { synced: false, summary: null, timestamp, error: 'No graph returned' };
|
|
467
|
+
}
|
|
468
|
+
const summary = this.getSessionSummary();
|
|
469
|
+
return { synced: true, summary, timestamp };
|
|
470
|
+
} catch (error) {
|
|
471
|
+
return { synced: false, summary: null, timestamp, error: error.message };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
342
474
|
}
|
|
343
475
|
|
|
344
476
|
module.exports = { AgentStateManager };
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const { BaseAgent, AgentRole, AgentCapabilities } = require('./base-agent');
|
|
14
|
+
const { PythonBridge } = require('../python-bridge');
|
|
14
15
|
|
|
15
16
|
class VerifierAgent extends BaseAgent {
|
|
16
17
|
constructor(options = {}) {
|
|
@@ -33,6 +34,9 @@ class VerifierAgent extends BaseAgent {
|
|
|
33
34
|
this.verificationResults = [];
|
|
34
35
|
this.currentPhase = null;
|
|
35
36
|
this.verdict = null;
|
|
37
|
+
|
|
38
|
+
// PythonBridge for music quality critics (lazy init via shared singleton)
|
|
39
|
+
this.pythonBridge = null;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
getSystemPrompt() {
|
|
@@ -447,6 +451,203 @@ Always structure your response as:
|
|
|
447
451
|
this.currentPhase = null;
|
|
448
452
|
this.verdict = null;
|
|
449
453
|
}
|
|
454
|
+
|
|
455
|
+
// ===== Music Quality Verification (Sprint 3 — Task 3.3) =====
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Lazily initialise and start the shared PythonBridge.
|
|
459
|
+
* @returns {Promise<PythonBridge>}
|
|
460
|
+
*/
|
|
461
|
+
async ensurePythonBridge() {
|
|
462
|
+
if (!this.pythonBridge) {
|
|
463
|
+
this.pythonBridge = PythonBridge.getShared();
|
|
464
|
+
}
|
|
465
|
+
if (!this.pythonBridge.isRunning) {
|
|
466
|
+
const alive = await this.pythonBridge.isAlive();
|
|
467
|
+
if (!alive) {
|
|
468
|
+
this.log('info', 'Starting PythonBridge for music critics');
|
|
469
|
+
await this.pythonBridge.start();
|
|
470
|
+
} else {
|
|
471
|
+
this.log('info', 'PythonBridge connected to existing server');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return this.pythonBridge;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Run VLC / BKAS / ADC quality-gate critics on a MIDI file.
|
|
479
|
+
*
|
|
480
|
+
* @param {string} midiPath Path to the MIDI file.
|
|
481
|
+
* @param {string} [genre] Genre identifier for context-aware eval.
|
|
482
|
+
* @param {object} [analysisData] Pre-extracted analysis data (voicings, bass_notes, etc.)
|
|
483
|
+
* @returns {Promise<{passed: boolean, metrics: Array, report: object}>}
|
|
484
|
+
*/
|
|
485
|
+
async runMusicCritics(midiPath, genre, analysisData = {}) {
|
|
486
|
+
await this.ensurePythonBridge();
|
|
487
|
+
this.log('info', 'Running music critics', { midiPath, genre });
|
|
488
|
+
|
|
489
|
+
const hasAnalysisData = analysisData && Object.keys(analysisData).length > 0;
|
|
490
|
+
const method = hasAnalysisData ? 'run_critics' : 'run_critics_midi';
|
|
491
|
+
const report = await this.pythonBridge.call(method, {
|
|
492
|
+
midi_path: midiPath,
|
|
493
|
+
genre,
|
|
494
|
+
...analysisData,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Record proof entries for each metric
|
|
498
|
+
if (report && Array.isArray(report.metrics)) {
|
|
499
|
+
for (const metric of report.metrics) {
|
|
500
|
+
this.addStructuredProof({
|
|
501
|
+
type: 'music-critic',
|
|
502
|
+
criticName: metric.name,
|
|
503
|
+
value: metric.value,
|
|
504
|
+
threshold: metric.threshold,
|
|
505
|
+
passed: metric.passed,
|
|
506
|
+
midiPath,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
this.addProof(
|
|
512
|
+
'music-critics-overall',
|
|
513
|
+
report.overall_passed ? 'PASS' : 'FAIL',
|
|
514
|
+
midiPath
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
passed: report.overall_passed,
|
|
519
|
+
metrics: report.metrics,
|
|
520
|
+
report,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Preflight gate for score plans before generation.
|
|
526
|
+
*
|
|
527
|
+
* Combines deterministic checks with a premium-model verifier pass.
|
|
528
|
+
* Returns pass/fail plus issues and recommendations.
|
|
529
|
+
*/
|
|
530
|
+
async preflightScorePlanGate(scorePlan, context = {}) {
|
|
531
|
+
const issues = [];
|
|
532
|
+
const recommendations = [];
|
|
533
|
+
|
|
534
|
+
const required = ['schema_version', 'prompt', 'bpm', 'key', 'mode', 'sections', 'tracks'];
|
|
535
|
+
for (const key of required) {
|
|
536
|
+
if (scorePlan?.[key] === undefined || scorePlan?.[key] === null) {
|
|
537
|
+
issues.push(`Missing required field: ${key}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (scorePlan?.schema_version !== 'score_plan_v1') {
|
|
542
|
+
issues.push('schema_version must be score_plan_v1');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (typeof scorePlan?.bpm !== 'number' || Number.isNaN(scorePlan.bpm) || scorePlan.bpm < 30 || scorePlan.bpm > 220) {
|
|
546
|
+
issues.push('bpm out of valid range [30,220]');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!Array.isArray(scorePlan?.sections) || scorePlan.sections.length < 1) {
|
|
550
|
+
issues.push('sections must be a non-empty array');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!Array.isArray(scorePlan?.tracks) || scorePlan.tracks.length < 1) {
|
|
554
|
+
issues.push('tracks must be a non-empty array');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (issues.length > 0) {
|
|
558
|
+
recommendations.push('Fix schema/shape issues before generation');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
let modelReview = null;
|
|
562
|
+
const verifierModel = context.model || 'claude-sonnet-4.5';
|
|
563
|
+
try {
|
|
564
|
+
const reviewPrompt = `You are a strict music plan verifier. Evaluate this score plan for generation risk and musical coherence.
|
|
565
|
+
|
|
566
|
+
Return JSON only with fields:
|
|
567
|
+
{
|
|
568
|
+
"passed": boolean,
|
|
569
|
+
"issues": string[],
|
|
570
|
+
"recommendations": string[]
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
Prompt context:
|
|
574
|
+
${context.prompt || ''}
|
|
575
|
+
|
|
576
|
+
Score plan:
|
|
577
|
+
${JSON.stringify(scorePlan, null, 2)}`;
|
|
578
|
+
|
|
579
|
+
const review = await this.chat(reviewPrompt, { model: verifierModel });
|
|
580
|
+
const text = (review?.text || '').trim();
|
|
581
|
+
const jsonText = text.startsWith('{') ? text : (text.match(/\{[\s\S]*\}/)?.[0] || '{}');
|
|
582
|
+
modelReview = JSON.parse(jsonText);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
modelReview = {
|
|
585
|
+
passed: true,
|
|
586
|
+
issues: [],
|
|
587
|
+
recommendations: [`Model verifier unavailable: ${error.message}`]
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (Array.isArray(modelReview?.issues)) {
|
|
592
|
+
issues.push(...modelReview.issues);
|
|
593
|
+
}
|
|
594
|
+
if (Array.isArray(modelReview?.recommendations)) {
|
|
595
|
+
recommendations.push(...modelReview.recommendations);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const passed = issues.length === 0 && modelReview?.passed !== false;
|
|
599
|
+
|
|
600
|
+
this.addStructuredProof({
|
|
601
|
+
type: 'score-plan-preflight',
|
|
602
|
+
passed,
|
|
603
|
+
verifierModel,
|
|
604
|
+
issuesCount: issues.length,
|
|
605
|
+
recommendationsCount: recommendations.length
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
passed,
|
|
610
|
+
verifierModel,
|
|
611
|
+
issues,
|
|
612
|
+
recommendations,
|
|
613
|
+
modelReview
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Analyze rendered audio against target genre expectations.
|
|
619
|
+
*/
|
|
620
|
+
async analyzeRenderedOutput(audioPath, genre = 'pop') {
|
|
621
|
+
await this.ensurePythonBridge();
|
|
622
|
+
this.log('info', 'Analyzing rendered output', { audioPath, genre });
|
|
623
|
+
|
|
624
|
+
const report = await this.pythonBridge.call('analyze_output', {
|
|
625
|
+
audio_path: audioPath,
|
|
626
|
+
genre,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
this.addStructuredProof({
|
|
630
|
+
type: 'output-analysis',
|
|
631
|
+
audioPath,
|
|
632
|
+
genre,
|
|
633
|
+
passed: !!report?.passed,
|
|
634
|
+
genreMatchScore: report?.genre_match_score,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
return report;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Stop and release the PythonBridge.
|
|
642
|
+
* @returns {Promise<void>}
|
|
643
|
+
*/
|
|
644
|
+
async disposePythonBridge() {
|
|
645
|
+
if (this.pythonBridge) {
|
|
646
|
+
this.log('info', 'Disposing PythonBridge');
|
|
647
|
+
await this.pythonBridge.stop();
|
|
648
|
+
this.pythonBridge = null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
450
651
|
}
|
|
451
652
|
|
|
452
653
|
module.exports = { VerifierAgent };
|