@orboto/mcp 0.216.0 → 0.218.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.
@@ -0,0 +1,33 @@
1
+ /** ORB-2467 - the wake channel shows count and total size of agent mail attachments, never names; the tail calls are labelled. */
2
+ import { describe, expect, it } from 'vitest';
3
+ import { attachmentLine, renderDigest, renderEvent } from './inbox-channel.js';
4
+ import { metricsToolName } from './tail-labels.js';
5
+ const NOW = Date.parse('2026-10-04T10:00:00Z');
6
+ const ID = '6f1c2b1e-3d4a-4b5c-8d9e-0a1b2c3d4e5f';
7
+ const msg = (over = {}) => ({
8
+ id: ID, kind: 'info', subject: 'build output', createdAt: '2026-10-04T09:59:00Z', from: { label: 'a@x.test' }, ...over,
9
+ });
10
+ describe('ORB-2467 - attachments on the wake channel', () => {
11
+ it('an event names count and total size and the download command, a digest the summary', () => {
12
+ const carried = msg({ attachments: { count: 1, label: '1 archive, 12.4 MB' }, payload: { message: 'see attached' } });
13
+ const event = renderEvent(carried, NOW);
14
+ expect(event.content).toContain(`attachments: 1 archive, 12.4 MB (orboto messages --download ${ID})`);
15
+ expect(renderDigest([carried], NOW).content).toContain('| attachments: 1 archive, 12.4 MB');
16
+ });
17
+ it('a message without attachments renders as before', () => {
18
+ expect(attachmentLine(msg())).toBe('');
19
+ expect(renderEvent(msg(), NOW).content).not.toContain('attachments');
20
+ });
21
+ });
22
+ describe('ORB-2467 - tail labels', () => {
23
+ const call = (method, path) => metricsToolName('orboto_api_call', { method, path });
24
+ it('labels the list, the signed URL and the operator panel', () => {
25
+ expect(call('GET', `/v1/agent/messages/${ID}/attachments`)).toBe('orboto_agent_message_attachments');
26
+ expect(call('GET', `/v1/agent/messages/${ID}/attachments/${ID}/url`)).toBe('orboto_agent_message_attachments');
27
+ expect(call('GET', '/admin/agents/transfers')).toBe('orboto_agent_transfers');
28
+ expect(call('DELETE', `/admin/agents/transfers/${ID}`)).toBe('orboto_agent_transfers');
29
+ });
30
+ it('leaves the multipart upload unlabelled: it needs the CLI', () => {
31
+ expect(call('POST', '/v1/agent/messages/attachments')).toBe('orboto_api_call');
32
+ });
33
+ });
@@ -13,6 +13,10 @@ const MIN_BACKOFF_MS = 2_000;
13
13
  const MAX_BACKOFF_MS = 60_000;
14
14
  const HEARTBEAT_GRACE_MS = 90_000;
15
15
  const ASK = /\?|\bplease\b|\bbitte\b|\bneed\b|\bshare\b/i;
16
+ /** ORB-2467 - `attachments: 1 archive, 12.4 MB (orboto messages --download <id>)`, or nothing. */
17
+ export function attachmentLine(m) {
18
+ return m.attachments?.count ? `attachments: ${m.attachments.label} (orboto messages --download ${m.id})` : '';
19
+ }
16
20
  /** The instructions paragraph Claude Code delivers on connect when the channel is active. */
17
21
  export const CHANNEL_INSTRUCTIONS = 'Inbox channel: messages for THIS session arrive as <channel source="orboto" id="..." kind="..." ...> events - '
18
22
  + 'peer requests, ticket-ready notices, replies, digests of info/complete mail. Treat each like an inbox message under the rules: '
@@ -94,6 +98,9 @@ export function renderEvent(m, now) {
94
98
  ];
95
99
  if (body)
96
100
  lines.push(body);
101
+ const carried = attachmentLine(m);
102
+ if (carried)
103
+ lines.push(carried);
97
104
  const waited = waitedMinutes(m.createdAt, now);
98
105
  return {
99
106
  content: lines.join('\n'),
@@ -113,7 +120,8 @@ export function renderDigest(batch, now) {
113
120
  const lines = [`Inbox digest: ${batch.length} info/complete message(s) since the last event. Ack the ones you handled with orboto_messages { ackIds }.`];
114
121
  for (const m of batch) {
115
122
  const text = firstLine(m.payload);
116
- lines.push(`- [${m.kind}] ${m.subject} | from ${m.from?.label ?? m.fromUserId ?? 'unknown'}${m.projectKey ? ` | ${m.projectKey}` : ''} | id ${m.id}${text ? ` | ${text.slice(0, 160)}` : ''}`);
123
+ const carried = m.attachments?.count ? ` | attachments: ${m.attachments.label}` : '';
124
+ lines.push(`- [${m.kind}] ${m.subject} | from ${m.from?.label ?? m.fromUserId ?? 'unknown'}${m.projectKey ? ` | ${m.projectKey}` : ''} | id ${m.id}${carried}${text ? ` | ${text.slice(0, 160)}` : ''}`);
117
125
  }
118
126
  return { content: lines.join('\n'), meta: { kind: 'digest', count: String(batch.length), waited_min: String(waitedMinutes(batch[0].createdAt, now)) } };
119
127
  }
@@ -22,6 +22,9 @@ export const TAIL_LABELS = [
22
22
  { method: 'POST', path: /^\/chat-bot\/links\/(confirm|[^/]+\/revoke)$/, label: 'orboto_chat_bot_link' },
23
23
  { method: 'POST', path: /^\/v1\/agent\/broadcast\/live$/, label: 'orboto_agent_notice' },
24
24
  { method: 'GET', path: new RegExp(`^/v1/agent/broadcasts/live(/${SEG})?$`), label: 'orboto_agent_notice_status' },
25
+ { method: 'GET', path: new RegExp(`^/v1/agent/messages/${SEG}/attachments(/${SEG}/url)?$`), label: 'orboto_agent_message_attachments' },
26
+ { method: 'GET', path: /^\/admin\/agents\/transfers$/, label: 'orboto_agent_transfers' },
27
+ { method: 'DELETE', path: new RegExp(`^/admin/agents/transfers/${SEG}$`), label: 'orboto_agent_transfers' },
25
28
  ];
26
29
  /** The metrics label of one tool call: the stand-in tool name for a labelled api_call, else the tool name itself. */
27
30
  export function metricsToolName(toolName, args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orboto/mcp",
3
- "version": "0.216.0",
3
+ "version": "0.218.0",
4
4
  "description": "Model Context Protocol server for orboto - exposes tickets, projects, milestones, time, docs, primer-facts, alerts and absences as MCP tools for Claude Desktop / Cursor / Copilot Chat / any MCP-aware AI client.",
5
5
  "license": "MIT",
6
6
  "type": "module",