@theronap/agnoclast-mcp 0.9.251 → 0.9.253
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/calendar_events.mjs +12 -3
- package/lib/obligations_render.mjs +2 -0
- package/lib/release_gate.mjs +1 -1
- package/lib/server.mjs +42 -10
- package/package.json +1 -1
package/lib/calendar_events.mjs
CHANGED
|
@@ -216,7 +216,7 @@ export const CREATE_EVENT_DESCRIPTION = 'Put an event on the person\'s calendar
|
|
|
216
216
|
|
|
217
217
|
export const UPDATE_EVENT_DESCRIPTION = 'Fix, move or cancel ONE event the person made on their Agnoclast calendar — a new time, an end or length, a new name or place, or `cancel: true` to take it off the calendar (one meeting of a repeating event too, e.g. no class on a holiday) — when they ask. Pass `event_id` (create_event and upcoming print it) and ONLY what changes. Moving the start keeps the event\'s length unless you also give `end`; a time with no offset is read in their own timezone. Pass "" as `end` or `location` to remove it. `cancel: false` puts a cancelled event back. To change or remove EVERY meeting of a repeating event, use update_series. Only events made on the Agnoclast calendar can be changed — never Google or Outlook ones.'
|
|
218
218
|
|
|
219
|
-
export const UPDATE_SERIES_DESCRIPTION = 'Change or delete a whole repeating event (a class or standing meeting made in Agnoclast) when the person asks. Name it with `series`: its exact title, its key, or its id; an unknown or ambiguous name answers with the choices. Pass ONLY what changes: `days` (MO TU WE TH FR SA SU), `start_time` / `end_time` (HH:MM, their local time), `from` / `until` (YYYY-MM-DD), `location` ("" removes it), `title`. Changes apply from today on: meetings that already happened keep their time, and a meeting cancelled on its own stays cancelled. `delete: true` removes the series and every upcoming meeting (past ones stay where they were filed). `container_type` changes what each meeting owes: meeting = a transcript or a note · work_block = a work session in the window · deadline = the artifact · appointment = nothing (a class, a visit). To change ONE meeting, use update_event instead.'
|
|
219
|
+
export const UPDATE_SERIES_DESCRIPTION = 'Change or delete a whole repeating event (a class or standing meeting made in Agnoclast) when the person asks. Name it with `series`: its exact title, its key, or its id; an unknown or ambiguous name answers with the choices. Pass ONLY what changes: `days` (MO TU WE TH FR SA SU), `start_time` / `end_time` (HH:MM, their local time), `from` / `until` (YYYY-MM-DD), `location` ("" removes it), `title`. Changes apply from today on: meetings that already happened keep their time, and a meeting cancelled on its own stays cancelled. EXCEPTIONS by date (YYYY-MM-DD): `cancel_dates` skips those meetings (a holiday), `restore_dates` puts them back, `add_dates` adds one extra meeting on that day at the series\' own time (e.g. a Tuesday that runs a Monday schedule); a meeting moved by hand with update_event keeps its new time through any later change. `delete: true` removes the series and every upcoming meeting (past ones stay where they were filed). `container_type` changes what each meeting owes: meeting = a transcript or a note · work_block = a work session in the window · deadline = the artifact · appointment = nothing (a class, a visit). To change ONE meeting, use update_event instead.'
|
|
220
220
|
|
|
221
221
|
/** PATCH /api/series/<ref> body for update_series, or null when nothing would change (event_time.ts seriesPatchBody). */
|
|
222
222
|
export function seriesPatchBody(args) {
|
|
@@ -231,6 +231,9 @@ export function seriesPatchBody(args) {
|
|
|
231
231
|
if (str(args.from)) body.windowFrom = str(args.from)
|
|
232
232
|
if (str(args.until)) body.windowUntil = str(args.until)
|
|
233
233
|
if (args.location !== undefined) body.location = str(args.location) || null
|
|
234
|
+
if (Array.isArray(args.cancel_dates) && args.cancel_dates.length) body.cancelDates = args.cancel_dates
|
|
235
|
+
if (Array.isArray(args.restore_dates) && args.restore_dates.length) body.restoreDates = args.restore_dates
|
|
236
|
+
if (Array.isArray(args.add_dates) && args.add_dates.length) body.addDates = args.add_dates
|
|
234
237
|
return Object.keys(body).length === 0 ? null : body
|
|
235
238
|
}
|
|
236
239
|
|
|
@@ -251,7 +254,7 @@ export function renderSeriesCreated(title, when, tz, body) {
|
|
|
251
254
|
}
|
|
252
255
|
if (body?.page?.title) lines.push(`Filed to the page "${oneLine(body.page.title, 120)}", so anything from those meetings lands there.`)
|
|
253
256
|
lines.push(`It shows on ${CONSOLE}/calendar and in upcoming. Nothing was added to Google or Outlook, and no one was invited.`)
|
|
254
|
-
lines.push(`To
|
|
257
|
+
lines.push(`To change it later, use update_series${body?.identifier ? ` (series ${body.identifier})` : ''}: its days or times, cancel_dates for a holiday, add_dates for an extra meeting, or delete. update_event changes one meeting by its event id.`)
|
|
255
258
|
return lines.join('\n')
|
|
256
259
|
}
|
|
257
260
|
|
|
@@ -285,7 +288,13 @@ export function renderSeriesRevised(r) {
|
|
|
285
288
|
`${r.upcoming ?? 0} upcoming meeting(s) on the calendar${r.firstStartsAt ? `; the next is ${dayLabel(new Date(r.firstStartsAt), tz)}, ${timeLabel(new Date(r.firstStartsAt), tz)}${r.firstEndsAt ? `–${timeLabel(new Date(r.firstEndsAt), tz)}` : ''}` : ''}.`,
|
|
286
289
|
]
|
|
287
290
|
if (r.cancelled) lines.push(`${r.cancelled} meeting(s) no longer on the schedule were cancelled.`)
|
|
288
|
-
|
|
291
|
+
const d = r.dates ?? {}
|
|
292
|
+
const day = (ymd) => allDayLabel(ymd)
|
|
293
|
+
if (d.cancelled?.length) lines.push(`Cancelled: ${d.cancelled.map(day).join('; ')}.`)
|
|
294
|
+
if (d.restored?.length) lines.push(`Back on the calendar: ${d.restored.map(day).join('; ')}.`)
|
|
295
|
+
if (d.added?.length) lines.push(`Extra meeting added: ${d.added.map(day).join('; ')}.`)
|
|
296
|
+
if (d.skipped?.length) lines.push(`Not changed: ${d.skipped.map((x) => `${day(x.date)} (${x.why})`).join('; ')}.`)
|
|
297
|
+
lines.push('Meetings that already happened keep their time. Your exceptions (a cancelled, moved or added meeting) stay through any later change.')
|
|
289
298
|
return lines.join('\n')
|
|
290
299
|
}
|
|
291
300
|
|
|
@@ -122,6 +122,8 @@ export function renderObligations(obs, now = Date.now(), extras = null) {
|
|
|
122
122
|
const pages = Array.isArray(o.pages) ? o.pages.filter((p) => p?.origin !== 'fallback') : []
|
|
123
123
|
if (pages.length) lines.push(` on: ${pages.slice(0, 4).map((p) => `“${String(p.title ?? '').replace(/\s+/g, ' ').trim().slice(0, 60)}”`).join(' · ')}${pages.length > 4 ? ` … and ${pages.length - 4} more` : ''}`)
|
|
124
124
|
if (o.needsPage === true) lines.push(' ⚠ on no page anyone chose — set_obligation_pages')
|
|
125
|
+
// ADR-0093: who is waiting on it. Absent on an older server: nothing is said.
|
|
126
|
+
if (o.owedTo?.title) lines.push(` → owed to “${String(o.owedTo.title).replace(/\s+/g, ' ').trim().slice(0, 60)}” (ref ${o.owedTo.ref}) — read their page for context`)
|
|
125
127
|
// Only when it is wider than the default: who else can see this when they look at its page.
|
|
126
128
|
if (tierReads(o.tier)) lines.push(` visible to: ${tierReads(o.tier)} — they can see it; nobody but you is told about it`)
|
|
127
129
|
// Whose idea it was, and whether it is on its way to someone else. Both change what to DO with it.
|
package/lib/release_gate.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// that web/lib/engine/release.test.ts pins to the server's registry.
|
|
6
6
|
|
|
7
7
|
/** Tools any release flag gates — hidden when the server cannot be asked. Mirror of allGatedTools(). */
|
|
8
|
-
export const GATED_FALLBACK = ['my_messages', 'read_conversation', 'send_message']
|
|
8
|
+
export const GATED_FALLBACK = ['my_messages', 'read_conversation', 'send_message', 'set_obligation_owed_to']
|
|
9
9
|
|
|
10
10
|
export const RELEASE_FETCH_TIMEOUT_MS = 2500
|
|
11
11
|
|
package/lib/server.mjs
CHANGED
|
@@ -3095,9 +3095,10 @@ function renderNudge(payload) {
|
|
|
3095
3095
|
closed: z.boolean().optional().describe('true = also show discharged/cancelled ones'),
|
|
3096
3096
|
limit: z.number().optional().describe('default 50'),
|
|
3097
3097
|
page: z.string().optional().describe("a page's `ref:` as read_page prints it. Lists EVERYONE's open obligations on that page that the person may see — theirs first, then other people's under each owner's name. Use it when a page read says other people have obligations there and the person asks to see them; seeing one does not make it theirs."),
|
|
3098
|
+
owed_to: z.string().optional().describe("a person's page `ref:` — only what the person owes THEM (\"what do I owe Tanner?\"), across every page"),
|
|
3098
3099
|
},
|
|
3099
3100
|
},
|
|
3100
|
-
async ({ closed, limit, page }) => {
|
|
3101
|
+
async ({ closed, limit, page, owed_to }) => {
|
|
3101
3102
|
let res
|
|
3102
3103
|
if (page) {
|
|
3103
3104
|
try {
|
|
@@ -3111,6 +3112,7 @@ function renderNudge(payload) {
|
|
|
3111
3112
|
const qs = new URLSearchParams()
|
|
3112
3113
|
if (closed) qs.set('closed', 'true')
|
|
3113
3114
|
if (limit) qs.set('limit', String(limit))
|
|
3115
|
+
if (owed_to) qs.set('owedTo', owed_to.trim())
|
|
3114
3116
|
res = await fetchCortex(`${BASE}/api/obligations?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
3115
3117
|
} catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
|
|
3116
3118
|
const out = await res.json().catch(() => null)
|
|
@@ -3124,7 +3126,7 @@ function renderNudge(payload) {
|
|
|
3124
3126
|
'track_obligation',
|
|
3125
3127
|
{
|
|
3126
3128
|
title: 'Record something you owe',
|
|
3127
|
-
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
|
|
3129
|
+
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: an invented date will interrupt someone. Omit due_at when there is no real deadline (a standing obligation); a guessed date is indistinguishable from a real one once stored, and leads straight to nagging about something that was never late. ⚠ OWED TO SOMEONE? Pass `owed_to`: the ref of their page (\"I owe Tanner a text\" → Tanner's page). It is attached, so a later agent reads it instead of asking who, and records from their email or phone count as evidence this may be done. `owed_to` is who WAITS; `for` is who OWES. `anchor_identifier` (e.g. `repo:owner/name`) covers evidence that is not a person.",
|
|
3128
3130
|
inputSchema: {
|
|
3129
3131
|
subject: z.string().describe('what is owed, in your own words'),
|
|
3130
3132
|
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.'),
|
|
@@ -3132,18 +3134,19 @@ function renderNudge(payload) {
|
|
|
3132
3134
|
note: z.string().optional().describe('with `for`: a short note to the person it is given to'),
|
|
3133
3135
|
due_at: z.string().optional().describe('ISO 8601. OMIT when there is no real deadline — never guess one.'),
|
|
3134
3136
|
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.'),
|
|
3135
|
-
|
|
3137
|
+
owed_to: z.string().optional().describe("the `ref:` of the page of the person (or org) this is owed TO — who is waiting on it. Read their page first. Omit when it is owed to nobody in particular."),
|
|
3138
|
+
anchor_identifier: z.string().optional().describe('e.g. "repo:owner/name" — records carrying it become evidence this may be done. For a person, prefer owed_to.'),
|
|
3136
3139
|
source_record_id: z.string().optional().describe('the record this came from, if any'),
|
|
3137
|
-
brain: z.string().optional().describe('required only if you belong to more than one
|
|
3140
|
+
brain: z.string().optional().describe('the brain\'s name or id — required only if you belong to more than one'),
|
|
3138
3141
|
},
|
|
3139
3142
|
},
|
|
3140
|
-
async ({ subject, pages, due_at, work_from, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote }) => {
|
|
3143
|
+
async ({ subject, pages, due_at, work_from, anchor_identifier, source_record_id, brain, for: giveTo, note: giveNote, owed_to }) => {
|
|
3141
3144
|
let res
|
|
3142
3145
|
try {
|
|
3143
3146
|
res = await fetchCortex(`${BASE}/api/obligations`, {
|
|
3144
3147
|
method: 'POST',
|
|
3145
3148
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
3146
|
-
body: JSON.stringify({ subject, pages, dueAt: due_at, workFrom: work_from, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote }),
|
|
3149
|
+
body: JSON.stringify({ subject, pages, dueAt: due_at, workFrom: work_from, anchorIdentifier: anchor_identifier, sourceRecordId: source_record_id, brain, for: giveTo, note: giveNote, owedTo: owed_to }),
|
|
3147
3150
|
})
|
|
3148
3151
|
} catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
|
|
3149
3152
|
const out = await res.json().catch(() => null)
|
|
@@ -3154,9 +3157,9 @@ function renderNudge(payload) {
|
|
|
3154
3157
|
? (work_from ? `From ${work_from.slice(0, 10)} it is named daily as "work on now" (and once on the desktop). ` : '')
|
|
3155
3158
|
+ '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.' : '.')
|
|
3156
3159
|
: 'It has no date, so nothing will raise it \u2014 ask with my_obligations.'
|
|
3157
|
-
const note =
|
|
3158
|
-
? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done
|
|
3159
|
-
|
|
3160
|
+
const note = (owed_to ? '\nOwed to that person: their page is attached, and records from them will be offered as evidence this is done.' : '')
|
|
3161
|
+
+ (anchor_identifier ? `\nRecords carrying ${anchor_identifier} will be offered as evidence this is done.` : '')
|
|
3162
|
+
+ `\n${nudge}`
|
|
3160
3163
|
const on = Number(out.pages) > 0 ? ` — on ${out.pages} page${out.pages === 1 ? '' : 's'}` : ''
|
|
3161
3164
|
// Given to someone else: say plainly which of the two things happened. "Assigned" and "asked"
|
|
3162
3165
|
// leave the obligation with different people, and the agent must not report one as the other.
|
|
@@ -3446,6 +3449,32 @@ function renderNudge(payload) {
|
|
|
3446
3449
|
},
|
|
3447
3450
|
)
|
|
3448
3451
|
|
|
3452
|
+
server.registerTool(
|
|
3453
|
+
'set_obligation_owed_to',
|
|
3454
|
+
{
|
|
3455
|
+
title: 'Say who an obligation is owed to',
|
|
3456
|
+
description: "Say who an existing obligation is owed TO — the person waiting on it (ADR-0093) — or clear it. Pass the `ref:` of their page (person or org), read first. Their page is attached to the obligation, my_obligations shows \"→ owed to …\", and records from any email, thread or phone claimed on that page become evidence it may be done. Pass an empty string to clear it; clearing does not take the obligation off their page (use set_obligation_pages for that). Owner only.",
|
|
3457
|
+
inputSchema: {
|
|
3458
|
+
obligation_id: z.string().describe('from my_obligations'),
|
|
3459
|
+
owed_to: z.string().describe("the person's page `ref:` as read_page prints it, or \"\" to clear"),
|
|
3460
|
+
},
|
|
3461
|
+
},
|
|
3462
|
+
async ({ obligation_id, owed_to }) => {
|
|
3463
|
+
let res
|
|
3464
|
+
try {
|
|
3465
|
+
res = await fetchCortex(`${BASE}/api/obligations/owed-to`, {
|
|
3466
|
+
method: 'POST',
|
|
3467
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
3468
|
+
body: JSON.stringify({ obligationId: obligation_id, owedTo: owed_to }),
|
|
3469
|
+
})
|
|
3470
|
+
} catch (e) { return toolError(`Could not reach obligations: ${e.message}`) }
|
|
3471
|
+
const out = await res.json().catch(() => null)
|
|
3472
|
+
if (!res.ok) return toolError(`Could not set it: ${out?.detail ?? out?.error ?? res.status}`)
|
|
3473
|
+
const t = out?.owedTo?.title
|
|
3474
|
+
return { content: [{ type: 'text', text: t ? `Owed to “${String(t).replace(/\s+/g, ' ').trim().slice(0, 60)}” — their page is attached.` : 'No longer marked as owed to anyone. Its pages are unchanged.' }] }
|
|
3475
|
+
},
|
|
3476
|
+
)
|
|
3477
|
+
|
|
3449
3478
|
server.registerTool(
|
|
3450
3479
|
'propose_obligation',
|
|
3451
3480
|
{
|
|
@@ -5523,13 +5552,16 @@ function renderNudge(payload) {
|
|
|
5523
5552
|
until: z.string().optional().describe('new last date, YYYY-MM-DD'),
|
|
5524
5553
|
location: z.string().optional().describe('new place; "" removes it'),
|
|
5525
5554
|
container_type: z.enum(['meeting', 'work_block', 'deadline', 'appointment']).optional().describe('what each meeting owes'),
|
|
5555
|
+
cancel_dates: z.array(z.string()).optional().describe('skip the meeting on these dates (YYYY-MM-DD) — e.g. a holiday'),
|
|
5556
|
+
restore_dates: z.array(z.string()).optional().describe('put back a cancelled meeting on these dates'),
|
|
5557
|
+
add_dates: z.array(z.string()).optional().describe('one extra meeting on each of these dates, at the series\' own time'),
|
|
5526
5558
|
},
|
|
5527
5559
|
},
|
|
5528
5560
|
async (args) => {
|
|
5529
5561
|
const ref = String(args.series ?? '').trim().replace(/^series:/i, '')
|
|
5530
5562
|
if (!ref) return toolError('series is required — its title, key or id.')
|
|
5531
5563
|
const body = seriesPatchBody(args)
|
|
5532
|
-
if (!body) return toolError('Say what to change: days, start_time, end_time, from, until, location, title, container_type — or delete: true.')
|
|
5564
|
+
if (!body) return toolError('Say what to change: days, start_time, end_time, from, until, location, title, container_type, cancel_dates, restore_dates, add_dates — or delete: true.')
|
|
5533
5565
|
let res
|
|
5534
5566
|
try {
|
|
5535
5567
|
res = await fetchCortex(`${BASE}/api/series/${encodeURIComponent(ref)}`, {
|
package/package.json
CHANGED