@llblab/pi-actors 0.22.4 → 0.23.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.
- package/AGENTS.md +1 -0
- package/BACKLOG.md +0 -220
- package/CHANGELOG.md +23 -1
- package/README.md +1 -1
- package/dist/index.js +15 -0
- package/dist/lib/actor-inspector-tui.d.ts +4 -0
- package/dist/lib/actor-inspector-tui.js +53 -12
- package/dist/lib/actor-rooms.d.ts +8 -0
- package/dist/lib/actor-rooms.js +61 -10
- package/dist/lib/async-runs.d.ts +34 -2
- package/dist/lib/async-runs.js +215 -22
- package/dist/lib/command-templates.js +5 -5
- package/dist/lib/limits.d.ts +11 -0
- package/dist/lib/limits.js +11 -0
- package/dist/lib/observability.js +6 -4
- package/dist/lib/output.js +4 -5
- package/dist/lib/recipe-discovery.js +1 -0
- package/dist/lib/recipe-references.d.ts +11 -2
- package/dist/lib/recipe-references.js +4 -2
- package/dist/lib/recipe-usage.d.ts +2 -1
- package/dist/lib/recipe-usage.js +15 -3
- package/dist/lib/runtime.js +12 -2
- package/dist/lib/tools.js +197 -38
- package/docs/async-runs.md +2 -1
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +0 -1
- package/index.ts +18 -0
- package/lib/actor-inspector-tui.ts +131 -29
- package/lib/actor-rooms.ts +155 -44
- package/lib/async-runs.ts +290 -31
- package/lib/command-templates.ts +5 -5
- package/lib/limits.ts +12 -0
- package/lib/observability.ts +10 -4
- package/lib/output.ts +4 -6
- package/lib/recipe-discovery.ts +1 -0
- package/lib/recipe-references.ts +20 -4
- package/lib/recipe-usage.ts +31 -4
- package/lib/runtime.ts +31 -7
- package/lib/tools.ts +296 -57
- package/package.json +2 -1
- package/scripts/async-runner.mjs +6 -0
- package/scripts/conformance.mjs +47 -0
- package/scripts/coordinator.mjs +13 -0
- package/scripts/locker.mjs +13 -0
- package/scripts/music-player.mjs +21 -2
- package/scripts/recipe-utils.mjs +13 -0
- package/scripts/validate-recipe.mjs +13 -0
- package/skills/actors/SKILL.md +3 -3
- package/skills/swarm/SKILL.md +1 -1
|
@@ -9,16 +9,19 @@ import * as path from "node:path";
|
|
|
9
9
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
10
10
|
|
|
11
11
|
import type { ActorMessage } from "./actor-messages.ts";
|
|
12
|
+
import * as Limits from "./limits.ts";
|
|
12
13
|
import * as Paths from "./paths.ts";
|
|
13
14
|
|
|
14
15
|
export interface ActorInspectorPreview {
|
|
15
16
|
body_preview?: string;
|
|
16
17
|
branch?: string;
|
|
17
18
|
channel: "broadcast" | "direct" | "room";
|
|
19
|
+
event_id?: string;
|
|
18
20
|
from?: string;
|
|
19
21
|
from_display?: string;
|
|
20
22
|
inbox_status?: string;
|
|
21
23
|
message_id?: string;
|
|
24
|
+
needs_response?: boolean;
|
|
22
25
|
run: string;
|
|
23
26
|
sequence?: number;
|
|
24
27
|
summary?: string;
|
|
@@ -57,6 +60,7 @@ export interface ActorInspectorPreviewReadOptions {
|
|
|
57
60
|
currentRunOnly?: boolean;
|
|
58
61
|
channels?: ActorInspectorPreview["channel"][];
|
|
59
62
|
mention?: string;
|
|
63
|
+
readKeys?: Iterable<string>;
|
|
60
64
|
roomLimitPerRun?: number;
|
|
61
65
|
unreadOnly?: boolean;
|
|
62
66
|
}
|
|
@@ -86,7 +90,10 @@ function readJsonLines(file: string): Record<string, unknown>[] {
|
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
function previewValue(
|
|
93
|
+
function previewValue(
|
|
94
|
+
value: unknown,
|
|
95
|
+
maxLength = Limits.INSPECTOR_BODY_PREVIEW_CHARS,
|
|
96
|
+
): string | undefined {
|
|
90
97
|
if (value === undefined) return undefined;
|
|
91
98
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
92
99
|
const compact = text.replaceAll(/\s+/g, " ").trim();
|
|
@@ -118,6 +125,11 @@ function previewFromMessage(
|
|
|
118
125
|
const summary =
|
|
119
126
|
typeof message.summary === "string" ? message.summary : undefined;
|
|
120
127
|
const body = asRecord(message.body);
|
|
128
|
+
const metadata = asRecord(message.metadata);
|
|
129
|
+
const eventId =
|
|
130
|
+
typeof message.id === "string" && message.id.trim()
|
|
131
|
+
? message.id.trim()
|
|
132
|
+
: undefined;
|
|
121
133
|
const display = from
|
|
122
134
|
? typeof body.display === "string" && body.display.trim()
|
|
123
135
|
? body.display.trim()
|
|
@@ -128,8 +140,10 @@ function previewFromMessage(
|
|
|
128
140
|
? { body_preview: previewValue(message.body) }
|
|
129
141
|
: {}),
|
|
130
142
|
channel: channelFor({ to }),
|
|
143
|
+
...(eventId ? { event_id: eventId } : {}),
|
|
131
144
|
...(from ? { from } : {}),
|
|
132
145
|
...(display ? { from_display: display } : {}),
|
|
146
|
+
...(metadata.requires_response === true ? { needs_response: true } : {}),
|
|
133
147
|
run,
|
|
134
148
|
...(summary ? { summary } : {}),
|
|
135
149
|
timestamp,
|
|
@@ -144,19 +158,29 @@ function readRoomRosterRecords(
|
|
|
144
158
|
): Record<string, Record<string, unknown>> {
|
|
145
159
|
try {
|
|
146
160
|
return JSON.parse(
|
|
147
|
-
fs.readFileSync(
|
|
161
|
+
fs.readFileSync(
|
|
162
|
+
path.join(stateDir, "rooms", room, "roster.json"),
|
|
163
|
+
"utf8",
|
|
164
|
+
),
|
|
148
165
|
) as Record<string, Record<string, unknown>>;
|
|
149
166
|
} catch {
|
|
150
167
|
return {};
|
|
151
168
|
}
|
|
152
169
|
}
|
|
153
170
|
|
|
154
|
-
function memberDisplay(
|
|
155
|
-
|
|
171
|
+
function memberDisplay(
|
|
172
|
+
_address: string,
|
|
173
|
+
member: Record<string, unknown>,
|
|
174
|
+
): string | undefined {
|
|
175
|
+
const display =
|
|
176
|
+
typeof member.display === "string" ? member.display.trim() : "";
|
|
156
177
|
return display || undefined;
|
|
157
178
|
}
|
|
158
179
|
|
|
159
|
-
function readRoomDisplayNames(
|
|
180
|
+
function readRoomDisplayNames(
|
|
181
|
+
stateDir: string,
|
|
182
|
+
room: string,
|
|
183
|
+
): Record<string, string> {
|
|
160
184
|
const roster = readRoomRosterRecords(stateDir, room);
|
|
161
185
|
return Object.fromEntries(
|
|
162
186
|
Object.entries(roster).flatMap(([address, member]) => {
|
|
@@ -227,17 +251,28 @@ function readBranchInboxPreviews(
|
|
|
227
251
|
const preview = previewFromMessage(
|
|
228
252
|
run,
|
|
229
253
|
message,
|
|
230
|
-
String(
|
|
254
|
+
String(
|
|
255
|
+
message.queued_at ??
|
|
256
|
+
message.received_at ??
|
|
257
|
+
message.timestamp ??
|
|
258
|
+
"",
|
|
259
|
+
),
|
|
231
260
|
);
|
|
232
261
|
if (!preview) return undefined;
|
|
233
262
|
return {
|
|
234
263
|
...preview,
|
|
235
264
|
branch: entry.name,
|
|
236
|
-
...(typeof message.id === "string"
|
|
237
|
-
|
|
265
|
+
...(typeof message.id === "string"
|
|
266
|
+
? { message_id: message.id }
|
|
267
|
+
: {}),
|
|
268
|
+
...(typeof message.status === "string"
|
|
269
|
+
? { inbox_status: message.status }
|
|
270
|
+
: {}),
|
|
238
271
|
};
|
|
239
272
|
})
|
|
240
|
-
.filter((preview): preview is ActorInspectorPreview =>
|
|
273
|
+
.filter((preview): preview is ActorInspectorPreview =>
|
|
274
|
+
Boolean(preview),
|
|
275
|
+
),
|
|
241
276
|
);
|
|
242
277
|
} catch (error) {
|
|
243
278
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
@@ -276,8 +311,29 @@ function matchesOwner(stateDir: string, ownerId: string | undefined): boolean {
|
|
|
276
311
|
return ownerId === undefined || getRunOwnerId(stateDir) === ownerId;
|
|
277
312
|
}
|
|
278
313
|
|
|
279
|
-
function
|
|
280
|
-
|
|
314
|
+
export function inspectorPreviewReadKey(
|
|
315
|
+
preview: ActorInspectorPreview,
|
|
316
|
+
): string {
|
|
317
|
+
return [
|
|
318
|
+
preview.channel,
|
|
319
|
+
preview.run,
|
|
320
|
+
preview.branch ?? "",
|
|
321
|
+
preview.event_id ?? preview.message_id ?? preview.timestamp,
|
|
322
|
+
preview.from ?? "",
|
|
323
|
+
preview.to,
|
|
324
|
+
preview.type,
|
|
325
|
+
].join("|");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function isUnreadPreview(
|
|
329
|
+
preview: ActorInspectorPreview,
|
|
330
|
+
readKeys?: Set<string>,
|
|
331
|
+
): boolean {
|
|
332
|
+
if (readKeys?.has(inspectorPreviewReadKey(preview))) return false;
|
|
333
|
+
return (
|
|
334
|
+
preview.inbox_status === "queued" ||
|
|
335
|
+
(preview.inbox_status === undefined && preview.branch !== undefined)
|
|
336
|
+
);
|
|
281
337
|
}
|
|
282
338
|
|
|
283
339
|
function matchesBranchFilter(
|
|
@@ -298,7 +354,8 @@ function matchesPreviewFilter(
|
|
|
298
354
|
if (options.channels?.length && !options.channels.includes(preview.channel)) {
|
|
299
355
|
return false;
|
|
300
356
|
}
|
|
301
|
-
|
|
357
|
+
const readKeys = options.readKeys ? new Set(options.readKeys) : undefined;
|
|
358
|
+
if (options.unreadOnly && !isUnreadPreview(preview, readKeys)) return false;
|
|
302
359
|
if (!matchesBranchFilter(preview, options.branch)) return false;
|
|
303
360
|
const mention = options.mention?.trim().toLowerCase();
|
|
304
361
|
if (!mention) return true;
|
|
@@ -421,6 +478,10 @@ function routeText(preview: ActorInspectorPreview): string {
|
|
|
421
478
|
return `${actor} → ${actorName(preview.to)}`;
|
|
422
479
|
}
|
|
423
480
|
|
|
481
|
+
function attentionMarker(preview: ActorInspectorPreview): string {
|
|
482
|
+
return preview.needs_response ? "! " : "";
|
|
483
|
+
}
|
|
484
|
+
|
|
424
485
|
function style(
|
|
425
486
|
styleFn: ((text: string) => string) | undefined,
|
|
426
487
|
text: string,
|
|
@@ -435,7 +496,8 @@ function previewText(preview: ActorInspectorPreview): string {
|
|
|
435
496
|
function propertyValue(value: unknown): string {
|
|
436
497
|
if (value === undefined) return "";
|
|
437
498
|
if (typeof value === "string") return value;
|
|
438
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
499
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
500
|
+
return String(value);
|
|
439
501
|
return JSON.stringify(value);
|
|
440
502
|
}
|
|
441
503
|
|
|
@@ -491,7 +553,7 @@ function renderCompactInspectorEntry(
|
|
|
491
553
|
const contentWidth = Math.max(8, width - prefix.length - suffix.length);
|
|
492
554
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
493
555
|
const sequencePrefix = `${sequence}${separator}`;
|
|
494
|
-
const route = routeText(preview)
|
|
556
|
+
const route = `${attentionMarker(preview)}${routeText(preview)}`;
|
|
495
557
|
const routePadding = " ".repeat(
|
|
496
558
|
Math.max(0, routeWidth - displayWidth(route)),
|
|
497
559
|
);
|
|
@@ -537,14 +599,19 @@ function renderInspectorEntry(
|
|
|
537
599
|
const contentWidth = Math.max(8, width - prefix.length - suffix.length);
|
|
538
600
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
539
601
|
const sequencePrefix = `${sequence}${separator}`;
|
|
540
|
-
const route = routeText(preview)
|
|
602
|
+
const route = `${attentionMarker(preview)}${routeText(preview)}`;
|
|
541
603
|
const type = preview.type;
|
|
542
604
|
const summary = preview.summary?.trim() ?? "";
|
|
543
|
-
const body =
|
|
605
|
+
const body =
|
|
606
|
+
preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
|
|
544
607
|
const visibleRoute = boundedLine(route, routeWidth);
|
|
545
608
|
const visibleType = boundedLine(type, typeWidth);
|
|
546
|
-
const boundedRoutePadding = " ".repeat(
|
|
547
|
-
|
|
609
|
+
const boundedRoutePadding = " ".repeat(
|
|
610
|
+
Math.max(0, routeWidth - displayWidth(visibleRoute)),
|
|
611
|
+
);
|
|
612
|
+
const boundedTypePadding = " ".repeat(
|
|
613
|
+
Math.max(0, typeWidth - displayWidth(visibleType)),
|
|
614
|
+
);
|
|
548
615
|
const leadParts = [
|
|
549
616
|
style(styles.muted, sequencePrefix),
|
|
550
617
|
style(styles.target, visibleRoute),
|
|
@@ -557,11 +624,20 @@ function renderInspectorEntry(
|
|
|
557
624
|
let lead = `${sequencePrefix}${visibleRoute}${boundedRoutePadding}${separator}${visibleType}${boundedTypePadding}${separator}`;
|
|
558
625
|
if (summary) {
|
|
559
626
|
const visibleSummary = boundedLine(summary, summaryWidth);
|
|
560
|
-
const summaryPadding = " ".repeat(
|
|
627
|
+
const summaryPadding = " ".repeat(
|
|
628
|
+
Math.max(0, summaryWidth - displayWidth(visibleSummary)),
|
|
629
|
+
);
|
|
561
630
|
lead += `${visibleSummary}${summaryPadding}${separator}`;
|
|
562
|
-
leadParts.push(
|
|
631
|
+
leadParts.push(
|
|
632
|
+
style(styles.preview, visibleSummary),
|
|
633
|
+
summaryPadding,
|
|
634
|
+
separator,
|
|
635
|
+
);
|
|
563
636
|
}
|
|
564
|
-
const visibleBody = boundedLine(
|
|
637
|
+
const visibleBody = boundedLine(
|
|
638
|
+
body,
|
|
639
|
+
Math.max(0, contentWidth - displayWidth(lead)),
|
|
640
|
+
);
|
|
565
641
|
const plain = `${lead}${visibleBody}`;
|
|
566
642
|
const rendered = `${leadParts.join("")}${style(styles.preview, visibleBody)}`;
|
|
567
643
|
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}${suffix}`;
|
|
@@ -589,7 +665,10 @@ export function readActorInspectorRoster(
|
|
|
589
665
|
|
|
590
666
|
function rosterRoleText(role: string | undefined): string | undefined {
|
|
591
667
|
const roleLabel = role?.split(";")[0] ?? "";
|
|
592
|
-
const cleaned = roleLabel
|
|
668
|
+
const cleaned = roleLabel
|
|
669
|
+
.replaceAll(/\s*\([^)]*\)\s*$/g, "")
|
|
670
|
+
.trim()
|
|
671
|
+
.toLowerCase();
|
|
593
672
|
if (!cleaned || cleaned === "actor") return undefined;
|
|
594
673
|
return cleaned.replaceAll(/\s+/g, "-");
|
|
595
674
|
}
|
|
@@ -603,7 +682,12 @@ function rosterMemberText(member: ActorInspectorRosterMember): string {
|
|
|
603
682
|
|
|
604
683
|
function isRosterMemberActive(member: ActorInspectorRosterMember): boolean {
|
|
605
684
|
const status = member.status?.trim().toLowerCase();
|
|
606
|
-
return
|
|
685
|
+
return (
|
|
686
|
+
!status ||
|
|
687
|
+
status === "present" ||
|
|
688
|
+
status === "active" ||
|
|
689
|
+
status === "running"
|
|
690
|
+
);
|
|
607
691
|
}
|
|
608
692
|
|
|
609
693
|
export function renderInspectorRosterLine(
|
|
@@ -633,12 +717,17 @@ export function renderInspectorRosterPanel(
|
|
|
633
717
|
const flush = () => {
|
|
634
718
|
const visible = boundedLine(plain, innerWidth);
|
|
635
719
|
const line = visible === plain ? rendered : style(styles.muted, visible);
|
|
636
|
-
lines.push(
|
|
720
|
+
lines.push(
|
|
721
|
+
` ${line}${" ".repeat(Math.max(0, innerWidth - displayWidth(visible)))} `,
|
|
722
|
+
);
|
|
637
723
|
};
|
|
638
724
|
for (const token of tokens) {
|
|
639
725
|
const separator = plain === prefix ? "" : ", ";
|
|
640
726
|
const nextPlain = `${plain}${separator}${token.text}`;
|
|
641
|
-
const renderedToken = style(
|
|
727
|
+
const renderedToken = style(
|
|
728
|
+
token.active ? styles.target : styles.muted,
|
|
729
|
+
token.text,
|
|
730
|
+
);
|
|
642
731
|
if (plain !== prefix && displayWidth(nextPlain) > innerWidth) {
|
|
643
732
|
flush();
|
|
644
733
|
plain = ` ${token.text}`;
|
|
@@ -664,12 +753,15 @@ export function renderInspectorItemView(
|
|
|
664
753
|
const orderedKeys = [
|
|
665
754
|
"channel",
|
|
666
755
|
"run",
|
|
756
|
+
"event_id",
|
|
667
757
|
"from",
|
|
668
758
|
"from_display",
|
|
669
759
|
"to",
|
|
670
760
|
"type",
|
|
671
761
|
"summary",
|
|
672
762
|
"body_preview",
|
|
763
|
+
"message_id",
|
|
764
|
+
"needs_response",
|
|
673
765
|
"timestamp",
|
|
674
766
|
"stripe",
|
|
675
767
|
] as const;
|
|
@@ -678,7 +770,9 @@ export function renderInspectorItemView(
|
|
|
678
770
|
.map((key) => [key, propertyValue(preview[key])] as const);
|
|
679
771
|
const keyWidth = Math.max(1, ...entries.map(([key]) => displayWidth(key)));
|
|
680
772
|
const sequenceText = String(preview.sequence ?? options.sequence);
|
|
681
|
-
const sequencePadding = " ".repeat(
|
|
773
|
+
const sequencePadding = " ".repeat(
|
|
774
|
+
Math.max(0, keyWidth - displayWidth(sequenceText)),
|
|
775
|
+
);
|
|
682
776
|
const headerSeparator = " ";
|
|
683
777
|
const route = routeText(preview);
|
|
684
778
|
const visibleRoute = boundedLine(
|
|
@@ -728,7 +822,9 @@ export function renderInspectorWidget(
|
|
|
728
822
|
safeWidth - 1 - sequencePrefixWidth - fixedSeparatorsWidth,
|
|
729
823
|
);
|
|
730
824
|
const naturalRouteWidth = Math.max(
|
|
731
|
-
...visible.map(({ preview }) =>
|
|
825
|
+
...visible.map(({ preview }) =>
|
|
826
|
+
displayWidth(`${attentionMarker(preview)}${routeText(preview)}`),
|
|
827
|
+
),
|
|
732
828
|
);
|
|
733
829
|
const naturalTypeWidth = Math.max(
|
|
734
830
|
...visible.map(({ preview }) => displayWidth(preview.type)),
|
|
@@ -741,13 +837,19 @@ export function renderInspectorWidget(
|
|
|
741
837
|
naturalTypeWidth,
|
|
742
838
|
Math.max(4, Math.floor(availableForColumns * 0.25)),
|
|
743
839
|
);
|
|
744
|
-
const messageWidth = Math.max(
|
|
840
|
+
const messageWidth = Math.max(
|
|
841
|
+
0,
|
|
842
|
+
availableForColumns - routeWidth - typeWidth,
|
|
843
|
+
);
|
|
745
844
|
const summaryWidths = visible
|
|
746
845
|
.map(({ preview }) => preview.summary?.trim())
|
|
747
846
|
.filter((summary): summary is string => Boolean(summary))
|
|
748
847
|
.map((summary) => displayWidth(summary));
|
|
749
848
|
const summaryWidth = summaryWidths.length
|
|
750
|
-
? Math.min(
|
|
849
|
+
? Math.min(
|
|
850
|
+
Math.max(...summaryWidths),
|
|
851
|
+
Math.max(1, Math.floor(messageWidth * 0.5)),
|
|
852
|
+
)
|
|
751
853
|
: 0;
|
|
752
854
|
for (const { preview, stripe } of visible) {
|
|
753
855
|
lines.push(
|