@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/runtime.js
CHANGED
|
@@ -7,11 +7,21 @@ import { existsSync, watch } from "node:fs";
|
|
|
7
7
|
import { basename, dirname } from "node:path";
|
|
8
8
|
import * as Paths from "./paths.js";
|
|
9
9
|
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
10
|
+
import * as RecipesReferences from "./recipes-references.js";
|
|
11
|
+
import * as RecipesUsage from "./recipes-usage.js";
|
|
10
12
|
import * as ToolsLocal from "./tools-local.js";
|
|
11
13
|
export function createAutoToolsRuntime(deps) {
|
|
12
14
|
const tools = new Map();
|
|
13
15
|
const runtimeToolFingerprints = new Map();
|
|
14
16
|
const runtimeTools = new Set();
|
|
17
|
+
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
18
|
+
const status = {
|
|
19
|
+
active_tool_count: 0,
|
|
20
|
+
last_scan_counts: { active: 0, rejected: 0, scanned: 0 },
|
|
21
|
+
registry_generation: 0,
|
|
22
|
+
watch_status: "closed",
|
|
23
|
+
watched_root: recipeRoot,
|
|
24
|
+
};
|
|
15
25
|
function notify(ctx, message, type) {
|
|
16
26
|
if (ctx.hasUI)
|
|
17
27
|
ctx.ui.notify(message, type);
|
|
@@ -31,6 +41,21 @@ export function createAutoToolsRuntime(deps) {
|
|
|
31
41
|
template: cfg.template,
|
|
32
42
|
});
|
|
33
43
|
}
|
|
44
|
+
function registeredToolSource(cfg) {
|
|
45
|
+
const raw = cfg.sourcePath
|
|
46
|
+
? RecipesReferences.readRawRecipeConfig(cfg.sourcePath)
|
|
47
|
+
: undefined;
|
|
48
|
+
const template = raw?.template;
|
|
49
|
+
if (typeof template === "string" &&
|
|
50
|
+
/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(template)) {
|
|
51
|
+
return template;
|
|
52
|
+
}
|
|
53
|
+
if (typeof template === "string" &&
|
|
54
|
+
(template.endsWith(".json") || template.endsWith(".md"))) {
|
|
55
|
+
return `<explicit-file:${basename(template)}>`;
|
|
56
|
+
}
|
|
57
|
+
return "template";
|
|
58
|
+
}
|
|
34
59
|
function deactivateMissingRuntimeTools(activeNames) {
|
|
35
60
|
const stale = [...runtimeTools].filter((name) => !activeNames.has(name));
|
|
36
61
|
if (stale.length === 0)
|
|
@@ -44,13 +69,29 @@ export function createAutoToolsRuntime(deps) {
|
|
|
44
69
|
const staleSet = new Set(stale);
|
|
45
70
|
deps.setActiveTools(deps.getActiveTools().filter((name) => !staleSet.has(name)));
|
|
46
71
|
}
|
|
72
|
+
function activationFor(name) {
|
|
73
|
+
const hostRegistered = deps.getAllTools?.().some((tool) => tool.name === name) ?? false;
|
|
74
|
+
const activeTool = deps.getActiveTools?.().includes(name) ?? false;
|
|
75
|
+
return {
|
|
76
|
+
active_tool: activeTool,
|
|
77
|
+
activation: hostRegistered && activeTool ? "current_session" : "unverified",
|
|
78
|
+
callable_now: hostRegistered && activeTool,
|
|
79
|
+
host_registered: hostRegistered,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
47
82
|
function registerRuntimeTool(cfg) {
|
|
48
83
|
const fingerprint = getToolFingerprint(cfg);
|
|
49
|
-
if (runtimeToolFingerprints.get(cfg.name)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
if (runtimeToolFingerprints.get(cfg.name) !== fingerprint) {
|
|
85
|
+
deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
|
|
86
|
+
runtimeTools.add(cfg.name);
|
|
87
|
+
runtimeToolFingerprints.set(cfg.name, fingerprint);
|
|
88
|
+
}
|
|
89
|
+
if (deps.getActiveTools && deps.setActiveTools) {
|
|
90
|
+
deps.setActiveTools([
|
|
91
|
+
...new Set([...deps.getActiveTools(), cfg.name]),
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
return activationFor(cfg.name);
|
|
54
95
|
}
|
|
55
96
|
function isStartupActionableRegistryWarning(warning) {
|
|
56
97
|
if (warning.includes(" shadows "))
|
|
@@ -80,13 +121,18 @@ export function createAutoToolsRuntime(deps) {
|
|
|
80
121
|
lines.push("Other registry diagnostics:");
|
|
81
122
|
lines.push(...other.map((warning) => `• ${warning}`));
|
|
82
123
|
}
|
|
124
|
+
lines.push("Next: inspect target=recipes view=doctor");
|
|
83
125
|
return `${lines.join("\n")}\n`;
|
|
84
126
|
}
|
|
85
|
-
function loadTools(ctx) {
|
|
127
|
+
function loadTools(ctx, resolutionContext) {
|
|
86
128
|
const warnings = [];
|
|
87
|
-
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
88
129
|
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
89
|
-
{
|
|
130
|
+
{
|
|
131
|
+
root: recipeRoot,
|
|
132
|
+
defaultTool: true,
|
|
133
|
+
mutableUsage: true,
|
|
134
|
+
resolutionContext,
|
|
135
|
+
},
|
|
90
136
|
]);
|
|
91
137
|
warnings.push(...discovered.diagnostics);
|
|
92
138
|
tools.clear();
|
|
@@ -109,17 +155,65 @@ export function createAutoToolsRuntime(deps) {
|
|
|
109
155
|
}
|
|
110
156
|
registerRuntimeTool(cfg);
|
|
111
157
|
}
|
|
158
|
+
status.active_tool_count = tools.size;
|
|
159
|
+
status.last_scan_at = new Date().toISOString();
|
|
160
|
+
status.last_scan_counts = {
|
|
161
|
+
active: tools.size,
|
|
162
|
+
rejected: discovered.entries.filter((entry) => entry.invalid).length,
|
|
163
|
+
scanned: discovered.entries.length,
|
|
164
|
+
};
|
|
165
|
+
status.registry_generation += 1;
|
|
166
|
+
status.resolution_generation = resolutionContext?.generation;
|
|
112
167
|
const startupWarnings = warnings.filter(isStartupActionableRegistryWarning);
|
|
113
168
|
if (startupWarnings.length > 0) {
|
|
114
169
|
notify(ctx, formatRecipeToolWarnings(startupWarnings), "warning");
|
|
115
170
|
}
|
|
116
171
|
}
|
|
117
172
|
return {
|
|
173
|
+
getStatus: () => ({
|
|
174
|
+
...status,
|
|
175
|
+
last_scan_counts: { ...status.last_scan_counts },
|
|
176
|
+
}),
|
|
177
|
+
getToolStatus: (name) => {
|
|
178
|
+
const cfg = tools.get(name);
|
|
179
|
+
if (!cfg)
|
|
180
|
+
return undefined;
|
|
181
|
+
const usage = cfg.sourcePath
|
|
182
|
+
? RecipesUsage.readRecipeUsage(cfg.sourcePath)
|
|
183
|
+
: undefined;
|
|
184
|
+
const activation = activationFor(name);
|
|
185
|
+
const args = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
|
|
186
|
+
return {
|
|
187
|
+
...activation,
|
|
188
|
+
activation_boundary: activation.callable_now
|
|
189
|
+
? "current_session"
|
|
190
|
+
: !activation.host_registered
|
|
191
|
+
? "host_registration"
|
|
192
|
+
: "active_tool_set",
|
|
193
|
+
persisted: Boolean(cfg.sourcePath),
|
|
194
|
+
registry_active: true,
|
|
195
|
+
source: registeredToolSource(cfg),
|
|
196
|
+
required_args: args.required,
|
|
197
|
+
optional_args: args.optional,
|
|
198
|
+
next_actions: activation.callable_now
|
|
199
|
+
? [`call tool ${name}`]
|
|
200
|
+
: [
|
|
201
|
+
`register_tool name=${name} update=true`,
|
|
202
|
+
`inspect target=tool:${name} view=status`,
|
|
203
|
+
],
|
|
204
|
+
...(usage?.launch_kind ? { launch_kind: usage.launch_kind } : {}),
|
|
205
|
+
spawn_calls: Number(usage?.spawn_calls ?? 0),
|
|
206
|
+
tool_calls: Number(usage?.tool_calls ?? 0),
|
|
207
|
+
};
|
|
208
|
+
},
|
|
118
209
|
getToolNameBlocker,
|
|
119
210
|
getTools: () => tools,
|
|
120
211
|
loadTools,
|
|
121
212
|
notify,
|
|
122
213
|
registerRuntimeTool,
|
|
214
|
+
setWatchStatus: (watchStatus) => {
|
|
215
|
+
status.watch_status = watchStatus;
|
|
216
|
+
},
|
|
123
217
|
};
|
|
124
218
|
}
|
|
125
219
|
export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
@@ -129,6 +223,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
129
223
|
let rootWatcher;
|
|
130
224
|
let parentWatcher;
|
|
131
225
|
let failureNotified = false;
|
|
226
|
+
const setWatchStatus = (watchStatus) => runtime.setWatchStatus?.(watchStatus);
|
|
132
227
|
const close = () => {
|
|
133
228
|
const closingRoot = rootWatcher;
|
|
134
229
|
rootWatcher = undefined;
|
|
@@ -139,11 +234,13 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
139
234
|
if (reloadTimeout)
|
|
140
235
|
clearTimeout(reloadTimeout);
|
|
141
236
|
reloadTimeout = undefined;
|
|
237
|
+
setWatchStatus("closed");
|
|
142
238
|
};
|
|
143
239
|
const notifyFailure = (ctx) => {
|
|
144
240
|
if (failureNotified)
|
|
145
241
|
return;
|
|
146
242
|
failureNotified = true;
|
|
243
|
+
setWatchStatus("failed");
|
|
147
244
|
ctx.ui.notify("Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.", "warning");
|
|
148
245
|
};
|
|
149
246
|
const scheduleReload = (ctx) => {
|
|
@@ -151,7 +248,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
151
248
|
if (reloadTimeout)
|
|
152
249
|
clearTimeout(reloadTimeout);
|
|
153
250
|
reloadTimeout = setTimeout(() => {
|
|
154
|
-
runtime.loadTools(ctx);
|
|
251
|
+
runtime.loadTools(ctx, deps.getResolutionContext?.());
|
|
155
252
|
ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
|
|
156
253
|
}, 150);
|
|
157
254
|
reloadTimeout.unref?.();
|
|
@@ -181,6 +278,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
181
278
|
scheduleReload(ctx);
|
|
182
279
|
});
|
|
183
280
|
parentWatcher = watcher;
|
|
281
|
+
setWatchStatus("watching_parent");
|
|
184
282
|
watcher.on("error", () => {
|
|
185
283
|
if (parentWatcher !== watcher)
|
|
186
284
|
return;
|
|
@@ -214,6 +312,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
214
312
|
scheduleReload(ctx);
|
|
215
313
|
});
|
|
216
314
|
rootWatcher = watcher;
|
|
315
|
+
setWatchStatus("watching_root");
|
|
217
316
|
watcher.on("error", () => {
|
|
218
317
|
if (rootWatcher !== watcher)
|
|
219
318
|
return;
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
|
|
4
4
|
* Owns exact inspect target/view dispatch; source projection stays in domain modules.
|
|
5
5
|
*/
|
|
6
|
+
import * as Runtime from "./runtime.ts";
|
|
6
7
|
export interface InspectToolDeps<TContext = unknown> {
|
|
7
8
|
getRunStatus?: (runOrDir: string) => Record<string, any>;
|
|
8
9
|
getTool?: (name: string) => any | undefined;
|
|
10
|
+
getToolStatus?: (name: string) => Record<string, unknown> | undefined;
|
|
9
11
|
listRuns?: () => Array<Record<string, any>>;
|
|
10
12
|
recipeRoot?: string;
|
|
13
|
+
registryStatus?: () => Runtime.RecipeRegistryStatus;
|
|
11
14
|
}
|
|
12
15
|
export declare function createInspectToolDefinition<TContext = unknown>(deps?: InspectToolDeps<TContext>): any;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
|
|
4
4
|
* Owns exact inspect target/view dispatch; source projection stays in domain modules.
|
|
5
5
|
*/
|
|
6
|
-
import { statSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
6
|
+
import { existsSync, statSync } from "node:fs";
|
|
7
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
8
8
|
import * as AsyncRuns from "./async-runs.js";
|
|
9
9
|
import * as ControlProjection from "./control-projection.js";
|
|
10
10
|
import * as Inspector from "./inspector.js";
|
|
@@ -157,7 +157,114 @@ function inspectRuntime(view, input, ctx, deps) {
|
|
|
157
157
|
return runtimeTriage(ctx, deps);
|
|
158
158
|
throw new Error("inspect runtime supports view=status, view=runs, or view=triage.");
|
|
159
159
|
}
|
|
160
|
-
function
|
|
160
|
+
function portableSkillRecipePath(file, skill, namespaces) {
|
|
161
|
+
const root = namespaces[skill]?.[0];
|
|
162
|
+
if (!root)
|
|
163
|
+
return `<active-skill:${skill}>`;
|
|
164
|
+
const relation = relative(root, file);
|
|
165
|
+
return !relation.startsWith("..") && !isAbsolute(relation)
|
|
166
|
+
? `<active-skill:${skill}>/${relation.replaceAll("\\", "/")}`
|
|
167
|
+
: `<active-skill:${skill}>`;
|
|
168
|
+
}
|
|
169
|
+
function portableSkillRecipeDiagnosticReason(diagnostic, namespaces) {
|
|
170
|
+
let reason = diagnostic.reason;
|
|
171
|
+
if (diagnostic.file) {
|
|
172
|
+
reason = reason.replaceAll(diagnostic.file, portableSkillRecipePath(diagnostic.file, diagnostic.skill, namespaces));
|
|
173
|
+
}
|
|
174
|
+
for (const root of namespaces[diagnostic.skill] ?? []) {
|
|
175
|
+
reason = reason.replaceAll(root, `<active-skill:${diagnostic.skill}>`);
|
|
176
|
+
}
|
|
177
|
+
return reason;
|
|
178
|
+
}
|
|
179
|
+
function inspectRecipeIdentity(identity, resolutionContext, namespaces, inventory, registryStatus) {
|
|
180
|
+
const match = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/u.exec(identity);
|
|
181
|
+
const skill = match?.[1] ?? "";
|
|
182
|
+
const roots = skill ? namespaces[skill] ?? [] : [];
|
|
183
|
+
const skillActive = roots.length === 1;
|
|
184
|
+
const matchingComponent = inventory.components.find((component) => component.identity === identity);
|
|
185
|
+
const matchingRejection = inventory.rejected.find((diagnostic) => diagnostic.skill === skill &&
|
|
186
|
+
(diagnostic.stem === undefined || diagnostic.stem === match?.[2]));
|
|
187
|
+
let sourceLocation;
|
|
188
|
+
let rejectedReason;
|
|
189
|
+
let resolvable = false;
|
|
190
|
+
if (!match) {
|
|
191
|
+
rejectedReason =
|
|
192
|
+
"Identity must use canonical <skill>/<recipe> syntax.";
|
|
193
|
+
}
|
|
194
|
+
else if (roots.length === 0) {
|
|
195
|
+
rejectedReason = `Active Skill Recipe not found: ${identity}`;
|
|
196
|
+
}
|
|
197
|
+
else if (roots.length > 1) {
|
|
198
|
+
rejectedReason = matchingRejection
|
|
199
|
+
? portableSkillRecipeDiagnosticReason(matchingRejection, namespaces)
|
|
200
|
+
: `Duplicate active Skill identity ${skill}`;
|
|
201
|
+
}
|
|
202
|
+
else if (matchingRejection) {
|
|
203
|
+
rejectedReason = portableSkillRecipeDiagnosticReason(matchingRejection, namespaces);
|
|
204
|
+
if (matchingRejection.file) {
|
|
205
|
+
sourceLocation = portableSkillRecipePath(matchingRejection.file, skill, namespaces);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
try {
|
|
210
|
+
const file = RecipesReferences.resolveRecipeReferencePath(identity, resolutionContext?.cwd, resolutionContext?.activeSkills);
|
|
211
|
+
if (!file || !existsSync(file)) {
|
|
212
|
+
rejectedReason = `Skill Recipe component not found: ${identity}`;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
sourceLocation = portableSkillRecipePath(file, skill, namespaces);
|
|
216
|
+
const recipe = RecipesReferences.readResolvedRecipeConfig(file, [], {
|
|
217
|
+
skillContext: resolutionContext?.activeSkills,
|
|
218
|
+
});
|
|
219
|
+
if (recipe)
|
|
220
|
+
resolvable = true;
|
|
221
|
+
else {
|
|
222
|
+
rejectedReason =
|
|
223
|
+
RecipesReferences.diagnoseRawRecipeConfigFailure(file) ??
|
|
224
|
+
`Recipe could not be resolved: ${identity}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
rejectedReason = error instanceof Error ? error.message : String(error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const componentStatus = resolvable
|
|
233
|
+
? "available"
|
|
234
|
+
: !match
|
|
235
|
+
? "invalid_identity"
|
|
236
|
+
: roots.length === 0
|
|
237
|
+
? "skill_inactive"
|
|
238
|
+
: roots.length > 1
|
|
239
|
+
? "ambiguous_skill"
|
|
240
|
+
: matchingRejection
|
|
241
|
+
? "rejected"
|
|
242
|
+
: matchingComponent
|
|
243
|
+
? "unresolvable"
|
|
244
|
+
: "missing";
|
|
245
|
+
return {
|
|
246
|
+
identity,
|
|
247
|
+
skill_active: skillActive,
|
|
248
|
+
resolvable,
|
|
249
|
+
catalog_partial: inventory.partial,
|
|
250
|
+
component_status: componentStatus,
|
|
251
|
+
...(sourceLocation ? { source_location: sourceLocation } : {}),
|
|
252
|
+
resolution_generation: resolutionContext?.generation ?? registryStatus?.resolution_generation ?? "unavailable",
|
|
253
|
+
...(rejectedReason ? { rejected_reason: rejectedReason } : {}),
|
|
254
|
+
next_actions: resolvable
|
|
255
|
+
? [
|
|
256
|
+
`spawn recipe=${identity}`,
|
|
257
|
+
`register_tool name=<tool-name> from=${identity}`,
|
|
258
|
+
]
|
|
259
|
+
: roots.length === 0 && skill
|
|
260
|
+
? [
|
|
261
|
+
`activate Skill ${skill}`,
|
|
262
|
+
`inspect target=recipes view=doctor identity=${identity}`,
|
|
263
|
+
]
|
|
264
|
+
: ["inspect target=recipes view=imports verbose=true"],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function inspectRecipes(view, input, deps, context) {
|
|
161
268
|
if (view !== "status" &&
|
|
162
269
|
view !== "summary" &&
|
|
163
270
|
view !== "doctor" &&
|
|
@@ -172,30 +279,47 @@ function inspectRecipes(view, deps, context) {
|
|
|
172
279
|
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
173
280
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
174
281
|
]);
|
|
175
|
-
const
|
|
176
|
-
? context.
|
|
282
|
+
const resolutionContext = context && typeof context === "object" && "recipeResolutionContext" in context
|
|
283
|
+
? context.recipeResolutionContext
|
|
177
284
|
: undefined;
|
|
178
|
-
const activeSkillContext =
|
|
285
|
+
const activeSkillContext = resolutionContext?.activeSkills ??
|
|
286
|
+
RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
|
|
179
287
|
const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces(activeSkillContext);
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
source_kind: "active_skill_component",
|
|
186
|
-
skill: component.skill,
|
|
187
|
-
stem: component.stem,
|
|
188
|
-
imports: component.imports,
|
|
189
|
-
}));
|
|
288
|
+
const skillInventory = RecipesReferences.inventoryActiveSkillRecipeComponents(activeSkillContext);
|
|
289
|
+
const registryStatus = deps.registryStatus?.();
|
|
290
|
+
const identity = typeof input.identity === "string" ? input.identity.trim() : "";
|
|
291
|
+
if (identity && view !== "doctor") {
|
|
292
|
+
throw new Error("inspect recipes identity is supported only with view=doctor.");
|
|
190
293
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
error: error instanceof Error ? error.message : String(error),
|
|
194
|
-
});
|
|
294
|
+
if (view === "doctor" && identity) {
|
|
295
|
+
return inspectRecipeIdentity(identity, resolutionContext, skillRecipeNamespaces, skillInventory, registryStatus);
|
|
195
296
|
}
|
|
297
|
+
const skillRecipeComponents = skillInventory.components.map((component) => ({
|
|
298
|
+
identity: component.identity,
|
|
299
|
+
source_kind: "active_skill_component",
|
|
300
|
+
skill: component.skill,
|
|
301
|
+
stem: component.stem,
|
|
302
|
+
imports: component.imports,
|
|
303
|
+
}));
|
|
304
|
+
const skillRecipeComponentDiagnostics = skillInventory.rejected.map((diagnostic) => ({
|
|
305
|
+
error: portableSkillRecipeDiagnosticReason(diagnostic, skillRecipeNamespaces),
|
|
306
|
+
...(diagnostic.file
|
|
307
|
+
? {
|
|
308
|
+
file: portableSkillRecipePath(diagnostic.file, diagnostic.skill, skillRecipeNamespaces),
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
311
|
+
skill: diagnostic.skill,
|
|
312
|
+
...(diagnostic.stem ? { stem: diagnostic.stem } : {}),
|
|
313
|
+
}));
|
|
196
314
|
const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
|
|
197
315
|
const summary = {
|
|
198
316
|
...discoverySummary,
|
|
317
|
+
...(registryStatus
|
|
318
|
+
? {
|
|
319
|
+
...registryStatus,
|
|
320
|
+
watched_root: "~/.pi/agent/recipes",
|
|
321
|
+
}
|
|
322
|
+
: {}),
|
|
199
323
|
active: Array.isArray(discoverySummary.active)
|
|
200
324
|
? discoverySummary.active.map((entry) => ({
|
|
201
325
|
...entry,
|
|
@@ -204,6 +328,7 @@ function inspectRecipes(view, deps, context) {
|
|
|
204
328
|
: discoverySummary.active,
|
|
205
329
|
skill_recipe_components: skillRecipeComponents,
|
|
206
330
|
skill_recipe_component_diagnostics: skillRecipeComponentDiagnostics,
|
|
331
|
+
skill_recipe_catalog_partial: skillInventory.partial,
|
|
207
332
|
active_skill_recipe_identities: Object.keys(skillRecipeNamespaces).sort(),
|
|
208
333
|
skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
|
|
209
334
|
.filter(([, roots]) => roots.length > 1)
|
|
@@ -303,20 +428,58 @@ function inspectTool(name, view, deps) {
|
|
|
303
428
|
throw new Error("inspect tool:<name> supports view=status or view=schema.");
|
|
304
429
|
}
|
|
305
430
|
const tool = deps.getTool?.(name);
|
|
306
|
-
|
|
307
|
-
|
|
431
|
+
const status = deps.getToolStatus?.(name);
|
|
432
|
+
if (!tool && !status) {
|
|
433
|
+
throw new Error(`Registered tool not found: ${name}. Next: inspect target=recipes view=doctor; do not substitute spawn for tool invocation.`);
|
|
434
|
+
}
|
|
435
|
+
if (view === "schema" && !tool) {
|
|
436
|
+
throw new Error(`Tool "${name}" is registered but its callable schema is unavailable; callable_now=${String(status?.callable_now ?? false)}, activation_boundary=${String(status?.activation_boundary ?? "host_registration")}. Next: inspect target=tool:${name} view=status.`);
|
|
437
|
+
}
|
|
308
438
|
return {
|
|
309
439
|
name,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
440
|
+
...(view === "status" ? status : {}),
|
|
441
|
+
...(tool
|
|
442
|
+
? {
|
|
443
|
+
description: tool.description,
|
|
444
|
+
parameters: tool.parameters,
|
|
445
|
+
promptSnippet: tool.promptSnippet,
|
|
446
|
+
}
|
|
447
|
+
: {}),
|
|
313
448
|
};
|
|
314
449
|
}
|
|
315
450
|
function compactResult(target, view, details) {
|
|
316
451
|
if (target === "runtime")
|
|
317
452
|
return compactRuntime(view, details);
|
|
453
|
+
if (target === "recipes" && typeof details.identity === "string") {
|
|
454
|
+
const lines = [
|
|
455
|
+
`recipes doctor identity=${details.identity} skill_active=${String(details.skill_active)} resolvable=${String(details.resolvable)} catalog_partial=${String(details.catalog_partial)} component_status=${String(details.component_status)}`,
|
|
456
|
+
...(details.source_location
|
|
457
|
+
? [`source=${String(details.source_location)}`]
|
|
458
|
+
: []),
|
|
459
|
+
`resolution_generation=${String(details.resolution_generation)}`,
|
|
460
|
+
...(details.rejected_reason
|
|
461
|
+
? [`rejected_reason=${String(details.rejected_reason)}`]
|
|
462
|
+
: []),
|
|
463
|
+
...(details.next_actions ?? []).map((action) => `next=${String(action)}`),
|
|
464
|
+
];
|
|
465
|
+
return `\n${lines.join("\n")}`;
|
|
466
|
+
}
|
|
318
467
|
if (target === "recipes")
|
|
319
468
|
return ToolsResponse.compactRecipeRegistry(details);
|
|
469
|
+
if (target.startsWith("tool:") && view === "status") {
|
|
470
|
+
const required = Array.isArray(details.required_args)
|
|
471
|
+
? details.required_args.map(String).join(",") || "none"
|
|
472
|
+
: "unknown";
|
|
473
|
+
const optional = Array.isArray(details.optional_args)
|
|
474
|
+
? details.optional_args.map(String).join(",") || "none"
|
|
475
|
+
: "unknown";
|
|
476
|
+
const next = Array.isArray(details.next_actions) && details.next_actions.length > 0
|
|
477
|
+
? String(details.next_actions[0])
|
|
478
|
+
: details.callable_now === true
|
|
479
|
+
? `call tool ${target.slice(5)}`
|
|
480
|
+
: `register_tool name=${target.slice(5)} update=true`;
|
|
481
|
+
return `\ntool=${target.slice(5)} source=${String(details.source ?? "unknown")} callable_now=${String(details.callable_now ?? false)} activation_boundary=${String(details.activation_boundary ?? "unknown")} required=${required} optional=${optional} launch_kind=${String(details.launch_kind ?? "none")} spawn_calls=${Number(details.spawn_calls ?? 0)} tool_calls=${Number(details.tool_calls ?? 0)} next=${next}`;
|
|
482
|
+
}
|
|
320
483
|
if (target.startsWith("tool:"))
|
|
321
484
|
return `\ntool=${target.slice(5)} view=${view}`;
|
|
322
485
|
return `\nrun=${target.slice(4)} view=${view}`;
|
|
@@ -327,6 +490,7 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
327
490
|
label: "Inspect",
|
|
328
491
|
description: "Inspect a Run's Recipe, Trace, or Control evidence, or runtime/recipe/tool diagnostics.",
|
|
329
492
|
parameters: Schema.objectSchema({
|
|
493
|
+
identity: Schema.stringSchema("Optional canonical <skill>/<recipe> identity for focused Recipe doctor diagnosis."),
|
|
330
494
|
lines: Schema.stringSchema("Bounded item count for Trace or recent Controls."),
|
|
331
495
|
source: Schema.stringSchema("Optional Trace source: all, lifecycle, control, process, agent, artifact, or runtime."),
|
|
332
496
|
status: Schema.stringSchema("Optional runtime Run status filter."),
|
|
@@ -343,7 +507,7 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
343
507
|
details = inspectRuntime(view, input, ctx, deps);
|
|
344
508
|
}
|
|
345
509
|
else if (target === "recipes") {
|
|
346
|
-
details = inspectRecipes(view, deps, ctx);
|
|
510
|
+
details = inspectRecipes(view, input, deps, ctx);
|
|
347
511
|
}
|
|
348
512
|
else {
|
|
349
513
|
const run = parseRunTarget(target);
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import * as ModelContext from "./model-context.ts";
|
|
7
7
|
import type { RegisteredTool } from "./config.ts";
|
|
8
8
|
import * as Execution from "./execution.ts";
|
|
9
|
-
import * as
|
|
9
|
+
import type * as RecipeResolution from "./recipes-context.ts";
|
|
10
10
|
export interface RuntimeToolContext extends ModelContext.CurrentModelContext {
|
|
11
|
-
|
|
11
|
+
recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
|
|
12
12
|
cwd: string;
|
|
13
13
|
sessionManager?: {
|
|
14
14
|
getSessionId?: () => string;
|
package/dist/lib/tools-local.js
CHANGED
|
@@ -44,7 +44,8 @@ function shouldAddRuntimeToolUsageHint(error) {
|
|
|
44
44
|
return (/^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message));
|
|
45
45
|
}
|
|
46
46
|
function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
47
|
-
const optional = cfg.args.filter((arg) => !
|
|
47
|
+
const optional = cfg.args.filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg) &&
|
|
48
|
+
!required.includes(arg));
|
|
48
49
|
const exampleDefaults = {
|
|
49
50
|
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
50
51
|
...cfg.defaults,
|
|
@@ -115,6 +116,8 @@ export function createRuntimeToolDefinition(cfg, exec) {
|
|
|
115
116
|
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
116
117
|
: Schema.getRequiredToolArgNames(requiredTemplateConfig);
|
|
117
118
|
for (const arg of cfg.args) {
|
|
119
|
+
if (RecipesReferences.isRuntimeOwnedRecipeInput(arg))
|
|
120
|
+
continue;
|
|
118
121
|
paramSchema[arg] = typedArgSchema(arg, cfg.argTypes?.[arg]);
|
|
119
122
|
if (requiredArgs.has(arg))
|
|
120
123
|
required.push(arg);
|
|
@@ -160,7 +163,7 @@ export function createRuntimeToolDefinition(cfg, exec) {
|
|
|
160
163
|
tool: cfg.name,
|
|
161
164
|
policy_values: ModelContext.withCurrentModelValues({ ...(cfg.recipe?.values ?? {}), ...values }, ctx),
|
|
162
165
|
values: resolveRecipeToolValues(cfg, values, ctx),
|
|
163
|
-
}, ctx.cwd, { skillContext: ctx.
|
|
166
|
+
}, ctx.cwd, { skillContext: ctx.recipeResolutionContext?.activeSkills });
|
|
164
167
|
return {
|
|
165
168
|
content: [
|
|
166
169
|
{
|
|
@@ -24,7 +24,9 @@ export function createRegisterToolDefinition(deps) {
|
|
|
24
24
|
args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
|
|
25
25
|
async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
|
|
26
26
|
description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
|
|
27
|
+
defaults: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.defaults),
|
|
27
28
|
draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
|
|
29
|
+
from: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.from),
|
|
28
30
|
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
29
31
|
template: unionSchema([
|
|
30
32
|
stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
@@ -33,7 +35,6 @@ export function createRegisterToolDefinition(deps) {
|
|
|
33
35
|
nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
|
|
34
36
|
]),
|
|
35
37
|
update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
|
|
36
|
-
values: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values),
|
|
37
38
|
}, []),
|
|
38
39
|
execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => Registry.executeRegisterTool(params, ctx, deps),
|
|
39
40
|
};
|
|
@@ -91,6 +91,8 @@ export function compactAsyncRunStatus(value) {
|
|
|
91
91
|
const result = asRecord(status.result);
|
|
92
92
|
const run = String(status.run ?? "<unknown>");
|
|
93
93
|
const tokens = [`run=${run}`, `status=${String(status.status ?? "unknown")}`];
|
|
94
|
+
if (status.launch_kind)
|
|
95
|
+
tokens.push(`launch_kind=${String(status.launch_kind)}`);
|
|
94
96
|
tokens.push(...compactModelPolicy(status.model_policy ?? progress.model_policy));
|
|
95
97
|
if (status.tool)
|
|
96
98
|
tokens.push(`tool=${String(status.tool)}`);
|
|
@@ -202,6 +204,9 @@ export function recipeRegistryNextActions(summary, view) {
|
|
|
202
204
|
if (view === "doctor" && typeof topAction.action === "string") {
|
|
203
205
|
actions.push(String(topAction.action));
|
|
204
206
|
}
|
|
207
|
+
if (summary.skill_recipe_catalog_partial === true) {
|
|
208
|
+
actions.push("inspect target=recipes view=imports verbose=true");
|
|
209
|
+
}
|
|
205
210
|
if (drafts.length > 0) {
|
|
206
211
|
actions.push("inspect target=recipes view=summary verbose=true");
|
|
207
212
|
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
@@ -226,12 +231,13 @@ export function compactRecipeRegistry(summary) {
|
|
|
226
231
|
const recommendations = Array.isArray(summary.recommendations)
|
|
227
232
|
? summary.recommendations.length
|
|
228
233
|
: 0;
|
|
234
|
+
const skillCatalogPartial = summary.skill_recipe_catalog_partial === true;
|
|
229
235
|
const currentPolicy = Array.isArray(summary.active)
|
|
230
236
|
? summary.active.filter((entry) => entry.current_policy).length
|
|
231
237
|
: 0;
|
|
232
238
|
const nextActions = Array.isArray(summary.next_actions)
|
|
233
239
|
? summary.next_actions
|
|
234
240
|
: [];
|
|
235
|
-
return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} current_policy=${currentPolicy} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
|
|
241
|
+
return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} current_policy=${currentPolicy} skill_catalog_partial=${skillCatalogPartial} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
|
|
236
242
|
}
|
|
237
243
|
export const DEFAULT_INSPECT_LINES = Limits.DEFAULT_INSPECT_LINES;
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Owns the public spawn execution path for run-backed actors
|
|
5
5
|
*/
|
|
6
6
|
import * as ModelContext from "./model-context.ts";
|
|
7
|
-
import * as
|
|
7
|
+
import type * as RecipeResolution from "./recipes-context.ts";
|
|
8
8
|
export interface SpawnToolContext extends ModelContext.CurrentModelContext {
|
|
9
|
-
|
|
9
|
+
recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
|
|
10
10
|
cwd: string;
|
|
11
11
|
sessionManager?: {
|
|
12
12
|
getSessionId?: () => string;
|
package/dist/lib/tools-spawn.js
CHANGED
|
@@ -111,7 +111,7 @@ export function createSpawnToolDefinition() {
|
|
|
111
111
|
artifacts: input.artifacts,
|
|
112
112
|
}
|
|
113
113
|
: {}),
|
|
114
|
-
}, ctx.cwd, { skillContext: ctx.
|
|
114
|
+
}, ctx.cwd, { skillContext: ctx.recipeResolutionContext?.activeSkills });
|
|
115
115
|
const draftRecipe = writeSpawnDraftRecipe(input, meta);
|
|
116
116
|
const nextActions = ToolsResponse.runNextActions(meta.run);
|
|
117
117
|
const details = {
|
package/dist/lib/tools.d.ts
CHANGED
|
@@ -15,8 +15,11 @@ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext
|
|
|
15
15
|
configPath: string;
|
|
16
16
|
getActiveTools: () => string[];
|
|
17
17
|
getRuntimeTool: (name: string) => unknown;
|
|
18
|
+
getRuntimeToolStatus: (name: string) => Record<string, unknown> | undefined;
|
|
18
19
|
handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
|
|
19
|
-
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool"
|
|
20
|
+
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool"> & {
|
|
21
|
+
getStatus(): import("./runtime.ts").RecipeRegistryStatus;
|
|
22
|
+
};
|
|
20
23
|
setActiveTools: (toolNames: string[]) => void;
|
|
21
24
|
}
|
|
22
25
|
export declare function resolveActiveRuntimeTool(name: string, activeTools: Pick<Map<string, unknown>, "has">, getDefinition: (name: string) => unknown): unknown;
|
package/dist/lib/tools.js
CHANGED
|
@@ -41,6 +41,8 @@ export function createCoreActorToolDefinitions(deps) {
|
|
|
41
41
|
}),
|
|
42
42
|
ToolsInspect.createInspectToolDefinition({
|
|
43
43
|
getTool: (name) => deps.getRuntimeTool(name),
|
|
44
|
+
getToolStatus: deps.getRuntimeToolStatus,
|
|
45
|
+
registryStatus: deps.registryRuntime.getStatus,
|
|
44
46
|
}),
|
|
45
47
|
];
|
|
46
48
|
}
|