@covibes/zeroshot 2.1.0 → 3.0.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/CHANGELOG.md +75 -0
- package/README.md +19 -8
- package/cli/index.js +146 -111
- package/cli/lib/first-run.js +11 -11
- package/cli/lib/update-checker.js +2 -1
- package/cluster-templates/base-templates/debug-workflow.json +75 -6
- package/docker/zeroshot-cluster/Dockerfile +8 -1
- package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
- package/lib/settings.js +46 -4
- package/package.json +1 -1
- package/src/agent/agent-config.js +38 -3
- package/src/agent/agent-task-executor.js +229 -85
- package/src/agent-wrapper.js +49 -13
- package/src/config-validator.js +198 -0
- package/src/copy-worker.js +43 -0
- package/src/isolation-manager.js +328 -108
- package/src/orchestrator.js +91 -10
- package/src/preflight.js +28 -2
- package/src/process-metrics.js +16 -4
- package/src/status-footer.js +151 -42
package/src/config-validator.js
CHANGED
|
@@ -75,6 +75,11 @@ function validateConfig(config, depth = 0) {
|
|
|
75
75
|
errors.push(...logicResult.errors);
|
|
76
76
|
warnings.push(...logicResult.warnings);
|
|
77
77
|
|
|
78
|
+
// === PHASE 5: Template variable validation ===
|
|
79
|
+
const templateResult = validateTemplateVariables(config, depth);
|
|
80
|
+
errors.push(...templateResult.errors);
|
|
81
|
+
warnings.push(...templateResult.warnings);
|
|
82
|
+
|
|
78
83
|
return {
|
|
79
84
|
valid: errors.length === 0,
|
|
80
85
|
errors,
|
|
@@ -559,6 +564,194 @@ function validateLogicScripts(config) {
|
|
|
559
564
|
return { errors, warnings };
|
|
560
565
|
}
|
|
561
566
|
|
|
567
|
+
/**
|
|
568
|
+
* Phase 5: Validate template variables against jsonSchema
|
|
569
|
+
* Ensures {{result.*}} references in hooks match defined schema properties
|
|
570
|
+
*/
|
|
571
|
+
function validateTemplateVariables(config, depth = 0) {
|
|
572
|
+
const errors = [];
|
|
573
|
+
const warnings = [];
|
|
574
|
+
|
|
575
|
+
if (!config.agents || !Array.isArray(config.agents)) {
|
|
576
|
+
return { errors, warnings };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const prefix = depth > 0 ? `Sub-cluster (depth ${depth}): ` : '';
|
|
580
|
+
|
|
581
|
+
for (const agent of config.agents) {
|
|
582
|
+
// Skip subclusters - they have their own validation
|
|
583
|
+
if (agent.type === 'subcluster') {
|
|
584
|
+
// Recursively validate subcluster config
|
|
585
|
+
if (agent.config?.agents) {
|
|
586
|
+
const subResult = validateTemplateVariables(agent.config, depth + 1);
|
|
587
|
+
// Prefix sub-cluster errors with agent ID
|
|
588
|
+
errors.push(...subResult.errors.map((e) => `Sub-cluster '${agent.id}': ${e}`));
|
|
589
|
+
warnings.push(...subResult.warnings.map((w) => `Sub-cluster '${agent.id}': ${w}`));
|
|
590
|
+
}
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const result = validateAgentTemplateVariables(agent, agent.id);
|
|
595
|
+
errors.push(...result.errors.map((e) => `${prefix}${e}`));
|
|
596
|
+
warnings.push(...result.warnings.map((w) => `${prefix}${w}`));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return { errors, warnings };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Validate template variables for a single agent
|
|
604
|
+
* @param {Object} agent - Agent configuration
|
|
605
|
+
* @param {String} agentId - Agent ID for error messages
|
|
606
|
+
* @returns {{ errors: string[], warnings: string[] }}
|
|
607
|
+
*/
|
|
608
|
+
function validateAgentTemplateVariables(agent, agentId) {
|
|
609
|
+
const errors = [];
|
|
610
|
+
const warnings = [];
|
|
611
|
+
|
|
612
|
+
// Extract schema properties (null if non-JSON output or text output)
|
|
613
|
+
const schemaProps = extractSchemaProperties(agent);
|
|
614
|
+
|
|
615
|
+
// If schemaProps is null, this agent doesn't use JSON output - skip validation
|
|
616
|
+
if (schemaProps === null) {
|
|
617
|
+
return { errors, warnings };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Extract template variables from hooks
|
|
621
|
+
const templateVars = extractTemplateVariables(agent);
|
|
622
|
+
|
|
623
|
+
// Check for undefined references (ERROR)
|
|
624
|
+
for (const varName of templateVars) {
|
|
625
|
+
if (!schemaProps.has(varName)) {
|
|
626
|
+
const availableProps = Array.from(schemaProps).join(', ');
|
|
627
|
+
errors.push(
|
|
628
|
+
`Agent '${agentId}': Template uses '{{result.${varName}}}' but '${varName}' is not defined in jsonSchema. ` +
|
|
629
|
+
`Available properties: [${availableProps}]`
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Check for unused schema properties (WARNING)
|
|
635
|
+
for (const prop of schemaProps) {
|
|
636
|
+
if (!templateVars.has(prop)) {
|
|
637
|
+
warnings.push(
|
|
638
|
+
`Agent '${agentId}': Schema property '${prop}' is defined but never referenced in hooks. ` +
|
|
639
|
+
`Consider removing it to save tokens.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return { errors, warnings };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Extract all template variables ({{result.*}}) from agent hooks
|
|
649
|
+
* Searches hooks.onComplete.config (recursive) and hooks.onComplete.transform.script
|
|
650
|
+
* Also searches triggers[].onComplete patterns
|
|
651
|
+
* @param {Object} agent - Agent configuration
|
|
652
|
+
* @returns {Set<string>} Set of variable names referenced
|
|
653
|
+
*/
|
|
654
|
+
function extractTemplateVariables(agent) {
|
|
655
|
+
const variables = new Set();
|
|
656
|
+
|
|
657
|
+
// Regex patterns - reset lastIndex before each use to avoid state pollution
|
|
658
|
+
const mustachePattern = /\{\{result\.([^}]+)\}\}/g;
|
|
659
|
+
const directPattern = /\bresult\.([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Recursively traverse an object/array and extract template variables from strings
|
|
663
|
+
*/
|
|
664
|
+
function traverseAndExtract(obj) {
|
|
665
|
+
if (obj === null || obj === undefined) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (typeof obj === 'string') {
|
|
670
|
+
// Extract mustache-style {{result.field}}
|
|
671
|
+
mustachePattern.lastIndex = 0;
|
|
672
|
+
let match;
|
|
673
|
+
while ((match = mustachePattern.exec(obj)) !== null) {
|
|
674
|
+
variables.add(match[1]);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (Array.isArray(obj)) {
|
|
680
|
+
for (const item of obj) {
|
|
681
|
+
traverseAndExtract(item);
|
|
682
|
+
}
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (typeof obj === 'object') {
|
|
687
|
+
for (const value of Object.values(obj)) {
|
|
688
|
+
traverseAndExtract(value);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Extract variables from transform script (direct result.field access)
|
|
695
|
+
*/
|
|
696
|
+
function extractFromScript(script) {
|
|
697
|
+
if (typeof script !== 'string') {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
directPattern.lastIndex = 0;
|
|
702
|
+
let match;
|
|
703
|
+
while ((match = directPattern.exec(script)) !== null) {
|
|
704
|
+
variables.add(match[1]);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Extract from hooks.onComplete.config
|
|
709
|
+
if (agent.hooks?.onComplete?.config) {
|
|
710
|
+
traverseAndExtract(agent.hooks.onComplete.config);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Extract from hooks.onComplete.transform.script
|
|
714
|
+
if (agent.hooks?.onComplete?.transform?.script) {
|
|
715
|
+
extractFromScript(agent.hooks.onComplete.transform.script);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Extract from triggers[].onComplete (some agents define hooks per-trigger)
|
|
719
|
+
if (agent.triggers && Array.isArray(agent.triggers)) {
|
|
720
|
+
for (const trigger of agent.triggers) {
|
|
721
|
+
if (trigger.onComplete?.config) {
|
|
722
|
+
traverseAndExtract(trigger.onComplete.config);
|
|
723
|
+
}
|
|
724
|
+
if (trigger.onComplete?.transform?.script) {
|
|
725
|
+
extractFromScript(trigger.onComplete.transform.script);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return variables;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Extract schema properties from agent's jsonSchema
|
|
735
|
+
* @param {Object} agent - Agent configuration
|
|
736
|
+
* @returns {Set<string>|null} Set of property names, or null if agent doesn't use JSON output
|
|
737
|
+
*/
|
|
738
|
+
function extractSchemaProperties(agent) {
|
|
739
|
+
// Non-JSON agents don't need validation
|
|
740
|
+
// Both 'json' and 'stream-json' use jsonSchema and need validation
|
|
741
|
+
if (!['json', 'stream-json'].includes(agent.outputFormat)) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// If explicit schema is provided, use its properties
|
|
746
|
+
if (agent.jsonSchema?.properties) {
|
|
747
|
+
return new Set(Object.keys(agent.jsonSchema.properties));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Default schema when outputFormat is 'json' but no explicit schema
|
|
751
|
+
// See: agent-config.js:62-69
|
|
752
|
+
return new Set(['summary', 'result']);
|
|
753
|
+
}
|
|
754
|
+
|
|
562
755
|
/**
|
|
563
756
|
* Check if iteration pattern is valid
|
|
564
757
|
*/
|
|
@@ -608,4 +801,9 @@ module.exports = {
|
|
|
608
801
|
validateLogicScripts,
|
|
609
802
|
isValidIterationPattern,
|
|
610
803
|
formatValidationResult,
|
|
804
|
+
// Phase 5: Template variable validation
|
|
805
|
+
validateTemplateVariables,
|
|
806
|
+
extractTemplateVariables,
|
|
807
|
+
extractSchemaProperties,
|
|
808
|
+
validateAgentTemplateVariables,
|
|
611
809
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy Worker - Worker thread for parallel file copying
|
|
3
|
+
*
|
|
4
|
+
* Handles copying a batch of files from source to destination.
|
|
5
|
+
* Used by IsolationManager._copyDirExcluding() for parallel copying.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { parentPort, workerData } = require('worker_threads');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const { files, sourceBase, destBase } = workerData;
|
|
13
|
+
|
|
14
|
+
let copied = 0;
|
|
15
|
+
let skipped = 0;
|
|
16
|
+
const errors = [];
|
|
17
|
+
|
|
18
|
+
for (const relativePath of files) {
|
|
19
|
+
const srcPath = path.join(sourceBase, relativePath);
|
|
20
|
+
const destPath = path.join(destBase, relativePath);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Ensure parent directory exists
|
|
24
|
+
const destDir = path.dirname(destPath);
|
|
25
|
+
if (!fs.existsSync(destDir)) {
|
|
26
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Copy the file
|
|
30
|
+
fs.copyFileSync(srcPath, destPath);
|
|
31
|
+
copied++;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
// Skip files we can't copy (permission denied, broken symlinks, etc.)
|
|
34
|
+
if (err.code === 'EACCES' || err.code === 'EPERM' || err.code === 'ENOENT') {
|
|
35
|
+
skipped++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
errors.push({ file: relativePath, error: err.message });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Report results back to main thread
|
|
43
|
+
parentPort.postMessage({ copied, skipped, errors });
|