@llblab/pi-actors 0.46.0 → 0.47.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/AGENTS.md +7 -5
- package/CHANGELOG.md +20 -0
- package/README.md +2 -1
- package/dist/lib/async-runs.d.ts +1 -0
- package/dist/lib/async-runs.js +4 -1
- package/dist/lib/execution.d.ts +1 -0
- package/dist/lib/execution.js +1 -0
- package/dist/lib/extension-runtime.js +25 -9
- package/dist/lib/inspector.js +1 -0
- package/dist/lib/prompts.d.ts +6 -5
- package/dist/lib/prompts.js +22 -23
- package/dist/lib/recipes-context.d.ts +12 -3
- package/dist/lib/recipes-context.js +28 -4
- package/dist/lib/recipes-discovery.d.ts +22 -0
- package/dist/lib/recipes-discovery.js +108 -23
- package/dist/lib/recipes-references.d.ts +18 -0
- package/dist/lib/recipes-references.js +129 -38
- package/dist/lib/registry.d.ts +23 -6
- package/dist/lib/registry.js +294 -101
- package/dist/lib/runtime.d.ts +30 -3
- package/dist/lib/runtime.js +108 -9
- package/dist/lib/tools-inspect.d.ts +3 -0
- package/dist/lib/tools-inspect.js +190 -26
- package/dist/lib/tools-local.d.ts +2 -2
- package/dist/lib/tools-local.js +5 -2
- package/dist/lib/tools-register.js +2 -1
- package/dist/lib/tools-response.js +7 -1
- package/dist/lib/tools-spawn.d.ts +2 -2
- package/dist/lib/tools-spawn.js +1 -1
- package/dist/lib/tools.d.ts +4 -1
- package/dist/lib/tools.js +2 -0
- package/dist/scripts/conformance.mjs +1 -0
- package/dist/skills/actors/SKILL.md +76 -65
- package/dist/skills/actors/references/diagnostics.md +44 -0
- package/dist/skills/actors/references/persistent-tools.md +74 -0
- package/dist/skills/actors/references/recipes.md +51 -0
- package/dist/skills/actors/references/runs.md +39 -0
- package/dist/skills/artifacts/SKILL.md +24 -7
- package/dist/skills/media/SKILL.md +35 -7
- package/dist/skills/project-work/SKILL.md +28 -7
- package/dist/skills/recipe-memory/SKILL.md +27 -7
- package/dist/skills/swarm/SKILL.md +41 -445
- package/dist/skills/swarm/references/development-swarm.md +87 -539
- package/dist/skills/swarm/references/review-swarms.md +115 -0
- package/docs/README.md +5 -5
- package/docs/recipe-library.md +15 -10
- package/docs/template-recipes.md +3 -1
- package/docs/tool-registry.md +18 -6
- package/lib/async-runs.ts +5 -1
- package/lib/execution.ts +2 -0
- package/lib/extension-runtime.ts +33 -14
- package/lib/inspector.ts +1 -0
- package/lib/prompts.ts +24 -24
- package/lib/recipes-context.ts +49 -4
- package/lib/recipes-discovery.ts +186 -25
- package/lib/recipes-references.ts +176 -44
- package/lib/registry.ts +441 -113
- package/lib/runtime.ts +147 -12
- package/lib/tools-inspect.ts +254 -28
- package/lib/tools-local.ts +9 -3
- package/lib/tools-register.ts +4 -3
- package/lib/tools-response.ts +7 -1
- package/lib/tools-spawn.ts +3 -2
- package/lib/tools.ts +4 -1
- package/package.json +1 -1
- package/scripts/conformance.mjs +1 -0
- package/skills/actors/SKILL.md +76 -65
- package/skills/actors/references/diagnostics.md +44 -0
- package/skills/actors/references/persistent-tools.md +74 -0
- package/skills/actors/references/recipes.md +51 -0
- package/skills/actors/references/runs.md +39 -0
- package/skills/artifacts/SKILL.md +24 -7
- package/skills/media/SKILL.md +35 -7
- package/skills/project-work/SKILL.md +28 -7
- package/skills/recipe-memory/SKILL.md +27 -7
- package/skills/swarm/SKILL.md +41 -445
- package/skills/swarm/references/development-swarm.md +87 -539
- package/skills/swarm/references/review-swarms.md +115 -0
|
@@ -94,10 +94,12 @@ function getRecipeRiskLabels(config) {
|
|
|
94
94
|
"risk.platform_specific",
|
|
95
95
|
].filter((label) => labels.has(label));
|
|
96
96
|
}
|
|
97
|
-
function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutableUsage = false) {
|
|
97
|
+
function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutableUsage = false, resolutionContext) {
|
|
98
98
|
const id = RecipesReferences.getRecipeIdFromPath(file);
|
|
99
99
|
try {
|
|
100
|
-
const config = RecipesReferences.readResolvedRecipeConfig(file
|
|
100
|
+
const config = RecipesReferences.readResolvedRecipeConfig(file, [], {
|
|
101
|
+
skillContext: resolutionContext?.activeSkills,
|
|
102
|
+
});
|
|
101
103
|
const invalid = !config;
|
|
102
104
|
const disabled = config?.disabled === true;
|
|
103
105
|
return {
|
|
@@ -112,6 +114,7 @@ function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutable
|
|
|
112
114
|
disabled,
|
|
113
115
|
tool: defaultTool && !disabled && !invalid,
|
|
114
116
|
mutableUsage,
|
|
117
|
+
resolutionContext,
|
|
115
118
|
diagnostics: getRecipeConfigDiagnostics(file, config),
|
|
116
119
|
riskLabels: getRecipeRiskLabels(config),
|
|
117
120
|
shadows: [],
|
|
@@ -129,8 +132,9 @@ function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutable
|
|
|
129
132
|
disabled: false,
|
|
130
133
|
tool: false,
|
|
131
134
|
mutableUsage,
|
|
135
|
+
resolutionContext,
|
|
132
136
|
diagnostics: [
|
|
133
|
-
`
|
|
137
|
+
`Recipe ${id} rejected: ${error instanceof Error ? error.message : String(error)}`,
|
|
134
138
|
],
|
|
135
139
|
riskLabels: [],
|
|
136
140
|
shadows: [],
|
|
@@ -147,6 +151,7 @@ function filesForSource(source) {
|
|
|
147
151
|
file: source.file,
|
|
148
152
|
defaultTool,
|
|
149
153
|
mutableUsage,
|
|
154
|
+
resolutionContext: source.resolutionContext,
|
|
150
155
|
},
|
|
151
156
|
];
|
|
152
157
|
return source.root
|
|
@@ -155,6 +160,7 @@ function filesForSource(source) {
|
|
|
155
160
|
file,
|
|
156
161
|
defaultTool,
|
|
157
162
|
mutableUsage,
|
|
163
|
+
resolutionContext: source.resolutionContext,
|
|
158
164
|
}))
|
|
159
165
|
: [];
|
|
160
166
|
}
|
|
@@ -222,7 +228,7 @@ function getRecipeRootDiagnostics(sources) {
|
|
|
222
228
|
return diagnostics;
|
|
223
229
|
}
|
|
224
230
|
export function discoverRecipeSources(sources) {
|
|
225
|
-
const entries = sources.flatMap((source, priority) => filesForSource(source).map(({ root, file, defaultTool, mutableUsage }) => readDiscoveredRecipe(root, file, priority, defaultTool, mutableUsage)));
|
|
231
|
+
const entries = sources.flatMap((source, priority) => filesForSource(source).map(({ root, file, defaultTool, mutableUsage, resolutionContext }) => readDiscoveredRecipe(root, file, priority, defaultTool, mutableUsage, resolutionContext)));
|
|
226
232
|
const byId = new Map();
|
|
227
233
|
for (const entry of entries) {
|
|
228
234
|
const bucket = byId.get(entry.id) ?? [];
|
|
@@ -647,12 +653,8 @@ export function summarizeDiscovery(result) {
|
|
|
647
653
|
integrity_manifest: createRecipeIntegrityManifest(result),
|
|
648
654
|
};
|
|
649
655
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
return undefined;
|
|
653
|
-
const cfg = entry.config;
|
|
654
|
-
const template = entry.path;
|
|
655
|
-
const description = cfg.description ?? `Execute template recipe: ${entry.id}`;
|
|
656
|
+
function projectRegisteredTool(identity, path, cfg, mutableUsage) {
|
|
657
|
+
const description = cfg.description ?? `Execute template recipe: ${identity}`;
|
|
656
658
|
const argTemplate = cfg.template;
|
|
657
659
|
const argTemplateConfig = typeof argTemplate === "object" && !Array.isArray(argTemplate)
|
|
658
660
|
? {
|
|
@@ -669,24 +671,27 @@ export function toRegisteredTool(entry) {
|
|
|
669
671
|
return [parsed.arg, parsed.type];
|
|
670
672
|
}));
|
|
671
673
|
assertToolSafeRepeatConfig(argTemplateConfig, explicitArgTypes, { ...(cfg.defaults ?? {}), ...(cfg.values ?? {}) });
|
|
672
|
-
const
|
|
673
|
-
Schema.assertCompatibleToolArgTypes(explicitArgTypes,
|
|
674
|
+
const inferredArgTypes = Schema.getTemplateArgTypes(argTemplateConfig);
|
|
675
|
+
Schema.assertCompatibleToolArgTypes(explicitArgTypes, inferredArgTypes);
|
|
676
|
+
const effectiveArgTypes = { ...inferredArgTypes, ...explicitArgTypes };
|
|
677
|
+
const args = Schema.getToolArgNames(argTemplateConfig).filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg));
|
|
678
|
+
const callerArgSet = new Set(args);
|
|
679
|
+
const argTypes = Object.fromEntries(Object.entries(effectiveArgTypes).filter(([arg]) => callerArgSet.has(arg)));
|
|
680
|
+
const defaults = Object.fromEntries(Object.entries(cfg.defaults ?? {}).filter(([arg]) => callerArgSet.has(arg)));
|
|
681
|
+
const storedArgs = cfg.args?.filter((arg) => callerArgSet.has(Schema.parseToolArgToken(String(arg)).arg));
|
|
674
682
|
return {
|
|
675
|
-
name:
|
|
683
|
+
name: identity,
|
|
676
684
|
description,
|
|
677
|
-
template,
|
|
685
|
+
template: path,
|
|
678
686
|
recipe: cfg,
|
|
679
|
-
args
|
|
680
|
-
defaults: Object.fromEntries(Object.entries(
|
|
681
|
-
key,
|
|
682
|
-
String(value),
|
|
683
|
-
])),
|
|
687
|
+
args,
|
|
688
|
+
defaults: Object.fromEntries(Object.entries(defaults).map(([key, value]) => [key, String(value)])),
|
|
684
689
|
...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
|
|
685
|
-
...(
|
|
686
|
-
...(
|
|
687
|
-
...(
|
|
690
|
+
...(mutableUsage ? { sourcePath: path } : {}),
|
|
691
|
+
...(storedArgs ? { storedArgs } : {}),
|
|
692
|
+
...(Object.keys(defaults).length > 0
|
|
688
693
|
? {
|
|
689
|
-
storedDefaults: Object.fromEntries(Object.entries(
|
|
694
|
+
storedDefaults: Object.fromEntries(Object.entries(defaults).map(([key, value]) => [
|
|
690
695
|
key,
|
|
691
696
|
String(value),
|
|
692
697
|
])),
|
|
@@ -694,3 +699,83 @@ export function toRegisteredTool(entry) {
|
|
|
694
699
|
: {}),
|
|
695
700
|
};
|
|
696
701
|
}
|
|
702
|
+
export function summarizeRegisteredToolArgs(tool) {
|
|
703
|
+
const publicArgs = tool.args.filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg));
|
|
704
|
+
const inlineDefaults = Schema.getExplicitToolArgDefaults(tool.recipe?.args);
|
|
705
|
+
const required = publicArgs.filter((arg) => !Object.hasOwn(tool.defaults, arg) &&
|
|
706
|
+
!Object.hasOwn(inlineDefaults, arg));
|
|
707
|
+
const requiredSet = new Set(required);
|
|
708
|
+
const optional = publicArgs.filter((arg) => !requiredSet.has(arg));
|
|
709
|
+
if (tool.recipe?.async === true) {
|
|
710
|
+
optional.push("run_id", "transport_context");
|
|
711
|
+
}
|
|
712
|
+
return { optional, required };
|
|
713
|
+
}
|
|
714
|
+
function boundedAdmissionDiagnostic(error) {
|
|
715
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
716
|
+
return message.length <= 4096 ? message : `${message.slice(0, 4095)}…`;
|
|
717
|
+
}
|
|
718
|
+
export function admitUserRecipe(sourcePath, resolutionContext, authoredRecipe, mutableUsage = true) {
|
|
719
|
+
const identity = RecipesReferences.getRecipeIdFromPath(sourcePath);
|
|
720
|
+
try {
|
|
721
|
+
const effectiveRecipe = RecipesReferences.readResolvedRecipeConfig(sourcePath, [], {
|
|
722
|
+
...(authoredRecipe
|
|
723
|
+
? { authoredEntry: { path: sourcePath, recipe: authoredRecipe } }
|
|
724
|
+
: {}),
|
|
725
|
+
skillContext: resolutionContext.activeSkills,
|
|
726
|
+
});
|
|
727
|
+
if (!effectiveRecipe) {
|
|
728
|
+
return {
|
|
729
|
+
diagnostics: [
|
|
730
|
+
authoredRecipe
|
|
731
|
+
? "Recipe must define a valid template."
|
|
732
|
+
: RecipesReferences.diagnoseRawRecipeConfigFailure(sourcePath) ??
|
|
733
|
+
"Recipe must define a valid template.",
|
|
734
|
+
],
|
|
735
|
+
identity,
|
|
736
|
+
sourcePath,
|
|
737
|
+
validated: false,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
if (effectiveRecipe.disabled === true) {
|
|
741
|
+
return {
|
|
742
|
+
diagnostics: ["Recipe is disabled."],
|
|
743
|
+
effectiveRecipe,
|
|
744
|
+
identity,
|
|
745
|
+
sourcePath,
|
|
746
|
+
validated: false,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const tool = projectRegisteredTool(identity, sourcePath, effectiveRecipe, mutableUsage);
|
|
750
|
+
return {
|
|
751
|
+
args: tool.args,
|
|
752
|
+
argTypes: tool.argTypes,
|
|
753
|
+
artifacts: effectiveRecipe.artifacts,
|
|
754
|
+
async: effectiveRecipe.async === true,
|
|
755
|
+
control: effectiveRecipe.control,
|
|
756
|
+
defaults: effectiveRecipe.defaults,
|
|
757
|
+
diagnostics: [],
|
|
758
|
+
effectiveRecipe,
|
|
759
|
+
identity,
|
|
760
|
+
sourcePath,
|
|
761
|
+
tool,
|
|
762
|
+
validated: true,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
return {
|
|
767
|
+
diagnostics: [boundedAdmissionDiagnostic(error)],
|
|
768
|
+
identity,
|
|
769
|
+
sourcePath,
|
|
770
|
+
validated: false,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
export function toRegisteredTool(entry) {
|
|
775
|
+
if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
|
|
776
|
+
return undefined;
|
|
777
|
+
if (entry.resolutionContext) {
|
|
778
|
+
return admitUserRecipe(entry.path, entry.resolutionContext, undefined, entry.mutableUsage).tool;
|
|
779
|
+
}
|
|
780
|
+
return projectRegisteredTool(entry.id, entry.path, entry.config, entry.mutableUsage);
|
|
781
|
+
}
|
|
@@ -56,9 +56,15 @@ export interface TemplateRecipeContextRecord {
|
|
|
56
56
|
source_kind: "active_skill_component" | "explicit_file_recipe" | "user_registry_capability";
|
|
57
57
|
}
|
|
58
58
|
export interface ReadResolvedRecipeConfigOptions {
|
|
59
|
+
authoredEntry?: {
|
|
60
|
+
path: string;
|
|
61
|
+
recipe: Record<string, unknown>;
|
|
62
|
+
};
|
|
59
63
|
includeActorRecipeContext?: boolean;
|
|
60
64
|
skillContext?: ActiveSkillRecipeContext;
|
|
61
65
|
}
|
|
66
|
+
export declare const RUNTIME_OWNED_RECIPE_INPUT_NAMES: readonly string[];
|
|
67
|
+
export declare function isRuntimeOwnedRecipeInput(name: string): boolean;
|
|
62
68
|
export interface ActiveSkillRecipeSource {
|
|
63
69
|
name: string;
|
|
64
70
|
filePath?: string;
|
|
@@ -77,6 +83,18 @@ export interface ActiveSkillRecipeComponent {
|
|
|
77
83
|
skill: string;
|
|
78
84
|
stem: string;
|
|
79
85
|
}
|
|
86
|
+
export interface SkillRecipeComponentDiagnostic {
|
|
87
|
+
file?: string;
|
|
88
|
+
reason: string;
|
|
89
|
+
skill: string;
|
|
90
|
+
stem?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface SkillRecipeComponentInventory {
|
|
93
|
+
components: ActiveSkillRecipeComponent[];
|
|
94
|
+
partial: boolean;
|
|
95
|
+
rejected: SkillRecipeComponentDiagnostic[];
|
|
96
|
+
}
|
|
97
|
+
export declare function inventoryActiveSkillRecipeComponents(context: ActiveSkillRecipeContext): SkillRecipeComponentInventory;
|
|
80
98
|
export declare function listActiveSkillRecipeComponents(context: ActiveSkillRecipeContext): ActiveSkillRecipeComponent[];
|
|
81
99
|
export declare function resolveRecipePath(value: string, baseDir?: string): string;
|
|
82
100
|
export declare function resolveRecipeReferencePath(value: unknown, baseDir?: string, context?: ActiveSkillRecipeContext): string | undefined;
|
|
@@ -11,7 +11,24 @@ import * as RecipeControl from "./recipe-control.js";
|
|
|
11
11
|
import * as Schema from "./schema.js";
|
|
12
12
|
const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
|
|
13
13
|
const MAX_RECIPE_IMPORT_DEPTH = 32;
|
|
14
|
-
const
|
|
14
|
+
const RESERVED_AUTHORED_RECIPE_VALUE_NAMES = new Set([
|
|
15
|
+
"recipe_dir",
|
|
16
|
+
"skill_dir",
|
|
17
|
+
]);
|
|
18
|
+
export const RUNTIME_OWNED_RECIPE_INPUT_NAMES = Object.freeze([
|
|
19
|
+
"owner_id",
|
|
20
|
+
"recipe_dir",
|
|
21
|
+
"run_instance_id",
|
|
22
|
+
"runtime_state_root",
|
|
23
|
+
"session_id",
|
|
24
|
+
"skill_dir",
|
|
25
|
+
"state_dir",
|
|
26
|
+
"trace_file",
|
|
27
|
+
]);
|
|
28
|
+
const RUNTIME_OWNED_RECIPE_INPUT_SET = new Set(RUNTIME_OWNED_RECIPE_INPUT_NAMES);
|
|
29
|
+
export function isRuntimeOwnedRecipeInput(name) {
|
|
30
|
+
return RUNTIME_OWNED_RECIPE_INPUT_SET.has(name);
|
|
31
|
+
}
|
|
15
32
|
export function createActiveSkillRecipeContext(skills) {
|
|
16
33
|
const rootsByName = new Map();
|
|
17
34
|
for (const skill of skills) {
|
|
@@ -49,19 +66,31 @@ function nestedRecipeFiles(root) {
|
|
|
49
66
|
return entry.isFile() && /\.(?:json|md)$/u.test(entry.name) ? [path] : [];
|
|
50
67
|
});
|
|
51
68
|
}
|
|
52
|
-
export function
|
|
69
|
+
export function inventoryActiveSkillRecipeComponents(context) {
|
|
53
70
|
const components = [];
|
|
71
|
+
const rejected = [];
|
|
54
72
|
for (const [skill, roots] of Object.entries(context.namespaces)) {
|
|
55
|
-
if (roots.length > 1)
|
|
56
|
-
|
|
73
|
+
if (roots.length > 1) {
|
|
74
|
+
rejected.push({
|
|
75
|
+
reason: `Duplicate active Skill identity ${skill} (${roots.length} active definitions); exact Recipe resolution is ambiguous. Next: inspect target=recipes view=doctor identity=${skill}/<recipe>`,
|
|
76
|
+
skill,
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
57
80
|
const root = roots[0];
|
|
58
81
|
if (!root || !existsSync(root))
|
|
59
82
|
continue;
|
|
60
83
|
const direct = readdirSync(root, { withFileTypes: true });
|
|
61
84
|
for (const entry of direct.filter((candidate) => candidate.isDirectory())) {
|
|
62
85
|
const nested = nestedRecipeFiles(join(root, entry.name));
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
for (const file of nested) {
|
|
87
|
+
rejected.push({
|
|
88
|
+
file,
|
|
89
|
+
reason: `Nested Skill Recipe file is not addressable: ${file}`,
|
|
90
|
+
skill,
|
|
91
|
+
stem: getRecipeIdFromPath(file),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
65
94
|
}
|
|
66
95
|
const files = direct
|
|
67
96
|
.filter((entry) => entry.isFile() && /\.(?:json|md)$/u.test(entry.name))
|
|
@@ -74,28 +103,58 @@ export function listActiveSkillRecipeComponents(context) {
|
|
|
74
103
|
byStem.set(stem, matches);
|
|
75
104
|
}
|
|
76
105
|
for (const [stem, matches] of [...byStem].sort(([a], [b]) => a.localeCompare(b))) {
|
|
77
|
-
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem)) {
|
|
107
|
+
rejected.push({
|
|
108
|
+
file: matches[0],
|
|
109
|
+
reason: `Invalid Skill Recipe filename stem: ${stem}`,
|
|
110
|
+
skill,
|
|
111
|
+
stem,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (matches.length > 1) {
|
|
116
|
+
rejected.push({
|
|
117
|
+
file: matches[0],
|
|
118
|
+
reason: `Skill Recipe stem collision: ${skill}/${stem} has both .json and .md files`,
|
|
119
|
+
skill,
|
|
120
|
+
stem,
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
81
124
|
const file = matches[0];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
alias,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
try {
|
|
126
|
+
const raw = readRawRecipeConfig(file);
|
|
127
|
+
if (!raw || !Object.hasOwn(raw, "template"))
|
|
128
|
+
throw new Error(`Invalid Skill Recipe component: ${skill}/${stem}`);
|
|
129
|
+
assertRecipeHasNoDeclaredName(raw);
|
|
130
|
+
components.push({
|
|
131
|
+
file,
|
|
132
|
+
identity: `${skill}/${stem}`,
|
|
133
|
+
imports: Object.fromEntries(Object.entries(getRecipeImports(raw)).map(([alias, value]) => [
|
|
134
|
+
alias,
|
|
135
|
+
getImportFrom(value),
|
|
136
|
+
])),
|
|
137
|
+
skill,
|
|
138
|
+
stem,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
rejected.push({
|
|
143
|
+
file,
|
|
144
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
145
|
+
skill,
|
|
146
|
+
stem,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
96
149
|
}
|
|
97
150
|
}
|
|
98
|
-
return components;
|
|
151
|
+
return { components, partial: rejected.length > 0, rejected };
|
|
152
|
+
}
|
|
153
|
+
export function listActiveSkillRecipeComponents(context) {
|
|
154
|
+
const inventory = inventoryActiveSkillRecipeComponents(context);
|
|
155
|
+
if (inventory.rejected.length > 0)
|
|
156
|
+
throw new Error(inventory.rejected[0].reason);
|
|
157
|
+
return inventory.components;
|
|
99
158
|
}
|
|
100
159
|
const SKILL_RECIPE_REFERENCE = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/;
|
|
101
160
|
function qualifiedRecipeNameForFile(file, context) {
|
|
@@ -116,10 +175,11 @@ function qualifiedRecipeNameForFile(file, context) {
|
|
|
116
175
|
}
|
|
117
176
|
function assertRecipeReferencePrefixWasNotRemoved(value) {
|
|
118
177
|
if (value.startsWith("std:")) {
|
|
119
|
-
throw new Error("std: Recipe references were removed in pi-actors 0.46
|
|
178
|
+
throw new Error("std: Recipe references were removed in pi-actors 0.46. Use <skill>/<recipe> or an explicit .json/.md file path. Next: inspect target=recipes view=status to identify the active owning Skill.");
|
|
120
179
|
}
|
|
121
180
|
if (value.startsWith("skill:")) {
|
|
122
|
-
|
|
181
|
+
const identity = value.slice("skill:".length);
|
|
182
|
+
throw new Error(`skill: Recipe references were removed in pi-actors 0.46. Use ${identity} without a prefix. Next: inspect target=recipes view=doctor identity=${identity}`);
|
|
123
183
|
}
|
|
124
184
|
}
|
|
125
185
|
function skillRecipeCandidates(value, context) {
|
|
@@ -129,12 +189,15 @@ function skillRecipeCandidates(value, context) {
|
|
|
129
189
|
const [, skillName, recipeName] = match;
|
|
130
190
|
const roots = [...(context.namespaces[skillName] ?? [])];
|
|
131
191
|
if (roots.length === 0)
|
|
132
|
-
throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}`);
|
|
192
|
+
throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}. Owning Skill "${skillName}" is not active. Next: activate Skill ${skillName}, then inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
|
|
133
193
|
if (roots.length > 1)
|
|
134
|
-
throw new Error(`Duplicate active Skill identity ${skillName}
|
|
194
|
+
throw new Error(`Duplicate active Skill identity ${skillName} (${roots.length} active definitions); ${skillName}/${recipeName} cannot resolve unambiguously. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
|
|
135
195
|
const root = roots[0];
|
|
136
196
|
const candidates = ["json", "md"].map((extension) => resolve(root, `${recipeName}.${extension}`));
|
|
137
197
|
const existing = candidates.filter((candidate) => existsSync(candidate));
|
|
198
|
+
if (existing.length === 0) {
|
|
199
|
+
throw new Error(`Skill Recipe component not found: ${skillName}/${recipeName}; owning Skill "${skillName}" is active. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
|
|
200
|
+
}
|
|
138
201
|
if (existing.length > 1) {
|
|
139
202
|
throw new Error(`Skill Recipe stem collision: ${skillName}/${recipeName} has both .json and .md files`);
|
|
140
203
|
}
|
|
@@ -164,13 +227,13 @@ function findOwningSkillDir(recipeFile) {
|
|
|
164
227
|
function assertRecipeHasNoDeclaredName(raw) {
|
|
165
228
|
if (!Object.hasOwn(raw, "name"))
|
|
166
229
|
return;
|
|
167
|
-
throw new Error("Recipe.name was removed in pi-actors 0.46
|
|
230
|
+
throw new Error("Recipe.name was removed in pi-actors 0.46. File-backed Recipe identity is derived from its file name. Next: remove the name field, keep the intended filename stem, then inspect target=recipes view=doctor.");
|
|
168
231
|
}
|
|
169
232
|
function assertRuntimeRecipeValuesNotDeclared(raw) {
|
|
170
233
|
const args = Array.isArray(raw.args)
|
|
171
234
|
? Schema.getExplicitToolArgNames(raw.args.map(String))
|
|
172
235
|
: [];
|
|
173
|
-
for (const name of
|
|
236
|
+
for (const name of RESERVED_AUTHORED_RECIPE_VALUE_NAMES) {
|
|
174
237
|
if (args.includes(name) ||
|
|
175
238
|
(isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
|
|
176
239
|
(isRecord(raw.values) && Object.hasOwn(raw.values, name))) {
|
|
@@ -731,6 +794,28 @@ function getDirectDelegatedRecipe(value, currentRecipeFile, stack, options = {})
|
|
|
731
794
|
}
|
|
732
795
|
return undefined;
|
|
733
796
|
}
|
|
797
|
+
function resolveDirectDelegationArgs(delegated, wrapperArgs, wrapperDefaults) {
|
|
798
|
+
if (!Array.isArray(delegated.args))
|
|
799
|
+
return Array.isArray(wrapperArgs) ? wrapperArgs.map(String) : undefined;
|
|
800
|
+
const delegatedSpec = Schema.parseToolArgDeclarationList(delegated.args);
|
|
801
|
+
if (delegatedSpec.error)
|
|
802
|
+
throw new Error(delegatedSpec.error);
|
|
803
|
+
const wrapperDeclarations = Array.isArray(wrapperArgs)
|
|
804
|
+
? wrapperArgs.map(String)
|
|
805
|
+
: [];
|
|
806
|
+
const wrapperSpec = Schema.parseToolArgDeclarationList(wrapperDeclarations);
|
|
807
|
+
if (wrapperSpec.error)
|
|
808
|
+
throw new Error(wrapperSpec.error);
|
|
809
|
+
const declared = new Set(delegatedSpec.args);
|
|
810
|
+
for (const arg of wrapperSpec.args) {
|
|
811
|
+
if (!declared.has(arg))
|
|
812
|
+
throw new Error(`Unknown delegated Recipe argument: ${arg}`);
|
|
813
|
+
}
|
|
814
|
+
Schema.assertCompatibleToolArgTypes(delegatedSpec.argTypes, wrapperSpec.argTypes);
|
|
815
|
+
assertRecipeDefaultsDeclared(delegated.args, wrapperDefaults, "delegated Recipe default");
|
|
816
|
+
Schema.normalizeRuntimeValues(Object.fromEntries(Object.entries(wrapperDefaults ?? {}).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value))), delegatedSpec.argTypes);
|
|
817
|
+
return delegated.args;
|
|
818
|
+
}
|
|
734
819
|
function expandImportNodes(value, imports, options = {}) {
|
|
735
820
|
if (typeof value === "string")
|
|
736
821
|
return value;
|
|
@@ -783,7 +868,9 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
783
868
|
if (stack.length >= MAX_RECIPE_IMPORT_DEPTH) {
|
|
784
869
|
throw new Error(`Recipe import depth exceeds limit ${MAX_RECIPE_IMPORT_DEPTH}: ${[...stack, path].join(" -> ")}`);
|
|
785
870
|
}
|
|
786
|
-
const raw =
|
|
871
|
+
const raw = options.authoredEntry?.path === path
|
|
872
|
+
? options.authoredEntry.recipe
|
|
873
|
+
: readRawRecipeConfig(path);
|
|
787
874
|
if (!raw || !Object.hasOwn(raw, "template"))
|
|
788
875
|
return undefined;
|
|
789
876
|
assertRecipeHasNoDeclaredName(raw);
|
|
@@ -844,7 +931,15 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
844
931
|
role: stack.length > 0 ? "import" : "entry",
|
|
845
932
|
})
|
|
846
933
|
: expandedTemplate;
|
|
847
|
-
const
|
|
934
|
+
const wrapperDefaults = isRecord(substituted.defaults)
|
|
935
|
+
? substituted.defaults
|
|
936
|
+
: undefined;
|
|
937
|
+
const mergedDefaults = mergeDefaults(delegated?.defaults, wrapperDefaults);
|
|
938
|
+
const effectiveArgs = delegated
|
|
939
|
+
? resolveDirectDelegationArgs(delegated, substituted.args, wrapperDefaults)
|
|
940
|
+
: Array.isArray(substituted.args)
|
|
941
|
+
? substituted.args
|
|
942
|
+
: undefined;
|
|
848
943
|
const artifactSource = isRecord(substituted.artifacts)
|
|
849
944
|
? substituted.artifacts
|
|
850
945
|
: delegated?.artifacts;
|
|
@@ -879,11 +974,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
879
974
|
? { imports: getRecipeImports(raw) }
|
|
880
975
|
: {}),
|
|
881
976
|
template: templateWithContext,
|
|
882
|
-
...(
|
|
883
|
-
? { args: substituted.args }
|
|
884
|
-
: Array.isArray(delegated?.args)
|
|
885
|
-
? { args: delegated.args }
|
|
886
|
-
: {}),
|
|
977
|
+
...(effectiveArgs ? { args: effectiveArgs } : {}),
|
|
887
978
|
...(mergedDefaults ? { defaults: mergedDefaults } : {}),
|
|
888
979
|
...(typeof substituted.parallel === "boolean"
|
|
889
980
|
? { parallel: substituted.parallel }
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -10,22 +10,32 @@ export interface RegisterToolInput {
|
|
|
10
10
|
description?: string;
|
|
11
11
|
async?: boolean;
|
|
12
12
|
template?: CommandTemplates.CommandTemplateValue | null;
|
|
13
|
+
from?: string;
|
|
14
|
+
defaults?: Record<string, unknown>;
|
|
13
15
|
draft?: string;
|
|
14
16
|
args?: string;
|
|
15
17
|
update?: boolean;
|
|
16
|
-
values?: Record<string, unknown>;
|
|
17
18
|
}
|
|
18
19
|
export interface RegisterToolResultDetails {
|
|
20
|
+
active_tool?: boolean;
|
|
21
|
+
activation?: "current_session" | "unverified";
|
|
22
|
+
activation_boundary?: string;
|
|
19
23
|
args?: string[];
|
|
20
24
|
async?: boolean;
|
|
21
|
-
|
|
25
|
+
callable_now?: boolean;
|
|
22
26
|
defaults?: Record<string, string>;
|
|
23
|
-
|
|
27
|
+
host_registered?: boolean;
|
|
28
|
+
next_actions?: string[];
|
|
29
|
+
optional_args?: string[];
|
|
30
|
+
persisted?: boolean;
|
|
24
31
|
promoted?: boolean;
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
registry_active?: boolean;
|
|
33
|
+
required_args?: string[];
|
|
34
|
+
resolved?: boolean;
|
|
35
|
+
source?: string;
|
|
27
36
|
templateWarnings?: string[];
|
|
28
37
|
tool: string;
|
|
38
|
+
validated?: boolean;
|
|
29
39
|
}
|
|
30
40
|
export interface RegisterToolResult {
|
|
31
41
|
content: Array<{
|
|
@@ -34,6 +44,12 @@ export interface RegisterToolResult {
|
|
|
34
44
|
}>;
|
|
35
45
|
details: RegisterToolResultDetails;
|
|
36
46
|
}
|
|
47
|
+
interface RuntimeActivation {
|
|
48
|
+
active_tool: boolean;
|
|
49
|
+
activation: "current_session" | "unverified";
|
|
50
|
+
callable_now: boolean;
|
|
51
|
+
host_registered: boolean;
|
|
52
|
+
}
|
|
37
53
|
export interface RegisterToolRuntimeDeps<TContext> {
|
|
38
54
|
configPath: string;
|
|
39
55
|
recipeRoot?: string;
|
|
@@ -41,8 +57,9 @@ export interface RegisterToolRuntimeDeps<TContext> {
|
|
|
41
57
|
getTools: () => Map<string, Config.RegisteredTool>;
|
|
42
58
|
getActiveTools: () => string[];
|
|
43
59
|
notify: (ctx: TContext, message: string, type: "info" | "warning" | "error") => void;
|
|
44
|
-
registerRuntimeTool: (cfg: Config.RegisteredTool) => void;
|
|
60
|
+
registerRuntimeTool: (cfg: Config.RegisteredTool) => RuntimeActivation | void;
|
|
45
61
|
reservedToolNames: Set<string>;
|
|
46
62
|
setActiveTools: (toolNames: string[]) => void;
|
|
47
63
|
}
|
|
48
64
|
export declare function executeRegisterTool<TContext>(params: unknown, ctx: TContext, deps: RegisterToolRuntimeDeps<TContext>): Promise<RegisterToolResult>;
|
|
65
|
+
export {};
|