@llblab/pi-actors 0.44.0 → 0.45.1
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/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/README.md +5 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +10 -106
- package/dist/lib/async-runs.d.ts +2 -0
- package/dist/lib/async-runs.js +33 -4
- package/dist/lib/config.js +1 -0
- package/dist/lib/extension-runtime.d.ts +21 -0
- package/dist/lib/extension-runtime.js +116 -0
- package/dist/lib/inspector.js +8 -2
- package/dist/lib/recipes-discovery.js +20 -3
- package/dist/lib/recipes-references.d.ts +10 -0
- package/dist/lib/recipes-references.js +168 -5
- package/dist/lib/schema.d.ts +3 -0
- package/dist/lib/schema.js +34 -1
- package/dist/lib/tools-inspect.js +10 -0
- package/dist/lib/tools-local.js +27 -10
- package/dist/lib/tools-spawn.js +2 -2
- package/dist/scripts/async-runner.mjs +0 -16
- package/dist/scripts/executable-block-style.d.mts +1 -0
- package/dist/scripts/executable-block-style.mjs +73 -0
- package/dist/scripts/locker.mjs +0 -21
- package/dist/scripts/recipe-utils.mjs +0 -1
- package/dist/scripts/release-gates.mjs +14 -11
- package/dist/scripts/validate-recipe.mjs +0 -1
- package/dist/skills/actors/SKILL.md +2 -2
- package/docs/command-templates.md +2 -2
- package/docs/recipe-library.md +2 -0
- package/docs/template-recipes.md +10 -4
- package/docs/tool-registry.md +1 -1
- package/index.ts +18 -107
- package/lib/async-runs.ts +43 -9
- package/lib/config.ts +4 -0
- package/lib/extension-runtime.ts +139 -0
- package/lib/inspector.ts +16 -2
- package/lib/recipes-discovery.ts +23 -3
- package/lib/recipes-references.ts +227 -7
- package/lib/schema.ts +53 -1
- package/lib/tools-inspect.ts +11 -0
- package/lib/tools-local.ts +49 -19
- package/lib/tools-spawn.ts +2 -2
- package/package.json +2 -2
- package/scripts/async-runner.mjs +0 -16
- package/scripts/executable-block-style.d.mts +1 -0
- package/scripts/executable-block-style.mjs +73 -0
- package/scripts/locker.mjs +0 -21
- package/scripts/recipe-utils.mjs +0 -1
- package/scripts/release-gates.mjs +14 -11
- package/scripts/validate-recipe.mjs +0 -1
- package/skills/actors/SKILL.md +2 -2
|
@@ -5,15 +5,106 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
-
import { basename, dirname, extname, resolve } from "node:path";
|
|
8
|
+
import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
9
9
|
import * as CommandTemplates from "./command-templates.js";
|
|
10
10
|
import * as Paths from "./paths.js";
|
|
11
11
|
import * as RecipeControl from "./recipe-control.js";
|
|
12
|
+
import * as Schema from "./schema.js";
|
|
12
13
|
const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
|
|
13
14
|
const MAX_RECIPE_IMPORT_DEPTH = 32;
|
|
15
|
+
const RUNTIME_RECIPE_VALUE_NAMES = new Set(["recipe_dir", "skill_dir"]);
|
|
16
|
+
const activeSkillRecipeRoots = new Map();
|
|
17
|
+
export function setActiveSkillRecipeSources(skills) {
|
|
18
|
+
activeSkillRecipeRoots.clear();
|
|
19
|
+
for (const skill of skills) {
|
|
20
|
+
const name = skill.name.trim();
|
|
21
|
+
const skillDir = skill.baseDir
|
|
22
|
+
? resolve(skill.baseDir)
|
|
23
|
+
: skill.filePath
|
|
24
|
+
? dirname(resolve(skill.filePath))
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!name || !skillDir)
|
|
27
|
+
continue;
|
|
28
|
+
const roots = activeSkillRecipeRoots.get(name) ?? new Set();
|
|
29
|
+
roots.add(resolve(skillDir, "recipes"));
|
|
30
|
+
activeSkillRecipeRoots.set(name, roots);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getActiveSkillRecipeNamespaces() {
|
|
34
|
+
return Object.fromEntries([...activeSkillRecipeRoots.entries()]
|
|
35
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
36
|
+
.map(([name, roots]) => [name, [...roots].sort()]));
|
|
37
|
+
}
|
|
38
|
+
function qualifiedRecipeNameForFile(file) {
|
|
39
|
+
const path = resolve(file);
|
|
40
|
+
const stdRelation = relative(Paths.getPackagedRecipeRoot(), path);
|
|
41
|
+
if (stdRelation &&
|
|
42
|
+
!stdRelation.startsWith("..") &&
|
|
43
|
+
!isAbsolute(stdRelation)) {
|
|
44
|
+
return `std:${stdRelation.replace(/\.(?:json|md)$/u, "")}`;
|
|
45
|
+
}
|
|
46
|
+
for (const [skillName, roots] of activeSkillRecipeRoots) {
|
|
47
|
+
if (roots.size !== 1)
|
|
48
|
+
continue;
|
|
49
|
+
const root = [...roots][0];
|
|
50
|
+
const relation = relative(root, path);
|
|
51
|
+
if (relation && !relation.startsWith("..") && !isAbsolute(relation))
|
|
52
|
+
return `skill:${skillName}/${relation.replaceAll("\\", "/").replace(/\.(?:json|md)$/u, "")}`;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function qualifiedRecipeCandidates(value) {
|
|
57
|
+
if (value.startsWith("std:")) {
|
|
58
|
+
return recipeNameFiles(value.slice(4)).map((file) => resolve(Paths.getPackagedRecipeRoot(), file));
|
|
59
|
+
}
|
|
60
|
+
if (!value.startsWith("skill:"))
|
|
61
|
+
return undefined;
|
|
62
|
+
const qualified = value.slice(6);
|
|
63
|
+
const separator = qualified.indexOf("/");
|
|
64
|
+
if (separator <= 0 || separator === qualified.length - 1)
|
|
65
|
+
throw new Error(`Invalid Skill Recipe reference: ${value}`);
|
|
66
|
+
const skillName = qualified.slice(0, separator);
|
|
67
|
+
const recipeName = qualified.slice(separator + 1);
|
|
68
|
+
const roots = [...(activeSkillRecipeRoots.get(skillName) ?? [])];
|
|
69
|
+
if (roots.length === 0)
|
|
70
|
+
throw new Error(`Active Skill Recipe namespace not found: ${skillName}`);
|
|
71
|
+
if (roots.length > 1)
|
|
72
|
+
throw new Error(`Ambiguous active Skill Recipe namespace ${skillName}: ${roots.join(", ")}`);
|
|
73
|
+
const root = roots[0];
|
|
74
|
+
return recipeNameFiles(recipeName).map((file) => {
|
|
75
|
+
const candidate = resolve(root, file);
|
|
76
|
+
const relation = relative(root, candidate);
|
|
77
|
+
if (!relation || relation.startsWith("..") || isAbsolute(relation))
|
|
78
|
+
throw new Error(`Skill Recipe reference escapes its recipes root: ${value}`);
|
|
79
|
+
return candidate;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
14
82
|
function hasWhitespace(value) {
|
|
15
83
|
return /\s/.test(value);
|
|
16
84
|
}
|
|
85
|
+
function findOwningSkillDir(recipeFile) {
|
|
86
|
+
let current = dirname(recipeFile);
|
|
87
|
+
while (true) {
|
|
88
|
+
if (existsSync(resolve(current, "SKILL.md")))
|
|
89
|
+
return current;
|
|
90
|
+
const parent = dirname(current);
|
|
91
|
+
if (parent === current)
|
|
92
|
+
return undefined;
|
|
93
|
+
current = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function assertRuntimeRecipeValuesNotDeclared(raw) {
|
|
97
|
+
const args = Array.isArray(raw.args)
|
|
98
|
+
? Schema.getExplicitToolArgNames(raw.args.map(String))
|
|
99
|
+
: [];
|
|
100
|
+
for (const name of RUNTIME_RECIPE_VALUE_NAMES) {
|
|
101
|
+
if (args.includes(name) ||
|
|
102
|
+
(isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
|
|
103
|
+
(isRecord(raw.values) && Object.hasOwn(raw.values, name))) {
|
|
104
|
+
throw new Error(`Recipe runtime value is reserved: ${name}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
17
108
|
export function resolveRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
|
|
18
109
|
const trimmed = value.trim();
|
|
19
110
|
const repoRoot = resolve(recipeRoot, "..");
|
|
@@ -42,6 +133,9 @@ function recipeNameFiles(value) {
|
|
|
42
133
|
return [`${trimmed}.json`, `${trimmed}.md`];
|
|
43
134
|
}
|
|
44
135
|
function recipeCandidatePaths(value, currentRecipeRoot) {
|
|
136
|
+
const qualified = qualifiedRecipeCandidates(value.trim());
|
|
137
|
+
if (qualified)
|
|
138
|
+
return qualified;
|
|
45
139
|
if (!isBareRecipeName(value))
|
|
46
140
|
return [resolveRecipePath(value, currentRecipeRoot)];
|
|
47
141
|
const roots = [
|
|
@@ -495,11 +589,46 @@ function mergeDefaults(...items) {
|
|
|
495
589
|
const merged = Object.assign({}, ...items.filter(Boolean));
|
|
496
590
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
497
591
|
}
|
|
592
|
+
function assertRecipeDefaultsDeclared(args, defaults, label) {
|
|
593
|
+
const spec = Schema.parseToolArgDeclarationList(args ?? []);
|
|
594
|
+
if (spec.error)
|
|
595
|
+
throw new Error(spec.error);
|
|
596
|
+
if (spec.args.length === 0)
|
|
597
|
+
return;
|
|
598
|
+
const declared = new Set(spec.args);
|
|
599
|
+
for (const key of Object.keys(defaults ?? {})) {
|
|
600
|
+
if (!declared.has(key))
|
|
601
|
+
throw new Error(`Unknown ${label} argument: ${key}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function assertRecipeArgumentContract(config, label = "Recipe default") {
|
|
605
|
+
assertRecipeDefaultsDeclared(config.args, config.defaults, label);
|
|
606
|
+
const spec = Schema.parseToolArgDeclarationList(config.args ?? []);
|
|
607
|
+
if (spec.error)
|
|
608
|
+
throw new Error(spec.error);
|
|
609
|
+
Schema.assertCompatibleToolArgTypes(spec.argTypes, Schema.getTemplateArgTypes({ template: config.template }));
|
|
610
|
+
const staticValues = Object.fromEntries(Object.entries({ ...config.defaults, ...config.values }).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value)));
|
|
611
|
+
Schema.normalizeRuntimeValues(staticValues, spec.argTypes);
|
|
612
|
+
}
|
|
613
|
+
function resolveRecipeValueLayers(args, ...layers) {
|
|
614
|
+
const spec = Schema.parseToolArgDeclarationList(args ?? []);
|
|
615
|
+
if (spec.error)
|
|
616
|
+
throw new Error(spec.error);
|
|
617
|
+
const merged = mergeDefaults(spec.defaults, ...layers);
|
|
618
|
+
if (!merged)
|
|
619
|
+
return undefined;
|
|
620
|
+
const staticValues = Object.fromEntries(Object.entries(merged).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value)));
|
|
621
|
+
return {
|
|
622
|
+
...merged,
|
|
623
|
+
...Schema.normalizeRuntimeValues(staticValues, spec.argTypes),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
498
626
|
function applyDefaultsToTemplate(template, defaults, overrides) {
|
|
499
627
|
const cleanOverrides = { ...overrides };
|
|
500
628
|
delete cleanOverrides.name;
|
|
501
629
|
delete cleanOverrides.template;
|
|
502
630
|
delete cleanOverrides.values;
|
|
631
|
+
delete cleanOverrides.defaults;
|
|
503
632
|
if (typeof template === "object" && !Array.isArray(template)) {
|
|
504
633
|
return {
|
|
505
634
|
...template,
|
|
@@ -537,7 +666,15 @@ function loadDelegatedRecipe(value, currentRecipeFile, stack, options) {
|
|
|
537
666
|
return config;
|
|
538
667
|
}
|
|
539
668
|
function applyDelegatedRecipeToNode(delegated, overrides = {}) {
|
|
540
|
-
|
|
669
|
+
const nodeDefaults = isRecord(overrides.defaults)
|
|
670
|
+
? overrides.defaults
|
|
671
|
+
: undefined;
|
|
672
|
+
const nodeValues = isRecord(overrides.values) ? overrides.values : undefined;
|
|
673
|
+
const values = resolveRecipeValueLayers(delegated.args, delegated.defaults, delegated.values, nodeDefaults, nodeValues, {
|
|
674
|
+
...(delegated.recipe_dir ? { recipe_dir: delegated.recipe_dir } : {}),
|
|
675
|
+
...(delegated.skill_dir ? { skill_dir: delegated.skill_dir } : {}),
|
|
676
|
+
});
|
|
677
|
+
return applyDefaultsToTemplate(delegated.template, values, overrides);
|
|
541
678
|
}
|
|
542
679
|
function expandRecipeDelegations(value, currentRecipeFile, stack, options = {}) {
|
|
543
680
|
if (typeof value === "string") {
|
|
@@ -593,7 +730,7 @@ function expandImportNodes(value, imports, options = {}) {
|
|
|
593
730
|
? record.defaults
|
|
594
731
|
: undefined;
|
|
595
732
|
const nodeValues = isRecord(record.values) ? record.values : undefined;
|
|
596
|
-
const defaults =
|
|
733
|
+
const defaults = resolveRecipeValueLayers(imported.config.args, imported.defaults, imported.values, nodeDefaults, nodeValues);
|
|
597
734
|
const expanded = applyDefaultsToTemplate(imported.config.template, defaults, record);
|
|
598
735
|
return options.includeActorRecipeContext
|
|
599
736
|
? withActorRecipeContext(expanded, {
|
|
@@ -631,6 +768,8 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
631
768
|
if (!raw || !Object.hasOwn(raw, "template"))
|
|
632
769
|
return undefined;
|
|
633
770
|
RecipeControl.assertRecipeHasNoMailbox(raw);
|
|
771
|
+
assertRuntimeRecipeValuesNotDeclared(raw);
|
|
772
|
+
assertRecipeArgumentContract(raw);
|
|
634
773
|
const imports = {};
|
|
635
774
|
for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
|
|
636
775
|
const importPath = resolveRecipeImportPath(getImportFrom(binding), dirname(path));
|
|
@@ -639,13 +778,18 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
639
778
|
throw new Error(`Recipe import not found: ${alias}`);
|
|
640
779
|
const bindingDefaults = typeof binding === "string" ? undefined : binding.defaults;
|
|
641
780
|
const bindingValues = typeof binding === "string" ? undefined : binding.values;
|
|
781
|
+
assertRecipeDefaultsDeclared(config.args, bindingDefaults, `Recipe import ${alias} default`);
|
|
642
782
|
imports[alias] = {
|
|
643
783
|
alias,
|
|
644
784
|
file: importPath,
|
|
645
785
|
name: config.name ?? alias,
|
|
646
786
|
config,
|
|
647
787
|
defaults: { ...(config.defaults ?? {}), ...(bindingDefaults ?? {}) },
|
|
648
|
-
values: {
|
|
788
|
+
values: {
|
|
789
|
+
...(bindingValues ?? {}),
|
|
790
|
+
...(config.recipe_dir ? { recipe_dir: config.recipe_dir } : {}),
|
|
791
|
+
...(config.skill_dir ? { skill_dir: config.skill_dir } : {}),
|
|
792
|
+
},
|
|
649
793
|
};
|
|
650
794
|
}
|
|
651
795
|
const substituted = substituteImportRefs(raw, imports);
|
|
@@ -661,6 +805,17 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
661
805
|
: {})
|
|
662
806
|
: expandRecipeDelegations(expandedImportsTemplate, path, stack, options);
|
|
663
807
|
const recipeName = getRecipeIdFromPath(path);
|
|
808
|
+
const recipeDir = dirname(path);
|
|
809
|
+
const skillDir = findOwningSkillDir(path);
|
|
810
|
+
if (!skillDir &&
|
|
811
|
+
JSON.stringify({
|
|
812
|
+
artifacts: substituted.artifacts,
|
|
813
|
+
defaults: substituted.defaults,
|
|
814
|
+
template: substituted.template,
|
|
815
|
+
values: substituted.values,
|
|
816
|
+
}).includes("{skill_dir}")) {
|
|
817
|
+
throw new Error(`Recipe uses {skill_dir} outside an owning Skill directory: ${path}`);
|
|
818
|
+
}
|
|
664
819
|
const templateWithContext = options.includeActorRecipeContext
|
|
665
820
|
? withActorRecipeContext(expandedTemplate, {
|
|
666
821
|
file: path,
|
|
@@ -678,6 +833,8 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
678
833
|
: delegated?.control);
|
|
679
834
|
return {
|
|
680
835
|
name: recipeName,
|
|
836
|
+
recipe_dir: recipeDir,
|
|
837
|
+
...(skillDir ? { skill_dir: skillDir } : {}),
|
|
681
838
|
...(typeof substituted.description === "string" &&
|
|
682
839
|
substituted.description.trim()
|
|
683
840
|
? { description: substituted.description.trim() }
|
|
@@ -765,7 +922,11 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
765
922
|
...(typeof substituted.repeat === "number"
|
|
766
923
|
? { repeat: substituted.repeat }
|
|
767
924
|
: {}),
|
|
768
|
-
|
|
925
|
+
values: {
|
|
926
|
+
...(isRecord(substituted.values) ? substituted.values : {}),
|
|
927
|
+
recipe_dir: recipeDir,
|
|
928
|
+
...(skillDir ? { skill_dir: skillDir } : {}),
|
|
929
|
+
},
|
|
769
930
|
...(isRecord(substituted.usage) ? { usage: substituted.usage } : {}),
|
|
770
931
|
};
|
|
771
932
|
}
|
|
@@ -780,12 +941,14 @@ function collectRecipeContextRecords(file, stack, importPath, alias) {
|
|
|
780
941
|
const raw = readRawRecipeConfig(path);
|
|
781
942
|
if (!raw || !Object.hasOwn(raw, "template"))
|
|
782
943
|
return [];
|
|
944
|
+
const qualifiedName = qualifiedRecipeNameForFile(path);
|
|
783
945
|
const record = {
|
|
784
946
|
...(alias ? { alias } : {}),
|
|
785
947
|
depth: stack.length,
|
|
786
948
|
file: path,
|
|
787
949
|
import_path: importPath,
|
|
788
950
|
name: getRecipeIdFromPath(path),
|
|
951
|
+
...(qualifiedName ? { qualified_name: qualifiedName } : {}),
|
|
789
952
|
recipe: raw,
|
|
790
953
|
role: stack.length === 0 ? "entry" : "import",
|
|
791
954
|
};
|
package/dist/lib/schema.d.ts
CHANGED
|
@@ -44,14 +44,17 @@ export declare function objectSchema(properties: Record<string, JsonSchema>, req
|
|
|
44
44
|
export declare function looseObjectSchema(description: string): JsonSchema;
|
|
45
45
|
export declare function parseToolArgToken(value: string): ParsedToolArgToken;
|
|
46
46
|
export declare function parseToolArgDeclarations(value: string): ToolArgSpec;
|
|
47
|
+
export declare function parseToolArgDeclarationList(value: unknown[]): ToolArgSpec;
|
|
47
48
|
export declare function normalizeStoredToolArgDeclarations(argsValue: unknown, defaultsValue: unknown): ToolArgSpec & {
|
|
48
49
|
changed: boolean;
|
|
49
50
|
provided: boolean;
|
|
50
51
|
};
|
|
51
52
|
export declare function formatToolArgs(args: string[]): string;
|
|
52
53
|
export declare function getTemplatePlaceholderNames(config: CommandTemplates.CommandTemplateConfig): string[];
|
|
54
|
+
export declare function assertCompatibleToolArgTypes(...sources: Array<Record<string, ToolArgType> | undefined>): void;
|
|
53
55
|
export declare function getTemplateArgTypes(config: CommandTemplates.CommandTemplateConfig): Record<string, ToolArgType>;
|
|
54
56
|
export declare function getExplicitToolArgNames(args: string[] | undefined): string[];
|
|
57
|
+
export declare function getExplicitToolArgDefaults(args: string[] | string | undefined): Record<string, string>;
|
|
55
58
|
export declare function getToolArgNames(config: CommandTemplates.CommandTemplateConfig): string[];
|
|
56
59
|
export declare function getRequiredToolArgNames(config: CommandTemplates.CommandTemplateConfig): Set<string>;
|
|
57
60
|
export declare function normalizeRuntimeValues(values: Record<string, unknown>, argTypes: Record<string, ToolArgType> | undefined): Record<string, unknown>;
|
package/dist/lib/schema.js
CHANGED
|
@@ -147,7 +147,10 @@ function isValidDefault(type, value) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
export function parseToolArgDeclarations(value) {
|
|
150
|
-
|
|
150
|
+
return parseToolArgDeclarationList(splitArgDeclarations(value));
|
|
151
|
+
}
|
|
152
|
+
export function parseToolArgDeclarationList(value) {
|
|
153
|
+
const source = value.map(String);
|
|
151
154
|
const seen = new Set();
|
|
152
155
|
const duplicates = new Set();
|
|
153
156
|
const args = [];
|
|
@@ -272,6 +275,8 @@ function collectCommandTemplateConfigDeclarations(config, declarations) {
|
|
|
272
275
|
collectTemplatePlaceholderDeclarations(config, declarations);
|
|
273
276
|
return;
|
|
274
277
|
}
|
|
278
|
+
if (!config || typeof config !== "object")
|
|
279
|
+
return;
|
|
275
280
|
if (Array.isArray(config)) {
|
|
276
281
|
for (const step of config)
|
|
277
282
|
collectCommandTemplateConfigDeclarations(step, declarations);
|
|
@@ -296,9 +301,30 @@ function getTemplatePlaceholderDeclarations(config) {
|
|
|
296
301
|
export function getTemplatePlaceholderNames(config) {
|
|
297
302
|
return mergeUnique(getTemplatePlaceholderDeclarations(config).map((item) => item.arg));
|
|
298
303
|
}
|
|
304
|
+
function toolArgTypesEqual(left, right) {
|
|
305
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
306
|
+
}
|
|
307
|
+
export function assertCompatibleToolArgTypes(...sources) {
|
|
308
|
+
const seen = {};
|
|
309
|
+
for (const source of sources) {
|
|
310
|
+
for (const [name, type] of Object.entries(source ?? {})) {
|
|
311
|
+
const previous = seen[name];
|
|
312
|
+
if (previous && !toolArgTypesEqual(previous, type)) {
|
|
313
|
+
throw new Error(`Conflicting argument type for ${name}: ${canonicalArgDeclaration(name, previous)} vs ${canonicalArgDeclaration(name, type)}`);
|
|
314
|
+
}
|
|
315
|
+
seen[name] = type;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
299
319
|
export function getTemplateArgTypes(config) {
|
|
300
320
|
const argTypes = {};
|
|
301
321
|
for (const declaration of getTemplatePlaceholderDeclarations(config)) {
|
|
322
|
+
const previous = argTypes[declaration.arg];
|
|
323
|
+
if (previous &&
|
|
324
|
+
declaration.type.kind !== "string" &&
|
|
325
|
+
!toolArgTypesEqual(previous, declaration.type)) {
|
|
326
|
+
throw new Error(`Conflicting argument type for ${declaration.arg}: ${canonicalArgDeclaration(declaration.arg, previous)} vs ${declaration.declaration}`);
|
|
327
|
+
}
|
|
302
328
|
if (declaration.type.kind !== "string")
|
|
303
329
|
argTypes[declaration.arg] = declaration.type;
|
|
304
330
|
}
|
|
@@ -307,6 +333,13 @@ export function getTemplateArgTypes(config) {
|
|
|
307
333
|
export function getExplicitToolArgNames(args) {
|
|
308
334
|
return mergeUnique((args ?? []).map((item) => parseToolArgToken(String(item)).arg));
|
|
309
335
|
}
|
|
336
|
+
export function getExplicitToolArgDefaults(args) {
|
|
337
|
+
const declarations = typeof args === "string" ? splitArgDeclarations(args) : (args ?? []);
|
|
338
|
+
return Object.fromEntries(declarations
|
|
339
|
+
.map((item) => parseToolArgToken(String(item)))
|
|
340
|
+
.filter((item) => item.defaultValue !== undefined)
|
|
341
|
+
.map((item) => [item.arg, item.defaultValue]));
|
|
342
|
+
}
|
|
310
343
|
export function getToolArgNames(config) {
|
|
311
344
|
const normalizedConfig = CommandTemplates.normalizeCommandTemplateConfig(config);
|
|
312
345
|
const declaredArgs = Array.isArray(normalizedConfig.args)
|
|
@@ -12,6 +12,7 @@ import * as Limits from "./limits.js";
|
|
|
12
12
|
import { computeRunControlCapacity } from "./run-evidence-policy.js";
|
|
13
13
|
import * as Paths from "./paths.js";
|
|
14
14
|
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
15
|
+
import * as RecipesReferences from "./recipes-references.js";
|
|
15
16
|
import * as ReviewDiagnostics from "./review-diagnostics.js";
|
|
16
17
|
import * as RunsControls from "./runs-controls.js";
|
|
17
18
|
import * as RunControlDelivery from "./runs-control-delivery.js";
|
|
@@ -172,8 +173,17 @@ function inspectRecipes(view, deps) {
|
|
|
172
173
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
173
174
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
174
175
|
]);
|
|
176
|
+
const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces();
|
|
175
177
|
const summary = {
|
|
176
178
|
...RecipesDiscovery.summarizeDiscovery(discovered),
|
|
179
|
+
skill_recipe_namespaces: skillRecipeNamespaces,
|
|
180
|
+
skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
|
|
181
|
+
.filter(([, roots]) => roots.length > 1)
|
|
182
|
+
.map(([name, roots]) => ({
|
|
183
|
+
name,
|
|
184
|
+
roots,
|
|
185
|
+
error: `Ambiguous active Skill Recipe namespace ${name}`,
|
|
186
|
+
})),
|
|
177
187
|
drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
178
188
|
};
|
|
179
189
|
return {
|
package/dist/lib/tools-local.js
CHANGED
|
@@ -45,11 +45,16 @@ function shouldAddRuntimeToolUsageHint(error) {
|
|
|
45
45
|
}
|
|
46
46
|
function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
47
47
|
const optional = cfg.args.filter((arg) => !required.includes(arg));
|
|
48
|
+
const exampleDefaults = {
|
|
49
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
50
|
+
...cfg.defaults,
|
|
51
|
+
...(cfg.recipe?.values ?? {}),
|
|
52
|
+
};
|
|
48
53
|
const example = {};
|
|
49
54
|
for (const arg of required)
|
|
50
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
55
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
51
56
|
for (const arg of optional)
|
|
52
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
57
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
53
58
|
if (includeRunId)
|
|
54
59
|
example.run_id = `${cfg.name}-1`;
|
|
55
60
|
const lines = [
|
|
@@ -62,6 +67,21 @@ function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
|
62
67
|
lines.push(`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`);
|
|
63
68
|
return lines.join("\n");
|
|
64
69
|
}
|
|
70
|
+
function resolveRecipeToolValues(cfg, callerValues, ctx) {
|
|
71
|
+
const recipeRuntimeValues = {
|
|
72
|
+
...(cfg.recipe?.recipe_dir
|
|
73
|
+
? { recipe_dir: cfg.recipe.recipe_dir }
|
|
74
|
+
: {}),
|
|
75
|
+
...(cfg.recipe?.skill_dir ? { skill_dir: cfg.recipe.skill_dir } : {}),
|
|
76
|
+
};
|
|
77
|
+
return Schema.normalizeRuntimeValues(ModelContext.withCurrentModelValues({
|
|
78
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
79
|
+
...cfg.defaults,
|
|
80
|
+
...(cfg.recipe?.values ?? {}),
|
|
81
|
+
...callerValues,
|
|
82
|
+
...recipeRuntimeValues,
|
|
83
|
+
}, ctx), cfg.argTypes);
|
|
84
|
+
}
|
|
65
85
|
function formatRuntimeToolArgumentError(cfg, error, required, includeRunId) {
|
|
66
86
|
const message = error instanceof Error ? error.message : String(error);
|
|
67
87
|
if (!shouldAddRuntimeToolUsageHint(error))
|
|
@@ -87,8 +107,10 @@ export function createRuntimeToolDefinition(cfg, exec) {
|
|
|
87
107
|
defaults: cfg.defaults,
|
|
88
108
|
template: requiredTemplate,
|
|
89
109
|
};
|
|
110
|
+
const recipeInlineDefaults = Schema.getExplicitToolArgDefaults(cfg.recipe?.args);
|
|
90
111
|
const requiredArgs = isRecipe && cfg.storedArgs !== undefined
|
|
91
|
-
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)
|
|
112
|
+
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg) &&
|
|
113
|
+
!Object.hasOwn(recipeInlineDefaults, arg)))
|
|
92
114
|
: RecipesReferences.isRecipeReference(cfg.template) && !recipeTemplate
|
|
93
115
|
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
94
116
|
: Schema.getRequiredToolArgNames(requiredTemplateConfig);
|
|
@@ -137,7 +159,7 @@ export function createRuntimeToolDefinition(cfg, exec) {
|
|
|
137
159
|
run_id: runId,
|
|
138
160
|
tool: cfg.name,
|
|
139
161
|
policy_values: ModelContext.withCurrentModelValues({ ...(cfg.recipe?.values ?? {}), ...values }, ctx),
|
|
140
|
-
values:
|
|
162
|
+
values: resolveRecipeToolValues(cfg, values, ctx),
|
|
141
163
|
}, ctx.cwd);
|
|
142
164
|
return {
|
|
143
165
|
content: [
|
|
@@ -150,12 +172,7 @@ export function createRuntimeToolDefinition(cfg, exec) {
|
|
|
150
172
|
};
|
|
151
173
|
}
|
|
152
174
|
if (isRecipe && recipeTemplate) {
|
|
153
|
-
|
|
154
|
-
...(cfg.recipe?.values ?? {}),
|
|
155
|
-
...cfg.defaults,
|
|
156
|
-
...params,
|
|
157
|
-
}, ctx);
|
|
158
|
-
return await Execution.executeRegisteredTool({ ...cfg, template: recipeTemplate }, Schema.normalizeRuntimeValues(paramsWithDefaults, cfg.argTypes), exec, ctx.cwd, signal);
|
|
175
|
+
return await Execution.executeRegisteredTool({ ...cfg, template: recipeTemplate }, resolveRecipeToolValues(cfg, params, ctx), exec, ctx.cwd, signal);
|
|
159
176
|
}
|
|
160
177
|
return await Execution.executeRegisteredTool(cfg, Schema.normalizeRuntimeValues(ModelContext.withCurrentModelValues(params, ctx), cfg.argTypes), exec, ctx.cwd, signal);
|
|
161
178
|
}
|
package/dist/lib/tools-spawn.js
CHANGED
|
@@ -90,8 +90,8 @@ export function createSpawnToolDefinition() {
|
|
|
90
90
|
parameters: Schema.objectSchema({
|
|
91
91
|
artifacts: Schema.looseObjectSchema("Optional named artifact paths for the spawned Run."),
|
|
92
92
|
as: Schema.stringSchema("Optional Run identity in exact run:<id> form."),
|
|
93
|
-
file: Schema.stringSchema("Optional template
|
|
94
|
-
recipe: Schema.stringSchema("Alias for file;
|
|
93
|
+
file: Schema.stringSchema("Optional template Recipe reference. Supports paths, bare user/adjacent/std names, std:<name>, and skill:<skill>/<recipe>."),
|
|
94
|
+
recipe: Schema.stringSchema("Alias for file; qualified Skill/std or ordinary Recipe reference to spawn."),
|
|
95
95
|
template: Schema.unionSchema([
|
|
96
96
|
Schema.stringSchema("Inline command template string"),
|
|
97
97
|
Schema.arraySchema("Inline command-template sequence or parallel tree"),
|
|
@@ -74,7 +74,6 @@ function summarizeCommandDetail(commandDetail) {
|
|
|
74
74
|
|
|
75
75
|
export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
76
76
|
if (!stateDir) throw new Error("missing state dir");
|
|
77
|
-
|
|
78
77
|
const runPath = join(stateDir, "run.json");
|
|
79
78
|
const progressPath = join(stateDir, "progress.json");
|
|
80
79
|
const resultPath = join(stateDir, "result.json");
|
|
@@ -83,11 +82,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
83
82
|
const stderrPath = join(stateDir, "stderr.log");
|
|
84
83
|
const executionPath = join(stateDir, "execution.json");
|
|
85
84
|
const meta = JSON.parse(readFileSync(runPath, "utf8"));
|
|
86
|
-
|
|
87
85
|
function event(kind, data = {}) {
|
|
88
86
|
appendRunTraceEvent(stateDir, { kind, data });
|
|
89
87
|
}
|
|
90
|
-
|
|
91
88
|
function observation(kind, summary, data = {}, delivery = "log", level = "info") {
|
|
92
89
|
appendRunTraceEvent(stateDir, {
|
|
93
90
|
kind,
|
|
@@ -97,7 +94,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
97
94
|
...(level === "error" ? { level: "error" } : {}),
|
|
98
95
|
});
|
|
99
96
|
}
|
|
100
|
-
|
|
101
97
|
function progress(phase, extra = {}) {
|
|
102
98
|
writeJsonAtomic(progressPath, {
|
|
103
99
|
...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
|
|
@@ -106,7 +102,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
106
102
|
...extra,
|
|
107
103
|
});
|
|
108
104
|
}
|
|
109
|
-
|
|
110
105
|
let activeSubagents = 0;
|
|
111
106
|
let completedSubagents = 0;
|
|
112
107
|
let promptCounter = 0;
|
|
@@ -115,7 +110,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
115
110
|
const evidenceRecords = [];
|
|
116
111
|
const stageOccurrences = new Map();
|
|
117
112
|
let reportEvidence;
|
|
118
|
-
|
|
119
113
|
function writeExecutionManifest(status) {
|
|
120
114
|
writeJsonAtomic(executionPath, {
|
|
121
115
|
version: 1,
|
|
@@ -129,7 +123,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
129
123
|
updated_at: new Date().toISOString(),
|
|
130
124
|
});
|
|
131
125
|
}
|
|
132
|
-
|
|
133
126
|
function commandSessionFiles(sessionDir) {
|
|
134
127
|
if (!sessionDir) return [];
|
|
135
128
|
try {
|
|
@@ -141,7 +134,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
141
134
|
return [];
|
|
142
135
|
}
|
|
143
136
|
}
|
|
144
|
-
|
|
145
137
|
function commandEvidenceStartRecord({
|
|
146
138
|
commandDetail,
|
|
147
139
|
commandId,
|
|
@@ -181,7 +173,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
181
173
|
: "not_required",
|
|
182
174
|
};
|
|
183
175
|
}
|
|
184
|
-
|
|
185
176
|
function commandEvidenceRecord({
|
|
186
177
|
captureDir,
|
|
187
178
|
commandDetail,
|
|
@@ -273,7 +264,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
273
264
|
: "rejected",
|
|
274
265
|
};
|
|
275
266
|
}
|
|
276
|
-
|
|
277
267
|
function auditReviewReport(text) {
|
|
278
268
|
const required = evidenceRecords
|
|
279
269
|
.filter((record) =>
|
|
@@ -294,11 +284,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
294
284
|
complete_allowed: missing.length === 0,
|
|
295
285
|
};
|
|
296
286
|
}
|
|
297
|
-
|
|
298
287
|
function getCommandDoneDelivery(result) {
|
|
299
288
|
return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
|
|
300
289
|
}
|
|
301
|
-
|
|
302
290
|
function progressRunning() {
|
|
303
291
|
progress("running", {
|
|
304
292
|
activeSubagents,
|
|
@@ -306,14 +294,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
306
294
|
failures: subagentFailures,
|
|
307
295
|
});
|
|
308
296
|
}
|
|
309
|
-
|
|
310
297
|
function promptFilePath() {
|
|
311
298
|
promptCounter += 1;
|
|
312
299
|
const dir = join(stateDir, "prompts");
|
|
313
300
|
mkdirSync(dir, { recursive: true });
|
|
314
301
|
return join(dir, `command-${String(promptCounter).padStart(3, "0")}.md`);
|
|
315
302
|
}
|
|
316
|
-
|
|
317
303
|
function readPromptText(promptFile) {
|
|
318
304
|
if (!promptFile) return undefined;
|
|
319
305
|
try {
|
|
@@ -322,7 +308,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
322
308
|
return undefined;
|
|
323
309
|
}
|
|
324
310
|
}
|
|
325
|
-
|
|
326
311
|
async function observedExec(command, args, options) {
|
|
327
312
|
captureCounter += 1;
|
|
328
313
|
const commandId = `command-${String(captureCounter).padStart(3, "0")}`;
|
|
@@ -485,7 +470,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
485
470
|
progressRunning();
|
|
486
471
|
return result;
|
|
487
472
|
}
|
|
488
|
-
|
|
489
473
|
try {
|
|
490
474
|
event("run.runner.start", { pid: process.pid });
|
|
491
475
|
progressRunning();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function executableBlockBlankLines(source: string): number[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Detect blank lines inside executable JavaScript and TypeScript blocks. */
|
|
2
|
+
|
|
3
|
+
export function executableBlockBlankLines(source) {
|
|
4
|
+
const lines = source.split(/\r?\n/u);
|
|
5
|
+
const blanks = [];
|
|
6
|
+
const stack = [];
|
|
7
|
+
let blockComment = false;
|
|
8
|
+
let currentExecutableDepth = 0;
|
|
9
|
+
let previousCode = "";
|
|
10
|
+
let quote;
|
|
11
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
12
|
+
const line = lines[lineIndex];
|
|
13
|
+
if (
|
|
14
|
+
/^\s*$/u.test(line) &&
|
|
15
|
+
currentExecutableDepth > 0 &&
|
|
16
|
+
quote !== "`"
|
|
17
|
+
)
|
|
18
|
+
blanks.push(lineIndex + 1);
|
|
19
|
+
let code = "";
|
|
20
|
+
let regularExpression = false;
|
|
21
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
22
|
+
const char = line[index];
|
|
23
|
+
const next = line[index + 1];
|
|
24
|
+
if (blockComment) {
|
|
25
|
+
if (char === "*" && next === "/") {
|
|
26
|
+
blockComment = false;
|
|
27
|
+
index += 1;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (quote) {
|
|
32
|
+
if (char === "\\") index += 1;
|
|
33
|
+
else if (char === quote) quote = undefined;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (regularExpression) {
|
|
37
|
+
if (char === "\\") index += 1;
|
|
38
|
+
else if (char === "/") regularExpression = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === "/" && next === "*") {
|
|
42
|
+
blockComment = true;
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === "/" && next === "/") break;
|
|
47
|
+
if (
|
|
48
|
+
char === "/" &&
|
|
49
|
+
/^(?:|.*(?:[=(,:;!&|?{}]|\b(?:return|case|throw)))\s*$/u.test(code)
|
|
50
|
+
) {
|
|
51
|
+
regularExpression = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
55
|
+
quote = char;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (char === "{") {
|
|
59
|
+
const prefix = code.trimEnd() || previousCode;
|
|
60
|
+
const executable =
|
|
61
|
+
/\)\s*$|=>\s*$|\b(?:else|try|finally|do)\s*$/u.test(prefix);
|
|
62
|
+
stack.push(executable);
|
|
63
|
+
if (executable) currentExecutableDepth += 1;
|
|
64
|
+
} else if (char === "}") {
|
|
65
|
+
if (stack.pop()) currentExecutableDepth -= 1;
|
|
66
|
+
}
|
|
67
|
+
code += char;
|
|
68
|
+
}
|
|
69
|
+
if (quote !== "`") quote = undefined;
|
|
70
|
+
if (code.trim()) previousCode = code.trim();
|
|
71
|
+
}
|
|
72
|
+
return blanks;
|
|
73
|
+
}
|