@theronap/agnoclast-mcp 0.9.200 → 0.9.202
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 +55 -51
- package/lib/settings_render.mjs +21 -0
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { repoFullNameFrom, workingFilesFrom } from './capture.mjs'
|
|
|
20
20
|
import { findTranscript, createWorkedFilesTracker } from './working_files.mjs'
|
|
21
21
|
import { spawnObligationWorker } from './obligations_worker.mjs'
|
|
22
22
|
import { renderObligations, renderPageObligations, tierReads, who } from './obligations_render.mjs'
|
|
23
|
+
import { renderSettings } from './settings_render.mjs'
|
|
23
24
|
import { formatCreated, formatDryRun, formatSources, homeFromArgs, pickSource, renderHomeProblem } from './custom_sources.mjs'
|
|
24
25
|
|
|
25
26
|
// PAGE tiers (ADR-0071). Six for a PAGE; a RECORD keeps three (`privacy` on log_session and
|
|
@@ -2837,57 +2838,8 @@ function renderNudge(payload) {
|
|
|
2837
2838
|
},
|
|
2838
2839
|
)
|
|
2839
2840
|
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
{
|
|
2843
|
-
title: 'Meetings that ended and were never written up',
|
|
2844
|
-
description: 'Turn meetings that have already ENDED into "capture what came out of this" obligations. ⚠ THE OBLIGATION IS NOT "ATTEND" — your calendar already does that, and putting every scheduled meeting into your obligations list would bury it (90 of 120 calendar records are recurring birthdays). The gap this fills is real and documented: meeting substance lands wherever the session that heard about it happened to be working, and a walkthrough with no note at all is the normal outcome. ⚠ DRY-RUN BY DEFAULT — it reports what it WOULD create, including every meeting it skipped and why, and writes nothing until apply: true. Excluded on purpose: meetings with no attendee but you (a birthday or a solo hold owes nobody anything), all-day markers, cancelled events, anything with no recorded end, and anything outside the lookback window — no backfill, because obligations nobody will ever action are how a list stops being read. Each one is discharged by a captured transcript ARRIVING, never by one being absent.',
|
|
2845
|
-
inputSchema: {
|
|
2846
|
-
lookback_days: z.number().optional().describe('how far back to sweep (default 14, max 90)'),
|
|
2847
|
-
apply: z.boolean().optional().describe('false/absent = dry run (default). true actually creates them.'),
|
|
2848
|
-
},
|
|
2849
|
-
},
|
|
2850
|
-
async ({ lookback_days, apply }) => {
|
|
2851
|
-
let res
|
|
2852
|
-
try {
|
|
2853
|
-
res = await fetchCortex(`${BASE}/api/obligations/from-calendar`, {
|
|
2854
|
-
method: 'POST',
|
|
2855
|
-
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2856
|
-
body: JSON.stringify({ lookbackDays: lookback_days, apply: apply === true }),
|
|
2857
|
-
})
|
|
2858
|
-
} catch (e) { return toolError(`Could not reach the producer: ${e.message}`) }
|
|
2859
|
-
const out = await res.json().catch(() => null)
|
|
2860
|
-
if (!res.ok) return toolError(`Could not run: ${out?.error ?? res.status}`)
|
|
2861
|
-
|
|
2862
|
-
const cands = out?.candidates ?? []
|
|
2863
|
-
const eligible = cands.filter((c) => !c.skipped)
|
|
2864
|
-
const lines = []
|
|
2865
|
-
if (out.mode === 'dry-run') {
|
|
2866
|
-
lines.push(`DRY RUN \u2014 nothing written. ${out.scanned} meeting(s) scanned, ${eligible.length} would become obligations.`)
|
|
2867
|
-
} else {
|
|
2868
|
-
lines.push(`${out.created} obligation(s) created from ${out.scanned} meeting(s) scanned.`)
|
|
2869
|
-
}
|
|
2870
|
-
if (eligible.length) {
|
|
2871
|
-
lines.push('', 'Would track:')
|
|
2872
|
-
for (const c of eligible.slice(0, 15)) {
|
|
2873
|
-
lines.push(` ${c.endedAt.slice(0, 10)} ${c.title ?? '(untitled)'} \u2014 ${c.attendees.length} attendee(s)`)
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
// \u26a0 THE SKIPS ARE PRINTED, NOT SUMMARISED AWAY. A producer that silently drops most of its
|
|
2877
|
-
// input reads as "there was nothing to do", which is indistinguishable from a broken filter.
|
|
2878
|
-
const skipped = cands.filter((c) => c.skipped)
|
|
2879
|
-
if (skipped.length) {
|
|
2880
|
-
const byReason = {}
|
|
2881
|
-
for (const c of skipped) byReason[c.skipped] = (byReason[c.skipped] ?? 0) + 1
|
|
2882
|
-
lines.push('', `Skipped ${skipped.length}:`)
|
|
2883
|
-
for (const [reason, n] of Object.entries(byReason).sort((a, b) => b[1] - a[1])) {
|
|
2884
|
-
lines.push(` ${n} ${reason}`)
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
if (out.mode === 'dry-run' && eligible.length) lines.push('', 'Re-run with apply: true to create them.')
|
|
2888
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
2889
|
-
},
|
|
2890
|
-
)
|
|
2841
|
+
// `obligations_from_calendar` was removed in 0.9.201 (2026-10-02): meetings no longer create
|
|
2842
|
+
// obligations by themselves. The server answers 410 to older clients that still offer it.
|
|
2891
2843
|
|
|
2892
2844
|
server.registerTool(
|
|
2893
2845
|
'my_obligations',
|
|
@@ -3029,6 +2981,58 @@ function renderNudge(payload) {
|
|
|
3029
2981
|
},
|
|
3030
2982
|
)
|
|
3031
2983
|
|
|
2984
|
+
// ADR-0074 — a person's own settings. The August 2026 ruling is "notices are on by default, and you
|
|
2985
|
+
// turn one off by telling the assistant"; these two tools are how the assistant does that.
|
|
2986
|
+
server.registerTool(
|
|
2987
|
+
'my_settings',
|
|
2988
|
+
{
|
|
2989
|
+
title: 'My settings',
|
|
2990
|
+
description: "The person's own Agnoclast settings: which notices they get without asking (meetings about to start, deadlines within the hour, what is due today, hand-offs, lapses) and their timezone. The same settings as the Settings page in the console. Read this before answering \"why am I (not) being told about X\".",
|
|
2991
|
+
inputSchema: {},
|
|
2992
|
+
},
|
|
2993
|
+
async () => {
|
|
2994
|
+
let res
|
|
2995
|
+
try {
|
|
2996
|
+
res = await fetchCortex(`${BASE}/api/settings`, { headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
2997
|
+
} catch (e) { return toolError(`Could not reach settings: ${e.message}`) }
|
|
2998
|
+
const out = await res.json().catch(() => null)
|
|
2999
|
+
if (!res.ok) return toolError(`Could not read settings: ${out?.detail ?? out?.error ?? res.status}`)
|
|
3000
|
+
return { content: [{ type: 'text', text: renderSettings(out) }] }
|
|
3001
|
+
},
|
|
3002
|
+
)
|
|
3003
|
+
|
|
3004
|
+
server.registerTool(
|
|
3005
|
+
'set_setting',
|
|
3006
|
+
{
|
|
3007
|
+
title: 'Change one of my settings',
|
|
3008
|
+
description: "Change one of the person's own settings — ONLY when they asked you to (\"stop telling me what's due today\", \"set my timezone to Denver\"). Never on your own initiative, and never to quiet something you find noisy: a notice the person did not turn off is one they rely on. Pass `key` and `value` for a notice (keys from my_settings, e.g. notices.due_today = false), or `timezone` alone with an IANA name such as America/Denver. Say what you changed, and that they can change it back in Settings.",
|
|
3009
|
+
inputSchema: {
|
|
3010
|
+
key: z.string().optional().describe('the setting, exactly as my_settings lists it, e.g. notices.due_today'),
|
|
3011
|
+
value: z.boolean().optional().describe('true = on, false = off'),
|
|
3012
|
+
timezone: z.string().optional().describe('instead of key/value: an IANA timezone, e.g. America/Denver'),
|
|
3013
|
+
},
|
|
3014
|
+
},
|
|
3015
|
+
async ({ key, value, timezone }) => {
|
|
3016
|
+
const body = timezone ? { timezone } : { key, value }
|
|
3017
|
+
let res
|
|
3018
|
+
try {
|
|
3019
|
+
res = await fetchCortex(`${BASE}/api/settings`, {
|
|
3020
|
+
method: 'POST',
|
|
3021
|
+
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
3022
|
+
body: JSON.stringify(body),
|
|
3023
|
+
})
|
|
3024
|
+
} catch (e) { return toolError(`Could not reach settings: ${e.message}`) }
|
|
3025
|
+
const out = await res.json().catch(() => null)
|
|
3026
|
+
if (!res.ok) return toolError(`Not changed: ${out?.detail ?? out?.error ?? res.status}`)
|
|
3027
|
+
const text = timezone
|
|
3028
|
+
? `Timezone set to ${out.timezone} on ${out.memberships} brain${out.memberships === 1 ? '' : 's'}. They can change it in Settings.`
|
|
3029
|
+
: !out.changed
|
|
3030
|
+
? `${out.key} was already ${out.value ? 'on' : 'off'} — nothing changed.`
|
|
3031
|
+
: `${out.key} is now ${out.value ? 'ON' : 'OFF'}. They can turn it back ${out.value ? 'off' : 'on'} in Settings (or ask you).`
|
|
3032
|
+
return { content: [{ type: 'text', text }] }
|
|
3033
|
+
},
|
|
3034
|
+
)
|
|
3035
|
+
|
|
3032
3036
|
server.registerTool(
|
|
3033
3037
|
'set_obligation_tier',
|
|
3034
3038
|
{
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// PURE: what my_settings prints (ADR-0074). Out of server.mjs so a test can reach it.
|
|
2
|
+
//
|
|
3
|
+
// Says ON/OFF per notice with the person's own words for it, and names the default when a setting has
|
|
4
|
+
// been changed from it — "off (default on)" is the line that answers "why am I not hearing about X?".
|
|
5
|
+
const one = (s) => String(s ?? '').replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
6
|
+
|
|
7
|
+
export function renderSettings(view) {
|
|
8
|
+
const settings = Array.isArray(view?.settings) ? view.settings : []
|
|
9
|
+
const lines = ['NOTICES — what Agnoclast tells this person without being asked (sessions and desktop):']
|
|
10
|
+
for (const s of settings) {
|
|
11
|
+
const state = s.value ? 'ON ' : 'OFF'
|
|
12
|
+
const changed = s.value !== s.default ? ` (changed — default ${s.default ? 'on' : 'off'})` : ''
|
|
13
|
+
lines.push(` ${state} ${one(s.key)} — ${one(s.label)}${changed}`)
|
|
14
|
+
}
|
|
15
|
+
if (!settings.length) lines.push(' (none returned — the server may predate settings)')
|
|
16
|
+
lines.push('')
|
|
17
|
+
lines.push(`TIMEZONE: ${view?.timezone ? one(view.timezone) : 'not set'}`)
|
|
18
|
+
lines.push('')
|
|
19
|
+
lines.push('Change one only when the person asks: set_setting. They can also change these in Settings (console avatar menu, or ⌘, in the desktop app).')
|
|
20
|
+
return lines.join('\n')
|
|
21
|
+
}
|
package/package.json
CHANGED