@grknbyk/agent-wire 0.8.1 → 0.8.2

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
@@ -83,7 +83,13 @@ Or, for any other MCP client:
83
83
 
84
84
  ## Tools your agent gets
85
85
 
86
- `send`, `send_file`, `inbox`, `archive`, `peers`, `members`, `channels`, `my_id`.
86
+ `send`, `send_file`, `inbox`, `archive`, `peers`, `members`, `channels`, `my_id`,
87
+ `status`.
88
+
89
+ `status` returns the same card the CLI draws, already fenced. It exists because a
90
+ shell result gets read, understood and then retyped as prose, and a drawn box does
91
+ not survive that — two installs reporting the same state should not produce two
92
+ different-looking answers.
87
93
 
88
94
  The mode of a channel is a command the user runs, never a tool. A message
89
95
  arriving from the channel must not be able to talk the agent into silencing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grknbyk/agent-wire",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Let AI coding agents message each other through a shared Slack channel, over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/inbox.mjs CHANGED
@@ -38,7 +38,12 @@ export function readInbox() {
38
38
  // per appended message is the whole cost of appending a message.
39
39
  const inboxKeys = () => derivedFromFile(paths.inbox, 'keys', () => new Set(readInbox().map(logKey)));
40
40
 
41
- export const stateOf = (states, item) => states[storageKey(item)] ?? 'unread';
41
+ // A message this agent sent is kept in the log so the conversation reads back
42
+ // whole, but it was never waiting on anybody. Left as unread it joined the tally
43
+ // of who needs an answer, and the agent was told ten people were waiting when one
44
+ // of them was itself.
45
+ export const stateOf = (states, item) =>
46
+ (item.authorship === 'self' ? 'read' : states[storageKey(item)] ?? 'unread');
42
47
 
43
48
  // The Slack timestamp is the idempotency key: a retried poll, an overlapping
44
49
  // window, or a re-installed app all replay the same ts, and a duplicate the human
package/src/mcp.mjs CHANGED
@@ -64,6 +64,11 @@ const TOOLS = [
64
64
  description: 'This agent\'s nickname, emoji, key fingerprint and channels.',
65
65
  inputSchema: { type: 'object', properties: {} },
66
66
  },
67
+ {
68
+ name: 'status',
69
+ description: 'The status card: identity, channels with their modes, who has written, and when the last poll ran. Print what this returns exactly as it arrives, inside a code block. It is a drawn box, so retyping the fields loses it.',
70
+ inputSchema: { type: 'object', properties: {} },
71
+ },
67
72
  {
68
73
  name: 'peers',
69
74
  description: 'Agent names seen in the channels so far, with the key pinned to each.',
@@ -154,11 +159,26 @@ const MODE_SUMMARY = {
154
159
  read: 'the messages themselves, in every prompt',
155
160
  };
156
161
 
157
- const PROMPTS = MODES.map((mode) => ({
158
- name: mode,
159
- description: `Set a channel to ${mode} for this session — ${MODE_SUMMARY[mode]}`,
160
- arguments: [{ name: 'channel', description: 'Channel name. Omit it when only one is configured.', required: false }],
161
- }));
162
+ const PROMPTS = [
163
+ ...MODES.map((mode) => ({
164
+ name: mode,
165
+ description: `Set a channel to ${mode} for this session ${MODE_SUMMARY[mode]}`,
166
+ arguments: [{ name: 'channel', description: 'Channel name. Omit it when only one is configured.', required: false }],
167
+ })),
168
+ { name: 'status', description: 'Show the agent-wire status card', arguments: [] },
169
+ ];
170
+
171
+ const STATUS_INSTRUCTION = {
172
+ description: 'Show the agent-wire status card',
173
+ messages: [{
174
+ role: 'user',
175
+ content: {
176
+ type: 'text',
177
+ text: 'Call the agent-wire status tool and print what it returns verbatim, inside a code block.'
178
+ + ' Do not summarise it, do not retype the fields, do not reformat the box. The drawing is the answer.',
179
+ },
180
+ }],
181
+ };
162
182
 
163
183
  function modeInstruction(mode, channel) {
164
184
  const command = `agent-wire ${mode}${channel ? ` ${channel}` : ''}`;
@@ -326,6 +346,15 @@ async function call(name, args, session) {
326
346
  // refusal and tells the user the wrong thing.
327
347
  if (!config) return 'agent-wire is not configured yet. Tell the user to run `agent-wire setup` in a real terminal window — it asks questions, so it will not run from a tool. Install it first with `npm i -g @grknbyk/agent-wire` if the command is missing.';
328
348
 
349
+ // The card reaches the user through a tool rather than a shell, because a
350
+ // shell result gets read, understood and then retyped as prose — and the box
351
+ // does not survive that. Fenced here so it arrives ready to pass on.
352
+ if (name === 'status') {
353
+ const { renderStatus } = await import('./status.mjs');
354
+ return 'Show this to the user exactly as it is, in a code block. Do not summarise it and do not retype the numbers.\n\n'
355
+ + `\`\`\`\n${renderStatus(config).trim()}\n\`\`\``;
356
+ }
357
+
329
358
  if (name === 'my_id') {
330
359
  const channels = (config.channels ?? []).map((channel) => `#${channel.name}`).join(', ') || 'none';
331
360
  return `${config.mark} ${config.nickname} — key ${config.public_key.slice(0, FINGERPRINT_CHARS)}… — channels: ${channels}`;
@@ -430,7 +459,10 @@ export function serve() {
430
459
  if (message.method === 'prompts/get') {
431
460
  const asked = PROMPTS.find((prompt) => prompt.name === message.params.name);
432
461
  if (!asked) return write({ jsonrpc: '2.0', id: message.id, error: { code: -32602, message: `no prompt named ${message.params.name}` } });
433
- return write({ jsonrpc: '2.0', id: message.id, result: modeInstruction(asked.name, message.params.arguments?.channel) });
462
+ const answer = asked.name === 'status'
463
+ ? STATUS_INSTRUCTION
464
+ : modeInstruction(asked.name, message.params.arguments?.channel);
465
+ return write({ jsonrpc: '2.0', id: message.id, result: answer });
434
466
  }
435
467
  if (message.method === 'ping') return write({ jsonrpc: '2.0', id: message.id, result: {} });
436
468
  if (message.method === 'tools/call') {