@spoons-and-mirrors/iam 0.1.0

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 ADDED
@@ -0,0 +1,40 @@
1
+ # IAM Plugin for OpenCode
2
+
3
+ Inter-agent messaging for parallel subagents.
4
+
5
+ ## The `iam` Tool
6
+
7
+ | Action | Args | Description |
8
+ |--------|------|-------------|
9
+ | `sessions` | - | List agents you can message |
10
+ | `read` | - | Read your messages (marks as read) |
11
+ | `write` | `--to <id> --message "..."` | Send a message |
12
+
13
+ ## Examples
14
+
15
+ ```bash
16
+ iam sessions
17
+ iam read
18
+ iam write --to ses_abc123 --message "What approach are you using?"
19
+ ```
20
+
21
+ ## How It Works
22
+
23
+ - **In-memory** - fast, no file clutter, resets on restart
24
+ - **Auto-discovery** - agents register on first iam use, see each other immediately
25
+ - **Urgent alerts** - recipients get `<system-reminder>` when they have unread mail
26
+
27
+ ## Files
28
+
29
+ ```
30
+ iam/
31
+ ├── index.ts # Plugin + tool + hooks
32
+ ├── prompt.ts # LLM-facing prompts
33
+ ├── logger.ts # Logs to .logs/iam.log
34
+ ├── PROGRESS.md # Development history
35
+ └── README.md
36
+ ```
37
+
38
+ ## Logs
39
+
40
+ Debug logs written to `.logs/iam.log` (clears on restart). Shows all tool calls, messages sent, sessions registered, and notifications injected.
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const plugin: Plugin;
3
+ export default plugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA0GjD,QAAA,MAAM,MAAM,EAAE,MAwIb,CAAA;AAED,eAAe,MAAM,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,274 @@
1
+ // index.ts
2
+ import { tool } from "@opencode-ai/plugin";
3
+
4
+ // prompt.ts
5
+ var TOOL_DESCRIPTION = `Inter-agent messaging. Use this to communicate with other parallel agents (task tools).
6
+
7
+ Actions:
8
+ - "sessions": Show session IDs of agents you can message
9
+ - "read": Read all your messages (marks them as read)
10
+ - "write": Send a message to another agent`;
11
+ var ARG_DESCRIPTIONS = {
12
+ action: "Action to perform",
13
+ to: "Recipient session ID (required for 'write')",
14
+ message: "Message content (required for 'write')"
15
+ };
16
+ var SESSIONS_EMPTY = `No other agents available yet.
17
+
18
+ Agents will appear here when:
19
+ - Parallel tasks are spawned by the same parent
20
+ - Another agent sends you a message`;
21
+ function sessionsResult(agents) {
22
+ return [
23
+ `Agents you can message:
24
+ `,
25
+ ...agents.map((id) => `- ${id}`),
26
+ ``,
27
+ `Use: iam write --to <session_id> --message "..."`
28
+ ].join(`
29
+ `);
30
+ }
31
+ var READ_EMPTY = `No messages in your iam.`;
32
+ function readResult(messages, unreadCount) {
33
+ const lines = [`Your iam (${unreadCount} were unread):
34
+ `, `---`];
35
+ for (const msg of messages) {
36
+ const time = new Date(msg.timestamp).toISOString();
37
+ const status = msg.read ? "" : " [NEW]";
38
+ lines.push(`[${time}] From: ${msg.from}${status}`);
39
+ lines.push(msg.body);
40
+ lines.push(`---`);
41
+ }
42
+ lines.push(``);
43
+ lines.push(`To reply: iam write --to <sender_session_id> --message "..."`);
44
+ return lines.join(`
45
+ `);
46
+ }
47
+ var WRITE_MISSING_TO = `Error: 'to' is required. Use: iam write --to <session_id> --message "..."`;
48
+ var WRITE_MISSING_MESSAGE = `Error: 'message' is required. Use: iam write --to <session_id> --message "..."`;
49
+ function writeResult(to, messageId) {
50
+ return `Message sent!
51
+
52
+ To: ${to}
53
+ Message ID: ${messageId}
54
+
55
+ The recipient will be notified.`;
56
+ }
57
+ function unknownAction(action) {
58
+ return `Unknown action: ${action}`;
59
+ }
60
+ var SYSTEM_PROMPT = `
61
+ # Inter-Agent Messaging
62
+
63
+ You have access to an \`iam\` tool for communicating with other parallel agents.
64
+
65
+ Commands:
66
+ - \`iam sessions\` - See agents you can message
67
+ - \`iam read\` - Read all your messages
68
+ - \`iam write --to <session_id> --message "..."\` - Send a message
69
+
70
+ Check your iam when notified about new messages.
71
+ `;
72
+ function urgentNotification(unreadCount) {
73
+ return `<system-reminder priority="critical">
74
+ URGENT: You have ${unreadCount} unread message(s) in your iam.
75
+ Use \`iam read\` NOW to check your messages before continuing other work.
76
+ </system-reminder>`;
77
+ }
78
+
79
+ // logger.ts
80
+ import * as fs from "fs";
81
+ import * as path from "path";
82
+ var LOG_DIR = path.join(process.cwd(), ".logs");
83
+ var LOG_FILE = path.join(LOG_DIR, "iam.log");
84
+ try {
85
+ if (!fs.existsSync(LOG_DIR)) {
86
+ fs.mkdirSync(LOG_DIR, { recursive: true });
87
+ }
88
+ fs.writeFileSync(LOG_FILE, "");
89
+ } catch {}
90
+ function formatTimestamp() {
91
+ return new Date().toISOString();
92
+ }
93
+ function writeLog(level, category, message, data) {
94
+ const timestamp = formatTimestamp();
95
+ const dataStr = data !== undefined ? ` | ${JSON.stringify(data)}` : "";
96
+ const logLine = `[${timestamp}] [${level}] [${category}] ${message}${dataStr}
97
+ `;
98
+ try {
99
+ fs.appendFileSync(LOG_FILE, logLine);
100
+ } catch {}
101
+ }
102
+ var log = {
103
+ debug: (category, message, data) => writeLog("DEBUG", category, message, data),
104
+ info: (category, message, data) => writeLog("INFO", category, message, data),
105
+ warn: (category, message, data) => writeLog("WARN", category, message, data),
106
+ error: (category, message, data) => writeLog("ERROR", category, message, data)
107
+ };
108
+ var LOG = {
109
+ TOOL: "TOOL",
110
+ MESSAGE: "MESSAGE",
111
+ SESSION: "SESSION",
112
+ HOOK: "HOOK",
113
+ INJECT: "INJECT"
114
+ };
115
+
116
+ // index.ts
117
+ var inboxes = new Map;
118
+ var activeSessions = new Set;
119
+ function generateId() {
120
+ return Math.random().toString(36).substring(2, 10);
121
+ }
122
+ function getInbox(sessionId) {
123
+ if (!inboxes.has(sessionId)) {
124
+ inboxes.set(sessionId, []);
125
+ }
126
+ return inboxes.get(sessionId);
127
+ }
128
+ function sendMessage(from, to, body) {
129
+ const message = {
130
+ id: generateId(),
131
+ from,
132
+ to,
133
+ body,
134
+ timestamp: Date.now(),
135
+ read: false
136
+ };
137
+ getInbox(to).push(message);
138
+ log.info(LOG.MESSAGE, `Message sent`, { id: message.id, from, to, bodyLength: body.length });
139
+ return message;
140
+ }
141
+ function getUnreadMessages(sessionId) {
142
+ return getInbox(sessionId).filter((m) => !m.read);
143
+ }
144
+ function getAllMessages(sessionId) {
145
+ return getInbox(sessionId);
146
+ }
147
+ function markAllRead(sessionId) {
148
+ const iam = getInbox(sessionId);
149
+ const unreadCount = iam.filter((m) => !m.read).length;
150
+ for (const msg of iam) {
151
+ msg.read = true;
152
+ }
153
+ log.info(LOG.MESSAGE, `Marked all read`, { sessionId, count: unreadCount });
154
+ }
155
+ function getKnownAgents(sessionId) {
156
+ const agents = [];
157
+ for (const id of activeSessions) {
158
+ if (id !== sessionId) {
159
+ agents.push(id);
160
+ }
161
+ }
162
+ return agents;
163
+ }
164
+ function registerSession(sessionId) {
165
+ if (!activeSessions.has(sessionId)) {
166
+ activeSessions.add(sessionId);
167
+ log.info(LOG.SESSION, `Session registered`, { sessionId, totalSessions: activeSessions.size });
168
+ }
169
+ }
170
+ var plugin = async () => {
171
+ log.info(LOG.HOOK, "Plugin initialized");
172
+ return {
173
+ tool: {
174
+ iam: tool({
175
+ description: TOOL_DESCRIPTION,
176
+ args: {
177
+ action: tool.schema.enum(["sessions", "read", "write"]).describe(ARG_DESCRIPTIONS.action),
178
+ to: tool.schema.string().optional().describe(ARG_DESCRIPTIONS.to),
179
+ message: tool.schema.string().optional().describe(ARG_DESCRIPTIONS.message)
180
+ },
181
+ async execute(args, context) {
182
+ const sessionId = context.sessionID;
183
+ registerSession(sessionId);
184
+ log.debug(LOG.TOOL, `iam action: ${args.action}`, { sessionId, args });
185
+ switch (args.action) {
186
+ case "sessions": {
187
+ const agents = getKnownAgents(sessionId);
188
+ log.debug(LOG.TOOL, `sessions result`, { sessionId, agentCount: agents.length, agents });
189
+ if (agents.length === 0) {
190
+ return SESSIONS_EMPTY;
191
+ }
192
+ return sessionsResult(agents);
193
+ }
194
+ case "read": {
195
+ const messages = getAllMessages(sessionId);
196
+ const unreadCount = messages.filter((m) => !m.read).length;
197
+ log.debug(LOG.TOOL, `read iam`, { sessionId, total: messages.length, unread: unreadCount });
198
+ markAllRead(sessionId);
199
+ if (messages.length === 0) {
200
+ return READ_EMPTY;
201
+ }
202
+ return readResult(messages, unreadCount);
203
+ }
204
+ case "write": {
205
+ if (!args.to) {
206
+ log.warn(LOG.TOOL, `write missing 'to'`, { sessionId });
207
+ return WRITE_MISSING_TO;
208
+ }
209
+ if (!args.message) {
210
+ log.warn(LOG.TOOL, `write missing 'message'`, { sessionId });
211
+ return WRITE_MISSING_MESSAGE;
212
+ }
213
+ const msg = sendMessage(sessionId, args.to, args.message);
214
+ return writeResult(args.to, msg.id);
215
+ }
216
+ default:
217
+ return unknownAction(args.action);
218
+ }
219
+ }
220
+ })
221
+ },
222
+ "tool.execute.after": async (input, output) => {
223
+ log.debug(LOG.HOOK, `tool.execute.after fired`, { tool: input.tool, sessionID: input.sessionID, hasMetadata: !!output.metadata });
224
+ if (input.tool === "task") {
225
+ log.debug(LOG.HOOK, `task metadata`, { metadata: output.metadata, output: output.output?.substring?.(0, 200) });
226
+ const newSessionId = output.metadata?.sessionId || output.metadata?.session_id;
227
+ if (newSessionId) {
228
+ log.info(LOG.HOOK, `task completed, registering session`, { newSessionId });
229
+ registerSession(newSessionId);
230
+ } else {
231
+ log.warn(LOG.HOOK, `task completed but no session_id in metadata`);
232
+ }
233
+ }
234
+ },
235
+ "experimental.chat.system.transform": async (_input, output) => {
236
+ output.system.push(SYSTEM_PROMPT);
237
+ },
238
+ "experimental.chat.messages.transform": async (_input, output) => {
239
+ const lastUserMsg = [...output.messages].reverse().find((m) => m.info.role === "user");
240
+ if (!lastUserMsg)
241
+ return;
242
+ const sessionId = lastUserMsg.info.sessionID;
243
+ const unread = getUnreadMessages(sessionId);
244
+ if (unread.length === 0)
245
+ return;
246
+ log.info(LOG.INJECT, `Injecting urgent notification`, { sessionId, unreadCount: unread.length });
247
+ const notification = urgentNotification(unread.length);
248
+ const syntheticMessage = {
249
+ info: {
250
+ id: "msg_iam_" + Date.now(),
251
+ sessionID: sessionId,
252
+ role: "user",
253
+ time: { created: Date.now() },
254
+ agent: lastUserMsg.info.agent || "code",
255
+ model: lastUserMsg.info.model
256
+ },
257
+ parts: [
258
+ {
259
+ id: "prt_iam_" + Date.now(),
260
+ sessionID: sessionId,
261
+ messageID: "msg_iam_" + Date.now(),
262
+ type: "text",
263
+ text: notification
264
+ }
265
+ ]
266
+ };
267
+ output.messages.push(syntheticMessage);
268
+ }
269
+ };
270
+ };
271
+ var inbox_default = plugin;
272
+ export {
273
+ inbox_default as default
274
+ };
@@ -0,0 +1,14 @@
1
+ export declare const log: {
2
+ debug: (category: string, message: string, data?: unknown) => void;
3
+ info: (category: string, message: string, data?: unknown) => void;
4
+ warn: (category: string, message: string, data?: unknown) => void;
5
+ error: (category: string, message: string, data?: unknown) => void;
6
+ };
7
+ export declare const LOG: {
8
+ readonly TOOL: "TOOL";
9
+ readonly MESSAGE: "MESSAGE";
10
+ readonly SESSION: "SESSION";
11
+ readonly HOOK: "HOOK";
12
+ readonly INJECT: "INJECT";
13
+ };
14
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../logger.ts"],"names":[],"mappings":"AAuCA,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"}
@@ -0,0 +1,22 @@
1
+ export declare const TOOL_DESCRIPTION = "Inter-agent messaging. Use this to communicate with other parallel agents (task tools).\n\nActions:\n- \"sessions\": Show session IDs of agents you can message\n- \"read\": Read all your messages (marks them as read)\n- \"write\": Send a message to another agent";
2
+ export declare const ARG_DESCRIPTIONS: {
3
+ readonly action: "Action to perform";
4
+ readonly to: "Recipient session ID (required for 'write')";
5
+ readonly message: "Message content (required for 'write')";
6
+ };
7
+ export declare const SESSIONS_EMPTY = "No other agents available yet.\n\nAgents will appear here when:\n- Parallel tasks are spawned by the same parent\n- Another agent sends you a message";
8
+ export declare function sessionsResult(agents: string[]): string;
9
+ export declare const READ_EMPTY = "No messages in your iam.";
10
+ export declare function readResult(messages: {
11
+ from: string;
12
+ body: string;
13
+ timestamp: number;
14
+ read: boolean;
15
+ }[], unreadCount: number): string;
16
+ export declare const WRITE_MISSING_TO = "Error: 'to' is required. Use: iam write --to <session_id> --message \"...\"";
17
+ export declare const WRITE_MISSING_MESSAGE = "Error: 'message' is required. Use: iam write --to <session_id> --message \"...\"";
18
+ export declare function writeResult(to: string, messageId: string): string;
19
+ export declare function unknownAction(action: string): string;
20
+ export declare const SYSTEM_PROMPT = "\n# Inter-Agent Messaging\n\nYou have access to an `iam` tool for communicating with other parallel agents.\n\nCommands:\n- `iam sessions` - See agents you can message\n- `iam read` - Read all your messages\n- `iam write --to <session_id> --message \"...\"` - Send a message\n\nCheck your iam when notified about new messages.\n";
21
+ export declare function urgentNotification(unreadCount: number): string;
22
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,2QAKc,CAAC;AAE5C,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAMX,eAAO,MAAM,cAAc,0JAIS,CAAC;AAErC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAOvD;AAED,eAAO,MAAM,UAAU,6BAA6B,CAAC;AAErD,wBAAgB,UAAU,CACxB,QAAQ,EAAE;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAC,EAAE,EAC1E,WAAW,EAAE,MAAM,GAClB,MAAM,CAeR;AAED,eAAO,MAAM,gBAAgB,gFAA8E,CAAC;AAC5G,eAAO,MAAM,qBAAqB,qFAAmF,CAAC;AAEtH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAMD,eAAO,MAAM,aAAa,6UAWzB,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@spoons-and-mirrors/iam",
3
+ "version": "0.1.0",
4
+ "description": "Inter-agent messaging for OpenCode parallel subagents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun build ./index.ts --outdir ./dist --target node --format esm --external '@opencode-ai/plugin' && tsc --emitDeclarationOnly",
20
+ "prepublishOnly": "bun run build",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "opencode",
25
+ "plugin",
26
+ "inter-agent",
27
+ "messaging",
28
+ "subagent"
29
+ ],
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "@opencode-ai/plugin": ">=0.13.7"
33
+ },
34
+ "devDependencies": {
35
+ "@opencode-ai/plugin": "^1.0.143",
36
+ "@types/node": "^24.10.1",
37
+ "bun-types": "^1.3.5",
38
+ "typescript": "^5.9.3"
39
+ }
40
+ }