@theronap/agnoclast-mcp 0.9.191 → 0.9.192
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/lib/server.mjs +25 -8
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -21,6 +21,22 @@ import { spawnObligationWorker } from './obligations_worker.mjs'
|
|
|
21
21
|
import { renderObligations } from './obligations_render.mjs'
|
|
22
22
|
import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, renderHomeProblem } from './custom_sources.mjs'
|
|
23
23
|
|
|
24
|
+
// PAGE tiers (ADR-0071). Six for a PAGE; a RECORD keeps three (`privacy` on log_session and
|
|
25
|
+
// set_record_privacy below) — an owner-relative tier is applied to a page's records when they are read
|
|
26
|
+
// and is never written onto one. The server validates against the same list (web/lib/engine/page_tiers.ts).
|
|
27
|
+
const PAGE_TIERS = ['accessible', 'down', 'reports', 'peers', 'scoped', 'confidential']
|
|
28
|
+
const PAGE_TIER_GLOSS = '"accessible" (everyone in the brain) · "down" (everyone below the owner) · "reports" (the owner\'s direct reports) · "peers" (everyone who shares the owner\'s manager) · "scoped" (the owner and their managers only) · "confidential" (the owner only, plus explicit grants)'
|
|
29
|
+
|
|
30
|
+
/** What an ownership transfer did to an owner-relative page's audience (ADR-0071) — or '' when nothing moved. */
|
|
31
|
+
function renderAudienceChange(change) {
|
|
32
|
+
if (!change) return ''
|
|
33
|
+
const names = (xs) => (Array.isArray(xs) ? xs.map((m) => m?.name).filter(Boolean).join(', ') : '')
|
|
34
|
+
const gains = names(change.gains)
|
|
35
|
+
const loses = names(change.loses)
|
|
36
|
+
if (!gains && !loses) return ''
|
|
37
|
+
return ` Its audience moved with its owner (${change.tier}):${gains ? ` now also read by ${gains}.` : ''}${loses ? ` No longer read by ${loses}.` : ''}`
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
// Reactive red-link triage (Mechanism 2). On a read_page miss, ask the server whether the name is a
|
|
25
41
|
// tracked wanted page, whether a bare node exists for it, and whether it's a deliberately demoted page,
|
|
26
42
|
// and turn that into an actionable prompt. Wording lives in ./red_link_triage.mjs (pure + tested).
|
|
@@ -2343,7 +2359,7 @@ function renderNudge(payload) {
|
|
|
2343
2359
|
base_version: z.string().describe('the `version` read_page prints for the SOURCE page (64-hex). REQUIRED — it is the concurrency check AND how the right brain is resolved.'),
|
|
2344
2360
|
reason: z.string().describe('WHY you are splitting, in one short phrase — recorded in page_history. Say what outgrew the page.'),
|
|
2345
2361
|
owner: z.string().optional().describe('omit to keep the SOURCE page\'s owner, so the child has exactly the same audience — almost always right. Naming a different user is an ownership change: it needs the page owner, or someone above them who can read the page, and must be an active member of this brain.'),
|
|
2346
|
-
tier: z.enum(
|
|
2362
|
+
tier: z.enum(PAGE_TIERS).optional().describe('omit to COPY the source page tier, which is almost always right. A LOOSER or merely DIFFERENT tier (peers from a reports page) changes who can read the moved text, so only the source page\'s owner may choose it; a STRICTER one is refused, since a split cannot tighten access.'),
|
|
2347
2363
|
},
|
|
2348
2364
|
},
|
|
2349
2365
|
async ({ name, headings, new_title, base_version, reason, owner, tier }) => {
|
|
@@ -4065,7 +4081,7 @@ function renderNudge(payload) {
|
|
|
4065
4081
|
org_id: z.string().describe('the org id of the brain to enumerate (from my_brains)'),
|
|
4066
4082
|
owner: z.string().optional().describe('only pages owned by this person — a user id or an EXACT display name. A name matching no member of the brain is an error, never a silently empty list. Owner applies to scoped/confidential pages; accessible pages have no owner.'),
|
|
4067
4083
|
updated_within_days: z.number().optional().describe('only pages touched in the last N days'),
|
|
4068
|
-
tier: z.enum(
|
|
4084
|
+
tier: z.enum(PAGE_TIERS).optional().describe('only pages at this tier'),
|
|
4069
4085
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('only nodes of this kind'),
|
|
4070
4086
|
validity: z.enum(['current', 'superseded', 'all']).optional().describe("default 'current'. Pass 'all' for the pre-2026-08 behavior, which mixed superseded pages in with nothing marking them."),
|
|
4071
4087
|
name_contains: z.string().optional().describe('case-insensitive substring match on the node name or page title'),
|
|
@@ -5053,12 +5069,12 @@ function renderNudge(payload) {
|
|
|
5053
5069
|
'set_page_privacy',
|
|
5054
5070
|
{
|
|
5055
5071
|
title: 'Change who can see a wiki page',
|
|
5056
|
-
description:
|
|
5072
|
+
description: `Re-tier a wiki page you own or may edit. ${PAGE_TIER_GLOSS}. Every tier but confidential is also readable by the page owner's managers. ⚠ reports / peers / down are RELATIVE TO THE PAGE'S OWNER, computed live: the audience is the OWNER's team, it changes when the owner moves in the org chart or the page is transferred, and ONLY the owner can move a page to or from one of them. They never change a record — records filed under the page are read by that same audience, each still capped by its own tier. Demoting a PROJECT page to scoped or confidential also demotes its evidence records (demote-only; each record's owner is notified and can revert). Promotions never touch records. Org admins may narrow any page to scoped or confidential, never widen.`,
|
|
5057
5073
|
inputSchema: {
|
|
5058
5074
|
kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
|
|
5059
5075
|
name: z.string().optional().describe('the exact page name (or pass `ref` instead — one of the two is required)'),
|
|
5060
5076
|
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 addresses exactly one page and never needs a brain to disambiguate it.'),
|
|
5061
|
-
tier: z.enum(
|
|
5077
|
+
tier: z.enum(PAGE_TIERS).describe('the new visibility tier'),
|
|
5062
5078
|
},
|
|
5063
5079
|
},
|
|
5064
5080
|
async ({ kind, name, ref, tier }) => {
|
|
@@ -5245,9 +5261,10 @@ function renderNudge(payload) {
|
|
|
5245
5261
|
name: z.string().optional().describe('the exact page name (or pass `ref` instead — one of the two is required)'),
|
|
5246
5262
|
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 addresses exactly one page and never needs a brain to disambiguate it.'),
|
|
5247
5263
|
to: z.string().describe('who becomes the owner — an org member\'s email, or their user id. Must be an ACTIVE member of the same brain as the page. Pass your own address to take ownership yourself.'),
|
|
5264
|
+
confirm_audience: z.string().optional().describe('ONLY after a refusal that lists who would start reading the page. A reports / peers / down page is addressed to its OWNER\'s team, so a new owner means a new audience; the first call is refused with the names and a confirm token. Pass that token here once the person asking has seen the list and means it. The token covers exactly that list — if the page or the org chart changed since, it is refused again with the new one. Never guess it and never send it on a first call.'),
|
|
5248
5265
|
},
|
|
5249
5266
|
},
|
|
5250
|
-
async ({ kind, name, ref, to }) => {
|
|
5267
|
+
async ({ kind, name, ref, to, confirm_audience }) => {
|
|
5251
5268
|
let res
|
|
5252
5269
|
try {
|
|
5253
5270
|
res = await fetchCortex(`${BASE}/api/brain/node-policy`, {
|
|
@@ -5255,7 +5272,7 @@ function renderNudge(payload) {
|
|
|
5255
5272
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
5256
5273
|
// No `tier`: a node has exactly one page (ADR-0064 §4), and the route resolves it. Sending a
|
|
5257
5274
|
// guessed tier is how this used to 404 on precisely the scoped/confidential pages it matters for.
|
|
5258
|
-
body: JSON.stringify({ kind, ...(name ? { name } : {}), ...(ref ? { ref } : {}), transfer_to: to }),
|
|
5275
|
+
body: JSON.stringify({ kind, ...(name ? { name } : {}), ...(ref ? { ref } : {}), transfer_to: to, ...(confirm_audience ? { confirm_audience } : {}) }),
|
|
5259
5276
|
})
|
|
5260
5277
|
} catch (e) {
|
|
5261
5278
|
return toolError(`Could not transfer ownership: ${e.message}`)
|
|
@@ -5271,7 +5288,7 @@ function renderNudge(payload) {
|
|
|
5271
5288
|
return {
|
|
5272
5289
|
content: [{
|
|
5273
5290
|
type: 'text',
|
|
5274
|
-
text: `Done — ${name ?? out.ref} now belongs to ${to}. The page is still ${out.tier}; transferring never re-tiers, so check that its audience is still right
|
|
5291
|
+
text: `Done — ${name ?? out.ref} now belongs to ${to}. The page is still ${out.tier}; transferring never re-tiers, so check that its audience is still right.${renderAudienceChange(out.audience_change)}`,
|
|
5275
5292
|
}],
|
|
5276
5293
|
}
|
|
5277
5294
|
},
|
|
@@ -5645,7 +5662,7 @@ function renderNudge(payload) {
|
|
|
5645
5662
|
heading: z.string().describe('e.g. Overview, Current state, Decisions, Open threads, People'),
|
|
5646
5663
|
body: z.string().describe('dense markdown WITH inline [[links]] where the prose references another node'),
|
|
5647
5664
|
})).describe('3-5 sections; the page body'),
|
|
5648
|
-
tier: z.enum(
|
|
5665
|
+
tier: z.enum(PAGE_TIERS).optional().describe('visibility tier. Omit for the safe default: scoped (you + your management chain) on nodes that support it — your user page, projects you own-scope — and accessible elsewhere (person/org pages are the shared wiki). Pass accessible explicitly when the page is meant for the whole org. For a team: reports (your direct reports), peers (everyone who shares your manager) or down (everyone below you) — each relative to YOU as the page\'s owner, so only on a page you own.'),
|
|
5649
5666
|
base_version: z.string().optional().describe('the `version` hash shown when you read this page (read_page) — REQUIRED when updating an existing page, so a concurrent edit is caught instead of clobbered. Omit only for a brand-new node. If the save returns "stale" or "read first", read_page again and retry with the fresh version.'),
|
|
5650
5667
|
reason: z.string().describe('WHY you are making this edit, in one short phrase — recorded permanently in page_history so a later reader can tell a routine addition from a correction. Say what CHANGED and what prompted it ("Ben pilot abandoned per Theron 07-17", "corrected: 0069 already widened the CHECK"), not what you did ("updated page"). This is the field that makes staleness auditable.'),
|
|
5651
5668
|
change_kind: z.enum(['add', 'correct', 'supersede', 'expand', 'retire']).optional().describe('what KIND of edit: "add" (new information), "correct" (the page said something FALSE — the currency-critical one), "supersede" (was true, now outdated by events), "expand" (elaborates, no claim changed), "retire" (putting the page or a section to rest). Be honest with "correct" — a page whose history shows repeated corrections is a page whose claims need checking, and that signal is the point.'),
|
package/package.json
CHANGED