@theronap/agnoclast-mcp 0.9.197 → 0.9.198
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/obligations_render.mjs +10 -0
- package/lib/server.mjs +32 -4
- package/package.json +1 -1
|
@@ -19,16 +19,26 @@
|
|
|
19
19
|
export function renderObligations(obs, now = Date.now()) {
|
|
20
20
|
if (!obs?.length) return 'Nothing open.'
|
|
21
21
|
const proposed = obs.filter((o) => o.state === 'proposed').length
|
|
22
|
+
// ADR-0073: every obligation lives on a page. `needsPage` is sent only by a server that knows the
|
|
23
|
+
// rule, so against an older one this count is zero and the output is exactly what it was.
|
|
24
|
+
const homeless = obs.filter((o) => o.needsPage === true).length
|
|
22
25
|
const lines = [
|
|
23
26
|
proposed
|
|
24
27
|
? `${obs.length} open — ${proposed} of them PROPOSED: extracted from a record, not entered by you. A wrong one: resolve_obligation with state "cancelled".`
|
|
25
28
|
: `${obs.length} open:`,
|
|
29
|
+
...(homeless
|
|
30
|
+
? [`${homeless} of them ${homeless === 1 ? 'is' : 'are'} on NO PAGE anyone chose — marked ⚠ below. Read the page each belongs on, then \`set_obligation_pages\`. Do not guess: ask when it is not clear.`]
|
|
31
|
+
: []),
|
|
26
32
|
'',
|
|
27
33
|
]
|
|
28
34
|
for (const o of obs) {
|
|
29
35
|
const when = describeDue(o, now)
|
|
30
36
|
lines.push(`${o.id}`)
|
|
31
37
|
lines.push(` ${o.subject} — ${when}${o.state === 'snoozed' ? ' (snooze expired)' : ''}${o.state === 'proposed' ? ' [PROPOSED]' : ''}`)
|
|
38
|
+
// Where it lives. Page titles are other people's text as much as subjects are: quoted, one line.
|
|
39
|
+
const pages = Array.isArray(o.pages) ? o.pages.filter((p) => p?.origin !== 'fallback') : []
|
|
40
|
+
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` : ''}`)
|
|
41
|
+
if (o.needsPage === true) lines.push(' ⚠ on no page anyone chose — set_obligation_pages')
|
|
32
42
|
// ⚠ EVIDENCE IS RENDERED AS A QUESTION, NEVER AS A VERDICT. The phrasing is the feature:
|
|
33
43
|
// "is this done?" costs the reader a second, "you still owe this" about finished work costs
|
|
34
44
|
// the channel its credibility.
|
package/lib/server.mjs
CHANGED
|
@@ -2918,22 +2918,23 @@ function renderNudge(payload) {
|
|
|
2918
2918
|
'track_obligation',
|
|
2919
2919
|
{
|
|
2920
2920
|
title: 'Record something you owe',
|
|
2921
|
-
description: "Record something you owe: a subject, optionally when it comes due, optionally whose reply would discharge it. ⚠ 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. 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.",
|
|
2921
|
+
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; it stays visible to its owner only. ⚠ 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. 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.",
|
|
2922
2922
|
inputSchema: {
|
|
2923
2923
|
subject: z.string().describe('what is owed, in your own words'),
|
|
2924
|
+
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.'),
|
|
2924
2925
|
due_at: z.string().optional().describe('ISO 8601. OMIT when there is no real deadline — never guess one.'),
|
|
2925
2926
|
anchor_identifier: z.string().optional().describe('e.g. "email:ben@example.com" — records from them become evidence this may be done'),
|
|
2926
2927
|
source_record_id: z.string().optional().describe('the record this came from, if any'),
|
|
2927
2928
|
brain: z.string().optional().describe('required only if you belong to more than one brain'),
|
|
2928
2929
|
},
|
|
2929
2930
|
},
|
|
2930
|
-
async ({ subject, due_at, anchor_identifier, source_record_id, brain }) => {
|
|
2931
|
+
async ({ subject, pages, due_at, anchor_identifier, source_record_id, brain }) => {
|
|
2931
2932
|
let res
|
|
2932
2933
|
try {
|
|
2933
2934
|
res = await fetchCortex(`${BASE}/api/obligations`, {
|
|
2934
2935
|
method: 'POST',
|
|
2935
2936
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2936
|
-
body: JSON.stringify({ subject, dueAt: due_at, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain }),
|
|
2937
|
+
body: JSON.stringify({ subject, pages, dueAt: due_at, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain }),
|
|
2937
2938
|
})
|
|
2938
2939
|
} catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
|
|
2939
2940
|
const out = await res.json().catch(() => null)
|
|
@@ -2946,7 +2947,34 @@ function renderNudge(payload) {
|
|
|
2946
2947
|
const note = anchor_identifier
|
|
2947
2948
|
? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done. ${nudge}`
|
|
2948
2949
|
: `\n${nudge}`
|
|
2949
|
-
|
|
2950
|
+
const on = Number(out.pages) > 0 ? ` — on ${out.pages} page${out.pages === 1 ? '' : 's'}` : ''
|
|
2951
|
+
return { content: [{ type: 'text', text: `Recorded: ${subject}${due_at ? ` (due ${due_at.slice(0, 10)})` : ' (no deadline)'}${on}\n${out.id}${note}` }] }
|
|
2952
|
+
},
|
|
2953
|
+
)
|
|
2954
|
+
|
|
2955
|
+
server.registerTool(
|
|
2956
|
+
'set_obligation_pages',
|
|
2957
|
+
{
|
|
2958
|
+
title: 'Say which pages an obligation lives on',
|
|
2959
|
+
description: "Say which page or pages an existing obligation lives on (ADR-0073). The list you pass REPLACES what it had, so pass the complete set. Use it for an obligation my_obligations marks ⚠ — one created before pages were required, or one a machine put on its owner's own page for want of anywhere better — and to correct a wrong placement. READ the pages first and choose; when it is not clear where something belongs, ask the person rather than guessing. An obligation can never be left on zero pages, only its owner can place it, and it stays visible to its owner only.",
|
|
2960
|
+
inputSchema: {
|
|
2961
|
+
obligation_id: z.string().describe('from my_obligations'),
|
|
2962
|
+
pages: z.array(z.string()).min(1).describe('the COMPLETE list of page refs it should live on, as read_page prints them'),
|
|
2963
|
+
},
|
|
2964
|
+
},
|
|
2965
|
+
async ({ obligation_id, pages }) => {
|
|
2966
|
+
let res
|
|
2967
|
+
try {
|
|
2968
|
+
res = await fetchCortex(`${BASE}/api/obligations/pages`, {
|
|
2969
|
+
method: 'POST',
|
|
2970
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2971
|
+
body: JSON.stringify({ obligationId: obligation_id, pages }),
|
|
2972
|
+
})
|
|
2973
|
+
} catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
|
|
2974
|
+
const out = await res.json().catch(() => null)
|
|
2975
|
+
if (!res.ok) return toolError(`Could not place it: ${out?.detail ?? out?.error ?? res.status}`)
|
|
2976
|
+
const titles = (out?.pages ?? []).map((p) => `“${String(p.title ?? '').replace(/\s+/g, ' ').trim().slice(0, 60)}”`)
|
|
2977
|
+
return { content: [{ type: 'text', text: `It now lives on: ${titles.join(' · ') || '(no page returned)'}` }] }
|
|
2950
2978
|
},
|
|
2951
2979
|
)
|
|
2952
2980
|
|
package/package.json
CHANGED