@theronap/agnoclast-mcp 0.9.229 → 0.9.231

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.
@@ -27,3 +27,24 @@ export function renderRemoved(out, person) {
27
27
  for (const w of Array.isArray(out?.warnings) ? out.warnings : []) lines.push(`- ${one(w, 400)}`)
28
28
  return lines.join('\n')
29
29
  }
30
+
31
+ // ── leave_brain (2026-10-12): the same two-call contract, said to the person who is leaving ──
32
+ export function renderLeaveQuestion(out) {
33
+ const p = out?.preview ?? {}
34
+ const lines = [
35
+ `NOT LEFT — this needs the person's yes.${out?.stale ? ' (The picture changed since the last confirmation; this is the current one.)' : ''}`,
36
+ `Leave the brain "${one(p.brain, 80)}"? You lose your role there (${one(p.role, 40)}) and can no longer open anything in it.`,
37
+ 'Your login, your other brains and everything you wrote stay. Nothing is deleted; someone in the brain can invite you back.',
38
+ ]
39
+ for (const s of Array.isArray(p.summary) ? p.summary : []) lines.push(one(s, 400).replace(/^Their /, 'Your '))
40
+ lines.push(`Tell the person exactly this and ask. Only if they say yes: leave_brain again with the same brain and confirm: "${one(out?.confirm_token, 40)}".`)
41
+ return lines.join('\n')
42
+ }
43
+
44
+ export function renderLeft(out) {
45
+ const lines = [out?.alreadyInactive
46
+ ? 'You had already left this brain — nothing more to do.'
47
+ : `You left "${one(out?.orgName, 80)}". You can no longer open anything in it.`]
48
+ for (const w of Array.isArray(out?.warnings) ? out.warnings : []) lines.push(`- ${one(w, 400)}`)
49
+ return lines.join('\n')
50
+ }
package/lib/server.mjs CHANGED
@@ -22,7 +22,7 @@ import { repoFullNameFrom, workingFilesFrom } from './capture.mjs'
22
22
  import { findTranscript, createWorkedFilesTracker } from './working_files.mjs'
23
23
  import { spawnObligationWorker } from './obligations_worker.mjs'
24
24
  import { renderObligations, renderPageObligations, tierReads, who } from './obligations_render.mjs'
25
- import { renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
25
+ import { renderLeaveQuestion, renderLeft, renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
26
26
  import { renderSettings } from './settings_render.mjs'
27
27
  import { renderConversationOffer, renderMaterializeBatch } from './intake_batch_render.mjs'
28
28
  import { obligationsForUnits, renderFilingObligations, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
@@ -3221,6 +3221,40 @@ function renderNudge(payload) {
3221
3221
  },
3222
3222
  )
3223
3223
 
3224
+ // 2026-10-12. A member leaves a brain THEMSELVES — the remove_member path with the caller as the target.
3225
+ server.registerTool(
3226
+ 'leave_brain',
3227
+ {
3228
+ title: 'Leave a brain',
3229
+ description: "Take the person OUT of one of their own brains — ONLY when the person asked you to leave it. Never suggest or do it on your own. The FIRST call changes nothing: it answers with which brain, the role they would lose, and where every one of their pages would go (to a steward — usually their manager), plus a confirm token. Tell the person exactly that and ask. Only if they say yes, call again with the same `brain` and `confirm` set to that token. Never pass a confirm token the person has not just approved. Leaving is soft and per brain: their login, their other brains and everything they wrote stay; nothing is deleted. The last active owner of a brain cannot leave it — they must make another member an owner first.",
3230
+ inputSchema: {
3231
+ brain: z.string().optional().describe("which brain to leave — its name or org id (from my_brains). Required if the person belongs to more than one brain; if two of their brains share that name, pass the org id"),
3232
+ confirm: z.string().optional().describe('ONLY after the person has seen what leaving does and said yes: the confirm token from the first call'),
3233
+ },
3234
+ },
3235
+ async ({ brain, confirm }) => {
3236
+ let res
3237
+ try {
3238
+ res = await fetchCortex(`${BASE}/api/members/leave`, {
3239
+ method: 'POST',
3240
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3241
+ body: JSON.stringify({ brain, confirm }),
3242
+ })
3243
+ } catch (e) { return toolError(`Could not reach Agnoclast: ${e.message}`) }
3244
+ const out = await res.json().catch(() => null)
3245
+ if (res.status === 409 && out?.error === 'confirm_leave') {
3246
+ return { content: [{ type: 'text', text: renderLeaveQuestion(out) }] }
3247
+ }
3248
+ if (!res.ok) {
3249
+ const cands = Array.isArray(out?.candidates) && out.candidates.length
3250
+ ? ` Candidates: ${out.candidates.map((c) => `"${c.brain}" (org id ${c.orgId})`).join(', ')}.`
3251
+ : ''
3252
+ return toolError(`Not left: ${out?.detail ?? out?.error ?? res.status}${cands}`)
3253
+ }
3254
+ return { content: [{ type: 'text', text: renderLeft(out) }] }
3255
+ },
3256
+ )
3257
+
3224
3258
  server.registerTool(
3225
3259
  'set_obligation_tier',
3226
3260
  {
@@ -5177,19 +5211,22 @@ function renderNudge(payload) {
5177
5211
  description: 'Set the CURRENCY of one of YOUR OWN authored pages so the brain can filter out stale info: "current" (reflects reality now — the default), "superseded" (replaced by a newer page — pass superseded_by with that page\'s name), or "historical" (kept for the record, no longer current). Optionally set modality: "reality" (as-built), "plan" (intended, not yet real), or "construction" (being built now). Call this the moment your understanding changes that a page is no longer the live truth — e.g. right after you author the new reality, mark the old page superseded. Owner-only.',
5178
5212
  inputSchema: {
5179
5213
  kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
5180
- name: z.string().describe('the exact page name'),
5214
+ name: z.string().optional().describe('the exact page name (or pass `ref` instead — one of the two is required)'),
5215
+ ref: z.string().optional().describe('the page\'s stable id, printed as `ref:` by read_page. PREFER THIS over name when you have it: a ref is unique across brains, so it never hits ambiguous_page_name.'),
5216
+ brain: z.string().optional().describe('optional: which brain the named page lives in, when the same name exists in several'),
5181
5217
  validity: z.enum(['current', 'superseded', 'historical']).describe('the currency state'),
5182
5218
  modality: z.enum(['reality', 'plan', 'construction']).optional().describe('what the page describes (optional)'),
5183
5219
  superseded_by: z.string().optional().describe('for superseded: the name of the page that replaced it'),
5184
5220
  },
5185
5221
  },
5186
- async ({ kind, name, validity, modality, superseded_by }) => {
5222
+ async ({ kind, name, ref, brain, validity, modality, superseded_by }) => {
5223
+ if (!name && !ref) return toolError('Could not set validity: pass `name` or `ref` (read_page prints the ref).')
5187
5224
  let res
5188
5225
  try {
5189
5226
  res = await fetchCortex(`${BASE}/api/brain/validity`, {
5190
5227
  method: 'POST',
5191
5228
  headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
5192
- body: JSON.stringify({ kind, name, validity, modality, superseded_by }),
5229
+ body: JSON.stringify({ kind, name, ref, brain, validity, modality, superseded_by }),
5193
5230
  })
5194
5231
  } catch (e) {
5195
5232
  return toolError(`Could not set validity: ${e.message}`)
@@ -5199,7 +5236,7 @@ function renderNudge(payload) {
5199
5236
  return toolError(`Could not set validity: ${d.message}`)
5200
5237
  }
5201
5238
  const out = await res.json()
5202
- return { content: [{ type: 'text', text: `Done — "${out.name}" is now ${out.validity}${out.modality ? ` / ${out.modality}` : ''} (${out.docs_updated} tier-doc(s) updated).` }] }
5239
+ return { content: [{ type: 'text', text: `Done — "${out.name ?? out.ref}" (${out.brain ?? 'brain ?'}) is now ${out.validity}${out.modality ? ` / ${out.modality}` : ''} (${out.docs_updated} tier-doc(s) updated).` }] }
5203
5240
  },
5204
5241
  )
5205
5242
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.229",
3
+ "version": "0.9.231",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {