@llblab/pi-actors 0.29.3 → 0.30.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.
@@ -7,7 +7,165 @@ import * as path from "node:path";
7
7
  import { visibleWidth } from "@earendil-works/pi-tui";
8
8
  import * as Limits from "./limits.js";
9
9
  import * as Paths from "./paths.js";
10
- import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.js";
10
+ import { readJsonFileResilient, readJsonlFileResilient, } from "./state-readers.js";
11
+ export const DEFAULT_ACTOR_INSPECTOR_ROWS = 12;
12
+ export const ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS = {
13
+ filter: "Filter actor inspector rows: all, room, direct, broadcast, unread, branch <name>, mention <text>",
14
+ inspect: "Inspect actor message by visible number",
15
+ toggle: "Toggle actor inspector widget; optional row count",
16
+ };
17
+ export function createActorInspectorControllerState() {
18
+ return {
19
+ readKeys: new Set(),
20
+ roomLimitPerRun: DEFAULT_ACTOR_INSPECTOR_ROWS,
21
+ rows: DEFAULT_ACTOR_INSPECTOR_ROWS,
22
+ unreadOnly: false,
23
+ visible: false,
24
+ };
25
+ }
26
+ export function getActorInspectorPreviewOptions(state, ownerId) {
27
+ return {
28
+ channels: state.channels,
29
+ currentRunOnly: true,
30
+ branch: state.branch,
31
+ mention: state.mention,
32
+ ownerId,
33
+ readKeys: state.readKeys,
34
+ roomLimitPerRun: state.roomLimitPerRun,
35
+ unreadOnly: state.unreadOnly,
36
+ };
37
+ }
38
+ export function handleActorInspectorToggle(state, args) {
39
+ const raw = Array.isArray(args) ? args[0] : String(args ?? "");
40
+ if (String(raw).trim()) {
41
+ const rows = Number.parseInt(String(raw), 10);
42
+ if (!Number.isFinite(rows) || rows <= 0) {
43
+ return {
44
+ notify: "Usage: /actors-inspector-toggle [rows] where rows > 0",
45
+ type: "warning",
46
+ update: false,
47
+ };
48
+ }
49
+ state.rows = rows;
50
+ state.roomLimitPerRun = rows;
51
+ state.selectedSequence = undefined;
52
+ state.visible = true;
53
+ return {
54
+ notify: `Actor inspector rows ${rows}`,
55
+ type: "info",
56
+ update: true,
57
+ };
58
+ }
59
+ if (state.selectedSequence !== undefined) {
60
+ state.selectedSequence = undefined;
61
+ state.visible = true;
62
+ return { notify: "Actor inspector table", type: "info", update: true };
63
+ }
64
+ if (state.visible)
65
+ state.visible = false;
66
+ else {
67
+ state.rows = DEFAULT_ACTOR_INSPECTOR_ROWS;
68
+ state.roomLimitPerRun = DEFAULT_ACTOR_INSPECTOR_ROWS;
69
+ state.visible = true;
70
+ }
71
+ return {
72
+ notify: `Actor inspector ${state.visible ? "shown" : "hidden"}`,
73
+ type: "info",
74
+ update: true,
75
+ };
76
+ }
77
+ export function handleActorInspectorFilter(state, args) {
78
+ const parts = Array.isArray(args)
79
+ ? args.map(String)
80
+ : String(args ?? "").split(/\s+/);
81
+ const mode = (parts[0] ?? "").trim().toLowerCase();
82
+ if (!mode || mode === "all" || mode === "clear") {
83
+ state.channels = undefined;
84
+ state.mention = undefined;
85
+ state.branch = undefined;
86
+ state.unreadOnly = false;
87
+ }
88
+ else if (mode === "room" || mode === "direct" || mode === "broadcast") {
89
+ state.channels = [mode];
90
+ state.mention = undefined;
91
+ }
92
+ else if (mode === "unread") {
93
+ state.unreadOnly = true;
94
+ }
95
+ else if (mode === "branch" || mode === "current-branch") {
96
+ const branch = parts.slice(1).join(" ").trim();
97
+ if (!branch) {
98
+ return {
99
+ notify: `Usage: /actors-inspector-filter ${mode} <branch-name>`,
100
+ type: "warning",
101
+ update: false,
102
+ };
103
+ }
104
+ state.branch = branch;
105
+ }
106
+ else if (mode === "mention") {
107
+ const mention = parts.slice(1).join(" ").trim();
108
+ if (!mention) {
109
+ return {
110
+ notify: "Usage: /actors-inspector-filter mention <text>",
111
+ type: "warning",
112
+ update: false,
113
+ };
114
+ }
115
+ state.channels = undefined;
116
+ state.mention = mention;
117
+ }
118
+ else {
119
+ return {
120
+ notify: "Usage: /actors-inspector-filter all|room|direct|broadcast|unread|branch <name>|mention <text>",
121
+ type: "warning",
122
+ update: false,
123
+ };
124
+ }
125
+ state.selectedSequence = undefined;
126
+ state.visible = true;
127
+ return {
128
+ notify: `Actor inspector filter ${mode || "all"}`,
129
+ type: "info",
130
+ update: true,
131
+ };
132
+ }
133
+ export function handleActorInspectorInspect(state, args, previews) {
134
+ const raw = Array.isArray(args) ? args[0] : String(args ?? "");
135
+ return selectActorInspectorSequence(state, Number.parseInt(String(raw), 10), previews);
136
+ }
137
+ export function selectActorInspectorSequence(state, sequence, previews) {
138
+ if (!Number.isFinite(sequence) || sequence <= 0) {
139
+ return {
140
+ notify: "Usage: /actors-inspect <number>",
141
+ type: "warning",
142
+ update: false,
143
+ };
144
+ }
145
+ const preview = previews.find((item) => item.sequence === sequence);
146
+ if (preview)
147
+ state.readKeys.add(inspectorPreviewReadKey(preview));
148
+ state.selectedSequence = sequence;
149
+ state.visible = true;
150
+ return {
151
+ notify: `Actor inspect item ${sequence}`,
152
+ type: "info",
153
+ update: true,
154
+ };
155
+ }
156
+ export function renderActorInspectorPanel(input) {
157
+ const previews = readActorInspectorPreviews(input.stateRoot, input.state.rows, getActorInspectorPreviewOptions(input.state, input.ownerId));
158
+ const rows = (input.state.selectedSequence !== undefined
159
+ ? renderInspectorItemView(previews, input.width, input.style, {
160
+ sequence: input.state.selectedSequence,
161
+ })
162
+ : renderInspectorWidget(previews, input.width, input.style)) ?? [];
163
+ const run = previews[0]?.run;
164
+ const roster = input.state.selectedSequence === undefined && run
165
+ ? renderInspectorRosterPanel(readActorInspectorRoster(input.stateRoot, run), input.width, input.style)
166
+ : undefined;
167
+ return roster ? [...roster, ...rows] : rows;
168
+ }
11
169
  function asRecord(value) {
12
170
  return value && typeof value === "object" && !Array.isArray(value)
13
171
  ? value
@@ -35,6 +35,32 @@ export interface RunSummary {
35
35
  runs: RunObservation[];
36
36
  total: number;
37
37
  }
38
+ export interface RunUiObservationState {
39
+ eventLines: Map<string, number>;
40
+ frame: number;
41
+ observed: Map<string, RunObservedStatus>;
42
+ outboxEventIds: Map<string, Set<string>>;
43
+ }
44
+ export interface RunUiSnapshot {
45
+ outboxEvents: RunOutboxEvent[];
46
+ status: string | undefined;
47
+ summary: RunSummary;
48
+ transitions: RunTransition[];
49
+ }
50
+ export interface RunUiNotificationSink {
51
+ notify(message: string, level: "info" | "warning" | "error"): void;
52
+ sendFollowUp(message: {
53
+ customType: string;
54
+ content: string;
55
+ display: true;
56
+ details: unknown;
57
+ }): void;
58
+ }
59
+ export declare function createRunUiObservationState(): RunUiObservationState;
60
+ export declare function readRunUiSnapshot(state: RunUiObservationState, ownerId: string): RunUiSnapshot;
61
+ export declare function pruneRunUiObservationState(state: RunUiObservationState, snapshot: Pick<RunUiSnapshot, "summary" | "transitions">): void;
62
+ export declare function deliverRunTransitionNotifications(transitions: RunTransition[], sink: RunUiNotificationSink): void;
63
+ export declare function deliverRunOutboxNotifications(events: RunOutboxEvent[], sink: RunUiNotificationSink): void;
38
64
  export interface RunRetirementCandidate {
39
65
  activeSubagents: number;
40
66
  childRuns: number;
@@ -49,6 +75,14 @@ export interface RunRetirementExecution {
49
75
  run: string;
50
76
  stateDir: string;
51
77
  }
78
+ export interface RunStateWatcher {
79
+ close(): void;
80
+ refresh(): void;
81
+ }
82
+ export declare function createRunStateWatcher(input: {
83
+ stateRoot?: string;
84
+ onChange: () => void;
85
+ }): RunStateWatcher;
52
86
  export interface RunRetirementExecutorOptions {
53
87
  attempted?: Set<string>;
54
88
  cancelRun: (candidate: RunRetirementCandidate) => Record<string, unknown>;
@@ -3,11 +3,113 @@
3
3
  * Zones: async runtime, ambient UI, diagnostics
4
4
  * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
5
5
  */
6
- import { existsSync, readdirSync, readFileSync } from "node:fs";
6
+ import { existsSync, readdirSync, readFileSync, watch, } from "node:fs";
7
7
  import { basename, dirname, isAbsolute, join, relative, resolve, } from "node:path";
8
8
  import * as AsyncRuns from "./async-runs.js";
9
9
  import * as Paths from "./paths.js";
10
10
  import { readJsonlFileResilient } from "./state-readers.js";
11
+ export function createRunUiObservationState() {
12
+ return {
13
+ eventLines: new Map(),
14
+ frame: 0,
15
+ observed: new Map(),
16
+ outboxEventIds: new Map(),
17
+ };
18
+ }
19
+ export function readRunUiSnapshot(state, ownerId) {
20
+ const summary = summarizeRuns(undefined, ownerId);
21
+ const status = renderRunStatus(summary, state.frame++);
22
+ return {
23
+ outboxEvents: detectRunOutboxEvents(state.eventLines, summary, state.outboxEventIds),
24
+ status,
25
+ summary,
26
+ transitions: detectRunTransitions(state.observed, summary),
27
+ };
28
+ }
29
+ export function pruneRunUiObservationState(state, snapshot) {
30
+ pruneRunObservationState(state.observed, state.eventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.outboxEventIds);
31
+ }
32
+ export function deliverRunTransitionNotifications(transitions, sink) {
33
+ for (const transition of transitions) {
34
+ if (!shouldNotifyRunTransition(transition))
35
+ continue;
36
+ const text = formatRunTransitionMessage(transition);
37
+ sink.notify(text, getRunTransitionNotificationType(transition));
38
+ if (!shouldSendRunTransitionFollowUp(transition))
39
+ continue;
40
+ sink.sendFollowUp({
41
+ customType: "pi-actors-run",
42
+ content: text,
43
+ display: true,
44
+ details: transition,
45
+ });
46
+ }
47
+ }
48
+ export function deliverRunOutboxNotifications(events, sink) {
49
+ for (const event of events) {
50
+ if (!shouldNotifyRunOutboxEvent(event))
51
+ continue;
52
+ const text = formatRunOutboxMessage(event);
53
+ sink.notify(text, getRunOutboxNotificationType(event));
54
+ if (!shouldSendRunOutboxFollowUp(event))
55
+ continue;
56
+ sink.sendFollowUp({
57
+ customType: "pi-actors-run-message",
58
+ content: text,
59
+ display: true,
60
+ details: event,
61
+ });
62
+ }
63
+ }
64
+ export function createRunStateWatcher(input) {
65
+ const stateRoot = input.stateRoot ?? Paths.getRunStateRoot();
66
+ let stateRootWatcher;
67
+ const runDirWatchers = new Map();
68
+ const close = () => {
69
+ stateRootWatcher?.close();
70
+ stateRootWatcher = undefined;
71
+ for (const watcher of runDirWatchers.values())
72
+ watcher.close();
73
+ runDirWatchers.clear();
74
+ };
75
+ const watchRunDir = (stateDir) => {
76
+ if (runDirWatchers.has(stateDir) || !existsSync(stateDir))
77
+ return;
78
+ try {
79
+ const watcher = watch(stateDir, input.onChange);
80
+ watcher.on("error", () => {
81
+ watcher.close();
82
+ runDirWatchers.delete(stateDir);
83
+ });
84
+ runDirWatchers.set(stateDir, watcher);
85
+ }
86
+ catch {
87
+ // Watching is best-effort; explicit inspect remains available.
88
+ }
89
+ };
90
+ function refresh() {
91
+ if (!existsSync(stateRoot))
92
+ return;
93
+ if (!stateRootWatcher) {
94
+ try {
95
+ stateRootWatcher = watch(stateRoot, input.onChange);
96
+ stateRootWatcher.on("error", () => {
97
+ stateRootWatcher?.close();
98
+ stateRootWatcher = undefined;
99
+ });
100
+ }
101
+ catch {
102
+ // Watching is best-effort; explicit inspect remains available.
103
+ }
104
+ }
105
+ for (const entry of readdirSync(stateRoot, { withFileTypes: true })) {
106
+ if (!entry.isDirectory())
107
+ continue;
108
+ watchRunDir(`${stateRoot}/${entry.name}`);
109
+ }
110
+ }
111
+ return { close, refresh };
112
+ }
11
113
  const TERMINAL = new Set([
12
114
  "done",
13
115
  "failed",
@@ -4,9 +4,18 @@
4
4
  * Owns agent directory, tools config, recipe root, and actor run state root resolution
5
5
  */
6
6
  export declare function getAgentDir(env?: Record<string, string | undefined>): string;
7
+ export interface ExtensionRuntimePaths {
8
+ configPath: string;
9
+ runStateRoot: string;
10
+ tempDir: string;
11
+ }
7
12
  export declare function getConfigPath(agentDir?: string): string;
8
13
  export declare function getExtensionTmpDir(agentDir?: string, extensionName?: string): string;
9
14
  export declare function getRunStateRoot(agentDir?: string): string;
15
+ export declare function getExtensionRuntimePaths(agentDir?: string): ExtensionRuntimePaths;
16
+ export declare const EXTENSION_RUNTIME_PATHS: ExtensionRuntimePaths;
17
+ export declare function getExtensionSkillsDir(extensionUrl: string): string;
18
+ export declare function getExistingExtensionSkillPaths(extensionUrl: string): string[];
10
19
  export declare function getRecipeRoot(agentDir?: string): string;
11
20
  export declare function getRecipeCandidateRoot(agentDir?: string): string;
12
21
  export declare function getPackagedRecipeRoot(): string;
package/dist/lib/paths.js CHANGED
@@ -21,6 +21,21 @@ export function getExtensionTmpDir(agentDir = getAgentDir(), extensionName = "pi
21
21
  export function getRunStateRoot(agentDir = getAgentDir()) {
22
22
  return join(getExtensionTmpDir(agentDir), "runs");
23
23
  }
24
+ export function getExtensionRuntimePaths(agentDir = getAgentDir()) {
25
+ return {
26
+ configPath: getConfigPath(agentDir),
27
+ runStateRoot: getRunStateRoot(agentDir),
28
+ tempDir: getExtensionTmpDir(agentDir),
29
+ };
30
+ }
31
+ export const EXTENSION_RUNTIME_PATHS = getExtensionRuntimePaths();
32
+ export function getExtensionSkillsDir(extensionUrl) {
33
+ return join(dirname(fileURLToPath(extensionUrl)), "skills");
34
+ }
35
+ export function getExistingExtensionSkillPaths(extensionUrl) {
36
+ const skillsDir = getExtensionSkillsDir(extensionUrl);
37
+ return existsSync(skillsDir) ? [skillsDir] : [];
38
+ }
24
39
  export function getRecipeRoot(agentDir = getAgentDir()) {
25
40
  return join(agentDir, "recipes");
26
41
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pi SDK adapter boundary.
3
+ * Zones: pi agent sdk boundary, extension host adapters
4
+ * Owns direct pi SDK imports and exposes narrow pi-actors-facing helpers/types for the composition root.
5
+ */
6
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
7
+ export type { ExtensionAPI, ExtensionContext };
8
+ export interface PiNotificationSink {
9
+ notify(message: string, level: "info" | "warning" | "error"): void;
10
+ sendFollowUp(message: {
11
+ customType: string;
12
+ content: string;
13
+ display: true;
14
+ details: unknown;
15
+ }): void;
16
+ }
17
+ export declare function getSessionId(ctx: ExtensionContext): string;
18
+ export declare function createNotificationSink(pi: ExtensionAPI, ctx: ExtensionContext): PiNotificationSink;
19
+ export declare function registerToolDefinitions(pi: ExtensionAPI, definitions: Iterable<unknown>): void;
package/dist/lib/pi.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pi SDK adapter boundary.
3
+ * Zones: pi agent sdk boundary, extension host adapters
4
+ * Owns direct pi SDK imports and exposes narrow pi-actors-facing helpers/types for the composition root.
5
+ */
6
+ export function getSessionId(ctx) {
7
+ return ctx.sessionManager.getSessionId();
8
+ }
9
+ export function createNotificationSink(pi, ctx) {
10
+ return {
11
+ notify: (message, level) => ctx.ui.notify(message, level),
12
+ sendFollowUp: (message) => pi.sendMessage(message, {
13
+ deliverAs: "followUp",
14
+ triggerTurn: true,
15
+ }),
16
+ };
17
+ }
18
+ export function registerToolDefinitions(pi, definitions) {
19
+ for (const definition of definitions)
20
+ pi.registerTool(definition);
21
+ }
@@ -33,4 +33,9 @@ export interface ToolRegistryRuntime {
33
33
  notify(ctx: RuntimeContext, message: string, type: "info" | "warning" | "error"): void;
34
34
  registerRuntimeTool(cfg: Config.RegisteredTool): void;
35
35
  }
36
+ export interface RecipeToolReloadWatcher {
37
+ close(): void;
38
+ watch(ctx: RuntimeContext): void;
39
+ }
36
40
  export declare function createAutoToolsRuntime(deps: ToolRegistryRuntimeDeps): ToolRegistryRuntime;
41
+ export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools">): RecipeToolReloadWatcher;
@@ -3,6 +3,7 @@
3
3
  * Zones: runtime coordination, registry loading, pi tools
4
4
  * Owns persisted tool loading, conflict detection, runtime registration, and warning notification
5
5
  */
6
+ import { existsSync, watch } from "node:fs";
6
7
  import * as Paths from "./paths.js";
7
8
  import * as RecipeDiscovery from "./recipe-discovery.js";
8
9
  import * as Tools from "./tools.js";
@@ -123,3 +124,50 @@ export function createAutoToolsRuntime(deps) {
123
124
  registerRuntimeTool,
124
125
  };
125
126
  }
127
+ export function createRecipeToolReloadWatcher(runtime) {
128
+ let reloadTimeout;
129
+ let rootWatcher;
130
+ let failureNotified = false;
131
+ const close = () => {
132
+ rootWatcher?.close();
133
+ rootWatcher = undefined;
134
+ if (reloadTimeout)
135
+ clearTimeout(reloadTimeout);
136
+ reloadTimeout = undefined;
137
+ };
138
+ const notifyFailure = (ctx) => {
139
+ if (failureNotified)
140
+ return;
141
+ failureNotified = true;
142
+ ctx.ui.notify("Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.", "warning");
143
+ };
144
+ const scheduleReload = (ctx) => {
145
+ failureNotified = false;
146
+ if (reloadTimeout)
147
+ clearTimeout(reloadTimeout);
148
+ reloadTimeout = setTimeout(() => {
149
+ runtime.loadTools(ctx);
150
+ ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
151
+ }, 150);
152
+ reloadTimeout.unref?.();
153
+ };
154
+ return {
155
+ close,
156
+ watch(ctx) {
157
+ const recipeRoot = Paths.getRecipeRoot();
158
+ if (rootWatcher || !existsSync(recipeRoot))
159
+ return;
160
+ try {
161
+ rootWatcher = watch(recipeRoot, () => scheduleReload(ctx));
162
+ rootWatcher.on("error", () => {
163
+ rootWatcher?.close();
164
+ rootWatcher = undefined;
165
+ notifyFailure(ctx);
166
+ });
167
+ }
168
+ catch {
169
+ notifyFailure(ctx);
170
+ }
171
+ },
172
+ };
173
+ }
@@ -8,6 +8,14 @@ import * as Execution from "./execution.ts";
8
8
  import * as Registry from "./registry.ts";
9
9
  export type RegisterToolInput = Registry.RegisterToolInput;
10
10
  export type RegisterToolRuntimeDeps<TContext> = Registry.RegisterToolRuntimeDeps<TContext>;
11
+ export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
12
+ configPath: string;
13
+ getActiveTools: () => string[];
14
+ getRuntimeTool: (name: string) => unknown;
15
+ registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getExternalToolConflict" | "getTools" | "notify" | "registerRuntimeTool">;
16
+ setActiveTools: (toolNames: string[]) => void;
17
+ }
18
+ export declare const RESERVED_TOOL_NAMES: Set<string>;
11
19
  type JsonSchema = Record<string, unknown>;
12
20
  export declare function createRegisterToolDefinition<TContext>(deps: RegisterToolRuntimeDeps<TContext>): {
13
21
  name: string;
@@ -35,5 +43,6 @@ export interface ActorMessageToolDeps<TContext = unknown> {
35
43
  getTool?: (name: string) => any | undefined;
36
44
  }
37
45
  export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
46
+ export declare function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): any[];
38
47
  export declare function createRuntimeToolDefinition(cfg: RegisteredTool, exec: Execution.RegisteredToolExec): any;
39
48
  export {};
package/dist/lib/tools.js CHANGED
@@ -18,6 +18,19 @@ import * as RecipeReferences from "./recipe-references.js";
18
18
  import * as RecipeUsage from "./recipe-usage.js";
19
19
  import * as Registry from "./registry.js";
20
20
  import * as Schema from "./schema.js";
21
+ export const RESERVED_TOOL_NAMES = new Set([
22
+ "read",
23
+ "write",
24
+ "edit",
25
+ "bash",
26
+ "find",
27
+ "grep",
28
+ "ls",
29
+ "register_tool",
30
+ "message",
31
+ "spawn",
32
+ "inspect",
33
+ ]);
21
34
  function stringSchema(description) {
22
35
  return { description, type: "string" };
23
36
  }
@@ -359,9 +372,15 @@ function summarizeOtherSessions(currentSession, allRuns) {
359
372
  }
360
373
  function compactSessionRuns(session, runs, summary = {}) {
361
374
  const suffix = [
362
- summary.other_sessions !== undefined ? `other_sessions=${String(summary.other_sessions)}` : "",
363
- summary.other_runs !== undefined ? `other_runs=${String(summary.other_runs)}` : "",
364
- ].filter(Boolean).join(" ");
375
+ summary.other_sessions !== undefined
376
+ ? `other_sessions=${String(summary.other_sessions)}`
377
+ : "",
378
+ summary.other_runs !== undefined
379
+ ? `other_runs=${String(summary.other_runs)}`
380
+ : "",
381
+ ]
382
+ .filter(Boolean)
383
+ .join(" ");
365
384
  if (runs.length === 0)
366
385
  return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
367
386
  return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
@@ -578,7 +597,9 @@ function formatToolActorFailure(tool, message, params, error) {
578
597
  });
579
598
  }
580
599
  function shadowedRecipeLaunchDiagnostic(recipe) {
581
- if (typeof recipe !== "string" || recipe.includes("/") || recipe.includes("~"))
600
+ if (typeof recipe !== "string" ||
601
+ recipe.includes("/") ||
602
+ recipe.includes("~"))
582
603
  return undefined;
583
604
  const discovery = RecipeDiscovery.discoverRecipeSources([
584
605
  { root: Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
@@ -604,7 +625,9 @@ function writeSpawnCandidateRecipe(input, meta) {
604
625
  if (process.env.NODE_TEST_CONTEXT &&
605
626
  process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1")
606
627
  return undefined;
607
- if (input.template === undefined || input.file !== undefined || input.recipe !== undefined)
628
+ if (input.template === undefined ||
629
+ input.file !== undefined ||
630
+ input.recipe !== undefined)
608
631
  return undefined;
609
632
  const root = Paths.getRecipeCandidateRoot();
610
633
  mkdirSync(root, { recursive: true });
@@ -656,12 +679,15 @@ async function routeBranchEnvelope(stateDir, runId, recipient, message, _options
656
679
  return await AsyncRuns.sendRunMessage(runId, JSON.stringify(branchMessage));
657
680
  }
658
681
  catch (error) {
659
- const record = error && typeof error === "object" ? error : {};
682
+ const record = error && typeof error === "object"
683
+ ? error
684
+ : {};
660
685
  if (record.queued === true) {
661
686
  return {
662
687
  control_path: record.control_path,
663
688
  control_type: record.control_type,
664
- delivery_error: record.delivery_error ?? (error instanceof Error ? error.message : String(error)),
689
+ delivery_error: record.delivery_error ??
690
+ (error instanceof Error ? error.message : String(error)),
665
691
  inbox_id: record.inbox_id,
666
692
  queued: true,
667
693
  run: runId,
@@ -805,7 +831,10 @@ export function createInspectToolDefinition(deps = {}) {
805
831
  const target = String(input.target ?? "");
806
832
  const view = String(input.view ?? "");
807
833
  if (target === "recipes" || target === "recipe-registry") {
808
- if (view !== "status" && view !== "summary" && view !== "doctor" && view !== "imports") {
834
+ if (view !== "status" &&
835
+ view !== "summary" &&
836
+ view !== "doctor" &&
837
+ view !== "imports") {
809
838
  throw new Error("inspect recipes supports view=status, view=summary, view=doctor, or view=imports.");
810
839
  }
811
840
  const discovered = RecipeDiscovery.discoverRecipeSources([
@@ -1257,6 +1286,27 @@ export function createActorMessageToolDefinition(deps = {}) {
1257
1286
  },
1258
1287
  };
1259
1288
  }
1289
+ export function createCoreActorToolDefinitions(deps) {
1290
+ return [
1291
+ createRegisterToolDefinition({
1292
+ configPath: deps.configPath,
1293
+ getActiveTools: deps.getActiveTools,
1294
+ getExternalToolConflict: deps.registryRuntime.getExternalToolConflict,
1295
+ getTools: deps.registryRuntime.getTools,
1296
+ notify: deps.registryRuntime.notify,
1297
+ registerRuntimeTool: deps.registryRuntime.registerRuntimeTool,
1298
+ reservedToolNames: RESERVED_TOOL_NAMES,
1299
+ setActiveTools: deps.setActiveTools,
1300
+ }),
1301
+ createSpawnToolDefinition(),
1302
+ createActorMessageToolDefinition({
1303
+ getTool: (name) => deps.getRuntimeTool(name),
1304
+ }),
1305
+ createInspectToolDefinition({
1306
+ getTool: (name) => deps.getRuntimeTool(name),
1307
+ }),
1308
+ ];
1309
+ }
1260
1310
  export function createRuntimeToolDefinition(cfg, exec) {
1261
1311
  const paramSchema = {};
1262
1312
  const required = [];
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.29.3
5
+ version: 0.30.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -2,7 +2,7 @@
2
2
  name: swarm
3
3
  description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
4
4
  metadata:
5
- version: 0.29.3
5
+ version: 0.30.0
6
6
  ---
7
7
 
8
8
  # Swarm