@orboto/mcp 0.212.6 → 0.213.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.
@@ -1,9 +1,15 @@
1
1
  import { rememberDeclaredScope } from './session-scope-memory.js';
2
2
  /** Must stay equal to ProjectKeyEnv in cli/internal/cmd/project_scope.go (drift test). */
3
3
  export const PROJECT_KEY_ENV = 'ORBOTO_PROJECT_KEY';
4
+ /** ORB-2448 - must stay equal to SessionRoleEnv in cli/internal/cmd/claude_control_session.go (drift test). */
5
+ export const SESSION_ROLE_ENV = 'ORBOTO_SESSION_ROLE';
6
+ /** ORB-2448 - must stay equal to ControlRoleDefault in the CLI and AGENT_CONTROL_ROLES in shared-schema. */
7
+ export const CONTROL_ROLE = 'coordinator';
4
8
  const PROJECT_KEY = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
5
- /** The worker scope on the project the environment names, or null. */
6
- export function envProjectScope(raw = process.env.ORBOTO_PROJECT_KEY) {
9
+ /** The control session the remembered role names (ORB-2448), else the worker scope on the project the environment names, or null. */
10
+ export function envProjectScope(raw = process.env.ORBOTO_PROJECT_KEY, role = process.env.ORBOTO_SESSION_ROLE) {
11
+ if (role?.trim().toLowerCase() === CONTROL_ROLE)
12
+ return { role: CONTROL_ROLE };
7
13
  const key = raw?.trim();
8
14
  if (!key || !PROJECT_KEY.test(key))
9
15
  return null;
@@ -14,8 +20,8 @@ export function scopeDeclared(scope) {
14
20
  return !!scope && (!!scope.role || (scope.projectKeys?.length ?? 0) > 0 || (scope.ticketKeys?.length ?? 0) > 0);
15
21
  }
16
22
  /** Declares the environment's project on the session row unless the row already carries a scope; answers the row. */
17
- export async function declareEnvProjectScope(client, instanceToken, raw = process.env.ORBOTO_PROJECT_KEY) {
18
- const scope = envProjectScope(raw);
23
+ export async function declareEnvProjectScope(client, instanceToken, raw = process.env.ORBOTO_PROJECT_KEY, role = process.env.ORBOTO_SESSION_ROLE) {
24
+ const scope = envProjectScope(raw, role);
19
25
  if (!scope)
20
26
  return null;
21
27
  const current = await client.get('/v1/agent/session', { instanceToken }).catch(() => null);
@@ -1,7 +1,7 @@
1
1
  /** ORB-2224 - the project a session declares from ORBOTO_PROJECT_KEY, and its drift against the CLI that writes it. */
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { declareEnvProjectScope, envProjectScope, PROJECT_KEY_ENV, scopeDeclared } from './project-scope.js';
4
+ import { CONTROL_ROLE, declareEnvProjectScope, envProjectScope, PROJECT_KEY_ENV, SESSION_ROLE_ENV, scopeDeclared } from './project-scope.js';
5
5
  import { _forgetDeclaredScopes, rememberedScope } from './session-scope-memory.js';
6
6
  import { OrbotoClient } from './orboto-client.js';
7
7
  import { makeSessionStartHandler } from './tools/session-start.js';
@@ -84,3 +84,47 @@ describe('ORB-2224 - ORBOTO_PROJECT_KEY', () => {
84
84
  expect(rememberedScope('mcp-scope1')).toEqual({ role: 'worker', projectKeys: ['ACME'] });
85
85
  });
86
86
  });
87
+ describe('ORB-2448 - ORBOTO_SESSION_ROLE', () => {
88
+ it('names the variable and the role the CLI and shared-schema use', () => {
89
+ expect(read('cli/internal/cmd/claude_control_session.go')).toContain(`const SessionRoleEnv = "${SESSION_ROLE_ENV}"`);
90
+ expect(read('cli/internal/cmd/claude_control_session.go')).toContain(`const ControlRoleDefault = "${CONTROL_ROLE}"`);
91
+ expect(read('packages/shared-schema/src/agent-session-scope.ts')).toContain(`AGENT_CONTROL_ROLES: readonly AgentSessionRole[] = ['${CONTROL_ROLE}']`);
92
+ expect(read('apps/mcp/src/project-scope.ts')).toContain(`process.env.${SESSION_ROLE_ENV}`);
93
+ expect(read('docs/env.md')).toContain(`| \`${SESSION_ROLE_ENV}\` | mcp |`);
94
+ });
95
+ it('declares a control session without a project ahead of the project key; any other role is ignored', async () => {
96
+ expect(envProjectScope('ACME', ' Coordinator ')).toEqual({ role: 'coordinator' });
97
+ expect(envProjectScope(undefined, 'coordinator')).toEqual({ role: 'coordinator' });
98
+ expect(envProjectScope('ACME', 'lead')).toEqual({ role: 'worker', projectKeys: ['ACME'] });
99
+ expect(envProjectScope(undefined, 'lead')).toBeNull();
100
+ const client = fakeClient({ sessionId: 'cbb52195-0000-4000-8000-000000000000', scope: null });
101
+ await declareEnvProjectScope(client, 'agent-control', undefined, 'coordinator');
102
+ expect(client.calls[1]).toEqual({ method: 'POST', path: '/v1/agent/heartbeat', body: { scope: { role: 'coordinator' } }, instanceToken: 'agent-control' });
103
+ expect(rememberedScope('agent-control')).toEqual({ role: 'coordinator' });
104
+ });
105
+ it('orboto_session_start without a scope declares the control session the environment names', async () => {
106
+ vi.stubEnv(SESSION_ROLE_ENV, 'coordinator');
107
+ vi.stubEnv(PROJECT_KEY_ENV, 'ACME');
108
+ const beats = [];
109
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, init) => {
110
+ const u = new URL(url.toString());
111
+ let body = {};
112
+ if (u.pathname === '/v1/agent/heartbeat') {
113
+ const sent = init?.body ? JSON.parse(init.body) : {};
114
+ beats.push(sent);
115
+ body = { sessionToken: 'x', sessionId: 'cbb52195-0000-4000-8000-000000000000', scope: sent.scope ?? null };
116
+ }
117
+ else if (u.pathname === '/v1/agent/session') {
118
+ body = { sessionId: 'cbb52195-0000-4000-8000-000000000000', scope: null };
119
+ }
120
+ else if (u.pathname === '/agent-instructions') {
121
+ body = { instructions: 'rules here', rulesHash: 'fixture' };
122
+ }
123
+ return { ok: true, status: 200, statusText: 'OK', json: async () => body, text: async () => '' };
124
+ });
125
+ const client = new OrbotoClient({ baseUrl: 'http://api.test', apiKey: 'k' });
126
+ await makeSessionStartHandler(client)({}, { sessionId: 'control1' });
127
+ expect(beats).toEqual([{}, { scope: { role: 'coordinator' } }]);
128
+ expect(rememberedScope('mcp-control1')).toEqual({ role: 'coordinator' });
129
+ });
130
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orboto/mcp",
3
- "version": "0.212.6",
3
+ "version": "0.213.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",