@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
package/lib/recipes-discovery.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { join } from "node:path";
|
|
|
12
12
|
import * as CommandTemplates from "./command-templates.ts";
|
|
13
13
|
import type { RegisteredTool } from "./config.ts";
|
|
14
14
|
import * as ModelContext from "./model-context.ts";
|
|
15
|
+
import type { RecipeResolutionContext } from "./recipes-context.ts";
|
|
15
16
|
import type { TemplateRecipeConfig } from "./recipes-references.ts";
|
|
16
17
|
import * as RecipesReferences from "./recipes-references.ts";
|
|
17
18
|
import * as RecipesUsage from "./recipes-usage.ts";
|
|
@@ -29,6 +30,7 @@ export interface DiscoveredRecipe {
|
|
|
29
30
|
disabled: boolean;
|
|
30
31
|
tool: boolean;
|
|
31
32
|
mutableUsage: boolean;
|
|
33
|
+
resolutionContext?: RecipeResolutionContext;
|
|
32
34
|
diagnostics: string[];
|
|
33
35
|
riskLabels: CommandTemplates.CommandTemplateRiskLabel[];
|
|
34
36
|
shadows: string[];
|
|
@@ -58,6 +60,22 @@ export interface RecipesDiscoverySource {
|
|
|
58
60
|
file?: string;
|
|
59
61
|
defaultTool?: boolean;
|
|
60
62
|
mutableUsage?: boolean;
|
|
63
|
+
resolutionContext?: RecipeResolutionContext;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface UserRecipeAdmission {
|
|
67
|
+
args?: string[];
|
|
68
|
+
argTypes?: RegisteredTool["argTypes"];
|
|
69
|
+
artifacts?: Record<string, string>;
|
|
70
|
+
async?: boolean;
|
|
71
|
+
control?: string[];
|
|
72
|
+
defaults?: Record<string, unknown>;
|
|
73
|
+
diagnostics: string[];
|
|
74
|
+
effectiveRecipe?: TemplateRecipeConfig;
|
|
75
|
+
identity: string;
|
|
76
|
+
sourcePath: string;
|
|
77
|
+
tool?: RegisteredTool;
|
|
78
|
+
validated: boolean;
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
function assertToolSafeRepeatConfig(
|
|
@@ -185,10 +203,13 @@ function readDiscoveredRecipe(
|
|
|
185
203
|
priority: number,
|
|
186
204
|
defaultTool = false,
|
|
187
205
|
mutableUsage = false,
|
|
206
|
+
resolutionContext?: RecipeResolutionContext,
|
|
188
207
|
): DiscoveredRecipe {
|
|
189
208
|
const id = RecipesReferences.getRecipeIdFromPath(file);
|
|
190
209
|
try {
|
|
191
|
-
const config = RecipesReferences.readResolvedRecipeConfig(file
|
|
210
|
+
const config = RecipesReferences.readResolvedRecipeConfig(file, [], {
|
|
211
|
+
skillContext: resolutionContext?.activeSkills,
|
|
212
|
+
});
|
|
192
213
|
const invalid = !config;
|
|
193
214
|
const disabled = config?.disabled === true;
|
|
194
215
|
return {
|
|
@@ -203,6 +224,7 @@ function readDiscoveredRecipe(
|
|
|
203
224
|
disabled,
|
|
204
225
|
tool: defaultTool && !disabled && !invalid,
|
|
205
226
|
mutableUsage,
|
|
227
|
+
resolutionContext,
|
|
206
228
|
diagnostics: getRecipeConfigDiagnostics(file, config),
|
|
207
229
|
riskLabels: getRecipeRiskLabels(config),
|
|
208
230
|
shadows: [],
|
|
@@ -219,8 +241,9 @@ function readDiscoveredRecipe(
|
|
|
219
241
|
disabled: false,
|
|
220
242
|
tool: false,
|
|
221
243
|
mutableUsage,
|
|
244
|
+
resolutionContext,
|
|
222
245
|
diagnostics: [
|
|
223
|
-
`
|
|
246
|
+
`Recipe ${id} rejected: ${error instanceof Error ? error.message : String(error)}`,
|
|
224
247
|
],
|
|
225
248
|
riskLabels: [],
|
|
226
249
|
shadows: [],
|
|
@@ -233,6 +256,7 @@ function filesForSource(source: RecipesDiscoverySource): Array<{
|
|
|
233
256
|
file: string;
|
|
234
257
|
defaultTool: boolean;
|
|
235
258
|
mutableUsage: boolean;
|
|
259
|
+
resolutionContext?: RecipeResolutionContext;
|
|
236
260
|
}> {
|
|
237
261
|
const defaultTool = source.defaultTool === true;
|
|
238
262
|
const mutableUsage = source.mutableUsage === true;
|
|
@@ -243,6 +267,7 @@ function filesForSource(source: RecipesDiscoverySource): Array<{
|
|
|
243
267
|
file: source.file,
|
|
244
268
|
defaultTool,
|
|
245
269
|
mutableUsage,
|
|
270
|
+
resolutionContext: source.resolutionContext,
|
|
246
271
|
},
|
|
247
272
|
];
|
|
248
273
|
return source.root
|
|
@@ -251,6 +276,7 @@ function filesForSource(source: RecipesDiscoverySource): Array<{
|
|
|
251
276
|
file,
|
|
252
277
|
defaultTool,
|
|
253
278
|
mutableUsage,
|
|
279
|
+
resolutionContext: source.resolutionContext,
|
|
254
280
|
}))
|
|
255
281
|
: [];
|
|
256
282
|
}
|
|
@@ -338,8 +364,16 @@ export function discoverRecipeSources(
|
|
|
338
364
|
sources: RecipesDiscoverySource[],
|
|
339
365
|
): RecipesDiscoveryResult {
|
|
340
366
|
const entries = sources.flatMap((source, priority) =>
|
|
341
|
-
filesForSource(source).map(
|
|
342
|
-
|
|
367
|
+
filesForSource(source).map(
|
|
368
|
+
({ root, file, defaultTool, mutableUsage, resolutionContext }) =>
|
|
369
|
+
readDiscoveredRecipe(
|
|
370
|
+
root,
|
|
371
|
+
file,
|
|
372
|
+
priority,
|
|
373
|
+
defaultTool,
|
|
374
|
+
mutableUsage,
|
|
375
|
+
resolutionContext,
|
|
376
|
+
),
|
|
343
377
|
),
|
|
344
378
|
);
|
|
345
379
|
const byId = new Map<string, DiscoveredRecipe[]>();
|
|
@@ -836,14 +870,13 @@ export function summarizeDiscovery(
|
|
|
836
870
|
};
|
|
837
871
|
}
|
|
838
872
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const
|
|
846
|
-
const description = cfg.description ?? `Execute template recipe: ${entry.id}`;
|
|
873
|
+
function projectRegisteredTool(
|
|
874
|
+
identity: string,
|
|
875
|
+
path: string,
|
|
876
|
+
cfg: TemplateRecipeConfig,
|
|
877
|
+
mutableUsage: boolean,
|
|
878
|
+
): RegisteredTool {
|
|
879
|
+
const description = cfg.description ?? `Execute template recipe: ${identity}`;
|
|
847
880
|
const argTemplate = cfg.template;
|
|
848
881
|
const argTemplateConfig =
|
|
849
882
|
typeof argTemplate === "object" && !Array.isArray(argTemplate)
|
|
@@ -867,27 +900,38 @@ export function toRegisteredTool(
|
|
|
867
900
|
explicitArgTypes,
|
|
868
901
|
{ ...(cfg.defaults ?? {}), ...(cfg.values ?? {}) },
|
|
869
902
|
);
|
|
870
|
-
const
|
|
871
|
-
Schema.assertCompatibleToolArgTypes(explicitArgTypes,
|
|
903
|
+
const inferredArgTypes = Schema.getTemplateArgTypes(argTemplateConfig);
|
|
904
|
+
Schema.assertCompatibleToolArgTypes(explicitArgTypes, inferredArgTypes);
|
|
905
|
+
const effectiveArgTypes = { ...inferredArgTypes, ...explicitArgTypes };
|
|
906
|
+
const args = Schema.getToolArgNames(argTemplateConfig).filter(
|
|
907
|
+
(arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg),
|
|
908
|
+
);
|
|
909
|
+
const callerArgSet = new Set(args);
|
|
910
|
+
const argTypes = Object.fromEntries(
|
|
911
|
+
Object.entries(effectiveArgTypes).filter(([arg]) => callerArgSet.has(arg)),
|
|
912
|
+
);
|
|
913
|
+
const defaults = Object.fromEntries(
|
|
914
|
+
Object.entries(cfg.defaults ?? {}).filter(([arg]) => callerArgSet.has(arg)),
|
|
915
|
+
);
|
|
916
|
+
const storedArgs = cfg.args?.filter((arg) =>
|
|
917
|
+
callerArgSet.has(Schema.parseToolArgToken(String(arg)).arg),
|
|
918
|
+
);
|
|
872
919
|
return {
|
|
873
|
-
name:
|
|
920
|
+
name: identity,
|
|
874
921
|
description,
|
|
875
|
-
template,
|
|
922
|
+
template: path,
|
|
876
923
|
recipe: cfg,
|
|
877
|
-
args
|
|
924
|
+
args,
|
|
878
925
|
defaults: Object.fromEntries(
|
|
879
|
-
Object.entries(
|
|
880
|
-
key,
|
|
881
|
-
String(value),
|
|
882
|
-
]),
|
|
926
|
+
Object.entries(defaults).map(([key, value]) => [key, String(value)]),
|
|
883
927
|
),
|
|
884
928
|
...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
|
|
885
|
-
...(
|
|
886
|
-
...(
|
|
887
|
-
...(
|
|
929
|
+
...(mutableUsage ? { sourcePath: path } : {}),
|
|
930
|
+
...(storedArgs ? { storedArgs } : {}),
|
|
931
|
+
...(Object.keys(defaults).length > 0
|
|
888
932
|
? {
|
|
889
933
|
storedDefaults: Object.fromEntries(
|
|
890
|
-
Object.entries(
|
|
934
|
+
Object.entries(defaults).map(([key, value]) => [
|
|
891
935
|
key,
|
|
892
936
|
String(value),
|
|
893
937
|
]),
|
|
@@ -896,3 +940,120 @@ export function toRegisteredTool(
|
|
|
896
940
|
: {}),
|
|
897
941
|
};
|
|
898
942
|
}
|
|
943
|
+
|
|
944
|
+
export function summarizeRegisteredToolArgs(tool: RegisteredTool): {
|
|
945
|
+
optional: string[];
|
|
946
|
+
required: string[];
|
|
947
|
+
} {
|
|
948
|
+
const publicArgs = tool.args.filter(
|
|
949
|
+
(arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg),
|
|
950
|
+
);
|
|
951
|
+
const inlineDefaults = Schema.getExplicitToolArgDefaults(tool.recipe?.args);
|
|
952
|
+
const required = publicArgs.filter(
|
|
953
|
+
(arg) =>
|
|
954
|
+
!Object.hasOwn(tool.defaults, arg) &&
|
|
955
|
+
!Object.hasOwn(inlineDefaults, arg),
|
|
956
|
+
);
|
|
957
|
+
const requiredSet = new Set(required);
|
|
958
|
+
const optional = publicArgs.filter((arg) => !requiredSet.has(arg));
|
|
959
|
+
if (tool.recipe?.async === true) {
|
|
960
|
+
optional.push("run_id", "transport_context");
|
|
961
|
+
}
|
|
962
|
+
return { optional, required };
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function boundedAdmissionDiagnostic(error: unknown): string {
|
|
966
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
967
|
+
return message.length <= 4096 ? message : `${message.slice(0, 4095)}…`;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function admitUserRecipe(
|
|
971
|
+
sourcePath: string,
|
|
972
|
+
resolutionContext: RecipeResolutionContext,
|
|
973
|
+
authoredRecipe?: Record<string, unknown>,
|
|
974
|
+
mutableUsage = true,
|
|
975
|
+
): UserRecipeAdmission {
|
|
976
|
+
const identity = RecipesReferences.getRecipeIdFromPath(sourcePath);
|
|
977
|
+
try {
|
|
978
|
+
const effectiveRecipe = RecipesReferences.readResolvedRecipeConfig(
|
|
979
|
+
sourcePath,
|
|
980
|
+
[],
|
|
981
|
+
{
|
|
982
|
+
...(authoredRecipe
|
|
983
|
+
? { authoredEntry: { path: sourcePath, recipe: authoredRecipe } }
|
|
984
|
+
: {}),
|
|
985
|
+
skillContext: resolutionContext.activeSkills,
|
|
986
|
+
},
|
|
987
|
+
);
|
|
988
|
+
if (!effectiveRecipe) {
|
|
989
|
+
return {
|
|
990
|
+
diagnostics: [
|
|
991
|
+
authoredRecipe
|
|
992
|
+
? "Recipe must define a valid template."
|
|
993
|
+
: RecipesReferences.diagnoseRawRecipeConfigFailure(sourcePath) ??
|
|
994
|
+
"Recipe must define a valid template.",
|
|
995
|
+
],
|
|
996
|
+
identity,
|
|
997
|
+
sourcePath,
|
|
998
|
+
validated: false,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
if (effectiveRecipe.disabled === true) {
|
|
1002
|
+
return {
|
|
1003
|
+
diagnostics: ["Recipe is disabled."],
|
|
1004
|
+
effectiveRecipe,
|
|
1005
|
+
identity,
|
|
1006
|
+
sourcePath,
|
|
1007
|
+
validated: false,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const tool = projectRegisteredTool(
|
|
1011
|
+
identity,
|
|
1012
|
+
sourcePath,
|
|
1013
|
+
effectiveRecipe,
|
|
1014
|
+
mutableUsage,
|
|
1015
|
+
);
|
|
1016
|
+
return {
|
|
1017
|
+
args: tool.args,
|
|
1018
|
+
argTypes: tool.argTypes,
|
|
1019
|
+
artifacts: effectiveRecipe.artifacts,
|
|
1020
|
+
async: effectiveRecipe.async === true,
|
|
1021
|
+
control: effectiveRecipe.control,
|
|
1022
|
+
defaults: effectiveRecipe.defaults,
|
|
1023
|
+
diagnostics: [],
|
|
1024
|
+
effectiveRecipe,
|
|
1025
|
+
identity,
|
|
1026
|
+
sourcePath,
|
|
1027
|
+
tool,
|
|
1028
|
+
validated: true,
|
|
1029
|
+
};
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
return {
|
|
1032
|
+
diagnostics: [boundedAdmissionDiagnostic(error)],
|
|
1033
|
+
identity,
|
|
1034
|
+
sourcePath,
|
|
1035
|
+
validated: false,
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export function toRegisteredTool(
|
|
1041
|
+
entry: DiscoveredRecipe,
|
|
1042
|
+
): RegisteredTool | undefined {
|
|
1043
|
+
if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
|
|
1044
|
+
return undefined;
|
|
1045
|
+
if (entry.resolutionContext) {
|
|
1046
|
+
return admitUserRecipe(
|
|
1047
|
+
entry.path,
|
|
1048
|
+
entry.resolutionContext,
|
|
1049
|
+
undefined,
|
|
1050
|
+
entry.mutableUsage,
|
|
1051
|
+
).tool;
|
|
1052
|
+
}
|
|
1053
|
+
return projectRegisteredTool(
|
|
1054
|
+
entry.id,
|
|
1055
|
+
entry.path,
|
|
1056
|
+
entry.config,
|
|
1057
|
+
entry.mutableUsage,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
@@ -87,11 +87,34 @@ export interface TemplateRecipeContextRecord {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export interface ReadResolvedRecipeConfigOptions {
|
|
90
|
+
authoredEntry?: { path: string; recipe: Record<string, unknown> };
|
|
90
91
|
includeActorRecipeContext?: boolean;
|
|
91
92
|
skillContext?: ActiveSkillRecipeContext;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const
|
|
95
|
+
const RESERVED_AUTHORED_RECIPE_VALUE_NAMES = new Set([
|
|
96
|
+
"recipe_dir",
|
|
97
|
+
"skill_dir",
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
export const RUNTIME_OWNED_RECIPE_INPUT_NAMES = Object.freeze([
|
|
101
|
+
"owner_id",
|
|
102
|
+
"recipe_dir",
|
|
103
|
+
"run_instance_id",
|
|
104
|
+
"runtime_state_root",
|
|
105
|
+
"session_id",
|
|
106
|
+
"skill_dir",
|
|
107
|
+
"state_dir",
|
|
108
|
+
"trace_file",
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const RUNTIME_OWNED_RECIPE_INPUT_SET = new Set(
|
|
112
|
+
RUNTIME_OWNED_RECIPE_INPUT_NAMES,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
export function isRuntimeOwnedRecipeInput(name: string): boolean {
|
|
116
|
+
return RUNTIME_OWNED_RECIPE_INPUT_SET.has(name);
|
|
117
|
+
}
|
|
95
118
|
|
|
96
119
|
export interface ActiveSkillRecipeSource {
|
|
97
120
|
name: string;
|
|
@@ -149,6 +172,19 @@ export interface ActiveSkillRecipeComponent {
|
|
|
149
172
|
stem: string;
|
|
150
173
|
}
|
|
151
174
|
|
|
175
|
+
export interface SkillRecipeComponentDiagnostic {
|
|
176
|
+
file?: string;
|
|
177
|
+
reason: string;
|
|
178
|
+
skill: string;
|
|
179
|
+
stem?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface SkillRecipeComponentInventory {
|
|
183
|
+
components: ActiveSkillRecipeComponent[];
|
|
184
|
+
partial: boolean;
|
|
185
|
+
rejected: SkillRecipeComponentDiagnostic[];
|
|
186
|
+
}
|
|
187
|
+
|
|
152
188
|
function nestedRecipeFiles(root: string): string[] {
|
|
153
189
|
if (!existsSync(root)) return [];
|
|
154
190
|
return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
@@ -158,20 +194,32 @@ function nestedRecipeFiles(root: string): string[] {
|
|
|
158
194
|
});
|
|
159
195
|
}
|
|
160
196
|
|
|
161
|
-
export function
|
|
197
|
+
export function inventoryActiveSkillRecipeComponents(
|
|
162
198
|
context: ActiveSkillRecipeContext,
|
|
163
|
-
):
|
|
199
|
+
): SkillRecipeComponentInventory {
|
|
164
200
|
const components: ActiveSkillRecipeComponent[] = [];
|
|
201
|
+
const rejected: SkillRecipeComponentDiagnostic[] = [];
|
|
165
202
|
for (const [skill, roots] of Object.entries(context.namespaces)) {
|
|
166
|
-
if (roots.length > 1)
|
|
167
|
-
|
|
203
|
+
if (roots.length > 1) {
|
|
204
|
+
rejected.push({
|
|
205
|
+
reason: `Duplicate active Skill identity ${skill} (${roots.length} active definitions); exact Recipe resolution is ambiguous. Next: inspect target=recipes view=doctor identity=${skill}/<recipe>`,
|
|
206
|
+
skill,
|
|
207
|
+
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
168
210
|
const root = roots[0];
|
|
169
211
|
if (!root || !existsSync(root)) continue;
|
|
170
212
|
const direct = readdirSync(root, { withFileTypes: true });
|
|
171
213
|
for (const entry of direct.filter((candidate) => candidate.isDirectory())) {
|
|
172
214
|
const nested = nestedRecipeFiles(join(root, entry.name));
|
|
173
|
-
|
|
174
|
-
|
|
215
|
+
for (const file of nested) {
|
|
216
|
+
rejected.push({
|
|
217
|
+
file,
|
|
218
|
+
reason: `Nested Skill Recipe file is not addressable: ${file}`,
|
|
219
|
+
skill,
|
|
220
|
+
stem: getRecipeIdFromPath(file),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
175
223
|
}
|
|
176
224
|
const files = direct
|
|
177
225
|
.filter(
|
|
@@ -186,30 +234,62 @@ export function listActiveSkillRecipeComponents(
|
|
|
186
234
|
byStem.set(stem, matches);
|
|
187
235
|
}
|
|
188
236
|
for (const [stem, matches] of [...byStem].sort(([a], [b]) => a.localeCompare(b))) {
|
|
189
|
-
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem))
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
237
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem)) {
|
|
238
|
+
rejected.push({
|
|
239
|
+
file: matches[0],
|
|
240
|
+
reason: `Invalid Skill Recipe filename stem: ${stem}`,
|
|
241
|
+
skill,
|
|
242
|
+
stem,
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (matches.length > 1) {
|
|
247
|
+
rejected.push({
|
|
248
|
+
file: matches[0],
|
|
249
|
+
reason: `Skill Recipe stem collision: ${skill}/${stem} has both .json and .md files`,
|
|
250
|
+
skill,
|
|
251
|
+
stem,
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
193
255
|
const file = matches[0];
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Object.
|
|
203
|
-
alias,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
256
|
+
try {
|
|
257
|
+
const raw = readRawRecipeConfig(file);
|
|
258
|
+
if (!raw || !Object.hasOwn(raw, "template"))
|
|
259
|
+
throw new Error(`Invalid Skill Recipe component: ${skill}/${stem}`);
|
|
260
|
+
assertRecipeHasNoDeclaredName(raw);
|
|
261
|
+
components.push({
|
|
262
|
+
file,
|
|
263
|
+
identity: `${skill}/${stem}`,
|
|
264
|
+
imports: Object.fromEntries(
|
|
265
|
+
Object.entries(getRecipeImports(raw)).map(([alias, value]) => [
|
|
266
|
+
alias,
|
|
267
|
+
getImportFrom(value),
|
|
268
|
+
]),
|
|
269
|
+
),
|
|
270
|
+
skill,
|
|
271
|
+
stem,
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
rejected.push({
|
|
275
|
+
file,
|
|
276
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
277
|
+
skill,
|
|
278
|
+
stem,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
210
281
|
}
|
|
211
282
|
}
|
|
212
|
-
return components;
|
|
283
|
+
return { components, partial: rejected.length > 0, rejected };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function listActiveSkillRecipeComponents(
|
|
287
|
+
context: ActiveSkillRecipeContext,
|
|
288
|
+
): ActiveSkillRecipeComponent[] {
|
|
289
|
+
const inventory = inventoryActiveSkillRecipeComponents(context);
|
|
290
|
+
if (inventory.rejected.length > 0)
|
|
291
|
+
throw new Error(inventory.rejected[0].reason);
|
|
292
|
+
return inventory.components;
|
|
213
293
|
}
|
|
214
294
|
|
|
215
295
|
const SKILL_RECIPE_REFERENCE =
|
|
@@ -239,12 +319,13 @@ function qualifiedRecipeNameForFile(
|
|
|
239
319
|
function assertRecipeReferencePrefixWasNotRemoved(value: string): void {
|
|
240
320
|
if (value.startsWith("std:")) {
|
|
241
321
|
throw new Error(
|
|
242
|
-
"std: Recipe references were removed in pi-actors 0.46
|
|
322
|
+
"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.",
|
|
243
323
|
);
|
|
244
324
|
}
|
|
245
325
|
if (value.startsWith("skill:")) {
|
|
326
|
+
const identity = value.slice("skill:".length);
|
|
246
327
|
throw new Error(
|
|
247
|
-
|
|
328
|
+
`skill: Recipe references were removed in pi-actors 0.46. Use ${identity} without a prefix. Next: inspect target=recipes view=doctor identity=${identity}`,
|
|
248
329
|
);
|
|
249
330
|
}
|
|
250
331
|
}
|
|
@@ -258,16 +339,23 @@ function skillRecipeCandidates(
|
|
|
258
339
|
const [, skillName, recipeName] = match;
|
|
259
340
|
const roots = [...(context.namespaces[skillName] ?? [])];
|
|
260
341
|
if (roots.length === 0)
|
|
261
|
-
throw new Error(
|
|
342
|
+
throw new Error(
|
|
343
|
+
`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}`,
|
|
344
|
+
);
|
|
262
345
|
if (roots.length > 1)
|
|
263
346
|
throw new Error(
|
|
264
|
-
`Duplicate active Skill identity ${skillName}
|
|
347
|
+
`Duplicate active Skill identity ${skillName} (${roots.length} active definitions); ${skillName}/${recipeName} cannot resolve unambiguously. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`,
|
|
265
348
|
);
|
|
266
349
|
const root = roots[0];
|
|
267
350
|
const candidates = ["json", "md"].map((extension) =>
|
|
268
351
|
resolve(root, `${recipeName}.${extension}`),
|
|
269
352
|
);
|
|
270
353
|
const existing = candidates.filter((candidate) => existsSync(candidate));
|
|
354
|
+
if (existing.length === 0) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Skill Recipe component not found: ${skillName}/${recipeName}; owning Skill "${skillName}" is active. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
271
359
|
if (existing.length > 1) {
|
|
272
360
|
throw new Error(
|
|
273
361
|
`Skill Recipe stem collision: ${skillName}/${recipeName} has both .json and .md files`,
|
|
@@ -303,7 +391,7 @@ function findOwningSkillDir(recipeFile: string): string | undefined {
|
|
|
303
391
|
function assertRecipeHasNoDeclaredName(raw: Record<string, unknown>): void {
|
|
304
392
|
if (!Object.hasOwn(raw, "name")) return;
|
|
305
393
|
throw new Error(
|
|
306
|
-
"Recipe.name was removed in pi-actors 0.46
|
|
394
|
+
"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.",
|
|
307
395
|
);
|
|
308
396
|
}
|
|
309
397
|
|
|
@@ -311,7 +399,7 @@ function assertRuntimeRecipeValuesNotDeclared(raw: Record<string, unknown>): voi
|
|
|
311
399
|
const args = Array.isArray(raw.args)
|
|
312
400
|
? Schema.getExplicitToolArgNames(raw.args.map(String))
|
|
313
401
|
: [];
|
|
314
|
-
for (const name of
|
|
402
|
+
for (const name of RESERVED_AUTHORED_RECIPE_VALUE_NAMES) {
|
|
315
403
|
if (
|
|
316
404
|
args.includes(name) ||
|
|
317
405
|
(isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
|
|
@@ -1068,6 +1156,46 @@ function getDirectDelegatedRecipe(
|
|
|
1068
1156
|
return undefined;
|
|
1069
1157
|
}
|
|
1070
1158
|
|
|
1159
|
+
function resolveDirectDelegationArgs(
|
|
1160
|
+
delegated: TemplateRecipeConfig,
|
|
1161
|
+
wrapperArgs: unknown,
|
|
1162
|
+
wrapperDefaults: Record<string, unknown> | undefined,
|
|
1163
|
+
): string[] | undefined {
|
|
1164
|
+
if (!Array.isArray(delegated.args))
|
|
1165
|
+
return Array.isArray(wrapperArgs) ? wrapperArgs.map(String) : undefined;
|
|
1166
|
+
const delegatedSpec = Schema.parseToolArgDeclarationList(delegated.args);
|
|
1167
|
+
if (delegatedSpec.error) throw new Error(delegatedSpec.error);
|
|
1168
|
+
const wrapperDeclarations = Array.isArray(wrapperArgs)
|
|
1169
|
+
? wrapperArgs.map(String)
|
|
1170
|
+
: [];
|
|
1171
|
+
const wrapperSpec = Schema.parseToolArgDeclarationList(wrapperDeclarations);
|
|
1172
|
+
if (wrapperSpec.error) throw new Error(wrapperSpec.error);
|
|
1173
|
+
const declared = new Set(delegatedSpec.args);
|
|
1174
|
+
for (const arg of wrapperSpec.args) {
|
|
1175
|
+
if (!declared.has(arg))
|
|
1176
|
+
throw new Error(`Unknown delegated Recipe argument: ${arg}`);
|
|
1177
|
+
}
|
|
1178
|
+
Schema.assertCompatibleToolArgTypes(
|
|
1179
|
+
delegatedSpec.argTypes,
|
|
1180
|
+
wrapperSpec.argTypes,
|
|
1181
|
+
);
|
|
1182
|
+
assertRecipeDefaultsDeclared(
|
|
1183
|
+
delegated.args,
|
|
1184
|
+
wrapperDefaults,
|
|
1185
|
+
"delegated Recipe default",
|
|
1186
|
+
);
|
|
1187
|
+
Schema.normalizeRuntimeValues(
|
|
1188
|
+
Object.fromEntries(
|
|
1189
|
+
Object.entries(wrapperDefaults ?? {}).filter(
|
|
1190
|
+
([, value]) =>
|
|
1191
|
+
typeof value !== "string" || !/\{[^{}]+\}/.test(value),
|
|
1192
|
+
),
|
|
1193
|
+
),
|
|
1194
|
+
delegatedSpec.argTypes,
|
|
1195
|
+
);
|
|
1196
|
+
return delegated.args;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1071
1199
|
function expandImportNodes(
|
|
1072
1200
|
value: CommandTemplateValue,
|
|
1073
1201
|
imports: Record<string, ImportedRecipe>,
|
|
@@ -1157,7 +1285,10 @@ export function readResolvedRecipeConfig(
|
|
|
1157
1285
|
`Recipe import depth exceeds limit ${MAX_RECIPE_IMPORT_DEPTH}: ${[...stack, path].join(" -> ")}`,
|
|
1158
1286
|
);
|
|
1159
1287
|
}
|
|
1160
|
-
const raw =
|
|
1288
|
+
const raw =
|
|
1289
|
+
options.authoredEntry?.path === path
|
|
1290
|
+
? options.authoredEntry.recipe
|
|
1291
|
+
: readRawRecipeConfig(path);
|
|
1161
1292
|
if (!raw || !Object.hasOwn(raw, "template")) return undefined;
|
|
1162
1293
|
assertRecipeHasNoDeclaredName(raw);
|
|
1163
1294
|
RecipeControl.assertRecipeHasNoMailbox(raw);
|
|
@@ -1244,10 +1375,15 @@ export function readResolvedRecipeConfig(
|
|
|
1244
1375
|
role: stack.length > 0 ? "import" : "entry",
|
|
1245
1376
|
})
|
|
1246
1377
|
: expandedTemplate;
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
);
|
|
1378
|
+
const wrapperDefaults = isRecord(substituted.defaults)
|
|
1379
|
+
? substituted.defaults
|
|
1380
|
+
: undefined;
|
|
1381
|
+
const mergedDefaults = mergeDefaults(delegated?.defaults, wrapperDefaults);
|
|
1382
|
+
const effectiveArgs = delegated
|
|
1383
|
+
? resolveDirectDelegationArgs(delegated, substituted.args, wrapperDefaults)
|
|
1384
|
+
: Array.isArray(substituted.args)
|
|
1385
|
+
? (substituted.args as string[])
|
|
1386
|
+
: undefined;
|
|
1251
1387
|
const artifactSource = isRecord(substituted.artifacts)
|
|
1252
1388
|
? substituted.artifacts
|
|
1253
1389
|
: delegated?.artifacts;
|
|
@@ -1284,11 +1420,7 @@ export function readResolvedRecipeConfig(
|
|
|
1284
1420
|
? { imports: getRecipeImports(raw) }
|
|
1285
1421
|
: {}),
|
|
1286
1422
|
template: templateWithContext,
|
|
1287
|
-
...(
|
|
1288
|
-
? { args: substituted.args as string[] }
|
|
1289
|
-
: Array.isArray(delegated?.args)
|
|
1290
|
-
? { args: delegated.args }
|
|
1291
|
-
: {}),
|
|
1423
|
+
...(effectiveArgs ? { args: effectiveArgs } : {}),
|
|
1292
1424
|
...(mergedDefaults ? { defaults: mergedDefaults } : {}),
|
|
1293
1425
|
...(typeof substituted.parallel === "boolean"
|
|
1294
1426
|
? { parallel: substituted.parallel }
|