@spoons-and-mirrors/iam 0.1.7 → 0.1.9

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 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,67 +15,102 @@ sequenceDiagram
17
15
  Parent->>A: spawn task
18
16
  Parent->>B: spawn task
19
17
 
20
- Note over A,B: Registration
18
+ Note over A,B: Attention mechanism activation
21
19
 
22
- A->>A: broadcast(message="Doing X")
23
- Note over A: Tool result shows agentB is available
20
+ A->>B: broadcast(message="Doing X")
21
+ Note over B: Get announcement in synthetic tool result
24
22
 
25
- B->>B: broadcast(message="Doing Y")
26
- Note over B: Tool result shows agentA is available
27
23
 
28
24
  A->>B: broadcast(recipient="agentB", message="Question?")
25
+ A->>B: broadcast(recipient="agentB", message="Other question?")
29
26
 
30
- Note over B: Receives message in inbox
27
+ Note over B: Get messages in synthetic tool result
31
28
 
32
- B->>A: broadcast(recipient="agentA", reply_to=1, message="Answer!")
29
+ B->>A: broadcast(reply_to=1, message="Answer!")
33
30
  Note over B: Tool result shows source message
31
+ Note over B: Clear message 1 from synthetic
34
32
 
35
33
  Note over A: Receives reply
36
- Note over B: Message removed from inbox
37
- Note over B: Audit trace persists in tool result
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ Add to your OpenCode config:
39
+
40
+ ```
41
+ "plugin": ["@spoons-and-mirrors/iam@latest"]
38
42
  ```
39
43
 
40
44
  ## The `broadcast` Tool
41
45
 
42
46
  ```
43
- broadcast(message="...") # Send to all agents
44
- broadcast(recipient="agentB", message="...") # Send to specific agent
45
- broadcast(reply_to=1, message="...") # Mark message as handled
46
- broadcast(recipient="agentA", reply_to=1, message="...") # Reply and mark handled
47
+ broadcast(message="...") # Send to all agents
48
+ broadcast(recipient="agentB", message="...") # Send to specific agent
49
+ broadcast(reply_to=1, message="...") # Reply to message #1 (auto-wires recipient)
47
50
  ```
48
51
 
49
52
  ### Parameters
50
53
 
51
- | Parameter | Required | Description |
52
- | ----------- | -------- | ----------------------------------------- |
53
- | `message` | Yes | Your message content |
54
- | `recipient` | No | Target agent(s), comma-separated |
55
- | `reply_to` | No | Message ID to mark as handled (e.g., `1`) |
54
+ | Parameter | Required | Description |
55
+ | ----------- | -------- | --------------------------------------------------------------- |
56
+ | `message` | Yes | Your message content |
57
+ | `recipient` | No | Target agent (single agent only) |
58
+ | `reply_to` | No | Message ID to reply to - auto-wires recipient to message sender |
56
59
 
57
60
  ## Receiving Messages
58
61
 
59
- Messages appear as a `broadcast` tool result with structured data:
62
+ Messages are injected as a synthetic `broadcast` tool result. Here's the complete structure:
60
63
 
61
64
  ```json
62
65
  {
63
- "messages": [
64
- {"id": 1, "from": "agentA", "body": "What's the status on the API?"},
65
- {"id": 2, "from": "agentA", "body": "Also, can you check the tests?"}
66
- ]
66
+ "tool": "broadcast",
67
+ "state": {
68
+ "status": "completed",
69
+ "input": { "synthetic": true },
70
+ "output": {
71
+ "hint": "ACTION REQUIRED: Announce yourself...",
72
+ "agents": [
73
+ { "name": "agentA", "status": "Working on frontend components" }
74
+ ],
75
+ "messages": [
76
+ {
77
+ "id": 1,
78
+ "from": "agentA",
79
+ "content": "What's the status on the API?"
80
+ },
81
+ {
82
+ "id": 2,
83
+ "from": "agentA",
84
+ "content": "Also, can you check the tests?"
85
+ }
86
+ ]
87
+ },
88
+ "title": "1 agent(s), 2 message(s)"
89
+ }
67
90
  }
68
91
  ```
69
92
 
93
+ - **`input.synthetic`**: Indicates this was injected by IAM, not a real agent call
94
+ - **`output.hint`**: Shown only if agent hasn't announced yet (disappears after first broadcast)
95
+ - **`output.agents`**: Other agents and their status (not replyable)
96
+ - **`output.messages`**: Messages you can reply to using `reply_to`
97
+
70
98
  Messages persist in the inbox until the agent marks them as handled using `reply_to`.
71
99
 
72
- **Discovery:** Agents discover each other by calling `broadcast` - the tool result shows all available agents.
100
+ **Discovery:** Agents discover each other through synthetic injection. The first `broadcast` call sets the agent's status, which other agents see in the `agents` array.
73
101
 
74
- ## Installation
102
+ ## Attention Layer
75
103
 
76
- Add to your OpenCode config:
104
+ 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.
77
105
 
78
- ```
79
- "plugin": ["@spoons-and-mirrors/iam@latest"]
80
- ```
106
+ After injection, the message chain looks like:
107
+
108
+ 1. system prompt
109
+ 2. user message
110
+ 3. assistant response
111
+ 4. tool calls...
112
+ 5. user message
113
+ 6. **`[broadcast]` 1 agent(s), 2 message(s)** ← injected at end
81
114
 
82
115
  ## Example Workflow
83
116
 
@@ -94,10 +127,12 @@ AgentB (working on backend):
94
127
  -> broadcast(message="Starting backend work")
95
128
  # Tool result shows: "Available agents: agentA"
96
129
  -> ... sees AgentA's question in inbox ...
97
- -> broadcast(recipient="agentA", reply_to=1, message="Here's the schema: {...}")
130
+ -> broadcast(reply_to=1, message="Here's the schema: {...}")
98
131
  # Tool result shows: Marked as handled: #1 from agentA
132
+ # Recipient auto-wired to agentA
99
133
 
100
134
  AgentA:
101
135
  -> ... sees AgentB's response in inbox ...
102
136
  -> broadcast(reply_to=1, message="Got it, thanks!")
137
+ # Recipient auto-wired to agentB
103
138
  ```
@@ -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,MA0Tb,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;AAsnBlD,QAAA,MAAM,MAAM,EAAE,MA6Xb,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.`;
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
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,25 +19,21 @@ function broadcastResult(alias, recipients, parallelAgents, handledMessage) {
19
19
  } else {
20
20
  lines.push(`No other agents available yet.`);
21
21
  }
22
- if (recipients.length > 0) {
23
- lines.push(``);
24
- const recipientStr = recipients.length === 1 ? recipients[0] : recipients.join(", ");
25
- lines.push(`Message sent to: ${recipientStr}`);
26
- }
27
22
  if (handledMessage) {
28
23
  lines.push(``);
29
24
  const preview = handledMessage.body.length > 80 ? handledMessage.body.substring(0, 80) + "..." : handledMessage.body;
30
- lines.push(`Marked as handled: #${handledMessage.id} from ${handledMessage.from}`);
25
+ lines.push(`Replied to #${handledMessage.id} from ${handledMessage.from}:`);
31
26
  lines.push(` "${preview}"`);
27
+ } else if (recipients.length > 0) {
28
+ lines.push(``);
29
+ const recipientStr = recipients.length === 1 ? recipients[0] : recipients.join(", ");
30
+ lines.push(`Message sent to: ${recipientStr}`);
32
31
  }
33
32
  return lines.join(`
34
33
  `);
35
34
  }
36
35
  var BROADCAST_MISSING_MESSAGE = `Error: 'message' parameter is required.`;
37
36
  var BROADCAST_SELF_MESSAGE = `Warning: You cannot send a message to yourself. The target alias is your own alias. Choose a different recipient.`;
38
- function broadcastMessageTooLong(length, maxLength) {
39
- return `Error: Message too long (${length} chars). Maximum allowed: ${maxLength} chars.`;
40
- }
41
37
  function broadcastUnknownRecipient(recipient, known) {
42
38
  const list = known.length > 0 ? `Known agents: ${known.join(", ")}` : "No agents available yet.";
43
39
  return `Error: Unknown recipient "${recipient}". ${list}`;
@@ -48,38 +44,49 @@ var SYSTEM_PROMPT = `
48
44
 
49
45
  Use \`broadcast\` to communicate with other parallel agents.
50
46
 
51
- ## IMPORTANT: Broadcast Immediately on Start
52
- Call \`broadcast(message="...")\` as your FIRST action to announce yourself and discover other agents. The tool result will show all available agents you can message.
47
+ ## IMPORTANT: Announce Yourself First
48
+ Your first action should be calling \`broadcast(message="what you're working on")\` to announce yourself. Until you do, other agents won't know your purpose. The synthetic tool result will show a hint reminding you to announce.
53
49
 
54
50
  ## Sending Messages
55
- - \`broadcast(message="...")\` → send to all agents
51
+ - \`broadcast(message="...")\` → announce yourself or send to all agents
56
52
  - \`broadcast(recipient="agentB", message="...")\` → send to specific agent
57
53
 
58
54
  ## Receiving Messages
59
- Incoming messages appear as \`broadcast\` tool results with a \`messages\` array:
55
+ Incoming messages appear as synthetic \`broadcast\` tool results:
60
56
  \`\`\`
61
- { messages: [{ id: 1, from: "agentA", body: "..." }, ...] }
57
+ {
58
+ hint: "ACTION REQUIRED: Announce yourself...", // only if you haven't announced
59
+ agents: [{ name: "agentA", status: "Working on X" }],
60
+ messages: [{ id: 1, from: "agentA", content: "..." }]
61
+ }
62
62
  \`\`\`
63
63
 
64
- Use \`reply_to\` to mark a message as handled (they persist until you do):
65
- - \`broadcast(recipient="agentA", reply_to=1, message="...")\`
64
+ - **hint**: Disappears after you announce yourself
65
+ - **agents**: Other agents and their status (not replyable)
66
+ - **messages**: Messages you can reply to using \`reply_to\`
67
+
68
+ Use \`reply_to\` to reply to a message (auto-wires recipient):
69
+ - \`broadcast(reply_to=1, message="...")\` → replies to message #1
66
70
  </instructions>
67
71
  `;
68
72
 
69
73
  // logger.ts
70
74
  import * as fs from "fs";
71
75
  import * as path from "path";
76
+ var DEBUG_ENABLED = process.env.OPENCODE_IAM_DEBUG_LOGS === "1";
72
77
  var LOG_DIR = path.join(process.cwd(), ".logs");
73
78
  var LOG_FILE = path.join(LOG_DIR, "iam.log");
74
79
  var WRITE_INTERVAL_MS = 100;
75
80
  var logBuffer = [];
76
81
  var writeScheduled = false;
77
- try {
78
- if (!fs.existsSync(LOG_DIR)) {
79
- fs.mkdirSync(LOG_DIR, { recursive: true });
80
- }
81
- fs.writeFileSync(LOG_FILE, "");
82
- } catch {}
82
+ if (DEBUG_ENABLED) {
83
+ try {
84
+ if (!fs.existsSync(LOG_DIR)) {
85
+ fs.mkdirSync(LOG_DIR, { recursive: true });
86
+ }
87
+ fs.writeFileSync(LOG_FILE, "");
88
+ } catch {}
89
+ }
83
90
  function formatTimestamp() {
84
91
  return new Date().toISOString();
85
92
  }
@@ -102,6 +109,9 @@ function scheduleFlush() {
102
109
  }
103
110
  }
104
111
  function writeLog(level, category, message, data) {
112
+ if (!DEBUG_ENABLED) {
113
+ return;
114
+ }
105
115
  const timestamp = formatTimestamp();
106
116
  const dataStr = data !== undefined ? ` | ${JSON.stringify(data)}` : "";
107
117
  const logLine = `[${timestamp}] [${level}] [${category}] ${message}${dataStr}
@@ -127,7 +137,7 @@ var LOG = {
127
137
  // index.ts
128
138
  var CHAR_CODE_A = 65;
129
139
  var ALPHABET_SIZE = 26;
130
- var MAX_DESCRIPTION_LENGTH = 100;
140
+ var MAX_DESCRIPTION_LENGTH = 300;
131
141
  var MESSAGE_TTL_MS = 30 * 60 * 1000;
132
142
  var UNHANDLED_TTL_MS = 2 * 60 * 60 * 1000;
133
143
  var MAX_INBOX_SIZE = 100;
@@ -139,12 +149,14 @@ var MAX_MESSAGE_LENGTH = 1e4;
139
149
  var inboxes = new Map;
140
150
  var sessionMsgCounter = new Map;
141
151
  var activeSessions = new Set;
152
+ var announcedSessions = new Set;
142
153
  var sessionToAlias = new Map;
143
154
  var aliasToSession = new Map;
144
155
  var agentDescriptions = new Map;
145
156
  var nextAgentIndex = 0;
146
157
  var registeringSessionsLock = new Set;
147
158
  var sessionParentCache = new Map;
159
+ var childSessionCache = new Set;
148
160
  function cleanupExpiredMessages() {
149
161
  const now = Date.now();
150
162
  let totalRemoved = 0;
@@ -354,21 +366,55 @@ async function getParentId(client, sessionId) {
354
366
  return null;
355
367
  }
356
368
  }
357
- function createInboxMessage(sessionId, messages, baseUserMessage) {
369
+ async function isChildSession(client, sessionId) {
370
+ if (childSessionCache.has(sessionId)) {
371
+ return true;
372
+ }
373
+ const parentId = await getParentId(client, sessionId);
374
+ if (parentId) {
375
+ childSessionCache.add(sessionId);
376
+ return true;
377
+ }
378
+ return false;
379
+ }
380
+ function createInboxMessage(sessionId, messages, baseUserMessage, parallelAgents, hasAnnounced) {
358
381
  const now = Date.now();
359
382
  const userInfo = baseUserMessage.info;
360
- const inboxMsgs = messages.map((m) => ({
361
- id: m.msgIndex,
362
- from: m.from,
363
- body: m.body
364
- }));
383
+ const outputData = {};
384
+ if (!hasAnnounced) {
385
+ outputData.hint = `ACTION REQUIRED: Announce yourself to other agents by calling broadcast(message="what you're working on")`;
386
+ }
387
+ if (parallelAgents.length > 0) {
388
+ outputData.agents = parallelAgents.map((agent) => ({
389
+ name: agent.alias,
390
+ status: agent.description
391
+ }));
392
+ }
393
+ if (messages.length > 0) {
394
+ outputData.messages = messages.map((m) => ({
395
+ id: m.msgIndex,
396
+ from: m.from,
397
+ content: m.body
398
+ }));
399
+ }
365
400
  const assistantMessageId = `msg_broadcast_${now}`;
366
401
  const partId = `prt_broadcast_${now}`;
367
402
  const callId = `call_broadcast_${now}`;
368
- log.debug(LOG.MESSAGE, `Creating bundled inbox message`, {
403
+ const titleParts = [];
404
+ if (parallelAgents.length > 0) {
405
+ titleParts.push(`${parallelAgents.length} agent(s)`);
406
+ }
407
+ if (messages.length > 0) {
408
+ titleParts.push(`${messages.length} message(s)`);
409
+ }
410
+ const title = titleParts.length > 0 ? titleParts.join(", ") : "Inbox";
411
+ const output = JSON.stringify(outputData);
412
+ log.info(LOG.MESSAGE, `Creating inbox injection`, {
369
413
  sessionId,
370
- messageCount: messages.length,
371
- msgIndices: messages.map((m) => m.msgIndex)
414
+ agents: parallelAgents.map((a) => a.alias),
415
+ agentStatuses: parallelAgents.map((a) => a.description?.substring(0, 50)),
416
+ messageIds: messages.map((m) => m.msgIndex),
417
+ messageFroms: messages.map((m) => m.from)
372
418
  });
373
419
  const result = {
374
420
  info: {
@@ -400,12 +446,13 @@ function createInboxMessage(sessionId, messages, baseUserMessage) {
400
446
  tool: "broadcast",
401
447
  state: {
402
448
  status: "completed",
403
- input: { messages: inboxMsgs },
404
- output: `${messages.length} message(s)`,
405
- title: `Inbox (${messages.length} messages)`,
449
+ input: { synthetic: true },
450
+ output,
451
+ title,
406
452
  metadata: {
407
- incoming_message: true,
408
- message_count: messages.length
453
+ incoming_message: messages.length > 0,
454
+ message_count: messages.length,
455
+ agent_count: parallelAgents.length
409
456
  },
410
457
  time: { start: now, end: now }
411
458
  }
@@ -425,55 +472,83 @@ var plugin = async (ctx) => {
425
472
  broadcast: tool({
426
473
  description: BROADCAST_DESCRIPTION,
427
474
  args: {
428
- recipient: tool.schema.string().optional().describe("Target agent(s), comma-separated. Omit to send to all."),
475
+ recipient: tool.schema.string().optional().describe("Target agent (single agent only). Omit to send to all."),
429
476
  message: tool.schema.string().describe("Your message"),
430
477
  reply_to: tool.schema.number().optional().describe("Message ID to mark as handled")
431
478
  },
432
479
  async execute(args, context) {
433
480
  const sessionId = context.sessionID;
434
- const isFirstCall = !activeSessions.has(sessionId);
481
+ const isFirstCall = !announcedSessions.has(sessionId);
435
482
  registerSession(sessionId);
436
483
  const alias = getAlias(sessionId);
437
484
  if (!args.message) {
438
485
  log.warn(LOG.TOOL, `broadcast missing 'message'`, { alias });
439
486
  return BROADCAST_MISSING_MESSAGE;
440
487
  }
441
- if (args.message.length > MAX_MESSAGE_LENGTH) {
442
- log.warn(LOG.TOOL, `broadcast message too long`, {
488
+ let messageContent = args.message;
489
+ if (messageContent.length > MAX_MESSAGE_LENGTH) {
490
+ log.warn(LOG.TOOL, `broadcast message truncated`, {
443
491
  alias,
444
- length: args.message.length
492
+ originalLength: messageContent.length,
493
+ truncatedTo: MAX_MESSAGE_LENGTH
445
494
  });
446
- return broadcastMessageTooLong(args.message.length, MAX_MESSAGE_LENGTH);
495
+ messageContent = messageContent.substring(0, MAX_MESSAGE_LENGTH) + "... [truncated]";
447
496
  }
448
- if (isFirstCall) {
449
- setDescription(sessionId, args.message);
450
- }
451
- log.debug(LOG.TOOL, `broadcast called`, {
452
- sessionId,
497
+ const parallelAgents = getParallelAgents(sessionId);
498
+ log.info(LOG.TOOL, `broadcast called`, {
453
499
  alias,
454
500
  recipient: args.recipient,
455
501
  reply_to: args.reply_to,
456
- messageLength: args.message.length,
502
+ messageLength: messageContent.length,
457
503
  isFirstCall
458
504
  });
505
+ if (isFirstCall && args.reply_to === undefined) {
506
+ announcedSessions.add(sessionId);
507
+ setDescription(sessionId, messageContent);
508
+ const knownAgents2 = getKnownAliases(sessionId);
509
+ log.info(LOG.TOOL, `First broadcast - status announcement`, {
510
+ alias,
511
+ status: messageContent.substring(0, 80),
512
+ discoveredAgents: knownAgents2
513
+ });
514
+ return broadcastResult(alias, knownAgents2, parallelAgents, undefined);
515
+ }
516
+ if (isFirstCall) {
517
+ announcedSessions.add(sessionId);
518
+ log.info(LOG.TOOL, `First broadcast with reply_to - treating as normal reply`, {
519
+ alias,
520
+ reply_to: args.reply_to
521
+ });
522
+ }
459
523
  let handledMessage;
524
+ let autoRecipient;
460
525
  if (args.reply_to !== undefined) {
461
526
  const handled = markMessagesAsHandled(sessionId, [args.reply_to]);
462
527
  if (handled.length > 0) {
463
528
  handledMessage = handled[0];
529
+ autoRecipient = handledMessage.from;
530
+ if (args.recipient && args.recipient !== autoRecipient) {
531
+ log.warn(LOG.TOOL, `reply_to provided - ignoring recipient param`, {
532
+ alias,
533
+ providedRecipient: args.recipient,
534
+ autoWiredRecipient: autoRecipient
535
+ });
536
+ }
464
537
  log.info(LOG.TOOL, `Handled message via reply_to`, {
465
538
  alias,
466
- msgId: args.reply_to
539
+ msgId: args.reply_to,
540
+ autoRecipient
467
541
  });
468
542
  }
469
543
  }
470
544
  const knownAgents = getKnownAliases(sessionId);
471
- const parallelAgents = getParallelAgents(sessionId);
472
545
  let targetAliases;
473
- if (!args.recipient) {
546
+ if (autoRecipient) {
547
+ targetAliases = [autoRecipient];
548
+ } else if (!args.recipient) {
474
549
  targetAliases = knownAgents;
475
550
  } else {
476
- targetAliases = args.recipient.split(",").map((s) => s.trim()).filter(Boolean);
551
+ targetAliases = [args.recipient.trim()];
477
552
  }
478
553
  if (targetAliases.length === 0) {
479
554
  log.info(LOG.TOOL, `No recipients, returning agent info`, {
@@ -514,7 +589,7 @@ var plugin = async (ctx) => {
514
589
  }
515
590
  const isTargetingParent = parentId && recipientSessions.includes(parentId);
516
591
  for (const recipientSessionId of recipientSessions) {
517
- sendMessage(alias, recipientSessionId, args.message);
592
+ sendMessage(alias, recipientSessionId, messageContent);
518
593
  }
519
594
  if (isTargetingParent) {
520
595
  log.info(LOG.MESSAGE, `Broadcasting to parent session, calling notify_once`, { sessionId, parentId });
@@ -564,19 +639,8 @@ var plugin = async (ctx) => {
564
639
  log.debug(LOG.INJECT, `No sessionID in system.transform input, skipping`);
565
640
  return;
566
641
  }
567
- try {
568
- const result = await client.session.get({ path: { id: sessionId } });
569
- if (!result.data?.parentID) {
570
- log.debug(LOG.INJECT, `Session has no parentID (main session), skipping IAM`, {
571
- sessionId
572
- });
573
- return;
574
- }
575
- } catch (e) {
576
- log.debug(LOG.INJECT, `Failed to get session info, skipping IAM`, {
577
- sessionId,
578
- error: String(e)
579
- });
642
+ if (!await isChildSession(client, sessionId)) {
643
+ log.debug(LOG.INJECT, `Session has no parentID (main session), skipping IAM`, { sessionId });
580
644
  return;
581
645
  }
582
646
  registerSession(sessionId);
@@ -593,25 +657,37 @@ var plugin = async (ctx) => {
593
657
  return;
594
658
  }
595
659
  const sessionId = lastUserMsg.info.sessionID;
660
+ if (!await isChildSession(client, sessionId)) {
661
+ log.debug(LOG.INJECT, `Skipping messages.transform for main session`, {
662
+ sessionId
663
+ });
664
+ return;
665
+ }
596
666
  const unhandled = getUnhandledMessages(sessionId);
667
+ const parallelAgents = getParallelAgents(sessionId);
597
668
  log.debug(LOG.INJECT, `Checking for messages in transform`, {
598
669
  sessionId,
599
- unhandledCount: unhandled.length
670
+ unhandledCount: unhandled.length,
671
+ parallelAgentCount: parallelAgents.length
600
672
  });
601
- if (unhandled.length === 0) {
673
+ if (unhandled.length === 0 && parallelAgents.length === 0) {
674
+ log.info(LOG.INJECT, `No agents or messages to inject`, {
675
+ sessionId,
676
+ alias: getAlias(sessionId)
677
+ });
602
678
  return;
603
679
  }
604
- log.info(LOG.INJECT, `Injecting bundled inbox message`, {
680
+ log.info(LOG.INJECT, `Injecting synthetic broadcast`, {
605
681
  sessionId,
606
- unhandledCount: unhandled.length,
607
- msgIndices: unhandled.map((m) => m.msgIndex)
682
+ alias: getAlias(sessionId),
683
+ agentCount: parallelAgents.length,
684
+ agents: parallelAgents.map((a) => a.alias),
685
+ messageCount: unhandled.length,
686
+ messageIds: unhandled.map((m) => m.msgIndex)
608
687
  });
609
- const inboxMsg = createInboxMessage(sessionId, unhandled, lastUserMsg);
688
+ const hasAnnounced = announcedSessions.has(sessionId);
689
+ const inboxMsg = createInboxMessage(sessionId, unhandled, lastUserMsg, parallelAgents, hasAnnounced);
610
690
  output.messages.push(inboxMsg);
611
- log.info(LOG.INJECT, `Injected inbox at end of message chain`, {
612
- sessionId,
613
- messageCount: unhandled.length
614
- });
615
691
  },
616
692
  "experimental.config.transform": async (_input, output) => {
617
693
  const experimental = output.experimental ?? {};
@@ -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;
@@ -11,7 +11,6 @@ export interface HandledMessage {
11
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 a message as handled (they persist until you do):\n- `broadcast(recipient=\"agentA\", reply_to=1, 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: Announce Yourself First\nYour first action should be calling `broadcast(message=\"what you're working on\")` to announce yourself. Until you do, other agents won't know your purpose. The synthetic tool result will show a hint reminding you to announce.\n\n## Sending Messages\n- `broadcast(message=\"...\")` \u2192 announce yourself or 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 hint: \"ACTION REQUIRED: Announce yourself...\", // only if you haven't announced\n agents: [{ name: \"agentA\", status: \"Working on X\" }],\n messages: [{ id: 1, from: \"agentA\", content: \"...\" }]\n}\n```\n\n- **hint**: Disappears after you announce yourself\n- **agents**: Other agents and their status (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,cAAc,CAAC,EAAE,cAAc,GAC9B,MAAM,CA2CR;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,00BAsBzB,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,ssCA8BzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoons-and-mirrors/iam",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Inter-agent messaging for OpenCode parallel subagents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",