@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.
Files changed (50) hide show
  1. package/BACKLOG.md +582 -0
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +1 -1
  4. package/dist/lib/async-runs.d.ts +1 -0
  5. package/dist/lib/async-runs.js +4 -1
  6. package/dist/lib/execution.d.ts +1 -0
  7. package/dist/lib/execution.js +1 -0
  8. package/dist/lib/extension-runtime.js +25 -9
  9. package/dist/lib/inspector.js +1 -0
  10. package/dist/lib/prompts.d.ts +1 -1
  11. package/dist/lib/prompts.js +2 -2
  12. package/dist/lib/recipes-context.d.ts +12 -3
  13. package/dist/lib/recipes-context.js +28 -4
  14. package/dist/lib/recipes-discovery.d.ts +18 -0
  15. package/dist/lib/recipes-discovery.js +95 -22
  16. package/dist/lib/recipes-references.d.ts +18 -0
  17. package/dist/lib/recipes-references.js +120 -33
  18. package/dist/lib/registry.d.ts +14 -1
  19. package/dist/lib/registry.js +110 -87
  20. package/dist/lib/runtime.d.ts +30 -3
  21. package/dist/lib/runtime.js +73 -9
  22. package/dist/lib/tools-inspect.d.ts +3 -0
  23. package/dist/lib/tools-inspect.js +40 -20
  24. package/dist/lib/tools-local.d.ts +2 -2
  25. package/dist/lib/tools-local.js +5 -2
  26. package/dist/lib/tools-response.js +2 -0
  27. package/dist/lib/tools-spawn.d.ts +2 -2
  28. package/dist/lib/tools-spawn.js +1 -1
  29. package/dist/lib/tools.d.ts +4 -1
  30. package/dist/lib/tools.js +2 -0
  31. package/dist/skills/actors/SKILL.md +3 -3
  32. package/docs/template-recipes.md +3 -1
  33. package/docs/tool-registry.md +9 -3
  34. package/lib/async-runs.ts +5 -1
  35. package/lib/execution.ts +2 -0
  36. package/lib/extension-runtime.ts +33 -14
  37. package/lib/inspector.ts +1 -0
  38. package/lib/prompts.ts +2 -2
  39. package/lib/recipes-context.ts +49 -4
  40. package/lib/recipes-discovery.ts +164 -24
  41. package/lib/recipes-references.ts +163 -39
  42. package/lib/registry.ts +185 -94
  43. package/lib/runtime.ts +108 -12
  44. package/lib/tools-inspect.ts +57 -23
  45. package/lib/tools-local.ts +9 -3
  46. package/lib/tools-response.ts +2 -0
  47. package/lib/tools-spawn.ts +3 -2
  48. package/lib/tools.ts +4 -1
  49. package/package.json +1 -1
  50. package/skills/actors/SKILL.md +3 -3
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { statSync } from "node:fs";
8
- import { join } from "node:path";
8
+ import { isAbsolute, join, relative } from "node:path";
9
9
 
10
10
  import * as AsyncRuns from "./async-runs.ts";
11
11
  import * as ControlProjection from "./control-projection.ts";
@@ -13,12 +13,14 @@ import * as Inspector from "./inspector.ts";
13
13
  import * as Limits from "./limits.ts";
14
14
  import { computeRunControlCapacity } from "./run-evidence-policy.ts";
15
15
  import * as Paths from "./paths.ts";
16
+ import type * as RecipeResolution from "./recipes-context.ts";
16
17
  import * as RecipesDiscovery from "./recipes-discovery.ts";
17
18
  import * as RecipesReferences from "./recipes-references.ts";
18
19
  import * as ReviewDiagnostics from "./review-diagnostics.ts";
19
20
  import * as RunsControls from "./runs-controls.ts";
20
21
  import * as RunControlDelivery from "./runs-control-delivery.ts";
21
22
  import * as RunsTrace from "./runs-trace.ts";
23
+ import * as Runtime from "./runtime.ts";
22
24
  import * as RuntimeIdentity from "./runtime-identity.ts";
23
25
  import * as RuntimeTriage from "./runtime-triage.ts";
24
26
  import * as Schema from "./schema.ts";
@@ -32,8 +34,10 @@ const maybeJsonText = ToolsResponse.maybeJsonText;
32
34
  export interface InspectToolDeps<TContext = unknown> {
33
35
  getRunStatus?: (runOrDir: string) => Record<string, any>;
34
36
  getTool?: (name: string) => any | undefined;
37
+ getToolStatus?: (name: string) => Record<string, unknown> | undefined;
35
38
  listRuns?: () => Array<Record<string, any>>;
36
39
  recipeRoot?: string;
40
+ registryStatus?: () => Runtime.RecipeRegistryStatus;
37
41
  }
38
42
 
39
43
  function runtimeStatus(): Record<string, unknown> {
@@ -191,6 +195,19 @@ function inspectRuntime(
191
195
  throw new Error("inspect runtime supports view=status, view=runs, or view=triage.");
192
196
  }
193
197
 
198
+ function portableSkillRecipePath(
199
+ file: string,
200
+ skill: string,
201
+ namespaces: Record<string, string[]>,
202
+ ): string {
203
+ const root = namespaces[skill]?.[0];
204
+ if (!root) return `<active-skill:${skill}>`;
205
+ const relation = relative(root, file);
206
+ return !relation.startsWith("..") && !isAbsolute(relation)
207
+ ? `<active-skill:${skill}>/${relation.replaceAll("\\", "/")}`
208
+ : `<active-skill:${skill}>`;
209
+ }
210
+
194
211
  function inspectRecipes(
195
212
  view: string,
196
213
  deps: InspectToolDeps,
@@ -214,37 +231,52 @@ function inspectRecipes(
214
231
  const discovered = RecipesDiscovery.discoverRecipeSources([
215
232
  { root: recipeRoot, defaultTool: true, mutableUsage: true },
216
233
  ]);
217
- const skillContext =
218
- context && typeof context === "object" && "activeSkillRecipeContext" in context
234
+ const resolutionContext =
235
+ context && typeof context === "object" && "recipeResolutionContext" in context
219
236
  ? (context as {
220
- activeSkillRecipeContext?: RecipesReferences.ActiveSkillRecipeContext;
221
- }).activeSkillRecipeContext
237
+ recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
238
+ }).recipeResolutionContext
222
239
  : undefined;
223
240
  const activeSkillContext =
224
- skillContext ?? RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
241
+ resolutionContext?.activeSkills ??
242
+ RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
225
243
  const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces(
226
244
  activeSkillContext,
227
245
  );
228
- let skillRecipeComponents: Array<Record<string, unknown>> = [];
229
- const skillRecipeComponentDiagnostics: Array<Record<string, unknown>> = [];
230
- try {
231
- skillRecipeComponents = RecipesReferences.listActiveSkillRecipeComponents(
232
- activeSkillContext,
233
- ).map((component) => ({
234
- identity: component.identity,
235
- source_kind: "active_skill_component",
236
- skill: component.skill,
237
- stem: component.stem,
238
- imports: component.imports,
239
- }));
240
- } catch (error) {
241
- skillRecipeComponentDiagnostics.push({
242
- error: error instanceof Error ? error.message : String(error),
243
- });
244
- }
246
+ const skillInventory =
247
+ RecipesReferences.inventoryActiveSkillRecipeComponents(activeSkillContext);
248
+ const skillRecipeComponents = skillInventory.components.map((component) => ({
249
+ identity: component.identity,
250
+ source_kind: "active_skill_component",
251
+ skill: component.skill,
252
+ stem: component.stem,
253
+ imports: component.imports,
254
+ }));
255
+ const skillRecipeComponentDiagnostics = skillInventory.rejected.map(
256
+ (diagnostic) => ({
257
+ error: diagnostic.reason,
258
+ ...(diagnostic.file
259
+ ? {
260
+ file: portableSkillRecipePath(
261
+ diagnostic.file,
262
+ diagnostic.skill,
263
+ skillRecipeNamespaces,
264
+ ),
265
+ }
266
+ : {}),
267
+ skill: diagnostic.skill,
268
+ ...(diagnostic.stem ? { stem: diagnostic.stem } : {}),
269
+ }),
270
+ );
245
271
  const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
246
272
  const summary = {
247
273
  ...discoverySummary,
274
+ ...(deps.registryStatus
275
+ ? {
276
+ ...deps.registryStatus(),
277
+ watched_root: "~/.pi/agent/recipes",
278
+ }
279
+ : {}),
248
280
  active: Array.isArray(discoverySummary.active)
249
281
  ? discoverySummary.active.map((entry) => ({
250
282
  ...(entry as Record<string, unknown>),
@@ -253,6 +285,7 @@ function inspectRecipes(
253
285
  : discoverySummary.active,
254
286
  skill_recipe_components: skillRecipeComponents,
255
287
  skill_recipe_component_diagnostics: skillRecipeComponentDiagnostics,
288
+ skill_recipe_catalog_partial: skillInventory.partial,
256
289
  active_skill_recipe_identities: Object.keys(skillRecipeNamespaces).sort(),
257
290
  skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
258
291
  .filter(([, roots]) => roots.length > 1)
@@ -369,6 +402,7 @@ function inspectTool(
369
402
  if (!tool) throw new Error(`registered tool not found: ${name}`);
370
403
  return {
371
404
  name,
405
+ ...(view === "status" ? deps.getToolStatus?.(name) : {}),
372
406
  description: tool.description,
373
407
  parameters: tool.parameters,
374
408
  promptSnippet: tool.promptSnippet,
@@ -10,6 +10,7 @@ import * as ModelContext from "./model-context.ts";
10
10
  import type { RegisteredTool } from "./config.ts";
11
11
  import * as Execution from "./execution.ts";
12
12
  import * as Prompts from "./prompts.ts";
13
+ import type * as RecipeResolution from "./recipes-context.ts";
13
14
  import * as RecipesReferences from "./recipes-references.ts";
14
15
  import * as RecipesUsage from "./recipes-usage.ts";
15
16
  import * as Schema from "./schema.ts";
@@ -18,7 +19,7 @@ import * as ToolsResponse from "./tools-response.ts";
18
19
  type JsonSchema = Schema.JsonSchema;
19
20
 
20
21
  export interface RuntimeToolContext extends ModelContext.CurrentModelContext {
21
- activeSkillRecipeContext?: RecipesReferences.ActiveSkillRecipeContext;
22
+ recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
22
23
  cwd: string;
23
24
  sessionManager?: { getSessionId?: () => string };
24
25
  }
@@ -67,7 +68,11 @@ function formatRuntimeToolUsageHint(
67
68
  required: string[],
68
69
  includeRunId: boolean,
69
70
  ): string {
70
- const optional = cfg.args.filter((arg) => !required.includes(arg));
71
+ const optional = cfg.args.filter(
72
+ (arg) =>
73
+ !RecipesReferences.isRuntimeOwnedRecipeInput(arg) &&
74
+ !required.includes(arg),
75
+ );
71
76
  const exampleDefaults = {
72
77
  ...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
73
78
  ...cfg.defaults,
@@ -176,6 +181,7 @@ export function createRuntimeToolDefinition(
176
181
  ? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
177
182
  : Schema.getRequiredToolArgNames(requiredTemplateConfig);
178
183
  for (const arg of cfg.args) {
184
+ if (RecipesReferences.isRuntimeOwnedRecipeInput(arg)) continue;
179
185
  paramSchema[arg] = typedArgSchema(arg, cfg.argTypes?.[arg]);
180
186
  if (requiredArgs.has(arg)) required.push(arg);
181
187
  }
@@ -244,7 +250,7 @@ export function createRuntimeToolDefinition(
244
250
  values: resolveRecipeToolValues(cfg, values, ctx),
245
251
  },
246
252
  ctx.cwd,
247
- { skillContext: ctx.activeSkillRecipeContext },
253
+ { skillContext: ctx.recipeResolutionContext?.activeSkills },
248
254
  );
249
255
  return {
250
256
  content: [
@@ -106,6 +106,8 @@ export function compactAsyncRunStatus(value: unknown): string {
106
106
  const result = asRecord(status.result);
107
107
  const run = String(status.run ?? "<unknown>");
108
108
  const tokens = [`run=${run}`, `status=${String(status.status ?? "unknown")}`];
109
+ if (status.launch_kind)
110
+ tokens.push(`launch_kind=${String(status.launch_kind)}`);
109
111
  tokens.push(...compactModelPolicy(status.model_policy ?? progress.model_policy));
110
112
  if (status.tool) tokens.push(`tool=${String(status.tool)}`);
111
113
  if (status.recipe) tokens.push(`recipe=${String(status.recipe)}`);
@@ -11,13 +11,14 @@ import * as AsyncRuns from "./async-runs.ts";
11
11
  import { withFileMutationLock } from "./file-state.ts";
12
12
  import * as ModelContext from "./model-context.ts";
13
13
  import * as Paths from "./paths.ts";
14
+ import type * as RecipeResolution from "./recipes-context.ts";
14
15
  import * as RecipesReferences from "./recipes-references.ts";
15
16
  import * as RecipesUsage from "./recipes-usage.ts";
16
17
  import * as Schema from "./schema.ts";
17
18
  import * as ToolsResponse from "./tools-response.ts";
18
19
 
19
20
  export interface SpawnToolContext extends ModelContext.CurrentModelContext {
20
- activeSkillRecipeContext?: RecipesReferences.ActiveSkillRecipeContext;
21
+ recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
21
22
  cwd: string;
22
23
  sessionManager?: { getSessionId?: () => string };
23
24
  }
@@ -184,7 +185,7 @@ export function createSpawnToolDefinition<
184
185
  : {}),
185
186
  },
186
187
  ctx.cwd,
187
- { skillContext: ctx.activeSkillRecipeContext },
188
+ { skillContext: ctx.recipeResolutionContext?.activeSkills },
188
189
  );
189
190
  const draftRecipe = writeSpawnDraftRecipe(input, meta);
190
191
  const nextActions = ToolsResponse.runNextActions(meta.run);
package/lib/tools.ts CHANGED
@@ -25,6 +25,7 @@ export interface CoreActorToolDefinitionDeps<
25
25
  configPath: string;
26
26
  getActiveTools: () => string[];
27
27
  getRuntimeTool: (name: string) => unknown;
28
+ getRuntimeToolStatus: (name: string) => Record<string, unknown> | undefined;
28
29
  handleRuntimeControl?: (
29
30
  action: string,
30
31
  input: unknown,
@@ -32,7 +33,7 @@ export interface CoreActorToolDefinitionDeps<
32
33
  registryRuntime: Pick<
33
34
  RegisterToolRuntimeDeps<TContext>,
34
35
  "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool"
35
- >;
36
+ > & { getStatus(): import("./runtime.ts").RecipeRegistryStatus };
36
37
  setActiveTools: (toolNames: string[]) => void;
37
38
  }
38
39
 
@@ -78,6 +79,8 @@ export function createCoreActorToolDefinitions<
78
79
  }),
79
80
  ToolsInspect.createInspectToolDefinition<TContext>({
80
81
  getTool: (name) => deps.getRuntimeTool(name),
82
+ getToolStatus: deps.getRuntimeToolStatus,
83
+ registryStatus: deps.registryRuntime.getStatus,
81
84
  }),
82
85
  ];
83
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.46.0",
3
+ "version": "0.46.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -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