@orboto/mcp 0.221.0 → 0.221.11

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.
Files changed (38) hide show
  1. package/dist/event-bridge.js +2 -0
  2. package/dist/http-transport.js +28 -6
  3. package/dist/inbox-channel.js +14 -11
  4. package/dist/instructions-budget.test.js +72 -2
  5. package/dist/manifest-size.test.js +8 -0
  6. package/dist/orboto-client.js +3 -1
  7. package/dist/orboto-client.test.js +1 -1
  8. package/dist/server.js +18 -28
  9. package/dist/session-origin.js +100 -0
  10. package/dist/session-origin.test.js +127 -0
  11. package/dist/tools/agent-coordination.js +39 -11
  12. package/dist/tools/agent-coordination.test.js +53 -35
  13. package/dist/tools/agent-messages.js +16 -5
  14. package/dist/tools/agent-who.js +23 -8
  15. package/dist/tools/agent-who.test.js +12 -0
  16. package/dist/tools/check-similar.js +5 -3
  17. package/dist/tools/doc-edits.js +6 -4
  18. package/dist/tools/doc-replace-wrapper.test.js +81 -0
  19. package/dist/tools/docs-ai.js +4 -0
  20. package/dist/tools/docs.js +25 -14
  21. package/dist/tools/docs.test.js +25 -12
  22. package/dist/tools/embedding-status.js +3 -0
  23. package/dist/tools/knowledge.js +2 -2
  24. package/dist/tools/knowledge.test.js +8 -0
  25. package/dist/tools/list-tickets.js +12 -9
  26. package/dist/tools/phase-b-tools.test.js +18 -0
  27. package/dist/tools/responsibilities.js +10 -0
  28. package/dist/tools/responsibilities.test.js +60 -0
  29. package/dist/tools/search.js +5 -3
  30. package/dist/tools/semantic-degraded-wrapper.test.js +70 -0
  31. package/dist/tools/semantic-degraded.test.js +65 -0
  32. package/dist/tools/session-start.js +8 -2
  33. package/dist/tools/shared.js +31 -3
  34. package/dist/tools/spec-schemas.js +2 -0
  35. package/dist/tools/wiki.js +15 -9
  36. package/dist/tools/wiki.test.js +23 -0
  37. package/dist/toolset.js +3 -2
  38. package/package.json +1 -1
@@ -7,6 +7,7 @@
7
7
  * descriptions per the skill/MCP English rule.
8
8
  */
9
9
  import { z } from 'zod';
10
+ import { SEMANTIC_DEGRADED_LINE, replacedLines } from './shared.js';
10
11
  import { resolveDocId } from './docs.js';
11
12
  function text(t, structured) {
12
13
  return { content: [{ type: 'text', text: t }], ...(structured ? { structuredContent: structured } : {}) };
@@ -49,7 +50,8 @@ export function makeWikiAskHandler(client) {
49
50
  body.limit = input.limit;
50
51
  const res = await client.post(`/ai/ask-docs`, body);
51
52
  const cites = res.citations.map((c) => `[${c.index}] ${c.title} - ${c.link}`).join('\n');
52
- return text(`${res.answer}\n\n${cites ? `Sources:\n${cites}` : ''}`.trim(), { answer: res.answer, citations: res.citations, mode: res.mode });
53
+ const degraded = res.semanticDegraded ? `\n\n(${SEMANTIC_DEGRADED_LINE})` : '';
54
+ return text(`${res.answer}\n\n${cites ? `Sources:\n${cites}` : ''}`.trim() + degraded, { answer: res.answer, citations: res.citations, mode: res.mode, semanticDegraded: res.semanticDegraded ?? false });
53
55
  };
54
56
  }
55
57
  export const wikiLintToolConfig = {
@@ -98,17 +100,19 @@ export function makeWikiPlanUpdateHandler(client) {
98
100
  }
99
101
  export const wikiApplyPlanToolConfig = {
100
102
  title: 'Apply a previously-planned wiki edit',
101
- description: 'Commit the page operations from a plan created by orboto_wiki_plan_update. Applies every op atomically (each page edit is snapshotted for rollback), then rebuilds the index doc of the space and appends a line to its log doc. Fails with 410 if the plan has expired (>15 min) or was already applied. Wraps POST /spaces/:id/docs/apply-plan.',
103
+ description: 'Commit the page operations from a plan created by orboto_wiki_plan_update. Applies every op atomically (each page edit is snapshotted for rollback), then rebuilds the index doc of the space and appends a line to its log doc. ORB-2468: a patch op that drops more than half of its page refuses the whole plan with 409 (nothing written) unless replace is true; each rewritten page comes back in replaced with the revision and the restore command. Fails with 410 if the plan has expired (>15 min) or was already applied. Wraps POST /spaces/:id/docs/apply-plan.',
102
104
  inputSchema: z.object({
103
105
  spaceId: z.string().uuid(),
104
- planId: z.string().uuid().describe('The planId returned by orboto_wiki_plan_update.'),
106
+ planId: z.string().uuid().describe('From orboto_wiki_plan_update.'),
107
+ replace: z.boolean().optional(),
105
108
  }).shape,
106
109
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
107
110
  };
108
111
  export function makeWikiApplyPlanHandler(client) {
109
112
  return async (input) => {
110
- const res = await client.post(`/spaces/${input.spaceId}/docs/apply-plan`, { planId: input.planId });
111
- return text(`Applied plan ${input.planId}: ${res.touchedDocs.length} page(s) updated.`, { touchedDocs: res.touchedDocs });
113
+ const res = await client.post(`/spaces/${input.spaceId}/docs/apply-plan`, { planId: input.planId, ...(input.replace ? { replace: true } : {}) });
114
+ const replaced = res.replaced ?? [];
115
+ return text(`Applied plan ${input.planId}: ${res.touchedDocs.length} page(s) updated.${replacedLines(replaced)}`, { touchedDocs: res.touchedDocs, replaced });
112
116
  };
113
117
  }
114
118
  export const wikiRecordToolConfig = {
@@ -150,21 +154,23 @@ export function makeWikiAppendSectionHandler(client) {
150
154
  }
151
155
  export const wikiSaveAnswerToolConfig = {
152
156
  title: 'Save a Q&A answer as a wiki page',
153
- description: 'Turn an answer (e.g. from orboto_wiki_ask) into a curated wiki page with smart-links to its citations. Idempotent per (space, question): re-saving the same question updates the page instead of duplicating it. Wraps POST /ai/save-answer-to-wiki.',
157
+ description: 'Turn an answer (e.g. from orboto_wiki_ask) into a curated wiki page with smart-links to its citations. Idempotent per (space, question): re-saving the same question updates the page instead of duplicating it. ORB-2468: a re-save that drops more than half of the saved page is refused with 409 unless replace is true; an update names the revision it replaced and the restore command. Wraps POST /ai/save-answer-to-wiki.',
154
158
  inputSchema: z.object({
155
159
  spaceId: z.string().uuid(),
156
160
  question: z.string().min(3).max(1000),
157
161
  answer: z.string().min(1).max(50_000),
158
- title: z.string().max(200).optional().describe('Page title; defaults to the question.'),
162
+ title: z.string().max(200).optional().describe('Defaults to the question.'),
159
163
  parentDocId: z.string().uuid().optional(),
160
- citations: z.array(z.object({ docId: z.string().min(1).describe('Doc UUID or human-readable doc key (ORB-D12 / DOC-5).'), title: z.string() })).max(50).optional().describe('Cited docs to smart-link (from orboto_wiki_ask).'),
164
+ citations: z.array(z.object({ docId: z.string().min(1).describe('Doc UUID or human-readable doc key (ORB-D12 / DOC-5).'), title: z.string() })).max(50).optional().describe('From orboto_wiki_ask.'),
165
+ replace: z.boolean().optional(),
161
166
  }).shape,
162
167
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
163
168
  };
164
169
  export function makeWikiSaveAnswerHandler(client) {
165
170
  return async (input) => {
166
171
  const res = await client.post(`/ai/save-answer-to-wiki`, { ...input, citations: input.citations ?? [] });
167
- return text(res.created ? `Saved as a new wiki page (docId: ${res.docId}).` : `Updated the existing wiki page (docId: ${res.docId}).`, { docId: res.docId, created: res.created });
172
+ const replaced = res.replaced ? [res.replaced] : [];
173
+ return text(res.created ? `Saved as a new wiki page (docId: ${res.docId}).` : `Updated the existing wiki page (docId: ${res.docId}).${replacedLines(replaced)}`, { docId: res.docId, created: res.created, replaced: res.replaced ?? null });
168
174
  };
169
175
  }
170
176
  export const wikiFlagStaleToolConfig = {
@@ -69,6 +69,22 @@ describe('orboto_wiki_plan_update + apply_plan', () => {
69
69
  const res = await makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1' });
70
70
  expect(res.content[0].text).toContain('1 page(s) updated');
71
71
  });
72
+ it('ORB-2468 - apply-plan sends replace only when asked and prints each replaced revision with its restore command', async () => {
73
+ const replaced = { docId: DOC, revisionId: 'r0000000-0000-4000-8000-000000000001', restoreCommand: 'orboto doc restore KB-D4 r0000000-0000-4000-8000-000000000001' };
74
+ const calls = stubJSON([{ json: { touchedDocs: [DOC] } }, { json: { touchedDocs: [DOC], replaced: [replaced] } }]);
75
+ await makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1' });
76
+ expect(calls[0].body).toEqual({ planId: 'p1' });
77
+ const res = await makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1', replace: true });
78
+ expect(calls[1].body).toEqual({ planId: 'p1', replace: true });
79
+ const out = res.content[0].text;
80
+ expect(out).toContain('Replaced: 1');
81
+ expect(out).toContain(replaced.restoreCommand);
82
+ expect(res.structuredContent).toMatchObject({ replaced: [replaced] });
83
+ });
84
+ it('ORB-2468 - apply-plan surfaces the 409 shrink refusal as OrbotoApiError', async () => {
85
+ stubJSON([{ ok: false, status: 409, json: { error: 'This write would drop 90% of the existing page KB-D4', errorKey: 'errors.docs.replace_drops_content' } }]);
86
+ await expect(makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1' })).rejects.toBeInstanceOf(OrbotoApiError);
87
+ });
72
88
  it('apply-plan surfaces a 410 expired plan as OrbotoApiError', async () => {
73
89
  stubJSON([{ ok: false, status: 410, json: { error: 'Plan has expired' } }]);
74
90
  await expect(makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1' })).rejects.toBeInstanceOf(OrbotoApiError);
@@ -100,6 +116,13 @@ describe('orboto_wiki_save_answer', () => {
100
116
  expect(calls[0]).toMatchObject({ method: 'POST', url: 'https://orboto.example.com/ai/save-answer-to-wiki', body: { spaceId: SPACE, question: 'how?', answer: 'like this', citations: [] } });
101
117
  expect(res.content[0].text).toMatch(/new wiki page/);
102
118
  });
119
+ it('ORB-2468 - a re-save passes replace and names the replaced revision', async () => {
120
+ const replaced = { docId: DOC, revisionId: 'r0000000-0000-4000-8000-000000000002', restoreCommand: 'orboto doc restore KB-D5 r0000000-0000-4000-8000-000000000002' };
121
+ const calls = stubJSON([{ json: { docId: DOC, created: false, replaced } }]);
122
+ const res = await makeWikiSaveAnswerHandler(client)({ spaceId: SPACE, question: 'how?', answer: 'new', replace: true });
123
+ expect(calls[0].body).toMatchObject({ replace: true });
124
+ expect(res.content[0].text).toContain(replaced.restoreCommand);
125
+ });
103
126
  });
104
127
  describe('orboto_wiki_flag_stale', () => {
105
128
  it('defaults stale=true and reports the flag', async () => {
package/dist/toolset.js CHANGED
@@ -6,10 +6,11 @@
6
6
  */
7
7
  /** ORB-2210 - no tools: the agent works through the orboto CLI, the server carries the wake channel. */
8
8
  export const CHANNEL_TOOLSET_INSTRUCTIONS = [
9
+ 'FIRST ACTION, before anything else: run `orboto session-start` in the shell - it prints the binding workspace rules and your in-progress work; run it again after a context compaction.',
9
10
  'orboto is a ticket and project management system; ticket keys look like `PROJ-123`.',
10
- 'This server carries no tools. Work through the orboto CLI in the shell: `orboto help` lists the commands,',
11
- '`orboto session-start` prints the binding workspace rules and your in-progress work (run it first and after a context compaction),',
11
+ 'This server carries no tools. Work through the orboto CLI: `orboto help` lists the commands,',
12
12
  'and `orboto agent-heartbeat --role <role> --scope-projects <KEY,KEY>` declares what this session is responsible for.',
13
+ 'Before you send agent mail, `orboto owner "<topic>"` names who owns the topic.',
13
14
  'Always: claim or create a ticket before touching code, one commit per ticket with the key in the subject, never mark work done that is not done.',
14
15
  'MCP tools come back on request: `?toolset=curated|full` (HTTP) or ORBOTO_MCP_TOOLSET=curated|full (stdio).',
15
16
  ].join(' ');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orboto/mcp",
3
- "version": "0.221.0",
3
+ "version": "0.221.11",
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",