@theronap/agnoclast-mcp 0.9.165 → 0.9.167
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 +65 -16
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -291,35 +291,39 @@ function renderSettling(payload) {
|
|
|
291
291
|
if (!offers.length) return ''
|
|
292
292
|
const total = Number(feed?.total ?? offers.length)
|
|
293
293
|
|
|
294
|
+
// ⚠ THE THREE STAGES READ DIFFERENTLY BECAUSE THE RIGHT RESPONSE DIFFERS. A `matched` arrival is
|
|
295
|
+
// probably yours. An `orphan` reached you because it reached NOBODY — you are not the obvious
|
|
296
|
+
// owner, you are the only one looking, and the burden of proof is lower for leaving it. Rendering
|
|
297
|
+
// all three as "matched to what you have open" would be a claim the feed cannot support, and an
|
|
298
|
+
// agent that catches a nudge overstating itself stops trusting the rest of it.
|
|
294
299
|
const lines = offers.map((x) => {
|
|
295
|
-
const why = x.
|
|
296
|
-
|
|
300
|
+
const why = x.via_reason === 'matched' && x.via?.length
|
|
301
|
+
? ` — matches ${x.via.map((v) => `\`${v}\``).join(', ')}`
|
|
302
|
+
: x.via_reason === 'orphan'
|
|
303
|
+
? ' — ⚠ NO session of yours matches this; it is here because it reached nobody'
|
|
304
|
+
: ' — every session that matched it declined, so it is back to everyone'
|
|
297
305
|
const looked = x.looks > 0 ? ` [looked at ${x.looks}x, still unclaimed]` : ''
|
|
298
306
|
return ` - ${x.source} · ${x.hoursAgo}h ago · ${x.intakeItemId}${why}${looked}`
|
|
299
307
|
})
|
|
300
308
|
|
|
301
|
-
// ⚠ SAY THE TRUE TOTAL, NOT THE PAGE SIZE. Measured 2026-09-22: one identifier matched 113 settling
|
|
302
|
-
// units against a cap of 12. A block that says "here are 12" when there are 113 is claiming
|
|
303
|
-
// certainty it does not have — and renders identically every turn, which teaches the reader that
|
|
304
|
-
// it never changes and can be skipped.
|
|
305
309
|
const more = total > offers.length
|
|
306
310
|
? `\n— Showing ${offers.length} of ${total}. \`intake_changes\` lists the rest; this is a sample, not the queue.`
|
|
307
311
|
: ''
|
|
308
312
|
|
|
309
|
-
// ⚠ CONTENTION IS STATED, exactly as the record nudge states `k`. Measured the same day: THREE live
|
|
310
|
-
// sessions each matched the SAME 113 units on one identifier. Silent about that, each would claim
|
|
311
|
-
// the same units and learn about the race from a failed lease.
|
|
312
313
|
const maxHolders = offers.reduce((m, x) => Math.max(m, Number(x.holders ?? 1)), 1)
|
|
313
314
|
const who = maxHolders > 1
|
|
314
315
|
? `\n— ⚠ ${maxHolders} live sessions of yours hold a matching identifier, so these are offered to all of them. \`intake_claim\` takes a lease — claim before you work, not after, or two sessions judge the same arrival.`
|
|
315
|
-
:
|
|
316
|
+
: ''
|
|
316
317
|
|
|
317
318
|
return `\n\n⚡ ARRIVALS SETTLING — yours alone, and no one else can see these yet:\n${lines.join('\n')}\n` +
|
|
318
319
|
`— These are unjudged ARRIVALS, not records. Since automatic materialization was deleted, nothing files them for you.\n` +
|
|
319
320
|
`— \`intake_look <id>\` reads the full body without claiming it — looking is free, non-exclusive and logged, so look rather than guess from the headline.\n` +
|
|
320
|
-
`— Then \`intake_claim\`
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
`— Then \`intake_claim {intakeItemIds: ["<id>"]}\` to take a lease on THAT unit (the array is required; without an id you claim the head of the queue instead), then \`intake_materialize\` into the brain and pages it belongs to — or \`intake_discard\`, or \`intake_defer\` with a question when only the owner can decide.\n` +
|
|
322
|
+
// ⚠ THE DECLINE IS NAMED HERE BECAUSE IT DID NOT EXIST BEFORE 0.9.167. An offer with no
|
|
323
|
+
// available "no" is re-offered on every tool call until the window closes, which is how an
|
|
324
|
+
// agent learns to skip the block entirely.
|
|
325
|
+
`— NOT yours? \`not_mine {intake_item_id: "<id>", reason: "..."}\` — per session, not a suppression: it keeps going to your other matching sessions, and once they have all declined it comes back to everyone.` +
|
|
326
|
+
more + who
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
function renderNudge(payload) {
|
|
@@ -982,6 +986,17 @@ function renderNudge(payload) {
|
|
|
982
986
|
claimKind: z.enum(['relevance', 'cleanup']).optional().describe('default relevance'),
|
|
983
987
|
limit: z.number().optional().describe('max items (default 10). Ignored when intakeItemIds is given.'),
|
|
984
988
|
leaseMinutes: z.number().optional().describe('lease length in minutes, 5-60 (default 30). Prefer intake_look for read-only inspection — a short lease is only for briefly exclusive work.'),
|
|
989
|
+
// 🔴 SINGULAR ACCEPTED, AND NOT AS A CONVENIENCE. Every sibling verb takes `intakeItemId`:
|
|
990
|
+
// intake_look, intake_materialize, intake_release, intake_defer, intake_discard. This one
|
|
991
|
+
// alone was plural, so the natural singular was NOT IN THE SCHEMA and was silently dropped
|
|
992
|
+
// — and the fallback is not an error, it is claiming the head of the queue. On 2026-09-22 a
|
|
993
|
+
// session offered one unit by the settling block called `intake_claim {intakeItemId}` and
|
|
994
|
+
// took a 30-minute lease on TEN unrelated units belonging to other sessions' work. A typo
|
|
995
|
+
// that becomes a side effect is worse than a typo that fails.
|
|
996
|
+
intakeItemId: z.string().optional().describe(
|
|
997
|
+
'a single unit to claim — the same thing as intakeItemIds with one element. Accepted because ' +
|
|
998
|
+
'every other intake verb spells it this way.',
|
|
999
|
+
),
|
|
985
1000
|
intakeItemIds: z.array(z.string()).optional().describe(
|
|
986
1001
|
'claim these specific units instead of the head of the queue (max 50). Without it you get FIFO, ' +
|
|
987
1002
|
'so reaching one known unit means claiming everything ahead of it. Ids come from intake_changes ' +
|
|
@@ -993,7 +1008,11 @@ function renderNudge(payload) {
|
|
|
993
1008
|
),
|
|
994
1009
|
},
|
|
995
1010
|
},
|
|
996
|
-
async ({ claimKind, limit, intakeItemIds, leaseMinutes }) => {
|
|
1011
|
+
async ({ claimKind, limit, intakeItemId, intakeItemIds, leaseMinutes }) => {
|
|
1012
|
+
// Merge the two spellings before anything else looks at them. Passing both is not an error —
|
|
1013
|
+
// the union is what the caller plainly meant.
|
|
1014
|
+
const ids = [...new Set([...(intakeItemIds ?? []), ...(intakeItemId ? [intakeItemId] : [])])]
|
|
1015
|
+
intakeItemIds = ids.length ? ids : undefined
|
|
997
1016
|
const res = await fetchCortex(`${BASE}/api/intake/claim`, {
|
|
998
1017
|
method: 'POST',
|
|
999
1018
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
@@ -2832,11 +2851,41 @@ function renderNudge(payload) {
|
|
|
2832
2851
|
title: 'Decline a nudged record — it is not this session\'s business',
|
|
2833
2852
|
description: 'Say a nudged record is NOT your business. Use it when the ⚡ ARRIVED block offered you a record and you have looked and it does not belong to what you are doing. ⚠ THIS IS NOT A CLAIM AND NOT A SUPPRESSION: the record stays unclaimed, stays on the general timeline, and stays offerable to any other session holding a matching identifier. You are saying "not MY business", never "nobody\'s business", and no other session is affected. Declining is a COMPLETE and expected answer — the cascade offers on the evidence of what you have open, which is a good guess and not a fact, and a wrong claim costs more than a decline. Without this the same nudge returns on your next tool call and the disagreement is recorded nowhere.',
|
|
2834
2853
|
inputSchema: {
|
|
2835
|
-
record_id: z.string().describe('the record id from the ⚡ ARRIVED block'),
|
|
2854
|
+
record_id: z.string().optional().describe('the record id from the ⚡ ARRIVED block'),
|
|
2855
|
+
// ⚠ THE TWO BLOCKS OFFER DIFFERENT KINDS OF THING, and this verb now answers both. ⚡ ARRIVED
|
|
2856
|
+
// offers unclaimed RECORDS; ⚡ ARRIVALS SETTLING offers intake ARRIVALS that are not records
|
|
2857
|
+
// and may never become one. Until 0.9.167 the settling block had no decline at all, so a unit
|
|
2858
|
+
// a session had judged irrelevant was re-offered on every tool call — pilot B's 22-hour
|
|
2859
|
+
// re-offer defect (§11), recreated by the fix for the other half of the same problem.
|
|
2860
|
+
intake_item_id: z.string().optional().describe('the ARRIVAL id from the ⚡ ARRIVALS SETTLING block (not a record id)'),
|
|
2836
2861
|
reason: z.string().describe('WHY it is not yours — recorded, and the only thing that can later answer "why did nobody take this"'),
|
|
2837
2862
|
},
|
|
2838
2863
|
},
|
|
2839
|
-
async ({ record_id, reason }) => {
|
|
2864
|
+
async ({ record_id, intake_item_id, reason }) => {
|
|
2865
|
+
// ⚠ REFUSE BOTH AND NEITHER EXPLICITLY. Tonight's other defect was a parameter accepted by the
|
|
2866
|
+
// caller, dropped by the schema, and replaced by a default that did something else entirely.
|
|
2867
|
+
// An ambiguous decline must fail loudly, never pick one.
|
|
2868
|
+
if (record_id && intake_item_id) {
|
|
2869
|
+
return toolError('Pass record_id OR intake_item_id, not both — they are different kinds of thing and I will not guess which you meant.')
|
|
2870
|
+
}
|
|
2871
|
+
if (!record_id && !intake_item_id) {
|
|
2872
|
+
return toolError('Pass record_id (from ⚡ ARRIVED) or intake_item_id (from ⚡ ARRIVALS SETTLING).')
|
|
2873
|
+
}
|
|
2874
|
+
if (intake_item_id) {
|
|
2875
|
+
let r
|
|
2876
|
+
try {
|
|
2877
|
+
r = await fetchCortex(`${BASE}/api/settling/reject`, {
|
|
2878
|
+
method: 'POST',
|
|
2879
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2880
|
+
body: JSON.stringify({ intakeItemId: intake_item_id, reason }),
|
|
2881
|
+
})
|
|
2882
|
+
} catch (e) {
|
|
2883
|
+
return toolError(`Could not decline ${intake_item_id}: ${e.message}`)
|
|
2884
|
+
}
|
|
2885
|
+
const o = await r.json().catch(() => null)
|
|
2886
|
+
if (!r.ok) return toolError(`Could not decline ${intake_item_id}: ${o?.error ?? r.status}`)
|
|
2887
|
+
return { content: [{ type: 'text', text: `Noted — arrival ${intake_item_id} will not be offered to THIS session again. It stays pending and still goes to your other matching sessions; once they have all declined it, it comes back to everyone as unclaimed.` }] }
|
|
2888
|
+
}
|
|
2840
2889
|
let res
|
|
2841
2890
|
try {
|
|
2842
2891
|
res = await fetchCortex(`${BASE}/api/nudge/reject`, {
|
package/package.json
CHANGED