@llblab/pi-actors 0.16.4 → 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 +8 -8
- package/BACKLOG.md +56 -21
- package/CHANGELOG.md +19 -8
- package/README.md +170 -274
- package/banner.jpg +0 -0
- package/docs/actor-messages.md +66 -3
- package/docs/async-runs.md +25 -3
- package/docs/recipe-library.md +4 -3
- package/docs/template-recipes.md +3 -4
- package/docs/tool-registry.md +7 -12
- package/index.ts +103 -3
- package/lib/actor-inspector-tui.ts +532 -0
- package/lib/actor-messages.ts +18 -0
- package/lib/actor-rooms.ts +373 -0
- package/lib/async-runs.ts +17 -1
- package/lib/config.ts +1 -1
- package/lib/paths.ts +1 -1
- package/lib/prompts.ts +3 -2
- package/lib/recipe-discovery.ts +83 -1
- package/lib/recipe-migration.ts +2 -2
- package/lib/recipe-references.ts +2 -0
- package/lib/tools.ts +292 -9
- package/package.json +1 -1
- package/recipes/lens-swarm.json +0 -1
- package/recipes/pipeline-room-swarm.json +49 -0
- package/scripts/room-swarm.mjs +244 -0
- package/skills/actors/SKILL.md +52 -9
- package/skills/swarm/SKILL.md +1 -1
package/lib/tools.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as ActorMessages from "./actor-messages.ts";
|
|
8
|
+
import * as ActorRooms from "./actor-rooms.ts";
|
|
8
9
|
import * as AsyncRuns from "./async-runs.ts";
|
|
9
10
|
import * as CommandTemplates from "./command-templates.ts";
|
|
10
11
|
import type { RegisteredTool } from "./config.ts";
|
|
@@ -192,6 +193,99 @@ function compactRunMessages(messages: AsyncRuns.RunOutboxEvent[]): string {
|
|
|
192
193
|
.join("\n")}`;
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
function compactPreview(value: unknown, maxLength = 80): string | undefined {
|
|
197
|
+
if (value === undefined) return undefined;
|
|
198
|
+
const text =
|
|
199
|
+
typeof value === "string" ? value : JSON.stringify(value, undefined, 0);
|
|
200
|
+
const compact = text.replaceAll(/\s+/g, "_");
|
|
201
|
+
return compact.length > maxLength
|
|
202
|
+
? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
|
|
203
|
+
: compact;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function compactRoomPreviews(previews: ActorRooms.RoomMessagePreview[]): string {
|
|
207
|
+
if (previews.length === 0) return "\n(no room message previews)";
|
|
208
|
+
return `\n${previews
|
|
209
|
+
.map((preview) =>
|
|
210
|
+
[
|
|
211
|
+
`ts=${preview.timestamp}`,
|
|
212
|
+
preview.from ? `from=${preview.from}` : "",
|
|
213
|
+
`to=${preview.to}`,
|
|
214
|
+
`type=${preview.type}`,
|
|
215
|
+
preview.summary ? `summary=${compactPreview(preview.summary)}` : "",
|
|
216
|
+
preview.body_preview ? `body=${compactPreview(preview.body_preview)}` : "",
|
|
217
|
+
]
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.join(" "),
|
|
220
|
+
)
|
|
221
|
+
.join("\n")}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function compactRoomMessages(messages: ActorRooms.RoomTimelineEntry[]): string {
|
|
225
|
+
if (messages.length === 0) return "\n(no room messages)";
|
|
226
|
+
return `\n${messages
|
|
227
|
+
.map((message) =>
|
|
228
|
+
[
|
|
229
|
+
`ts=${message.received_at}`,
|
|
230
|
+
`from=${String(message.from ?? "<unknown>")}`,
|
|
231
|
+
`to=${message.to}`,
|
|
232
|
+
`type=${message.type}`,
|
|
233
|
+
`summary=${String(message.summary ?? "").replaceAll(/\s+/g, "_")}`,
|
|
234
|
+
compactPreview(message.body) ? `body=${compactPreview(message.body)}` : "",
|
|
235
|
+
]
|
|
236
|
+
.filter(Boolean)
|
|
237
|
+
.join(" "),
|
|
238
|
+
)
|
|
239
|
+
.join("\n")}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function compactRoomContacts(contacts: ActorRooms.RoomContact[]): string {
|
|
243
|
+
if (contacts.length === 0) return "\n(no room contacts)";
|
|
244
|
+
return `\n${contacts
|
|
245
|
+
.map((contact) =>
|
|
246
|
+
[
|
|
247
|
+
`address=${contact.address}`,
|
|
248
|
+
contact.role !== undefined ? `role=${String(contact.role)}` : "",
|
|
249
|
+
contact.parent !== undefined ? `parent=${String(contact.parent)}` : "",
|
|
250
|
+
contact.caps !== undefined ? `caps=${Array.isArray(contact.caps) ? contact.caps.join(",") : String(contact.caps)}` : "",
|
|
251
|
+
contact.claim !== undefined ? `claim=${String(contact.claim).replaceAll(/\s+/g, "_")}` : "",
|
|
252
|
+
contact.status !== undefined ? `status=${String(contact.status)}` : "",
|
|
253
|
+
]
|
|
254
|
+
.filter(Boolean)
|
|
255
|
+
.join(" "),
|
|
256
|
+
)
|
|
257
|
+
.join("\n")}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function compactRoomRoster(roster: Record<string, ActorRooms.RoomMember>): string {
|
|
261
|
+
const members = Object.values(roster);
|
|
262
|
+
if (members.length === 0) return "\n(no room members)";
|
|
263
|
+
return `\n${members
|
|
264
|
+
.map((member) =>
|
|
265
|
+
[
|
|
266
|
+
`address=${member.address}`,
|
|
267
|
+
`role=${String(member.role ?? "")}`,
|
|
268
|
+
member.parent !== undefined ? `parent=${String(member.parent)}` : "",
|
|
269
|
+
member.caps !== undefined ? `caps=${Array.isArray(member.caps) ? member.caps.join(",") : String(member.caps)}` : "",
|
|
270
|
+
member.claim !== undefined ? `claim=${String(member.claim).replaceAll(/\s+/g, "_")}` : "",
|
|
271
|
+
`status=${String(member.status ?? "")}`,
|
|
272
|
+
`last_seen=${member.last_seen}`,
|
|
273
|
+
].join(" "),
|
|
274
|
+
)
|
|
275
|
+
.join("\n")}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function compactRoomStatus(status: ActorRooms.RoomStatus): string {
|
|
279
|
+
return `\nroom=${status.room} messages=${status.message_count} roster=${status.roster_count}${status.last_message_at ? ` last_message_at=${status.last_message_at}` : ""}${status.last_message_from ? ` last_from=${status.last_message_from}` : ""}${status.last_message_type ? ` last_type=${status.last_message_type}` : ""}${status.last_message_summary ? ` last_summary=${compactPreview(status.last_message_summary)}` : ""}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function compactCommunicationSnapshot(
|
|
283
|
+
snapshot: ActorRooms.ActorCommunicationSnapshot | undefined,
|
|
284
|
+
): string {
|
|
285
|
+
if (!snapshot) return "\n(no communication snapshot)";
|
|
286
|
+
return `\nself=${snapshot.self} root=${snapshot.root} rooms=${snapshot.rooms.length} updated_at=${snapshot.updated_at}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
195
289
|
function compactActorFiles(status: Record<string, unknown>): string {
|
|
196
290
|
const run = String(status.run ?? "<unknown>");
|
|
197
291
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -200,6 +294,7 @@ function compactActorFiles(status: Record<string, unknown>): string {
|
|
|
200
294
|
status.stderrLog,
|
|
201
295
|
status.eventsFile,
|
|
202
296
|
status.outboxFile,
|
|
297
|
+
status.state_dir ? `${String(status.state_dir)}/communication.json` : undefined,
|
|
203
298
|
status.state_dir ? `${String(status.state_dir)}/result.json` : undefined,
|
|
204
299
|
].filter((file): file is string => typeof file === "string");
|
|
205
300
|
const artifactText = Object.keys(artifacts).length
|
|
@@ -240,7 +335,10 @@ function compactRecipeRegistry(summary: Record<string, unknown>): string {
|
|
|
240
335
|
const diagnostics = Array.isArray(summary.diagnostics)
|
|
241
336
|
? summary.diagnostics.length
|
|
242
337
|
: 0;
|
|
243
|
-
|
|
338
|
+
const recommendations = Array.isArray(summary.recommendations)
|
|
339
|
+
? summary.recommendations.length
|
|
340
|
+
: 0;
|
|
341
|
+
return `\nrecipes active=${active} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
|
|
244
342
|
}
|
|
245
343
|
|
|
246
344
|
function compactActorMessageResult(
|
|
@@ -255,6 +353,11 @@ function compactActorMessageResult(
|
|
|
255
353
|
if (result.bytes !== undefined) tokens.push(`bytes=${String(result.bytes)}`);
|
|
256
354
|
if (result.control) tokens.push(`control=${String(result.control)}`);
|
|
257
355
|
if (result.outbox) tokens.push(`messages=${String(result.outbox)}`);
|
|
356
|
+
if (result.message_count !== undefined)
|
|
357
|
+
tokens.push(`messages=${String(result.message_count)}`);
|
|
358
|
+
if (result.roster_count !== undefined)
|
|
359
|
+
tokens.push(`roster=${String(result.roster_count)}`);
|
|
360
|
+
if (result.room) tokens.push(`room=${String(result.room)}`);
|
|
258
361
|
if (result.tool) tokens.push(`tool=${String(result.tool)}`);
|
|
259
362
|
if (result.stopped === true) tokens.push("stopped=true");
|
|
260
363
|
if (result.signal) tokens.push(`signal=${String(result.signal)}`);
|
|
@@ -352,6 +455,25 @@ function runIdFromActorAddress(
|
|
|
352
455
|
return parsed.value;
|
|
353
456
|
}
|
|
354
457
|
|
|
458
|
+
function assertMessageSenderBelongsToRun(
|
|
459
|
+
message: ActorMessages.ActorMessage,
|
|
460
|
+
run: string,
|
|
461
|
+
routeLabel: string,
|
|
462
|
+
): void {
|
|
463
|
+
if (!message.from) {
|
|
464
|
+
throw new Error(`message to ${message.to} requires from=<actor address>.`);
|
|
465
|
+
}
|
|
466
|
+
const sender = ActorMessages.parseActorAddress(message.from);
|
|
467
|
+
if (
|
|
468
|
+
(sender.kind !== "run" && sender.kind !== "branch") ||
|
|
469
|
+
sender.value !== run
|
|
470
|
+
) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
`message to ${routeLabel} requires from=run:${run} or branch:${run}/<branch>; got ${message.from}.`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
355
477
|
export function createSpawnToolDefinition<
|
|
356
478
|
TContext extends AsyncRunToolContext,
|
|
357
479
|
>(): any {
|
|
@@ -422,6 +544,8 @@ export function createSpawnToolDefinition<
|
|
|
422
544
|
},
|
|
423
545
|
ctx.cwd,
|
|
424
546
|
);
|
|
547
|
+
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
548
|
+
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
425
549
|
return {
|
|
426
550
|
content: [
|
|
427
551
|
{
|
|
@@ -475,6 +599,10 @@ function assertRunAccessibleToContext(
|
|
|
475
599
|
return status;
|
|
476
600
|
}
|
|
477
601
|
|
|
602
|
+
function assertRunExistsForActorMessage(runId: string): Record<string, unknown> {
|
|
603
|
+
return AsyncRuns.getRunStatus(runId);
|
|
604
|
+
}
|
|
605
|
+
|
|
478
606
|
export function createInspectToolDefinition<TContext = unknown>(
|
|
479
607
|
deps: InspectToolDeps<TContext> = {},
|
|
480
608
|
): any {
|
|
@@ -482,7 +610,7 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
482
610
|
name: "inspect",
|
|
483
611
|
label: "Inspect",
|
|
484
612
|
description:
|
|
485
|
-
"Intentionally inspect an actor. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox; coordinator/session status; and tool:<name> status/schema.",
|
|
613
|
+
"Intentionally inspect an actor. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
|
|
486
614
|
parameters: objectSchema(
|
|
487
615
|
{
|
|
488
616
|
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
@@ -490,13 +618,13 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
490
618
|
"Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
|
|
491
619
|
),
|
|
492
620
|
target: stringSchema(
|
|
493
|
-
"Actor address to inspect, e.g. run:<id>, coordinator, session:<id>, session:all, or tool:<name>.",
|
|
621
|
+
"Actor address to inspect, e.g. run:<id>, room:<run>, coordinator, session:<id>, session:all, or tool:<name>.",
|
|
494
622
|
),
|
|
495
623
|
verbose: booleanSchema(
|
|
496
624
|
"Return full JSON instead of compact text where available.",
|
|
497
625
|
),
|
|
498
626
|
view: stringSchema(
|
|
499
|
-
"Inspection view: status, tail, messages, artifacts, files, or
|
|
627
|
+
"Inspection view: status, tail, messages, artifacts, files, mailbox, communication, roster, or contacts.",
|
|
500
628
|
),
|
|
501
629
|
},
|
|
502
630
|
["target", "view"],
|
|
@@ -618,6 +746,100 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
618
746
|
details,
|
|
619
747
|
};
|
|
620
748
|
}
|
|
749
|
+
if (address.kind === "room" && address.value && address.room) {
|
|
750
|
+
const status = assertRunAccessibleToContext(address.value, ctx);
|
|
751
|
+
const stateDir = String(status.state_dir ?? "");
|
|
752
|
+
if (!stateDir) throw new Error(`room:${address.value} has no run state directory.`);
|
|
753
|
+
if (view === "status") {
|
|
754
|
+
const status = ActorRooms.getRoomStatus(stateDir, address.room);
|
|
755
|
+
return {
|
|
756
|
+
content: [
|
|
757
|
+
{
|
|
758
|
+
type: "text" as const,
|
|
759
|
+
text: maybeJsonText(
|
|
760
|
+
status,
|
|
761
|
+
input.verbose === true,
|
|
762
|
+
compactRoomStatus(status),
|
|
763
|
+
),
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
details: status,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
if (view === "previews") {
|
|
770
|
+
const previews = ActorRooms.readRoomMessagePreviews(
|
|
771
|
+
stateDir,
|
|
772
|
+
address.room,
|
|
773
|
+
Number(input.lines || 40),
|
|
774
|
+
);
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "text" as const,
|
|
779
|
+
text: maybeJsonText(
|
|
780
|
+
previews,
|
|
781
|
+
input.verbose === true,
|
|
782
|
+
compactRoomPreviews(previews),
|
|
783
|
+
),
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
details: { previews },
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
if (view === "messages") {
|
|
790
|
+
const messages = ActorRooms.readRoomMessages(
|
|
791
|
+
stateDir,
|
|
792
|
+
address.room,
|
|
793
|
+
Number(input.lines || 40),
|
|
794
|
+
);
|
|
795
|
+
return {
|
|
796
|
+
content: [
|
|
797
|
+
{
|
|
798
|
+
type: "text" as const,
|
|
799
|
+
text: maybeJsonText(
|
|
800
|
+
messages,
|
|
801
|
+
input.verbose === true,
|
|
802
|
+
compactRoomMessages(messages),
|
|
803
|
+
),
|
|
804
|
+
},
|
|
805
|
+
],
|
|
806
|
+
details: { messages },
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
if (view === "contacts") {
|
|
810
|
+
const contacts = ActorRooms.readRoomContacts(stateDir, address.room);
|
|
811
|
+
return {
|
|
812
|
+
content: [
|
|
813
|
+
{
|
|
814
|
+
type: "text" as const,
|
|
815
|
+
text: maybeJsonText(
|
|
816
|
+
contacts,
|
|
817
|
+
input.verbose === true,
|
|
818
|
+
compactRoomContacts(contacts),
|
|
819
|
+
),
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
details: { contacts },
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
if (view === "roster") {
|
|
826
|
+
const roster = ActorRooms.readRoomRoster(stateDir, address.room);
|
|
827
|
+
return {
|
|
828
|
+
content: [
|
|
829
|
+
{
|
|
830
|
+
type: "text" as const,
|
|
831
|
+
text: maybeJsonText(
|
|
832
|
+
roster,
|
|
833
|
+
input.verbose === true,
|
|
834
|
+
compactRoomRoster(roster),
|
|
835
|
+
),
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
details: { roster },
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
throw new Error("inspect room:<run> supports view=status, view=messages, view=previews, view=roster, or view=contacts.");
|
|
842
|
+
}
|
|
621
843
|
const runId = address.kind === "run" ? address.value : undefined;
|
|
622
844
|
if (!runId)
|
|
623
845
|
throw new Error(
|
|
@@ -702,9 +924,28 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
702
924
|
details: { mailbox },
|
|
703
925
|
};
|
|
704
926
|
}
|
|
927
|
+
case "communication": {
|
|
928
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
929
|
+
const snapshot = ActorRooms.readCommunicationSnapshot(
|
|
930
|
+
String(status.state_dir ?? ""),
|
|
931
|
+
);
|
|
932
|
+
return {
|
|
933
|
+
content: [
|
|
934
|
+
{
|
|
935
|
+
type: "text" as const,
|
|
936
|
+
text: maybeJsonText(
|
|
937
|
+
snapshot ?? {},
|
|
938
|
+
input.verbose === true,
|
|
939
|
+
compactCommunicationSnapshot(snapshot),
|
|
940
|
+
),
|
|
941
|
+
},
|
|
942
|
+
],
|
|
943
|
+
details: { communication: snapshot },
|
|
944
|
+
};
|
|
945
|
+
}
|
|
705
946
|
default:
|
|
706
947
|
throw new Error(
|
|
707
|
-
"inspect view must be one of: status, tail, messages, artifacts, files, mailbox.",
|
|
948
|
+
"inspect view must be one of: status, tail, messages, artifacts, files, mailbox, communication.",
|
|
708
949
|
);
|
|
709
950
|
}
|
|
710
951
|
},
|
|
@@ -722,7 +963,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
722
963
|
name: "message",
|
|
723
964
|
label: "Message",
|
|
724
965
|
description:
|
|
725
|
-
"Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, tool:<name> calls, and coordinator/session-bound run messages.",
|
|
966
|
+
"Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
|
|
726
967
|
parameters: objectSchema(
|
|
727
968
|
{
|
|
728
969
|
body: unionSchema([
|
|
@@ -744,7 +985,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
744
985
|
reply_to: stringSchema("Optional message id this message replies to."),
|
|
745
986
|
summary: stringSchema("Optional short human-facing summary."),
|
|
746
987
|
to: stringSchema(
|
|
747
|
-
"Destination actor address, e.g. run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.",
|
|
988
|
+
"Destination actor address, e.g. run:<id>, branch:<run>/<branch>, room:<run>, coordinator, session:<id>, or tool:<name>.",
|
|
748
989
|
),
|
|
749
990
|
type: stringSchema(
|
|
750
991
|
"Semantic message type, e.g. control.approve or checkpoint.needs_scope.",
|
|
@@ -780,11 +1021,51 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
780
1021
|
);
|
|
781
1022
|
}
|
|
782
1023
|
} else if (address.kind === "branch" && address.value) {
|
|
783
|
-
|
|
1024
|
+
const runId = address.value;
|
|
1025
|
+
if (message.from) assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
|
|
1026
|
+
const status = message.from
|
|
1027
|
+
? assertRunExistsForActorMessage(runId)
|
|
1028
|
+
: assertRunAccessibleToContext(runId, ctx);
|
|
1029
|
+
const stateDir = String(status.state_dir ?? "");
|
|
1030
|
+
if (stateDir && address.branch) {
|
|
1031
|
+
const ensureBranchMember = (actorAddress: string) => {
|
|
1032
|
+
ActorRooms.ensureRoomMember(
|
|
1033
|
+
stateDir,
|
|
1034
|
+
runId,
|
|
1035
|
+
"main",
|
|
1036
|
+
actorAddress,
|
|
1037
|
+
{
|
|
1038
|
+
parent: `run:${runId}`,
|
|
1039
|
+
role: "branch",
|
|
1040
|
+
status: "present",
|
|
1041
|
+
},
|
|
1042
|
+
"Branch joined default room",
|
|
1043
|
+
);
|
|
1044
|
+
ActorRooms.writeBranchCommunicationSnapshot(
|
|
1045
|
+
stateDir,
|
|
1046
|
+
runId,
|
|
1047
|
+
actorAddress,
|
|
1048
|
+
);
|
|
1049
|
+
};
|
|
1050
|
+
ensureBranchMember(message.to);
|
|
1051
|
+
if (message.from) {
|
|
1052
|
+
const sender = ActorMessages.parseActorAddress(message.from);
|
|
1053
|
+
if (sender.kind === "branch" && sender.value === runId) {
|
|
1054
|
+
ensureBranchMember(message.from);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
ActorRooms.writeCommunicationSnapshot(stateDir, runId);
|
|
1058
|
+
}
|
|
784
1059
|
result = AsyncRuns.sendRunMessage(
|
|
785
1060
|
address.value,
|
|
786
1061
|
JSON.stringify(message),
|
|
787
1062
|
);
|
|
1063
|
+
} else if (address.kind === "room" && address.value && address.room) {
|
|
1064
|
+
assertMessageSenderBelongsToRun(message, address.value, `room:${address.value}`);
|
|
1065
|
+
const status = assertRunExistsForActorMessage(address.value);
|
|
1066
|
+
const stateDir = String(status.state_dir ?? "");
|
|
1067
|
+
if (!stateDir) throw new Error(`${message.to} has no run state directory.`);
|
|
1068
|
+
result = { ...ActorRooms.appendRoomMessage(stateDir, address.room, message) };
|
|
788
1069
|
} else if (address.kind === "tool" && address.value) {
|
|
789
1070
|
const tool = deps.getTool?.(address.value);
|
|
790
1071
|
if (!tool || typeof tool.execute !== "function") {
|
|
@@ -842,7 +1123,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
842
1123
|
});
|
|
843
1124
|
} else {
|
|
844
1125
|
throw new Error(
|
|
845
|
-
`message currently supports run:<id>, branch:<run>/<branch>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
1126
|
+
`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
846
1127
|
);
|
|
847
1128
|
}
|
|
848
1129
|
return {
|
|
@@ -942,6 +1223,8 @@ export function createRuntimeToolDefinition(
|
|
|
942
1223
|
},
|
|
943
1224
|
ctx.cwd,
|
|
944
1225
|
);
|
|
1226
|
+
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
1227
|
+
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
945
1228
|
return {
|
|
946
1229
|
content: [
|
|
947
1230
|
{ type: "text" as const, text: compactAsyncRunStatus(meta) },
|
package/package.json
CHANGED
package/recipes/lens-swarm.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lens-swarm",
|
|
3
3
|
"description": "General-purpose multi-lens review swarm. Launches independent reviewers by lens, then verifies, merges, judges, and normalizes the result.",
|
|
4
|
-
"tool": true,
|
|
5
4
|
"async": true,
|
|
6
5
|
"imports": {
|
|
7
6
|
"coordinator": "subagent-review-coordinator.json"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pipeline-room-swarm",
|
|
3
|
+
"async": true,
|
|
4
|
+
"args": [
|
|
5
|
+
"mission:string",
|
|
6
|
+
"model:string",
|
|
7
|
+
"thinking:string",
|
|
8
|
+
"roles:string",
|
|
9
|
+
"roles_path:path",
|
|
10
|
+
"rounds:int",
|
|
11
|
+
"delay:int",
|
|
12
|
+
"locker:bool",
|
|
13
|
+
"locker_lease_ms:int",
|
|
14
|
+
"artifact_path:path",
|
|
15
|
+
"repo:path"
|
|
16
|
+
],
|
|
17
|
+
"defaults": {
|
|
18
|
+
"thinking": "off",
|
|
19
|
+
"roles": "",
|
|
20
|
+
"roles_path": "",
|
|
21
|
+
"rounds": "4",
|
|
22
|
+
"delay": "10",
|
|
23
|
+
"locker": "false",
|
|
24
|
+
"locker_lease_ms": "600000",
|
|
25
|
+
"artifact_path": "{state_dir}/room-swarm-artifact.md",
|
|
26
|
+
"repo": "~/.pi/agent/extensions/pi-actors"
|
|
27
|
+
},
|
|
28
|
+
"artifacts": {
|
|
29
|
+
"artifact": "{artifact_path}",
|
|
30
|
+
"locker_journal": "{state_dir}/locker/journal.jsonl",
|
|
31
|
+
"locker_locks": "{state_dir}/locker/locks.json",
|
|
32
|
+
"locker_queue": "{state_dir}/locker/queue.json"
|
|
33
|
+
},
|
|
34
|
+
"mailbox": {
|
|
35
|
+
"accepts": [
|
|
36
|
+
"control.stop",
|
|
37
|
+
"control.cancel",
|
|
38
|
+
"control.kill"
|
|
39
|
+
],
|
|
40
|
+
"emits": [
|
|
41
|
+
"chat.message",
|
|
42
|
+
"actor.join",
|
|
43
|
+
"actor.leave",
|
|
44
|
+
"run.done",
|
|
45
|
+
"run.failed"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"template": "{repo}/scripts/room-swarm.mjs --run-id={run_id} --mission={mission} --model={model} --thinking={thinking} --roles={roles} --roles-path={roles_path} --rounds={rounds} --delay={delay} --locker={locker} --locker-lease-ms={locker_lease_ms} --artifact-path={artifact_path}"
|
|
49
|
+
}
|