@theronap/agnoclast-mcp 0.9.233 → 0.9.235

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 CHANGED
@@ -31,6 +31,7 @@ import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, r
31
31
  import { formatSeriesRefusal, formatSeriesUpdated } from './series_render.mjs'
32
32
  import { formatSends } from './sends_render.mjs'
33
33
  import { applyLock, fetchLockState, lockedAnswerFor, lockStateWithin } from './locked_tools.mjs'
34
+ import { filterUnshownSettling } from './settling_shown.mjs'
34
35
 
35
36
  // PAGE tiers (ADR-0071). Six for a PAGE; a RECORD keeps three (`privacy` on log_session and
36
37
  // set_record_privacy below) — an owner-relative tier is applied to a page's records when they are read
@@ -450,9 +451,18 @@ function renderArrival(payload) {
450
451
  ` — \`staged_records\` lists them. Not offered to you, just counted; \`ignore_arrivals\` if this is noise right now.`
451
452
  }
452
453
 
454
+ // ASK ONCE, THEN ONLY WHAT IS NEW (Theron, 2026-10-14). This block rode on EVERY tool reply, unchanged,
455
+ // and an offer that can be ignored on every call is ignored on every call — measured that day: 869
456
+ // pending, 0 claimed, while the block was printed to every session. Now a unit is shown to this process
457
+ // (one MCP server = one session) the first time only, framed as a decision owed before other work.
458
+ // Units every session passes on are escalated to the PERSON at the next session start
459
+ // (web/lib/engine/orphan_escalation.ts), so a unit dropped here is never lost — it changes audience.
460
+ const settlingShown = new Set()
461
+
453
462
  function renderSettling(payload) {
454
463
  const feed = payload?.settling
455
- const offers = Array.isArray(feed?.offers) ? feed.offers : []
464
+ const firstSight = settlingShown.size === 0
465
+ const offers = filterUnshownSettling(Array.isArray(feed?.offers) ? feed.offers : [], settlingShown)
456
466
  if (!offers.length) return ''
457
467
  const total = Number(feed?.total ?? offers.length)
458
468
 
@@ -483,7 +493,10 @@ function renderSettling(payload) {
483
493
  ? `\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.`
484
494
  : ''
485
495
 
486
- return `\n\n⚡ ARRIVALS SETTLING — yours alone, and no one else can see these yet:\n${lines.join('\n')}\n` +
496
+ const head = firstSight
497
+ ? `\n\n⚡ ARRIVALS SETTLING — DECIDE EACH LINE BEFORE CONTINUING: \`intake_claim\` it if it belongs to this session's work, otherwise \`not_mine\`. You are asked once; when every session has passed, the person is told at the next session start.\n`
498
+ : `\n\n⚡ NEW ARRIVALS SETTLING — decide each: \`intake_claim\` or \`not_mine\`:\n`
499
+ return `${head}${lines.join('\n')}\n` +
487
500
  `— These are unjudged ARRIVALS, not records. Since automatic materialization was deleted, nothing files them for you.\n` +
488
501
  `— \`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` +
489
502
  `— 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` +
@@ -1185,6 +1198,7 @@ function renderNudge(payload) {
1185
1198
  inputSchema: {
1186
1199
  claimKind: z.enum(['relevance', 'cleanup']).optional().describe('default relevance'),
1187
1200
  limit: z.number().optional().describe('max items (default 10). Ignored when intakeItemIds is given.'),
1201
+ 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.'),
1188
1202
  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.'),
1189
1203
  // 🔴 SINGULAR ACCEPTED, AND NOT AS A CONVENIENCE. Every sibling verb takes `intakeItemId`:
1190
1204
  // intake_look, intake_materialize, intake_release, intake_defer, intake_discard. This one
@@ -1208,7 +1222,7 @@ function renderNudge(payload) {
1208
1222
  ),
1209
1223
  },
1210
1224
  },
1211
- async ({ claimKind, limit, intakeItemId, intakeItemIds, leaseMinutes }) => {
1225
+ async ({ claimKind, limit, source, intakeItemId, intakeItemIds, leaseMinutes }) => {
1212
1226
  // Merge the two spellings before anything else looks at them. Passing both is not an error —
1213
1227
  // the union is what the caller plainly meant.
1214
1228
  const ids = [...new Set([...(intakeItemIds ?? []), ...(intakeItemId ? [intakeItemId] : [])])]
@@ -1225,6 +1239,7 @@ function renderNudge(payload) {
1225
1239
  // survive as one; sending `[]` where the caller sent nothing would silently switch a FIFO
1226
1240
  // claim into a no-op.
1227
1241
  ...(intakeItemIds ? { intakeItemIds } : {}),
1242
+ ...(source ? { source } : {}),
1228
1243
  }),
1229
1244
  })
1230
1245
  if (!res.ok) {
@@ -0,0 +1,9 @@
1
+ // Ask-once filter for the ⚡ ARRIVALS SETTLING block (2026-10-14). Pure, so it is testable without
2
+ // importing server.mjs. An offer is fresh if ANY id it stands for has not been shown to this session;
3
+ // a text conversation counts as one offer carrying many ids, so one new text re-raises its chat.
4
+ export function filterUnshownSettling(offers, shown) {
5
+ const idsOf = (x) => (x.conversation?.ids?.length ? x.conversation.ids : [x.intakeItemId])
6
+ const fresh = offers.filter((x) => idsOf(x).some((id) => !shown.has(id)))
7
+ for (const x of fresh) for (const id of idsOf(x)) shown.add(id)
8
+ return fresh
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.233",
3
+ "version": "0.9.235",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {