@theronap/agnoclast-mcp 0.9.252 → 0.9.254

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.
@@ -122,6 +122,8 @@ export function renderObligations(obs, now = Date.now(), extras = null) {
122
122
  const pages = Array.isArray(o.pages) ? o.pages.filter((p) => p?.origin !== 'fallback') : []
123
123
  if (pages.length) lines.push(` on: ${pages.slice(0, 4).map((p) => `“${String(p.title ?? '').replace(/\s+/g, ' ').trim().slice(0, 60)}”`).join(' · ')}${pages.length > 4 ? ` … and ${pages.length - 4} more` : ''}`)
124
124
  if (o.needsPage === true) lines.push(' ⚠ on no page anyone chose — set_obligation_pages')
125
+ // ADR-0093: who is waiting on it. Absent on an older server: nothing is said.
126
+ if (o.owedTo?.title) lines.push(` → owed to “${String(o.owedTo.title).replace(/\s+/g, ' ').trim().slice(0, 60)}” (ref ${o.owedTo.ref}) — read their page for context`)
125
127
  // Only when it is wider than the default: who else can see this when they look at its page.
126
128
  if (tierReads(o.tier)) lines.push(` visible to: ${tierReads(o.tier)} — they can see it; nobody but you is told about it`)
127
129
  // Whose idea it was, and whether it is on its way to someone else. Both change what to DO with it.
@@ -5,7 +5,7 @@
5
5
  // that web/lib/engine/release.test.ts pins to the server's registry.
6
6
 
7
7
  /** Tools any release flag gates — hidden when the server cannot be asked. Mirror of allGatedTools(). */
8
- export const GATED_FALLBACK = ['my_messages', 'read_conversation', 'send_message']
8
+ export const GATED_FALLBACK = ['my_messages', 'read_conversation', 'send_message', 'set_manager']
9
9
 
10
10
  export const RELEASE_FETCH_TIMEOUT_MS = 2500
11
11
 
package/lib/server.mjs CHANGED
@@ -3095,9 +3095,10 @@ function renderNudge(payload) {
3095
3095
  closed: z.boolean().optional().describe('true = also show discharged/cancelled ones'),
3096
3096
  limit: z.number().optional().describe('default 50'),
3097
3097
  page: z.string().optional().describe("a page's `ref:` as read_page prints it. Lists EVERYONE's open obligations on that page that the person may see — theirs first, then other people's under each owner's name. Use it when a page read says other people have obligations there and the person asks to see them; seeing one does not make it theirs."),
3098
+ owed_to: z.string().optional().describe("a person's page `ref:` — only what the person owes THEM (\"what do I owe Tanner?\"), across every page"),
3098
3099
  },
3099
3100
  },
3100
- async ({ closed, limit, page }) => {
3101
+ async ({ closed, limit, page, owed_to }) => {
3101
3102
  let res
3102
3103
  if (page) {
3103
3104
  try {
@@ -3111,6 +3112,7 @@ function renderNudge(payload) {
3111
3112
  const qs = new URLSearchParams()
3112
3113
  if (closed) qs.set('closed', 'true')
3113
3114
  if (limit) qs.set('limit', String(limit))
3115
+ if (owed_to) qs.set('owedTo', owed_to.trim())
3114
3116
  res = await fetchCortex(`${BASE}/api/obligations?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
3115
3117
  } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3116
3118
  const out = await res.json().catch(() => null)
@@ -3124,7 +3126,7 @@ function renderNudge(payload) {
3124
3126
  'track_obligation',
3125
3127
  {
3126
3128
  title: 'Record something you owe',
3127
- description: "Record something you owe: a subject, the page or pages it is about, optionally when it comes due, optionally whose reply would discharge it. ⚠ EVERY OBLIGATION LIVES ON A PAGE, CHOSEN NOW. `pages` is required: READ the page this is about first (read_page prints its `ref:`), then pass that ref — several when it genuinely belongs on several. Never guess a page to get past the check, and never default to the person's own page: if you cannot tell where it belongs, ask them. A page says where the obligation is FOUND, not who can see it: it is visible to its owner only unless the person later widens it (`set_obligation_tier`). ⚠ ONLY A DATE MAKES IT COME BACK. A DATED obligation is raised to the person's sessions by time, unasked, BEFORE it lapses: counted on the day it is due, and named when a stated clock time is within the hour. ⚠ A DEADLINE IS NOT WHEN TO START: pass `work_from` when the work needs lead time (a 10-day project opens 10 days ahead; an exam, days of study). From then it is named daily as \"work on now\", and once on the desktop when it opens. Once late it is never raised again, and an UNDATED one is never raised by anything — it surfaces only when someone asks with my_obligations. ⚠ NEVER INVENT A DUE DATE — and that matters more now, because an invented date will interrupt someone. Omit due_at when there genuinely is no deadline (a standing obligation); a guessed date is indistinguishable from a real one the moment it is stored, and it is the direct route to nagging about something that was never actually late. `anchor_identifier` is the useful part: give it the counterparty's address as `email:someone@example.com` and any record arriving from them is automatically offered as evidence that this may be done — computed when the record lands, not by a scanner.",
3129
+ description: "Record something you owe: a subject, the page or pages it is about, optionally when it comes due, optionally whose reply would discharge it. ⚠ EVERY OBLIGATION LIVES ON A PAGE, CHOSEN NOW. `pages` is required: READ the page this is about first (read_page prints its `ref:`), then pass that ref — several when it genuinely belongs on several. Never guess a page to get past the check, and never default to the person's OWN page: if you cannot tell where it belongs, ask them. A page says where the obligation is FOUND, not who can see it: it is visible to its owner only unless the person later widens it (`set_obligation_tier`). ⚠ ONLY A DATE MAKES IT COME BACK. A DATED obligation is raised to the person's sessions by time, unasked, BEFORE it lapses: counted on the day it is due, and named when a stated clock time is within the hour. ⚠ A DEADLINE IS NOT WHEN TO START: pass `work_from` when the work needs lead time (a 10-day project opens 10 days ahead; an exam, days of study). From then it is named daily as \"work on now\", and once on the desktop when it opens. Once late it is never raised again, and an UNDATED one is never raised by anything — it surfaces only when someone asks with my_obligations. ⚠ NEVER INVENT A DUE DATE: an invented date will interrupt someone. Omit due_at when there is no real deadline (a standing obligation); a guessed date is indistinguishable from a real one once stored, and leads straight to nagging about something that was never late. ⚠ OWED TO SOMEONE? Pass `owed_to`: the ref of their page (\"I owe Tanner a text\" → Tanner's page). It is attached, so a later agent reads it instead of asking who, and records from their email or phone count as evidence this may be done. `owed_to` is who WAITS; `for` is who OWES. `anchor_identifier` (e.g. `repo:owner/name`) covers evidence that is not a person.",
3128
3130
  inputSchema: {
3129
3131
  subject: z.string().describe('what is owed, in your own words'),
3130
3132
  pages: z.array(z.string()).min(1).describe('REQUIRED. The `ref:` of each page this obligation is about, exactly as read_page prints it. Read the page first; do not guess.'),
@@ -3132,18 +3134,19 @@ function renderNudge(payload) {
3132
3134
  note: z.string().optional().describe('with `for`: a short note to the person it is given to'),
3133
3135
  due_at: z.string().optional().describe('ISO 8601. OMIT when there is no real deadline — never guess one.'),
3134
3136
  work_from: z.string().optional().describe('ISO 8601: when work should START — the window opens (a bare date = the start of that day). Needs due_at, and must come before it. Estimate it from what the work involves; omit for things needing no lead time.'),
3135
- anchor_identifier: z.string().optional().describe('e.g. "email:ben@example.com" — records from them become evidence this may be done'),
3137
+ owed_to: z.string().optional().describe("the `ref:` of the page of the person (or org) this is owed TO — who is waiting on it. Read their page first. Omit when it is owed to nobody in particular."),
3138
+ anchor_identifier: z.string().optional().describe('e.g. "repo:owner/name" — records carrying it become evidence this may be done. For a person, prefer owed_to.'),
3136
3139
  source_record_id: z.string().optional().describe('the record this came from, if any'),
3137
- brain: z.string().optional().describe('required only if you belong to more than one brain'),
3140
+ brain: z.string().optional().describe('the brain\'s name or id — required only if you belong to more than one'),
3138
3141
  },
3139
3142
  },
3140
- async ({ subject, pages, due_at, work_from, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote }) => {
3143
+ async ({ subject, pages, due_at, work_from, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote, owed_to }) => {
3141
3144
  let res
3142
3145
  try {
3143
3146
  res = await fetchCortex(`${BASE}/api/obligations`, {
3144
3147
  method: 'POST',
3145
3148
  headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3146
- body: JSON.stringify({ subject, pages, dueAt: due_at, workFrom: work_from, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote }),
3149
+ body: JSON.stringify({ subject, pages, dueAt: due_at, workFrom: work_from, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote, owedTo: owed_to }),
3147
3150
  })
3148
3151
  } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3149
3152
  const out = await res.json().catch(() => null)
@@ -3154,9 +3157,9 @@ function renderNudge(payload) {
3154
3157
  ? (work_from ? `From ${work_from.slice(0, 10)} it is named daily as "work on now" (and once on the desktop). ` : '')
3155
3158
  + 'It will be raised to your sessions on the day it is due' + (/T\d{2}:\d{2}/.test(due_at) ? ', and again when it is within the hour.' : '.')
3156
3159
  : 'It has no date, so nothing will raise it \u2014 ask with my_obligations.'
3157
- const note = anchor_identifier
3158
- ? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done. ${nudge}`
3159
- : `\n${nudge}`
3160
+ const note = (owed_to ? '\nOwed to that person: their page is attached, and records from them will be offered as evidence this is done.' : '')
3161
+ + (anchor_identifier ? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done.` : '')
3162
+ + `\n${nudge}`
3160
3163
  const on = Number(out.pages) > 0 ? ` — on ${out.pages} page${out.pages === 1 ? '' : 's'}` : ''
3161
3164
  // Given to someone else: say plainly which of the two things happened. "Assigned" and "asked"
3162
3165
  // leave the obligation with different people, and the agent must not report one as the other.
@@ -3344,6 +3347,33 @@ function renderNudge(payload) {
3344
3347
  },
3345
3348
  )
3346
3349
 
3350
+ // 2026-10-21. Change who a member reports to — people and AI agents (Theron: "It's got to be real").
3351
+ server.registerTool(
3352
+ 'set_manager',
3353
+ {
3354
+ title: 'Change who someone reports to',
3355
+ description: "Move a member of one of the person's brains — a person or an AI agent — to report to someone else (a person or an agent). ONLY when the person asked you to. Everyone below the member moves with them. This changes permissions: the new manager and everyone above them can see the member's scoped work, and what the manager gives them becomes an assignment instead of a request; the old chain loses both. Reversible: move them back the same way. Only an owner or admin of the brain can do it; nobody reports to themselves or to someone in their own line of reports, and owners stay at the top. Use an email address when a name could match two people.",
3356
+ inputSchema: {
3357
+ member: z.string().describe('who moves: their email address, or their name as it appears in the brain'),
3358
+ manager: z.string().describe('who they will report to: an email address or a name as it appears in the brain'),
3359
+ brain: z.string().optional().describe('which brain — required if the person belongs to more than one'),
3360
+ },
3361
+ },
3362
+ async ({ member, manager, brain }) => {
3363
+ let res
3364
+ try {
3365
+ res = await fetchCortex(`${BASE}/api/members/manager`, {
3366
+ method: 'POST',
3367
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3368
+ body: JSON.stringify({ member, manager, brain }),
3369
+ })
3370
+ } catch (e) { return toolError(`Could not reach Agnoclast: ${e.message}`) }
3371
+ const out = await res.json().catch(() => null)
3372
+ if (!res.ok) return toolError(`Not moved: ${out?.detail ?? out?.error ?? res.status}`)
3373
+ return { content: [{ type: 'text', text: out.summary }] }
3374
+ },
3375
+ )
3376
+
3347
3377
  // 2026-10-12. A member leaves a brain THEMSELVES — the remove_member path with the caller as the target.
3348
3378
  server.registerTool(
3349
3379
  'leave_brain',
@@ -3446,6 +3476,32 @@ function renderNudge(payload) {
3446
3476
  },
3447
3477
  )
3448
3478
 
3479
+ server.registerTool(
3480
+ 'set_obligation_owed_to',
3481
+ {
3482
+ title: 'Say who an obligation is owed to',
3483
+ description: "Say who an existing obligation is owed TO — the person waiting on it (ADR-0093) — or clear it. Pass the `ref:` of their page (person or org), read first. Their page is attached to the obligation, my_obligations shows \"→ owed to …\", and records from any email, thread or phone claimed on that page become evidence it may be done. Pass an empty string to clear it; clearing does not take the obligation off their page (use set_obligation_pages for that). Owner only.",
3484
+ inputSchema: {
3485
+ obligation_id: z.string().describe('from my_obligations'),
3486
+ owed_to: z.string().describe("the person's page `ref:` as read_page prints it, or \"\" to clear"),
3487
+ },
3488
+ },
3489
+ async ({ obligation_id, owed_to }) => {
3490
+ let res
3491
+ try {
3492
+ res = await fetchCortex(`${BASE}/api/obligations/owed-to`, {
3493
+ method: 'POST',
3494
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3495
+ body: JSON.stringify({ obligationId: obligation_id, owedTo: owed_to }),
3496
+ })
3497
+ } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3498
+ const out = await res.json().catch(() => null)
3499
+ if (!res.ok) return toolError(`Could not set it: ${out?.detail ?? out?.error ?? res.status}`)
3500
+ const t = out?.owedTo?.title
3501
+ return { content: [{ type: 'text', text: t ? `Owed to “${String(t).replace(/\s+/g, ' ').trim().slice(0, 60)}” — their page is attached.` : 'No longer marked as owed to anyone. Its pages are unchanged.' }] }
3502
+ },
3503
+ )
3504
+
3449
3505
  server.registerTool(
3450
3506
  'propose_obligation',
3451
3507
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.252",
3
+ "version": "0.9.254",
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": {