@llblab/pi-actors 0.45.0 → 0.45.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/AGENTS.md CHANGED
@@ -44,6 +44,7 @@ Pi host
44
44
 
45
45
  ## Key Domains
46
46
 
47
+ - `extension-runtime.ts`: low-level Pi session lifecycle, tool adaptation, and runtime service composition behind the thin `index.ts` event-registration root.
47
48
  - `command-templates.ts`: portable synchronous execution graph.
48
49
  - `recipes-references.ts`, `recipes-discovery.ts`, `recipe-control.ts`: Recipe resolution, imports, shadowing, and Control declarations.
49
50
  - `async-runs.ts`: lifecycle facade.
@@ -135,6 +136,7 @@ When a deferred Run result gates the next step, wait for its terminal follow-up.
135
136
  ## Documentation and Release Discipline
136
137
 
137
138
  - Keep published text portable: use `~`, `<repo>`, or relative paths.
139
+ - Keep executable JavaScript and TypeScript blocks compact: no blank lines inside function bodies or control-flow blocks. Structural object, type, interface, class, and module bodies are exempt.
138
140
  - Update `skills/actors/SKILL.md` when durable operating mechanics change.
139
141
  - Keep `skills/swarm/SKILL.md` focused on multi-agent methodology rather than kernel internals.
140
142
  - Recipe `description` is optional because discovery supplies stable fallback tool copy; packaged Recipe QA must report zero diagnostics and zero release-blocking warnings without component boilerplate.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.45.1: Entrypoint and Release Policy
6
+
7
+ - `Architecture`: Keeps `index.ts` as a thin Pi event-registration and composition root while moving session lifecycle, runtime service composition, Skill discovery, and tool adaptation into the acyclic `extension-runtime.ts` domain.
8
+ - `Release Policy`: Raises the fixed shipped-source ceiling to 32,000 lines and rejects blank lines inside executable JavaScript/TypeScript blocks across the complete source and test tree while exempting structural declarations.
9
+ - `Package Metadata`: Moves the package image to the new raw-content endpoint to restore cross-origin loading.
10
+
5
11
  ## 0.45.0: Skill Recipe Standard Library
6
12
 
7
13
  - `Recipe Values`: Makes inline argument defaults optional, applies caller > values > defaults > inline precedence across tools, Runs, imports, and delegation, and validates final typed/enum values plus duplicate, unknown-default, and conflicting-type declarations.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # pi-actors
2
2
 
3
+ ![pi-actors banner](https://raw.githubusercontent.com/llblab/pi-actors/main/banner.jpg)
4
+
3
5
  Local Run kernel and persistent tool registry for [Pi](https://github.com/badlogic/pi-mono).
4
6
 
5
7
  ```text
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * pi-actors — actor runtime and persistent local tool registry for pi.
3
3
  * Zones: composition root, pi agent, actor runtime
4
- *
5
- * Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
4
+ * Owns extension composition and Pi event registration, not domain behavior.
6
5
  */
7
6
  import * as Pi from "./lib/pi.ts";
8
7
  export default function toolRegistryExtension(pi: Pi.ExtensionAPI): void;
package/dist/index.js CHANGED
@@ -1,117 +1,17 @@
1
1
  /**
2
2
  * pi-actors — actor runtime and persistent local tool registry for pi.
3
3
  * Zones: composition root, pi agent, actor runtime
4
- *
5
- * Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
4
+ * Owns extension composition and Pi event registration, not domain behavior.
6
5
  */
7
- import * as AutomaticReviewRuntime from "./lib/automatic-review-runtime.js";
8
- import * as CommandTemplates from "./lib/command-templates.js";
6
+ import * as ExtensionRuntime from "./lib/extension-runtime.js";
9
7
  import * as InspectorCommand from "./lib/inspector-command.js";
10
- import * as Paths from "./lib/paths.js";
11
- import * as Pi from "./lib/pi.js";
12
- import * as Prompts from "./lib/prompts.js";
13
- import * as RecipesReferences from "./lib/recipes-references.js";
14
- import * as RunUiRuntime from "./lib/run-ui-runtime.js";
15
- import * as Runtime from "./lib/runtime.js";
16
- import * as Temp from "./lib/temp.js";
17
- import * as Tools from "./lib/tools.js";
18
- import * as ToolsResponse from "./lib/tools-response.js";
19
8
  export default function toolRegistryExtension(pi) {
20
- let activeRunContext;
21
- const getRunOwnerId = Pi.getSessionId;
22
- const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
23
- getActiveContext: () => activeRunContext,
24
- getRunOwnerId,
25
- getThinkingLevel: () => pi.getThinkingLevel(),
26
- });
27
- const runUiRuntime = RunUiRuntime.createRunUiRuntime({
28
- getActiveContext: () => activeRunContext,
29
- getRunOwnerId,
30
- onRunEvent: automaticReview.schedule,
31
- pi,
32
- });
33
- const actorToolDefinitions = new Map();
34
- const withCurrentThinkingContext = (definition) => {
35
- if (typeof definition.execute !== "function")
36
- return definition;
37
- const execute = definition.execute;
38
- return {
39
- ...definition,
40
- execute: async (...args) => {
41
- const nextArgs = [...args];
42
- const ctx = nextArgs[4];
43
- if (ctx && typeof ctx === "object") {
44
- nextArgs[4] = {
45
- ...ctx,
46
- getThinkingLevel: () => pi.getThinkingLevel(),
47
- };
48
- }
49
- try {
50
- return ToolsResponse.spaceToolResult(await execute(...nextArgs));
51
- }
52
- catch (error) {
53
- throw ToolsResponse.spaceToolError(error);
54
- }
55
- },
56
- };
57
- };
58
- const runtime = Runtime.createAutoToolsRuntime({
59
- configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
60
- exec: CommandTemplates.execCommandTemplate,
61
- getActiveTools: () => pi.getActiveTools(),
62
- registerTool: (definition) => {
63
- const wrapped = withCurrentThinkingContext(definition);
64
- actorToolDefinitions.set(wrapped.name, wrapped);
65
- pi.registerTool(wrapped);
66
- },
67
- reservedToolNames: Tools.RESERVED_TOOL_NAMES,
68
- setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
69
- });
70
- const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime);
71
- pi.on("resources_discover", async () => {
72
- const skillPaths = Paths.getExistingExtensionSkillPaths(import.meta.url);
73
- if (skillPaths.length === 0)
74
- return;
75
- return { skillPaths };
76
- });
77
- pi.on("session_start", async (_event, ctx) => {
78
- // Clear the pre-overlay widget after hot reloads from older pi-actors builds.
79
- ctx.ui.setWidget("zz-pi-actors-comms", undefined);
80
- activeRunContext = ctx;
81
- runUiRuntime.close();
82
- automaticReview.close();
83
- recipeReload.close();
84
- await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
85
- if (activeRunContext !== ctx)
86
- return;
87
- automaticReview.start(ctx);
88
- runtime.loadTools(ctx);
89
- runUiRuntime.start(ctx);
90
- recipeReload.watch(ctx);
91
- });
92
- pi.on("agent_end", async (_event, ctx) => {
93
- if (activeRunContext === ctx)
94
- automaticReview.schedule();
95
- });
96
- pi.on("session_shutdown", async (event, ctx) => {
97
- activeRunContext = undefined;
98
- automaticReview.close();
99
- recipeReload.close();
100
- runUiRuntime.shutdown(event.reason, ctx);
101
- });
102
- InspectorCommand.registerActorInspectorCommand(pi, getRunOwnerId);
103
- pi.on("before_agent_start", async (event) => {
104
- RecipesReferences.setActiveSkillRecipeSources(event.systemPromptOptions.skills ?? []);
105
- return {
106
- systemPrompt: `${event.systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
107
- };
108
- });
109
- Pi.registerToolDefinitions(pi, Tools.createCoreActorToolDefinitions({
110
- configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
111
- getActiveTools: () => pi.getActiveTools(),
112
- getRuntimeTool: (name) => Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) => actorToolDefinitions.get(activeName)),
113
- handleRuntimeControl: automaticReview.handleControl,
114
- registryRuntime: runtime,
115
- setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
116
- }).map(withCurrentThinkingContext));
9
+ const runtime = ExtensionRuntime.createActorExtensionRuntime(pi);
10
+ pi.on("resources_discover", async () => runtime.discoverResources(import.meta.url));
11
+ pi.on("session_start", async (_event, ctx) => runtime.onSessionStart(ctx));
12
+ pi.on("agent_end", async (_event, ctx) => runtime.onAgentEnd(ctx));
13
+ pi.on("session_shutdown", async (event, ctx) => runtime.onSessionShutdown(event.reason, ctx));
14
+ pi.on("before_agent_start", async (event) => runtime.beforeAgentStart(event.systemPrompt, event.systemPromptOptions.skills ?? []));
15
+ InspectorCommand.registerActorInspectorCommand(pi, runtime.getRunOwnerId);
16
+ runtime.registerCoreTools();
117
17
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pi actor extension runtime coordinator.
3
+ * Zones: extension session lifecycle, host tool adaptation, runtime service composition
4
+ * Owns low-level Pi lifecycle effects and tool wrapping without owning event registration.
5
+ */
6
+ import * as Pi from "./pi.ts";
7
+ import * as RecipesReferences from "./recipes-references.ts";
8
+ export interface ActorExtensionRuntime {
9
+ beforeAgentStart(systemPrompt: string, skills: RecipesReferences.ActiveSkillRecipeSource[]): {
10
+ systemPrompt: string;
11
+ };
12
+ discoverResources(metaUrl: string): {
13
+ skillPaths: string[];
14
+ } | undefined;
15
+ getRunOwnerId(ctx: Pi.ExtensionContext): string;
16
+ onAgentEnd(ctx: Pi.ExtensionContext): void;
17
+ onSessionShutdown(reason: string, ctx: Pi.ExtensionContext): void;
18
+ onSessionStart(ctx: Pi.ExtensionContext): Promise<void>;
19
+ registerCoreTools(): void;
20
+ }
21
+ export declare function createActorExtensionRuntime(pi: Pi.ExtensionAPI): ActorExtensionRuntime;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Pi actor extension runtime coordinator.
3
+ * Zones: extension session lifecycle, host tool adaptation, runtime service composition
4
+ * Owns low-level Pi lifecycle effects and tool wrapping without owning event registration.
5
+ */
6
+ import * as AutomaticReviewRuntime from "./automatic-review-runtime.js";
7
+ import * as CommandTemplates from "./command-templates.js";
8
+ import * as Paths from "./paths.js";
9
+ import * as Pi from "./pi.js";
10
+ import * as Prompts from "./prompts.js";
11
+ import * as RecipesReferences from "./recipes-references.js";
12
+ import * as RunUiRuntime from "./run-ui-runtime.js";
13
+ import * as Runtime from "./runtime.js";
14
+ import * as Temp from "./temp.js";
15
+ import * as Tools from "./tools.js";
16
+ import * as ToolsResponse from "./tools-response.js";
17
+ export function createActorExtensionRuntime(pi) {
18
+ let activeRunContext;
19
+ const getRunOwnerId = Pi.getSessionId;
20
+ const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
21
+ getActiveContext: () => activeRunContext,
22
+ getRunOwnerId,
23
+ getThinkingLevel: () => pi.getThinkingLevel(),
24
+ });
25
+ const runUiRuntime = RunUiRuntime.createRunUiRuntime({
26
+ getActiveContext: () => activeRunContext,
27
+ getRunOwnerId,
28
+ onRunEvent: automaticReview.schedule,
29
+ pi,
30
+ });
31
+ const actorToolDefinitions = new Map();
32
+ const withCurrentThinkingContext = (definition) => {
33
+ if (typeof definition.execute !== "function")
34
+ return definition;
35
+ const execute = definition.execute;
36
+ return {
37
+ ...definition,
38
+ execute: async (...args) => {
39
+ const nextArgs = [...args];
40
+ const ctx = nextArgs[4];
41
+ if (ctx && typeof ctx === "object") {
42
+ nextArgs[4] = {
43
+ ...ctx,
44
+ getThinkingLevel: () => pi.getThinkingLevel(),
45
+ };
46
+ }
47
+ try {
48
+ return ToolsResponse.spaceToolResult(await execute(...nextArgs));
49
+ }
50
+ catch (error) {
51
+ throw ToolsResponse.spaceToolError(error);
52
+ }
53
+ },
54
+ };
55
+ };
56
+ const runtime = Runtime.createAutoToolsRuntime({
57
+ configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
58
+ exec: CommandTemplates.execCommandTemplate,
59
+ getActiveTools: () => pi.getActiveTools(),
60
+ registerTool: (definition) => {
61
+ const wrapped = withCurrentThinkingContext(definition);
62
+ actorToolDefinitions.set(wrapped.name, wrapped);
63
+ pi.registerTool(wrapped);
64
+ },
65
+ reservedToolNames: Tools.RESERVED_TOOL_NAMES,
66
+ setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
67
+ });
68
+ const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime);
69
+ return {
70
+ beforeAgentStart(systemPrompt, skills) {
71
+ RecipesReferences.setActiveSkillRecipeSources(skills);
72
+ return {
73
+ systemPrompt: `${systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
74
+ };
75
+ },
76
+ discoverResources(metaUrl) {
77
+ const skillPaths = Paths.getExistingExtensionSkillPaths(metaUrl);
78
+ return skillPaths.length > 0 ? { skillPaths } : undefined;
79
+ },
80
+ getRunOwnerId,
81
+ onAgentEnd(ctx) {
82
+ if (activeRunContext === ctx)
83
+ automaticReview.schedule();
84
+ },
85
+ onSessionShutdown(reason, ctx) {
86
+ activeRunContext = undefined;
87
+ automaticReview.close();
88
+ recipeReload.close();
89
+ runUiRuntime.shutdown(reason, ctx);
90
+ },
91
+ async onSessionStart(ctx) {
92
+ ctx.ui.setWidget("zz-pi-actors-comms", undefined);
93
+ activeRunContext = ctx;
94
+ runUiRuntime.close();
95
+ automaticReview.close();
96
+ recipeReload.close();
97
+ await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
98
+ if (activeRunContext !== ctx)
99
+ return;
100
+ automaticReview.start(ctx);
101
+ runtime.loadTools(ctx);
102
+ runUiRuntime.start(ctx);
103
+ recipeReload.watch(ctx);
104
+ },
105
+ registerCoreTools() {
106
+ Pi.registerToolDefinitions(pi, Tools.createCoreActorToolDefinitions({
107
+ configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
108
+ getActiveTools: () => pi.getActiveTools(),
109
+ getRuntimeTool: (name) => Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) => actorToolDefinitions.get(activeName)),
110
+ handleRuntimeControl: automaticReview.handleControl,
111
+ registryRuntime: runtime,
112
+ setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
113
+ }).map(withCurrentThinkingContext));
114
+ },
115
+ };
116
+ }
@@ -74,7 +74,6 @@ function summarizeCommandDetail(commandDetail) {
74
74
 
75
75
  export async function runAsyncRunner(stateDir = process.argv[2]) {
76
76
  if (!stateDir) throw new Error("missing state dir");
77
-
78
77
  const runPath = join(stateDir, "run.json");
79
78
  const progressPath = join(stateDir, "progress.json");
80
79
  const resultPath = join(stateDir, "result.json");
@@ -83,11 +82,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
83
82
  const stderrPath = join(stateDir, "stderr.log");
84
83
  const executionPath = join(stateDir, "execution.json");
85
84
  const meta = JSON.parse(readFileSync(runPath, "utf8"));
86
-
87
85
  function event(kind, data = {}) {
88
86
  appendRunTraceEvent(stateDir, { kind, data });
89
87
  }
90
-
91
88
  function observation(kind, summary, data = {}, delivery = "log", level = "info") {
92
89
  appendRunTraceEvent(stateDir, {
93
90
  kind,
@@ -97,7 +94,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
97
94
  ...(level === "error" ? { level: "error" } : {}),
98
95
  });
99
96
  }
100
-
101
97
  function progress(phase, extra = {}) {
102
98
  writeJsonAtomic(progressPath, {
103
99
  ...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
@@ -106,7 +102,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
106
102
  ...extra,
107
103
  });
108
104
  }
109
-
110
105
  let activeSubagents = 0;
111
106
  let completedSubagents = 0;
112
107
  let promptCounter = 0;
@@ -115,7 +110,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
115
110
  const evidenceRecords = [];
116
111
  const stageOccurrences = new Map();
117
112
  let reportEvidence;
118
-
119
113
  function writeExecutionManifest(status) {
120
114
  writeJsonAtomic(executionPath, {
121
115
  version: 1,
@@ -129,7 +123,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
129
123
  updated_at: new Date().toISOString(),
130
124
  });
131
125
  }
132
-
133
126
  function commandSessionFiles(sessionDir) {
134
127
  if (!sessionDir) return [];
135
128
  try {
@@ -141,7 +134,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
141
134
  return [];
142
135
  }
143
136
  }
144
-
145
137
  function commandEvidenceStartRecord({
146
138
  commandDetail,
147
139
  commandId,
@@ -181,7 +173,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
181
173
  : "not_required",
182
174
  };
183
175
  }
184
-
185
176
  function commandEvidenceRecord({
186
177
  captureDir,
187
178
  commandDetail,
@@ -273,7 +264,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
273
264
  : "rejected",
274
265
  };
275
266
  }
276
-
277
267
  function auditReviewReport(text) {
278
268
  const required = evidenceRecords
279
269
  .filter((record) =>
@@ -294,11 +284,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
294
284
  complete_allowed: missing.length === 0,
295
285
  };
296
286
  }
297
-
298
287
  function getCommandDoneDelivery(result) {
299
288
  return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
300
289
  }
301
-
302
290
  function progressRunning() {
303
291
  progress("running", {
304
292
  activeSubagents,
@@ -306,14 +294,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
306
294
  failures: subagentFailures,
307
295
  });
308
296
  }
309
-
310
297
  function promptFilePath() {
311
298
  promptCounter += 1;
312
299
  const dir = join(stateDir, "prompts");
313
300
  mkdirSync(dir, { recursive: true });
314
301
  return join(dir, `command-${String(promptCounter).padStart(3, "0")}.md`);
315
302
  }
316
-
317
303
  function readPromptText(promptFile) {
318
304
  if (!promptFile) return undefined;
319
305
  try {
@@ -322,7 +308,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
322
308
  return undefined;
323
309
  }
324
310
  }
325
-
326
311
  async function observedExec(command, args, options) {
327
312
  captureCounter += 1;
328
313
  const commandId = `command-${String(captureCounter).padStart(3, "0")}`;
@@ -485,7 +470,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
485
470
  progressRunning();
486
471
  return result;
487
472
  }
488
-
489
473
  try {
490
474
  event("run.runner.start", { pid: process.pid });
491
475
  progressRunning();
@@ -0,0 +1 @@
1
+ export function executableBlockBlankLines(source: string): number[];
@@ -0,0 +1,73 @@
1
+ /** Detect blank lines inside executable JavaScript and TypeScript blocks. */
2
+
3
+ export function executableBlockBlankLines(source) {
4
+ const lines = source.split(/\r?\n/u);
5
+ const blanks = [];
6
+ const stack = [];
7
+ let blockComment = false;
8
+ let currentExecutableDepth = 0;
9
+ let previousCode = "";
10
+ let quote;
11
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
12
+ const line = lines[lineIndex];
13
+ if (
14
+ /^\s*$/u.test(line) &&
15
+ currentExecutableDepth > 0 &&
16
+ quote !== "`"
17
+ )
18
+ blanks.push(lineIndex + 1);
19
+ let code = "";
20
+ let regularExpression = false;
21
+ for (let index = 0; index < line.length; index += 1) {
22
+ const char = line[index];
23
+ const next = line[index + 1];
24
+ if (blockComment) {
25
+ if (char === "*" && next === "/") {
26
+ blockComment = false;
27
+ index += 1;
28
+ }
29
+ continue;
30
+ }
31
+ if (quote) {
32
+ if (char === "\\") index += 1;
33
+ else if (char === quote) quote = undefined;
34
+ continue;
35
+ }
36
+ if (regularExpression) {
37
+ if (char === "\\") index += 1;
38
+ else if (char === "/") regularExpression = false;
39
+ continue;
40
+ }
41
+ if (char === "/" && next === "*") {
42
+ blockComment = true;
43
+ index += 1;
44
+ continue;
45
+ }
46
+ if (char === "/" && next === "/") break;
47
+ if (
48
+ char === "/" &&
49
+ /^(?:|.*(?:[=(,:;!&|?{}]|\b(?:return|case|throw)))\s*$/u.test(code)
50
+ ) {
51
+ regularExpression = true;
52
+ continue;
53
+ }
54
+ if (char === '"' || char === "'" || char === "`") {
55
+ quote = char;
56
+ continue;
57
+ }
58
+ if (char === "{") {
59
+ const prefix = code.trimEnd() || previousCode;
60
+ const executable =
61
+ /\)\s*$|=>\s*$|\b(?:else|try|finally|do)\s*$/u.test(prefix);
62
+ stack.push(executable);
63
+ if (executable) currentExecutableDepth += 1;
64
+ } else if (char === "}") {
65
+ if (stack.pop()) currentExecutableDepth -= 1;
66
+ }
67
+ code += char;
68
+ }
69
+ if (quote !== "`") quote = undefined;
70
+ if (code.trim()) previousCode = code.trim();
71
+ }
72
+ return blanks;
73
+ }
@@ -65,7 +65,6 @@ async function runLocker(argv = process.argv.slice(2)) {
65
65
  const controlPath = join(stateDir, "control.fifo");
66
66
  let runInstanceId;
67
67
  mkdirSync(stateDir, { recursive: true });
68
-
69
68
  function readJson(path, fallback) {
70
69
  if (!existsSync(path)) return fallback;
71
70
  try {
@@ -74,11 +73,9 @@ async function runLocker(argv = process.argv.slice(2)) {
74
73
  return fallback;
75
74
  }
76
75
  }
77
-
78
76
  function writeJson(path, value) {
79
77
  writeJsonAtomic(path, value);
80
78
  }
81
-
82
79
  function getControlEndpoint() {
83
80
  if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
84
81
  const hash = createHash("sha256")
@@ -87,7 +84,6 @@ async function runLocker(argv = process.argv.slice(2)) {
87
84
  .slice(0, 20);
88
85
  return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
89
86
  }
90
-
91
87
  function writeControlEndpoint(endpoint) {
92
88
  if (!runInstanceId) throw new Error("Run generation unavailable for Control endpoint");
93
89
  writeJson(join(stateDir, "control-endpoint.json"), {
@@ -96,7 +92,6 @@ async function runLocker(argv = process.argv.slice(2)) {
96
92
  run_instance_id: runInstanceId,
97
93
  });
98
94
  }
99
-
100
95
  function journal(event, data = {}) {
101
96
  const release = acquireFileMutationLock(journalPath);
102
97
  try {
@@ -111,11 +106,9 @@ async function runLocker(argv = process.argv.slice(2)) {
111
106
  writeTextAtomic(journalPath, content);
112
107
  } finally { release(); }
113
108
  }
114
-
115
109
  function encodeJournal(records) {
116
110
  return records.length ? `${records.map((record) => JSON.stringify(record)).join("\n")}\n` : "";
117
111
  }
118
-
119
112
  function emitTrace(kind, summary, data = {}, level = "info") {
120
113
  appendRunTraceEvent(stateDir, {
121
114
  attention: "followup",
@@ -125,11 +118,9 @@ async function runLocker(argv = process.argv.slice(2)) {
125
118
  summary,
126
119
  });
127
120
  }
128
-
129
121
  function now() {
130
122
  return Date.now();
131
123
  }
132
-
133
124
  function cleanExpiredLocks(locks) {
134
125
  const current = now();
135
126
  const kept = {};
@@ -139,7 +130,6 @@ async function runLocker(argv = process.argv.slice(2)) {
139
130
  }
140
131
  return kept;
141
132
  }
142
-
143
133
  function normalizeControl(line) {
144
134
  const trimmed = line.trim();
145
135
  if (!trimmed) return undefined;
@@ -160,8 +150,6 @@ async function runLocker(argv = process.argv.slice(2)) {
160
150
  );
161
151
  }
162
152
  }
163
-
164
-
165
153
  function tailJournal(count) {
166
154
  if (!existsSync(journalPath)) return [];
167
155
  return readFileSync(journalPath, "utf8")
@@ -177,7 +165,6 @@ async function runLocker(argv = process.argv.slice(2)) {
177
165
  }
178
166
  });
179
167
  }
180
-
181
168
  function printSnapshot() {
182
169
  const locks = cleanExpiredLocks(readJson(locksPath, {}));
183
170
  writeJson(locksPath, locks);
@@ -195,7 +182,6 @@ async function runLocker(argv = process.argv.slice(2)) {
195
182
  ),
196
183
  );
197
184
  }
198
-
199
185
  function nextTask(queue, locks) {
200
186
  const items = Array.isArray(queue.items) ? queue.items : [];
201
187
  const index = items.findIndex((item) => {
@@ -205,7 +191,6 @@ async function runLocker(argv = process.argv.slice(2)) {
205
191
  if (index < 0) return undefined;
206
192
  return items.splice(index, 1)[0];
207
193
  }
208
-
209
194
  function handle(control) {
210
195
  const action = control.action;
211
196
  const body =
@@ -333,18 +318,15 @@ async function runLocker(argv = process.argv.slice(2)) {
333
318
  journal("lock.unknown", { action, input: body });
334
319
  emitTrace("lock.unknown", `Unknown Control ${action}`, { action, input: body }, "warning");
335
320
  }
336
-
337
321
  if (mode === "snapshot") {
338
322
  printSnapshot();
339
323
  process.exit(0);
340
324
  }
341
-
342
325
  const startupRun = readJson(join(stateDir, "run.json"), undefined);
343
326
  if (!startupRun || typeof startupRun.run_instance_id !== "string") {
344
327
  throw new Error("Run generation unavailable for Control service");
345
328
  }
346
329
  runInstanceId = startupRun.run_instance_id;
347
-
348
330
  function handleLine(line) {
349
331
  const control = normalizeControl(line);
350
332
  if (!control) return false;
@@ -376,7 +358,6 @@ async function runLocker(argv = process.argv.slice(2)) {
376
358
  return false;
377
359
  }
378
360
  }
379
-
380
361
  async function serveFifo(endpoint) {
381
362
  if (!existsSync(endpoint.path)) {
382
363
  const result = spawnSync("mkfifo", [endpoint.path]);
@@ -403,7 +384,6 @@ async function runLocker(argv = process.argv.slice(2)) {
403
384
  fs.closeSync(fd);
404
385
  }
405
386
  }
406
-
407
387
  async function serveNamedPipe(endpoint) {
408
388
  let resolveStopped;
409
389
  const stopped = new Promise((resolve) => {
@@ -428,7 +408,6 @@ async function runLocker(argv = process.argv.slice(2)) {
428
408
  });
429
409
  await stopped;
430
410
  }
431
-
432
411
  const endpoint = getControlEndpoint();
433
412
  writeJson(queuePath, readJson(queuePath, { items: [] }));
434
413
  writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
@@ -369,7 +369,6 @@ if (!command) {
369
369
  usage();
370
370
  process.exit(1);
371
371
  }
372
-
373
372
  if (command === "run-summary")
374
373
  runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
375
374
  else if (command === "run-ops-snapshot")