@llblab/pi-actors 0.17.1 → 0.19.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 +6 -2
- package/BACKLOG.md +32 -26
- package/CHANGELOG.md +19 -3
- package/README.md +23 -8
- package/docs/actor-messages.md +5 -3
- package/docs/async-runs.md +3 -5
- package/docs/command-templates.md +2 -0
- package/docs/recipe-library.md +3 -1
- package/docs/task-first-recipes.md +29 -0
- package/docs/template-recipes.md +9 -14
- package/index.ts +111 -32
- package/lib/actor-inspector-tui.ts +192 -42
- package/lib/actor-rooms.ts +220 -26
- package/lib/async-runs.ts +59 -1
- package/lib/execution.ts +17 -0
- package/lib/file-state.ts +2 -1
- package/lib/observability.ts +82 -2
- package/lib/prompts.ts +2 -2
- package/lib/recipe-discovery.ts +86 -6
- package/lib/recipe-migration.ts +0 -2
- package/lib/recipe-references.ts +43 -10
- package/lib/temp.ts +55 -2
- package/lib/tools.ts +99 -11
- package/package.json +1 -1
- package/recipes/coordinator-locker.json +1 -2
- package/recipes/lens-swarm.json +0 -1
- package/recipes/locker.json +45 -0
- package/recipes/music-player.json +0 -1
- package/recipes/pipeline-architect-coordinator.json +0 -1
- package/recipes/pipeline-artifact-bundle.json +0 -1
- package/recipes/pipeline-artifact-report.json +0 -1
- package/recipes/pipeline-artifact-write.json +0 -1
- package/recipes/pipeline-async-run-ops.json +0 -1
- package/recipes/pipeline-checkpoint-continuation.json +0 -1
- package/recipes/pipeline-development-tasking.json +0 -1
- package/recipes/pipeline-docs-maintenance.json +0 -1
- package/recipes/pipeline-media-library.json +0 -1
- package/recipes/pipeline-quorum-review.json +0 -1
- package/recipes/pipeline-release-readiness.json +0 -1
- package/recipes/pipeline-release-summary.json +0 -1
- package/recipes/pipeline-repo-health.json +0 -1
- package/recipes/pipeline-research-synthesis.json +0 -1
- package/recipes/pipeline-review-readiness.json +0 -1
- package/recipes/pipeline-room-swarm.json +3 -2
- package/recipes/subagent-artifact.json +0 -1
- package/recipes/subagent-checkpoint.json +0 -1
- package/recipes/subagent-conflict-report.json +0 -1
- package/recipes/subagent-contradiction-map.json +0 -1
- package/recipes/subagent-critic.json +0 -1
- package/recipes/subagent-evidence-map.json +0 -1
- package/recipes/subagent-followup.json +0 -1
- package/recipes/subagent-judge.json +0 -1
- package/recipes/subagent-merge.json +0 -1
- package/recipes/subagent-message.json +0 -1
- package/recipes/subagent-normalize.json +0 -1
- package/recipes/subagent-plan.json +0 -1
- package/recipes/subagent-prompt.json +0 -1
- package/recipes/subagent-quorum.json +0 -1
- package/recipes/subagent-review-coordinator.json +0 -1
- package/recipes/subagent-review.json +0 -1
- package/recipes/subagent-task-card.json +0 -1
- package/recipes/subagent-tools.json +0 -1
- package/recipes/subagent-verify.json +0 -1
- package/recipes/subagents-prompts.json +0 -1
- package/recipes/utility-actor-message.json +0 -1
- package/recipes/utility-artifact-manifest.json +0 -1
- package/recipes/utility-artifact-write.json +0 -1
- package/recipes/utility-changelog-head.json +0 -1
- package/recipes/utility-changelog-section.json +0 -1
- package/recipes/utility-coordinator-lock-snapshot.json +0 -1
- package/recipes/utility-git-log.json +0 -1
- package/recipes/utility-git-status.json +0 -1
- package/recipes/utility-jsonl-tail.json +0 -1
- package/recipes/utility-markdown-index.json +0 -1
- package/recipes/utility-package-summary.json +0 -1
- package/recipes/utility-playlist-build.json +0 -1
- package/recipes/utility-playlist-scan.json +0 -1
- package/recipes/utility-run-ops-snapshot.json +0 -1
- package/recipes/utility-run-state-files.json +0 -1
- package/recipes/utility-run-summary.json +0 -1
- package/recipes/utility-skill-summary.json +0 -1
- package/recipes/utility-validate-recipe.json +0 -1
- package/recipes/utility-validation-wrapper.json +0 -1
- package/scripts/coordinator.mjs +434 -0
- package/scripts/{coordinator-locker.mjs → locker.mjs} +23 -22
- package/skills/actors/SKILL.md +26 -12
- package/skills/swarm/SKILL.md +15 -1
- package/scripts/room-swarm.mjs +0 -244
package/index.ts
CHANGED
|
@@ -50,7 +50,13 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
50
50
|
let runStatusFrame = 0;
|
|
51
51
|
let communicationWidgetVisible = false;
|
|
52
52
|
let actorInspectorRows = 12;
|
|
53
|
+
let actorInspectorChannels:
|
|
54
|
+
| ActorInspectorTui.ActorInspectorPreview["channel"][]
|
|
55
|
+
| undefined;
|
|
56
|
+
let actorInspectorMention: string | undefined;
|
|
57
|
+
const actorInspectorRoomLimitPerRun = 6;
|
|
53
58
|
let selectedInspectorSequence: number | undefined;
|
|
59
|
+
let recipeWatcherFailureNotified = false;
|
|
54
60
|
const getRunOwnerId = (ctx: ExtensionContext): string =>
|
|
55
61
|
ctx.sessionManager.getSessionId();
|
|
56
62
|
const updateRunUi = (ctx: ExtensionContext, notify = false): void => {
|
|
@@ -64,40 +70,59 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
64
70
|
ctx.ui.setWidget(
|
|
65
71
|
"zz-pi-actors-comms",
|
|
66
72
|
communicationWidgetVisible
|
|
67
|
-
? () =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
? () => {
|
|
74
|
+
const style = {
|
|
75
|
+
actor: (text: string) => ctx.ui.theme.fg("accent", text),
|
|
76
|
+
muted: (text: string) => ctx.ui.theme.fg("dim", text),
|
|
77
|
+
preview: (text: string) => ctx.ui.theme.fg("text", text),
|
|
78
|
+
stripe: (text: string) => text,
|
|
79
|
+
stripeAlt: (text: string) =>
|
|
80
|
+
ctx.ui.theme.bg("customMessageBg", text),
|
|
81
|
+
target: (text: string) => ctx.ui.theme.fg("success", text),
|
|
82
|
+
type: (text: string) => ctx.ui.theme.fg("warning", text),
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
invalidate() {},
|
|
86
|
+
render(width: number) {
|
|
87
|
+
const previews = ActorInspectorTui.readActorInspectorPreviews(
|
|
88
|
+
RUN_STATE_ROOT,
|
|
89
|
+
actorInspectorRows,
|
|
90
|
+
{
|
|
91
|
+
channels: actorInspectorChannels,
|
|
92
|
+
currentRunOnly: true,
|
|
93
|
+
mention: actorInspectorMention,
|
|
94
|
+
ownerId,
|
|
95
|
+
roomLimitPerRun: actorInspectorRoomLimitPerRun,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
const rows =
|
|
99
|
+
(selectedInspectorSequence !== undefined
|
|
100
|
+
? ActorInspectorTui.renderInspectorItemView(
|
|
101
|
+
previews,
|
|
102
|
+
width,
|
|
103
|
+
style,
|
|
104
|
+
{ sequence: selectedInspectorSequence },
|
|
105
|
+
)
|
|
106
|
+
: ActorInspectorTui.renderInspectorWidget(
|
|
107
|
+
previews,
|
|
108
|
+
width,
|
|
109
|
+
style,
|
|
110
|
+
)) ?? [];
|
|
111
|
+
const run = previews[0]?.run;
|
|
112
|
+
const roster = run
|
|
113
|
+
? ActorInspectorTui.renderInspectorRosterPanel(
|
|
114
|
+
ActorInspectorTui.readActorInspectorRoster(
|
|
115
|
+
RUN_STATE_ROOT,
|
|
116
|
+
run,
|
|
117
|
+
),
|
|
89
118
|
width,
|
|
90
119
|
style,
|
|
91
|
-
{ sequence: selectedInspectorSequence },
|
|
92
120
|
)
|
|
93
|
-
:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
},
|
|
100
|
-
})
|
|
121
|
+
: undefined;
|
|
122
|
+
return roster ? [...roster, ...rows] : rows;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
101
126
|
: undefined,
|
|
102
127
|
{ placement: "belowEditor" },
|
|
103
128
|
);
|
|
@@ -127,6 +152,12 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
127
152
|
{ deliverAs: "followUp", triggerTurn: true },
|
|
128
153
|
);
|
|
129
154
|
}
|
|
155
|
+
Observability.pruneRunObservationState(
|
|
156
|
+
observedRuns,
|
|
157
|
+
observedRunEventLines,
|
|
158
|
+
summary,
|
|
159
|
+
transitions.map((transition) => transition.run),
|
|
160
|
+
);
|
|
130
161
|
for (const event of outboxEvents) {
|
|
131
162
|
if (!Observability.shouldNotifyRunOutboxEvent(event)) continue;
|
|
132
163
|
const text = Observability.formatRunOutboxMessage(event);
|
|
@@ -200,7 +231,16 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
200
231
|
if (recipeReloadTimeout) clearTimeout(recipeReloadTimeout);
|
|
201
232
|
recipeReloadTimeout = undefined;
|
|
202
233
|
};
|
|
234
|
+
const notifyRecipeWatcherFailure = (ctx: ExtensionContext): void => {
|
|
235
|
+
if (recipeWatcherFailureNotified) return;
|
|
236
|
+
recipeWatcherFailureNotified = true;
|
|
237
|
+
ctx.ui.notify(
|
|
238
|
+
"Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.",
|
|
239
|
+
"warning",
|
|
240
|
+
);
|
|
241
|
+
};
|
|
203
242
|
const scheduleRecipeReload = (ctx: ExtensionContext): void => {
|
|
243
|
+
recipeWatcherFailureNotified = false;
|
|
204
244
|
if (recipeReloadTimeout) clearTimeout(recipeReloadTimeout);
|
|
205
245
|
recipeReloadTimeout = setTimeout(() => {
|
|
206
246
|
runtime.loadTools(ctx);
|
|
@@ -216,9 +256,10 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
216
256
|
recipeRootWatcher.on("error", () => {
|
|
217
257
|
recipeRootWatcher?.close();
|
|
218
258
|
recipeRootWatcher = undefined;
|
|
259
|
+
notifyRecipeWatcherFailure(ctx);
|
|
219
260
|
});
|
|
220
261
|
} catch {
|
|
221
|
-
|
|
262
|
+
notifyRecipeWatcherFailure(ctx);
|
|
222
263
|
}
|
|
223
264
|
};
|
|
224
265
|
const actorToolDefinitions = new Map<string, any>();
|
|
@@ -292,6 +333,44 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
292
333
|
);
|
|
293
334
|
},
|
|
294
335
|
});
|
|
336
|
+
pi.registerCommand("actors-inspector-filter", {
|
|
337
|
+
description:
|
|
338
|
+
"Filter actor inspector rows: all, room, direct, broadcast, mention <text>",
|
|
339
|
+
handler: async (args, ctx) => {
|
|
340
|
+
const parts = Array.isArray(args)
|
|
341
|
+
? args.map(String)
|
|
342
|
+
: String(args ?? "").split(/\s+/);
|
|
343
|
+
const mode = (parts[0] ?? "").trim().toLowerCase();
|
|
344
|
+
if (!mode || mode === "all" || mode === "clear") {
|
|
345
|
+
actorInspectorChannels = undefined;
|
|
346
|
+
actorInspectorMention = undefined;
|
|
347
|
+
} else if (mode === "room" || mode === "direct" || mode === "broadcast") {
|
|
348
|
+
actorInspectorChannels = [mode];
|
|
349
|
+
actorInspectorMention = undefined;
|
|
350
|
+
} else if (mode === "mention") {
|
|
351
|
+
const mention = parts.slice(1).join(" ").trim();
|
|
352
|
+
if (!mention) {
|
|
353
|
+
ctx.ui.notify(
|
|
354
|
+
"Usage: /actors-inspector-filter mention <text>",
|
|
355
|
+
"warning",
|
|
356
|
+
);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
actorInspectorChannels = undefined;
|
|
360
|
+
actorInspectorMention = mention;
|
|
361
|
+
} else {
|
|
362
|
+
ctx.ui.notify(
|
|
363
|
+
"Usage: /actors-inspector-filter all|room|direct|broadcast|mention <text>",
|
|
364
|
+
"warning",
|
|
365
|
+
);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
selectedInspectorSequence = undefined;
|
|
369
|
+
communicationWidgetVisible = true;
|
|
370
|
+
updateRunUi(ctx);
|
|
371
|
+
ctx.ui.notify(`Actor inspector filter ${mode || "all"}`, "info");
|
|
372
|
+
},
|
|
373
|
+
});
|
|
295
374
|
pi.registerCommand("actors-inspect", {
|
|
296
375
|
description: "Inspect actor message by visible number",
|
|
297
376
|
handler: async (args, ctx) => {
|
|
@@ -41,9 +41,19 @@ export interface ActorInspectorItemViewOptions {
|
|
|
41
41
|
sequence: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export interface ActorInspectorRosterMember {
|
|
45
|
+
address: string;
|
|
46
|
+
display?: string;
|
|
47
|
+
role?: string;
|
|
48
|
+
status?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
export interface ActorInspectorPreviewReadOptions {
|
|
45
52
|
ownerId?: string;
|
|
46
53
|
currentRunOnly?: boolean;
|
|
54
|
+
channels?: ActorInspectorPreview["channel"][];
|
|
55
|
+
mention?: string;
|
|
56
|
+
roomLimitPerRun?: number;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
@@ -123,32 +133,42 @@ function previewFromMessage(
|
|
|
123
133
|
};
|
|
124
134
|
}
|
|
125
135
|
|
|
126
|
-
function
|
|
136
|
+
function readRoomRosterRecords(
|
|
137
|
+
stateDir: string,
|
|
138
|
+
room: string,
|
|
139
|
+
): Record<string, Record<string, unknown>> {
|
|
127
140
|
try {
|
|
128
|
-
|
|
141
|
+
return JSON.parse(
|
|
129
142
|
fs.readFileSync(path.join(stateDir, "rooms", room, "roster.json"), "utf8"),
|
|
130
143
|
) as Record<string, Record<string, unknown>>;
|
|
131
|
-
return Object.fromEntries(
|
|
132
|
-
Object.entries(roster).flatMap(([address, member]) => {
|
|
133
|
-
const glyph = typeof member.glyph === "string" ? member.glyph.trim() : "";
|
|
134
|
-
const display = typeof member.display === "string" ? member.display.trim() : "";
|
|
135
|
-
if (display) return [[address, display]];
|
|
136
|
-
if (!glyph) return [];
|
|
137
|
-
return [[address, `${glyph} ${actorName(address)}`]];
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
144
|
} catch {
|
|
141
145
|
return {};
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
function memberDisplay(_address: string, member: Record<string, unknown>): string | undefined {
|
|
150
|
+
const display = typeof member.display === "string" ? member.display.trim() : "";
|
|
151
|
+
return display || undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function readRoomDisplayNames(stateDir: string, room: string): Record<string, string> {
|
|
155
|
+
const roster = readRoomRosterRecords(stateDir, room);
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
Object.entries(roster).flatMap(([address, member]) => {
|
|
158
|
+
const display = memberDisplay(address, member);
|
|
159
|
+
return display ? [[address, display]] : [];
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
145
164
|
function readRoomPreviews(
|
|
146
165
|
run: string,
|
|
147
166
|
stateDir: string,
|
|
167
|
+
limitPerRun?: number,
|
|
148
168
|
): ActorInspectorPreview[] {
|
|
149
169
|
const roomsDir = path.join(stateDir, "rooms");
|
|
150
170
|
try {
|
|
151
|
-
|
|
171
|
+
const previews = fs
|
|
152
172
|
.readdirSync(roomsDir, { withFileTypes: true })
|
|
153
173
|
.filter((entry) => entry.isDirectory())
|
|
154
174
|
.flatMap((entry) => {
|
|
@@ -166,6 +186,8 @@ function readRoomPreviews(
|
|
|
166
186
|
Boolean(preview),
|
|
167
187
|
);
|
|
168
188
|
});
|
|
189
|
+
const limit = Number.isFinite(limitPerRun) ? Math.max(0, Number(limitPerRun)) : undefined;
|
|
190
|
+
return limit === undefined ? previews : previews.slice(-limit);
|
|
169
191
|
} catch (error) {
|
|
170
192
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
171
193
|
return [];
|
|
@@ -218,6 +240,25 @@ function matchesOwner(stateDir: string, ownerId: string | undefined): boolean {
|
|
|
218
240
|
return ownerId === undefined || getRunOwnerId(stateDir) === ownerId;
|
|
219
241
|
}
|
|
220
242
|
|
|
243
|
+
function matchesPreviewFilter(
|
|
244
|
+
preview: ActorInspectorPreview,
|
|
245
|
+
options: ActorInspectorPreviewReadOptions,
|
|
246
|
+
): boolean {
|
|
247
|
+
if (options.channels?.length && !options.channels.includes(preview.channel)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const mention = options.mention?.trim().toLowerCase();
|
|
251
|
+
if (!mention) return true;
|
|
252
|
+
return [
|
|
253
|
+
preview.from,
|
|
254
|
+
preview.from_display,
|
|
255
|
+
preview.to,
|
|
256
|
+
preview.type,
|
|
257
|
+
preview.summary,
|
|
258
|
+
preview.body_preview,
|
|
259
|
+
].some((value) => value?.toLowerCase().includes(mention));
|
|
260
|
+
}
|
|
261
|
+
|
|
221
262
|
export function readActorInspectorPreviews(
|
|
222
263
|
stateRoot = Paths.getRunStateRoot(),
|
|
223
264
|
limit = 8,
|
|
@@ -231,7 +272,7 @@ export function readActorInspectorPreviews(
|
|
|
231
272
|
const stateDir = path.join(stateRoot, entry.name);
|
|
232
273
|
if (!matchesOwner(stateDir, options.ownerId)) return [];
|
|
233
274
|
return [
|
|
234
|
-
...readRoomPreviews(entry.name, stateDir),
|
|
275
|
+
...readRoomPreviews(entry.name, stateDir, options.roomLimitPerRun),
|
|
235
276
|
...readInboxPreviews(entry.name, stateDir),
|
|
236
277
|
...readOutboxPreviews(entry.name, stateDir),
|
|
237
278
|
];
|
|
@@ -243,10 +284,11 @@ export function readActorInspectorPreviews(
|
|
|
243
284
|
: undefined;
|
|
244
285
|
return previews
|
|
245
286
|
.filter((preview) => !currentRun || preview.run === currentRun)
|
|
287
|
+
.filter((preview) => matchesPreviewFilter(preview, options))
|
|
246
288
|
.map((preview, index) => ({
|
|
247
289
|
...preview,
|
|
248
290
|
sequence: index + 1,
|
|
249
|
-
stripe: index % 2 ===
|
|
291
|
+
stripe: index % 2 === 1,
|
|
250
292
|
}))
|
|
251
293
|
.slice(-Math.max(1, limit));
|
|
252
294
|
} catch (error) {
|
|
@@ -285,8 +327,12 @@ function roomName(address: string): string | undefined {
|
|
|
285
327
|
return room ? room[1] : undefined;
|
|
286
328
|
}
|
|
287
329
|
|
|
330
|
+
function routeActorText(preview: ActorInspectorPreview): string {
|
|
331
|
+
return preview.from_display || actorName(preview.from);
|
|
332
|
+
}
|
|
333
|
+
|
|
288
334
|
function routeText(preview: ActorInspectorPreview): string {
|
|
289
|
-
const actor =
|
|
335
|
+
const actor = routeActorText(preview);
|
|
290
336
|
if (preview.channel === "room") return `${actor} # all`;
|
|
291
337
|
if (preview.channel === "broadcast") return `${actor} ⇢ ${preview.to}`;
|
|
292
338
|
return `${actor} → ${actorName(preview.to)}`;
|
|
@@ -314,17 +360,18 @@ function displayWidth(value: string): number {
|
|
|
314
360
|
return visibleWidth(value);
|
|
315
361
|
}
|
|
316
362
|
|
|
363
|
+
const lineSegmenter = new Intl.Segmenter();
|
|
364
|
+
|
|
317
365
|
function boundedLine(value: string, width: number): string {
|
|
318
366
|
if (width <= 0) return "";
|
|
319
367
|
if (visibleWidth(value) <= width) return value;
|
|
320
|
-
const ellipsis = "
|
|
368
|
+
const ellipsis = "…";
|
|
321
369
|
const ellipsisWidth = visibleWidth(ellipsis);
|
|
322
370
|
if (width <= ellipsisWidth) return ellipsis.slice(0, width);
|
|
323
371
|
let output = "";
|
|
324
372
|
let used = 0;
|
|
325
373
|
const maxTextWidth = width - ellipsisWidth;
|
|
326
|
-
const
|
|
327
|
-
for (const { segment } of segmenter.segment(value)) {
|
|
374
|
+
for (const { segment } of lineSegmenter.segment(value)) {
|
|
328
375
|
const segmentWidth = visibleWidth(segment);
|
|
329
376
|
if (used + segmentWidth > maxTextWidth) break;
|
|
330
377
|
output += segment;
|
|
@@ -357,9 +404,10 @@ function renderCompactInspectorEntry(
|
|
|
357
404
|
): string[] {
|
|
358
405
|
const separator = " ";
|
|
359
406
|
const prefix = " ";
|
|
360
|
-
const
|
|
407
|
+
const suffix = " ";
|
|
408
|
+
const contentWidth = Math.max(8, width - prefix.length - suffix.length);
|
|
361
409
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
362
|
-
const sequencePrefix = `${sequence}
|
|
410
|
+
const sequencePrefix = `${sequence}${separator}`;
|
|
363
411
|
const route = routeText(preview);
|
|
364
412
|
const routePadding = " ".repeat(
|
|
365
413
|
Math.max(0, routeWidth - displayWidth(route)),
|
|
@@ -384,7 +432,7 @@ function renderCompactInspectorEntry(
|
|
|
384
432
|
separator,
|
|
385
433
|
style(styles.preview, visibleHeadline),
|
|
386
434
|
].join("");
|
|
387
|
-
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}`;
|
|
435
|
+
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}${suffix}`;
|
|
388
436
|
if (stripe && styles.stripe) return [styles.stripe(line)];
|
|
389
437
|
if (!stripe && styles.stripeAlt) return [styles.stripeAlt(line)];
|
|
390
438
|
return [line];
|
|
@@ -402,39 +450,123 @@ function renderInspectorEntry(
|
|
|
402
450
|
): string[] {
|
|
403
451
|
const separator = " ";
|
|
404
452
|
const prefix = " ";
|
|
405
|
-
const
|
|
453
|
+
const suffix = " ";
|
|
454
|
+
const contentWidth = Math.max(8, width - prefix.length - suffix.length);
|
|
406
455
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
407
456
|
const sequencePrefix = `${sequence}${separator}`;
|
|
408
457
|
const route = routeText(preview);
|
|
409
458
|
const type = preview.type;
|
|
410
459
|
const summary = preview.summary?.trim() ?? "";
|
|
411
460
|
const body = preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const renderedLead = [
|
|
461
|
+
const visibleRoute = boundedLine(route, routeWidth);
|
|
462
|
+
const visibleType = boundedLine(type, typeWidth);
|
|
463
|
+
const boundedRoutePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(visibleRoute)));
|
|
464
|
+
const boundedTypePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(visibleType)));
|
|
465
|
+
const leadParts = [
|
|
418
466
|
style(styles.muted, sequencePrefix),
|
|
419
|
-
style(styles.target,
|
|
420
|
-
|
|
421
|
-
separator,
|
|
422
|
-
style(styles.type, type),
|
|
423
|
-
typePadding,
|
|
467
|
+
style(styles.target, visibleRoute),
|
|
468
|
+
boundedRoutePadding,
|
|
424
469
|
separator,
|
|
425
|
-
style(styles.
|
|
426
|
-
|
|
470
|
+
style(styles.type, visibleType),
|
|
471
|
+
boundedTypePadding,
|
|
427
472
|
separator,
|
|
428
|
-
]
|
|
473
|
+
];
|
|
474
|
+
let lead = `${sequencePrefix}${visibleRoute}${boundedRoutePadding}${separator}${visibleType}${boundedTypePadding}${separator}`;
|
|
475
|
+
if (summary) {
|
|
476
|
+
const visibleSummary = boundedLine(summary, summaryWidth);
|
|
477
|
+
const summaryPadding = " ".repeat(Math.max(0, summaryWidth - displayWidth(visibleSummary)));
|
|
478
|
+
lead += `${visibleSummary}${summaryPadding}${separator}`;
|
|
479
|
+
leadParts.push(style(styles.preview, visibleSummary), summaryPadding, separator);
|
|
480
|
+
}
|
|
429
481
|
const visibleBody = boundedLine(body, Math.max(0, contentWidth - displayWidth(lead)));
|
|
430
482
|
const plain = `${lead}${visibleBody}`;
|
|
431
|
-
const rendered = `${
|
|
432
|
-
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}`;
|
|
483
|
+
const rendered = `${leadParts.join("")}${style(styles.preview, visibleBody)}`;
|
|
484
|
+
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}${suffix}`;
|
|
433
485
|
if (stripe && styles.stripe) return [styles.stripe(line)];
|
|
434
486
|
if (!stripe && styles.stripeAlt) return [styles.stripeAlt(line)];
|
|
435
487
|
return [line];
|
|
436
488
|
}
|
|
437
489
|
|
|
490
|
+
export function readActorInspectorRoster(
|
|
491
|
+
stateRoot = Paths.getRunStateRoot(),
|
|
492
|
+
run: string,
|
|
493
|
+
room = "main",
|
|
494
|
+
): ActorInspectorRosterMember[] {
|
|
495
|
+
const stateDir = path.join(stateRoot, run);
|
|
496
|
+
const roster = readRoomRosterRecords(stateDir, room);
|
|
497
|
+
return Object.entries(roster).map(([address, member]) => ({
|
|
498
|
+
address,
|
|
499
|
+
...(memberDisplay(address, member)
|
|
500
|
+
? { display: memberDisplay(address, member) }
|
|
501
|
+
: {}),
|
|
502
|
+
...(typeof member.role === "string" ? { role: member.role } : {}),
|
|
503
|
+
...(typeof member.status === "string" ? { status: member.status } : {}),
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function rosterRoleText(role: string | undefined): string | undefined {
|
|
508
|
+
const cleaned = role?.replaceAll(/\s*\([^)]*\)\s*$/g, "").trim().toLowerCase();
|
|
509
|
+
if (!cleaned || cleaned === "actor") return undefined;
|
|
510
|
+
return cleaned.replaceAll(/\s+/g, "-");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function rosterMemberText(member: ActorInspectorRosterMember): string {
|
|
514
|
+
const name = member.display || actorName(member.address);
|
|
515
|
+
const role = rosterRoleText(member.role);
|
|
516
|
+
return role ? `${role}/${name}` : name;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function isRosterMemberActive(member: ActorInspectorRosterMember): boolean {
|
|
520
|
+
const status = member.status?.trim().toLowerCase();
|
|
521
|
+
return !status || status === "present" || status === "active" || status === "running";
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function renderInspectorRosterLine(
|
|
525
|
+
members: ActorInspectorRosterMember[],
|
|
526
|
+
width = 80,
|
|
527
|
+
styles: ActorInspectorWidgetStyle = {},
|
|
528
|
+
): string | undefined {
|
|
529
|
+
return renderInspectorRosterPanel(members, width, styles)?.[0];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export function renderInspectorRosterPanel(
|
|
533
|
+
members: ActorInspectorRosterMember[],
|
|
534
|
+
width = 80,
|
|
535
|
+
styles: ActorInspectorWidgetStyle = {},
|
|
536
|
+
): string[] | undefined {
|
|
537
|
+
if (members.length === 0) return undefined;
|
|
538
|
+
const safeWidth = Math.max(1, width);
|
|
539
|
+
const innerWidth = Math.max(1, safeWidth - 2);
|
|
540
|
+
const prefix = `roster ${members.length}: `;
|
|
541
|
+
const tokens = members.map((member) => ({
|
|
542
|
+
active: isRosterMemberActive(member),
|
|
543
|
+
text: rosterMemberText(member),
|
|
544
|
+
}));
|
|
545
|
+
const lines: string[] = [];
|
|
546
|
+
let plain = prefix;
|
|
547
|
+
let rendered = style(styles.muted, prefix);
|
|
548
|
+
const flush = () => {
|
|
549
|
+
const visible = boundedLine(plain, innerWidth);
|
|
550
|
+
const line = visible === plain ? rendered : style(styles.muted, visible);
|
|
551
|
+
lines.push(` ${line}${" ".repeat(Math.max(0, innerWidth - displayWidth(visible)))} `);
|
|
552
|
+
};
|
|
553
|
+
for (const token of tokens) {
|
|
554
|
+
const separator = plain === prefix ? "" : ", ";
|
|
555
|
+
const nextPlain = `${plain}${separator}${token.text}`;
|
|
556
|
+
const renderedToken = style(token.active ? styles.target : styles.muted, token.text);
|
|
557
|
+
if (plain !== prefix && displayWidth(nextPlain) > innerWidth) {
|
|
558
|
+
flush();
|
|
559
|
+
plain = ` ${token.text}`;
|
|
560
|
+
rendered = `${style(styles.muted, " ")}${renderedToken}`;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
plain = nextPlain;
|
|
564
|
+
rendered = `${rendered}${style(styles.muted, separator)}${renderedToken}`;
|
|
565
|
+
}
|
|
566
|
+
flush();
|
|
567
|
+
return lines;
|
|
568
|
+
}
|
|
569
|
+
|
|
438
570
|
export function renderInspectorItemView(
|
|
439
571
|
previews: ActorInspectorPreview[],
|
|
440
572
|
width = 80,
|
|
@@ -496,24 +628,42 @@ export function renderInspectorWidget(
|
|
|
496
628
|
void options;
|
|
497
629
|
const visible = previews.map((preview, index) => ({
|
|
498
630
|
preview: { ...preview, sequence: preview.sequence ?? index + 1 },
|
|
499
|
-
stripe: preview.stripe ?? index % 2 ===
|
|
631
|
+
stripe: preview.stripe ?? index % 2 === 1,
|
|
500
632
|
}));
|
|
501
633
|
const sequenceWidth = Math.max(
|
|
502
634
|
1,
|
|
503
635
|
...visible.map(({ preview }) => String(preview.sequence ?? 0).length),
|
|
504
636
|
);
|
|
505
637
|
const lines: string[] = [];
|
|
506
|
-
const
|
|
638
|
+
const separatorWidth = 2;
|
|
639
|
+
const sequencePrefixWidth = sequenceWidth + separatorWidth;
|
|
640
|
+
const fixedSeparatorsWidth = separatorWidth * 3;
|
|
641
|
+
const availableForColumns = Math.max(
|
|
642
|
+
0,
|
|
643
|
+
safeWidth - 1 - sequencePrefixWidth - fixedSeparatorsWidth,
|
|
644
|
+
);
|
|
645
|
+
const naturalRouteWidth = Math.max(
|
|
507
646
|
...visible.map(({ preview }) => displayWidth(routeText(preview))),
|
|
508
647
|
);
|
|
509
|
-
const
|
|
648
|
+
const naturalTypeWidth = Math.max(
|
|
510
649
|
...visible.map(({ preview }) => displayWidth(preview.type)),
|
|
511
650
|
);
|
|
651
|
+
const routeWidth = Math.min(
|
|
652
|
+
naturalRouteWidth,
|
|
653
|
+
Math.max(4, Math.floor(availableForColumns * 0.35)),
|
|
654
|
+
);
|
|
655
|
+
const typeWidth = Math.min(
|
|
656
|
+
naturalTypeWidth,
|
|
657
|
+
Math.max(4, Math.floor(availableForColumns * 0.25)),
|
|
658
|
+
);
|
|
659
|
+
const messageWidth = Math.max(0, availableForColumns - routeWidth - typeWidth);
|
|
512
660
|
const summaryWidths = visible
|
|
513
661
|
.map(({ preview }) => preview.summary?.trim())
|
|
514
662
|
.filter((summary): summary is string => Boolean(summary))
|
|
515
663
|
.map((summary) => displayWidth(summary));
|
|
516
|
-
const summaryWidth = summaryWidths.length
|
|
664
|
+
const summaryWidth = summaryWidths.length
|
|
665
|
+
? Math.min(Math.max(...summaryWidths), Math.max(1, Math.floor(messageWidth * 0.5)))
|
|
666
|
+
: 0;
|
|
517
667
|
for (const { preview, stripe } of visible) {
|
|
518
668
|
lines.push(
|
|
519
669
|
...renderInspectorEntry(
|