@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/dist/lib/registry.js
CHANGED
|
@@ -8,9 +8,11 @@ import { dirname, join, relative, resolve } from "node:path";
|
|
|
8
8
|
import * as CommandTemplates from "./command-templates.js";
|
|
9
9
|
import * as Config from "./config.js";
|
|
10
10
|
import * as ExecutionOutput from "./execution-output.js";
|
|
11
|
-
import { withFileMutationLock, writeJsonAtomic } from "./file-state.js";
|
|
11
|
+
import { withFileMutationLock, writeJsonAtomic, writeTextAtomic, } from "./file-state.js";
|
|
12
12
|
import * as Identity from "./identity.js";
|
|
13
13
|
import * as Paths from "./paths.js";
|
|
14
|
+
import * as RecipesContext from "./recipes-context.js";
|
|
15
|
+
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
14
16
|
import * as RecipesReferences from "./recipes-references.js";
|
|
15
17
|
import * as Schema from "./schema.js";
|
|
16
18
|
function textContent(text) {
|
|
@@ -33,6 +35,42 @@ function getRecipeRoot(deps) {
|
|
|
33
35
|
function getToolRecipePath(deps, name) {
|
|
34
36
|
return join(getRecipeRoot(deps), `${name}.json`);
|
|
35
37
|
}
|
|
38
|
+
function activationBoundary(activation) {
|
|
39
|
+
if (activation?.callable_now)
|
|
40
|
+
return "current_session";
|
|
41
|
+
if (!activation)
|
|
42
|
+
return "session_activation_unverified";
|
|
43
|
+
if (!activation.host_registered)
|
|
44
|
+
return "host_registration";
|
|
45
|
+
if (!activation.active_tool)
|
|
46
|
+
return "active_tool_set";
|
|
47
|
+
return "callability_verification";
|
|
48
|
+
}
|
|
49
|
+
function recipeDoctorAction(source) {
|
|
50
|
+
return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(source)
|
|
51
|
+
? `inspect target=recipes view=doctor identity=${source}`
|
|
52
|
+
: "inspect target=recipes view=doctor";
|
|
53
|
+
}
|
|
54
|
+
function registrationNextActions(tool, source, callableNow) {
|
|
55
|
+
if (callableNow) {
|
|
56
|
+
return [
|
|
57
|
+
`call tool ${tool}`,
|
|
58
|
+
`inspect target=tool:${tool} view=status`,
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
return [
|
|
62
|
+
`inspect target=tool:${tool} view=status`,
|
|
63
|
+
...(source === "template" || source === "draft"
|
|
64
|
+
? []
|
|
65
|
+
: [recipeDoctorAction(source)]),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
function activeSkillSummary(resolutionContext) {
|
|
69
|
+
const names = Object.keys(RecipesReferences.getActiveSkillRecipeNamespaces(resolutionContext.activeSkills))
|
|
70
|
+
.sort()
|
|
71
|
+
.slice(0, 20);
|
|
72
|
+
return names.length > 0 ? names.join(", ") : "<none>";
|
|
73
|
+
}
|
|
36
74
|
function assertDraftPath(deps, draft) {
|
|
37
75
|
const draftRoot = resolve(getRecipeRoot(deps), "drafts");
|
|
38
76
|
const path = resolve(draft);
|
|
@@ -58,48 +96,59 @@ function promoteDraftRecipe(name, input, ctx, deps) {
|
|
|
58
96
|
if ((existing || existsSync(targetPath)) && !input.update) {
|
|
59
97
|
throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
|
|
60
98
|
}
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
99
|
+
const raw = RecipesReferences.readRawRecipeConfig(draftPath);
|
|
100
|
+
if (!raw) {
|
|
63
101
|
const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
|
|
64
102
|
throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid${reason ? `: ${reason}` : "."}`));
|
|
65
103
|
}
|
|
66
|
-
const
|
|
104
|
+
const authoredRecipe = buildAuthoredRecipe({
|
|
67
105
|
...input,
|
|
68
|
-
description: input.description ??
|
|
69
|
-
|
|
70
|
-
}, existing);
|
|
71
|
-
const
|
|
106
|
+
description: input.description ??
|
|
107
|
+
(typeof raw.description === "string" ? raw.description : undefined),
|
|
108
|
+
}, existing, raw);
|
|
109
|
+
const resolutionContext = getRegistrationResolutionContext(ctx, deps);
|
|
110
|
+
const candidate = RecipesDiscovery.admitUserRecipe(targetPath, resolutionContext, authoredRecipe);
|
|
111
|
+
if (!candidate.validated || !candidate.tool) {
|
|
112
|
+
throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid: ${candidate.diagnostics.join("; ")}`));
|
|
113
|
+
}
|
|
72
114
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
73
|
-
writeJsonAtomic(targetPath,
|
|
74
|
-
promoted
|
|
75
|
-
promoted.sourcePath = targetPath;
|
|
115
|
+
writeJsonAtomic(targetPath, authoredRecipe);
|
|
116
|
+
const promoted = RecipesDiscovery.admitUserRecipe(targetPath, resolutionContext).tool;
|
|
76
117
|
tools.set(name, promoted);
|
|
77
|
-
deps.registerRuntimeTool(promoted);
|
|
118
|
+
const activation = deps.registerRuntimeTool(promoted) ?? undefined;
|
|
119
|
+
const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(promoted);
|
|
120
|
+
const callableNow = activation?.callable_now ?? false;
|
|
121
|
+
const nextActions = registrationNextActions(name, "draft", callableNow);
|
|
78
122
|
deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
|
|
79
123
|
return {
|
|
80
124
|
content: [
|
|
81
|
-
textContent(ExecutionOutput.formatToolText(`${existing ? "Updated" : "Registered"} tool "${name}" from draft
|
|
125
|
+
textContent(ExecutionOutput.formatToolText(`${existing ? "Updated" : "Registered"} tool "${name}" from draft; callable_now=${callableNow}; next=${nextActions[0]}.`)),
|
|
82
126
|
],
|
|
83
127
|
details: {
|
|
128
|
+
active_tool: activation?.active_tool ?? false,
|
|
129
|
+
activation: activation?.activation ?? "unverified",
|
|
130
|
+
activation_boundary: activationBoundary(activation),
|
|
84
131
|
args: promoted.args,
|
|
85
|
-
|
|
86
|
-
|
|
132
|
+
callable_now: callableNow,
|
|
133
|
+
defaults: promoted.defaults,
|
|
134
|
+
host_registered: activation?.host_registered ?? false,
|
|
135
|
+
next_actions: nextActions,
|
|
136
|
+
optional_args: argSummary.optional,
|
|
137
|
+
persisted: true,
|
|
87
138
|
promoted: true,
|
|
139
|
+
registry_active: tools.get(name) === promoted,
|
|
140
|
+
required_args: argSummary.required,
|
|
141
|
+
resolved: true,
|
|
142
|
+
source: "draft",
|
|
88
143
|
tool: name,
|
|
144
|
+
validated: true,
|
|
89
145
|
},
|
|
90
146
|
};
|
|
91
147
|
}
|
|
92
|
-
function persistToolRecipe(deps,
|
|
93
|
-
const path = getToolRecipePath(deps,
|
|
148
|
+
function persistToolRecipe(deps, name, recipe) {
|
|
149
|
+
const path = getToolRecipePath(deps, name);
|
|
94
150
|
mkdirSync(dirname(path), { recursive: true });
|
|
95
|
-
writeJsonAtomic(path,
|
|
96
|
-
description: cfg.description,
|
|
97
|
-
...(cfg.recipe?.async !== undefined ? { async: cfg.recipe.async } : {}),
|
|
98
|
-
...(cfg.storedArgs ? { args: cfg.storedArgs } : {}),
|
|
99
|
-
...(cfg.storedDefaults ? { defaults: cfg.storedDefaults } : {}),
|
|
100
|
-
...(cfg.recipe?.values ? { values: cfg.recipe.values } : {}),
|
|
101
|
-
template: cfg.template,
|
|
102
|
-
});
|
|
151
|
+
writeJsonAtomic(path, recipe);
|
|
103
152
|
return path;
|
|
104
153
|
}
|
|
105
154
|
function deleteTool(name, ctx, deps) {
|
|
@@ -122,7 +171,7 @@ function deleteTool(name, ctx, deps) {
|
|
|
122
171
|
content: [
|
|
123
172
|
textContent(ExecutionOutput.formatToolText(`Deleted tool "${name}". Reload to remove it from the complete registry.`)),
|
|
124
173
|
],
|
|
125
|
-
details: {
|
|
174
|
+
details: { tool: name },
|
|
126
175
|
};
|
|
127
176
|
}
|
|
128
177
|
function readAuthoritativeStoredTool(deps, name) {
|
|
@@ -130,11 +179,15 @@ function readAuthoritativeStoredTool(deps, name) {
|
|
|
130
179
|
if (!existsSync(path))
|
|
131
180
|
return { exists: false };
|
|
132
181
|
try {
|
|
133
|
-
const
|
|
182
|
+
const bytes = readFileSync(path, "utf8");
|
|
183
|
+
const recipe = JSON.parse(bytes);
|
|
184
|
+
const normalized = Config.normalizeStoredTool(name, recipe, deps.reservedToolNames);
|
|
134
185
|
if (!normalized.cfg)
|
|
135
|
-
return { exists: true };
|
|
186
|
+
return { bytes, exists: true, recipe };
|
|
136
187
|
return {
|
|
188
|
+
bytes,
|
|
137
189
|
exists: true,
|
|
190
|
+
recipe,
|
|
138
191
|
tool: { ...normalized.cfg, sourcePath: path },
|
|
139
192
|
};
|
|
140
193
|
}
|
|
@@ -142,7 +195,25 @@ function readAuthoritativeStoredTool(deps, name) {
|
|
|
142
195
|
return { exists: true };
|
|
143
196
|
}
|
|
144
197
|
}
|
|
198
|
+
function looksLikeRecipeReference(value) {
|
|
199
|
+
const trimmed = value.trim();
|
|
200
|
+
return (trimmed.endsWith(".json") ||
|
|
201
|
+
trimmed.endsWith(".md") ||
|
|
202
|
+
/^[^/:\\]+\/[^/:\\]+$/.test(trimmed));
|
|
203
|
+
}
|
|
204
|
+
function assertTemplateIsNotNestedRecipe(value) {
|
|
205
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
206
|
+
return;
|
|
207
|
+
const record = value;
|
|
208
|
+
if (typeof record.template === "string" &&
|
|
209
|
+
looksLikeRecipeReference(record.template) &&
|
|
210
|
+
(Object.keys(record).length === 1 ||
|
|
211
|
+
["artifacts", "async", "control", "description", "imports", "values"].some((key) => Object.hasOwn(record, key)))) {
|
|
212
|
+
throw new Error(ExecutionOutput.formatToolText("Nested Recipe-shaped templates are not a register_tool source mode. Next: retry with from=<skill>/<recipe> and defaults={...}."));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
145
215
|
function getInputTemplate(value) {
|
|
216
|
+
assertTemplateIsNotNestedRecipe(value);
|
|
146
217
|
if (typeof value === "string")
|
|
147
218
|
return value.trim();
|
|
148
219
|
if (value === null || value === undefined)
|
|
@@ -161,80 +232,132 @@ function getInputTemplate(value) {
|
|
|
161
232
|
}
|
|
162
233
|
throw new Error(ExecutionOutput.formatToolText("Tool template must be a string, object, or sequence."));
|
|
163
234
|
}
|
|
164
|
-
function
|
|
235
|
+
function getRegistrationResolutionContext(ctx, deps) {
|
|
236
|
+
if (ctx &&
|
|
237
|
+
typeof ctx === "object" &&
|
|
238
|
+
"recipeResolutionContext" in ctx) {
|
|
239
|
+
const resolutionContext = ctx.recipeResolutionContext;
|
|
240
|
+
if (resolutionContext)
|
|
241
|
+
return resolutionContext;
|
|
242
|
+
}
|
|
243
|
+
return RecipesContext.createEmptyRecipeResolutionContext("offline-register-tool", getRecipeRoot(deps));
|
|
244
|
+
}
|
|
245
|
+
function normalizeDefaults(value) {
|
|
246
|
+
if (value === undefined)
|
|
247
|
+
return undefined;
|
|
248
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
249
|
+
throw new Error(ExecutionOutput.formatToolText("Tool defaults must be an object."));
|
|
250
|
+
}
|
|
251
|
+
return { ...value };
|
|
252
|
+
}
|
|
253
|
+
function normalizeFromReference(value, resolutionContext) {
|
|
254
|
+
const trimmed = value.trim();
|
|
255
|
+
if (!trimmed) {
|
|
256
|
+
throw new Error(ExecutionOutput.formatToolText("Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path."));
|
|
257
|
+
}
|
|
258
|
+
if (trimmed.startsWith("std:")) {
|
|
259
|
+
throw new Error(ExecutionOutput.formatToolText("std: Recipe references were removed; use <skill>/<recipe> or an explicit .json/.md file path."));
|
|
260
|
+
}
|
|
261
|
+
if (trimmed.startsWith("skill:")) {
|
|
262
|
+
throw new Error(ExecutionOutput.formatToolText(`skill: Recipe references were removed; use ${trimmed.slice("skill:".length)} without a prefix.`));
|
|
263
|
+
}
|
|
264
|
+
if (trimmed.endsWith(".json") || trimmed.endsWith(".md")) {
|
|
265
|
+
return resolve(resolutionContext.cwd, trimmed);
|
|
266
|
+
}
|
|
267
|
+
if (!looksLikeRecipeReference(trimmed)) {
|
|
268
|
+
throw new Error(ExecutionOutput.formatToolText("Tool from must use <skill>/<recipe> or an explicit .json/.md file path."));
|
|
269
|
+
}
|
|
270
|
+
return trimmed;
|
|
271
|
+
}
|
|
272
|
+
function buildAuthoredRecipe(input, existing, existingRecipe) {
|
|
165
273
|
const explicitArgs = input.args === undefined
|
|
166
274
|
? undefined
|
|
167
275
|
: Schema.parseToolArgDeclarations(input.args);
|
|
168
276
|
if (explicitArgs?.error)
|
|
169
277
|
throw new Error(ExecutionOutput.formatToolText(explicitArgs.error));
|
|
278
|
+
const from = typeof input.from === "string" ? input.from.trim() : "";
|
|
170
279
|
const description = (input.description ?? existing?.description ?? "").trim();
|
|
171
|
-
if (!description) {
|
|
172
|
-
throw new Error(ExecutionOutput.formatToolText("Tool description is required
|
|
280
|
+
if (!description && !from) {
|
|
281
|
+
throw new Error(ExecutionOutput.formatToolText("Tool description is required for command-template registration."));
|
|
282
|
+
}
|
|
283
|
+
const inputDefaults = normalizeDefaults(input.defaults);
|
|
284
|
+
if (from) {
|
|
285
|
+
const runtimeOwnedDefault = Object.keys(inputDefaults ?? {}).find((key) => RecipesReferences.isRuntimeOwnedRecipeInput(key));
|
|
286
|
+
if (runtimeOwnedDefault) {
|
|
287
|
+
throw new Error(ExecutionOutput.formatToolText(`Tool defaults cannot set runtime-owned input: ${runtimeOwnedDefault}. Next: remove that default and retry register_tool; the runtime supplies it.`));
|
|
288
|
+
}
|
|
289
|
+
const authored = {
|
|
290
|
+
...(description ? { description } : {}),
|
|
291
|
+
template: from,
|
|
292
|
+
};
|
|
293
|
+
const retainedDefaults = inputDefaults ??
|
|
294
|
+
(existingRecipe?.defaults && typeof existingRecipe.defaults === "object"
|
|
295
|
+
? existingRecipe.defaults
|
|
296
|
+
: undefined);
|
|
297
|
+
if (retainedDefaults && Object.keys(retainedDefaults).length > 0)
|
|
298
|
+
authored.defaults = retainedDefaults;
|
|
299
|
+
return authored;
|
|
173
300
|
}
|
|
174
301
|
const template = getInputTemplate(input.template);
|
|
175
302
|
if (template === null) {
|
|
176
303
|
throw new Error(ExecutionOutput.formatToolText("Tool template cannot be null here."));
|
|
177
304
|
}
|
|
178
|
-
const finalTemplate = template === undefined || template === ""
|
|
305
|
+
const finalTemplate = template === undefined || template === ""
|
|
306
|
+
? existingRecipe?.template ?? existing?.template
|
|
307
|
+
: template;
|
|
179
308
|
if (!finalTemplate) {
|
|
180
309
|
throw new Error(ExecutionOutput.formatToolText("Tool template is required."));
|
|
181
310
|
}
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
? {
|
|
185
|
-
name: inputRecipe,
|
|
186
|
-
...(typeof input.async === "boolean" ? { async: input.async } : {}),
|
|
187
|
-
template: finalTemplate,
|
|
188
|
-
...(input.values && typeof input.values === "object"
|
|
189
|
-
? { values: input.values }
|
|
190
|
-
: {}),
|
|
191
|
-
}
|
|
192
|
-
: template === undefined
|
|
193
|
-
? existing?.recipe
|
|
194
|
-
: undefined;
|
|
195
|
-
const defaults = explicitArgs?.defaults ?? existing?.storedDefaults ?? {};
|
|
196
|
-
const storedArgs = explicitArgs
|
|
197
|
-
? explicitArgs.declarations
|
|
198
|
-
: existing?.storedArgs;
|
|
199
|
-
const storedDefaults = Object.keys(defaults).length > 0 ? defaults : undefined;
|
|
200
|
-
const recipeTemplate = RecipesReferences.getRecipeTemplate(finalTemplate);
|
|
201
|
-
const argTemplate = recipeTemplate ?? finalTemplate;
|
|
202
|
-
const argTemplateConfig = typeof argTemplate === "object" && !Array.isArray(argTemplate)
|
|
203
|
-
? {
|
|
204
|
-
...argTemplate,
|
|
205
|
-
...(storedArgs !== undefined ? { args: storedArgs } : {}),
|
|
206
|
-
defaults: { ...(argTemplate.defaults ?? {}), ...defaults },
|
|
207
|
-
}
|
|
208
|
-
: {
|
|
209
|
-
args: storedArgs,
|
|
210
|
-
defaults,
|
|
211
|
-
template: argTemplate,
|
|
212
|
-
};
|
|
213
|
-
const inferredArgTypes = Schema.getTemplateArgTypes(argTemplateConfig);
|
|
214
|
-
const argTypes = {
|
|
215
|
-
...inferredArgTypes,
|
|
216
|
-
...(existing?.argTypes ?? {}),
|
|
217
|
-
...(explicitArgs?.argTypes ?? {}),
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
name,
|
|
311
|
+
const authored = {
|
|
312
|
+
...(existingRecipe ?? {}),
|
|
221
313
|
description,
|
|
222
314
|
template: finalTemplate,
|
|
223
|
-
...(recipe ? { recipe } : {}),
|
|
224
|
-
args: RecipesReferences.isRecipeTool(finalTemplate, recipe) &&
|
|
225
|
-
storedArgs !== undefined
|
|
226
|
-
? Schema.getExplicitToolArgNames(storedArgs)
|
|
227
|
-
: RecipesReferences.isRecipeReference(finalTemplate) && !recipeTemplate
|
|
228
|
-
? Schema.getExplicitToolArgNames(storedArgs)
|
|
229
|
-
: Schema.getToolArgNames(argTemplateConfig),
|
|
230
|
-
defaults,
|
|
231
|
-
...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
|
|
232
|
-
...(storedArgs !== undefined ? { storedArgs } : {}),
|
|
233
|
-
...(storedDefaults !== undefined ? { storedDefaults } : {}),
|
|
234
315
|
};
|
|
316
|
+
if (typeof input.async === "boolean")
|
|
317
|
+
authored.async = input.async;
|
|
318
|
+
if (explicitArgs) {
|
|
319
|
+
authored.args = explicitArgs.declarations;
|
|
320
|
+
const defaults = { ...explicitArgs.defaults, ...(inputDefaults ?? {}) };
|
|
321
|
+
if (Object.keys(defaults).length > 0)
|
|
322
|
+
authored.defaults = defaults;
|
|
323
|
+
else
|
|
324
|
+
delete authored.defaults;
|
|
325
|
+
}
|
|
326
|
+
else if (inputDefaults !== undefined) {
|
|
327
|
+
if (Object.keys(inputDefaults).length > 0)
|
|
328
|
+
authored.defaults = inputDefaults;
|
|
329
|
+
else
|
|
330
|
+
delete authored.defaults;
|
|
331
|
+
}
|
|
332
|
+
return authored;
|
|
333
|
+
}
|
|
334
|
+
function assertRegisterToolInputModes(input) {
|
|
335
|
+
const record = input;
|
|
336
|
+
if (Object.hasOwn(record, "values")) {
|
|
337
|
+
throw new Error(ExecutionOutput.formatToolText("register_tool values was removed. Next: retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition."));
|
|
338
|
+
}
|
|
339
|
+
const fromProvided = Object.hasOwn(record, "from");
|
|
340
|
+
if (fromProvided &&
|
|
341
|
+
(typeof input.from !== "string" || input.from.trim().length === 0)) {
|
|
342
|
+
throw new Error(ExecutionOutput.formatToolText("Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path."));
|
|
343
|
+
}
|
|
344
|
+
const draftProvided = typeof input.draft === "string" && input.draft.trim().length > 0;
|
|
345
|
+
const templateProvided = Object.hasOwn(record, "template");
|
|
346
|
+
const modeCount = Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
|
|
347
|
+
if (modeCount > 1) {
|
|
348
|
+
throw new Error(ExecutionOutput.formatToolText("Use exactly one register_tool source mode: from, template, or draft. Next: remove the extra source fields and retry register_tool."));
|
|
349
|
+
}
|
|
350
|
+
if (fromProvided &&
|
|
351
|
+
(Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))) {
|
|
352
|
+
throw new Error(ExecutionOutput.formatToolText("register_tool from inherits args and async behavior. Next: retry register_tool with from and optional defaults only; omit args and async."));
|
|
353
|
+
}
|
|
235
354
|
}
|
|
236
355
|
function executeRegisterToolUnlocked(params, ctx, deps) {
|
|
237
|
-
const
|
|
356
|
+
const rawInput = params && typeof params === "object"
|
|
357
|
+
? params
|
|
358
|
+
: {};
|
|
359
|
+
assertRegisterToolInputModes(rawInput);
|
|
360
|
+
let input = rawInput;
|
|
238
361
|
if (!input.name)
|
|
239
362
|
return listTools(deps);
|
|
240
363
|
const name = Identity.normalizeToolName(input.name);
|
|
@@ -246,6 +369,23 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
|
|
|
246
369
|
if (typeof input.draft === "string" && input.draft.trim()) {
|
|
247
370
|
return promoteDraftRecipe(name, input, ctx, deps);
|
|
248
371
|
}
|
|
372
|
+
const resolutionContext = getRegistrationResolutionContext(ctx, deps);
|
|
373
|
+
const requestedSource = typeof rawInput.from === "string" ? rawInput.from.trim() : undefined;
|
|
374
|
+
if (typeof input.from === "string") {
|
|
375
|
+
try {
|
|
376
|
+
input = {
|
|
377
|
+
...input,
|
|
378
|
+
from: normalizeFromReference(input.from, resolutionContext),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
const reason = error instanceof Error ? error.message.trim() : String(error);
|
|
383
|
+
const diagnosticSource = requestedSource?.startsWith("skill:")
|
|
384
|
+
? requestedSource.slice("skill:".length)
|
|
385
|
+
: requestedSource ?? "";
|
|
386
|
+
throw new Error(ExecutionOutput.formatToolText(`Recipe source "${requestedSource}" is invalid: ${reason}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(diagnosticSource)}`));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
249
389
|
const templateProvided = Object.hasOwn(input, "template");
|
|
250
390
|
const template = getInputTemplate(input.template);
|
|
251
391
|
if (templateProvided && (template === null || template === ""))
|
|
@@ -259,40 +399,93 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
|
|
|
259
399
|
if ((authoritative.exists || existing) && !input.update) {
|
|
260
400
|
throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
|
|
261
401
|
}
|
|
262
|
-
if (template === undefined && !existing) {
|
|
263
|
-
throw new Error(ExecutionOutput.formatToolText("
|
|
402
|
+
if (template === undefined && !input.from && !existing) {
|
|
403
|
+
throw new Error(ExecutionOutput.formatToolText("New registration requires exactly one source mode: from, template, or draft. Next: retry register_tool with one source field."));
|
|
404
|
+
}
|
|
405
|
+
const recipePath = getToolRecipePath(deps, name);
|
|
406
|
+
const authoredRecipe = buildAuthoredRecipe(input, existing, authoritative.recipe);
|
|
407
|
+
const candidate = RecipesDiscovery.admitUserRecipe(recipePath, resolutionContext, authoredRecipe);
|
|
408
|
+
if (!candidate.validated || !candidate.tool) {
|
|
409
|
+
const sourceDiagnostic = requestedSource
|
|
410
|
+
? `Recipe source "${requestedSource}" validation failed: ${candidate.diagnostics.join("; ")}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(requestedSource)}`
|
|
411
|
+
: `Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`;
|
|
412
|
+
throw new Error(ExecutionOutput.formatToolText(sourceDiagnostic));
|
|
264
413
|
}
|
|
265
|
-
const
|
|
266
|
-
let
|
|
414
|
+
const activeBefore = deps.getActiveTools();
|
|
415
|
+
let persisted = false;
|
|
416
|
+
let activation;
|
|
417
|
+
let cfg;
|
|
267
418
|
try {
|
|
268
|
-
|
|
419
|
+
persistToolRecipe(deps, name, authoredRecipe);
|
|
420
|
+
persisted = true;
|
|
421
|
+
const admitted = RecipesDiscovery.admitUserRecipe(recipePath, resolutionContext);
|
|
422
|
+
if (!admitted.validated || !admitted.tool) {
|
|
423
|
+
throw new Error(`Persisted tool recipe admission failed: ${admitted.diagnostics.join("; ")}`);
|
|
424
|
+
}
|
|
425
|
+
cfg = admitted.tool;
|
|
426
|
+
tools.set(name, cfg);
|
|
427
|
+
activation = deps.registerRuntimeTool(cfg) ?? undefined;
|
|
428
|
+
if (activation &&
|
|
429
|
+
(!activation.host_registered ||
|
|
430
|
+
!activation.active_tool ||
|
|
431
|
+
!activation.callable_now)) {
|
|
432
|
+
throw new Error(`Tool "${name}" activation failed at ${activationBoundary(activation)}; host_registered=${activation.host_registered}, active_tool=${activation.active_tool}, callable_now=${activation.callable_now}. Registration was rolled back. Next: inspect target=runtime view=status; do not use spawn as proof of tool invocation.`);
|
|
433
|
+
}
|
|
269
434
|
}
|
|
270
435
|
catch (error) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
436
|
+
if (persisted) {
|
|
437
|
+
if (authoritative.bytes !== undefined)
|
|
438
|
+
writeTextAtomic(recipePath, authoritative.bytes);
|
|
439
|
+
else if (existsSync(recipePath))
|
|
440
|
+
unlinkSync(recipePath);
|
|
441
|
+
}
|
|
442
|
+
if (existing) {
|
|
443
|
+
tools.set(name, existing);
|
|
444
|
+
deps.registerRuntimeTool(existing);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
tools.delete(name);
|
|
448
|
+
}
|
|
449
|
+
deps.setActiveTools(activeBefore);
|
|
450
|
+
throw new Error(ExecutionOutput.formatToolText(`Tool registration transaction failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
451
|
+
}
|
|
452
|
+
deps.notify(ctx, `Tool activated: ${name}`, "info");
|
|
453
|
+
const templateWarnings = CommandTemplates.getCommandTemplateWarnings(typeof cfg.recipe?.template === "object" && !Array.isArray(cfg.recipe.template)
|
|
454
|
+
? cfg.recipe.template
|
|
455
|
+
: { template: cfg.recipe?.template ?? cfg.template });
|
|
280
456
|
const warningText = templateWarnings.length > 0
|
|
281
457
|
? `\nWarnings:\n${templateWarnings.map((warning) => `- ${warning}`).join("\n")}`
|
|
282
458
|
: "";
|
|
459
|
+
const callableNow = activation?.callable_now ?? false;
|
|
460
|
+
const source = requestedSource ?? "template";
|
|
461
|
+
const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
|
|
462
|
+
const nextActions = registrationNextActions(name, source, callableNow);
|
|
463
|
+
const outcomeText = callableNow
|
|
464
|
+
? `${existing ? "Updated" : "Registered"} tool "${name}" from ${source}; callable_now=true`
|
|
465
|
+
: `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${activationBoundary(activation)}`;
|
|
283
466
|
return {
|
|
284
467
|
content: [
|
|
285
|
-
textContent(ExecutionOutput.formatToolText(`${
|
|
468
|
+
textContent(ExecutionOutput.formatToolText(`${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`)),
|
|
286
469
|
],
|
|
287
470
|
details: {
|
|
471
|
+
active_tool: activation?.active_tool ?? false,
|
|
472
|
+
activation: activation?.activation ?? "unverified",
|
|
473
|
+
activation_boundary: activationBoundary(activation),
|
|
288
474
|
args: cfg.args,
|
|
289
|
-
|
|
475
|
+
callable_now: callableNow,
|
|
290
476
|
defaults: cfg.defaults,
|
|
477
|
+
host_registered: activation?.host_registered ?? false,
|
|
478
|
+
next_actions: nextActions,
|
|
479
|
+
optional_args: argSummary.optional,
|
|
480
|
+
persisted: true,
|
|
481
|
+
registry_active: tools.get(name) === cfg,
|
|
482
|
+
required_args: argSummary.required,
|
|
483
|
+
resolved: true,
|
|
484
|
+
source,
|
|
291
485
|
...(cfg.recipe?.async !== undefined ? { async: cfg.recipe.async } : {}),
|
|
292
|
-
...(cfg.recipe?.name ? { recipeName: cfg.recipe.name } : {}),
|
|
293
|
-
...(cfg.template ? { template: cfg.template } : {}),
|
|
294
486
|
...(templateWarnings.length > 0 ? { templateWarnings } : {}),
|
|
295
487
|
tool: name,
|
|
488
|
+
validated: true,
|
|
296
489
|
},
|
|
297
490
|
};
|
|
298
491
|
}
|
package/dist/lib/runtime.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { watch } from "node:fs";
|
|
7
7
|
import * as Config from "./config.ts";
|
|
8
8
|
import type { RegisteredToolExec } from "./execution.ts";
|
|
9
|
+
import type { RecipeResolutionContext } from "./recipes-context.ts";
|
|
9
10
|
import * as ToolsLocal from "./tools-local.ts";
|
|
10
11
|
export interface RuntimeContext {
|
|
11
12
|
hasUI: boolean;
|
|
@@ -13,21 +14,46 @@ export interface RuntimeContext {
|
|
|
13
14
|
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
14
15
|
};
|
|
15
16
|
}
|
|
17
|
+
export interface RuntimeToolActivation {
|
|
18
|
+
active_tool: boolean;
|
|
19
|
+
activation: "current_session" | "unverified";
|
|
20
|
+
callable_now: boolean;
|
|
21
|
+
host_registered: boolean;
|
|
22
|
+
}
|
|
16
23
|
export interface ToolRegistryRuntimeDeps {
|
|
17
24
|
configPath: string;
|
|
18
25
|
exec: RegisteredToolExec;
|
|
19
26
|
recipeRoot?: string;
|
|
20
27
|
getActiveTools?: () => string[];
|
|
28
|
+
getAllTools?: () => Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
}>;
|
|
21
31
|
registerTool: (definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>) => void;
|
|
22
32
|
reservedToolNames: Set<string>;
|
|
23
33
|
setActiveTools?: (toolNames: string[]) => void;
|
|
24
34
|
}
|
|
35
|
+
export interface RecipeRegistryStatus {
|
|
36
|
+
active_tool_count: number;
|
|
37
|
+
last_scan_counts: {
|
|
38
|
+
active: number;
|
|
39
|
+
rejected: number;
|
|
40
|
+
scanned: number;
|
|
41
|
+
};
|
|
42
|
+
last_scan_at?: string;
|
|
43
|
+
registry_generation: number;
|
|
44
|
+
resolution_generation?: string;
|
|
45
|
+
watch_status: "closed" | "failed" | "watching_parent" | "watching_root";
|
|
46
|
+
watched_root: string;
|
|
47
|
+
}
|
|
25
48
|
export interface ToolRegistryRuntime {
|
|
49
|
+
getStatus(): RecipeRegistryStatus;
|
|
50
|
+
getToolStatus(name: string): Record<string, unknown> | undefined;
|
|
26
51
|
getToolNameBlocker(name: string): string | undefined;
|
|
27
52
|
getTools(): Map<string, Config.RegisteredTool>;
|
|
28
|
-
loadTools(ctx: RuntimeContext): void;
|
|
53
|
+
loadTools(ctx: RuntimeContext, resolutionContext?: RecipeResolutionContext): void;
|
|
29
54
|
notify(ctx: RuntimeContext, message: string, type: "info" | "warning" | "error"): void;
|
|
30
|
-
registerRuntimeTool(cfg: Config.RegisteredTool):
|
|
55
|
+
registerRuntimeTool(cfg: Config.RegisteredTool): RuntimeToolActivation;
|
|
56
|
+
setWatchStatus(status: RecipeRegistryStatus["watch_status"]): void;
|
|
31
57
|
}
|
|
32
58
|
export interface RecipeToolReloadWatcher {
|
|
33
59
|
close(): void;
|
|
@@ -36,7 +62,8 @@ export interface RecipeToolReloadWatcher {
|
|
|
36
62
|
export declare function createAutoToolsRuntime(deps: ToolRegistryRuntimeDeps): ToolRegistryRuntime;
|
|
37
63
|
export interface RecipeToolReloadWatcherDeps {
|
|
38
64
|
exists?: (path: string) => boolean;
|
|
65
|
+
getResolutionContext?: () => RecipeResolutionContext | undefined;
|
|
39
66
|
recipeRoot?: string;
|
|
40
67
|
watchPath?: typeof watch;
|
|
41
68
|
}
|
|
42
|
-
export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools"
|
|
69
|
+
export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools"> & Partial<Pick<ToolRegistryRuntime, "setWatchStatus">>, deps?: RecipeToolReloadWatcherDeps): RecipeToolReloadWatcher;
|