@orboto/mcp 0.212.0 → 0.212.1

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,24 @@
1
+ /**
2
+ * ORB-2347 - the inbox tool names the unregistered-caller rule in its description and in its answer.
3
+ */
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { OrbotoClient } from '../orboto-client.js';
6
+ import { agentMessagesToolConfig, makeAgentMessagesHandler } from './agent-messages.js';
7
+ afterEach(() => { vi.restoreAllMocks(); });
8
+ const client = new OrbotoClient({ baseUrl: 'https://orboto.example.com', apiKey: 'orb_x' });
9
+ function stubInbox(sessionId) {
10
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async () => ({ ok: true, status: 200, statusText: 'OK', json: async () => ({ messages: [], sessionId }), text: async () => '' }));
11
+ }
12
+ describe('ORB-2347 - orboto_messages for an unregistered caller', () => {
13
+ it('the description states the rule', () => {
14
+ expect(agentMessagesToolConfig.description).toContain('Before orboto_session_start only account mail is listed');
15
+ });
16
+ it('an answer without a session id carries the register hint; a registered answer does not', async () => {
17
+ stubInbox(null);
18
+ const unregistered = (await makeAgentMessagesHandler(client)({})).content[0];
19
+ expect(unregistered.text).toContain('Not registered: only account mail is listed');
20
+ stubInbox('8772a665-0000-4000-8000-000000000000');
21
+ const registered = (await makeAgentMessagesHandler(client)({})).content[0];
22
+ expect(registered.text).not.toContain('Not registered');
23
+ });
24
+ });
@@ -11,9 +11,10 @@ import { z } from 'zod';
11
11
  import { makeAgentMessageWorkHandler } from './agent-message-work.js';
12
12
  import { mcpInstanceToken } from './shared.js';
13
13
  const PartySchema = z.object({ userId: z.string(), email: z.string(), sessionId: z.string().nullable(), role: z.string().nullable(), label: z.string() });
14
+ const UNREGISTERED_INBOX_HINT = 'Not registered: only account mail is listed. Register with orboto_session_start to see the mail addressed to your session.';
14
15
  export const agentMessagesToolConfig = {
15
16
  title: 'Agent inbox and message work',
16
- description: 'Your inbox as THIS session sees it. It holds mail to this session, account mail inside your declared scope, broadcasts; sibling-session mail and own sends stay out, all:true shows the whole account (ORB-2136). messageWork carries durable work ownership - claim before executing. ackIds acks: it completes an unclaimed request; claimed work with open steps is refused until messageWork completes or blocks it. dismiss is the third answer: not_mine hides it here and tells the sender, obsolete/duplicate close it for the account (note or duplicateOf required). Never leave mail you read and judged. Reply via orboto_agent_notify with threadId (toSessionRef = from.sessionId reaches that instance).',
17
+ description: 'Your inbox as THIS session sees it. It holds mail to this session, account mail inside your declared scope, broadcasts; sibling-session mail and own sends stay out, all:true is the account view (ORB-2136). Before orboto_session_start only account mail is listed; register to see mail addressed to your session (ORB-2347). messageWork carries durable work ownership - claim before executing. ackIds acks: it completes an unclaimed request; claimed work with open steps is refused until messageWork completes or blocks it. dismiss is the third answer: not_mine hides it here and tells the sender, obsolete/duplicate close it for the account (note or duplicateOf required). Never leave mail you read and judged. Reply via orboto_agent_notify with threadId (toSessionRef = from.sessionId reaches that instance).',
17
18
  inputSchema: z.object({
18
19
  messageWork: z.record(z.unknown()).optional()
19
20
  .describe('Work envelope {messageId, mutation, cursor, limit, openOnly}; schema: orboto_api_search.'),
@@ -101,6 +102,8 @@ export function makeAgentMessagesHandler(client) {
101
102
  }
102
103
  if (sessionId)
103
104
  lines.push(`This session: ${sessionId.slice(0, 8)} (ref ${instanceToken}).`);
105
+ else
106
+ lines.push(UNREGISTERED_INBOX_HINT);
104
107
  return {
105
108
  content: [{ type: 'text', text: lines.join('\n') }],
106
109
  structuredContent: {
@@ -301,7 +301,9 @@ export function makeSessionStartHandler(client, opts = {}) {
301
301
  lines.push('', '## This session');
302
302
  const scopeText = registration?.scope
303
303
  ? [registration.scope.role ? `role ${registration.scope.role}` : null, registration.scope.projectKeys?.length ? `projects ${registration.scope.projectKeys.join(', ')}` : null, registration.scope.ticketKeys?.length ? `tickets ${registration.scope.ticketKeys.join(', ')}` : null].filter(Boolean).join('; ')
304
- : 'no scope declared - this session lists every account-addressed message; pass scope: { projectKeys, role } to narrow it';
304
+ : registration
305
+ ? 'no scope declared - this session lists every account-addressed message; pass scope: { projectKeys, role } to narrow it'
306
+ : 'registration failed - only account mail is listed; retry orboto_session_start or check mcp:use';
305
307
  lines.push(`ref ${instanceToken}${registration ? `, instance ${registration.sessionId.slice(0, 8)}` : ''}. ${scopeText}. Peers reach exactly this session with orboto_agent_notify { toSessionRef: "${registration ? registration.sessionId.slice(0, 8) : instanceToken}" }.`);
306
308
  const roleAddresses = registration?.scope?.role ? (registration.scope.projectKeys ?? []).map((k) => `${registration.scope.role}@${k}`) : [];
307
309
  if (roleAddresses.length)
@@ -618,3 +618,18 @@ describe('ORB-2136 - session scope and ref', () => {
618
618
  expect(docs).toContain('orboto mcp install');
619
619
  });
620
620
  });
621
+ describe('ORB-2347 - an unregistered caller is told it sees account mail only', () => {
622
+ it('states the rule in the This session line when the heartbeat registers nothing, and keeps the scope text for a registered session', async () => {
623
+ stubByPath({ '/agent-instructions': { instructions: 'rules', rulesHash: 'h' } });
624
+ const unregistered = (await makeSessionStartHandler(client)()).content[0];
625
+ expect(unregistered.text).toContain('registration failed - only account mail is listed; retry orboto_session_start');
626
+ expect(unregistered.text).not.toContain('lists every account-addressed message');
627
+ stubByPath({
628
+ '/v1/agent/heartbeat': { sessionId: '8772a665-0000-4000-8000-000000000000', scope: null },
629
+ '/agent-instructions': { instructions: 'rules', rulesHash: 'h2' },
630
+ });
631
+ const registered = (await makeSessionStartHandler(client)({ forceRules: true })).content[0];
632
+ expect(registered.text).toContain('lists every account-addressed message');
633
+ expect(registered.text).not.toContain('not registered');
634
+ });
635
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orboto/mcp",
3
- "version": "0.212.0",
3
+ "version": "0.212.1",
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",