@theronap/agnoclast-mcp 0.9.220 → 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
  }
@@ -10,6 +10,12 @@
10
10
  /** Default cap on the content shown. A lecture runs ~50,000 characters; the client's reply budget is finite. */
11
11
  export const READ_RECORD_DEFAULT_CHARS = 40_000
12
12
 
13
+ /** What read_record returns of a transcript (Theron, 2026-10-08: "the agent should first look at the notes"). */
14
+ export const READ_RECORD_PARTS = ['notes', 'transcript', 'all']
15
+
16
+ /** Sources whose content is a transcript (web/lib/engine/transcript_retention.ts TRANSCRIPT_KEPT_SOURCES). */
17
+ const TRANSCRIPT_SOURCES = new Set(['handoff', 'meeting'])
18
+
13
19
  const SOURCE_LABEL = {
14
20
  github: 'GitHub', email: 'Email', 'email-in': 'Email', gmail: 'Email', 'claude-code': 'Claude Code session',
15
21
  imessage: 'Text messages', 'google-calendar': 'Calendar', 'agnoclast-calendar': 'Calendar',
@@ -19,8 +25,15 @@ const SOURCE_LABEL = {
19
25
  const label = (source) => SOURCE_LABEL[source] ?? source
20
26
  const line = (e) => ` · ${e.title || '(untitled)'} — ${label(e.source)} · ${e.occurredAt} — id ${e.id}`
21
27
 
22
- /** PURE. The tool's reply for one record. `maxChars` caps the content and says how much was left out. */
23
- export function renderRecord(rec, maxChars = READ_RECORD_DEFAULT_CHARS) {
28
+ /**
29
+ * PURE. The tool's reply for one record. `maxChars` caps the content and says how much was left out.
30
+ *
31
+ * TWO LAYERS ON A TRANSCRIPT (Theron, 2026-10-08): a meeting or lecture carries NOTES (the gist, decisions,
32
+ * action items, written by whoever filed it) and the full transcript. By default a record WITH notes returns
33
+ * the notes and says how long the transcript is, so a session gets the gist without reading 50,000
34
+ * characters; `part: "transcript"` or `"all"` returns the text. A record without notes is unchanged.
35
+ */
36
+ export function renderRecord(rec, maxChars = READ_RECORD_DEFAULT_CHARS, part = 'notes') {
24
37
  if (!rec || typeof rec !== 'object') return 'No record came back.'
25
38
  const cap = Number.isFinite(maxChars) && maxChars > 0 ? Math.trunc(maxChars) : READ_RECORD_DEFAULT_CHARS
26
39
  const out = []
@@ -29,14 +42,28 @@ export function renderRecord(rec, maxChars = READ_RECORD_DEFAULT_CHARS) {
29
42
  out.push(`id: ${rec.id}`)
30
43
  out.push('')
31
44
  const content = typeof rec.content === 'string' ? rec.content : ''
32
- if (!content.trim()) {
45
+ const notes = typeof rec.notes === 'string' && rec.notes.trim() ? rec.notes.trim() : null
46
+ const want = READ_RECORD_PARTS.includes(part) ? part : 'notes'
47
+ if (notes && want !== 'transcript') {
48
+ out.push('## Notes')
49
+ out.push(notes)
50
+ out.push('')
51
+ }
52
+ if (notes && want === 'notes') {
53
+ out.push(`Full transcript: ${content.length.toLocaleString('en-US')} characters, not shown. Call read_record with part: "transcript" when you need the exact words; grep and search already cover it.`)
54
+ } else if (!content.trim()) {
33
55
  out.push('(No content was captured for this record.)')
34
56
  } else if (content.length > cap) {
57
+ if (notes) out.push('## Full transcript')
35
58
  out.push(content.slice(0, cap))
36
59
  out.push(`\n… ${content.length - cap} more characters. Call read_record again with max_chars: ${content.length} to read all of it.`)
37
60
  } else {
61
+ if (notes) out.push('## Full transcript')
38
62
  out.push(content)
39
63
  }
64
+ if (!notes && TRANSCRIPT_SOURCES.has(rec.source) && content.length > 2000) {
65
+ out.push(`\n(No notes on this transcript yet. If it is yours, write them with set_record_notes: the gist, decisions and action items, names spelled right.)`)
66
+ }
40
67
  out.push('')
41
68
  const pages = Array.isArray(rec.pages) ? rec.pages : []
42
69
  out.push(pages.length
package/lib/server.mjs CHANGED
@@ -26,7 +26,7 @@ import { 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
28
  import { obligationsForUnits, renderFilingObligations, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
29
- import { renderRecord, READ_RECORD_DEFAULT_CHARS } from './record_render.mjs'
29
+ import { renderRecord, READ_RECORD_DEFAULT_CHARS, READ_RECORD_PARTS } from './record_render.mjs'
30
30
  import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, renderHomeProblem } from './custom_sources.mjs'
31
31
  import { formatSends } from './sends_render.mjs'
32
32
 
@@ -1234,6 +1234,7 @@ function renderNudge(payload) {
1234
1234
  'Name the governing page with governingPage (one of documentIds, never a person page) — otherwise the ranking picks it. ' +
1235
1235
  '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. ' +
1236
1236
  '⚠ ADR-0051: a unit does not finish until you looked for its OTHER homes. A call whose documentIds add nothing beyond the deterministic homes it gets anyway is REFUSED once — name the pages this unit is about (the design it implements, the project, the person), or go look, or say you looked with searchedForOtherHomes. ' +
1237
+ '⚠ 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. ' +
1237
1238
  '⚠ 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.',
1238
1239
  inputSchema: {
1239
1240
  intakeItemId: z.string().describe('intake item uuid'),
@@ -1246,7 +1247,10 @@ function renderNudge(payload) {
1246
1247
  orgId: z.string().describe('destination brain org uuid'),
1247
1248
  documentIds: z.array(z.string()).optional().describe('additional pages in orgId — each a page `ref:` exactly as read_page shows it, or a brain_documents id (deterministic homes are merged automatically)'),
1248
1249
  title: z.string().optional(),
1249
- summary: z.string().optional(),
1250
+ summary: z.string().optional().describe(
1251
+ 'what future retrieval reads first. For a meeting or lecture TRANSCRIPT these are its NOTES, and they are required: ' +
1252
+ 'a few lines of gist, then decisions, action items and dates, with names spelled right. The transcript is kept as it is.',
1253
+ ),
1250
1254
  source: z.string().optional(),
1251
1255
  recordType: z.string().optional(),
1252
1256
  dedupeKey: z.string().optional(),
@@ -1375,6 +1379,11 @@ function renderNudge(payload) {
1375
1379
  ))
1376
1380
  }
1377
1381
  if (err === 'obligations_unanswered') return toolError(OBLIGATIONS_UNANSWERED_TEXT)
1382
+ if (err === 'notes_required') {
1383
+ let parsed = null
1384
+ try { parsed = JSON.parse(body) } catch { /* handled above */ }
1385
+ return toolError(`Not filed yet: this unit is a transcript and ${parsed?.problem ?? 'it has no notes'}. ${parsed?.detail ?? 'Pass summary as the notes and call again.'}`)
1386
+ }
1378
1387
  }
1379
1388
  if (preview && res.status === 404) {
1380
1389
  return toolError('This server cannot preview a materialization yet — nothing was written. Materialize without mode, or wait for the server to update.')
@@ -2976,25 +2985,26 @@ function renderNudge(payload) {
2976
2985
  'track_obligation',
2977
2986
  {
2978
2987
  title: 'Record something you owe',
2979
- 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.",
2980
2989
  inputSchema: {
2981
2990
  subject: z.string().describe('what is owed, in your own words'),
2982
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.'),
2983
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.'),
2984
2993
  note: z.string().optional().describe('with `for`: a short note to the person it is given to'),
2985
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.'),
2986
2996
  anchor_identifier: z.string().optional().describe('e.g. "email:ben@example.com" — records from them become evidence this may be done'),
2987
2997
  source_record_id: z.string().optional().describe('the record this came from, if any'),
2988
2998
  brain: z.string().optional().describe('required only if you belong to more than one brain'),
2989
2999
  },
2990
3000
  },
2991
- 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 }) => {
2992
3002
  let res
2993
3003
  try {
2994
3004
  res = await fetchCortex(`${BASE}/api/obligations`, {
2995
3005
  method: 'POST',
2996
3006
  headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
2997
- 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 }),
2998
3008
  })
2999
3009
  } catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
3000
3010
  const out = await res.json().catch(() => null)
@@ -3002,7 +3012,8 @@ function renderNudge(payload) {
3002
3012
  // What will actually happen, stated per case: a promise of a nudge that never comes is worse
3003
3013
  // than no promise, and so is "nothing will nudge you" over something that now will.
3004
3014
  const nudge = due_at
3005
- ? '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.' : '.')
3006
3017
  : 'It has no date, so nothing will raise it \u2014 ask with my_obligations.'
3007
3018
  const note = anchor_identifier
3008
3019
  ? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done. ${nudge}`
@@ -3020,6 +3031,38 @@ function renderNudge(payload) {
3020
3031
  },
3021
3032
  )
3022
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
+
3023
3066
  server.registerTool(
3024
3067
  'give_obligation',
3025
3068
  {
@@ -4571,13 +4614,21 @@ function renderNudge(payload) {
4571
4614
  'session, a meeting), and, when it IS a container, every record inside it. Use it to open what ' +
4572
4615
  'list_records, my_day or timeline_pull only summarize, and to walk a class session: read the session, ' +
4573
4616
  'then read_record each id it lists. A record you may not read answers "not found", exactly like one ' +
4574
- 'that does not exist.',
4617
+ 'that does not exist. ' +
4618
+ 'A meeting or lecture transcript has TWO LAYERS: notes (the gist, decisions, action items) and the full ' +
4619
+ 'transcript. By default you get the NOTES and the transcript\'s length; read the notes first, and ask for ' +
4620
+ 'part: "transcript" only when you need the exact words. To FIND something said in a meeting, grep and ' +
4621
+ 'search already cover the transcript.',
4575
4622
  inputSchema: {
4576
4623
  id: z.string().describe('the record id, from list_records, my_day, timeline_pull or another read_record'),
4577
4624
  max_chars: z.number().optional().describe(`cap on the content shown (default ${READ_RECORD_DEFAULT_CHARS}); the reply says how much was left out`),
4625
+ part: z.enum(READ_RECORD_PARTS).optional().describe(
4626
+ 'for a transcript with notes: "notes" (default) = the notes only; "transcript" = the full text only; "all" = both. ' +
4627
+ 'A record without notes always returns its content.',
4628
+ ),
4578
4629
  },
4579
4630
  },
4580
- async ({ id, max_chars }) => {
4631
+ async ({ id, max_chars, part }) => {
4581
4632
  let res
4582
4633
  try {
4583
4634
  res = await fetchCortex(`${BASE}/api/records/${encodeURIComponent(id)}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
@@ -4590,7 +4641,46 @@ function renderNudge(payload) {
4590
4641
  return toolError(`Could not read the record: ${d.message}`)
4591
4642
  }
4592
4643
  const body = await res.json().catch(() => null)
4593
- return { content: [{ type: 'text', text: withAskNotice(renderRecord(body?.record, max_chars)) }] }
4644
+ return { content: [{ type: 'text', text: withAskNotice(renderRecord(body?.record, max_chars, part)) }] }
4645
+ },
4646
+ )
4647
+
4648
+ // set_record_notes — write the NOTES layer of one of your own transcripts (Theron, 2026-10-08: "two
4649
+ // layers … a summary and notes from the transcript … the full transcript"). The server keeps the
4650
+ // before-state in record history and refuses anything that is not your own kept transcript.
4651
+ server.registerTool(
4652
+ 'set_record_notes',
4653
+ {
4654
+ title: 'Write the notes on a transcript record',
4655
+ description:
4656
+ 'Write or rewrite the NOTES on one of your own meeting or lecture transcripts: the layer read_record shows ' +
4657
+ 'before the transcript. Read the transcript first (read_record part: "transcript"). Good notes: a few lines of ' +
4658
+ 'gist, then decisions, action items and dates, with people\'s names spelled correctly (check them against person ' +
4659
+ 'pages; speech recognition misspells names). Never change the transcript itself: obligations quote it word for ' +
4660
+ 'word. Only your own records, and only transcripts; anything else answers not found or not a transcript.',
4661
+ inputSchema: {
4662
+ record_id: z.string().describe('the transcript record id'),
4663
+ notes: z.string().describe('the notes, in plain text or simple markdown; the short layer, so well under 12,000 characters'),
4664
+ },
4665
+ },
4666
+ async ({ record_id, notes }) => {
4667
+ let res
4668
+ try {
4669
+ res = await fetchCortex(`${BASE}/api/records/${encodeURIComponent(record_id)}/notes`, {
4670
+ method: 'POST',
4671
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
4672
+ body: JSON.stringify({ notes }),
4673
+ })
4674
+ } catch (e) {
4675
+ return toolError(`Could not reach Agnoclast: ${e.message}`)
4676
+ }
4677
+ const body = await res.json().catch(() => ({}))
4678
+ if (res.ok && body?.ok) return { content: [{ type: 'text', text: `Notes saved on ${record_id}. read_record now shows them before the transcript.` }] }
4679
+ if (res.status === 404 && !body?.error) return toolError('This server does not take transcript notes yet. Nothing was written.')
4680
+ if (res.status === 404) return toolError(`No transcript ${record_id} of yours. Notes go only on your own records.`)
4681
+ if (res.status === 409) return toolError(`${record_id} is not a transcript (${body?.detail ?? 'no kept text'}). Nothing was written.`)
4682
+ if (res.status === 422) return toolError(`Not saved: ${body?.detail ?? 'the notes were refused'}.`)
4683
+ return toolError(`Could not save the notes: HTTP ${res.status} ${JSON.stringify(body)}`)
4594
4684
  },
4595
4685
  )
4596
4686
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.220",
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": {