@theronap/agnoclast-mcp 0.9.254 → 0.9.256
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/intake_id_alias.mjs +31 -0
- package/lib/server.mjs +78 -26
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ONE INTAKE UNIT ID, TWO SPELLINGS (2026-10-22).
|
|
2
|
+
//
|
|
3
|
+
// `not_mine` took `intake_item_id` while `intake_release` (and every other intake verb) took
|
|
4
|
+
// `intakeItemId`. The schemas strip an unknown key, so the wrong spelling arrived as "missing" — one
|
|
5
|
+
// session made 607 failing calls on 10-21/10-22 guessing between the two. Every intake verb now takes
|
|
6
|
+
// BOTH spellings; when both are given they must agree, never silently pick one.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PURE. The intake unit id from either spelling, or an error string.
|
|
10
|
+
* `required: false` lets a verb that also takes another kind of id (not_mine's record_id) accept neither.
|
|
11
|
+
*/
|
|
12
|
+
export function intakeIdFrom(args, { required = true } = {}) {
|
|
13
|
+
const camel = typeof args?.intakeItemId === 'string' ? args.intakeItemId.trim() : ''
|
|
14
|
+
const snake = typeof args?.intake_item_id === 'string' ? args.intake_item_id.trim() : ''
|
|
15
|
+
if (camel && snake && camel !== snake) {
|
|
16
|
+
return { error: `intakeItemId (${camel}) and intake_item_id (${snake}) disagree — pass one of them.` }
|
|
17
|
+
}
|
|
18
|
+
const id = camel || snake
|
|
19
|
+
if (!id && required) return { error: 'intakeItemId is required (intake_item_id is accepted too).' }
|
|
20
|
+
return { id: id || null }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** intakeIdFrom for an expression-bodied handler: the id, or a thrown Error the SDK returns as a tool error. */
|
|
24
|
+
export function requireIntakeId(args) {
|
|
25
|
+
const r = intakeIdFrom(args)
|
|
26
|
+
if (r.error) throw new Error(r.error)
|
|
27
|
+
return r.id
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The zod description for the alias field, so every verb says the same thing. */
|
|
31
|
+
export const INTAKE_ID_ALIAS_NOTE = 'same as intakeItemId — either spelling is accepted'
|
package/lib/server.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { renderObligations, renderPageObligations, tierReads, who } from './obli
|
|
|
25
25
|
import { renderLeaveQuestion, renderLeft, renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
|
|
26
26
|
import { renderSettings } from './settings_render.mjs'
|
|
27
27
|
import { renderConversationOffer, renderMaterializeBatch } from './intake_batch_render.mjs'
|
|
28
|
+
import { intakeIdFrom, requireIntakeId, INTAKE_ID_ALIAS_NOTE } from './intake_id_alias.mjs'
|
|
28
29
|
import { obligationsForUnits, renderFilingObligations, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
|
|
29
30
|
import { renderRecord, READ_RECORD_DEFAULT_CHARS, READ_RECORD_PARTS } from './record_render.mjs'
|
|
30
31
|
import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, renderHomeProblem } from './custom_sources.mjs'
|
|
@@ -1204,9 +1205,14 @@ function renderNudge(payload) {
|
|
|
1204
1205
|
title: 'Claim private intake items',
|
|
1205
1206
|
description:
|
|
1206
1207
|
'Claim a lease on private intake items (relevance or cleanup). Returns decrypted payloads for the lease holder only. Requires x-cortex-session-key (set automatically by this MCP server). Aged work never blocks a claim: the response reports cleanupDueCount and how far behind the oldest unit is, and servicing it is expected but always your call. Claiming is a commitment to process — hand back anything you will not finish with intake_release, or intake_defer if it is the owner\'s decision to make. ' +
|
|
1207
|
-
'YOU ARE THE JUDGE of every unit you claim (ADR-0062): nothing is published until you materialize, and materializing is when it becomes visible. Each unit carries a `proposal` — the brain its signals point to and, for that brain, the pages it would attach to, the page that would govern it, and the `tier` it would be born with — computed without writing anything. Decide the brain, the pages (confirm, drop or add), which page should govern (the page whose reasons for restricting its audience fit the content, not a person page), and obligations. Pages only tighten a tier; if a record should be LOOSER than `tierReason.baseline`, that is the owner\'s call — intake_defer with the question.'
|
|
1208
|
+
'YOU ARE THE JUDGE of every unit you claim (ADR-0062): nothing is published until you materialize, and materializing is when it becomes visible. Each unit carries a `proposal` — the brain its signals point to and, for that brain, the pages it would attach to, the page that would govern it, and the `tier` it would be born with — computed without writing anything. Decide the brain, the pages (confirm, drop or add), which page should govern (the page whose reasons for restricting its audience fit the content, not a person page), and obligations. Pages only tighten a tier; if a record should be LOOSER than `tierReason.baseline`, that is the owner\'s call — intake_defer with the question. ' +
|
|
1209
|
+
'view "compact" (trial, only where the server enables it — otherwise you get the full response): the judge rules, your brains, the lease and the cleanup count come ONCE per response in `rules`/`brains`, not on every unit; each unit carries its full content, only the proposed brain\'s pages as one line each, and up front what intake_materialize would refuse it for (`materialize.otherHomes`, `materialize.folderQuestions`). Read `rules` before judging.',
|
|
1208
1210
|
inputSchema: {
|
|
1209
1211
|
claimKind: z.enum(['relevance', 'cleanup']).optional().describe('default relevance'),
|
|
1212
|
+
view: z.enum(['full', 'compact']).optional().describe(
|
|
1213
|
+
'default "full". "compact" sends the rules once per response instead of on every unit, and only the proposed ' +
|
|
1214
|
+
'brain\'s placement. Honoured only where the server enables it; otherwise the response is the full one.',
|
|
1215
|
+
),
|
|
1210
1216
|
limit: z.number().optional().describe('max items (default 10). Ignored when intakeItemIds is given.'),
|
|
1211
1217
|
source: z.string().optional().describe('claim only the oldest units of ONE source — e.g. "imessage", "meeting", "email", "github", "claude-code". Reaches texts without first claiming every older GitHub/session unit ahead of them. Ignored when intakeItemIds is given.'),
|
|
1212
1218
|
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.'),
|
|
@@ -1221,6 +1227,7 @@ function renderNudge(payload) {
|
|
|
1221
1227
|
'a single unit to claim — the same thing as intakeItemIds with one element. Accepted because ' +
|
|
1222
1228
|
'every other intake verb spells it this way.',
|
|
1223
1229
|
),
|
|
1230
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1224
1231
|
intakeItemIds: z.array(z.string()).optional().describe(
|
|
1225
1232
|
'claim these specific units instead of the head of the queue (max 50). Without it you get FIFO, ' +
|
|
1226
1233
|
'so reaching one known unit means claiming everything ahead of it. Ids come from intake_changes ' +
|
|
@@ -1232,9 +1239,11 @@ function renderNudge(payload) {
|
|
|
1232
1239
|
),
|
|
1233
1240
|
},
|
|
1234
1241
|
},
|
|
1235
|
-
async ({ claimKind, limit, source, intakeItemId, intakeItemIds, leaseMinutes }) => {
|
|
1236
|
-
// Merge the
|
|
1237
|
-
// the union is what the caller plainly meant.
|
|
1242
|
+
async ({ claimKind, limit, source, intakeItemId, intakeItemIds, leaseMinutes, intake_item_id, view }) => {
|
|
1243
|
+
// Merge the spellings before anything else looks at them. Passing both is not an error —
|
|
1244
|
+
// the union is what the caller plainly meant. (intake_item_id: 2026-10-22, see intake_id_alias.mjs —
|
|
1245
|
+
// dropped by the schema it would have meant a FIFO claim of the head of the queue.)
|
|
1246
|
+
if (intake_item_id) intakeItemIds = [...(intakeItemIds ?? []), intake_item_id]
|
|
1238
1247
|
const ids = [...new Set([...(intakeItemIds ?? []), ...(intakeItemId ? [intakeItemId] : [])])]
|
|
1239
1248
|
intakeItemIds = ids.length ? ids : undefined
|
|
1240
1249
|
const res = await fetchCortex(`${BASE}/api/intake/claim`, {
|
|
@@ -1250,6 +1259,8 @@ function renderNudge(payload) {
|
|
|
1250
1259
|
// claim into a no-op.
|
|
1251
1260
|
...(intakeItemIds ? { intakeItemIds } : {}),
|
|
1252
1261
|
...(source ? { source } : {}),
|
|
1262
|
+
// Sent only when asked for: a default claim's request body is exactly what it was.
|
|
1263
|
+
...(view === 'compact' ? { view } : {}),
|
|
1253
1264
|
}),
|
|
1254
1265
|
})
|
|
1255
1266
|
if (!res.ok) {
|
|
@@ -1282,7 +1293,9 @@ function renderNudge(payload) {
|
|
|
1282
1293
|
+ `Nothing is blocked — run intake_claim with claimKind:"cleanup" when you reach a natural break.`
|
|
1283
1294
|
}
|
|
1284
1295
|
|
|
1285
|
-
|
|
1296
|
+
// The compact view is printed without indentation — it is what the view is for. The full one, as before.
|
|
1297
|
+
const text = j?.view === 'compact' ? JSON.stringify(j) : JSON.stringify(j, null, 2)
|
|
1298
|
+
return { content: [{ type: 'text', text: text + nudge }] }
|
|
1286
1299
|
},
|
|
1287
1300
|
)
|
|
1288
1301
|
|
|
@@ -1294,19 +1307,21 @@ function renderNudge(payload) {
|
|
|
1294
1307
|
'Atomically publish a claimed intake item into one brain — the disclosure moment (ADR-0062). Deterministic identifier homes in that brain are always attached; documentIds may add further pages. The record is born at the strictest of its source baseline, what the producer asked for, and every attached page; a source floor (iMessage, claude-code) pins it. ' +
|
|
1295
1308
|
'Name the governing page with governingPage (one of documentIds, never a person page) — otherwise the ranking picks it. ' +
|
|
1296
1309
|
'Pass mode "preview" first when your choice differs from the claim\'s proposal: it returns the pages, the governing page and the tier this exact call would produce, and writes nothing. Never writes private intake into search/history before this call. ' +
|
|
1297
|
-
'⚠ ADR-0051
|
|
1310
|
+
'⚠ ADR-0051 — BEFORE you file, look for this unit\'s OTHER homes: the pages it is about beyond what an identifier matched (the design it implements, the project, the person, the decision it bears on). grep or read for them, then name them in documentIds. A call that names nothing beyond the deterministic homes still FILES, on those homes only, and says so — add pages later with route_record. ' +
|
|
1298
1311
|
'⚠ A meeting or lecture TRANSCRIPT files with NOTES in `summary` (Theron, 2026-10-08): the gist, decisions, action items and dates, names spelled right. Without them the call is REFUSED; the transcript itself is kept on the record unchanged. ' +
|
|
1299
1312
|
'⚠ ADR-0077: a GitHub commit or PR does not finish until every changed path has an answer. If a changed file\'s deepest folder (or, in a grab-bag folder like web/lib/engine/ or db/supabase/, the file itself) has no claim and no decline, the call is REFUSED with the open questions; answer each in folderDecisions and call again.',
|
|
1300
1313
|
inputSchema: {
|
|
1301
|
-
intakeItemId: z.string().describe('intake item uuid'),
|
|
1314
|
+
intakeItemId: z.string().optional().describe('intake item uuid (required — intake_item_id is accepted too)'),
|
|
1315
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1302
1316
|
intakeItemIds: z.array(z.string()).optional().describe(
|
|
1303
1317
|
'MORE units to file exactly the same way, in one call — one record EACH, never merged (ADR-0076 §4.5). ' +
|
|
1304
|
-
'
|
|
1318
|
+
'Any units you hold a live claim on may be batched, even from separate intake_claim calls — except that texts from ' +
|
|
1319
|
+
'DIFFERENT text conversations never share a batch. Claim them with `intake_claim`, then pass ' +
|
|
1305
1320
|
'the first as intakeItemId and the rest here. title/summary/dedupeKey are ignored in a batch (each unit keeps its ' +
|
|
1306
1321
|
'own), and a preview covers intakeItemId only. Each id is reported separately; a failure never hides behind a success.',
|
|
1307
1322
|
),
|
|
1308
1323
|
orgId: z.string().describe('destination brain org uuid'),
|
|
1309
|
-
documentIds: z.array(z.string()).optional().describe('additional pages in orgId — each a page
|
|
1324
|
+
documentIds: z.array(z.string()).optional().describe('additional pages in orgId — each a page TITLE exactly as read_page shows it, its `ref:`, or a brain_documents id (deterministic homes are merged automatically). A title that matches no page, or more than one, is refused with the candidates listed'),
|
|
1310
1325
|
title: z.string().optional(),
|
|
1311
1326
|
summary: z.string().optional().describe(
|
|
1312
1327
|
'what future retrieval reads first. For a meeting or lecture TRANSCRIPT these are its NOTES, and they are required: ' +
|
|
@@ -1317,7 +1332,7 @@ function renderNudge(payload) {
|
|
|
1317
1332
|
dedupeKey: z.string().optional(),
|
|
1318
1333
|
origin: z.enum(['deterministic', 'llm', 'user', 'session']).optional(),
|
|
1319
1334
|
governingPage: z.string().optional().describe(
|
|
1320
|
-
'the page that should GOVERN this record (ADR-0062) — a page `ref:` or document id, and one of documentIds. ' +
|
|
1335
|
+
'the page that should GOVERN this record (ADR-0062) — a page title, `ref:` or document id, and one of documentIds. ' +
|
|
1321
1336
|
'Choose the page whose reasons for restricting its audience fit the content; never a person page. It can only ' +
|
|
1322
1337
|
'tighten the record, and the ranking will not move it. Omit to leave the choice to the ranking.',
|
|
1323
1338
|
),
|
|
@@ -1326,9 +1341,8 @@ function renderNudge(payload) {
|
|
|
1326
1341
|
),
|
|
1327
1342
|
searchedForOtherHomes: z.boolean().optional().describe(
|
|
1328
1343
|
'ADR-0051. "I looked for other homes and there are genuinely none." Only consulted when documentIds add no page ' +
|
|
1329
|
-
'beyond the deterministic homes
|
|
1330
|
-
'looking
|
|
1331
|
-
'judged page on any of them.',
|
|
1344
|
+
'beyond the deterministic homes; it suppresses the "filed on its deterministic homes only" note. Send it only AFTER ' +
|
|
1345
|
+
'looking — it is the answer to the question, not a way past it.',
|
|
1332
1346
|
),
|
|
1333
1347
|
folderDecisions: z.array(z.object({
|
|
1334
1348
|
identifier: z.string().describe('the open question\'s identifier, or a folder above it that its answerWith lists'),
|
|
@@ -1356,6 +1370,9 @@ function renderNudge(payload) {
|
|
|
1356
1370
|
},
|
|
1357
1371
|
},
|
|
1358
1372
|
async (args) => {
|
|
1373
|
+
const unit = intakeIdFrom(args)
|
|
1374
|
+
if (unit.error) return toolError(unit.error)
|
|
1375
|
+
args = { ...args, intakeItemId: unit.id }
|
|
1359
1376
|
// ADR-0076 §4.5 — A BATCH: one decision, N records. Theron, 2026-10-05: a conversation's waiting texts
|
|
1360
1377
|
// are looked up once and handled together, but "it shouldn't file all of them as one record". The
|
|
1361
1378
|
// loop lives here, as it does for place_staged_record's batch: the route stays one unit per call.
|
|
@@ -1380,8 +1397,8 @@ function renderNudge(payload) {
|
|
|
1380
1397
|
attachmentMeta: (args.documentIds ?? []).map(() => ({ origin: args.origin ?? 'llm' })),
|
|
1381
1398
|
...(args.governingPage ? { governingDocumentId: args.governingPage } : {}),
|
|
1382
1399
|
...(args.searchedForOtherHomes ? { searchedForOtherHomes: true } : {}),
|
|
1383
|
-
//
|
|
1384
|
-
//
|
|
1400
|
+
// The server refuses an id it cannot safely file with the first: a text from ANOTHER text
|
|
1401
|
+
// conversation, or a unit this session holds no live claim on (not_same_batch).
|
|
1385
1402
|
sameBatchAs: batch[0],
|
|
1386
1403
|
...obligationsFor(id),
|
|
1387
1404
|
}),
|
|
@@ -1389,8 +1406,11 @@ function renderNudge(payload) {
|
|
|
1389
1406
|
const o = await r.json().catch(() => null)
|
|
1390
1407
|
const why = o?.error === 'obligations_unanswered'
|
|
1391
1408
|
? 'not filed — a text needs `obligations` ([] when it asks nothing)'
|
|
1392
|
-
:
|
|
1393
|
-
|
|
1409
|
+
: o?.error === 'not_same_batch'
|
|
1410
|
+
? `not filed — ${o?.hint ?? 'not in the same batch as the first id'}`
|
|
1411
|
+
: `${o?.error ?? `HTTP ${r.status}`}${o?.detail ? ` — ${o.detail}` : ''}`
|
|
1412
|
+
const homesNote = o?.otherHomes?.note ? ' (deterministic homes only — add pages with route_record)' : ''
|
|
1413
|
+
results.push({ id, ok: r.ok, detail: r.ok ? `${o?.recordId ?? 'filed'}${homesNote}${filingObligationsBrief(o?.obligations)}` : why })
|
|
1394
1414
|
} catch (e) {
|
|
1395
1415
|
results.push({ id, ok: false, detail: e.message })
|
|
1396
1416
|
}
|
|
@@ -1484,6 +1504,8 @@ function renderNudge(payload) {
|
|
|
1484
1504
|
// the ordinary case (observed 2026-09-03 on the first handoff record materialised). Silence is
|
|
1485
1505
|
// correct here, and it also covers a server too old to send the field at all.
|
|
1486
1506
|
const lines = []
|
|
1507
|
+
// ADR-0051, non-blocking since server 2026-10-22: filed, but on deterministic homes only.
|
|
1508
|
+
if (body?.otherHomes?.note) lines.push(`⚠ ${body.otherHomes.note}`)
|
|
1487
1509
|
const governingLine = renderGoverningLine(body?.governing)
|
|
1488
1510
|
if (governingLine) lines.push(governingLine)
|
|
1489
1511
|
const sa = body?.sessionAttachments
|
|
@@ -1538,11 +1560,16 @@ function renderNudge(payload) {
|
|
|
1538
1560
|
description:
|
|
1539
1561
|
'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.',
|
|
1540
1562
|
inputSchema: {
|
|
1541
|
-
intakeItemId: z.string().describe('intake item uuid from intake_claim'),
|
|
1563
|
+
intakeItemId: z.string().optional().describe('intake item uuid from intake_claim (required — intake_item_id is accepted too)'),
|
|
1564
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1542
1565
|
reason: z.string().describe('why this is nothing — recorded on the claim, and the only surviving trace of the decision'),
|
|
1543
1566
|
},
|
|
1544
1567
|
},
|
|
1545
|
-
async (
|
|
1568
|
+
async (args) => {
|
|
1569
|
+
const { reason } = args
|
|
1570
|
+
const unit = intakeIdFrom(args)
|
|
1571
|
+
if (unit.error) return toolError(unit.error)
|
|
1572
|
+
const intakeItemId = unit.id
|
|
1546
1573
|
let res
|
|
1547
1574
|
try {
|
|
1548
1575
|
res = await fetchCortex(`${BASE}/api/intake/discard`, {
|
|
@@ -1577,12 +1604,17 @@ function renderNudge(payload) {
|
|
|
1577
1604
|
description:
|
|
1578
1605
|
'The THIRD terminal outcome, and the right one whenever the honest answer is "this is not mine to decide." Use it when a claimed unit is someone else\'s private content, when the destination brain is a real judgment call rather than a lookup, or when publishing and destroying are both wrong — a stranger\'s message thread, a photo you cannot place, an email whose brain depends on context only the owner has. Destroys NOTHING: the unit stays encrypted and intact, its state becomes `awaiting_user`, and your `question` is what the owner actually sees. Prefer this over letting a lease lapse — a lapsed lease says nothing, increments `attempts`, and hands the identical dead end to the next session. Requires the claim, one item per call.',
|
|
1579
1606
|
inputSchema: {
|
|
1580
|
-
intakeItemId: z.string().describe('intake item uuid from intake_claim'),
|
|
1607
|
+
intakeItemId: z.string().optional().describe('intake item uuid from intake_claim (required — intake_item_id is accepted too)'),
|
|
1608
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1581
1609
|
question: z.string().describe('what you need the owner to decide, in their words not yours — this is the entire message they get, so "which brain should this iMessage thread go to, if any?" beats "needs triage"'),
|
|
1582
1610
|
options: z.array(z.string()).optional().describe('the concrete choices, when the decision is a pick rather than an open question — e.g. ["Personal","TTO","Discard — nothing to record"]. Stored on the question and rendered by intake_cleanup_status, so the owner can answer with a choice instead of prose.'),
|
|
1583
1611
|
},
|
|
1584
1612
|
},
|
|
1585
|
-
async (
|
|
1613
|
+
async (args) => {
|
|
1614
|
+
const { question, options } = args
|
|
1615
|
+
const unit = intakeIdFrom(args)
|
|
1616
|
+
if (unit.error) return toolError(unit.error)
|
|
1617
|
+
const intakeItemId = unit.id
|
|
1586
1618
|
let res
|
|
1587
1619
|
try {
|
|
1588
1620
|
res = await fetchCortex(`${BASE}/api/intake/defer`, {
|
|
@@ -1617,11 +1649,16 @@ function renderNudge(payload) {
|
|
|
1617
1649
|
description:
|
|
1618
1650
|
'Give a claimed unit back to the queue WITHOUT deciding anything. This is not a fourth outcome — materialize, discard and defer resolve a unit; release just ends your hold on it. Use it when you claimed more than you can act on, or when the unit turns out to belong to work you are not doing. Prefer it over letting the lease expire: a lapse and a crash are indistinguishable in the ledger, so silently timing out costs the pile the one signal that says a session looked and chose to pass. Use intake_defer instead when the unit needs the OWNER to decide — release puts it back in front of the next session, which will hit whatever wall you did.',
|
|
1619
1651
|
inputSchema: {
|
|
1620
|
-
intakeItemId: z.string().describe('intake item uuid from intake_claim'),
|
|
1652
|
+
intakeItemId: z.string().optional().describe('intake item uuid from intake_claim (required — intake_item_id is accepted too)'),
|
|
1653
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1621
1654
|
reason: z.string().describe('why you are handing it back — "claimed too broadly", "not related to this session\'s work". Recorded on the claim, and the only thing distinguishing this from a lapsed lease'),
|
|
1622
1655
|
},
|
|
1623
1656
|
},
|
|
1624
|
-
async (
|
|
1657
|
+
async (args) => {
|
|
1658
|
+
const { reason } = args
|
|
1659
|
+
const unit = intakeIdFrom(args)
|
|
1660
|
+
if (unit.error) return toolError(unit.error)
|
|
1661
|
+
const intakeItemId = unit.id
|
|
1625
1662
|
let res
|
|
1626
1663
|
try {
|
|
1627
1664
|
res = await fetchCortex(`${BASE}/api/intake/release`, {
|
|
@@ -1715,11 +1752,12 @@ function renderNudge(payload) {
|
|
|
1715
1752
|
description:
|
|
1716
1753
|
'Read a PENDING intake unit\'s FULL decrypted body AND its `proposal` — the brain its signals point to, the pages it would attach to, the page that would govern it, the tier it would be born with, and `unclaimedIdentifiers` — without taking a lease. A read RECEIPT, not a claim (ADR-0035). The proposal rides with the look because they answer one question together: what is this, and where would it go — so you can tell whether a unit is yours to judge BEFORE leasing it. Use it when the settling feed\'s headline + candidate identifiers cannot tell you whether the unit belongs to your current work; looking is non-exclusive (any number of sessions may inspect the same unit) and always logged, so look freely rather than guessing from the headline — diligence is subsidized here. After looking: claim it if it is yours to process, or simply move on — no release needed, you never held it. The response includes how many sessions have looked (`looks`/`distinctLookers`): several looks and no claim is a sign the unit needs the triage agent or the owner, not another look. Resolved units are refused — read those as records.',
|
|
1717
1754
|
inputSchema: {
|
|
1718
|
-
intakeItemId: z.string().describe('intake item uuid, from the settling feed (intake_changes) or intake_claim'),
|
|
1755
|
+
intakeItemId: z.string().optional().describe('intake item uuid, from the settling feed (intake_changes) or intake_claim (required — intake_item_id is accepted too)'),
|
|
1756
|
+
intake_item_id: z.string().optional().describe(INTAKE_ID_ALIAS_NOTE),
|
|
1719
1757
|
workingIdentifiers: z.array(z.string()).optional().describe('the identifiers your current work touches (repo:…, file:…, project:…) — stored on the receipt; feeds look→claim conversion analysis'),
|
|
1720
1758
|
},
|
|
1721
1759
|
},
|
|
1722
|
-
async ({ intakeItemId, workingIdentifiers }) => renderIntakeLook(intakeItemId, workingIdentifiers ?? []),
|
|
1760
|
+
async ({ intakeItemId, intake_item_id, workingIdentifiers }) => renderIntakeLook(requireIntakeId({ intakeItemId, intake_item_id }), workingIdentifiers ?? []),
|
|
1723
1761
|
)
|
|
1724
1762
|
|
|
1725
1763
|
server.registerTool(
|
|
@@ -4069,10 +4107,24 @@ function renderNudge(payload) {
|
|
|
4069
4107
|
// a session had judged irrelevant was re-offered on every tool call — pilot B's 22-hour
|
|
4070
4108
|
// re-offer defect (§11), recreated by the fix for the other half of the same problem.
|
|
4071
4109
|
intake_item_id: z.string().optional().describe('the ARRIVAL id from the ⚡ ARRIVALS SETTLING block (not a record id)'),
|
|
4110
|
+
// 2026-10-22: every other intake verb spells it intakeItemId; one session made 607 failing calls
|
|
4111
|
+
// guessing between the two. Both are accepted — see intake_id_alias.mjs.
|
|
4112
|
+
intakeItemId: z.string().optional().describe('same as intake_item_id — either spelling is accepted'),
|
|
4113
|
+
recordId: z.string().optional().describe('same as record_id — either spelling is accepted'),
|
|
4072
4114
|
reason: z.string().describe('WHY it is not yours — recorded, and the only thing that can later answer "why did nobody take this"'),
|
|
4073
4115
|
},
|
|
4074
4116
|
},
|
|
4075
|
-
async (
|
|
4117
|
+
async (args) => {
|
|
4118
|
+
const { reason } = args
|
|
4119
|
+
const unit = intakeIdFrom(args, { required: false })
|
|
4120
|
+
if (unit.error) return toolError(unit.error)
|
|
4121
|
+
const intake_item_id = unit.id
|
|
4122
|
+
const recCamel = typeof args.recordId === 'string' ? args.recordId.trim() : ''
|
|
4123
|
+
const recSnake = typeof args.record_id === 'string' ? args.record_id.trim() : ''
|
|
4124
|
+
if (recCamel && recSnake && recCamel !== recSnake) {
|
|
4125
|
+
return toolError(`recordId (${recCamel}) and record_id (${recSnake}) disagree — pass one of them.`)
|
|
4126
|
+
}
|
|
4127
|
+
const record_id = recCamel || recSnake || null
|
|
4076
4128
|
// ⚠ REFUSE BOTH AND NEITHER EXPLICITLY. Tonight's other defect was a parameter accepted by the
|
|
4077
4129
|
// caller, dropped by the schema, and replaced by a default that did something else entirely.
|
|
4078
4130
|
// An ambiguous decline must fail loudly, never pick one.
|
package/package.json
CHANGED