@llblab/pi-actors 0.24.2 → 0.24.4

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.4: Music Player Message Control Hotfix
6
+
7
+ - `[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.
8
+ - `[Music Player]` Emit `player.stopped` when playback stops so actor inspector and run messages show an explicit domain stop event before terminal completion.
9
+
10
+ ## 0.24.3: Actor Inspector Body Field Hotfix
11
+
12
+ - `[Inspector]` Removed the selected-item key-label shim by projecting actor-message detail rows with a real `body` field instead of rendering `body_preview` under an alias.
13
+
5
14
  ## 0.24.2: Actor Inspector Item View Hotfix
6
15
 
7
16
  - `[Inspector]` Removed the roster panel from selected actor-message item inspection, kept the compact route header styling, added the same left/right gutter used by inspector rows, and renamed the selected-item `body_preview` label to `body`.
@@ -318,11 +318,6 @@ function propertyValue(value) {
318
318
  return String(value);
319
319
  return JSON.stringify(value);
320
320
  }
321
- function itemViewKeyLabel(key) {
322
- if (key === "body_preview")
323
- return "body";
324
- return key;
325
- }
326
321
  function displayWidth(value) {
327
322
  return visibleWidth(value);
328
323
  }
@@ -507,24 +502,24 @@ export function renderInspectorItemView(previews, width = 80, styles = {}, optio
507
502
  const prefix = " ";
508
503
  const suffix = " ";
509
504
  const contentWidth = Math.max(8, safeWidth - prefix.length - suffix.length);
510
- const orderedKeys = [
511
- "channel",
512
- "run",
513
- "event_id",
514
- "from",
515
- "from_display",
516
- "to",
517
- "type",
518
- "summary",
519
- "body_preview",
520
- "message_id",
521
- "needs_response",
522
- "timestamp",
523
- "stripe",
505
+ const rawEntries = [
506
+ ["channel", preview.channel],
507
+ ["run", preview.run],
508
+ ["event_id", preview.event_id],
509
+ ["from", preview.from],
510
+ ["from_display", preview.from_display],
511
+ ["to", preview.to],
512
+ ["type", preview.type],
513
+ ["summary", preview.summary],
514
+ ["body", preview.body_preview],
515
+ ["message_id", preview.message_id],
516
+ ["needs_response", preview.needs_response],
517
+ ["timestamp", preview.timestamp],
518
+ ["stripe", preview.stripe],
524
519
  ];
525
- const entries = orderedKeys
526
- .filter((key) => preview[key] !== undefined)
527
- .map((key) => [key, propertyValue(preview[key])]);
520
+ const entries = rawEntries
521
+ .filter((entry) => entry[1] !== undefined)
522
+ .map(([key, value]) => [key, propertyValue(value)]);
528
523
  const keyWidth = Math.max(1, ...entries.map(([key]) => displayWidth(key)));
529
524
  const sequenceText = String(preview.sequence ?? options.sequence);
530
525
  const sequencePadding = " ".repeat(Math.max(0, keyWidth - displayWidth(sequenceText)));
@@ -536,13 +531,12 @@ export function renderInspectorItemView(previews, width = 80, styles = {}, optio
536
531
  const headerPadding = Math.max(0, contentWidth - visibleWidth(headerPlain));
537
532
  const lines = [`${prefix}${header}${" ".repeat(headerPadding)}${suffix}`, ""];
538
533
  for (const [key, value] of entries) {
539
- const label = itemViewKeyLabel(key);
540
- const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(label)));
534
+ const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(key)));
541
535
  const separator = " ";
542
536
  const valueWidth = Math.max(0, contentWidth - keyWidth - separator.length);
543
537
  const visibleValue = boundedLine(value, valueWidth);
544
- const plain = `${label}${keyPadding}${separator}${visibleValue}`;
545
- const rendered = `${style(styles.muted, label)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
538
+ const plain = `${key}${keyPadding}${separator}${visibleValue}`;
539
+ const rendered = `${style(styles.muted, key)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
546
540
  const padding = Math.max(0, contentWidth - visibleWidth(plain));
547
541
  lines.push(`${prefix}${rendered}${" ".repeat(padding)}${suffix}`);
548
542
  }
package/dist/lib/tools.js CHANGED
@@ -481,6 +481,8 @@ function getRunOwnerId(ctx) {
481
481
  return ctx.sessionManager?.getSessionId?.();
482
482
  }
483
483
  function messageBodyToRunLine(message) {
484
+ if (message.type !== "run.message")
485
+ return JSON.stringify(message);
484
486
  if (typeof message.body === "string")
485
487
  return message.body;
486
488
  if (message.body === undefined)
@@ -31,7 +31,8 @@
31
31
  "player.status"
32
32
  ],
33
33
  "emits": [
34
- "player.track"
34
+ "player.track",
35
+ "player.stopped"
35
36
  ]
36
37
  },
37
38
  "template": "{repo}/scripts/music-player.mjs {command} {source} {loop} {volume} {player} {state_dir}"
@@ -404,26 +404,39 @@ function readText(path) {
404
404
  }
405
405
  }
406
406
 
407
- function emitTrackEvent(ctx, index, count, track, player) {
408
- const title = track.split(/[\\/]/).filter(Boolean).pop() || track;
407
+ function emitPlayerEvent(ctx, type, summary, body = {}) {
409
408
  writeText(
410
409
  ctx.eventFile,
411
410
  `${JSON.stringify({
412
- body: { count, index, player, track },
413
- data: { count, index, player, track },
411
+ body,
412
+ data: body,
414
413
  delivery: "log",
415
- event: "player.track",
414
+ event: type,
416
415
  from: `run:${basename(ctx.stateDir)}`,
417
416
  level: "info",
418
- summary: `Now playing: ${title}`,
417
+ summary,
419
418
  to: "coordinator",
420
419
  ts: new Date().toISOString(),
421
- type: "player.track",
420
+ type,
422
421
  })}\n`,
423
422
  "a",
424
423
  );
425
424
  }
426
425
 
426
+ function emitTrackEvent(ctx, index, count, track, player) {
427
+ const title = track.split(/[\\/]/).filter(Boolean).pop() || track;
428
+ emitPlayerEvent(ctx, "player.track", `Now playing: ${title}`, {
429
+ count,
430
+ index,
431
+ player,
432
+ track,
433
+ });
434
+ }
435
+
436
+ function emitStoppedEvent(ctx, reason = "stop") {
437
+ emitPlayerEvent(ctx, "player.stopped", "Music player stopped", { reason });
438
+ }
439
+
427
440
  function writeStatus(ctx, state, index, count, track, player, pid = "") {
428
441
  const updatedAt = new Date().toISOString();
429
442
  writeText(
@@ -611,23 +624,27 @@ function writeInboxMessages(ctx, messages) {
611
624
  );
612
625
  }
613
626
 
627
+ function commandFromControlToken(token) {
628
+ if (CONTROL_COMMANDS.has(token)) return token;
629
+ if (token === "control.stop" || token === "control.cancel") return "stop";
630
+ const match = /^player\.(.+)$/.exec(token);
631
+ if (match && CONTROL_COMMANDS.has(match[1])) return match[1];
632
+ return undefined;
633
+ }
634
+
614
635
  function commandFromInboxMessage(message) {
615
- if (
616
- typeof message.body === "string" &&
617
- CONTROL_COMMANDS.has(message.body.trim())
618
- ) {
619
- return message.body.trim();
636
+ if (typeof message.type === "string") {
637
+ const command = commandFromControlToken(message.type.trim());
638
+ if (command) return command;
639
+ }
640
+ if (typeof message.body === "string") {
641
+ const command = message.body.trim();
642
+ if (CONTROL_COMMANDS.has(command)) return command;
620
643
  }
621
644
  if (message.body && typeof message.body === "object") {
622
645
  const command = String(message.body.command ?? "").trim();
623
646
  if (CONTROL_COMMANDS.has(command)) return command;
624
647
  }
625
- if (typeof message.type === "string") {
626
- if (message.type === "control.stop" || message.type === "control.cancel")
627
- return "stop";
628
- const match = /^player\.(.+)$/.exec(message.type);
629
- if (match && CONTROL_COMMANDS.has(match[1])) return match[1];
630
- }
631
648
  return undefined;
632
649
  }
633
650
 
@@ -895,6 +912,7 @@ async function playMain(args) {
895
912
  const command = readAndClearCommand(ctx);
896
913
  if (command === "stop") {
897
914
  writeStatus(ctx, "stopped", index, count, track, player, "");
915
+ emitStoppedEvent(ctx, "message");
898
916
  return;
899
917
  }
900
918
  if (command === "previous" || command === "prev") {
@@ -913,6 +931,7 @@ async function playMain(args) {
913
931
  }
914
932
  }
915
933
  writeStatus(ctx, "stopped", index, tracks.length, "", player, "");
934
+ emitStoppedEvent(ctx, "complete");
916
935
  } finally {
917
936
  cleanup();
918
937
  await Promise.race([controlLoop?.promise ?? Promise.resolve(), sleep(100)]);
@@ -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.2
5
+ version: 0.24.4
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.2
5
+ version: 0.24.4
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -478,11 +478,6 @@ function propertyValue(value: unknown): string {
478
478
  return JSON.stringify(value);
479
479
  }
480
480
 
481
- function itemViewKeyLabel(key: string): string {
482
- if (key === "body_preview") return "body";
483
- return key;
484
- }
485
-
486
481
  function displayWidth(value: string): number {
487
482
  return visibleWidth(value);
488
483
  }
@@ -735,24 +730,24 @@ export function renderInspectorItemView(
735
730
  const prefix = " ";
736
731
  const suffix = " ";
737
732
  const contentWidth = Math.max(8, safeWidth - prefix.length - suffix.length);
738
- const orderedKeys = [
739
- "channel",
740
- "run",
741
- "event_id",
742
- "from",
743
- "from_display",
744
- "to",
745
- "type",
746
- "summary",
747
- "body_preview",
748
- "message_id",
749
- "needs_response",
750
- "timestamp",
751
- "stripe",
752
- ] as const;
753
- const entries = orderedKeys
754
- .filter((key) => preview[key] !== undefined)
755
- .map((key) => [key, propertyValue(preview[key])] as const);
733
+ const rawEntries: Array<readonly [string, unknown]> = [
734
+ ["channel", preview.channel],
735
+ ["run", preview.run],
736
+ ["event_id", preview.event_id],
737
+ ["from", preview.from],
738
+ ["from_display", preview.from_display],
739
+ ["to", preview.to],
740
+ ["type", preview.type],
741
+ ["summary", preview.summary],
742
+ ["body", preview.body_preview],
743
+ ["message_id", preview.message_id],
744
+ ["needs_response", preview.needs_response],
745
+ ["timestamp", preview.timestamp],
746
+ ["stripe", preview.stripe],
747
+ ];
748
+ const entries = rawEntries
749
+ .filter((entry) => entry[1] !== undefined)
750
+ .map(([key, value]) => [key, propertyValue(value)] as const);
756
751
  const keyWidth = Math.max(1, ...entries.map(([key]) => displayWidth(key)));
757
752
  const sequenceText = String(preview.sequence ?? options.sequence);
758
753
  const sequencePadding = " ".repeat(
@@ -769,13 +764,12 @@ export function renderInspectorItemView(
769
764
  const headerPadding = Math.max(0, contentWidth - visibleWidth(headerPlain));
770
765
  const lines = [`${prefix}${header}${" ".repeat(headerPadding)}${suffix}`, ""];
771
766
  for (const [key, value] of entries) {
772
- const label = itemViewKeyLabel(key);
773
- const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(label)));
767
+ const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(key)));
774
768
  const separator = " ";
775
769
  const valueWidth = Math.max(0, contentWidth - keyWidth - separator.length);
776
770
  const visibleValue = boundedLine(value, valueWidth);
777
- const plain = `${label}${keyPadding}${separator}${visibleValue}`;
778
- const rendered = `${style(styles.muted, label)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
771
+ const plain = `${key}${keyPadding}${separator}${visibleValue}`;
772
+ const rendered = `${style(styles.muted, key)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
779
773
  const padding = Math.max(0, contentWidth - visibleWidth(plain));
780
774
  lines.push(`${prefix}${rendered}${" ".repeat(padding)}${suffix}`);
781
775
  }
package/lib/tools.ts CHANGED
@@ -593,6 +593,7 @@ function getRunOwnerId(ctx: AsyncRunToolContext): string | undefined {
593
593
  }
594
594
 
595
595
  function messageBodyToRunLine(message: ActorMessages.ActorMessage): string {
596
+ if (message.type !== "run.message") return JSON.stringify(message);
596
597
  if (typeof message.body === "string") return message.body;
597
598
  if (message.body === undefined) return message.type;
598
599
  return JSON.stringify(message.body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.24.2",
3
+ "version": "0.24.4",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -31,7 +31,8 @@
31
31
  "player.status"
32
32
  ],
33
33
  "emits": [
34
- "player.track"
34
+ "player.track",
35
+ "player.stopped"
35
36
  ]
36
37
  },
37
38
  "template": "{repo}/scripts/music-player.mjs {command} {source} {loop} {volume} {player} {state_dir}"
@@ -404,26 +404,39 @@ function readText(path) {
404
404
  }
405
405
  }
406
406
 
407
- function emitTrackEvent(ctx, index, count, track, player) {
408
- const title = track.split(/[\\/]/).filter(Boolean).pop() || track;
407
+ function emitPlayerEvent(ctx, type, summary, body = {}) {
409
408
  writeText(
410
409
  ctx.eventFile,
411
410
  `${JSON.stringify({
412
- body: { count, index, player, track },
413
- data: { count, index, player, track },
411
+ body,
412
+ data: body,
414
413
  delivery: "log",
415
- event: "player.track",
414
+ event: type,
416
415
  from: `run:${basename(ctx.stateDir)}`,
417
416
  level: "info",
418
- summary: `Now playing: ${title}`,
417
+ summary,
419
418
  to: "coordinator",
420
419
  ts: new Date().toISOString(),
421
- type: "player.track",
420
+ type,
422
421
  })}\n`,
423
422
  "a",
424
423
  );
425
424
  }
426
425
 
426
+ function emitTrackEvent(ctx, index, count, track, player) {
427
+ const title = track.split(/[\\/]/).filter(Boolean).pop() || track;
428
+ emitPlayerEvent(ctx, "player.track", `Now playing: ${title}`, {
429
+ count,
430
+ index,
431
+ player,
432
+ track,
433
+ });
434
+ }
435
+
436
+ function emitStoppedEvent(ctx, reason = "stop") {
437
+ emitPlayerEvent(ctx, "player.stopped", "Music player stopped", { reason });
438
+ }
439
+
427
440
  function writeStatus(ctx, state, index, count, track, player, pid = "") {
428
441
  const updatedAt = new Date().toISOString();
429
442
  writeText(
@@ -611,23 +624,27 @@ function writeInboxMessages(ctx, messages) {
611
624
  );
612
625
  }
613
626
 
627
+ function commandFromControlToken(token) {
628
+ if (CONTROL_COMMANDS.has(token)) return token;
629
+ if (token === "control.stop" || token === "control.cancel") return "stop";
630
+ const match = /^player\.(.+)$/.exec(token);
631
+ if (match && CONTROL_COMMANDS.has(match[1])) return match[1];
632
+ return undefined;
633
+ }
634
+
614
635
  function commandFromInboxMessage(message) {
615
- if (
616
- typeof message.body === "string" &&
617
- CONTROL_COMMANDS.has(message.body.trim())
618
- ) {
619
- return message.body.trim();
636
+ if (typeof message.type === "string") {
637
+ const command = commandFromControlToken(message.type.trim());
638
+ if (command) return command;
639
+ }
640
+ if (typeof message.body === "string") {
641
+ const command = message.body.trim();
642
+ if (CONTROL_COMMANDS.has(command)) return command;
620
643
  }
621
644
  if (message.body && typeof message.body === "object") {
622
645
  const command = String(message.body.command ?? "").trim();
623
646
  if (CONTROL_COMMANDS.has(command)) return command;
624
647
  }
625
- if (typeof message.type === "string") {
626
- if (message.type === "control.stop" || message.type === "control.cancel")
627
- return "stop";
628
- const match = /^player\.(.+)$/.exec(message.type);
629
- if (match && CONTROL_COMMANDS.has(match[1])) return match[1];
630
- }
631
648
  return undefined;
632
649
  }
633
650
 
@@ -895,6 +912,7 @@ async function playMain(args) {
895
912
  const command = readAndClearCommand(ctx);
896
913
  if (command === "stop") {
897
914
  writeStatus(ctx, "stopped", index, count, track, player, "");
915
+ emitStoppedEvent(ctx, "message");
898
916
  return;
899
917
  }
900
918
  if (command === "previous" || command === "prev") {
@@ -913,6 +931,7 @@ async function playMain(args) {
913
931
  }
914
932
  }
915
933
  writeStatus(ctx, "stopped", index, tracks.length, "", player, "");
934
+ emitStoppedEvent(ctx, "complete");
916
935
  } finally {
917
936
  cleanup();
918
937
  await Promise.race([controlLoop?.promise ?? Promise.resolve(), sleep(100)]);
@@ -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.2
5
+ version: 0.24.4
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.2
5
+ version: 0.24.4
6
6
  ---
7
7
 
8
8
  # Swarm