@llblab/pi-actors 0.17.0 → 0.17.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 +2 -2
- package/BACKLOG.md +15 -11
- package/CHANGELOG.md +6 -1
- package/README.md +171 -302
- package/docs/actor-messages.md +3 -1
- package/docs/recipe-library.md +1 -0
- package/index.ts +73 -28
- package/lib/actor-inspector-tui.ts +214 -108
- package/lib/actor-rooms.ts +4 -0
- package/package.json +1 -1
- package/recipes/pipeline-room-swarm.json +49 -0
- package/scripts/room-swarm.mjs +244 -0
- package/skills/actors/SKILL.md +2 -2
- package/skills/swarm/SKILL.md +1 -1
package/index.ts
CHANGED
|
@@ -49,7 +49,8 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
49
49
|
const observedRunEventLines = new Map<string, number>();
|
|
50
50
|
let runStatusFrame = 0;
|
|
51
51
|
let communicationWidgetVisible = false;
|
|
52
|
-
let
|
|
52
|
+
let actorInspectorRows = 12;
|
|
53
|
+
let selectedInspectorSequence: number | undefined;
|
|
53
54
|
const getRunOwnerId = (ctx: ExtensionContext): string =>
|
|
54
55
|
ctx.sessionManager.getSessionId();
|
|
55
56
|
const updateRunUi = (ctx: ExtensionContext, notify = false): void => {
|
|
@@ -66,25 +67,34 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
66
67
|
? () => ({
|
|
67
68
|
invalidate() {},
|
|
68
69
|
render(width: number) {
|
|
70
|
+
const previews = ActorInspectorTui.readActorInspectorPreviews(
|
|
71
|
+
RUN_STATE_ROOT,
|
|
72
|
+
actorInspectorRows,
|
|
73
|
+
{ ownerId, currentRunOnly: true },
|
|
74
|
+
);
|
|
75
|
+
const style = {
|
|
76
|
+
actor: (text: string) => ctx.ui.theme.fg("accent", text),
|
|
77
|
+
muted: (text: string) => ctx.ui.theme.fg("dim", text),
|
|
78
|
+
preview: (text: string) => ctx.ui.theme.fg("text", text),
|
|
79
|
+
stripe: (text: string) => text,
|
|
80
|
+
stripeAlt: (text: string) =>
|
|
81
|
+
ctx.ui.theme.bg("customMessageBg", text),
|
|
82
|
+
target: (text: string) => ctx.ui.theme.fg("success", text),
|
|
83
|
+
type: (text: string) => ctx.ui.theme.fg("warning", text),
|
|
84
|
+
};
|
|
69
85
|
return (
|
|
70
|
-
|
|
71
|
-
ActorInspectorTui.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
stripeAlt: (text) => ctx.ui.theme.bg("customMessageBg", text),
|
|
83
|
-
target: (text) => ctx.ui.theme.fg("success", text),
|
|
84
|
-
type: (text) => ctx.ui.theme.fg("warning", text),
|
|
85
|
-
},
|
|
86
|
-
{ verbosity: inspectorVerbosity },
|
|
87
|
-
) ?? []
|
|
86
|
+
(selectedInspectorSequence !== undefined
|
|
87
|
+
? ActorInspectorTui.renderInspectorItemView(
|
|
88
|
+
previews,
|
|
89
|
+
width,
|
|
90
|
+
style,
|
|
91
|
+
{ sequence: selectedInspectorSequence },
|
|
92
|
+
)
|
|
93
|
+
: ActorInspectorTui.renderInspectorWidget(
|
|
94
|
+
previews,
|
|
95
|
+
width,
|
|
96
|
+
style,
|
|
97
|
+
)) ?? []
|
|
88
98
|
);
|
|
89
99
|
},
|
|
90
100
|
})
|
|
@@ -243,9 +253,38 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
243
253
|
closeRecipeWatcher();
|
|
244
254
|
});
|
|
245
255
|
pi.registerCommand("actors-inspector-toggle", {
|
|
246
|
-
description: "Toggle actor inspector widget",
|
|
247
|
-
handler: async (
|
|
248
|
-
|
|
256
|
+
description: "Toggle actor inspector widget; optional row count",
|
|
257
|
+
handler: async (args, ctx) => {
|
|
258
|
+
const raw = Array.isArray(args) ? args[0] : String(args ?? "");
|
|
259
|
+
if (String(raw).trim()) {
|
|
260
|
+
const rows = Number.parseInt(String(raw), 10);
|
|
261
|
+
if (!Number.isFinite(rows) || rows <= 0) {
|
|
262
|
+
ctx.ui.notify(
|
|
263
|
+
"Usage: /actors-inspector-toggle [rows] where rows > 0",
|
|
264
|
+
"warning",
|
|
265
|
+
);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
actorInspectorRows = rows;
|
|
269
|
+
selectedInspectorSequence = undefined;
|
|
270
|
+
communicationWidgetVisible = true;
|
|
271
|
+
updateRunUi(ctx);
|
|
272
|
+
ctx.ui.notify(`Actor inspector rows ${rows}`, "info");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (selectedInspectorSequence !== undefined) {
|
|
276
|
+
selectedInspectorSequence = undefined;
|
|
277
|
+
communicationWidgetVisible = true;
|
|
278
|
+
updateRunUi(ctx);
|
|
279
|
+
ctx.ui.notify("Actor inspector table", "info");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (communicationWidgetVisible) {
|
|
283
|
+
communicationWidgetVisible = false;
|
|
284
|
+
} else {
|
|
285
|
+
actorInspectorRows = 12;
|
|
286
|
+
communicationWidgetVisible = true;
|
|
287
|
+
}
|
|
249
288
|
updateRunUi(ctx);
|
|
250
289
|
ctx.ui.notify(
|
|
251
290
|
`Actor inspector ${communicationWidgetVisible ? "shown" : "hidden"}`,
|
|
@@ -253,13 +292,19 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
253
292
|
);
|
|
254
293
|
},
|
|
255
294
|
});
|
|
256
|
-
pi.registerCommand("actors-
|
|
257
|
-
description: "
|
|
258
|
-
handler: async (
|
|
259
|
-
|
|
260
|
-
|
|
295
|
+
pi.registerCommand("actors-inspect", {
|
|
296
|
+
description: "Inspect actor message by visible number",
|
|
297
|
+
handler: async (args, ctx) => {
|
|
298
|
+
const raw = Array.isArray(args) ? args[0] : String(args ?? "");
|
|
299
|
+
const sequence = Number.parseInt(String(raw), 10);
|
|
300
|
+
if (!Number.isFinite(sequence) || sequence <= 0) {
|
|
301
|
+
ctx.ui.notify("Usage: /actors-inspect <number>", "warning");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
selectedInspectorSequence = sequence;
|
|
305
|
+
communicationWidgetVisible = true;
|
|
261
306
|
updateRunUi(ctx);
|
|
262
|
-
ctx.ui.notify(`Actor
|
|
307
|
+
ctx.ui.notify(`Actor inspect item ${sequence}`, "info");
|
|
263
308
|
},
|
|
264
309
|
});
|
|
265
310
|
pi.on("before_agent_start", async (event) => ({
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
|
|
9
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
10
|
+
|
|
9
11
|
import type { ActorMessage } from "./actor-messages.ts";
|
|
10
12
|
import * as Paths from "./paths.ts";
|
|
11
13
|
|
|
@@ -13,6 +15,7 @@ export interface ActorInspectorPreview {
|
|
|
13
15
|
body_preview?: string;
|
|
14
16
|
channel: "broadcast" | "direct" | "room";
|
|
15
17
|
from?: string;
|
|
18
|
+
from_display?: string;
|
|
16
19
|
run: string;
|
|
17
20
|
sequence?: number;
|
|
18
21
|
summary?: string;
|
|
@@ -32,10 +35,10 @@ export interface ActorInspectorWidgetStyle {
|
|
|
32
35
|
type?: (text: string) => string;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
export
|
|
38
|
+
export interface ActorInspectorRenderOptions {}
|
|
36
39
|
|
|
37
|
-
export interface
|
|
38
|
-
|
|
40
|
+
export interface ActorInspectorItemViewOptions {
|
|
41
|
+
sequence: number;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export interface ActorInspectorPreviewReadOptions {
|
|
@@ -78,9 +81,12 @@ function previewValue(value: unknown, maxLength = 320): string | undefined {
|
|
|
78
81
|
: compact;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
function channelFor(
|
|
84
|
+
function channelFor(
|
|
85
|
+
message: Pick<ActorMessage, "to">,
|
|
86
|
+
): ActorInspectorPreview["channel"] {
|
|
82
87
|
if (message.to.startsWith("room:")) return "room";
|
|
83
|
-
if (message.to === "coordinator" || message.to.startsWith("session:"))
|
|
88
|
+
if (message.to === "coordinator" || message.to.startsWith("session:"))
|
|
89
|
+
return "broadcast";
|
|
84
90
|
return "direct";
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -88,16 +94,27 @@ function previewFromMessage(
|
|
|
88
94
|
run: string,
|
|
89
95
|
message: Record<string, unknown>,
|
|
90
96
|
timestamp: string,
|
|
97
|
+
displayNames: Record<string, string> = {},
|
|
91
98
|
): ActorInspectorPreview | undefined {
|
|
92
99
|
const to = typeof message.to === "string" ? message.to : undefined;
|
|
93
100
|
const type = typeof message.type === "string" ? message.type : undefined;
|
|
94
101
|
if (!to || !type) return undefined;
|
|
95
102
|
const from = typeof message.from === "string" ? message.from : undefined;
|
|
96
|
-
const summary =
|
|
103
|
+
const summary =
|
|
104
|
+
typeof message.summary === "string" ? message.summary : undefined;
|
|
105
|
+
const body = asRecord(message.body);
|
|
106
|
+
const display = from
|
|
107
|
+
? typeof body.display === "string" && body.display.trim()
|
|
108
|
+
? body.display.trim()
|
|
109
|
+
: displayNames[from]
|
|
110
|
+
: undefined;
|
|
97
111
|
return {
|
|
98
|
-
...(previewValue(message.body)
|
|
112
|
+
...(previewValue(message.body)
|
|
113
|
+
? { body_preview: previewValue(message.body) }
|
|
114
|
+
: {}),
|
|
99
115
|
channel: channelFor({ to }),
|
|
100
116
|
...(from ? { from } : {}),
|
|
117
|
+
...(display ? { from_display: display } : {}),
|
|
101
118
|
run,
|
|
102
119
|
...(summary ? { summary } : {}),
|
|
103
120
|
timestamp,
|
|
@@ -106,30 +123,59 @@ function previewFromMessage(
|
|
|
106
123
|
};
|
|
107
124
|
}
|
|
108
125
|
|
|
109
|
-
function
|
|
126
|
+
function readRoomDisplayNames(stateDir: string, room: string): Record<string, string> {
|
|
127
|
+
try {
|
|
128
|
+
const roster = JSON.parse(
|
|
129
|
+
fs.readFileSync(path.join(stateDir, "rooms", room, "roster.json"), "utf8"),
|
|
130
|
+
) 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
|
+
} catch {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function readRoomPreviews(
|
|
146
|
+
run: string,
|
|
147
|
+
stateDir: string,
|
|
148
|
+
): ActorInspectorPreview[] {
|
|
110
149
|
const roomsDir = path.join(stateDir, "rooms");
|
|
111
150
|
try {
|
|
112
151
|
return fs
|
|
113
152
|
.readdirSync(roomsDir, { withFileTypes: true })
|
|
114
153
|
.filter((entry) => entry.isDirectory())
|
|
115
|
-
.flatMap((entry) =>
|
|
116
|
-
|
|
154
|
+
.flatMap((entry) => {
|
|
155
|
+
const displayNames = readRoomDisplayNames(stateDir, entry.name);
|
|
156
|
+
return readJsonLines(path.join(roomsDir, entry.name, "messages.jsonl"))
|
|
117
157
|
.map((message) =>
|
|
118
158
|
previewFromMessage(
|
|
119
159
|
run,
|
|
120
160
|
message,
|
|
121
161
|
String(message.received_at ?? message.timestamp ?? ""),
|
|
162
|
+
displayNames,
|
|
122
163
|
),
|
|
123
164
|
)
|
|
124
|
-
.filter((preview): preview is ActorInspectorPreview =>
|
|
125
|
-
|
|
165
|
+
.filter((preview): preview is ActorInspectorPreview =>
|
|
166
|
+
Boolean(preview),
|
|
167
|
+
);
|
|
168
|
+
});
|
|
126
169
|
} catch (error) {
|
|
127
170
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
128
171
|
return [];
|
|
129
172
|
}
|
|
130
173
|
}
|
|
131
174
|
|
|
132
|
-
function readInboxPreviews(
|
|
175
|
+
function readInboxPreviews(
|
|
176
|
+
run: string,
|
|
177
|
+
stateDir: string,
|
|
178
|
+
): ActorInspectorPreview[] {
|
|
133
179
|
return readJsonLines(path.join(stateDir, "inbox.jsonl"))
|
|
134
180
|
.map((message) =>
|
|
135
181
|
previewFromMessage(
|
|
@@ -141,7 +187,10 @@ function readInboxPreviews(run: string, stateDir: string): ActorInspectorPreview
|
|
|
141
187
|
.filter((preview): preview is ActorInspectorPreview => Boolean(preview));
|
|
142
188
|
}
|
|
143
189
|
|
|
144
|
-
function readOutboxPreviews(
|
|
190
|
+
function readOutboxPreviews(
|
|
191
|
+
run: string,
|
|
192
|
+
stateDir: string,
|
|
193
|
+
): ActorInspectorPreview[] {
|
|
145
194
|
return readJsonLines(path.join(stateDir, "outbox.jsonl"))
|
|
146
195
|
.map((event) => {
|
|
147
196
|
const message = asRecord(event.message ?? event);
|
|
@@ -189,7 +238,9 @@ export function readActorInspectorPreviews(
|
|
|
189
238
|
})
|
|
190
239
|
.filter((preview) => preview.timestamp)
|
|
191
240
|
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
192
|
-
const currentRun = options.currentRunOnly
|
|
241
|
+
const currentRun = options.currentRunOnly
|
|
242
|
+
? previews.at(-1)?.run
|
|
243
|
+
: undefined;
|
|
193
244
|
return previews
|
|
194
245
|
.filter((preview) => !currentRun || preview.run === currentRun)
|
|
195
246
|
.map((preview, index) => ({
|
|
@@ -210,9 +261,10 @@ function shorten(
|
|
|
210
261
|
options: { preserveSpaces?: boolean } = { preserveSpaces: true },
|
|
211
262
|
): string {
|
|
212
263
|
if (!value) return "-";
|
|
213
|
-
const compact =
|
|
214
|
-
|
|
215
|
-
|
|
264
|
+
const compact =
|
|
265
|
+
options.preserveSpaces === false
|
|
266
|
+
? value.replaceAll(/\s+/g, "_")
|
|
267
|
+
: value.replaceAll(/\s+/g, " ").trim();
|
|
216
268
|
if (maxLength <= 1) return compact.slice(0, Math.max(0, maxLength));
|
|
217
269
|
return compact.length > maxLength
|
|
218
270
|
? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
|
|
@@ -234,13 +286,16 @@ function roomName(address: string): string | undefined {
|
|
|
234
286
|
}
|
|
235
287
|
|
|
236
288
|
function routeText(preview: ActorInspectorPreview): string {
|
|
237
|
-
const actor = actorName(preview.from);
|
|
289
|
+
const actor = preview.from_display || actorName(preview.from);
|
|
238
290
|
if (preview.channel === "room") return `${actor} # all`;
|
|
239
291
|
if (preview.channel === "broadcast") return `${actor} ⇢ ${preview.to}`;
|
|
240
292
|
return `${actor} → ${actorName(preview.to)}`;
|
|
241
293
|
}
|
|
242
294
|
|
|
243
|
-
function style(
|
|
295
|
+
function style(
|
|
296
|
+
styleFn: ((text: string) => string) | undefined,
|
|
297
|
+
text: string,
|
|
298
|
+
): string {
|
|
244
299
|
return styleFn ? styleFn(text) : text;
|
|
245
300
|
}
|
|
246
301
|
|
|
@@ -248,57 +303,46 @@ function previewText(preview: ActorInspectorPreview): string {
|
|
|
248
303
|
return preview.summary || preview.body_preview || "-";
|
|
249
304
|
}
|
|
250
305
|
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return value.replaceAll(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function charDisplayWidth(char: string): number {
|
|
260
|
-
const code = char.codePointAt(0) ?? 0;
|
|
261
|
-
if (code === 0) return 0;
|
|
262
|
-
if (code < 32 || (code >= 0x7f && code < 0xa0)) return 0;
|
|
263
|
-
if (
|
|
264
|
-
code >= 0x1100 &&
|
|
265
|
-
(code <= 0x115f ||
|
|
266
|
-
code === 0x2329 ||
|
|
267
|
-
code === 0x232a ||
|
|
268
|
-
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
269
|
-
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
270
|
-
(code >= 0xf900 && code <= 0xfaff) ||
|
|
271
|
-
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
272
|
-
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
273
|
-
(code >= 0xff00 && code <= 0xff60) ||
|
|
274
|
-
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
275
|
-
(code >= 0x1f300 && code <= 0x1faff))
|
|
276
|
-
) return 2;
|
|
277
|
-
return 1;
|
|
306
|
+
function propertyValue(value: unknown): string {
|
|
307
|
+
if (value === undefined) return "";
|
|
308
|
+
if (typeof value === "string") return value;
|
|
309
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
310
|
+
return JSON.stringify(value);
|
|
278
311
|
}
|
|
279
312
|
|
|
280
313
|
function displayWidth(value: string): number {
|
|
281
|
-
return
|
|
314
|
+
return visibleWidth(value);
|
|
282
315
|
}
|
|
283
316
|
|
|
284
317
|
function boundedLine(value: string, width: number): string {
|
|
285
|
-
if (
|
|
286
|
-
if (
|
|
318
|
+
if (width <= 0) return "";
|
|
319
|
+
if (visibleWidth(value) <= width) return value;
|
|
320
|
+
const ellipsis = "...";
|
|
321
|
+
const ellipsisWidth = visibleWidth(ellipsis);
|
|
322
|
+
if (width <= ellipsisWidth) return ellipsis.slice(0, width);
|
|
287
323
|
let output = "";
|
|
288
324
|
let used = 0;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
used
|
|
325
|
+
const maxTextWidth = width - ellipsisWidth;
|
|
326
|
+
const segmenter = new Intl.Segmenter();
|
|
327
|
+
for (const { segment } of segmenter.segment(value)) {
|
|
328
|
+
const segmentWidth = visibleWidth(segment);
|
|
329
|
+
if (used + segmentWidth > maxTextWidth) break;
|
|
330
|
+
output += segment;
|
|
331
|
+
used += segmentWidth;
|
|
294
332
|
}
|
|
295
|
-
return `${output}
|
|
333
|
+
return `${output}${ellipsis}`;
|
|
296
334
|
}
|
|
297
335
|
|
|
298
|
-
function padLine(
|
|
336
|
+
function padLine(
|
|
337
|
+
plain: string,
|
|
338
|
+
rendered: string,
|
|
339
|
+
width: number,
|
|
340
|
+
styles: ActorInspectorWidgetStyle,
|
|
341
|
+
): string {
|
|
299
342
|
const boundedPlain = boundedLine(plain, width);
|
|
300
|
-
const visible =
|
|
301
|
-
|
|
343
|
+
const visible =
|
|
344
|
+
boundedPlain === plain ? rendered : style(styles.preview, boundedPlain);
|
|
345
|
+
const padding = Math.max(0, width - visibleWidth(boundedPlain));
|
|
302
346
|
return `${visible}${" ".repeat(padding)}`;
|
|
303
347
|
}
|
|
304
348
|
|
|
@@ -317,11 +361,18 @@ function renderCompactInspectorEntry(
|
|
|
317
361
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
318
362
|
const sequencePrefix = `${sequence} `;
|
|
319
363
|
const route = routeText(preview);
|
|
320
|
-
const routePadding = " ".repeat(
|
|
321
|
-
|
|
364
|
+
const routePadding = " ".repeat(
|
|
365
|
+
Math.max(0, routeWidth - displayWidth(route)),
|
|
366
|
+
);
|
|
367
|
+
const typePadding = " ".repeat(
|
|
368
|
+
Math.max(0, typeWidth - displayWidth(preview.type)),
|
|
369
|
+
);
|
|
322
370
|
const headline = previewText(preview);
|
|
323
371
|
const lead = `${sequencePrefix}${route}${routePadding}${separator}${preview.type}${typePadding}${separator}`;
|
|
324
|
-
const visibleHeadline = boundedLine(
|
|
372
|
+
const visibleHeadline = boundedLine(
|
|
373
|
+
headline,
|
|
374
|
+
Math.max(0, contentWidth - displayWidth(lead)),
|
|
375
|
+
);
|
|
325
376
|
const plain = `${lead}${visibleHeadline}`;
|
|
326
377
|
const rendered = [
|
|
327
378
|
style(styles.muted, sequencePrefix),
|
|
@@ -339,11 +390,13 @@ function renderCompactInspectorEntry(
|
|
|
339
390
|
return [line];
|
|
340
391
|
}
|
|
341
392
|
|
|
342
|
-
function
|
|
393
|
+
function renderInspectorEntry(
|
|
343
394
|
preview: ActorInspectorPreview,
|
|
344
395
|
width: number,
|
|
345
396
|
sequenceWidth: number,
|
|
346
|
-
|
|
397
|
+
routeWidth: number,
|
|
398
|
+
typeWidth: number,
|
|
399
|
+
summaryWidth: number,
|
|
347
400
|
styles: ActorInspectorWidgetStyle,
|
|
348
401
|
stripe: boolean,
|
|
349
402
|
): string[] {
|
|
@@ -351,39 +404,84 @@ function renderVerboseInspectorEntry(
|
|
|
351
404
|
const prefix = " ";
|
|
352
405
|
const contentWidth = Math.max(8, width - prefix.length);
|
|
353
406
|
const sequence = String(preview.sequence ?? 0).padStart(sequenceWidth, " ");
|
|
354
|
-
const sequencePrefix = `${sequence}
|
|
355
|
-
const detailSequencePrefix = `${" ".repeat(sequenceWidth)} `;
|
|
407
|
+
const sequencePrefix = `${sequence}${separator}`;
|
|
356
408
|
const route = routeText(preview);
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
const detailPlain = `${detailLead}${visibleDetail}`;
|
|
367
|
-
const header = [
|
|
409
|
+
const type = preview.type;
|
|
410
|
+
const summary = preview.summary?.trim() ?? "";
|
|
411
|
+
const body = preview.body_preview?.trim() || (!summary ? previewText(preview) : "-");
|
|
412
|
+
const routePadding = " ".repeat(Math.max(0, routeWidth - displayWidth(route)));
|
|
413
|
+
const typePadding = " ".repeat(Math.max(0, typeWidth - displayWidth(type)));
|
|
414
|
+
const visibleSummary = boundedLine(summary, summaryWidth);
|
|
415
|
+
const summaryPadding = " ".repeat(Math.max(0, summaryWidth - displayWidth(visibleSummary)));
|
|
416
|
+
const lead = `${sequencePrefix}${route}${routePadding}${separator}${type}${typePadding}${separator}${visibleSummary}${summaryPadding}${separator}`;
|
|
417
|
+
const renderedLead = [
|
|
368
418
|
style(styles.muted, sequencePrefix),
|
|
369
419
|
style(styles.target, route),
|
|
370
420
|
routePadding,
|
|
371
421
|
separator,
|
|
372
|
-
style(styles.
|
|
373
|
-
].join("");
|
|
374
|
-
const detailLine = [
|
|
375
|
-
style(styles.muted, detailSequencePrefix),
|
|
376
|
-
style(styles.type, preview.type),
|
|
422
|
+
style(styles.type, type),
|
|
377
423
|
typePadding,
|
|
378
424
|
separator,
|
|
379
|
-
style(styles.preview,
|
|
425
|
+
style(styles.preview, visibleSummary),
|
|
426
|
+
summaryPadding,
|
|
427
|
+
separator,
|
|
380
428
|
].join("");
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (stripe && styles.stripe) return
|
|
386
|
-
if (!stripe && styles.stripeAlt) return
|
|
429
|
+
const visibleBody = boundedLine(body, Math.max(0, contentWidth - displayWidth(lead)));
|
|
430
|
+
const plain = `${lead}${visibleBody}`;
|
|
431
|
+
const rendered = `${renderedLead}${style(styles.preview, visibleBody)}`;
|
|
432
|
+
const line = `${prefix}${padLine(plain, rendered, contentWidth, styles)}`;
|
|
433
|
+
if (stripe && styles.stripe) return [styles.stripe(line)];
|
|
434
|
+
if (!stripe && styles.stripeAlt) return [styles.stripeAlt(line)];
|
|
435
|
+
return [line];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function renderInspectorItemView(
|
|
439
|
+
previews: ActorInspectorPreview[],
|
|
440
|
+
width = 80,
|
|
441
|
+
styles: ActorInspectorWidgetStyle = {},
|
|
442
|
+
options: ActorInspectorItemViewOptions,
|
|
443
|
+
): string[] | undefined {
|
|
444
|
+
const preview = previews.find((item) => item.sequence === options.sequence);
|
|
445
|
+
if (!preview) return undefined;
|
|
446
|
+
const safeWidth = Math.max(1, width);
|
|
447
|
+
const orderedKeys = [
|
|
448
|
+
"channel",
|
|
449
|
+
"run",
|
|
450
|
+
"from",
|
|
451
|
+
"from_display",
|
|
452
|
+
"to",
|
|
453
|
+
"type",
|
|
454
|
+
"summary",
|
|
455
|
+
"body_preview",
|
|
456
|
+
"timestamp",
|
|
457
|
+
"stripe",
|
|
458
|
+
] as const;
|
|
459
|
+
const entries = orderedKeys
|
|
460
|
+
.filter((key) => preview[key] !== undefined)
|
|
461
|
+
.map((key) => [key, propertyValue(preview[key])] as const);
|
|
462
|
+
const keyWidth = Math.max(1, ...entries.map(([key]) => displayWidth(key)));
|
|
463
|
+
const sequenceText = String(preview.sequence ?? options.sequence);
|
|
464
|
+
const sequencePadding = " ".repeat(Math.max(0, keyWidth - displayWidth(sequenceText)));
|
|
465
|
+
const headerSeparator = " ";
|
|
466
|
+
const route = routeText(preview);
|
|
467
|
+
const visibleRoute = boundedLine(
|
|
468
|
+
route,
|
|
469
|
+
Math.max(0, safeWidth - keyWidth - headerSeparator.length),
|
|
470
|
+
);
|
|
471
|
+
const headerPlain = `${sequenceText}${sequencePadding}${headerSeparator}${visibleRoute}`;
|
|
472
|
+
const header = `${style(styles.muted, sequenceText)}${sequencePadding}${headerSeparator}${style(styles.target, visibleRoute)}`;
|
|
473
|
+
const headerPadding = Math.max(0, safeWidth - visibleWidth(headerPlain));
|
|
474
|
+
const lines = [`${header}${" ".repeat(headerPadding)}`, ""];
|
|
475
|
+
for (const [key, value] of entries) {
|
|
476
|
+
const keyPadding = " ".repeat(Math.max(0, keyWidth - displayWidth(key)));
|
|
477
|
+
const separator = " ";
|
|
478
|
+
const valueWidth = Math.max(0, safeWidth - keyWidth - separator.length);
|
|
479
|
+
const visibleValue = boundedLine(value, valueWidth);
|
|
480
|
+
const plain = `${key}${keyPadding}${separator}${visibleValue}`;
|
|
481
|
+
const rendered = `${style(styles.muted, key)}${keyPadding}${separator}${style(styles.preview, visibleValue)}`;
|
|
482
|
+
const padding = Math.max(0, safeWidth - visibleWidth(plain));
|
|
483
|
+
lines.push(`${rendered}${" ".repeat(padding)}`);
|
|
484
|
+
}
|
|
387
485
|
return lines;
|
|
388
486
|
}
|
|
389
487
|
|
|
@@ -395,32 +493,40 @@ export function renderInspectorWidget(
|
|
|
395
493
|
): string[] | undefined {
|
|
396
494
|
if (previews.length === 0) return undefined;
|
|
397
495
|
const safeWidth = Math.max(1, width);
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
.
|
|
402
|
-
|
|
403
|
-
stripe: preview.stripe ?? index % 2 === 0,
|
|
404
|
-
}))
|
|
405
|
-
.slice(-visibleLimit);
|
|
496
|
+
void options;
|
|
497
|
+
const visible = previews.map((preview, index) => ({
|
|
498
|
+
preview: { ...preview, sequence: preview.sequence ?? index + 1 },
|
|
499
|
+
stripe: preview.stripe ?? index % 2 === 0,
|
|
500
|
+
}));
|
|
406
501
|
const sequenceWidth = Math.max(
|
|
407
502
|
1,
|
|
408
503
|
...visible.map(({ preview }) => String(preview.sequence ?? 0).length),
|
|
409
504
|
);
|
|
410
505
|
const lines: string[] = [];
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
return lines;
|
|
418
|
-
}
|
|
419
|
-
const labelWidth = Math.max(
|
|
420
|
-
...visible.flatMap(({ preview }) => [routeText(preview).length, preview.type.length]),
|
|
506
|
+
const routeWidth = Math.max(
|
|
507
|
+
...visible.map(({ preview }) => displayWidth(routeText(preview))),
|
|
508
|
+
);
|
|
509
|
+
const typeWidth = Math.max(
|
|
510
|
+
...visible.map(({ preview }) => displayWidth(preview.type)),
|
|
421
511
|
);
|
|
512
|
+
const summaryWidths = visible
|
|
513
|
+
.map(({ preview }) => preview.summary?.trim())
|
|
514
|
+
.filter((summary): summary is string => Boolean(summary))
|
|
515
|
+
.map((summary) => displayWidth(summary));
|
|
516
|
+
const summaryWidth = summaryWidths.length ? Math.max(...summaryWidths) : 0;
|
|
422
517
|
for (const { preview, stripe } of visible) {
|
|
423
|
-
lines.push(
|
|
518
|
+
lines.push(
|
|
519
|
+
...renderInspectorEntry(
|
|
520
|
+
preview,
|
|
521
|
+
safeWidth,
|
|
522
|
+
sequenceWidth,
|
|
523
|
+
routeWidth,
|
|
524
|
+
typeWidth,
|
|
525
|
+
summaryWidth,
|
|
526
|
+
styles,
|
|
527
|
+
stripe,
|
|
528
|
+
),
|
|
529
|
+
);
|
|
424
530
|
}
|
|
425
531
|
return lines;
|
|
426
532
|
}
|
package/lib/actor-rooms.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface RoomMember {
|
|
|
13
13
|
address: string;
|
|
14
14
|
caps?: unknown;
|
|
15
15
|
claim?: unknown;
|
|
16
|
+
display?: unknown;
|
|
17
|
+
glyph?: unknown;
|
|
16
18
|
joined_at: string;
|
|
17
19
|
last_seen: string;
|
|
18
20
|
parent?: unknown;
|
|
@@ -164,6 +166,8 @@ function updateRosterForMessage(
|
|
|
164
166
|
last_seen: receivedAt,
|
|
165
167
|
...(body.caps !== undefined ? { caps: body.caps } : current?.caps !== undefined ? { caps: current.caps } : {}),
|
|
166
168
|
...(body.claim !== undefined ? { claim: body.claim } : current?.claim !== undefined ? { claim: current.claim } : {}),
|
|
169
|
+
...(body.display !== undefined ? { display: body.display } : current?.display !== undefined ? { display: current.display } : {}),
|
|
170
|
+
...(body.glyph !== undefined ? { glyph: body.glyph } : current?.glyph !== undefined ? { glyph: current.glyph } : {}),
|
|
167
171
|
...(body.parent !== undefined ? { parent: body.parent } : current?.parent !== undefined ? { parent: current.parent } : {}),
|
|
168
172
|
...(body.role !== undefined ? { role: body.role } : current?.role !== undefined ? { role: current.role } : { role: "actor" }),
|
|
169
173
|
status: String(body.status ?? current?.status ?? "present"),
|