@theronap/agnoclast-mcp 0.9.221 → 0.9.222

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.
@@ -101,6 +101,20 @@ export function formatPressing(pressing) {
101
101
  const more = count > names.length ? ` … and ${count - names.length} more` : ''
102
102
  lines.push(`⏰ Due today (${count}): ${names.join(' · ')}${more}`)
103
103
  }
104
+ // 2026-10-08 — WHAT TO BE WORKING ON: obligations whose work window is open, soonest deadline first.
105
+ // Theron: "that is the thing that they should be working on in the time frame in advance of that
106
+ // thing." Once a day. A window that opened in the last day is marked so the start is not missed.
107
+ else if (p?.kind === 'work_now') {
108
+ const count = Number(p.count) || 0
109
+ if (!count) continue
110
+ const names = (Array.isArray(p.names) ? p.names : []).map((n) => {
111
+ const due = String(n?.due ?? '').replace(/\s+/g, ' ').trim().slice(0, 40)
112
+ const done = n?.maybeDone ? ' — may ALREADY BE DONE; ask' : ''
113
+ return `${named(n?.text)} (${n?.opened ? 'starts today — ' : ''}${due ? `due ${due}` : 'due soon'})${done}`
114
+ })
115
+ const more = count > names.length ? ` … and ${count - names.length} more` : ''
116
+ lines.push(`🧭 Work on now (${count}): ${names.join(' · ')}${more}`)
117
+ }
104
118
  // ADR-0073 §2.7 — a hand-off, said once to the one person it concerns. Names and notes are other
105
119
  // people's text: quoted and bounded like every title above.
106
120
  else if (p?.kind === 'asked') {
@@ -140,7 +154,8 @@ export function pressingAcks(pressing) {
140
154
  for (const p of Array.isArray(pressing) ? pressing : []) {
141
155
  if (typeof p?.subject !== 'string' || typeof p?.bucket !== 'string') continue
142
156
  const handoff = p.kind === 'asked' || p.kind === 'given' || p.kind === 'declined' || p.kind === 'lapsed'
143
- if (p.kind === 'event' || p.kind === 'due_soon' || handoff || (p.kind === 'due_today' && Number(p.count) > 0)) {
157
+ const rollup = (p.kind === 'due_today' || p.kind === 'work_now') && Number(p.count) > 0
158
+ if (p.kind === 'event' || p.kind === 'due_soon' || handoff || rollup) {
144
159
  out.push({ subject: p.subject, bucket: p.bucket })
145
160
  }
146
161
  }
@@ -152,5 +152,11 @@ function describeDue(o, now) {
152
152
  const what = o.dueAllDay ? `${day} (all day)` : `${day} ${o.dueAt.slice(11, 16)}`
153
153
  if (after <= now) return `OVERDUE ${what}`
154
154
  if (o.dueAllDay && at <= now) return `DUE TODAY ${what}`
155
- return `due ${what}`
155
+ return `due ${what}${windowNote(o.workFrom, now)}`
156
+ }
157
+
158
+ // 2026-10-08 — when work should start. Absent on a server older than work windows: nothing is said.
159
+ function windowNote(workFrom, now) {
160
+ if (!workFrom) return ''
161
+ return new Date(workFrom).getTime() <= now ? ` · IN ITS WORK WINDOW since ${String(workFrom).slice(0, 10)}` : ` · work from ${String(workFrom).slice(0, 10)}`
156
162
  }
package/lib/server.mjs CHANGED
@@ -2985,25 +2985,26 @@ function renderNudge(payload) {
2985
2985
  'track_obligation',
2986
2986
  {
2987
2987
  title: 'Record something you owe',
2988
- 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, not who can see it: it is visible to its owner only unless the person later widens it (`set_obligation_tier`). ⚠ 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.",
2988
+ 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, not who can see it: it is visible to its owner only unless the person later widens it (`set_obligation_tier`). ⚠ 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. ⚠ A DEADLINE IS NOT WHEN TO START: pass `work_from` when the work needs lead time (a 10-day project opens 10 days ahead; an exam, days of study). From then it is named daily as \"work on now\", and once on the desktop when it opens. 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.",
2989
2989
  inputSchema: {
2990
2990
  subject: z.string().describe('what is owed, in your own words'),
2991
2991
  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.'),
2992
2992
  for: z.string().optional().describe('ONLY when it is someone ELSE who should owe this: their email address, or their name as it appears in this brain. Omit for something the person owes themselves. You do not choose whether it is assigned or requested — the reporting line does.'),
2993
2993
  note: z.string().optional().describe('with `for`: a short note to the person it is given to'),
2994
2994
  due_at: z.string().optional().describe('ISO 8601. OMIT when there is no real deadline — never guess one.'),
2995
+ work_from: z.string().optional().describe('ISO 8601: when work should START — the window opens (a bare date = the start of that day). Needs due_at, and must come before it. Estimate it from what the work involves; omit for things needing no lead time.'),
2995
2996
  anchor_identifier: z.string().optional().describe('e.g. "email:ben@example.com" — records from them become evidence this may be done'),
2996
2997
  source_record_id: z.string().optional().describe('the record this came from, if any'),
2997
2998
  brain: z.string().optional().describe('required only if you belong to more than one brain'),
2998
2999
  },
2999
3000
  },
3000
- async ({ subject, pages, due_at, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote }) => {
3001
+ async ({ subject, pages, due_at, work_from, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote }) => {
3001
3002
  let res
3002
3003
  try {
3003
3004
  res = await fetchCortex(`${BASE}/api/obligations`, {
3004
3005
  method: 'POST',
3005
3006
  headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3006
- body: JSON.stringify({ subject, pages, dueAt: due_at, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote }),
3007
+ body: JSON.stringify({ subject, pages, dueAt: due_at, workFrom: work_from, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote }),
3007
3008
  })
3008
3009
  } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3009
3010
  const out = await res.json().catch(() => null)
@@ -3011,7 +3012,8 @@ function renderNudge(payload) {
3011
3012
  // What will actually happen, stated per case: a promise of a nudge that never comes is worse
3012
3013
  // than no promise, and so is "nothing will nudge you" over something that now will.
3013
3014
  const nudge = due_at
3014
- ? 'It will be raised to your sessions on the day it is due' + (/T\d{2}:\d{2}/.test(due_at) ? ', and again when it is within the hour.' : '.')
3015
+ ? (work_from ? `From ${work_from.slice(0, 10)} it is named daily as "work on now" (and once on the desktop). ` : '')
3016
+ + 'It will be raised to your sessions on the day it is due' + (/T\d{2}:\d{2}/.test(due_at) ? ', and again when it is within the hour.' : '.')
3015
3017
  : 'It has no date, so nothing will raise it \u2014 ask with my_obligations.'
3016
3018
  const note = anchor_identifier
3017
3019
  ? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done. ${nudge}`
@@ -3029,6 +3031,38 @@ function renderNudge(payload) {
3029
3031
  },
3030
3032
  )
3031
3033
 
3034
+ server.registerTool(
3035
+ 'set_obligation_window',
3036
+ {
3037
+ title: 'Set when work on an obligation should start',
3038
+ description: "Open, move or remove an obligation's WORK WINDOW — when work on it should start, not when it is due (2026-10-08). From that moment until it is late, it is named once a day in the person's sessions as \"work on now\", soonest deadline first, and announced once on the desktop when the window opens. Use it when you learn what the work involves after it was recorded: \"it needs ten consecutive days of observation\" opens the window ten days before the deadline, whatever was guessed at capture; \"study for the final\" might open five days ahead. A bare date opens it at the start of that day in the owner's zone. It needs a due date and must come before it. Pass work_from null to remove the window. Only the owner can set it, and only while the obligation is open. A proposal (unconfirmed) is never raised by its window.",
3039
+ inputSchema: {
3040
+ obligation_id: z.string().describe('from my_obligations'),
3041
+ work_from: z.string().nullable().describe('ISO 8601 date or date-time when work should start; null removes the window'),
3042
+ },
3043
+ },
3044
+ async ({ obligation_id, work_from }) => {
3045
+ let res
3046
+ try {
3047
+ res = await fetchCortex(`${BASE}/api/obligations/window`, {
3048
+ method: 'POST',
3049
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3050
+ body: JSON.stringify({ obligationId: obligation_id, workFrom: work_from ?? null }),
3051
+ })
3052
+ } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3053
+ const out = await res.json().catch(() => null)
3054
+ if (!res.ok) return toolError(`Could not set the window: ${out?.detail ?? out?.error ?? res.status}`)
3055
+ return {
3056
+ content: [{
3057
+ type: 'text',
3058
+ text: out.workFrom
3059
+ ? `Window set: work on it from ${out.workFrom}. From then until it is due it is named daily as "work on now".`
3060
+ : 'Window removed: it is raised only on the day it is due.',
3061
+ }],
3062
+ }
3063
+ },
3064
+ )
3065
+
3032
3066
  server.registerTool(
3033
3067
  'give_obligation',
3034
3068
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.221",
3
+ "version": "0.9.222",
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": {