@orboto/mcp 0.192.0 → 0.194.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.
Files changed (131) hide show
  1. package/dist/event-bridge.js +1 -47
  2. package/dist/event-bridge.test.js +0 -19
  3. package/dist/http-transport-binding.test.js +1 -4
  4. package/dist/http-transport.js +1 -213
  5. package/dist/http-transport.test.js +0 -56
  6. package/dist/index.js +1 -48
  7. package/dist/input-schema.js +2 -76
  8. package/dist/input-schema.test.js +1 -15
  9. package/dist/instructions-budget.test.js +0 -2
  10. package/dist/manifest-size.test.js +3 -72
  11. package/dist/oauth-bootstrap.js +0 -47
  12. package/dist/oauth-bootstrap.test.js +4 -16
  13. package/dist/orboto-client.js +0 -30
  14. package/dist/orboto-client.test.js +0 -12
  15. package/dist/prompts.js +0 -39
  16. package/dist/protect-text-usage.test.js +1 -9
  17. package/dist/required-rules-wrapper.test.js +0 -5
  18. package/dist/required-rules.js +0 -2
  19. package/dist/resources.js +0 -62
  20. package/dist/resources.test.js +0 -16
  21. package/dist/response-budget.js +2 -150
  22. package/dist/response-budget.test.js +1 -32
  23. package/dist/server.js +4 -180
  24. package/dist/session-nudge.js +0 -9
  25. package/dist/session-nudge.test.js +2 -10
  26. package/dist/session-nudge.transport.test.js +0 -23
  27. package/dist/tool-annotations.test.js +0 -44
  28. package/dist/tool-docs.js +1 -12
  29. package/dist/tool-docs.test.js +2 -8
  30. package/dist/tool-parameters.test.js +0 -19
  31. package/dist/tools/absence-writes.js +1 -17
  32. package/dist/tools/admin-translations.js +0 -11
  33. package/dist/tools/admin-writes.js +0 -28
  34. package/dist/tools/admin-writes.test.js +0 -1
  35. package/dist/tools/agent-coordination.js +2 -24
  36. package/dist/tools/agent-coordination.test.js +0 -2
  37. package/dist/tools/agent-instructions.js +0 -10
  38. package/dist/tools/agent-instructions.test.js +0 -2
  39. package/dist/tools/agent-messages.js +0 -1
  40. package/dist/tools/ai-status.js +0 -14
  41. package/dist/tools/ai-usage.js +0 -10
  42. package/dist/tools/analytics.js +0 -2
  43. package/dist/tools/api-call.js +0 -12
  44. package/dist/tools/api-call.test.js +1 -2
  45. package/dist/tools/api-search.js +1 -6
  46. package/dist/tools/approvals.js +0 -6
  47. package/dist/tools/attach.js +0 -25
  48. package/dist/tools/backup.js +0 -13
  49. package/dist/tools/backup.test.js +1 -2
  50. package/dist/tools/bulk-create.js +1 -25
  51. package/dist/tools/bulk-create.test.js +0 -4
  52. package/dist/tools/bulk-writes.js +69 -61
  53. package/dist/tools/bulk-writes.test.js +34 -4
  54. package/dist/tools/check-similar.js +0 -17
  55. package/dist/tools/checklist-writes.js +82 -27
  56. package/dist/tools/checklist-writes.test.js +69 -1
  57. package/dist/tools/claim.js +1 -71
  58. package/dist/tools/claim.test.js +0 -2
  59. package/dist/tools/critical-path.js +0 -5
  60. package/dist/tools/critical-path.test.js +0 -3
  61. package/dist/tools/cross-project-links.ee.js +1 -24
  62. package/dist/tools/doc-attachments.js +0 -28
  63. package/dist/tools/doc-attachments.test.js +1 -4
  64. package/dist/tools/doc-comments.js +1 -37
  65. package/dist/tools/doc-comments.test.js +1 -2
  66. package/dist/tools/doc-edits.js +1 -31
  67. package/dist/tools/doc-edits.test.js +0 -9
  68. package/dist/tools/doc-export.js +0 -24
  69. package/dist/tools/doc-export.test.js +0 -12
  70. package/dist/tools/doc-revisions.js +0 -23
  71. package/dist/tools/docs-ai.js +0 -34
  72. package/dist/tools/docs-ai.test.js +0 -11
  73. package/dist/tools/docs.js +1 -75
  74. package/dist/tools/docs.test.js +1 -3
  75. package/dist/tools/embedding-status.js +0 -13
  76. package/dist/tools/embedding-status.test.js +0 -1
  77. package/dist/tools/feedback.js +1 -15
  78. package/dist/tools/feedback.test.js +0 -1
  79. package/dist/tools/get-checklists.js +6 -14
  80. package/dist/tools/get-project-primer.js +1 -25
  81. package/dist/tools/get-project.js +0 -17
  82. package/dist/tools/get-ticket.js +1 -61
  83. package/dist/tools/get-timer.js +0 -11
  84. package/dist/tools/git-health-reasons.js +0 -1
  85. package/dist/tools/identity.js +0 -13
  86. package/dist/tools/list-projects.js +0 -20
  87. package/dist/tools/list-projects.test.js +0 -1
  88. package/dist/tools/list-tickets.js +10 -9
  89. package/dist/tools/milestones-crud.test.js +0 -4
  90. package/dist/tools/milestones.js +0 -43
  91. package/dist/tools/my-tickets.js +0 -7
  92. package/dist/tools/phase-b-tools.test.js +14 -21
  93. package/dist/tools/primer-facts.js +1 -49
  94. package/dist/tools/primer-facts.test.js +0 -20
  95. package/dist/tools/project-listings.js +0 -25
  96. package/dist/tools/query.js +1 -12
  97. package/dist/tools/query.test.js +0 -8
  98. package/dist/tools/raci.js +0 -11
  99. package/dist/tools/requirements-spec.js +1 -12
  100. package/dist/tools/response-expand.js +0 -19
  101. package/dist/tools/review-policy.js +0 -24
  102. package/dist/tools/review-policy.test.js +0 -2
  103. package/dist/tools/search.js +1 -13
  104. package/dist/tools/session-start.js +1 -136
  105. package/dist/tools/session-start.test.js +1 -44
  106. package/dist/tools/set-parent.js +1 -19
  107. package/dist/tools/shared.js +0 -41
  108. package/dist/tools/shared.test.js +0 -4
  109. package/dist/tools/similar-projection.js +0 -15
  110. package/dist/tools/ticket-attachments.js +1 -25
  111. package/dist/tools/ticket-schedules.js +0 -9
  112. package/dist/tools/ticket-schedules.test.js +0 -1
  113. package/dist/tools/ticket-writes.js +1 -110
  114. package/dist/tools/ticket-writes.test.js +1 -15
  115. package/dist/tools/time-writes.js +26 -46
  116. package/dist/tools/time-writes.test.js +18 -0
  117. package/dist/tools/update-project.js +1 -27
  118. package/dist/tools/update-project.test.js +1 -1
  119. package/dist/tools/wiki.js +0 -8
  120. package/dist/tools/work-next.js +0 -5
  121. package/dist/tools/work-sessions-start.test.js +0 -2
  122. package/dist/tools/work-sessions.js +0 -48
  123. package/dist/tools/work-sessions.test.js +0 -5
  124. package/dist/toolset.js +2 -81
  125. package/dist/toolset.test.js +0 -20
  126. package/dist/version-guard.test.js +0 -4
  127. package/dist/version.js +0 -12
  128. package/dist/with-metrics-output-schema.test.js +1 -14
  129. package/dist/with-metrics.js +2 -74
  130. package/dist/with-metrics.test.js +7 -29
  131. package/package.json +1 -1
@@ -1,27 +1,8 @@
1
1
  /**
2
2
  * ORB-915 - doc-export MCP tools (epic ORB-911 Phase 4).
3
- *
4
- * - orboto_export_doc_md - GET /docs/:id/export/md (text/markdown)
5
- * - orboto_export_doc_pdf - POST /docs/:id/export/pdf (application/pdf)
6
- *
7
- * The Markdown export differs from `orboto_get_doc.content` because
8
- * the server-side renderer strips backlinks / smart-link inflation
9
- * and emits the raw saved Markdown - useful when the agent wants to
10
- * pipe the body through another tool without the get-doc envelope.
11
- *
12
- * The PDF export returns binary bytes. MCP supports binary resource
13
- * attachments via the `{ type: 'resource', resource: { uri, blob:
14
- * <base64>, mimeType } }` content shape; we return the PDF that way.
15
- *
16
- * Both endpoints rely on `OrbotoClient.getText` / `postBinary` which
17
- * landed alongside this phase - JSON-only `get` / `post` would have
18
- * blown up on the non-JSON response bodies.
19
3
  */
20
4
  import { z } from 'zod';
21
5
  import { resolveDocId } from './docs.js';
22
- // ---------------------------------------------------------------------------
23
- // orboto_export_doc_md
24
- // ---------------------------------------------------------------------------
25
6
  export const exportDocMdToolConfig = {
26
7
  title: 'Export a doc page as Markdown',
27
8
  description: 'Return the doc body as plain Markdown (text/markdown). Differs from orboto_get_doc.content in that the server-side export endpoint emits the canonical saved body - agents pulling docs to feed into another tool should prefer this over get_doc, which wraps the body in get-doc envelope text + backlinks.',
@@ -44,9 +25,6 @@ export function makeExportDocMdHandler(client) {
44
25
  };
45
26
  };
46
27
  }
47
- // ---------------------------------------------------------------------------
48
- // orboto_export_doc_pdf
49
- // ---------------------------------------------------------------------------
50
28
  export const exportDocPdfToolConfig = {
51
29
  title: 'Export a doc page as PDF',
52
30
  description: 'Render the doc page to PDF via the workspace\'s PdfService (Puppeteer-backed) and return the bytes as a base64 MCP resource attachment. Requires the PDF engine to be configured - deployments without Chromium / Puppeteer return 503 (errors.pdf.engine_unavailable) which surfaces as an OrbotoApiError.',
@@ -59,8 +37,6 @@ export function makeExportDocPdfHandler(client) {
59
37
  return async ({ docId }) => {
60
38
  docId = await resolveDocId(client, docId);
61
39
  const { bytes, contentType } = await client.postBinary(`/docs/${docId}/export/pdf`);
62
- // MCP's `resource` content type takes a base64 blob - Buffer →
63
- // base64 is the standard Node path.
64
40
  const base64 = Buffer.from(bytes).toString('base64');
65
41
  return {
66
42
  content: [
@@ -1,15 +1,5 @@
1
1
  /**
2
2
  * ORB-915 - doc-export tool tests.
3
- *
4
- * Markdown export: stub fetch returning a string + content-type
5
- * text/markdown. Verify the tool returns the Markdown verbatim.
6
- *
7
- * PDF export: stub fetch returning a Uint8Array body. Verify the
8
- * tool wraps it as an MCP resource attachment with base64 blob.
9
- *
10
- * 503 from a deployment with no PDF engine: verify the
11
- * OrbotoApiError bubbles up so the model can tell the user what's
12
- * wrong.
13
3
  */
14
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15
5
  import { OrbotoApiError, OrbotoClient } from '../orboto-client.js';
@@ -81,7 +71,6 @@ describe('orboto_export_doc_md', () => {
81
71
  });
82
72
  describe('orboto_export_doc_pdf', () => {
83
73
  it('POSTs /docs/:id/export/pdf and wraps the bytes as a base64 MCP resource', async () => {
84
- // Pretend the renderer returned a minimal 8-byte PDF header.
85
74
  const pdfBytes = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x37]);
86
75
  const calls = stubBinary(pdfBytes);
87
76
  const res = await makeExportDocPdfHandler(client)({ docId: DOC_ID });
@@ -93,7 +82,6 @@ describe('orboto_export_doc_pdf', () => {
93
82
  expect(first.type).toBe('resource');
94
83
  expect(first.resource.uri).toBe(`orboto://doc/${DOC_ID}/export.pdf`);
95
84
  expect(first.resource.mimeType).toBe('application/pdf');
96
- // The blob is the base64 of the bytes - decode it back and compare.
97
85
  const decoded = Buffer.from(first.resource.blob, 'base64');
98
86
  expect(Array.from(decoded)).toEqual(Array.from(pdfBytes));
99
87
  expect(res.structuredContent).toMatchObject({
@@ -1,25 +1,8 @@
1
1
  /**
2
2
  * ORB-916 - doc revision history MCP tools (epic ORB-911 Phase 5).
3
- *
4
- * - orboto_list_doc_revisions - GET /docs/:id/revisions (cursor-paged)
5
- * - orboto_get_doc_revision - GET /docs/:id/revisions/:rid
6
- * - orboto_restore_doc_revision - POST /docs/:id/revisions/:rid/restore
7
- *
8
- * Revisions are auto-captured by PATCH /docs/:id every time the title
9
- * or body changes, so by the time an agent wants to roll back there's
10
- * usually a long history to walk. The list endpoint is cursor-paged
11
- * (newest-first by editedAt DESC + id tiebreak) - the tool surfaces
12
- * `nextCursor` in structuredContent so the caller can page through if
13
- * the default 25-row page isn't enough.
14
- *
15
- * Restore writes a new revision capturing the current body before
16
- * rolling back, so the restore itself is also undoable.
17
3
  */
18
4
  import { z } from 'zod';
19
5
  import { resolveDocId } from './docs.js';
20
- // ---------------------------------------------------------------------------
21
- // orboto_list_doc_revisions
22
- // ---------------------------------------------------------------------------
23
6
  export const listDocRevisionsToolConfig = {
24
7
  title: 'List the revision history of a doc page',
25
8
  description: 'Return saved revisions for a doc page, newest-first. Each row carries the revision id, title at snapshot time, editor user id, and editedAt timestamp; the body content is NOT in this list (use orboto_get_doc_revision to fetch one). Cursor-paged - pass `cursor` to walk older pages.',
@@ -69,9 +52,6 @@ export function makeListDocRevisionsHandler(client) {
69
52
  };
70
53
  };
71
54
  }
72
- // ---------------------------------------------------------------------------
73
- // orboto_get_doc_revision
74
- // ---------------------------------------------------------------------------
75
55
  export const getDocRevisionToolConfig = {
76
56
  title: 'Get a single doc revision',
77
57
  description: 'Return the full content + title of one saved revision. Use this to diff against the current doc before deciding whether to restore.',
@@ -101,9 +81,6 @@ export function makeGetDocRevisionHandler(client) {
101
81
  };
102
82
  };
103
83
  }
104
- // ---------------------------------------------------------------------------
105
- // orboto_restore_doc_revision
106
- // ---------------------------------------------------------------------------
107
84
  export const restoreDocRevisionToolConfig = {
108
85
  title: 'Restore a doc page to a saved revision',
109
86
  description: 'Roll back the doc to the saved revision. The API snapshots the CURRENT body to a fresh revision row first, so the restore itself is also undoable. Returns the post-restore doc state.',
@@ -1,34 +1,7 @@
1
1
  /**
2
2
  * ORB-799 - docs-AI surface (ask + ingest).
3
- *
4
- * Three tools that mirror the wrapper's `ask-docs` / `ingest-url` /
5
- * `ingest-file` subcommands. Together they're the foundation for any
6
- * agent that needs to "remember this URL" / "add this PDF to our
7
- * wiki" workflows.
8
- *
9
- * - orboto_ask_docs - RAG Q&A across wiki docs, returns answer +
10
- * citations. Requires both `configured` and
11
- * `embeddingsConfigured` on `/ai/status`
12
- * (RAG retrieval needs an embedding-capable
13
- * provider).
14
- * - orboto_ingest_url - fetch + extract + create doc from a public
15
- * URL via the Readability-fallback pipeline.
16
- * - orboto_ingest_file - upload + extract + create doc from a local
17
- * file (PDF / DOCX / Markdown / plain text).
18
- * Uses the multipart-upload route, so the MCP
19
- * tool receives the bytes as a base64 string
20
- * to avoid the model needing local FS access.
21
- *
22
- * AI-gated note: the `ai_status` tool exists for pre-flight. We do NOT
23
- * pre-check inside the handlers - the API returns the same gating
24
- * error regardless and a pre-check would double the latency of every
25
- * call. Models are expected to call `orboto_ai_status` once per
26
- * session if they're unsure of workspace shape.
27
3
  */
28
4
  import { z } from 'zod';
29
- // ---------------------------------------------------------------------------
30
- // orboto_ask_docs
31
- // ---------------------------------------------------------------------------
32
5
  export const askDocsToolConfig = {
33
6
  title: 'Ask a question against the wiki (RAG)',
34
7
  description: 'Run a natural-language question against the wiki via the workspace\'s RAG pipeline. Returns an `answer` (Markdown) + `citations` array `[{index, title, link, spaceName}]`. The model is the workspace\'s configured AI provider; retrieval requires the embedding provider too - call `orboto_ai_status` to verify both before relying on this. Restrict to a single doc space with `spaceId` when scoping a query (e.g. "only the runbooks space"). Latency is dominated by retrieval + LLM round-trip; expect 2-10s.',
@@ -67,9 +40,6 @@ export function makeAskDocsHandler(client) {
67
40
  };
68
41
  };
69
42
  }
70
- // ---------------------------------------------------------------------------
71
- // orboto_ingest_url
72
- // ---------------------------------------------------------------------------
73
43
  export const ingestUrlToolConfig = {
74
44
  title: 'Ingest a public URL into a wiki space',
75
45
  description: 'Fetch a public URL, run Readability extraction, store the result as a Markdown doc in `spaceId`. Optionally nest under `parentDocId` to keep the tree tidy. Sets `readabilityFallback: true` when the page didn\'t look like a recognisable article (the body falls back to full-page text - a flag the caller should surface to the operator for review).',
@@ -108,9 +78,6 @@ export function makeIngestUrlHandler(client) {
108
78
  };
109
79
  };
110
80
  }
111
- // ---------------------------------------------------------------------------
112
- // orboto_ingest_file
113
- // ---------------------------------------------------------------------------
114
81
  function mimetypeFor(filename) {
115
82
  const lower = filename.toLowerCase();
116
83
  if (lower.endsWith('.pdf'))
@@ -136,7 +103,6 @@ export function makeIngestFileHandler(client) {
136
103
  return async ({ spaceId, filename, contentBase64, parentDocId }) => {
137
104
  let arrayBuffer;
138
105
  try {
139
- // See attach.ts for the Buffer → ArrayBuffer copy rationale.
140
106
  const buf = Buffer.from(contentBase64, 'base64');
141
107
  arrayBuffer = new ArrayBuffer(buf.byteLength);
142
108
  new Uint8Array(arrayBuffer).set(buf);
@@ -1,13 +1,5 @@
1
1
  /**
2
2
  * ORB-799 - docs-AI tools tests.
3
- *
4
- * `orboto_ask_docs` happy + AI-not-configured-style 400.
5
- * `orboto_ingest_url` happy + readability-fallback flag.
6
- * `orboto_ingest_file` happy + empty-bytes refusal.
7
- *
8
- * The multipart upload's wire format is verified by inspecting the
9
- * FormData body the fetch mock receives - we read the form-fields
10
- * back to assert filename + parent.
11
3
  */
12
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
5
  import { OrbotoApiError, OrbotoClient } from '../orboto-client.js';
@@ -124,9 +116,6 @@ describe('orboto_ingest_url', () => {
124
116
  });
125
117
  describe('orboto_ingest_file', () => {
126
118
  it('POSTs multipart with filename + decoded content', async () => {
127
- // Capture the multipart body via the fetch mock - FormData is
128
- // opaque to JSON.parse, so we don't decode it; we only assert the
129
- // route, method, and the JSON response shape.
130
119
  const calls = [];
131
120
  vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, init) => {
132
121
  calls.push({
@@ -1,30 +1,10 @@
1
1
  /**
2
2
  * ORB-244 Phase B - doc tools.
3
3
  *
4
- * `orboto_list_doc_spaces` and `orboto_get_doc` share this file for the
5
- * same reason the milestone tools do - cheap neighbours on the same
6
- * API root. Docs carry a human-readable key (`ORB-D12` / `DOC-5`,
7
- * ORB-1004) and spaces carry one too (`ORB-S1` / `SPACE-5`, ORB-1015);
8
- * both resolve by key or UUID.
9
- *
10
- * ORB-912 (epic ORB-911) added the write-path neighbours so an
11
- * MCP-aware client can do the full space lifecycle without falling
12
- * back to REST: create / update / delete spaces, list docs in a
13
- * space, and (in follow-up phases) write / move / attach / export /
14
- * roll back individual doc pages.
15
- *
16
- * Schema alignment: field names match `@orboto/shared-schema`
17
- * exactly - `content` not `body` on docs, `excerpt` not `snippet` on
18
- * hits, backlinks carry `{type, id, label, sourceDocId,
19
- * sourceDocTitle, sourceSpaceId}`. Getting those wrong 500s the tool
20
- * because the API's Zod response validator rejects off-shape rows
21
- * before they leave the server.
4
+ * @see ORB-1004, ORB-1015, ORB-912, ORB-911
22
5
  */
23
6
  import { z } from 'zod';
24
7
  import { resolveProjectByKey } from './shared.js';
25
- // ---------------------------------------------------------------------------
26
- // orboto_list_doc_spaces
27
- // ---------------------------------------------------------------------------
28
8
  export const listDocSpacesToolConfig = {
29
9
  title: 'List doc spaces',
30
10
  description: 'List wiki spaces (global and project-scoped) the caller can read. Each space contains a tree of docs accessed via orboto_get_doc.',
@@ -37,20 +17,12 @@ export function makeListDocSpacesHandler(client) {
37
17
  const text = spaces.length === 0
38
18
  ? 'No doc spaces visible to this user.'
39
19
  : spaces.map((s) => {
40
- // `type` is the authoritative scope indicator; `projectId` is
41
- // populated when type === 'project'. We use the type flag so
42
- // the text rendering doesn't depend on an extra API join.
43
20
  const scope = s.type === 'project' ? 'project-scoped' : 'workspace-wide';
44
- // ORB-1161 - show key AND uuid. Doc tools accept the key directly,
45
- // but surface the uuid too for any tool that still needs it.
46
21
  return `- ${s.name} (${scope}) - key: ${s.key ?? '(none)'} id: ${s.id}`;
47
22
  }).join('\n');
48
23
  return {
49
24
  content: [{ type: 'text', text }],
50
25
  structuredContent: {
51
- // ORB-1700 - metadata only; description/slug/visibility belong to
52
- // the follow-up space read. id stays: every doc tool accepts it,
53
- // and pre-key spaces have no key to address by.
54
26
  spaces: spaces.map((s) => ({
55
27
  id: s.id,
56
28
  key: s.key ?? null,
@@ -61,13 +33,7 @@ export function makeListDocSpacesHandler(client) {
61
33
  };
62
34
  };
63
35
  }
64
- // ---------------------------------------------------------------------------
65
- // orboto_get_doc
66
- // ---------------------------------------------------------------------------
67
36
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
- // ORB-1084 - every docId-taking tool accepts the human-readable doc
69
- // key (ORB-D12 / DOC-5) as well; writes were UUID-only while no tool
70
- // exposed the full UUID, so agents could read but never write.
71
37
  export async function resolveDocId(client, docIdOrKey) {
72
38
  if (UUID_RE.test(docIdOrKey))
73
39
  return docIdOrKey;
@@ -84,8 +50,6 @@ export const getDocToolConfig = {
84
50
  };
85
51
  export function makeGetDocHandler(client) {
86
52
  return async ({ docId }) => {
87
- // ORB-1004 - accept a doc key too; resolve via the by-key route, then
88
- // use the resolved UUID for the backlinks fetch.
89
53
  const doc = UUID_RE.test(docId)
90
54
  ? await client.get(`/docs/${docId}`)
91
55
  : await client.get(`/docs/by-key/${encodeURIComponent(docId)}`);
@@ -99,10 +63,6 @@ export function makeGetDocHandler(client) {
99
63
  if (backlinks.length > 0) {
100
64
  lines.push('', `## Backlinks (${backlinks.length})`);
101
65
  for (const b of backlinks) {
102
- // sourceDocTitle is the doc that links *to* this one; the
103
- // `type` + `id` pair identifies the target - same row, target
104
- // side. For most backlinks the target is this doc, so we
105
- // surface the source.
106
66
  lines.push(`- ${b.sourceDocTitle}`);
107
67
  }
108
68
  }
@@ -131,9 +91,6 @@ export function makeGetDocHandler(client) {
131
91
  };
132
92
  };
133
93
  }
134
- // ---------------------------------------------------------------------------
135
- // orboto_create_doc_space (ORB-912)
136
- // ---------------------------------------------------------------------------
137
94
  export const createDocSpaceToolConfig = {
138
95
  title: 'Create a doc space',
139
96
  description: 'Create a new wiki space. IMPORTANT: every project ALREADY has an auto-generated general space (slug `<key>-general`, system-generated, holds the AI primer + manual notes) - do NOT create a second space just to hold a project\'s docs. Call orboto_list_doc_spaces first and reuse the existing project space; only create a new one for a genuinely separate collection. `type=global` creates a workspace-wide space (super-admin only); `type=project` creates a space scoped to a project - pass `projectKey` (e.g. "ORB"); a raw `projectId` UUID is still accepted for back-compat. The slug is derived from `name` when omitted. Returns the new space row.',
@@ -152,8 +109,6 @@ export function makeCreateDocSpaceHandler(client) {
152
109
  return async (input) => {
153
110
  const body = { name: input.name, type: input.type };
154
111
  if (input.type === 'project') {
155
- // Prefer the key (agent-facing surfaces speak keys); fall back to a raw
156
- // UUID for back-compat.
157
112
  let projectId = input.projectId;
158
113
  if (!projectId && input.projectKey) {
159
114
  projectId = (await resolveProjectByKey(client, input.projectKey)).id;
@@ -187,9 +142,6 @@ export function makeCreateDocSpaceHandler(client) {
187
142
  };
188
143
  };
189
144
  }
190
- // ---------------------------------------------------------------------------
191
- // orboto_update_doc_space (ORB-912)
192
- // ---------------------------------------------------------------------------
193
145
  export const updateDocSpaceToolConfig = {
194
146
  title: 'Update a doc space',
195
147
  description: 'Patch a space\'s name / description / icon / slug, and (ORB-1080) its visibility settings: `visibility` (workspace = every internal member, restricted = explicit allowlist via memberIds), `guestsVisible` (external users see the space only when true). Visibility fields require space-management rights (global spaces: super-admin; project spaces: project:edit) - plain membership gets a 403 for those. `isPublic` is deprecated (replaced by guestsVisible). `type` and `projectId` are immutable; auto-generated primer spaces refuse name/slug edits.',
@@ -219,7 +171,6 @@ export function makeUpdateDocSpaceHandler(client) {
219
171
  body.isPublic = input.isPublic;
220
172
  if (input.slug !== undefined)
221
173
  body.slug = input.slug;
222
- // ORB-1080 - visibility settings (manage-gated server-side).
223
174
  if (input.visibility !== undefined)
224
175
  body.visibility = input.visibility;
225
176
  if (input.guestsVisible !== undefined)
@@ -245,9 +196,6 @@ export function makeUpdateDocSpaceHandler(client) {
245
196
  };
246
197
  };
247
198
  }
248
- // ---------------------------------------------------------------------------
249
- // orboto_delete_doc_space (ORB-912)
250
- // ---------------------------------------------------------------------------
251
199
  export const deleteDocSpaceToolConfig = {
252
200
  title: 'Delete a doc space',
253
201
  description: 'DESTRUCTIVE - cascades through every doc in the space (the pages themselves are gone, not just hidden). System-generated project primer spaces refuse deletion (they cascade only when the owning project is deleted). Returns success silently; 404 surfaces as an OrbotoApiError.',
@@ -265,9 +213,6 @@ export function makeDeleteDocSpaceHandler(client) {
265
213
  };
266
214
  };
267
215
  }
268
- // ---------------------------------------------------------------------------
269
- // orboto_list_docs_in_space (ORB-912)
270
- // ---------------------------------------------------------------------------
271
216
  export const listDocsInSpaceToolConfig = {
272
217
  title: 'List docs inside a space',
273
218
  description: 'Return the flat list of doc pages in a space - each carries `parentDocId` so the caller can reconstruct the tree client-side. Use this when an agent needs to find a doc by title rather than asking for a UUID first. Pair with orboto_get_doc to read individual page bodies.',
@@ -285,9 +230,6 @@ export function makeListDocsInSpaceHandler(client) {
285
230
  structuredContent: { docs: [] },
286
231
  };
287
232
  }
288
- // Index by parent so we can render a simple indented tree. The
289
- // tree-walk runs in JS - the API hands us the flat list because
290
- // sort-order is per-parent and traversal is the caller's concern.
291
233
  const byParent = new Map();
292
234
  for (const r of rows) {
293
235
  const key = r.parentDocId ?? null;
@@ -304,7 +246,6 @@ export function makeListDocsInSpaceHandler(client) {
304
246
  for (const c of children) {
305
247
  const indent = ' '.repeat(depth);
306
248
  const iconPart = c.icon ? `${c.icon} ` : '';
307
- // ORB-1004 - show the typeable key (falls back to UUID).
308
249
  lines.push(`${indent}- ${iconPart}${c.title} (${c.docKey ?? c.id})`);
309
250
  walk(c.id, depth + 1);
310
251
  }
@@ -328,9 +269,6 @@ export function makeListDocsInSpaceHandler(client) {
328
269
  };
329
270
  };
330
271
  }
331
- // ---------------------------------------------------------------------------
332
- // orboto_create_doc (ORB-913)
333
- // ---------------------------------------------------------------------------
334
272
  export const createDocToolConfig = {
335
273
  title: 'Create a doc page',
336
274
  description: 'Create a new wiki page in `spaceId` with the supplied Markdown body. Unlike orboto_ingest_url / orboto_ingest_file (which derive content from an external source), this is the plain "I have the Markdown, make me a page" path. Title is required; content is optional (creates an empty page). Returns the new doc row including its UUID and its human-readable doc key (e.g. ORB-D12).',
@@ -370,9 +308,6 @@ export function makeCreateDocHandler(client) {
370
308
  };
371
309
  };
372
310
  }
373
- // ---------------------------------------------------------------------------
374
- // orboto_update_doc (ORB-913)
375
- // ---------------------------------------------------------------------------
376
311
  export const updateDocToolConfig = {
377
312
  title: 'Update a doc page',
378
313
  description: 'Patch a doc\'s title / content / visibility / parent / icon. Content-only updates do not require sending the title. Every body or title change snapshots the previous version into the revision history automatically - agents can roll back via orboto_restore_doc_revision (when that tool ships in Phase 5). Re-embeds the doc when content or title change.',
@@ -417,9 +352,6 @@ export function makeUpdateDocHandler(client) {
417
352
  };
418
353
  };
419
354
  }
420
- // ---------------------------------------------------------------------------
421
- // orboto_delete_doc (ORB-913)
422
- // ---------------------------------------------------------------------------
423
355
  export const deleteDocToolConfig = {
424
356
  title: 'Delete a doc page',
425
357
  description: 'DESTRUCTIVE - removes the page from the tree. Revisions are NOT auto-cascaded; the API keeps them so an admin could in principle resurrect from history, but the page itself is gone from listings. System-generated primer docs refuse deletion (the regenerator re-creates them). Returns success silently; 404 is also silent (idempotent).',
@@ -438,9 +370,6 @@ export function makeDeleteDocHandler(client) {
438
370
  };
439
371
  };
440
372
  }
441
- // ---------------------------------------------------------------------------
442
- // orboto_move_doc (ORB-913)
443
- // ---------------------------------------------------------------------------
444
373
  export const moveDocToolConfig = {
445
374
  title: 'Move a doc page (reparent / reorder / cross-space)',
446
375
  description: 'Reparent a doc, change its sort order, or move it into a different space. All three target fields are optional but at least one must be supplied. Distinct from orboto_update_doc\'s parentDocId because the API endpoint emits a different WS event for the tree-rebuild path connected clients care about.',
@@ -477,9 +406,6 @@ export function makeMoveDocHandler(client) {
477
406
  };
478
407
  };
479
408
  }
480
- // ---------------------------------------------------------------------------
481
- // orboto_duplicate_doc_space (ORB-918)
482
- // ---------------------------------------------------------------------------
483
409
  export const duplicateDocSpaceToolConfig = {
484
410
  title: 'Duplicate a doc space (clone the space + every doc inside it)',
485
411
  description: 'Fork a doc space along with its entire doc tree. The new space\'s name becomes `<source> (copy)`, parent-child relationships in the tree are preserved via UUID remap. Useful when an agent wants to iterate on a runbook space without mutating the original.',
@@ -148,7 +148,7 @@ describe('orboto_list_docs_in_space', () => {
148
148
  sortOrder: 0,
149
149
  };
150
150
  it('renders an indented tree text + flat structured list', async () => {
151
- stubJSON([{ json: [CHILD, ROOT] /* deliberately unsorted to verify the local sort */ }]);
151
+ stubJSON([{ json: [CHILD, ROOT] }]);
152
152
  const res = await makeListDocsInSpaceHandler(client)({ spaceId: PROJECT_SCOPED_SPACE.id });
153
153
  const text = res.content[0].text;
154
154
  expect(text.indexOf('Architecture')).toBeLessThan(text.indexOf('Queue worker'));
@@ -229,8 +229,6 @@ describe('orboto_create_doc', () => {
229
229
  });
230
230
  });
231
231
  describe('orboto_update_doc', () => {
232
- // ORB-1084 - write tools accept the human-readable doc key: the key
233
- // resolves via /docs/by-key, then the PATCH targets the UUID.
234
232
  it('resolves a doc key to the UUID before writing', async () => {
235
233
  const calls = stubJSON([
236
234
  { json: DOC }, // by-key resolve
@@ -1,15 +1,5 @@
1
1
  /**
2
2
  * ORB-1309 - `orboto_embedding_status`.
3
- *
4
- * Operator diagnostic for the embedding pipeline: provider/model/dims, coverage
5
- * (embedded vs total, and how many are PENDING - never-embedded / stale - per
6
- * tickets / comments / docs and overall), the circuit-breaker state, and when
7
- * the last embedding was written. Wraps GET /admin/ai/embedding-status.
8
- *
9
- * `orboto_ai_status` only reports whether embeddings are configured; this is the
10
- * deeper surface for "why is semantic search / duplicate detection / ask-docs
11
- * stale" - a tripped breaker, a stuck queue, or a provider that stopped
12
- * responding. Admin:ai:read gated (403 for non-admin callers).
13
3
  */
14
4
  import { z } from 'zod';
15
5
  export const embeddingStatusToolConfig = {
@@ -52,12 +42,9 @@ export function makeEmbeddingStatusHandler(client) {
52
42
  lines.push(`Coverage: ${o.embedded} / ${o.embeddable} embeddable embedded · ${o.pending} pending${o.noContent > 0 ? ` · ${o.noContent} with no embeddable content (excluded)` : ''}`);
53
43
  lines.push(` tickets ${s.coverage.ticket.embedded}/${s.coverage.ticket.embeddable} · comments ${s.coverage.comment.embedded}/${s.coverage.comment.embeddable} · docs ${s.coverage.doc.embedded}/${s.coverage.doc.embeddable} (embeddable)`);
54
44
  lines.push(`Circuit breaker: ${s.breaker.tripped ? `TRIPPED - ${s.breaker.lastTrippedReason ?? 'unknown reason'} (${s.breaker.consecutiveFailures} consecutive failures)` : 'ok'}`);
55
- // ORB-1715 - a billing gate is the one cause the customer can fix
56
- // themselves; name it instead of letting it read as a provider fault.
57
45
  if (s.billingGate?.gated) {
58
46
  lines.push(`BILLING GATE: managed AI is paused for billing${s.billingGate.reason ? ` (${s.billingGate.reason})` : ''} since ${s.billingGate.blockedAt ?? 'unknown'}. The workspace admin resolves this in their orboto account; queued items embed automatically after the unblock.`);
59
47
  }
60
- // ORB-1719 - spend visibility so an agent can warn BEFORE the gate.
61
48
  if (s.spend?.allowanceGrantMicrocents != null && s.spend.allowanceConsumedMicrocents != null) {
62
49
  const eur = (mc) => (mc / 1_000_000).toFixed(2);
63
50
  lines.push(`Included AI this month: ${eur(s.spend.allowanceConsumedMicrocents)} of ${eur(s.spend.allowanceGrantMicrocents)} EUR used${s.spend.allowanceRemainingMicrocents != null ? ` · ${eur(s.spend.allowanceRemainingMicrocents)} EUR left` : ''} · overage ${s.spend.overageMode ?? 'unknown'}`);
@@ -57,7 +57,6 @@ describe('tools/embedding-status', () => {
57
57
  const text = result.content[0].text;
58
58
  expect(text).toContain('BILLING GATE');
59
59
  expect(text).toContain('allowance_exhausted');
60
- // Contract guarantee: never vendor prose on any surface.
61
60
  expect(text).not.toContain('Key is blocked');
62
61
  });
63
62
  it('surfaces the spend state so agents can warn before the gate (ORB-1719)', async () => {
@@ -2,24 +2,12 @@
2
2
  * ORB-1910 - `orboto_report_feedback`: file a bug report / feedback / feature
3
3
  * request with the operator from inside the workspace (epic ORB-1907).
4
4
  *
5
- * Thin wrapper over the tenant relay (`POST /feedback`, ORB-1908): the
6
- * instance validates the closed schema, normalises the text and forwards
7
- * the report to the control plane with its own credential - this tool never
8
- * talks to the control plane and never learns the reporter's identity. It
9
- * checks `GET /feedback/availability` first so a self-hosted instance gets a
10
- * plain refusal naming the public issue tracker instead of a 503.
11
- *
12
- * The result carries the report id ONLY. The body is never echoed back into
13
- * the model's context: a report may quote hostile text (a bug reproduction
14
- * with an injected instruction), and re-reading it would be the one way this
15
- * tool could turn data into an instruction.
5
+ * @see ORB-1908
16
6
  */
17
7
  import { z } from 'zod';
18
8
  const PUBLIC_TRACKER = 'https://github.com/orboto/orboto-cli/issues';
19
9
  export const reportFeedbackToolConfig = {
20
10
  title: 'Send feedback or a bug report to the operator',
21
- // ORB-1805 - kept short: every client pays for this text at connect;
22
- // orboto_help serves the full contract (caps, attachment policy, privacy).
23
11
  description: 'File a bug report, feedback or feature request with the orboto operator from inside this workspace. Relayed without your identity; up to 3 png/jpeg/txt attachments (2 MB each, base64). Refused on self-hosted instances without an operator link. Answers with the report id only.',
24
12
  inputSchema: z.object({
25
13
  kind: z.enum(['bug', 'feedback', 'feature']),
@@ -37,8 +25,6 @@ export const reportFeedbackToolConfig = {
37
25
  contentBase64: z.string().min(1),
38
26
  })).max(3).optional(),
39
27
  }).shape,
40
- // A write that creates one report on the operator side; nothing is
41
- // removed or overwritten, and sending twice files two reports.
42
28
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
43
29
  };
44
30
  export function makeReportFeedbackHandler(client) {
@@ -56,7 +56,6 @@ describe('orboto_report_feedback', () => {
56
56
  attachments: [{ filename: 'a.txt', mimetype: 'text/plain', contentBase64: 'aGk=' }],
57
57
  });
58
58
  expect(result.structuredContent).toEqual({ reportId: 'rep-9', kind: 'bug', attachments: 1 });
59
- // The body never comes back into the model's context.
60
59
  expect(JSON.stringify(result)).not.toContain('Ignore previous instructions');
61
60
  });
62
61
  it('refuses on an instance without an operator link and names the public tracker - no POST', async () => {
@@ -1,19 +1,7 @@
1
1
  /**
2
2
  * ORB-244 Phase B - `orboto_get_checklists`.
3
3
  *
4
- * Dedicated read tool for a ticket's checklists. Two callers want
5
- * this without paying for the full ticket payload:
6
- *
7
- * 1. Models asking "what's still unchecked on ACME-42?" - don't
8
- * need description + comments + git activity for that.
9
- * 2. Phase-C write tools (`orboto_check` / `orboto_uncheck`) - the
10
- * user usually wants to confirm the item exists before
11
- * toggling it, and this tool is the cheap round-trip.
12
- *
13
- * ORB-234 detail exposed here: when a checklist item links to
14
- * another ticket, `effectiveCompleted` mirrors that ticket's status
15
- * category. We surface the linked ticket so the model can explain
16
- * "item is done because [ORB-99] shipped" rather than just "done".
4
+ * @see ORB-234, ORB-99
17
5
  */
18
6
  import { z } from 'zod';
19
7
  import { resolveTicketByKey } from './shared.js';
@@ -42,7 +30,8 @@ export function makeGetChecklistsHandler(client) {
42
30
  const linkSuffix = i.linkedTicketKey
43
31
  ? ` ↪ [${i.linkedTicketKey}] ${i.linkedTicketTitle ?? ''} (${i.linkedTicketStatusCategory ?? 'unknown'})`
44
32
  : '';
45
- lines.push(`- [${i.effectiveCompleted ? 'x' : ' '}] ${i.content}${linkSuffix}`);
33
+ const metaSuffix = `${i.assigneeName ? ` @${i.assigneeName}` : ''}${i.dueDate ? ` (due ${i.dueDate})` : ''}`;
34
+ lines.push(`- [${i.effectiveCompleted ? 'x' : ' '}] ${i.content}${linkSuffix}${metaSuffix}`);
46
35
  }
47
36
  lines.push('');
48
37
  }
@@ -59,6 +48,9 @@ export function makeGetChecklistsHandler(client) {
59
48
  id: i.id,
60
49
  content: i.content,
61
50
  done: i.effectiveCompleted,
51
+ assignedTo: i.assignedTo ?? null,
52
+ assigneeName: i.assigneeName ?? null,
53
+ dueDate: i.dueDate ?? null,
62
54
  linkedTicket: i.linkedTicketKey ? {
63
55
  key: i.linkedTicketKey,
64
56
  title: i.linkedTicketTitle,
@@ -1,27 +1,7 @@
1
1
  /**
2
2
  * ORB-408 (Phase 3 of ORB-406) - `orboto_get_project_primer`.
3
3
  *
4
- * Returns the project's auto-generated AI Context Pack as a single
5
- * markdown blob, token-budget aware. AI agents call this as their
6
- * first read per session.
7
- *
8
- * What's actually in the primer (ORB-563):
9
- * - active milestones, ticket counts, recently-closed milestones
10
- * - top docs from the project space
11
- * - structured project facts (the `primer_facts` table - workspace +
12
- * project rows)
13
- * - recent activity, when the operator enabled it
14
- * - repo briefings (e.g. CLAUDE.md / AGENTS.md) ONLY when the
15
- * operator configured `repoFiles` AND the API host has those files
16
- * on its local filesystem. Most Coolify / SaaS deployments do
17
- * neither - agents must NOT assume CLAUDE.md / AGENTS.md content
18
- * is in the primer. If the team relies on CLAUDE.md / AGENTS.md
19
- * content cross-deployment, those conventions belong in
20
- * `primer_facts` instead.
21
- *
22
- * Sections that didn't make the budget come back in
23
- * `truncatedSections` so the agent can decide whether to bump the
24
- * budget for a follow-up call.
4
+ * @see ORB-563
25
5
  */
26
6
  import { z } from 'zod';
27
7
  import { resolveProjectByKey } from './shared.js';
@@ -42,10 +22,6 @@ export function makeGetProjectPrimerHandler(client) {
42
22
  if (maxTokens !== undefined)
43
23
  qs.set('max_tokens', String(maxTokens));
44
24
  const res = await client.get(`/projects/${project.id}/ai-primer?${qs.toString()}`);
45
- // ORB-1104 - the trimmed-sections note is now appended in-band by
46
- // assembleWithBudget, so res.markdown already carries it (and so do
47
- // baked snapshots + the raw markdown route). No manual append here,
48
- // otherwise it would double up.
49
25
  return {
50
26
  content: [
51
27
  {