@spoons-and-mirrors/iam 0.1.4 → 0.1.6

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,42 +1,100 @@
1
- # IAM Plugin for OpenCode
1
+ # IAM (Inter-Agent Messaging)
2
2
 
3
- Lets parallel subagents talk to each other. No configuration needed — just install and it works.
3
+ Enable parallel agents communication for opencode
4
4
 
5
- ## What It Does
6
-
7
- When you spawn multiple agents with the `task` tool, they can:
8
- - **Announce** what they're working on (and see all parallel agents)
9
- - **Broadcast** messages to one, some, or all agents
10
- - Get **notified** when new messages arrive
5
+ `@spoons-and-mirrors/iam@latest`
11
6
 
12
7
  ## How It Works
13
8
 
14
- Agents get friendly names (agentA, agentB, ...) and automatically discover each other.
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
+ ```mermaid
12
+ sequenceDiagram
13
+ participant Parent as Parent Session
14
+ participant A as AgentA
15
+ participant B as AgentB
16
+
17
+ Parent->>A: spawn task
18
+ Parent->>B: spawn task
19
+
20
+ Note over A,B: Both agents auto-register on first LLM call
21
+
22
+ A->>B: broadcast(recipient="agentB", message="Question?")
23
+
24
+ Note over B: Receives message
25
+ Note over B: Responds
26
+
27
+ B->>A: broadcast(recipient="agentA", reply_to=[1], message="Answer!")
28
+
29
+ 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
32
+ ```
33
+
34
+ ## The `broadcast` Tool
35
+
36
+ ```
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
41
+ ```
42
+
43
+ ### Parameters
44
+
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]`) |
50
+
51
+ ## Receiving Messages
52
+
53
+ Messages appear as a `broadcast` tool result with structured data:
54
+
55
+ ```json
56
+ {
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"]
62
+ }
63
+ ```
15
64
 
16
- When an agent announces, the response shows all other parallel agents whether they've announced yet or not. This gives agents instant awareness of who's working alongside them. Agents can re-announce to update their status.
65
+ The `agents` array always shows available agents to message. This is injected even when there are no incoming messages.
17
66
 
18
- When an agent completes their task, they're encouraged to broadcast a completion message so others know.
67
+ Messages persist in the inbox until the agent marks them as handled using `reply_to`.
19
68
 
20
- The plugin injects IAM instructions into the **system prompt** for child sessions only (sessions with a `parentID`).
69
+ ## Installation
21
70
 
22
- ## Actions
71
+ Add to your OpenCode config:
23
72
 
24
- | Action | Description |
25
- |--------|-------------|
26
- | `announce` | Declare what you're working on. Shows all parallel agents. Can re-announce to update. |
27
- | `read` | Read your inbox (marks messages as read). |
28
- | `broadcast` | Send a message. Use `to` for specific agent(s), or omit for all. |
73
+ ```
74
+ "plugin": ["@spoons-and-mirrors/iam@latest"]
75
+ ```
29
76
 
30
- ## Examples
77
+ ## Example Workflow
31
78
 
32
79
  ```
33
- # Announce what you're doing (also shows parallel agents)
34
- action="announce", message="Refactoring the auth module"
80
+ # Parent spawns two agents to work on different parts of a feature
35
81
 
36
- # Message everyone
37
- action="broadcast", message="Found a bug in config.ts, heads up"
82
+ AgentA (working on frontend):
83
+ broadcast(message="Starting frontend work")
84
+ → ... does work ...
85
+ → broadcast(recipient="agentB", message="Need the API schema")
38
86
 
39
- # Message specific agent(s)
40
- action="broadcast", to="agentA", message="Can you check auth.ts?"
41
- action="broadcast", to="agentA,agentC", message="Sync up on API changes"
87
+ 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: {...}")
91
+
92
+ AgentA:
93
+ → ... sees AgentB's response in inbox ...
94
+ → broadcast(reply_to=[1], message="Got it, thanks!")
42
95
  ```
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,CAAA;AAqLjD,QAAA,MAAM,MAAM,EAAE,MAiLb,CAAA;AAED,eAAe,MAAM,CAAA"}
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"}
package/dist/index.js CHANGED
@@ -2,147 +2,122 @@
2
2
  import { tool } from "@opencode-ai/plugin";
3
3
 
4
4
  // prompt.ts
5
- var TOOL_DESCRIPTION = `Inter-agent messaging. Use this to communicate with other parallel agents (task tools).
6
-
7
- Actions:
8
- - "announce": Announce what you're working on (do this first!). Shows all parallel agents. You can re-announce to update your status.
9
- - "read": Read your messages (marks them as read)
10
- - "broadcast": Send a message (requires 'message', optional 'to')`;
11
- var ARG_DESCRIPTIONS = {
12
- action: "Action to perform",
13
- to: "Recipient(s): 'agentA', 'agentA,agentC', or 'all' (default: all)",
14
- message: "Your announcement or message content"
15
- };
16
- function formatAgentList(agents) {
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) {
17
7
  const lines = [];
18
- for (const agent of agents) {
19
- if (agent.description) {
20
- lines.push(`• ${agent.alias} is working on: ${agent.description}`);
21
- } else {
22
- lines.push(`• ${agent.alias} is running (hasn't announced yet)`);
8
+ lines.push(`You are: ${alias}`);
9
+ lines.push(``);
10
+ if (parallelAgents.length > 0) {
11
+ lines.push(`Available agents to message:`);
12
+ for (const agent of parallelAgents) {
13
+ if (agent.description) {
14
+ lines.push(` - ${agent.alias}: ${agent.description}`);
15
+ } else {
16
+ lines.push(` - ${agent.alias}`);
17
+ }
23
18
  }
24
- }
25
- return lines;
26
- }
27
- function readResult(alias, messages, unreadCount, hasAnnounced) {
28
- const lines = [`You are: ${alias}`, ``];
29
- if (messages.length === 0) {
30
- lines.push(`No messages in your inbox.`);
31
19
  } else {
32
- lines.push(`Your inbox (${unreadCount} were unread):`, `---`);
33
- for (const msg of messages) {
34
- const time = new Date(msg.timestamp).toISOString();
35
- const status = msg.read ? "" : " [NEW]";
36
- lines.push(`[${time}] From: ${msg.from}${status}`);
37
- lines.push(msg.body);
38
- lines.push(`---`);
39
- }
40
- lines.push(``);
41
- lines.push(`To reply: use action="broadcast" with to="<sender>" and message="..."`);
20
+ lines.push(`No other agents available yet.`);
42
21
  }
43
- if (!hasAnnounced) {
22
+ if (recipients.length > 0) {
44
23
  lines.push(``);
45
- lines.push(`IMPORTANT: You MUST use action="announce" to declare what you're working on before continuing.`);
24
+ const recipientStr = recipients.length === 1 ? recipients[0] : recipients.join(", ");
25
+ lines.push(`Message sent to: ${recipientStr}`);
46
26
  }
47
- return lines.join(`
48
- `);
49
- }
50
- function announceResult(alias, parallelAgents) {
51
- const lines = [
52
- `Announced! Other agents will see your description when they call announce.`,
53
- ``,
54
- `You are: ${alias}`
55
- ];
56
- if (parallelAgents.length > 0) {
27
+ if (handledMessages.length > 0) {
57
28
  lines.push(``);
58
- lines.push(`--- Parallel Agents ---`);
59
- lines.push(...formatAgentList(parallelAgents));
60
- lines.push(``);
61
- lines.push(`Use action="broadcast" to coordinate with them.`);
62
- } else {
63
- lines.push(``);
64
- lines.push(`No other agents running yet.`);
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
+ }
65
34
  }
66
35
  return lines.join(`
67
36
  `);
68
37
  }
69
- var BROADCAST_MISSING_MESSAGE = `Error: 'message' parameter is required for action="broadcast".`;
70
- function broadcastUnknownRecipient(to, known) {
71
- const list = known.length > 0 ? `Known agents: ${known.join(", ")}` : "No agents available yet.";
72
- return `Error: Unknown recipient "${to}". ${list}`;
73
- }
74
- function broadcastResult(recipients, messageId) {
75
- const recipientStr = recipients.length === 1 ? recipients[0] : recipients.join(", ");
76
- return `Message sent!
77
-
78
- To: ${recipientStr}
79
- Message ID: ${messageId}
80
-
81
- Recipients will be notified.`;
38
+ var BROADCAST_MISSING_MESSAGE = `Error: 'message' parameter is required.`;
39
+ 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.`;
82
42
  }
83
- function unknownAction(action) {
84
- return `Unknown action: ${action}. Valid actions: announce, read, broadcast`;
43
+ function broadcastUnknownRecipient(recipient, known) {
44
+ const list = known.length > 0 ? `Known agents: ${known.join(", ")}` : "No agents available yet.";
45
+ return `Error: Unknown recipient "${recipient}". ${list}`;
85
46
  }
86
47
  var SYSTEM_PROMPT = `
87
48
  <instructions tool="iam">
88
49
  # Inter-Agent Messaging
89
50
 
90
- You have access to an \`iam\` tool for communicating with other parallel agents.
51
+ Use \`broadcast\` to communicate with other parallel agents.
91
52
 
92
- Usage:
93
- - action="announce", message="..." - Announce what you're working on (do this first!)
94
- - action="read" - Read your messages
95
- - action="broadcast", message="..." - Message all agents
96
- - action="broadcast", to="agentA", message="..." - Message specific agent(s)
53
+ ## IMPORTANT: Broadcast Immediately on Start
54
+ 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.
97
55
 
98
- At the start of your task, use announce to let other agents know what you're doing.
99
- You can re-announce to update your status as your task evolves.
100
- Check your inbox when notified about new messages.
56
+ ## Sending Messages
57
+ - \`broadcast(message="...")\` send to all agents
58
+ - \`broadcast(recipient="agentB", message="...")\` send to specific agent
101
59
 
102
- When you complete your task, broadcast to all: "Done. Here's what I found/did: ..."
60
+ ## Receiving Messages
61
+ Incoming messages appear as \`broadcast\` tool results with a \`messages\` array:
62
+ \`\`\`
63
+ { messages: [{ id: 1, from: "agentA", body: "..." }, ...] }
64
+ \`\`\`
65
+
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="...")\`
103
69
  </instructions>
104
70
  `;
105
- function urgentNotification(unreadCount) {
106
- return `<system-reminder priority="critical">
107
- URGENT: You have ${unreadCount} unread message(s) in your IAM inbox.
108
- Use the iam tool with action="read" NOW to check your messages before continuing other work.
109
- </system-reminder>`;
110
- }
111
71
 
112
72
  // logger.ts
113
73
  import * as fs from "fs";
114
74
  import * as path from "path";
115
- var __dirname = "/home/spoon/.config/opencode/plugin/iam";
116
- var IS_DEV = fs.existsSync(path.join(__dirname, "..", ".git"));
117
75
  var LOG_DIR = path.join(process.cwd(), ".logs");
118
76
  var LOG_FILE = path.join(LOG_DIR, "iam.log");
119
- if (IS_DEV) {
77
+ var WRITE_INTERVAL_MS = 100;
78
+ var logBuffer = [];
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 {}
86
+ function formatTimestamp() {
87
+ return new Date().toISOString();
88
+ }
89
+ async function flushLogs() {
90
+ if (logBuffer.length === 0) {
91
+ writeScheduled = false;
92
+ return;
93
+ }
94
+ const toWrite = logBuffer.join("");
95
+ logBuffer = [];
96
+ writeScheduled = false;
120
97
  try {
121
- if (!fs.existsSync(LOG_DIR)) {
122
- fs.mkdirSync(LOG_DIR, { recursive: true });
123
- }
124
- fs.writeFileSync(LOG_FILE, "");
98
+ await fs.promises.appendFile(LOG_FILE, toWrite);
125
99
  } catch {}
126
100
  }
127
- function formatTimestamp() {
128
- return new Date().toISOString();
101
+ function scheduleFlush() {
102
+ if (!writeScheduled) {
103
+ writeScheduled = true;
104
+ setTimeout(flushLogs, WRITE_INTERVAL_MS);
105
+ }
129
106
  }
130
107
  function writeLog(level, category, message, data) {
131
- if (!IS_DEV)
132
- return;
133
108
  const timestamp = formatTimestamp();
134
109
  const dataStr = data !== undefined ? ` | ${JSON.stringify(data)}` : "";
135
110
  const logLine = `[${timestamp}] [${level}] [${category}] ${message}${dataStr}
136
111
  `;
137
- try {
138
- fs.appendFileSync(LOG_FILE, logLine);
139
- } catch {}
112
+ logBuffer.push(logLine);
113
+ scheduleFlush();
140
114
  }
141
115
  var log = {
142
116
  debug: (category, message, data) => writeLog("DEBUG", category, message, data),
143
117
  info: (category, message, data) => writeLog("INFO", category, message, data),
144
118
  warn: (category, message, data) => writeLog("WARN", category, message, data),
145
- error: (category, message, data) => writeLog("ERROR", category, message, data)
119
+ error: (category, message, data) => writeLog("ERROR", category, message, data),
120
+ flush: flushLogs
146
121
  };
147
122
  var LOG = {
148
123
  TOOL: "TOOL",
@@ -153,18 +128,76 @@ var LOG = {
153
128
  };
154
129
 
155
130
  // index.ts
131
+ var CHAR_CODE_A = 65;
132
+ var ALPHABET_SIZE = 26;
133
+ var MAX_DESCRIPTION_LENGTH = 100;
134
+ var MESSAGE_TTL_MS = 30 * 60 * 1000;
135
+ var UNHANDLED_TTL_MS = 2 * 60 * 60 * 1000;
136
+ var MAX_INBOX_SIZE = 100;
137
+ var PARENT_CACHE_TTL_MS = 5 * 60 * 1000;
138
+ var CLEANUP_INTERVAL_MS = 60 * 1000;
139
+ var DEFAULT_MODEL_ID = "gpt-4o-2024-08-06";
140
+ var DEFAULT_PROVIDER_ID = "openai";
141
+ var MAX_MESSAGE_LENGTH = 1e4;
156
142
  var inboxes = new Map;
143
+ var sessionMsgCounter = new Map;
157
144
  var activeSessions = new Set;
158
145
  var sessionToAlias = new Map;
159
146
  var aliasToSession = new Map;
160
147
  var agentDescriptions = new Map;
161
148
  var nextAgentIndex = 0;
162
- var instructedSessions = new Set;
149
+ var registeringSessionsLock = new Set;
163
150
  var sessionParentCache = new Map;
151
+ function cleanupExpiredMessages() {
152
+ const now = Date.now();
153
+ let totalRemoved = 0;
154
+ for (const [sessionId, messages] of inboxes) {
155
+ const before = messages.length;
156
+ const filtered = messages.filter((m) => {
157
+ if (m.handled) {
158
+ return now - m.timestamp < MESSAGE_TTL_MS;
159
+ }
160
+ return now - m.timestamp < UNHANDLED_TTL_MS;
161
+ });
162
+ if (filtered.length > MAX_INBOX_SIZE) {
163
+ const unhandled = filtered.filter((m) => !m.handled);
164
+ const handled = filtered.filter((m) => m.handled);
165
+ handled.sort((a, b) => b.timestamp - a.timestamp);
166
+ if (unhandled.length > MAX_INBOX_SIZE) {
167
+ unhandled.sort((a, b) => b.timestamp - a.timestamp);
168
+ inboxes.set(sessionId, unhandled.slice(0, MAX_INBOX_SIZE));
169
+ totalRemoved += before - MAX_INBOX_SIZE;
170
+ } else {
171
+ const kept = [
172
+ ...unhandled,
173
+ ...handled.slice(0, MAX_INBOX_SIZE - unhandled.length)
174
+ ];
175
+ inboxes.set(sessionId, kept);
176
+ totalRemoved += before - kept.length;
177
+ }
178
+ } else {
179
+ inboxes.set(sessionId, filtered);
180
+ totalRemoved += before - filtered.length;
181
+ }
182
+ if (inboxes.get(sessionId).length === 0) {
183
+ inboxes.delete(sessionId);
184
+ }
185
+ }
186
+ for (const [sessionId, cached] of sessionParentCache) {
187
+ if (now - cached.cachedAt > PARENT_CACHE_TTL_MS) {
188
+ sessionParentCache.delete(sessionId);
189
+ }
190
+ }
191
+ if (totalRemoved > 0) {
192
+ log.debug(LOG.MESSAGE, `Cleanup removed ${totalRemoved} expired messages`);
193
+ }
194
+ }
195
+ setInterval(cleanupExpiredMessages, CLEANUP_INTERVAL_MS);
164
196
  function getNextAlias() {
165
- const letter = String.fromCharCode(65 + nextAgentIndex % 26);
166
- const suffix = nextAgentIndex >= 26 ? Math.floor(nextAgentIndex / 26).toString() : "";
197
+ const index = nextAgentIndex;
167
198
  nextAgentIndex++;
199
+ const letter = String.fromCharCode(CHAR_CODE_A + index % ALPHABET_SIZE);
200
+ const suffix = index >= ALPHABET_SIZE ? Math.floor(index / ALPHABET_SIZE).toString() : "";
168
201
  return `agent${letter}${suffix}`;
169
202
  }
170
203
  function getAlias(sessionId) {
@@ -172,22 +205,28 @@ function getAlias(sessionId) {
172
205
  }
173
206
  function setDescription(sessionId, description) {
174
207
  const alias = getAlias(sessionId);
175
- agentDescriptions.set(alias, description);
176
- log.info(LOG.SESSION, `Agent announced`, { alias, description });
208
+ const truncated = description.substring(0, MAX_DESCRIPTION_LENGTH);
209
+ agentDescriptions.set(alias, truncated);
210
+ log.info(LOG.SESSION, `Agent announced`, { alias, description: truncated });
177
211
  }
178
212
  function getDescription(alias) {
179
213
  return agentDescriptions.get(alias);
180
214
  }
181
- function hasAnnounced(sessionId) {
182
- const alias = getAlias(sessionId);
183
- return agentDescriptions.has(alias);
184
- }
185
- function resolveAlias(aliasOrSessionId) {
215
+ function resolveAlias(aliasOrSessionId, parentId) {
216
+ if (aliasOrSessionId === "parent" && parentId) {
217
+ return parentId;
218
+ }
186
219
  return aliasToSession.get(aliasOrSessionId) || (activeSessions.has(aliasOrSessionId) ? aliasOrSessionId : undefined);
187
220
  }
188
221
  function generateId() {
189
222
  return Math.random().toString(36).substring(2, 10);
190
223
  }
224
+ function getNextMsgIndex(sessionId) {
225
+ const current = sessionMsgCounter.get(sessionId) || 0;
226
+ const next = current + 1;
227
+ sessionMsgCounter.set(sessionId, next);
228
+ return next;
229
+ }
191
230
  function getInbox(sessionId) {
192
231
  if (!inboxes.has(sessionId)) {
193
232
  inboxes.set(sessionId, []);
@@ -197,152 +236,326 @@ function getInbox(sessionId) {
197
236
  function sendMessage(from, to, body) {
198
237
  const message = {
199
238
  id: generateId(),
239
+ msgIndex: getNextMsgIndex(to),
200
240
  from,
201
241
  to,
202
242
  body,
203
243
  timestamp: Date.now(),
204
- read: false
244
+ handled: false
205
245
  };
206
- getInbox(to).push(message);
207
- log.info(LOG.MESSAGE, `Message sent`, { id: message.id, from, to, bodyLength: body.length });
246
+ const queue = getInbox(to);
247
+ if (queue.length >= MAX_INBOX_SIZE) {
248
+ const handledIndex = queue.findIndex((m) => m.handled);
249
+ if (handledIndex !== -1) {
250
+ queue.splice(handledIndex, 1);
251
+ } else {
252
+ queue.shift();
253
+ }
254
+ log.warn(LOG.MESSAGE, `Queue full, removed oldest message`, { to });
255
+ }
256
+ queue.push(message);
257
+ log.info(LOG.MESSAGE, `Message sent`, {
258
+ id: message.id,
259
+ msgIndex: message.msgIndex,
260
+ from,
261
+ to,
262
+ bodyLength: body.length
263
+ });
208
264
  return message;
209
265
  }
210
- function getUnreadMessages(sessionId) {
211
- return getInbox(sessionId).filter((m) => !m.read);
212
- }
213
- function getAllMessages(sessionId) {
214
- return getInbox(sessionId);
266
+ function getUnhandledMessages(sessionId) {
267
+ return getInbox(sessionId).filter((m) => !m.handled);
215
268
  }
216
- function markAllRead(sessionId) {
217
- const iam = getInbox(sessionId);
218
- const unreadCount = iam.filter((m) => !m.read).length;
219
- for (const msg of iam) {
220
- msg.read = true;
269
+ function markMessagesAsHandled(sessionId, msgIndices) {
270
+ const queue = getInbox(sessionId);
271
+ const handled = [];
272
+ for (const msg of queue) {
273
+ if (msgIndices.includes(msg.msgIndex) && !msg.handled) {
274
+ msg.handled = true;
275
+ handled.push({
276
+ id: msg.msgIndex,
277
+ from: msg.from,
278
+ body: msg.body
279
+ });
280
+ log.info(LOG.MESSAGE, `Message marked as handled`, {
281
+ sessionId,
282
+ msgIndex: msg.msgIndex,
283
+ from: msg.from
284
+ });
285
+ }
221
286
  }
222
- log.info(LOG.MESSAGE, `Marked all read`, { sessionId, count: unreadCount });
287
+ return handled;
223
288
  }
224
- function getKnownAgents(sessionId) {
289
+ function getKnownAliases(sessionId) {
290
+ const selfAlias = sessionToAlias.get(sessionId);
225
291
  const agents = [];
226
- for (const id of activeSessions) {
227
- if (id !== sessionId) {
228
- agents.push(getAlias(id));
292
+ for (const alias of aliasToSession.keys()) {
293
+ if (alias !== selfAlias) {
294
+ agents.push(alias);
229
295
  }
230
296
  }
231
297
  return agents;
232
298
  }
233
299
  function getParallelAgents(sessionId) {
234
- return getKnownAgents(sessionId).map((alias) => ({
235
- alias,
236
- description: getDescription(alias)
237
- }));
300
+ const selfAlias = sessionToAlias.get(sessionId);
301
+ const agents = [];
302
+ for (const [alias, sessId] of aliasToSession.entries()) {
303
+ if (alias !== selfAlias) {
304
+ agents.push({
305
+ alias,
306
+ description: getDescription(alias)
307
+ });
308
+ }
309
+ }
310
+ return agents;
238
311
  }
239
312
  function registerSession(sessionId) {
240
- if (!activeSessions.has(sessionId)) {
241
- activeSessions.add(sessionId);
242
- const alias = getNextAlias();
243
- sessionToAlias.set(sessionId, alias);
244
- aliasToSession.set(alias, sessionId);
245
- log.info(LOG.SESSION, `Session registered`, { sessionId, alias, totalSessions: activeSessions.size });
313
+ if (activeSessions.has(sessionId)) {
314
+ return;
315
+ }
316
+ if (registeringSessionsLock.has(sessionId)) {
317
+ return;
318
+ }
319
+ registeringSessionsLock.add(sessionId);
320
+ try {
321
+ if (!activeSessions.has(sessionId)) {
322
+ activeSessions.add(sessionId);
323
+ const alias = getNextAlias();
324
+ sessionToAlias.set(sessionId, alias);
325
+ aliasToSession.set(alias, sessionId);
326
+ log.info(LOG.SESSION, `Session registered`, {
327
+ sessionId,
328
+ alias,
329
+ totalSessions: activeSessions.size
330
+ });
331
+ }
332
+ } finally {
333
+ registeringSessionsLock.delete(sessionId);
246
334
  }
247
335
  }
248
336
  async function getParentId(client, sessionId) {
249
- if (sessionParentCache.has(sessionId)) {
250
- return sessionParentCache.get(sessionId);
337
+ const now = Date.now();
338
+ const cached = sessionParentCache.get(sessionId);
339
+ if (cached && now - cached.cachedAt < PARENT_CACHE_TTL_MS) {
340
+ return cached.value;
251
341
  }
252
342
  try {
253
343
  const response = await client.session.get({ path: { id: sessionId } });
254
344
  const parentId = response.data?.parentID || null;
255
- sessionParentCache.set(sessionId, parentId);
345
+ sessionParentCache.set(sessionId, { value: parentId, cachedAt: now });
256
346
  log.debug(LOG.SESSION, `Looked up parentID`, { sessionId, parentId });
257
347
  return parentId;
258
348
  } catch (e) {
259
- log.warn(LOG.SESSION, `Failed to get session info`, { sessionId, error: String(e) });
260
- sessionParentCache.set(sessionId, null);
349
+ log.warn(LOG.SESSION, `Failed to get session info`, {
350
+ sessionId,
351
+ error: String(e)
352
+ });
353
+ sessionParentCache.set(sessionId, {
354
+ value: null,
355
+ cachedAt: now - PARENT_CACHE_TTL_MS + 60000
356
+ });
261
357
  return null;
262
358
  }
263
359
  }
360
+ function createInboxMessage(sessionId, messages, baseUserMessage) {
361
+ const now = Date.now();
362
+ const userInfo = baseUserMessage.info;
363
+ const inboxMsgs = messages.map((m) => ({
364
+ id: m.msgIndex,
365
+ from: m.from,
366
+ body: m.body
367
+ }));
368
+ const assistantMessageId = `msg_broadcast_${now}`;
369
+ const partId = `prt_broadcast_${now}`;
370
+ const callId = `call_broadcast_${now}`;
371
+ log.debug(LOG.MESSAGE, `Creating bundled inbox message`, {
372
+ sessionId,
373
+ messageCount: messages.length,
374
+ msgIndices: messages.map((m) => m.msgIndex)
375
+ });
376
+ const result = {
377
+ info: {
378
+ id: assistantMessageId,
379
+ sessionID: sessionId,
380
+ role: "assistant",
381
+ agent: userInfo.agent || "code",
382
+ parentID: userInfo.id,
383
+ modelID: userInfo.model?.modelID || DEFAULT_MODEL_ID,
384
+ providerID: userInfo.model?.providerID || DEFAULT_PROVIDER_ID,
385
+ mode: "default",
386
+ path: { cwd: "/", root: "/" },
387
+ time: { created: now, completed: now },
388
+ cost: 0,
389
+ tokens: {
390
+ input: 0,
391
+ output: 0,
392
+ reasoning: 0,
393
+ cache: { read: 0, write: 0 }
394
+ }
395
+ },
396
+ parts: [
397
+ {
398
+ id: partId,
399
+ sessionID: sessionId,
400
+ messageID: assistantMessageId,
401
+ type: "tool",
402
+ callID: callId,
403
+ tool: "broadcast",
404
+ state: {
405
+ status: "completed",
406
+ input: { messages: inboxMsgs },
407
+ output: `${messages.length} message(s)`,
408
+ title: `Inbox (${messages.length} messages)`,
409
+ metadata: {
410
+ incoming_message: true,
411
+ message_count: messages.length
412
+ },
413
+ time: { start: now, end: now }
414
+ }
415
+ }
416
+ ]
417
+ };
418
+ if (userInfo.variant !== undefined) {
419
+ result.info.variant = userInfo.variant;
420
+ }
421
+ return result;
422
+ }
264
423
  var plugin = async (ctx) => {
265
424
  log.info(LOG.HOOK, "Plugin initialized");
266
425
  const client = ctx.client;
267
426
  return {
268
427
  tool: {
269
- iam: tool({
270
- description: TOOL_DESCRIPTION,
428
+ broadcast: tool({
429
+ description: BROADCAST_DESCRIPTION,
271
430
  args: {
272
- action: tool.schema.enum(["read", "broadcast", "announce"]).describe(ARG_DESCRIPTIONS.action),
273
- to: tool.schema.string().optional().describe(ARG_DESCRIPTIONS.to),
274
- message: tool.schema.string().optional().describe(ARG_DESCRIPTIONS.message)
431
+ recipient: tool.schema.string().optional().describe("Target agent(s), comma-separated. Omit to send to all."),
432
+ 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])")
275
434
  },
276
435
  async execute(args, context) {
277
436
  const sessionId = context.sessionID;
437
+ const isFirstCall = !activeSessions.has(sessionId);
278
438
  registerSession(sessionId);
279
439
  const alias = getAlias(sessionId);
280
- const announced = hasAnnounced(sessionId);
281
- log.debug(LOG.TOOL, `iam action: ${args.action}`, { sessionId, alias, args });
282
- switch (args.action) {
283
- case "read": {
284
- const messages = getAllMessages(sessionId);
285
- const unreadCount = messages.filter((m) => !m.read).length;
286
- log.debug(LOG.TOOL, `read inbox`, { alias, total: messages.length, unread: unreadCount });
287
- markAllRead(sessionId);
288
- return readResult(alias, messages, unreadCount, announced);
440
+ if (!args.message) {
441
+ log.warn(LOG.TOOL, `broadcast missing 'message'`, { alias });
442
+ return BROADCAST_MISSING_MESSAGE;
443
+ }
444
+ if (args.message.length > MAX_MESSAGE_LENGTH) {
445
+ log.warn(LOG.TOOL, `broadcast message too long`, {
446
+ alias,
447
+ length: args.message.length
448
+ });
449
+ return broadcastMessageTooLong(args.message.length, MAX_MESSAGE_LENGTH);
450
+ }
451
+ if (isFirstCall) {
452
+ setDescription(sessionId, args.message);
453
+ }
454
+ log.debug(LOG.TOOL, `broadcast called`, {
455
+ sessionId,
456
+ alias,
457
+ recipient: args.recipient,
458
+ reply_to: args.reply_to,
459
+ messageLength: args.message.length,
460
+ isFirstCall
461
+ });
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`, {
466
+ alias,
467
+ requested: args.reply_to,
468
+ actuallyHandled: handledMessages.length
469
+ });
470
+ }
471
+ const knownAgents = getKnownAliases(sessionId);
472
+ const parallelAgents = getParallelAgents(sessionId);
473
+ let targetAliases;
474
+ if (!args.recipient) {
475
+ targetAliases = knownAgents;
476
+ } else {
477
+ targetAliases = args.recipient.split(",").map((s) => s.trim()).filter(Boolean);
478
+ }
479
+ if (targetAliases.length === 0) {
480
+ log.info(LOG.TOOL, `No recipients, returning agent info`, {
481
+ alias
482
+ });
483
+ return broadcastResult(alias, [], parallelAgents, handledMessages);
484
+ }
485
+ const parentId = await getParentId(client, sessionId);
486
+ const recipientSessions = [];
487
+ const validTargets = [];
488
+ for (const targetAlias of targetAliases) {
489
+ const recipientSessionId = resolveAlias(targetAlias, parentId);
490
+ if (!recipientSessionId) {
491
+ log.warn(LOG.TOOL, `broadcast unknown recipient`, {
492
+ alias,
493
+ to: targetAlias
494
+ });
495
+ return broadcastUnknownRecipient(targetAlias, knownAgents);
289
496
  }
290
- case "broadcast": {
291
- if (!args.message) {
292
- log.warn(LOG.TOOL, `broadcast missing 'message'`, { alias });
293
- return BROADCAST_MISSING_MESSAGE;
294
- }
295
- const knownAgents = getKnownAgents(sessionId);
296
- let targetAliases;
297
- if (!args.to || args.to.toLowerCase() === "all") {
298
- targetAliases = knownAgents;
299
- } else {
300
- targetAliases = args.to.split(",").map((s) => s.trim()).filter(Boolean);
497
+ if (recipientSessionId === sessionId) {
498
+ log.warn(LOG.TOOL, `Skipping self-message`, {
499
+ alias,
500
+ targetAlias
501
+ });
502
+ if (targetAliases.length === 1) {
503
+ return BROADCAST_SELF_MESSAGE;
301
504
  }
302
- if (targetAliases.length === 0) {
303
- return `No agents to broadcast to. Use action="announce" to see parallel agents.`;
304
- }
305
- const recipientSessions = [];
306
- for (const targetAlias of targetAliases) {
307
- const recipientSessionId = resolveAlias(targetAlias);
308
- if (!recipientSessionId) {
309
- log.warn(LOG.TOOL, `broadcast unknown recipient`, { alias, to: targetAlias });
310
- return broadcastUnknownRecipient(targetAlias, knownAgents);
311
- }
312
- recipientSessions.push(recipientSessionId);
313
- }
314
- let messageId = "";
315
- for (const recipientSessionId of recipientSessions) {
316
- const msg = sendMessage(alias, recipientSessionId, args.message);
317
- messageId = msg.id;
318
- }
319
- return broadcastResult(targetAliases, messageId);
505
+ continue;
320
506
  }
321
- case "announce": {
322
- if (!args.message) {
323
- log.warn(LOG.TOOL, `announce missing 'message'`, { alias });
324
- return `Error: 'message' parameter is required for action="announce". Describe what you're working on.`;
507
+ recipientSessions.push(recipientSessionId);
508
+ validTargets.push(targetAlias);
509
+ }
510
+ if (recipientSessions.length === 0) {
511
+ log.info(LOG.TOOL, `No valid recipients after filtering`, {
512
+ alias
513
+ });
514
+ return broadcastResult(alias, [], parallelAgents, handledMessages);
515
+ }
516
+ const isTargetingParent = parentId && recipientSessions.includes(parentId);
517
+ for (const recipientSessionId of recipientSessions) {
518
+ sendMessage(alias, recipientSessionId, args.message);
519
+ }
520
+ if (isTargetingParent) {
521
+ log.info(LOG.MESSAGE, `Broadcasting to parent session, calling notify_once`, { sessionId, parentId });
522
+ try {
523
+ const internalClient = client._client;
524
+ if (internalClient?.post) {
525
+ await internalClient.post({
526
+ url: `/session/${parentId}/notify_once`,
527
+ body: {
528
+ text: `[IAM] Message from ${alias}: ${args.message}`
529
+ }
530
+ });
531
+ log.info(LOG.MESSAGE, `Parent session notified successfully`, {
532
+ parentId
533
+ });
325
534
  }
326
- setDescription(sessionId, args.message);
327
- const parallelAgents = getParallelAgents(sessionId);
328
- return announceResult(alias, parallelAgents);
535
+ } catch (e) {
536
+ log.warn(LOG.MESSAGE, `Failed to notify parent session`, {
537
+ parentId,
538
+ error: String(e)
539
+ });
329
540
  }
330
- default:
331
- return unknownAction(args.action);
332
541
  }
542
+ return broadcastResult(alias, validTargets, parallelAgents, handledMessages);
333
543
  }
334
544
  })
335
545
  },
336
546
  "tool.execute.after": async (input, output) => {
337
- log.debug(LOG.HOOK, `tool.execute.after fired`, { tool: input.tool, sessionID: input.sessionID, hasMetadata: !!output.metadata });
547
+ log.debug(LOG.HOOK, `tool.execute.after fired`, {
548
+ tool: input.tool,
549
+ sessionID: input.sessionID,
550
+ hasMetadata: !!output.metadata
551
+ });
338
552
  if (input.tool === "task") {
339
- log.debug(LOG.HOOK, `task metadata`, { metadata: output.metadata, output: output.output?.substring?.(0, 200) });
340
553
  const newSessionId = output.metadata?.sessionId || output.metadata?.session_id;
341
554
  if (newSessionId) {
342
- log.info(LOG.HOOK, `task completed, registering session`, { newSessionId });
555
+ log.info(LOG.HOOK, `task completed, ensuring session registered`, {
556
+ newSessionId
557
+ });
343
558
  registerSession(newSessionId);
344
- } else {
345
- log.warn(LOG.HOOK, `task completed but no session_id in metadata`);
346
559
  }
347
560
  }
348
561
  },
@@ -352,43 +565,63 @@ var plugin = async (ctx) => {
352
565
  log.debug(LOG.INJECT, `No sessionID in system.transform input, skipping`);
353
566
  return;
354
567
  }
355
- const parentId = await getParentId(client, sessionId);
356
- if (!parentId) {
357
- log.debug(LOG.INJECT, `Skipping system prompt injection for main session (no parentID)`, { sessionId });
568
+ try {
569
+ const result = await client.session.get({ path: { id: sessionId } });
570
+ if (!result.data?.parentID) {
571
+ log.debug(LOG.INJECT, `Session has no parentID (main session), skipping IAM`, {
572
+ sessionId
573
+ });
574
+ return;
575
+ }
576
+ } catch (e) {
577
+ log.debug(LOG.INJECT, `Failed to get session info, skipping IAM`, {
578
+ sessionId,
579
+ error: String(e)
580
+ });
358
581
  return;
359
582
  }
583
+ registerSession(sessionId);
360
584
  output.system.push(SYSTEM_PROMPT);
361
- log.info(LOG.INJECT, `Injected IAM system prompt for child session`, { sessionId, parentId });
585
+ log.info(LOG.INJECT, `Registered session and injected IAM system prompt`, {
586
+ sessionId,
587
+ alias: getAlias(sessionId)
588
+ });
362
589
  },
363
590
  "experimental.chat.messages.transform": async (_input, output) => {
364
591
  const lastUserMsg = [...output.messages].reverse().find((m) => m.info.role === "user");
365
- if (!lastUserMsg)
592
+ if (!lastUserMsg) {
593
+ log.debug(LOG.INJECT, `No user message found in transform, skipping IAM injection`);
366
594
  return;
595
+ }
367
596
  const sessionId = lastUserMsg.info.sessionID;
368
- const unread = getUnreadMessages(sessionId);
369
- if (unread.length === 0)
597
+ const unhandled = getUnhandledMessages(sessionId);
598
+ log.debug(LOG.INJECT, `Checking for messages in transform`, {
599
+ sessionId,
600
+ unhandledCount: unhandled.length
601
+ });
602
+ if (unhandled.length === 0) {
370
603
  return;
371
- log.info(LOG.INJECT, `Injecting urgent notification`, { sessionId, unreadCount: unread.length });
372
- const syntheticMessage = {
373
- info: {
374
- id: "msg_iam_" + Date.now(),
375
- sessionID: sessionId,
376
- role: "user",
377
- time: { created: Date.now() },
378
- agent: lastUserMsg.info.agent || "code",
379
- model: lastUserMsg.info.model
380
- },
381
- parts: [
382
- {
383
- id: "prt_iam_" + Date.now(),
384
- sessionID: sessionId,
385
- messageID: "msg_iam_" + Date.now(),
386
- type: "text",
387
- text: urgentNotification(unread.length)
388
- }
389
- ]
604
+ }
605
+ log.info(LOG.INJECT, `Injecting bundled inbox message`, {
606
+ sessionId,
607
+ unhandledCount: unhandled.length,
608
+ msgIndices: unhandled.map((m) => m.msgIndex)
609
+ });
610
+ const inboxMsg = createInboxMessage(sessionId, unhandled, lastUserMsg);
611
+ output.messages.push(inboxMsg);
612
+ log.info(LOG.INJECT, `Injected inbox at end of message chain`, {
613
+ sessionId,
614
+ messageCount: unhandled.length
615
+ });
616
+ },
617
+ "experimental.config.transform": async (_input, output) => {
618
+ const experimental = output.experimental ?? {};
619
+ const existingSubagentTools = experimental.subagent_tools ?? [];
620
+ output.experimental = {
621
+ ...experimental,
622
+ subagent_tools: [...existingSubagentTools, "broadcast"]
390
623
  };
391
- output.messages.push(syntheticMessage);
624
+ log.info(LOG.HOOK, `Added 'broadcast' to experimental.subagent_tools`);
392
625
  }
393
626
  };
394
627
  };
package/dist/logger.d.ts CHANGED
@@ -1,8 +1,14 @@
1
+ /**
2
+ * Flush buffered logs to file asynchronously
3
+ */
4
+ declare function flushLogs(): Promise<void>;
1
5
  export declare const log: {
2
6
  debug: (category: string, message: string, data?: unknown) => void;
3
7
  info: (category: string, message: string, data?: unknown) => void;
4
8
  warn: (category: string, message: string, data?: unknown) => void;
5
9
  error: (category: string, message: string, data?: unknown) => void;
10
+ /** Force immediate flush (useful before process exit) */
11
+ flush: typeof flushLogs;
6
12
  };
7
13
  export declare const LOG: {
8
14
  readonly TOOL: "TOOL";
@@ -11,4 +17,5 @@ export declare const LOG: {
11
17
  readonly HOOK: "HOOK";
12
18
  readonly INJECT: "INJECT";
13
19
  };
20
+ export {};
14
21
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../logger.ts"],"names":[],"mappings":"AA+CA,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;CAE1D,CAAA;AAGD,eAAO,MAAM,GAAG;;;;;;CAMN,CAAA"}
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"}
package/dist/prompt.d.ts CHANGED
@@ -1,25 +1,17 @@
1
- export declare const TOOL_DESCRIPTION = "Inter-agent messaging. Use this to communicate with other parallel agents (task tools).\n\nActions:\n- \"announce\": Announce what you're working on (do this first!). Shows all parallel agents. You can re-announce to update your status.\n- \"read\": Read your messages (marks them as read)\n- \"broadcast\": Send a message (requires 'message', optional 'to')";
2
- export declare const ARG_DESCRIPTIONS: {
3
- readonly action: "Action to perform";
4
- readonly to: "Recipient(s): 'agentA', 'agentA,agentC', or 'all' (default: all)";
5
- readonly message: "Your announcement or message content";
6
- };
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.";
7
2
  export interface ParallelAgent {
8
3
  alias: string;
9
4
  description?: string;
10
5
  }
11
- export declare function formatAgentList(agents: ParallelAgent[]): string[];
12
- export declare function readResult(alias: string, messages: {
6
+ export interface HandledMessage {
7
+ id: number;
13
8
  from: string;
14
9
  body: string;
15
- timestamp: number;
16
- read: boolean;
17
- }[], unreadCount: number, hasAnnounced: boolean): string;
18
- export declare function announceResult(alias: string, parallelAgents: ParallelAgent[]): string;
19
- export declare const BROADCAST_MISSING_MESSAGE = "Error: 'message' parameter is required for action=\"broadcast\".";
20
- export declare function broadcastUnknownRecipient(to: string, known: string[]): string;
21
- export declare function broadcastResult(recipients: string[], messageId: string): string;
22
- export declare function unknownAction(action: string): string;
23
- export declare const SYSTEM_PROMPT = "\n<instructions tool=\"iam\">\n# Inter-Agent Messaging\n\nYou have access to an `iam` tool for communicating with other parallel agents.\n\nUsage:\n- action=\"announce\", message=\"...\" - Announce what you're working on (do this first!)\n- action=\"read\" - Read your messages\n- action=\"broadcast\", message=\"...\" - Message all agents\n- action=\"broadcast\", to=\"agentA\", message=\"...\" - Message specific agent(s)\n\nAt the start of your task, use announce to let other agents know what you're doing.\nYou can re-announce to update your status as your task evolves.\nCheck your inbox when notified about new messages.\n\nWhen you complete your task, broadcast to all: \"Done. Here's what I found/did: ...\"\n</instructions>\n";
24
- export declare function urgentNotification(unreadCount: number): string;
10
+ }
11
+ export declare function broadcastResult(alias: string, recipients: string[], parallelAgents: ParallelAgent[], handledMessages: HandledMessage[]): string;
12
+ export declare const BROADCAST_MISSING_MESSAGE = "Error: 'message' parameter is required.";
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
+ 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";
25
17
  //# sourceMappingURL=prompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,2WAKqC,CAAC;AAEnE,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAMX,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAUjE;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,EAC5E,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,OAAO,GACpB,MAAM,CA0BR;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,MAAM,CAmBrF;AAED,eAAO,MAAM,yBAAyB,qEAAmE,CAAC;AAE1G,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAG7E;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG/E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAMD,eAAO,MAAM,aAAa,ouBAkBzB,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoons-and-mirrors/iam",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Inter-agent messaging for OpenCode parallel subagents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,12 +29,13 @@
29
29
  ],
30
30
  "license": "MIT",
31
31
  "peerDependencies": {
32
- "@opencode-ai/plugin": ">=0.13.7"
32
+ "@opencode-ai/plugin": "^1.0.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@opencode-ai/plugin": "^1.0.143",
36
36
  "@types/node": "^24.10.1",
37
37
  "bun-types": "^1.3.5",
38
+ "prettier": "^3.7.4",
38
39
  "typescript": "^5.9.3"
39
40
  }
40
41
  }