@theronap/cortex-mcp 0.9.87 → 0.9.88
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 +90 -3
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -486,6 +486,45 @@ export async function runServer(version) {
|
|
|
486
486
|
},
|
|
487
487
|
)
|
|
488
488
|
|
|
489
|
+
server.registerTool(
|
|
490
|
+
'intake_discard',
|
|
491
|
+
{
|
|
492
|
+
title: 'Discard a private intake item',
|
|
493
|
+
description:
|
|
494
|
+
'The OTHER terminal outcome for a claimed intake unit: this is nothing, drop it. Use it for content that should never become a record — unsubscribe receipts, empty greetings, marketing blasts, a stranger\'s photo — instead of materializing junk into a brain because materialize was the only verb available. IRREVERSIBLE: the ciphertext and nonces are destroyed in the same transaction, and a content-free tombstone stops the connector re-delivering the unit. You must already hold the claim (discarding something you never read is refused), `reason` is required and is stored, and it is one item per call — a loop discarding a whole pile on one decision is a bulk job, not judgment.',
|
|
495
|
+
inputSchema: {
|
|
496
|
+
intakeItemId: z.string().describe('intake item uuid from intake_claim'),
|
|
497
|
+
reason: z.string().describe('why this is nothing — recorded on the claim, and the only surviving trace of the decision'),
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
async ({ intakeItemId, reason }) => {
|
|
501
|
+
let res
|
|
502
|
+
try {
|
|
503
|
+
res = await fetchCortex(`${BASE}/api/intake/discard`, {
|
|
504
|
+
method: 'POST',
|
|
505
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
506
|
+
body: JSON.stringify({ intakeItemId, reason }),
|
|
507
|
+
})
|
|
508
|
+
} catch (e) {
|
|
509
|
+
return toolError(`Could not discard: ${e.message}`)
|
|
510
|
+
}
|
|
511
|
+
const out = await res.json().catch(() => null)
|
|
512
|
+
if (!res.ok) {
|
|
513
|
+
if (out?.error === 'not_claimed') {
|
|
514
|
+
return toolError('You do not hold a claim on that item — intake_claim it first, so the discard follows from having read it.')
|
|
515
|
+
}
|
|
516
|
+
if (out?.error === 'already_materialized') {
|
|
517
|
+
return toolError('That unit already became a record. Discarding it now would orphan the record from its source — detach or retier the record instead.')
|
|
518
|
+
}
|
|
519
|
+
return toolError(`Could not discard: ${out?.error ?? res.status}${out?.detail ? ` — ${out.detail}` : ''}`)
|
|
520
|
+
}
|
|
521
|
+
if (out?.alreadyDiscarded) {
|
|
522
|
+
return { content: [{ type: 'text', text: 'Already discarded — nothing to do.' }] }
|
|
523
|
+
}
|
|
524
|
+
return { content: [{ type: 'text', text: 'Discarded. Content destroyed, tombstone written so the source cannot re-deliver it.' }] }
|
|
525
|
+
},
|
|
526
|
+
)
|
|
527
|
+
|
|
489
528
|
server.registerTool(
|
|
490
529
|
'intake_cleanup_status',
|
|
491
530
|
{
|
|
@@ -1654,16 +1693,19 @@ export async function runServer(version) {
|
|
|
1654
1693
|
},
|
|
1655
1694
|
)
|
|
1656
1695
|
|
|
1657
|
-
// ── Gate 4 record triage
|
|
1696
|
+
// ── Gate 4 record triage, over timeline_claims ────────────────────────────────────────────
|
|
1658
1697
|
// A connector event materializes in seconds and has no idea what the work WAS. The session that
|
|
1659
1698
|
// did the work knows exactly, and arrives later. These three tools are that handoff: look at what
|
|
1660
1699
|
// landed, claim what is yours, route it when you know where it goes.
|
|
1700
|
+
//
|
|
1701
|
+
// The ledger is `timeline_claims` (0105) — one claim discipline over one stream. Unclaimed means
|
|
1702
|
+
// NO claim row: absence IS the backlog, and nothing is written at ingest.
|
|
1661
1703
|
|
|
1662
1704
|
server.registerTool(
|
|
1663
1705
|
'pending_records',
|
|
1664
1706
|
{
|
|
1665
1707
|
title: 'Records waiting for a home',
|
|
1666
|
-
description: 'List connector records (GitHub pushes, PRs, email) that
|
|
1708
|
+
description: 'List recent connector records (GitHub pushes, PRs, email) that NOBODY HAS ATTENDED TO yet — no claim row in the Gate 4 ledger. Check this when your session starts if the headline count sounds related to what you are about to work on; records from your own recent commits are usually in here, and you are the only one who can recognize them as yours. Returns titles and current homes only, never payloads. Use view "sweep" on one record to get graded page-name candidates without reading any pages.',
|
|
1667
1709
|
inputSchema: {
|
|
1668
1710
|
view: z.enum(['digest', 'sweep']).optional().describe('digest = what is waiting (default); sweep = cheap graded candidates for one record'),
|
|
1669
1711
|
recordId: z.string().optional().describe('required for view "sweep"'),
|
|
@@ -1728,7 +1770,7 @@ export async function runServer(version) {
|
|
|
1728
1770
|
'claim_record',
|
|
1729
1771
|
{
|
|
1730
1772
|
title: 'Claim a pending record as your work',
|
|
1731
|
-
description: 'Say "this record is mine, I will route it once I know where it goes." Use it as soon as you recognize your own work in pending_records, even before you know the destination page — the claim
|
|
1773
|
+
description: 'Say "this record is mine, I will route it once I know where it goes." Use it as soon as you recognize your own work in pending_records, even before you know the destination page — the claim takes it out of the backlog so nothing else guesses at something you have real context on. Claims are leased and expire, so a dead session never holds a record hostage, and a record held by another LIVE session cannot be taken. Pass release=true to give one back when it turns out not to be yours — that deletes the claim, so the record looks untouched again rather than attended-to.',
|
|
1732
1774
|
inputSchema: {
|
|
1733
1775
|
recordId: z.string().describe('record id from pending_records'),
|
|
1734
1776
|
note: z.string().optional().describe('what you think this is — kept for audit'),
|
|
@@ -1790,6 +1832,51 @@ export async function runServer(version) {
|
|
|
1790
1832
|
},
|
|
1791
1833
|
)
|
|
1792
1834
|
|
|
1835
|
+
server.registerTool(
|
|
1836
|
+
'unroute_record',
|
|
1837
|
+
{
|
|
1838
|
+
title: 'Remove pages a record should not be on',
|
|
1839
|
+
description:
|
|
1840
|
+
'Detach pages a record does not belong on — the inverse of route_record, and the only way a wrong placement can be undone. route_record is purely ADDITIVE, so attaching more pages can never fix a bad one. Use this when you can see a record sitting on a page it has no real relationship to — the classic case is a fuzzy title match, e.g. a commit attached to a page merely because both contain a common word. Two things it will refuse rather than surprise you: it will not remove every attachment (a record with no home is invisible, which is worse than a wrong home — route or park it instead), and detaching the page that GOVERNS the tier can tighten the record but never republish it, since a widening is pinned and proposed for a human to confirm.',
|
|
1841
|
+
inputSchema: {
|
|
1842
|
+
recordId: z.string().describe('record id from pending_records'),
|
|
1843
|
+
documentIds: z.array(z.string()).describe('page document ids to REMOVE from this record'),
|
|
1844
|
+
reason: z.string().describe('why these placements are wrong — recorded with the removal'),
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1847
|
+
async ({ recordId, documentIds, reason }) => {
|
|
1848
|
+
let res
|
|
1849
|
+
try {
|
|
1850
|
+
res = await fetchCortex(`${BASE}/api/brain/triage`, {
|
|
1851
|
+
method: 'POST',
|
|
1852
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
1853
|
+
body: JSON.stringify({ action: 'detach', recordId, documentIds, reason }),
|
|
1854
|
+
})
|
|
1855
|
+
} catch (e) {
|
|
1856
|
+
return toolError(`Could not detach: ${e.message}`)
|
|
1857
|
+
}
|
|
1858
|
+
const out = await res.json().catch(() => null)
|
|
1859
|
+
if (!res.ok) {
|
|
1860
|
+
// These two are guard rails, not faults — say what to do instead of just naming the code.
|
|
1861
|
+
if (out?.error === 'would_strand') {
|
|
1862
|
+
return toolError(`Refused: ${out.detail ?? 'that would leave the record with no pages at all.'}`)
|
|
1863
|
+
}
|
|
1864
|
+
if (out?.error === 'not_attached') {
|
|
1865
|
+
return toolError(`Nothing removed: ${out.detail ?? 'those pages are not attached to this record.'}`)
|
|
1866
|
+
}
|
|
1867
|
+
return toolError(`Could not detach: ${out?.error ?? res.status}${out?.detail ? ` — ${out.detail}` : ''}`)
|
|
1868
|
+
}
|
|
1869
|
+
const n = out?.detached?.length ?? 0
|
|
1870
|
+
const left = out?.remaining ?? 0
|
|
1871
|
+
return {
|
|
1872
|
+
content: [{
|
|
1873
|
+
type: 'text',
|
|
1874
|
+
text: `Detached ${n} page(s); ${left} attachment(s) remain. Record tier: ${out?.privacy ?? 'unchanged'}. Recorded as a session judgment.`,
|
|
1875
|
+
}],
|
|
1876
|
+
}
|
|
1877
|
+
},
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1793
1880
|
server.registerTool(
|
|
1794
1881
|
'snooze_red_link',
|
|
1795
1882
|
{
|