@llblab/pi-actors 0.46.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +582 -0
- package/CHANGELOG.md +9 -0
- package/README.md +1 -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 +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipes-context.d.ts +12 -3
- package/dist/lib/recipes-context.js +28 -4
- package/dist/lib/recipes-discovery.d.ts +18 -0
- package/dist/lib/recipes-discovery.js +95 -22
- package/dist/lib/recipes-references.d.ts +18 -0
- package/dist/lib/recipes-references.js +120 -33
- package/dist/lib/registry.d.ts +14 -1
- package/dist/lib/registry.js +110 -87
- package/dist/lib/runtime.d.ts +30 -3
- package/dist/lib/runtime.js +73 -9
- package/dist/lib/tools-inspect.d.ts +3 -0
- package/dist/lib/tools-inspect.js +40 -20
- package/dist/lib/tools-local.d.ts +2 -2
- package/dist/lib/tools-local.js +5 -2
- package/dist/lib/tools-response.js +2 -0
- 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/skills/actors/SKILL.md +3 -3
- package/docs/template-recipes.md +3 -1
- package/docs/tool-registry.md +9 -3
- 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 +2 -2
- package/lib/recipes-context.ts +49 -4
- package/lib/recipes-discovery.ts +164 -24
- package/lib/recipes-references.ts +163 -39
- package/lib/registry.ts +185 -94
- package/lib/runtime.ts +108 -12
- package/lib/tools-inspect.ts +57 -23
- package/lib/tools-local.ts +9 -3
- package/lib/tools-response.ts +2 -0
- package/lib/tools-spawn.ts +3 -2
- package/lib/tools.ts +4 -1
- package/package.json +1 -1
- package/skills/actors/SKILL.md +3 -3
package/dist/lib/runtime.js
CHANGED
|
@@ -7,11 +7,20 @@ 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 RecipesUsage from "./recipes-usage.js";
|
|
10
11
|
import * as ToolsLocal from "./tools-local.js";
|
|
11
12
|
export function createAutoToolsRuntime(deps) {
|
|
12
13
|
const tools = new Map();
|
|
13
14
|
const runtimeToolFingerprints = new Map();
|
|
14
15
|
const runtimeTools = new Set();
|
|
16
|
+
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
17
|
+
const status = {
|
|
18
|
+
active_tool_count: 0,
|
|
19
|
+
last_scan_counts: { active: 0, rejected: 0, scanned: 0 },
|
|
20
|
+
registry_generation: 0,
|
|
21
|
+
watch_status: "closed",
|
|
22
|
+
watched_root: recipeRoot,
|
|
23
|
+
};
|
|
15
24
|
function notify(ctx, message, type) {
|
|
16
25
|
if (ctx.hasUI)
|
|
17
26
|
ctx.ui.notify(message, type);
|
|
@@ -44,13 +53,29 @@ export function createAutoToolsRuntime(deps) {
|
|
|
44
53
|
const staleSet = new Set(stale);
|
|
45
54
|
deps.setActiveTools(deps.getActiveTools().filter((name) => !staleSet.has(name)));
|
|
46
55
|
}
|
|
56
|
+
function activationFor(name) {
|
|
57
|
+
const hostRegistered = deps.getAllTools?.().some((tool) => tool.name === name) ?? false;
|
|
58
|
+
const activeTool = deps.getActiveTools?.().includes(name) ?? false;
|
|
59
|
+
return {
|
|
60
|
+
active_tool: activeTool,
|
|
61
|
+
activation: hostRegistered && activeTool ? "current_session" : "unverified",
|
|
62
|
+
callable_now: hostRegistered && activeTool,
|
|
63
|
+
host_registered: hostRegistered,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
47
66
|
function registerRuntimeTool(cfg) {
|
|
48
67
|
const fingerprint = getToolFingerprint(cfg);
|
|
49
|
-
if (runtimeToolFingerprints.get(cfg.name)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
if (runtimeToolFingerprints.get(cfg.name) !== fingerprint) {
|
|
69
|
+
deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
|
|
70
|
+
runtimeTools.add(cfg.name);
|
|
71
|
+
runtimeToolFingerprints.set(cfg.name, fingerprint);
|
|
72
|
+
}
|
|
73
|
+
if (deps.getActiveTools && deps.setActiveTools) {
|
|
74
|
+
deps.setActiveTools([
|
|
75
|
+
...new Set([...deps.getActiveTools(), cfg.name]),
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
return activationFor(cfg.name);
|
|
54
79
|
}
|
|
55
80
|
function isStartupActionableRegistryWarning(warning) {
|
|
56
81
|
if (warning.includes(" shadows "))
|
|
@@ -82,11 +107,15 @@ export function createAutoToolsRuntime(deps) {
|
|
|
82
107
|
}
|
|
83
108
|
return `${lines.join("\n")}\n`;
|
|
84
109
|
}
|
|
85
|
-
function loadTools(ctx) {
|
|
110
|
+
function loadTools(ctx, resolutionContext) {
|
|
86
111
|
const warnings = [];
|
|
87
|
-
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
88
112
|
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
89
|
-
{
|
|
113
|
+
{
|
|
114
|
+
root: recipeRoot,
|
|
115
|
+
defaultTool: true,
|
|
116
|
+
mutableUsage: true,
|
|
117
|
+
resolutionContext,
|
|
118
|
+
},
|
|
90
119
|
]);
|
|
91
120
|
warnings.push(...discovered.diagnostics);
|
|
92
121
|
tools.clear();
|
|
@@ -109,17 +138,47 @@ export function createAutoToolsRuntime(deps) {
|
|
|
109
138
|
}
|
|
110
139
|
registerRuntimeTool(cfg);
|
|
111
140
|
}
|
|
141
|
+
status.active_tool_count = tools.size;
|
|
142
|
+
status.last_scan_at = new Date().toISOString();
|
|
143
|
+
status.last_scan_counts = {
|
|
144
|
+
active: tools.size,
|
|
145
|
+
rejected: discovered.entries.filter((entry) => entry.invalid).length,
|
|
146
|
+
scanned: discovered.entries.length,
|
|
147
|
+
};
|
|
148
|
+
status.registry_generation += 1;
|
|
149
|
+
status.resolution_generation = resolutionContext?.generation;
|
|
112
150
|
const startupWarnings = warnings.filter(isStartupActionableRegistryWarning);
|
|
113
151
|
if (startupWarnings.length > 0) {
|
|
114
152
|
notify(ctx, formatRecipeToolWarnings(startupWarnings), "warning");
|
|
115
153
|
}
|
|
116
154
|
}
|
|
117
155
|
return {
|
|
156
|
+
getStatus: () => ({
|
|
157
|
+
...status,
|
|
158
|
+
last_scan_counts: { ...status.last_scan_counts },
|
|
159
|
+
}),
|
|
160
|
+
getToolStatus: (name) => {
|
|
161
|
+
const cfg = tools.get(name);
|
|
162
|
+
if (!cfg)
|
|
163
|
+
return undefined;
|
|
164
|
+
const usage = cfg.sourcePath
|
|
165
|
+
? RecipesUsage.readRecipeUsage(cfg.sourcePath)
|
|
166
|
+
: undefined;
|
|
167
|
+
return {
|
|
168
|
+
...activationFor(name),
|
|
169
|
+
launch_kind: usage?.launch_kind,
|
|
170
|
+
spawn_calls: Number(usage?.spawn_calls ?? 0),
|
|
171
|
+
tool_calls: Number(usage?.tool_calls ?? 0),
|
|
172
|
+
};
|
|
173
|
+
},
|
|
118
174
|
getToolNameBlocker,
|
|
119
175
|
getTools: () => tools,
|
|
120
176
|
loadTools,
|
|
121
177
|
notify,
|
|
122
178
|
registerRuntimeTool,
|
|
179
|
+
setWatchStatus: (watchStatus) => {
|
|
180
|
+
status.watch_status = watchStatus;
|
|
181
|
+
},
|
|
123
182
|
};
|
|
124
183
|
}
|
|
125
184
|
export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
@@ -129,6 +188,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
129
188
|
let rootWatcher;
|
|
130
189
|
let parentWatcher;
|
|
131
190
|
let failureNotified = false;
|
|
191
|
+
const setWatchStatus = (watchStatus) => runtime.setWatchStatus?.(watchStatus);
|
|
132
192
|
const close = () => {
|
|
133
193
|
const closingRoot = rootWatcher;
|
|
134
194
|
rootWatcher = undefined;
|
|
@@ -139,11 +199,13 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
139
199
|
if (reloadTimeout)
|
|
140
200
|
clearTimeout(reloadTimeout);
|
|
141
201
|
reloadTimeout = undefined;
|
|
202
|
+
setWatchStatus("closed");
|
|
142
203
|
};
|
|
143
204
|
const notifyFailure = (ctx) => {
|
|
144
205
|
if (failureNotified)
|
|
145
206
|
return;
|
|
146
207
|
failureNotified = true;
|
|
208
|
+
setWatchStatus("failed");
|
|
147
209
|
ctx.ui.notify("Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.", "warning");
|
|
148
210
|
};
|
|
149
211
|
const scheduleReload = (ctx) => {
|
|
@@ -151,7 +213,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
151
213
|
if (reloadTimeout)
|
|
152
214
|
clearTimeout(reloadTimeout);
|
|
153
215
|
reloadTimeout = setTimeout(() => {
|
|
154
|
-
runtime.loadTools(ctx);
|
|
216
|
+
runtime.loadTools(ctx, deps.getResolutionContext?.());
|
|
155
217
|
ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
|
|
156
218
|
}, 150);
|
|
157
219
|
reloadTimeout.unref?.();
|
|
@@ -181,6 +243,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
181
243
|
scheduleReload(ctx);
|
|
182
244
|
});
|
|
183
245
|
parentWatcher = watcher;
|
|
246
|
+
setWatchStatus("watching_parent");
|
|
184
247
|
watcher.on("error", () => {
|
|
185
248
|
if (parentWatcher !== watcher)
|
|
186
249
|
return;
|
|
@@ -214,6 +277,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
|
|
|
214
277
|
scheduleReload(ctx);
|
|
215
278
|
});
|
|
216
279
|
rootWatcher = watcher;
|
|
280
|
+
setWatchStatus("watching_root");
|
|
217
281
|
watcher.on("error", () => {
|
|
218
282
|
if (rootWatcher !== watcher)
|
|
219
283
|
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;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns exact inspect target/view dispatch; source projection stays in domain modules.
|
|
5
5
|
*/
|
|
6
6
|
import { statSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
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,6 +157,15 @@ 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 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
|
+
}
|
|
160
169
|
function inspectRecipes(view, deps, context) {
|
|
161
170
|
if (view !== "status" &&
|
|
162
171
|
view !== "summary" &&
|
|
@@ -172,30 +181,39 @@ function inspectRecipes(view, deps, context) {
|
|
|
172
181
|
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
173
182
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
174
183
|
]);
|
|
175
|
-
const
|
|
176
|
-
? context.
|
|
184
|
+
const resolutionContext = context && typeof context === "object" && "recipeResolutionContext" in context
|
|
185
|
+
? context.recipeResolutionContext
|
|
177
186
|
: undefined;
|
|
178
|
-
const activeSkillContext =
|
|
187
|
+
const activeSkillContext = resolutionContext?.activeSkills ??
|
|
188
|
+
RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
|
|
179
189
|
const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces(activeSkillContext);
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
const skillInventory = RecipesReferences.inventoryActiveSkillRecipeComponents(activeSkillContext);
|
|
191
|
+
const skillRecipeComponents = skillInventory.components.map((component) => ({
|
|
192
|
+
identity: component.identity,
|
|
193
|
+
source_kind: "active_skill_component",
|
|
194
|
+
skill: component.skill,
|
|
195
|
+
stem: component.stem,
|
|
196
|
+
imports: component.imports,
|
|
197
|
+
}));
|
|
198
|
+
const skillRecipeComponentDiagnostics = skillInventory.rejected.map((diagnostic) => ({
|
|
199
|
+
error: diagnostic.reason,
|
|
200
|
+
...(diagnostic.file
|
|
201
|
+
? {
|
|
202
|
+
file: portableSkillRecipePath(diagnostic.file, diagnostic.skill, skillRecipeNamespaces),
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
205
|
+
skill: diagnostic.skill,
|
|
206
|
+
...(diagnostic.stem ? { stem: diagnostic.stem } : {}),
|
|
207
|
+
}));
|
|
196
208
|
const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
|
|
197
209
|
const summary = {
|
|
198
210
|
...discoverySummary,
|
|
211
|
+
...(deps.registryStatus
|
|
212
|
+
? {
|
|
213
|
+
...deps.registryStatus(),
|
|
214
|
+
watched_root: "~/.pi/agent/recipes",
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
199
217
|
active: Array.isArray(discoverySummary.active)
|
|
200
218
|
? discoverySummary.active.map((entry) => ({
|
|
201
219
|
...entry,
|
|
@@ -204,6 +222,7 @@ function inspectRecipes(view, deps, context) {
|
|
|
204
222
|
: discoverySummary.active,
|
|
205
223
|
skill_recipe_components: skillRecipeComponents,
|
|
206
224
|
skill_recipe_component_diagnostics: skillRecipeComponentDiagnostics,
|
|
225
|
+
skill_recipe_catalog_partial: skillInventory.partial,
|
|
207
226
|
active_skill_recipe_identities: Object.keys(skillRecipeNamespaces).sort(),
|
|
208
227
|
skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
|
|
209
228
|
.filter(([, roots]) => roots.length > 1)
|
|
@@ -307,6 +326,7 @@ function inspectTool(name, view, deps) {
|
|
|
307
326
|
throw new Error(`registered tool not found: ${name}`);
|
|
308
327
|
return {
|
|
309
328
|
name,
|
|
329
|
+
...(view === "status" ? deps.getToolStatus?.(name) : {}),
|
|
310
330
|
description: tool.description,
|
|
311
331
|
parameters: tool.parameters,
|
|
312
332
|
promptSnippet: tool.promptSnippet,
|
|
@@ -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
|
{
|
|
@@ -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)}`);
|
|
@@ -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
|
}
|
|
@@ -19,9 +19,9 @@ Use the swarm skill separately for decomposition, quorum design, reviewer lenses
|
|
|
19
19
|
- `spawn`: create one Run from a Recipe or inline command template.
|
|
20
20
|
- `message`: send one actor-local Control to `run:<id>`, or the reserved review actions to `runtime`.
|
|
21
21
|
- `inspect`: inspect `run:<id>`, `runtime`, `recipes`, or `tool:<name>`.
|
|
22
|
-
- `register_tool`: persist a trusted capability; it does not address a running actor.
|
|
22
|
+
- `register_tool`: persist a trusted capability; it does not address a running actor. Trust current-session callability only when its result says `callable_now: true`.
|
|
23
23
|
|
|
24
|
-
A Run target exposes exactly three inspect views: `recipe`, `trace`, and `control`.
|
|
24
|
+
A Run target exposes exactly three inspect views: `recipe`, `trace`, and `control`. Spawning a user Recipe is still a Recipe launch (`launch_kind: "spawn"`), not evidence that its registered tool was exposed or invoked; registered-tool execution reports `launch_kind: "tool"`, and `inspect target=tool:<name> view=status` separates both usage counts.
|
|
25
25
|
|
|
26
26
|
## Recipe
|
|
27
27
|
|
|
@@ -29,7 +29,7 @@ A Recipe defines execution. It may declare named typed args, inline fallbacks, c
|
|
|
29
29
|
|
|
30
30
|
Do not declare Control for ordinary one-shot work. Runtime lifecycle actions such as `kill` stay runtime-owned and must not appear in Recipe Control declarations. Imported Recipes act as local definitions inside one Run; they do not create nested Runs unless execution explicitly spawns them.
|
|
31
31
|
|
|
32
|
-
Prefer maintained Skill-owned Recipes over ad hoc wrappers. Skill Recipe identity is `<active Skill name>/<Recipe filename stem>`; Recipe files have no top-level `name`. `SKILL.md` `name` is Pi host metadata matching its directory, not an additional pi-actors identity field. Use `<skill>/<recipe>` for an exact direct component or an explicit `.json` / `.md` path. Entry paths resolve from invocation `cwd`; relative imports resolve from the importing Recipe directory. File-backed Recipes own `{recipe_dir}` and Skill Recipes own `{skill_dir}`; callers never pass or override these origins. Skill Recipes are components, not automatic tools. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
|
|
32
|
+
Prefer maintained Skill-owned Recipes over ad hoc wrappers. Skill Recipe identity is `<active Skill name>/<Recipe filename stem>`; Recipe files have no top-level `name`. `SKILL.md` `name` is Pi host metadata matching its directory, not an additional pi-actors identity field. Use `<skill>/<recipe>` for an exact direct component or an explicit `.json` / `.md` path. Entry paths resolve from invocation `cwd`; relative imports resolve from the importing Recipe directory. File-backed Recipes own `{recipe_dir}` and Skill Recipes own `{skill_dir}`; callers never pass or override these origins. Skill Recipes are components, not automatic tools. A rejected component makes catalog inspection partial without blocking exact resolution of unrelated valid components in the same live session context. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
|
|
33
33
|
|
|
34
34
|
## Trace
|
|
35
35
|
|
package/docs/template-recipes.md
CHANGED
|
@@ -147,7 +147,9 @@ Resolution fails before launch when required current policy is unavailable. The
|
|
|
147
147
|
|
|
148
148
|
## Resolution Context
|
|
149
149
|
|
|
150
|
-
User Recipes under `~/.pi/agent/recipes` remain intentionally registered tools, not an ambient import namespace. Each session receives
|
|
150
|
+
User Recipes under `~/.pi/agent/recipes` remain intentionally registered tools, not an ambient import namespace. Each session receives one immutable resolution context from Pi's loaded Skill metadata; spawn, user-Recipe admission, registration, schema derivation, live inspection, and watcher reconciliation consume that same context rather than scanning ambient Skill roots or keeping a process-global mutable namespace. A launch captures its resolved graph, so later Skill changes affect only future launches. An invalid or missing exact target fails without fallback. Disabled Recipes cannot launch. Registry watchers converge after atomic changes without executing partial definitions.
|
|
151
|
+
|
|
152
|
+
Active-Skill catalog inventory is fail-soft diagnostic state, not exact-resolution authority. Invalid components are reported individually and make the catalog partial while unrelated valid `<skill>/<recipe>` references remain exactly resolvable.
|
|
151
153
|
|
|
152
154
|
## Validation
|
|
153
155
|
|
package/docs/tool-registry.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
~/.pi/agent/recipes/*.json
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Each
|
|
9
|
+
Each valid Recipe admitted against the current session context can become an agent-callable tool. `register_tool` creates, updates, promotes, or deletes these files through fenced mutation paths and reports the resulting activation state.
|
|
10
10
|
|
|
11
11
|
## Registration
|
|
12
12
|
|
|
@@ -20,9 +20,13 @@ Register a typed/defaulted template or Recipe-backed definition when reuse justi
|
|
|
20
20
|
|
|
21
21
|
Promote an immutable captured draft only with its draft path and explicit target name. Name collisions require `update=true`. Invalid content fails before active mutation.
|
|
22
22
|
|
|
23
|
+
Registration resolves the effective delegated contract before persistence, then reports distinct `persisted`, `registry_active`, `host_registered`, `active_tool`, and `callable_now` states. Treat the tool as callable in the current session only when `callable_now` is true; persistence alone is not activation proof.
|
|
24
|
+
|
|
23
25
|
## Resolution
|
|
24
26
|
|
|
25
|
-
User Recipes are the only file-discovered tool source. Invalid or disabled user entries fail closed. Active Skill Recipes remain exact components outside tool discovery. Runtime reload watches the user Recipe root and converges after atomic changes; stale watcher generations cannot replace current registration state.
|
|
27
|
+
User Recipes are the only file-discovered tool source. Invalid or disabled user entries fail closed. Active Skill Recipes remain exact components outside tool discovery. Spawn, registration, registry admission, schema derivation, and live inspection resolve against one immutable session context containing the current working directory and active Skills. Runtime reload watches the user Recipe root using that current context and converges after atomic changes; stale watcher generations cannot replace current registration state.
|
|
28
|
+
|
|
29
|
+
Skill component inventory is diagnostic and fail-soft: valid components remain listed and exactly resolvable when an unrelated component is rejected. Recipe inspection reports rejected components and marks the catalog partial instead of treating one bad component as an empty catalog.
|
|
26
30
|
|
|
27
31
|
Inspect registry state with:
|
|
28
32
|
|
|
@@ -31,7 +35,9 @@ inspect target=recipes view=status
|
|
|
31
35
|
inspect target=tool:<name> view=status
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
Recipe inspection reports active, shadowed, invalid, disabled, diagnostic, risk, usage, and review evidence. Tool
|
|
38
|
+
Recipe inspection reports generation, scan/watch state, active, shadowed, invalid, disabled, component rejection, diagnostic, risk, usage, and review evidence. Tool status reports current activation plus separate `tool_calls` and `spawn_calls`; tool schema reports the caller-owned capability contract. A registered tool is not a running actor.
|
|
39
|
+
|
|
40
|
+
`spawn recipe=<name>` executes a Recipe and reports `launch_kind: "spawn"`; it does not prove that a registered tool was exposed or invoked. Registered-tool execution reports `launch_kind: "tool"`.
|
|
35
41
|
|
|
36
42
|
## Automatic Review
|
|
37
43
|
|
package/lib/async-runs.ts
CHANGED
|
@@ -161,6 +161,7 @@ export interface AsyncRunMeta {
|
|
|
161
161
|
argv: string[];
|
|
162
162
|
createdAt: string;
|
|
163
163
|
cwd: string;
|
|
164
|
+
launch_kind?: AsyncRunLaunchSource;
|
|
164
165
|
launch_source?: AsyncRunLaunchSource;
|
|
165
166
|
launch_correlation?: {
|
|
166
167
|
correlation_id?: string;
|
|
@@ -600,7 +601,10 @@ export function startRun(
|
|
|
600
601
|
createdAt: new Date().toISOString(),
|
|
601
602
|
cwd,
|
|
602
603
|
...(startParams.launch_source
|
|
603
|
-
? {
|
|
604
|
+
? {
|
|
605
|
+
launch_kind: startParams.launch_source,
|
|
606
|
+
launch_source: startParams.launch_source,
|
|
607
|
+
}
|
|
604
608
|
: {}),
|
|
605
609
|
...(startParams.launch_correlation
|
|
606
610
|
? { launch_correlation: startParams.launch_correlation } : {}),
|
package/lib/execution.ts
CHANGED
|
@@ -83,6 +83,7 @@ export interface RegisteredToolExecutionResult {
|
|
|
83
83
|
command: string;
|
|
84
84
|
fullOutputPath?: string;
|
|
85
85
|
killed: boolean;
|
|
86
|
+
launch_kind: "tool";
|
|
86
87
|
stderrBytes?: number;
|
|
87
88
|
stderrCapturedBytes?: number;
|
|
88
89
|
stderrFile?: string;
|
|
@@ -1136,6 +1137,7 @@ export async function executeRegisteredTool(
|
|
|
1136
1137
|
command,
|
|
1137
1138
|
fullOutputPath: result.stdoutFile ?? formatted.fullOutputPath,
|
|
1138
1139
|
killed: result.killed,
|
|
1140
|
+
launch_kind: "tool",
|
|
1139
1141
|
...getCaptureDetails(result),
|
|
1140
1142
|
...(executed.branches.length > 0 ? { branches: executed.branches } : {}),
|
|
1141
1143
|
...(executed.failures.length > 0
|
package/lib/extension-runtime.ts
CHANGED
|
@@ -9,6 +9,7 @@ import * as CommandTemplates from "./command-templates.ts";
|
|
|
9
9
|
import * as Paths from "./paths.ts";
|
|
10
10
|
import * as Pi from "./pi.ts";
|
|
11
11
|
import * as Prompts from "./prompts.ts";
|
|
12
|
+
import * as RecipeResolution from "./recipes-context.ts";
|
|
12
13
|
import * as RecipesReferences from "./recipes-references.ts";
|
|
13
14
|
import * as RunUiRuntime from "./run-ui-runtime.ts";
|
|
14
15
|
import * as Runtime from "./runtime.ts";
|
|
@@ -34,14 +35,21 @@ export function createActorExtensionRuntime(
|
|
|
34
35
|
pi: Pi.ExtensionAPI,
|
|
35
36
|
): ActorExtensionRuntime {
|
|
36
37
|
let activeRunContext: Pi.ExtensionContext | undefined;
|
|
37
|
-
const
|
|
38
|
+
const recipeResolutionContextsBySession = new Map<
|
|
38
39
|
string,
|
|
39
|
-
|
|
40
|
+
RecipeResolution.RecipeResolutionContext
|
|
40
41
|
>();
|
|
41
42
|
const getRunOwnerId = Pi.getSessionId;
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const getRecipeResolutionContext = (ctx: Pi.ExtensionContext) => {
|
|
44
|
+
const sessionId = getRunOwnerId(ctx);
|
|
45
|
+
const resolutionContext = recipeResolutionContextsBySession.get(sessionId);
|
|
46
|
+
if (!resolutionContext) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Recipe resolution context is unavailable for session ${sessionId}.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return resolutionContext;
|
|
52
|
+
};
|
|
45
53
|
const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
|
|
46
54
|
getActiveContext: () => activeRunContext,
|
|
47
55
|
getRunOwnerId,
|
|
@@ -67,7 +75,7 @@ export function createActorExtensionRuntime(
|
|
|
67
75
|
if (ctx && typeof ctx === "object") {
|
|
68
76
|
nextArgs[4] = {
|
|
69
77
|
...(ctx as Record<string, unknown>),
|
|
70
|
-
|
|
78
|
+
recipeResolutionContext: getRecipeResolutionContext(
|
|
71
79
|
ctx as Pi.ExtensionContext,
|
|
72
80
|
),
|
|
73
81
|
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
@@ -85,6 +93,7 @@ export function createActorExtensionRuntime(
|
|
|
85
93
|
configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
|
|
86
94
|
exec: CommandTemplates.execCommandTemplate,
|
|
87
95
|
getActiveTools: () => pi.getActiveTools(),
|
|
96
|
+
getAllTools: () => pi.getAllTools(),
|
|
88
97
|
registerTool: (definition) => {
|
|
89
98
|
const wrapped = withCurrentThinkingContext(definition);
|
|
90
99
|
actorToolDefinitions.set(wrapped.name, wrapped);
|
|
@@ -93,13 +102,22 @@ export function createActorExtensionRuntime(
|
|
|
93
102
|
reservedToolNames: Tools.RESERVED_TOOL_NAMES,
|
|
94
103
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
95
104
|
});
|
|
96
|
-
const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime
|
|
105
|
+
const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime, {
|
|
106
|
+
getResolutionContext: () =>
|
|
107
|
+
activeRunContext
|
|
108
|
+
? getRecipeResolutionContext(activeRunContext)
|
|
109
|
+
: undefined,
|
|
110
|
+
});
|
|
97
111
|
return {
|
|
98
112
|
beforeAgentStart(systemPrompt, skills, ctx) {
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
const sessionId = getRunOwnerId(ctx);
|
|
114
|
+
const resolutionContext = RecipeResolution.createRecipeResolutionContext(
|
|
115
|
+
sessionId,
|
|
116
|
+
ctx.cwd,
|
|
101
117
|
RecipesReferences.createActiveSkillRecipeContext(skills),
|
|
102
118
|
);
|
|
119
|
+
recipeResolutionContextsBySession.set(sessionId, resolutionContext);
|
|
120
|
+
runtime.loadTools(ctx, resolutionContext);
|
|
103
121
|
return {
|
|
104
122
|
systemPrompt: `${systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
|
|
105
123
|
};
|
|
@@ -113,16 +131,17 @@ export function createActorExtensionRuntime(
|
|
|
113
131
|
if (activeRunContext === ctx) automaticReview.schedule();
|
|
114
132
|
},
|
|
115
133
|
onSessionShutdown(reason, ctx) {
|
|
116
|
-
|
|
134
|
+
recipeResolutionContextsBySession.delete(getRunOwnerId(ctx));
|
|
117
135
|
activeRunContext = undefined;
|
|
118
136
|
automaticReview.close();
|
|
119
137
|
recipeReload.close();
|
|
120
138
|
runUiRuntime.shutdown(reason, ctx);
|
|
121
139
|
},
|
|
122
140
|
async onSessionStart(ctx) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
const sessionId = getRunOwnerId(ctx);
|
|
142
|
+
recipeResolutionContextsBySession.set(
|
|
143
|
+
sessionId,
|
|
144
|
+
RecipeResolution.createEmptyRecipeResolutionContext(sessionId, ctx.cwd),
|
|
126
145
|
);
|
|
127
146
|
ctx.ui.setWidget("zz-pi-actors-comms", undefined);
|
|
128
147
|
activeRunContext = ctx;
|
|
@@ -132,7 +151,6 @@ export function createActorExtensionRuntime(
|
|
|
132
151
|
await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
|
|
133
152
|
if (activeRunContext !== ctx) return;
|
|
134
153
|
automaticReview.start(ctx);
|
|
135
|
-
runtime.loadTools(ctx);
|
|
136
154
|
runUiRuntime.start(ctx);
|
|
137
155
|
recipeReload.watch(ctx);
|
|
138
156
|
},
|
|
@@ -148,6 +166,7 @@ export function createActorExtensionRuntime(
|
|
|
148
166
|
runtime.getTools(),
|
|
149
167
|
(activeName) => actorToolDefinitions.get(activeName),
|
|
150
168
|
),
|
|
169
|
+
getRuntimeToolStatus: runtime.getToolStatus,
|
|
151
170
|
handleRuntimeControl: automaticReview.handleControl,
|
|
152
171
|
registryRuntime: runtime,
|
|
153
172
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
package/lib/inspector.ts
CHANGED
|
@@ -183,6 +183,7 @@ export function readActorInspectorRecipe(
|
|
|
183
183
|
logical_reference: primaryLogicalReference,
|
|
184
184
|
...(typeof primary?.skill === "string" ? { skill: primary.skill } : {}),
|
|
185
185
|
source_kind: primarySourceKind,
|
|
186
|
+
launch_kind: meta.launch_kind ?? meta.launch_source,
|
|
186
187
|
launch_source: meta.launch_source,
|
|
187
188
|
}),
|
|
188
189
|
launch: redactedRecord({
|
package/lib/prompts.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const REGISTER_TOOL_PROMPT_SNIPPET =
|
|
|
14
14
|
|
|
15
15
|
export const REGISTER_TOOL_GUIDELINES = [
|
|
16
16
|
"Use register_tool to wrap trusted local commands, scripts, programs, libraries, or template recipes as persistent pi tools.",
|
|
17
|
-
"After register_tool succeeds,
|
|
17
|
+
"After register_tool succeeds, trust its callable_now and activation result; persisted definitions remain available for admission after reload.",
|
|
18
18
|
'Set template=null or template="" in register_tool to delete a persisted tool.',
|
|
19
19
|
"Set update=true in register_tool to overwrite an existing tool registration.",
|
|
20
20
|
];
|
|
@@ -24,7 +24,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
24
24
|
- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
|
|
25
25
|
- Command templates stay sync and shell-free: string leaves split into executable + argv, infer .js/.mjs through node→bun→deno run and .sh through bash, and treat operators such as && as literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
|
|
26
26
|
- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
|
|
27
|
-
- ~/.pi/agent/recipes/*.json is actor muscle memory:
|
|
27
|
+
- ~/.pi/agent/recipes/*.json is actor muscle memory: valid admitted recipes are reconciled as tools across sessions; register_tool writes there and reports current activation truth.
|
|
28
28
|
- Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.
|
|
29
29
|
- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
|
|
30
30
|
- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
|