@theronap/cortex-mcp 0.9.133 → 0.9.135
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 +53 -2
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -2253,6 +2253,45 @@ function renderNudge(payload) {
|
|
|
2253
2253
|
},
|
|
2254
2254
|
)
|
|
2255
2255
|
|
|
2256
|
+
server.registerTool(
|
|
2257
|
+
'supersede_record',
|
|
2258
|
+
{
|
|
2259
|
+
title: 'Mark a record as no longer the answer',
|
|
2260
|
+
description: "Say that a record is wrong, outdated or replaced — WITHOUT deleting it. Use it when you re-capture something properly and the old copy should stop showing up: a transcript filed with a bad occurred_at, a duplicate capture, a low-fidelity copy replaced by a content_path one. \u26a0 NOTHING IS DESTROYED AND THIS IS REVERSIBLE (`restore: true`). The row, its page attachments and its containment all stay exactly as they were; only the READS change — it disappears from its pages, from its container's contents, and from the \"holds N\" count on find_sessions. It stays SEARCHABLE on purpose: hiding a record from search would be most of the way to deleting it without the audit trail. \u26a0 THERE IS NO DELETE, DELIBERATELY (ADR-0049) — 13 of the 15 foreign keys to a record CASCADE, so a real delete would unfile everything inside a container, drop clearance anchors and rewrite the claim ledger, while still leaving revision rows behind. If you actually need content destroyed rather than hidden, say so to the user rather than reaching for this. \u26a0 REFUSED ON A CONTAINER (a class session, a meeting): hiding an event that still holds other records leaves their placements pointing at nothing. Supersede the records INSIDE it instead.",
|
|
2261
|
+
inputSchema: {
|
|
2262
|
+
record_id: z.string().describe('the record that is no longer the answer'),
|
|
2263
|
+
reason: z.string().optional().describe('WHY it is no longer right — required unless restoring, and the one thing that cannot be inferred later'),
|
|
2264
|
+
superseded_by: z.string().optional().describe('the record that REPLACES it, if there is one. Must be in the same brain. Optional: "this is wrong" is a complete statement on its own.'),
|
|
2265
|
+
restore: z.boolean().optional().describe('true = undo a previous supersede and make it current again'),
|
|
2266
|
+
},
|
|
2267
|
+
},
|
|
2268
|
+
async ({ record_id, reason, superseded_by, restore }) => {
|
|
2269
|
+
let res
|
|
2270
|
+
try {
|
|
2271
|
+
res = await fetchCortex(`${BASE}/api/records/supersede`, {
|
|
2272
|
+
method: 'POST',
|
|
2273
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2274
|
+
body: JSON.stringify({ recordId: record_id, reason, supersededBy: superseded_by, restore }),
|
|
2275
|
+
})
|
|
2276
|
+
} catch (e) {
|
|
2277
|
+
return toolError(`Could not reach Agnoclast: ${e.message}`)
|
|
2278
|
+
}
|
|
2279
|
+
const body = await res.json().catch(() => ({}))
|
|
2280
|
+
if (!res.ok || !body?.ok) {
|
|
2281
|
+
return toolError(`Could not supersede ${record_id}: ${body?.error ?? res.status}${body?.detail ? ` \u2014 ${body.detail}` : ''}`)
|
|
2282
|
+
}
|
|
2283
|
+
if (restore) {
|
|
2284
|
+
return { content: [{ type: 'text', text: `Restored ${record_id} — it is a current record again and reappears on its pages and in its container.` }] }
|
|
2285
|
+
}
|
|
2286
|
+
const l = [`Superseded ${record_id}.`]
|
|
2287
|
+
if (body.supersededBy) l.push(`Replaced by ${body.supersededBy}.`)
|
|
2288
|
+
// \u26a0 SAY WHAT DID NOT HAPPEN. The caller's next question is always whether this deleted
|
|
2289
|
+
// something. It did not, and a reader who assumes it did will not think to restore.
|
|
2290
|
+
l.push('Nothing was deleted — the row, its page attachments and its containment are intact. It is hidden from its pages, its container contents and session holds-counts, and stays searchable. `restore: true` reverses this.')
|
|
2291
|
+
return { content: [{ type: 'text', text: l.join('\n') }] }
|
|
2292
|
+
},
|
|
2293
|
+
)
|
|
2294
|
+
|
|
2256
2295
|
server.registerTool(
|
|
2257
2296
|
'find_sessions',
|
|
2258
2297
|
{
|
|
@@ -2379,7 +2418,7 @@ function renderNudge(payload) {
|
|
|
2379
2418
|
'capture_meeting',
|
|
2380
2419
|
{
|
|
2381
2420
|
title: 'Capture a meeting transcript, joined to its calendar event',
|
|
2382
|
-
description: "Capture a meeting transcript you were given — a paste, an export, notes — as a real record joined to the calendar event it came from. ⚠ CALL IT TWICE. Without `calendar_record_id` it WRITES NOTHING and returns candidate calendar events near that time; you pick one and call again. Overlap alone is not an answer — 90 of 120 calendar records are recurring birthdays and an all-day event overlaps its whole day, so each candidate carries `caveats` saying why it might be wrong. Read them before choosing. WHY THE JOIN MATTERS: a pasted transcript has no attendee list, so on its own it is reachable from nobody. The calendar event's attendee list is authoritative, and the captured meeting inherits it — that is what makes the transcript findable from the people who were in it. The brain follows from the matched event; you never pick one. Attendees who have no page come back as `unclaimed` — each is a person in the room nobody has authored, and authoring one reaches
|
|
2421
|
+
description: "Capture a meeting transcript you were given — a paste, an export, notes — as a real record joined to the calendar event it came from. ⚠ CALL IT TWICE. Without `calendar_record_id` it WRITES NOTHING and returns candidate calendar events near that time; you pick one and call again. Overlap alone is not an answer — 90 of 120 calendar records are recurring birthdays and an all-day event overlaps its whole day, so each candidate carries `caveats` saying why it might be wrong. Read them before choosing. WHY THE JOIN MATTERS: a pasted transcript has no attendee list, so on its own it is reachable from nobody. The calendar event's attendee list is authoritative, and the captured meeting inherits it — that is what makes the transcript findable from the people who were in it. The brain follows from the matched event; you never pick one. Attendees who have no page come back as `unclaimed` — each is a person in the room nobody has authored, and authoring one reaches their EXISTING records too, not only future meetings: claiming their identifier heals history in the same statement. Measured 2026-09-03 — ten calendar records spanning two months attached 49ms after the claim. ⚠ THE CONFIRMATION DOES NOT TELL YOU THAT. It says only that future events will attach, so a heal that just moved ten records is indistinguishable from one that did nothing. Verify by reading the page, never by trusting the reply. Meetings are stored `scoped`, never org-wide.",
|
|
2383
2422
|
inputSchema: {
|
|
2384
2423
|
occurred_at: z.string().describe('when the meeting STARTED, ISO 8601. Required for both the proposal and the capture.'),
|
|
2385
2424
|
ends_at: z.string().optional().describe('when it ended, ISO 8601. Omit and the calendar event\'s end is inherited. NEVER guess one — unknown must stay unknown.'),
|
|
@@ -3300,7 +3339,19 @@ function renderNudge(payload) {
|
|
|
3300
3339
|
const out = await res.json().catch(() => null)
|
|
3301
3340
|
if (!res.ok) return toolError(`Could not set routing identifier: ${out?.error ?? res.status}`)
|
|
3302
3341
|
const set = out?.set?.join(', ') ?? identifier
|
|
3303
|
-
|
|
3342
|
+
// ⚠ SAY WHAT ALREADY HAPPENED, NOT ONLY WHAT WILL. The claim heals history in the same
|
|
3343
|
+
// statement, so by the time this renders, existing records are ALREADY attached. The previous
|
|
3344
|
+
// wording mentioned only future events — true, and load-bearingly incomplete, which is harder
|
|
3345
|
+
// to catch than a false statement because checking it confirms it.
|
|
3346
|
+
// A ZERO IS EXPLAINED, not printed bare: an unexplained 0 recreates the same ambiguity one
|
|
3347
|
+
// level down, and identity claims (email:, thread:) attach only the CLAIMANT'S OWN records.
|
|
3348
|
+
const healed = typeof out?.attachedExisting === 'number' ? out.attachedExisting : null
|
|
3349
|
+
const healedLine = healed === null
|
|
3350
|
+
? ''
|
|
3351
|
+
: healed > 0
|
|
3352
|
+
? ` ${healed} existing record(s) ALREADY attached to this page as a result.`
|
|
3353
|
+
: ' 0 existing records attached — either nothing carries this identifier yet, or records do but belong to another owner (an identity claim only attaches your own).'
|
|
3354
|
+
return { content: [{ type: 'text', text: `Routing identifier set on document ${out?.documentId ?? '?'}: ${set}.${healedLine} Future matching events will attach here too (body mentions alone will not).` }] }
|
|
3304
3355
|
},
|
|
3305
3356
|
)
|
|
3306
3357
|
|
package/package.json
CHANGED