@llblab/pi-actors 0.24.4 → 0.24.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.24.6: Async Run Restart Status Hotfix
6
+
7
+ - `[Async Runs]` Treat freshly spawned runner PIDs as running during a short Linux `/proc` identity grace window, preventing restart status checks and immediate run messages from misclassifying a new run as `exited` before its command line is observable.
8
+
9
+ ## 0.24.5: Auto-Discovered Skills Hotfix
10
+
11
+ - `[Skills]` Auto-discovered source checkouts now contribute their co-located `skills/` directory through Pi resource discovery, so extension-local actors/swarm skills load even when `pi-actors` is used directly from `~/.pi/agent/extensions` instead of installed as a package.
12
+ - `[Packaging]` Kept package skill metadata directory-based for both `dist/skills` and source `skills`, preserving bundled skill discovery through package installs while the extension also contributes co-located skills at runtime.
13
+
5
14
  ## 0.24.4: Music Player Message Control Hotfix
6
15
 
7
16
  - `[Music Player]` Preserve typed actor-message envelopes for run-mailbox delivery so `player.<action>` messages dispatch by message type instead of relying on body payload text.
package/README.md CHANGED
@@ -43,7 +43,7 @@ Or from git:
43
43
  pi install git:github.com/llblab/pi-actors
44
44
  ```
45
45
 
46
- The npm package is dist-first for JavaScript-only runtimes: default Pi metadata points at compiled `dist/` entrypoints and mirrored runtime assets. Source TypeScript and source skills remain in the package for TypeScript-native runtimes through optional source metadata.
46
+ The npm package is dist-first for JavaScript-only runtimes: default Pi metadata points at compiled `dist/` entrypoints and mirrored runtime assets. Source TypeScript and source skills remain in the package for TypeScript-native runtimes through optional source metadata. When the source checkout is auto-discovered as an extension, it also contributes its co-located `skills/` directory during Pi resource discovery so the actors and swarm skills travel with the extension.
47
47
 
48
48
  ## Address Surface
49
49
 
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@
5
5
  * Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
6
6
  */
7
7
  import { existsSync, readdirSync, watch } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import * as ActorInspectorTui from "./lib/actor-inspector-tui.js";
9
11
  import * as AsyncRuns from "./lib/async-runs.js";
10
12
  import * as CommandTemplates from "./lib/command-templates.js";
@@ -14,6 +16,8 @@ import * as Prompts from "./lib/prompts.js";
14
16
  import * as Runtime from "./lib/runtime.js";
15
17
  import * as Temp from "./lib/temp.js";
16
18
  import * as Tools from "./lib/tools.js";
19
+ const EXTENSION_DIR = dirname(fileURLToPath(import.meta.url));
20
+ const EXTENSION_SKILLS_DIR = join(EXTENSION_DIR, "skills");
17
21
  const CONFIG_PATH = Paths.getConfigPath();
18
22
  const TEMP_DIR = Paths.getExtensionTmpDir();
19
23
  const RUN_STATE_ROOT = Paths.getRunStateRoot();
@@ -246,6 +250,11 @@ export default function toolRegistryExtension(pi) {
246
250
  reservedToolNames: RESERVED_TOOL_NAMES,
247
251
  setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
248
252
  });
253
+ pi.on("resources_discover", async () => {
254
+ if (!existsSync(EXTENSION_SKILLS_DIR))
255
+ return;
256
+ return { skillPaths: [EXTENSION_SKILLS_DIR] };
257
+ });
249
258
  pi.on("session_start", async (_event, ctx) => {
250
259
  await Temp.prepareExtensionTempDir(TEMP_DIR);
251
260
  runtime.loadTools(ctx);
@@ -19,6 +19,7 @@ import * as RecipeUsage from "./recipe-usage.js";
19
19
  import { notifyRuntimeWake } from "./runtime-notifier.js";
20
20
  const START_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
21
21
  const RUN_INBOX_LOCK_TIMEOUT_MS = 5000;
22
+ const RUNNER_IDENTITY_GRACE_MS = 5000;
22
23
  const DEFAULT_STATE_ROOT = Paths.getRunStateRoot();
23
24
  const DEFAULT_RECIPE_ROOT = Paths.getRecipeRoot();
24
25
  function packageRoot() {
@@ -215,6 +216,12 @@ function pidMatchesRun(pid, cwd, stateDir) {
215
216
  return false;
216
217
  }
217
218
  }
219
+ function isWithinRunnerIdentityGrace(meta) {
220
+ const createdAt = typeof meta.createdAt === "string" ? Date.parse(meta.createdAt) : NaN;
221
+ return (Number.isFinite(createdAt) &&
222
+ Date.now() - createdAt >= 0 &&
223
+ Date.now() - createdAt <= RUNNER_IDENTITY_GRACE_MS);
224
+ }
218
225
  function tailFile(path, lines) {
219
226
  if (!existsSync(path))
220
227
  return "";
@@ -446,7 +453,8 @@ export function getRunStatus(runOrDir) {
446
453
  const aliveOwnedRunner = Boolean(pid &&
447
454
  isAlive(pid) &&
448
455
  (!Array.isArray(meta.argv) ||
449
- pidMatchesRun(pid, String(meta.cwd ?? ""), stateDir)));
456
+ pidMatchesRun(pid, String(meta.cwd ?? ""), stateDir) ||
457
+ isWithinRunnerIdentityGrace(meta)));
450
458
  const status = result
451
459
  ? Number(result.code ?? 0) === 0
452
460
  ? "done"
@@ -891,7 +899,8 @@ export async function sendRunMessage(runOrDir, message, options = {}) {
891
899
  const pid = Number(status.pid || 0);
892
900
  if (!pid || !isAlive(pid))
893
901
  throw new Error(`Run pid is not alive: ${run}`);
894
- if (!pidMatchesRun(pid, String(status.cwd), stateDir))
902
+ if (!pidMatchesRun(pid, String(status.cwd), stateDir) &&
903
+ !isWithinRunnerIdentityGrace(status))
895
904
  throw new Error(`Run pid owner mismatch: ${run}`);
896
905
  const endpoint = getRunControlEndpoint(status, stateDir);
897
906
  const payload = message.endsWith("\n") ? message : `${message}\n`;
@@ -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.24.4
5
+ version: 0.24.6
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.24.4
5
+ version: 0.24.6
6
6
  ---
7
7
 
8
8
  # Swarm
package/index.ts CHANGED
@@ -6,6 +6,8 @@
6
6
  */
7
7
 
8
8
  import { existsSync, readdirSync, watch, type FSWatcher } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
9
11
 
10
12
  import type {
11
13
  ExtensionAPI,
@@ -22,6 +24,8 @@ import * as Runtime from "./lib/runtime.ts";
22
24
  import * as Temp from "./lib/temp.ts";
23
25
  import * as Tools from "./lib/tools.ts";
24
26
 
27
+ const EXTENSION_DIR = dirname(fileURLToPath(import.meta.url));
28
+ const EXTENSION_SKILLS_DIR = join(EXTENSION_DIR, "skills");
25
29
  const CONFIG_PATH = Paths.getConfigPath();
26
30
  const TEMP_DIR = Paths.getExtensionTmpDir();
27
31
  const RUN_STATE_ROOT = Paths.getRunStateRoot();
@@ -299,6 +303,10 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
299
303
  reservedToolNames: RESERVED_TOOL_NAMES,
300
304
  setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
301
305
  });
306
+ pi.on("resources_discover", async () => {
307
+ if (!existsSync(EXTENSION_SKILLS_DIR)) return;
308
+ return { skillPaths: [EXTENSION_SKILLS_DIR] };
309
+ });
302
310
  pi.on("session_start", async (_event, ctx) => {
303
311
  await Temp.prepareExtensionTempDir(TEMP_DIR);
304
312
  runtime.loadTools(ctx);
package/lib/async-runs.ts CHANGED
@@ -41,6 +41,7 @@ import { notifyRuntimeWake } from "./runtime-notifier.ts";
41
41
 
42
42
  const START_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
43
43
  const RUN_INBOX_LOCK_TIMEOUT_MS = 5000;
44
+ const RUNNER_IDENTITY_GRACE_MS = 5000;
44
45
 
45
46
  export type AsyncRunLaunchSource = "spawn" | "tool";
46
47
 
@@ -377,6 +378,16 @@ function pidMatchesRun(pid: number, cwd: string, stateDir: string): boolean {
377
378
  }
378
379
  }
379
380
 
381
+ function isWithinRunnerIdentityGrace(meta: Record<string, unknown>): boolean {
382
+ const createdAt =
383
+ typeof meta.createdAt === "string" ? Date.parse(meta.createdAt) : NaN;
384
+ return (
385
+ Number.isFinite(createdAt) &&
386
+ Date.now() - createdAt >= 0 &&
387
+ Date.now() - createdAt <= RUNNER_IDENTITY_GRACE_MS
388
+ );
389
+ }
390
+
380
391
  function tailFile(path: string, lines: number): string {
381
392
  if (!existsSync(path)) return "";
382
393
  const content = readFileSync(path, "utf8").trimEnd();
@@ -658,7 +669,8 @@ export function getRunStatus(runOrDir: string): Record<string, unknown> {
658
669
  pid &&
659
670
  isAlive(pid) &&
660
671
  (!Array.isArray(meta.argv) ||
661
- pidMatchesRun(pid, String(meta.cwd ?? ""), stateDir)),
672
+ pidMatchesRun(pid, String(meta.cwd ?? ""), stateDir) ||
673
+ isWithinRunnerIdentityGrace(meta)),
662
674
  );
663
675
  const status: AsyncRunStatus = result
664
676
  ? Number(result.code ?? 0) === 0
@@ -1240,7 +1252,10 @@ export async function sendRunMessage(
1240
1252
  throw new Error(`Run is not running: ${run}`);
1241
1253
  const pid = Number(status.pid || 0);
1242
1254
  if (!pid || !isAlive(pid)) throw new Error(`Run pid is not alive: ${run}`);
1243
- if (!pidMatchesRun(pid, String(status.cwd), stateDir))
1255
+ if (
1256
+ !pidMatchesRun(pid, String(status.cwd), stateDir) &&
1257
+ !isWithinRunnerIdentityGrace(status)
1258
+ )
1244
1259
  throw new Error(`Run pid owner mismatch: ${run}`);
1245
1260
  const endpoint = getRunControlEndpoint(status, stateDir);
1246
1261
  const payload = message.endsWith("\n") ? message : `${message}\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.24.4",
3
+ "version": "0.24.6",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -35,9 +35,9 @@
35
35
  },
36
36
  "files": [
37
37
  "index.ts",
38
+ "dist",
38
39
  "lib",
39
40
  "scripts",
40
- "dist",
41
41
  "recipes",
42
42
  "skills",
43
43
  "README.md",
@@ -56,12 +56,10 @@
56
56
  "./index.ts"
57
57
  ],
58
58
  "skills": [
59
- "./dist/skills/actors/SKILL.md",
60
- "./dist/skills/swarm/SKILL.md"
59
+ "./dist/skills"
61
60
  ],
62
61
  "sourceSkills": [
63
- "./skills/actors/SKILL.md",
64
- "./skills/swarm/SKILL.md"
62
+ "./skills"
65
63
  ],
66
64
  "image": "https://github.com/llblab/pi-actors/raw/main/banner.jpg"
67
65
  },
@@ -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.24.4
5
+ version: 0.24.6
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.24.4
5
+ version: 0.24.6
6
6
  ---
7
7
 
8
8
  # Swarm