@spoons-and-mirrors/iam 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # IAM (Inter-Agent Messaging)
2
2
 
3
- Enable parallel agents communication for opencode
3
+ ### Enable parallel agents communication for opencode
4
4
 
5
- `@spoons-and-mirrors/iam@latest`
5
+ ![header](./header2.webp)
6
6
 
7
7
  ## How It Works
8
8
 
9
- Parallel agents they can send messages to each other using the `broadcast` tool. Messages are relayed to the proper agent's context.
10
-
11
9
  ```mermaid
12
10
  sequenceDiagram
13
11
  participant Parent as Parent Session
@@ -17,62 +15,105 @@ sequenceDiagram
17
15
  Parent->>A: spawn task
18
16
  Parent->>B: spawn task
19
17
 
20
- Note over A,B: Both agents auto-register on first LLM call
18
+ Note over A,B: Attention mechanism activation
19
+
20
+ A->>B: broadcast(message="Doing X") notifies all parallel agents
21
+ Note over A: Tool result shows agentB is available
22
+
23
+ B->>A: broadcast(message="Doing Y") notifies all parallel agents
24
+ Note over B: Tool result shows agentA is available
25
+
26
+ Note over A,B: Attention mechanism activated
21
27
 
22
28
  A->>B: broadcast(recipient="agentB", message="Question?")
29
+ A->>B: broadcast(recipient="agentB", message="Other question?")
23
30
 
24
- Note over B: Receives message
25
- Note over B: Responds
31
+ Note over B: Receive messages in context
26
32
 
27
- B->>A: broadcast(recipient="agentA", reply_to=[1], message="Answer!")
33
+ B->>A: broadcast(reply_to=1, message="Answer!")
34
+ Note over B: Tool result shows source message
35
+ Note over B: Message 1 removed from context
36
+ Note over B: Audit trace persists in tool result
28
37
 
29
38
  Note over A: Receives reply
30
- Note over B: Tool output shows both reply and handled message
31
- Note over B: Remove message from inbox
39
+ ```
40
+
41
+ ## Installation
42
+
43
+ Add to your OpenCode config:
44
+
45
+ ```
46
+ "plugin": ["@spoons-and-mirrors/iam@latest"]
32
47
  ```
33
48
 
34
49
  ## The `broadcast` Tool
35
50
 
36
51
  ```
37
- broadcast(message="...") # Send to all agents
38
- broadcast(recipient="agentB", message="...") # Send to specific agent
39
- broadcast(reply_to=[1, 2], message="...") # Mark messages as handled
40
- broadcast(recipient="agentA", reply_to=[1], message="...") # Reply and mark handled
52
+ broadcast(message="...") # Send to all agents
53
+ broadcast(recipient="agentB", message="...") # Send to specific agent
54
+ broadcast(reply_to=1, message="...") # Reply to message #1 (auto-wires recipient)
41
55
  ```
42
56
 
43
57
  ### Parameters
44
58
 
45
- | Parameter | Required | Description |
46
- | ----------- | -------- | -------------------------------------------------------------------- |
47
- | `message` | Yes | Your message content |
48
- | `recipient` | No | Target agent(s), comma-separated. Omit to send to all |
49
- | `reply_to` | No | Array of message IDs to mark as handled (e.g., `[1]` or `[1, 2, 3]`) |
59
+ | Parameter | Required | Description |
60
+ | ----------- | -------- | --------------------------------------------------------------- |
61
+ | `message` | Yes | Your message content |
62
+ | `recipient` | No | Target agent (single agent only) |
63
+ | `reply_to` | No | Message ID to reply to - auto-wires recipient to message sender |
50
64
 
51
65
  ## Receiving Messages
52
66
 
53
- Messages appear as a `broadcast` tool result with structured data:
67
+ Messages are injected as a synthetic `broadcast` tool result. Here's the complete structure:
54
68
 
55
69
  ```json
56
70
  {
57
- "messages": [
58
- { "id": 1, "from": "agentA", "body": "What's the status on the API?" },
59
- { "id": 2, "from": "agentA", "body": "Also, can you check the tests?" }
60
- ],
61
- "agents": ["agentA", "agentC: Working on backend"]
71
+ "tool": "broadcast",
72
+ "state": {
73
+ "status": "completed",
74
+ "input": { "synthetic": true },
75
+ "output": {
76
+ "agents": [
77
+ { "name": "agentA", "status": "Working on frontend components" }
78
+ ],
79
+ "messages": [
80
+ {
81
+ "id": 1,
82
+ "from": "agentA",
83
+ "content": "What's the status on the API?"
84
+ },
85
+ {
86
+ "id": 2,
87
+ "from": "agentA",
88
+ "content": "Also, can you check the tests?"
89
+ }
90
+ ]
91
+ },
92
+ "title": "1 agent(s), 2 message(s)"
93
+ }
62
94
  }
63
95
  ```
64
96
 
65
- The `agents` array always shows available agents to message. This is injected even when there are no incoming messages.
97
+ - **`input.synthetic`**: Indicates this was injected by IAM, not a real agent call
98
+ - **`output.agents`**: Status announcements from other agents (not replyable)
99
+ - **`output.messages`**: Actual messages you can reply to using `reply_to`
66
100
 
67
101
  Messages persist in the inbox until the agent marks them as handled using `reply_to`.
68
102
 
69
- ## Installation
103
+ **Discovery:** Agents discover each other by calling `broadcast` which activates the attention mechanism.
70
104
 
71
- Add to your OpenCode config:
105
+ ## Attention Layer
72
106
 
73
- ```
74
- "plugin": ["@spoons-and-mirrors/iam@latest"]
75
- ```
107
+ On every LLM fetch, pending inbox messages are injected as a synthetic `broadcast` tool result at the end of the message chain. The synthetic call has `input: { synthetic: true }` to indicate it was injected by IAM, not a real agent call.
108
+
109
+ After injection, the message chain looks like:
110
+
111
+ 1. system prompt
112
+ 2. user message
113
+ 3. assistant response
114
+ 4. tool calls...
115
+ 5. user message
116
+ 6. **`[broadcast]` 1 agent(s), 2 message(s)** ← injected at end
76
117
 
77
118
  ## Example Workflow
78
119
 
@@ -80,21 +121,21 @@ Add to your OpenCode config:
80
121
  # Parent spawns two agents to work on different parts of a feature
81
122
 
82
123
  AgentA (working on frontend):
83
- broadcast(message="Starting frontend work")
84
- ... does work ...
85
- broadcast(recipient="agentB", message="Need the API schema")
124
+ -> broadcast(message="Starting frontend work")
125
+ # Tool result shows: "Available agents: agentB"
126
+ -> ... does work ...
127
+ -> broadcast(recipient="agentB", message="Need the API schema")
86
128
 
87
129
  AgentB (working on backend):
88
- broadcast(message="Starting backend work")
89
- ... sees AgentA's question in inbox ...
90
- broadcast(recipient="agentA", reply_to=[1], message="Here's the schema: {...}")
130
+ -> broadcast(message="Starting backend work")
131
+ # Tool result shows: "Available agents: agentA"
132
+ -> ... sees AgentA's question in inbox ...
133
+ -> broadcast(reply_to=1, message="Here's the schema: {...}")
134
+ # Tool result shows: Marked as handled: #1 from agentA
135
+ # Recipient auto-wired to agentA
91
136
 
92
137
  AgentA:
93
- ... sees AgentB's response in inbox ...
94
- broadcast(reply_to=[1], message="Got it, thanks!")
138
+ -> ... sees AgentB's response in inbox ...
139
+ -> broadcast(reply_to=1, message="Got it, thanks!")
140
+ # Recipient auto-wired to agentB
95
141
  ```
96
-
97
- ## Notes
98
-
99
- - Agents are assigned aliases automatically: `agentA`, `agentB`, `agentC`, etc.
100
- - Logs are written to `.logs/iam.log` for debugging
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAijBlD,QAAA,MAAM,MAAM,EAAE,MAwTb,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAkmBlD,QAAA,MAAM,MAAM,EAAE,MAyXb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { tool } from "@opencode-ai/plugin";
3
3
 
4
4
  // prompt.ts
5
- var BROADCAST_DESCRIPTION = `Communicate with other parallel agents. Use 'recipient' for specific agent(s), or omit to message all. Use 'reply_to' to mark messages as handled.`;
6
- function broadcastResult(alias, recipients, parallelAgents, handledMessages) {
5
+ var BROADCAST_DESCRIPTION = `Communicate with other parallel agents. Use 'recipient' for a specific agent, or omit to message all. Use 'reply_to' to reply (auto-wires recipient to sender).`;
6
+ function broadcastResult(alias, recipients, parallelAgents, handledMessage) {
7
7
  const lines = [];
8
8
  lines.push(`You are: ${alias}`);
9
9
  lines.push(``);
10
10
  if (parallelAgents.length > 0) {
11
- lines.push(`Available agents to message:`);
11
+ lines.push(`Available agents:`);
12
12
  for (const agent of parallelAgents) {
13
13
  if (agent.description) {
14
14
  lines.push(` - ${agent.alias}: ${agent.description}`);
@@ -19,27 +19,21 @@ function broadcastResult(alias, recipients, parallelAgents, handledMessages) {
19
19
  } else {
20
20
  lines.push(`No other agents available yet.`);
21
21
  }
22
- if (recipients.length > 0) {
22
+ if (handledMessage) {
23
+ lines.push(``);
24
+ const preview = handledMessage.body.length > 80 ? handledMessage.body.substring(0, 80) + "..." : handledMessage.body;
25
+ lines.push(`Replied to #${handledMessage.id} from ${handledMessage.from}:`);
26
+ lines.push(` "${preview}"`);
27
+ } else if (recipients.length > 0) {
23
28
  lines.push(``);
24
29
  const recipientStr = recipients.length === 1 ? recipients[0] : recipients.join(", ");
25
30
  lines.push(`Message sent to: ${recipientStr}`);
26
31
  }
27
- if (handledMessages.length > 0) {
28
- lines.push(``);
29
- lines.push(`Marked as handled: ${handledMessages.length} message(s)`);
30
- for (const msg of handledMessages) {
31
- const preview = msg.body.length > 80 ? msg.body.substring(0, 80) + "..." : msg.body;
32
- lines.push(` #${msg.id} from ${msg.from}: "${preview}"`);
33
- }
34
- }
35
32
  return lines.join(`
36
33
  `);
37
34
  }
38
35
  var BROADCAST_MISSING_MESSAGE = `Error: 'message' parameter is required.`;
39
36
  var BROADCAST_SELF_MESSAGE = `Warning: You cannot send a message to yourself. The target alias is your own alias. Choose a different recipient.`;
40
- function broadcastMessageTooLong(length, maxLength) {
41
- return `Error: Message too long (${length} chars). Maximum allowed: ${maxLength} chars.`;
42
- }
43
37
  function broadcastUnknownRecipient(recipient, known) {
44
38
  const list = known.length > 0 ? `Known agents: ${known.join(", ")}` : "No agents available yet.";
45
39
  return `Error: Unknown recipient "${recipient}". ${list}`;
@@ -58,31 +52,39 @@ Call \`broadcast(message="...")\` as your FIRST action to announce yourself and
58
52
  - \`broadcast(recipient="agentB", message="...")\` → send to specific agent
59
53
 
60
54
  ## Receiving Messages
61
- Incoming messages appear as \`broadcast\` tool results with a \`messages\` array:
55
+ Incoming messages appear as synthetic \`broadcast\` tool results:
62
56
  \`\`\`
63
- { messages: [{ id: 1, from: "agentA", body: "..." }, ...] }
57
+ {
58
+ agents: [{ name: "agentA", status: "Working on X" }],
59
+ messages: [{ id: 1, from: "agentA", content: "..." }]
60
+ }
64
61
  \`\`\`
65
62
 
66
- Use \`reply_to\` to mark messages as handled (they persist until you do):
67
- - \`broadcast(recipient="agentA", reply_to=[1], message="...")\`
68
- - \`broadcast(recipient="agentA", reply_to=[1, 2, 3], message="...")\`
63
+ - **agents**: Status announcements (not replyable)
64
+ - **messages**: Messages you can reply to using \`reply_to\`
65
+
66
+ Use \`reply_to\` to reply to a message (auto-wires recipient):
67
+ - \`broadcast(reply_to=1, message="...")\` → replies to message #1
69
68
  </instructions>
70
69
  `;
71
70
 
72
71
  // logger.ts
73
72
  import * as fs from "fs";
74
73
  import * as path from "path";
74
+ var DEBUG_ENABLED = process.env.OPENCODE_IAM_DEBUG_LOGS === "1";
75
75
  var LOG_DIR = path.join(process.cwd(), ".logs");
76
76
  var LOG_FILE = path.join(LOG_DIR, "iam.log");
77
77
  var WRITE_INTERVAL_MS = 100;
78
78
  var logBuffer = [];
79
79
  var writeScheduled = false;
80
- try {
81
- if (!fs.existsSync(LOG_DIR)) {
82
- fs.mkdirSync(LOG_DIR, { recursive: true });
83
- }
84
- fs.writeFileSync(LOG_FILE, "");
85
- } catch {}
80
+ if (DEBUG_ENABLED) {
81
+ try {
82
+ if (!fs.existsSync(LOG_DIR)) {
83
+ fs.mkdirSync(LOG_DIR, { recursive: true });
84
+ }
85
+ fs.writeFileSync(LOG_FILE, "");
86
+ } catch {}
87
+ }
86
88
  function formatTimestamp() {
87
89
  return new Date().toISOString();
88
90
  }
@@ -105,6 +107,9 @@ function scheduleFlush() {
105
107
  }
106
108
  }
107
109
  function writeLog(level, category, message, data) {
110
+ if (!DEBUG_ENABLED) {
111
+ return;
112
+ }
108
113
  const timestamp = formatTimestamp();
109
114
  const dataStr = data !== undefined ? ` | ${JSON.stringify(data)}` : "";
110
115
  const logLine = `[${timestamp}] [${level}] [${category}] ${message}${dataStr}
@@ -130,7 +135,7 @@ var LOG = {
130
135
  // index.ts
131
136
  var CHAR_CODE_A = 65;
132
137
  var ALPHABET_SIZE = 26;
133
- var MAX_DESCRIPTION_LENGTH = 100;
138
+ var MAX_DESCRIPTION_LENGTH = 300;
134
139
  var MESSAGE_TTL_MS = 30 * 60 * 1000;
135
140
  var UNHANDLED_TTL_MS = 2 * 60 * 60 * 1000;
136
141
  var MAX_INBOX_SIZE = 100;
@@ -142,6 +147,7 @@ var MAX_MESSAGE_LENGTH = 1e4;
142
147
  var inboxes = new Map;
143
148
  var sessionMsgCounter = new Map;
144
149
  var activeSessions = new Set;
150
+ var announcedSessions = new Set;
145
151
  var sessionToAlias = new Map;
146
152
  var aliasToSession = new Map;
147
153
  var agentDescriptions = new Map;
@@ -233,15 +239,17 @@ function getInbox(sessionId) {
233
239
  }
234
240
  return inboxes.get(sessionId);
235
241
  }
236
- function sendMessage(from, to, body) {
242
+ function sendMessage(from, to, body, isStatusAnnouncement = false) {
243
+ const msgIndex = isStatusAnnouncement ? 0 : getNextMsgIndex(to);
237
244
  const message = {
238
245
  id: generateId(),
239
- msgIndex: getNextMsgIndex(to),
246
+ msgIndex,
240
247
  from,
241
248
  to,
242
249
  body,
243
250
  timestamp: Date.now(),
244
- handled: false
251
+ handled: false,
252
+ isStatusAnnouncement
245
253
  };
246
254
  const queue = getInbox(to);
247
255
  if (queue.length >= MAX_INBOX_SIZE) {
@@ -259,7 +267,8 @@ function sendMessage(from, to, body) {
259
267
  msgIndex: message.msgIndex,
260
268
  from,
261
269
  to,
262
- bodyLength: body.length
270
+ bodyLength: body.length,
271
+ isStatusAnnouncement
263
272
  });
264
273
  return message;
265
274
  }
@@ -360,18 +369,39 @@ async function getParentId(client, sessionId) {
360
369
  function createInboxMessage(sessionId, messages, baseUserMessage) {
361
370
  const now = Date.now();
362
371
  const userInfo = baseUserMessage.info;
363
- const inboxMsgs = messages.map((m) => ({
364
- id: m.msgIndex,
365
- from: m.from,
366
- body: m.body
367
- }));
372
+ const statusAnnouncements = messages.filter((m) => m.isStatusAnnouncement);
373
+ const regularMessages = messages.filter((m) => !m.isStatusAnnouncement);
374
+ const outputData = {};
375
+ if (statusAnnouncements.length > 0) {
376
+ outputData.agents = statusAnnouncements.map((m) => ({
377
+ name: m.from,
378
+ status: m.body
379
+ }));
380
+ }
381
+ if (regularMessages.length > 0) {
382
+ outputData.messages = regularMessages.map((m) => ({
383
+ id: m.msgIndex,
384
+ from: m.from,
385
+ content: m.body
386
+ }));
387
+ }
368
388
  const assistantMessageId = `msg_broadcast_${now}`;
369
389
  const partId = `prt_broadcast_${now}`;
370
390
  const callId = `call_broadcast_${now}`;
391
+ const titleParts = [];
392
+ if (statusAnnouncements.length > 0) {
393
+ titleParts.push(`${statusAnnouncements.length} agent(s)`);
394
+ }
395
+ if (regularMessages.length > 0) {
396
+ titleParts.push(`${regularMessages.length} message(s)`);
397
+ }
398
+ const title = titleParts.length > 0 ? titleParts.join(", ") : "Inbox";
399
+ const output = JSON.stringify(outputData);
371
400
  log.debug(LOG.MESSAGE, `Creating bundled inbox message`, {
372
401
  sessionId,
373
- messageCount: messages.length,
374
- msgIndices: messages.map((m) => m.msgIndex)
402
+ totalCount: messages.length,
403
+ statusCount: statusAnnouncements.length,
404
+ messageCount: regularMessages.length
375
405
  });
376
406
  const result = {
377
407
  info: {
@@ -403,12 +433,13 @@ function createInboxMessage(sessionId, messages, baseUserMessage) {
403
433
  tool: "broadcast",
404
434
  state: {
405
435
  status: "completed",
406
- input: { messages: inboxMsgs },
407
- output: `${messages.length} message(s)`,
408
- title: `Inbox (${messages.length} messages)`,
436
+ input: { synthetic: true },
437
+ output,
438
+ title,
409
439
  metadata: {
410
- incoming_message: true,
411
- message_count: messages.length
440
+ incoming_message: regularMessages.length > 0,
441
+ message_count: regularMessages.length,
442
+ status_count: statusAnnouncements.length
412
443
  },
413
444
  time: { start: now, end: now }
414
445
  }
@@ -428,59 +459,96 @@ var plugin = async (ctx) => {
428
459
  broadcast: tool({
429
460
  description: BROADCAST_DESCRIPTION,
430
461
  args: {
431
- recipient: tool.schema.string().optional().describe("Target agent(s), comma-separated. Omit to send to all."),
462
+ recipient: tool.schema.string().optional().describe("Target agent (single agent only). Omit to send to all."),
432
463
  message: tool.schema.string().describe("Your message"),
433
- reply_to: tool.schema.array(tool.schema.number()).optional().describe("Message IDs to mark as handled (e.g., [1, 2, 3])")
464
+ reply_to: tool.schema.number().optional().describe("Message ID to mark as handled")
434
465
  },
435
466
  async execute(args, context) {
436
467
  const sessionId = context.sessionID;
437
- const isFirstCall = !activeSessions.has(sessionId);
468
+ const isFirstCall = !announcedSessions.has(sessionId);
438
469
  registerSession(sessionId);
439
470
  const alias = getAlias(sessionId);
440
471
  if (!args.message) {
441
472
  log.warn(LOG.TOOL, `broadcast missing 'message'`, { alias });
442
473
  return BROADCAST_MISSING_MESSAGE;
443
474
  }
444
- if (args.message.length > MAX_MESSAGE_LENGTH) {
445
- log.warn(LOG.TOOL, `broadcast message too long`, {
475
+ let messageContent = args.message;
476
+ if (messageContent.length > MAX_MESSAGE_LENGTH) {
477
+ log.warn(LOG.TOOL, `broadcast message truncated`, {
446
478
  alias,
447
- length: args.message.length
479
+ originalLength: messageContent.length,
480
+ truncatedTo: MAX_MESSAGE_LENGTH
448
481
  });
449
- return broadcastMessageTooLong(args.message.length, MAX_MESSAGE_LENGTH);
450
- }
451
- if (isFirstCall) {
452
- setDescription(sessionId, args.message);
482
+ messageContent = messageContent.substring(0, MAX_MESSAGE_LENGTH) + "... [truncated]";
453
483
  }
484
+ const parallelAgents = getParallelAgents(sessionId);
454
485
  log.debug(LOG.TOOL, `broadcast called`, {
455
486
  sessionId,
456
487
  alias,
457
488
  recipient: args.recipient,
458
489
  reply_to: args.reply_to,
459
- messageLength: args.message.length,
490
+ messageLength: messageContent.length,
460
491
  isFirstCall
461
492
  });
462
- let handledMessages = [];
463
- if (args.reply_to && args.reply_to.length > 0) {
464
- handledMessages = markMessagesAsHandled(sessionId, args.reply_to);
465
- log.info(LOG.TOOL, `Handled messages via reply_to`, {
493
+ if (isFirstCall && args.reply_to === undefined) {
494
+ announcedSessions.add(sessionId);
495
+ setDescription(sessionId, messageContent);
496
+ const knownAgents2 = getKnownAliases(sessionId);
497
+ for (const targetAlias of knownAgents2) {
498
+ const recipientSessionId = aliasToSession.get(targetAlias);
499
+ if (recipientSessionId) {
500
+ sendMessage(alias, recipientSessionId, messageContent, true);
501
+ }
502
+ }
503
+ log.info(LOG.TOOL, `First broadcast - sent status announcement`, {
466
504
  alias,
467
- requested: args.reply_to,
468
- actuallyHandled: handledMessages.length
505
+ status: messageContent,
506
+ sentTo: knownAgents2
469
507
  });
508
+ return broadcastResult(alias, knownAgents2, parallelAgents, undefined);
509
+ }
510
+ if (isFirstCall) {
511
+ announcedSessions.add(sessionId);
512
+ log.info(LOG.TOOL, `First broadcast with reply_to - treating as normal reply`, {
513
+ alias,
514
+ reply_to: args.reply_to
515
+ });
516
+ }
517
+ let handledMessage;
518
+ let autoRecipient;
519
+ if (args.reply_to !== undefined) {
520
+ const handled = markMessagesAsHandled(sessionId, [args.reply_to]);
521
+ if (handled.length > 0) {
522
+ handledMessage = handled[0];
523
+ autoRecipient = handledMessage.from;
524
+ if (args.recipient && args.recipient !== autoRecipient) {
525
+ log.warn(LOG.TOOL, `reply_to provided - ignoring recipient param`, {
526
+ alias,
527
+ providedRecipient: args.recipient,
528
+ autoWiredRecipient: autoRecipient
529
+ });
530
+ }
531
+ log.info(LOG.TOOL, `Handled message via reply_to`, {
532
+ alias,
533
+ msgId: args.reply_to,
534
+ autoRecipient
535
+ });
536
+ }
470
537
  }
471
538
  const knownAgents = getKnownAliases(sessionId);
472
- const parallelAgents = getParallelAgents(sessionId);
473
539
  let targetAliases;
474
- if (!args.recipient) {
540
+ if (autoRecipient) {
541
+ targetAliases = [autoRecipient];
542
+ } else if (!args.recipient) {
475
543
  targetAliases = knownAgents;
476
544
  } else {
477
- targetAliases = args.recipient.split(",").map((s) => s.trim()).filter(Boolean);
545
+ targetAliases = [args.recipient.trim()];
478
546
  }
479
547
  if (targetAliases.length === 0) {
480
548
  log.info(LOG.TOOL, `No recipients, returning agent info`, {
481
549
  alias
482
550
  });
483
- return broadcastResult(alias, [], parallelAgents, handledMessages);
551
+ return broadcastResult(alias, [], parallelAgents, handledMessage);
484
552
  }
485
553
  const parentId = await getParentId(client, sessionId);
486
554
  const recipientSessions = [];
@@ -511,11 +579,11 @@ var plugin = async (ctx) => {
511
579
  log.info(LOG.TOOL, `No valid recipients after filtering`, {
512
580
  alias
513
581
  });
514
- return broadcastResult(alias, [], parallelAgents, handledMessages);
582
+ return broadcastResult(alias, [], parallelAgents, handledMessage);
515
583
  }
516
584
  const isTargetingParent = parentId && recipientSessions.includes(parentId);
517
585
  for (const recipientSessionId of recipientSessions) {
518
- sendMessage(alias, recipientSessionId, args.message);
586
+ sendMessage(alias, recipientSessionId, messageContent, false);
519
587
  }
520
588
  if (isTargetingParent) {
521
589
  log.info(LOG.MESSAGE, `Broadcasting to parent session, calling notify_once`, { sessionId, parentId });
@@ -539,7 +607,7 @@ var plugin = async (ctx) => {
539
607
  });
540
608
  }
541
609
  }
542
- return broadcastResult(alias, validTargets, parallelAgents, handledMessages);
610
+ return broadcastResult(alias, validTargets, parallelAgents, handledMessage);
543
611
  }
544
612
  })
545
613
  },
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../logger.ts"],"names":[],"mappings":"AAkCA;;GAEG;AACH,iBAAe,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAexC;AA0BD,eAAO,MAAM,GAAG;sBACI,MAAM,WAAW,MAAM,SAAS,OAAO;qBAGxC,MAAM,WAAW,MAAM,SAAS,OAAO;qBAGvC,MAAM,WAAW,MAAM,SAAS,OAAO;sBAGtC,MAAM,WAAW,MAAM,SAAS,OAAO;IAGzD,yDAAyD;;CAE1D,CAAC;AAGF,eAAO,MAAM,GAAG;;;;;;CAMN,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../logger.ts"],"names":[],"mappings":"AAuCA;;GAEG;AACH,iBAAe,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAexC;AA+BD,eAAO,MAAM,GAAG;sBACI,MAAM,WAAW,MAAM,SAAS,OAAO;qBAGxC,MAAM,WAAW,MAAM,SAAS,OAAO;qBAGvC,MAAM,WAAW,MAAM,SAAS,OAAO;sBAGtC,MAAM,WAAW,MAAM,SAAS,OAAO;IAGzD,yDAAyD;;CAE1D,CAAC;AAGF,eAAO,MAAM,GAAG;;;;;;CAMN,CAAC"}
package/dist/prompt.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const BROADCAST_DESCRIPTION = "Communicate with other parallel agents. Use 'recipient' for specific agent(s), or omit to message all. Use 'reply_to' to mark messages as handled.";
1
+ export declare const BROADCAST_DESCRIPTION = "Communicate with other parallel agents. Use 'recipient' for a specific agent, or omit to message all. Use 'reply_to' to reply (auto-wires recipient to sender).";
2
2
  export interface ParallelAgent {
3
3
  alias: string;
4
4
  description?: string;
@@ -8,10 +8,9 @@ export interface HandledMessage {
8
8
  from: string;
9
9
  body: string;
10
10
  }
11
- export declare function broadcastResult(alias: string, recipients: string[], parallelAgents: ParallelAgent[], handledMessages: HandledMessage[]): string;
11
+ export declare function broadcastResult(alias: string, recipients: string[], parallelAgents: ParallelAgent[], handledMessage?: HandledMessage): string;
12
12
  export declare const BROADCAST_MISSING_MESSAGE = "Error: 'message' parameter is required.";
13
13
  export declare const BROADCAST_SELF_MESSAGE = "Warning: You cannot send a message to yourself. The target alias is your own alias. Choose a different recipient.";
14
- export declare function broadcastMessageTooLong(length: number, maxLength: number): string;
15
14
  export declare function broadcastUnknownRecipient(recipient: string, known: string[]): string;
16
- export declare const SYSTEM_PROMPT = "\n<instructions tool=\"iam\">\n# Inter-Agent Messaging\n\nUse `broadcast` to communicate with other parallel agents.\n\n## IMPORTANT: Broadcast Immediately on Start\nCall `broadcast(message=\"...\")` as your FIRST action to announce yourself and discover other agents. The tool result will show all available agents you can message.\n\n## Sending Messages\n- `broadcast(message=\"...\")` \u2192 send to all agents\n- `broadcast(recipient=\"agentB\", message=\"...\")` \u2192 send to specific agent\n\n## Receiving Messages\nIncoming messages appear as `broadcast` tool results with a `messages` array:\n```\n{ messages: [{ id: 1, from: \"agentA\", body: \"...\" }, ...] }\n```\n\nUse `reply_to` to mark messages as handled (they persist until you do):\n- `broadcast(recipient=\"agentA\", reply_to=[1], message=\"...\")`\n- `broadcast(recipient=\"agentA\", reply_to=[1, 2, 3], message=\"...\")`\n</instructions>\n";
15
+ export declare const SYSTEM_PROMPT = "\n<instructions tool=\"iam\">\n# Inter-Agent Messaging\n\nUse `broadcast` to communicate with other parallel agents.\n\n## IMPORTANT: Broadcast Immediately on Start\nCall `broadcast(message=\"...\")` as your FIRST action to announce yourself and discover other agents. The tool result will show all available agents you can message.\n\n## Sending Messages\n- `broadcast(message=\"...\")` \u2192 send to all agents\n- `broadcast(recipient=\"agentB\", message=\"...\")` \u2192 send to specific agent\n\n## Receiving Messages\nIncoming messages appear as synthetic `broadcast` tool results:\n```\n{\n agents: [{ name: \"agentA\", status: \"Working on X\" }],\n messages: [{ id: 1, from: \"agentA\", content: \"...\" }]\n}\n```\n\n- **agents**: Status announcements (not replyable)\n- **messages**: Messages you can reply to using `reply_to`\n\nUse `reply_to` to reply to a message (auto-wires recipient):\n- `broadcast(reply_to=1, message=\"...\")` \u2192 replies to message #1\n</instructions>\n";
17
16
  //# sourceMappingURL=prompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,uJAAuJ,CAAC;AAM1L,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAAE,EACpB,cAAc,EAAE,aAAa,EAAE,EAC/B,eAAe,EAAE,cAAc,EAAE,GAChC,MAAM,CAyCR;AAED,eAAO,MAAM,yBAAyB,4CAA4C,CAAC;AAEnF,eAAO,MAAM,sBAAsB,sHAAsH,CAAC;AAE1J,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,MAAM,CAER;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EAAE,GACd,MAAM,CAMR;AAMD,eAAO,MAAM,aAAa,q5BAuBzB,CAAC"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,oKAAoK,CAAC;AAMvM,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAAE,EACpB,cAAc,EAAE,aAAa,EAAE,EAC/B,cAAc,CAAC,EAAE,cAAc,GAC9B,MAAM,CAuCR;AAED,eAAO,MAAM,yBAAyB,4CAA4C,CAAC;AAEnF,eAAO,MAAM,sBAAsB,sHAAsH,CAAC;AAE1J,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EAAE,GACd,MAAM,CAMR;AAMD,eAAO,MAAM,aAAa,w+BA4BzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoons-and-mirrors/iam",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Inter-agent messaging for OpenCode parallel subagents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",