@orboto/mcp 0.178.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/LICENSE.md +21 -0
- package/README.md +123 -0
- package/dist/event-bridge.js +250 -0
- package/dist/event-bridge.test.js +165 -0
- package/dist/http-transport-binding.test.js +151 -0
- package/dist/http-transport.js +608 -0
- package/dist/http-transport.test.js +408 -0
- package/dist/index.js +128 -0
- package/dist/input-schema.js +293 -0
- package/dist/input-schema.test.js +198 -0
- package/dist/instructions-budget.test.js +34 -0
- package/dist/manifest-size.test.js +192 -0
- package/dist/mcp-instrument.js +24 -0
- package/dist/oauth-bootstrap.js +413 -0
- package/dist/oauth-bootstrap.test.js +212 -0
- package/dist/orboto-client.js +250 -0
- package/dist/orboto-client.test.js +141 -0
- package/dist/prompts.js +219 -0
- package/dist/prompts.test.js +79 -0
- package/dist/protect-text-usage.test.js +38 -0
- package/dist/resources.js +260 -0
- package/dist/resources.test.js +191 -0
- package/dist/response-budget.js +515 -0
- package/dist/response-budget.test.js +343 -0
- package/dist/server.js +547 -0
- package/dist/session-nudge.js +81 -0
- package/dist/session-nudge.test.js +100 -0
- package/dist/session-nudge.transport.test.js +100 -0
- package/dist/tool-annotations.test.js +97 -0
- package/dist/tool-docs.js +64 -0
- package/dist/tool-docs.test.js +90 -0
- package/dist/tool-parameters.test.js +145 -0
- package/dist/tools/absence-writes.js +93 -0
- package/dist/tools/absence-writes.test.js +106 -0
- package/dist/tools/admin-translations.js +83 -0
- package/dist/tools/admin-writes.js +174 -0
- package/dist/tools/admin-writes.test.js +120 -0
- package/dist/tools/agent-coordination.js +160 -0
- package/dist/tools/agent-coordination.test.js +156 -0
- package/dist/tools/agent-drift.js +79 -0
- package/dist/tools/agent-drift.test.js +87 -0
- package/dist/tools/agent-instructions.js +172 -0
- package/dist/tools/agent-instructions.test.js +124 -0
- package/dist/tools/agent-messages.js +75 -0
- package/dist/tools/ai-status.js +55 -0
- package/dist/tools/ai-status.test.js +43 -0
- package/dist/tools/ai-usage.js +68 -0
- package/dist/tools/analytics.js +72 -0
- package/dist/tools/analytics.test.js +71 -0
- package/dist/tools/api-call.js +72 -0
- package/dist/tools/api-call.test.js +104 -0
- package/dist/tools/api-search.js +77 -0
- package/dist/tools/api-search.test.js +90 -0
- package/dist/tools/approvals.js +68 -0
- package/dist/tools/approvals.test.js +48 -0
- package/dist/tools/attach.js +108 -0
- package/dist/tools/attach.test.js +117 -0
- package/dist/tools/backup.js +93 -0
- package/dist/tools/backup.test.js +108 -0
- package/dist/tools/bulk-create.js +213 -0
- package/dist/tools/bulk-create.test.js +135 -0
- package/dist/tools/bulk-writes.js +339 -0
- package/dist/tools/bulk-writes.test.js +239 -0
- package/dist/tools/check-similar.js +84 -0
- package/dist/tools/check-similar.test.js +101 -0
- package/dist/tools/checklist-writes.js +202 -0
- package/dist/tools/checklist-writes.test.js +184 -0
- package/dist/tools/claim.js +236 -0
- package/dist/tools/claim.test.js +207 -0
- package/dist/tools/critical-path.js +83 -0
- package/dist/tools/critical-path.test.js +114 -0
- package/dist/tools/cross-project-links.ee.js +124 -0
- package/dist/tools/cross-project-links.ee.test.js +163 -0
- package/dist/tools/customer-report.js +88 -0
- package/dist/tools/customer-report.test.js +55 -0
- package/dist/tools/doc-attachments.js +184 -0
- package/dist/tools/doc-attachments.test.js +136 -0
- package/dist/tools/doc-comments.js +228 -0
- package/dist/tools/doc-comments.test.js +192 -0
- package/dist/tools/doc-edits.js +229 -0
- package/dist/tools/doc-edits.test.js +218 -0
- package/dist/tools/doc-export.js +87 -0
- package/dist/tools/doc-export.test.js +109 -0
- package/dist/tools/doc-revisions.js +133 -0
- package/dist/tools/doc-revisions.test.js +103 -0
- package/dist/tools/docs-ai.js +171 -0
- package/dist/tools/docs-ai.test.js +160 -0
- package/dist/tools/docs.js +557 -0
- package/dist/tools/docs.test.js +365 -0
- package/dist/tools/embedding-status.js +100 -0
- package/dist/tools/embedding-status.test.js +86 -0
- package/dist/tools/free-busy.js +49 -0
- package/dist/tools/free-busy.test.js +46 -0
- package/dist/tools/get-checklists.js +72 -0
- package/dist/tools/get-project-primer.js +58 -0
- package/dist/tools/get-project.js +77 -0
- package/dist/tools/get-ticket.js +321 -0
- package/dist/tools/get-timer.js +62 -0
- package/dist/tools/help.js +44 -0
- package/dist/tools/identity.js +60 -0
- package/dist/tools/identity.test.js +67 -0
- package/dist/tools/list-git-app-installations.js +45 -0
- package/dist/tools/list-projects.js +72 -0
- package/dist/tools/list-projects.test.js +67 -0
- package/dist/tools/list-tickets.js +86 -0
- package/dist/tools/milestones-crud.test.js +154 -0
- package/dist/tools/milestones.js +279 -0
- package/dist/tools/milestones.test.js +60 -0
- package/dist/tools/my-tickets.js +65 -0
- package/dist/tools/personal-facts.js +72 -0
- package/dist/tools/personal-facts.test.js +45 -0
- package/dist/tools/phase-b-tools.test.js +530 -0
- package/dist/tools/portfolio.js +56 -0
- package/dist/tools/primer-facts.js +346 -0
- package/dist/tools/primer-facts.test.js +353 -0
- package/dist/tools/project-listings.js +116 -0
- package/dist/tools/project-listings.test.js +106 -0
- package/dist/tools/query.js +65 -0
- package/dist/tools/query.test.js +128 -0
- package/dist/tools/raci.js +108 -0
- package/dist/tools/raci.test.js +70 -0
- package/dist/tools/requirements-spec.js +89 -0
- package/dist/tools/requirements-spec.test.js +62 -0
- package/dist/tools/response-expand.js +104 -0
- package/dist/tools/review-policy.js +105 -0
- package/dist/tools/review-policy.test.js +71 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/session-start.js +435 -0
- package/dist/tools/session-start.test.js +568 -0
- package/dist/tools/set-parent.js +76 -0
- package/dist/tools/set-parent.test.js +115 -0
- package/dist/tools/shared.js +226 -0
- package/dist/tools/shared.test.js +169 -0
- package/dist/tools/similar-projection.js +30 -0
- package/dist/tools/ticket-attachments.js +131 -0
- package/dist/tools/ticket-attachments.test.js +106 -0
- package/dist/tools/ticket-schedules.js +112 -0
- package/dist/tools/ticket-schedules.test.js +102 -0
- package/dist/tools/ticket-writes.js +778 -0
- package/dist/tools/ticket-writes.test.js +643 -0
- package/dist/tools/time-writes.js +238 -0
- package/dist/tools/time-writes.test.js +138 -0
- package/dist/tools/update-project.js +165 -0
- package/dist/tools/update-project.test.js +172 -0
- package/dist/tools/wiki.js +185 -0
- package/dist/tools/wiki.test.js +104 -0
- package/dist/tools/work-sessions-next.test.js +150 -0
- package/dist/tools/work-sessions-start.test.js +174 -0
- package/dist/tools/work-sessions.js +709 -0
- package/dist/tools/work-sessions.test.js +358 -0
- package/dist/toolset.js +149 -0
- package/dist/toolset.test.js +150 -0
- package/dist/version-guard.test.js +51 -0
- package/dist/version.js +28 -0
- package/dist/with-metrics-mail-nudge.test.js +41 -0
- package/dist/with-metrics-output-schema.test.js +91 -0
- package/dist/with-metrics.js +211 -0
- package/dist/with-metrics.test.js +230 -0
- package/package.json +66 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORB-1611 - unit tests for orboto_work_start, the bundled acquire +
|
|
3
|
+
* context-bundle tool. Mirrors work-sessions.test.ts's stub pattern; the
|
|
4
|
+
* behaviours worth pinning: the bundle renders every section, the
|
|
5
|
+
* instance token still rides unprompted, the per-connection rules-hash
|
|
6
|
+
* cache round-trips across two calls, and the 409 paths (lease held,
|
|
7
|
+
* claim conflict) read the same as orboto_work_session_start's.
|
|
8
|
+
*/
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
+
import { OrbotoClient } from '../orboto-client.js';
|
|
11
|
+
import { makeWorkStartHandler } from './work-sessions.js';
|
|
12
|
+
beforeEach(() => { vi.restoreAllMocks(); });
|
|
13
|
+
afterEach(() => { vi.restoreAllMocks(); });
|
|
14
|
+
function stub(responses) {
|
|
15
|
+
const calls = [];
|
|
16
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, init) => {
|
|
17
|
+
calls.push({
|
|
18
|
+
url: url.toString(),
|
|
19
|
+
method: init?.method ?? 'GET',
|
|
20
|
+
body: init?.body ? JSON.parse(init.body) : undefined,
|
|
21
|
+
});
|
|
22
|
+
const r = responses.shift();
|
|
23
|
+
if (!r)
|
|
24
|
+
throw new Error('unexpected extra fetch');
|
|
25
|
+
return {
|
|
26
|
+
ok: r.ok ?? true,
|
|
27
|
+
status: r.status ?? 200,
|
|
28
|
+
statusText: 'OK',
|
|
29
|
+
json: async () => ('json' in r ? r.json : {}),
|
|
30
|
+
text: async () => r.text ?? '',
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return calls;
|
|
34
|
+
}
|
|
35
|
+
const client = new OrbotoClient({ baseUrl: 'https://orboto.example.com', apiKey: 'orb_x' });
|
|
36
|
+
const PROJ = { id: 'p1', key: 'ACME', name: 'Acme', description: null, status: 'active' };
|
|
37
|
+
const TICKET = { id: 't1', ticketKey: 'ACME-42', title: 'Do the thing', projectId: 'p1' };
|
|
38
|
+
const SESSION = {
|
|
39
|
+
id: 'ws1',
|
|
40
|
+
ticketId: 't1',
|
|
41
|
+
role: 'implementation',
|
|
42
|
+
status: 'active',
|
|
43
|
+
startedAt: '2026-07-24T10:00:00Z',
|
|
44
|
+
leaseUntil: '2026-07-24T10:15:00Z',
|
|
45
|
+
activeTimerId: 'timer1',
|
|
46
|
+
commitSha: null,
|
|
47
|
+
ticketKey: 'ACME-42',
|
|
48
|
+
};
|
|
49
|
+
const BUNDLE = {
|
|
50
|
+
session: SESSION,
|
|
51
|
+
reused: false,
|
|
52
|
+
rulesHash: 'abc123',
|
|
53
|
+
rulesUnchanged: false,
|
|
54
|
+
rules: 'Follow the ticket-first rule.',
|
|
55
|
+
primer: { markdown: '# Primer\nProject conventions.', totalTokens: 42 },
|
|
56
|
+
ticket: {
|
|
57
|
+
ticketKey: 'ACME-42',
|
|
58
|
+
title: 'Do the thing',
|
|
59
|
+
description: 'Needs doing.',
|
|
60
|
+
status: 'IN_PROGRESS',
|
|
61
|
+
statusName: 'In Progress',
|
|
62
|
+
priority: 'high',
|
|
63
|
+
type: 'task',
|
|
64
|
+
},
|
|
65
|
+
checklists: [
|
|
66
|
+
{
|
|
67
|
+
title: 'Acceptance criteria',
|
|
68
|
+
triggersDone: true,
|
|
69
|
+
progress: { done: 0, total: 1 },
|
|
70
|
+
items: [{ content: 'Write a test', effectiveCompleted: false, linkedTicketKey: null, linkedTicketStatusCategory: null }],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
dependencies: {
|
|
74
|
+
blockedBy: [{ ticketKey: 'ACME-41', title: 'Blocker', statusName: 'To Do' }],
|
|
75
|
+
blocks: [],
|
|
76
|
+
},
|
|
77
|
+
gitHealth: [],
|
|
78
|
+
siblingSessions: [],
|
|
79
|
+
};
|
|
80
|
+
describe('orboto_work_start', () => {
|
|
81
|
+
it('acquires the lease and renders every bundle section', async () => {
|
|
82
|
+
const calls = stub([
|
|
83
|
+
{ json: [PROJ] },
|
|
84
|
+
{ json: TICKET },
|
|
85
|
+
{ json: BUNDLE },
|
|
86
|
+
]);
|
|
87
|
+
const res = await makeWorkStartHandler(client)({ ticketKey: 'ACME-42' });
|
|
88
|
+
const post = calls[calls.length - 1];
|
|
89
|
+
expect(post.method).toBe('POST');
|
|
90
|
+
expect(post.url).toContain('/work-sessions/start');
|
|
91
|
+
expect(post.body?.ticketId).toBe('t1');
|
|
92
|
+
// The instance token must be sent unprompted, same contract as
|
|
93
|
+
// orboto_work_session_start.
|
|
94
|
+
expect(String(post.body?.agentSessionToken)).toMatch(/^mcp-/);
|
|
95
|
+
const text = res.content[0].text;
|
|
96
|
+
expect(text).toContain('Started a implementation work session on ACME-42');
|
|
97
|
+
expect(text).toContain('ws1');
|
|
98
|
+
expect(text).toContain('## Working rules');
|
|
99
|
+
expect(text).toContain('Follow the ticket-first rule.');
|
|
100
|
+
expect(text).toContain('## Project primer');
|
|
101
|
+
expect(text).toContain('Project conventions.');
|
|
102
|
+
expect(text).toContain('## Ticket: ACME-42');
|
|
103
|
+
expect(text).toContain('Needs doing.');
|
|
104
|
+
expect(text).toContain('## Checklists');
|
|
105
|
+
expect(text).toContain('Write a test');
|
|
106
|
+
expect(text).toContain('## Dependencies');
|
|
107
|
+
expect(text).toContain('ACME-41');
|
|
108
|
+
expect(text).toContain('## Other sessions on this ticket');
|
|
109
|
+
expect(res.structuredContent.ticket.ticketKey).toBe('ACME-42');
|
|
110
|
+
});
|
|
111
|
+
it('caches the rules hash across calls on the same connection and sends it back', async () => {
|
|
112
|
+
const handler = makeWorkStartHandler(client);
|
|
113
|
+
stub([{ json: [PROJ] }, { json: TICKET }, { json: BUNDLE }]);
|
|
114
|
+
await handler({ ticketKey: 'ACME-42' });
|
|
115
|
+
const calls2 = stub([
|
|
116
|
+
{ json: [PROJ] },
|
|
117
|
+
{ json: TICKET },
|
|
118
|
+
{ json: { ...BUNDLE, reused: true, rulesUnchanged: true, rules: undefined } },
|
|
119
|
+
]);
|
|
120
|
+
const res2 = await handler({ ticketKey: 'ACME-42' });
|
|
121
|
+
const post2 = calls2[calls2.length - 1];
|
|
122
|
+
expect(post2.body?.knownRulesHash).toBe('abc123');
|
|
123
|
+
const text2 = res2.content[0].text;
|
|
124
|
+
expect(text2).toContain('Unchanged since your last work-start');
|
|
125
|
+
expect(text2).toContain('Renewed your existing');
|
|
126
|
+
});
|
|
127
|
+
it('surfaces the lease holder on a 409 instead of throwing', async () => {
|
|
128
|
+
const holder = {
|
|
129
|
+
sessionId: 'other-session',
|
|
130
|
+
userEmail: 'rival@orboto.test',
|
|
131
|
+
userFullName: 'Rival Bot',
|
|
132
|
+
role: 'implementation',
|
|
133
|
+
startedAt: '2026-07-24T09:00:00Z',
|
|
134
|
+
leaseUntil: '2026-07-24T09:15:00Z',
|
|
135
|
+
};
|
|
136
|
+
stub([
|
|
137
|
+
{ json: [PROJ] },
|
|
138
|
+
{ json: TICKET },
|
|
139
|
+
{ ok: false, status: 409, text: JSON.stringify({ error: 'held', errorKey: 'errors.work_sessions.lease_held', holder }) },
|
|
140
|
+
]);
|
|
141
|
+
const res = await makeWorkStartHandler(client)({ ticketKey: 'ACME-42' });
|
|
142
|
+
expect(res.isError).toBe(true);
|
|
143
|
+
const text = res.content[0].text;
|
|
144
|
+
expect(text).toContain('Rival Bot');
|
|
145
|
+
expect(text).toContain('takeover=true');
|
|
146
|
+
expect(res.structuredContent.holder.sessionId).toBe('other-session');
|
|
147
|
+
});
|
|
148
|
+
it('surfaces claim conflicts distinctly from a lease conflict on 409', async () => {
|
|
149
|
+
const conflicts = [{
|
|
150
|
+
claim: { kind: 'named', value: 'unity-editor:main', mode: 'write' },
|
|
151
|
+
holders: [{ sessionId: 's2', ticketKey: 'ACME-9', userEmail: 'rival@orboto.test', userFullName: 'Rival Bot', claim: { kind: 'named', value: 'unity-editor:main', mode: 'write' } }],
|
|
152
|
+
}];
|
|
153
|
+
stub([
|
|
154
|
+
{ json: [PROJ] },
|
|
155
|
+
{ json: TICKET },
|
|
156
|
+
{ ok: false, status: 409, text: JSON.stringify({ error: 'held', errorKey: 'errors.work_sessions.claim_conflict', claimConflicts: conflicts }) },
|
|
157
|
+
]);
|
|
158
|
+
const res = await makeWorkStartHandler(client)({
|
|
159
|
+
ticketKey: 'ACME-42',
|
|
160
|
+
resourceClaims: [{ kind: 'named', value: 'unity-editor:main', mode: 'write' }],
|
|
161
|
+
});
|
|
162
|
+
expect(res.isError).toBe(true);
|
|
163
|
+
const text = res.content[0].text;
|
|
164
|
+
expect(text).toContain('Rival Bot');
|
|
165
|
+
expect(text).toContain('unity-editor:main');
|
|
166
|
+
expect(res.structuredContent.claimConflict).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
it('reports an unresolvable ticket as a text error instead of throwing', async () => {
|
|
169
|
+
stub([{ ok: false, status: 404, text: '' }]);
|
|
170
|
+
const res = await makeWorkStartHandler(client)({ ticketKey: 'ACME-999' });
|
|
171
|
+
expect(res.isError).toBe(true);
|
|
172
|
+
expect(res.content[0].text).toContain('not found');
|
|
173
|
+
});
|
|
174
|
+
});
|