@theronap/agnoclast-mcp 0.9.264 → 0.9.266
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/agents/agnoclast-filer-shadow.md +3 -2
- package/lib/release_gate.mjs +1 -1
- package/lib/server.mjs +146 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agnoclast-filer-shadow
|
|
3
3
|
description: SHADOW ONLY (ADR-0095 shadow week). Decides routine Agnoclast arrivals the way a cheap filer would, through intake_shadow, which writes nothing — the normal agnoclast-filer still files them. Dispatch it, with run_in_background, only when the Agnoclast prompt notice says to. Pass the ids in the prompt.
|
|
4
|
-
model: haiku
|
|
4
|
+
model: claude-haiku-5-5
|
|
5
5
|
tools: ToolSearch, mcp__agnoclast__intake_shadow, mcp__agnoclast__grep, mcp__agnoclast__read_page, mcp__cortex__intake_shadow, mcp__cortex__grep, mcp__cortex__read_page
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,7 +9,8 @@ You are a SHADOW filer. Nothing you decide is filed: the normal filer files ever
|
|
|
9
9
|
are logged and compared with what it actually filed, to measure whether a cheap model can file routine units well.
|
|
10
10
|
Decide each one exactly as you would if it were real.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Your FIRST action is a ToolSearch call: `select:mcp__agnoclast__intake_shadow,mcp__agnoclast__grep,mcp__agnoclast__read_page`.
|
|
13
|
+
Then CALL the tools — never write a tool call out as text: a call that is only written down decides nothing.
|
|
13
14
|
Always pass `judgedBy: "worker:haiku"`.
|
|
14
15
|
|
|
15
16
|
## Steps
|
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 = ['intake_shadow', 'my_messages', 'read_conversation', 'send_message', 'set_manager']
|
|
8
|
+
export const GATED_FALLBACK = ['document_diff', 'get_document', 'intake_shadow', 'list_documents', 'my_messages', 'pin_document', 'read_conversation', 'read_document', 'send_message', 'set_manager']
|
|
9
9
|
|
|
10
10
|
export const RELEASE_FETCH_TIMEOUT_MS = 2500
|
|
11
11
|
|
package/lib/server.mjs
CHANGED
|
@@ -2323,10 +2323,12 @@ function renderNudge(payload) {
|
|
|
2323
2323
|
// everything attached to it.
|
|
2324
2324
|
//
|
|
2325
2325
|
// The honest condition is: nothing to show at all.
|
|
2326
|
-
if (!t.identifiers?.length && !t.events?.length) {
|
|
2326
|
+
if (!t.identifiers?.length && !t.events?.length && !t.headerNotes?.length) {
|
|
2327
2327
|
return { content: [{ type: 'text', text: `"${name}" has no history yet — no identifier stamps and nothing attached. Stamp its page with [[repo:owner/name]] (what it identifies), or attach records to it, and events will accrue here.` }] }
|
|
2328
2328
|
}
|
|
2329
2329
|
const lines = [`# ${name} — node timeline (history; the page is the present)`]
|
|
2330
|
+
// ADR-0096: pinned files head the timeline. Composed server-side (pageFileNotes), printed verbatim.
|
|
2331
|
+
for (const n of t.headerNotes ?? []) lines.push(n)
|
|
2330
2332
|
if (t.identifiers?.length) {
|
|
2331
2333
|
lines.push(`Joins: ${t.identifiers.map((i) => `[[${i.id}]] · ${i.visibleCount} visible`).join(' | ')}${t.incomplete ? ' (partial — one join failed to read)' : ''}`)
|
|
2332
2334
|
} else {
|
|
@@ -7271,6 +7273,149 @@ function renderNudge(payload) {
|
|
|
7271
7273
|
},
|
|
7272
7274
|
)
|
|
7273
7275
|
|
|
7276
|
+
// ── Stored documents (ADR-0096): files kept in Agnoclast on a page, versioned, readable as text ──
|
|
7277
|
+
server.registerTool(
|
|
7278
|
+
'pin_document',
|
|
7279
|
+
{
|
|
7280
|
+
title: 'Pin a stored file to the top of its page',
|
|
7281
|
+
description: 'Pin (or unpin) a stored file so it heads its page and the page\'s timeline — for the file a page is really about, like the accounting workbook on an "Accounting records" page. Needs edit access to the page. Get file_id from the 📌/📎 lines read_page prints at the top of a page.',
|
|
7282
|
+
inputSchema: {
|
|
7283
|
+
file_id: z.string().describe('the stored file\'s id (read_page prints it as "id …")'),
|
|
7284
|
+
pinned: z.boolean().optional().describe('false to unpin; default true'),
|
|
7285
|
+
},
|
|
7286
|
+
},
|
|
7287
|
+
async ({ file_id, pinned }) => {
|
|
7288
|
+
let res
|
|
7289
|
+
try {
|
|
7290
|
+
res = await fetchCortex(`${BASE}/api/stored-files/${encodeURIComponent(file_id)}/pin`, {
|
|
7291
|
+
method: 'POST', headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
7292
|
+
body: JSON.stringify({ pinned: pinned !== false }),
|
|
7293
|
+
})
|
|
7294
|
+
} catch (e) { return toolError(`Could not pin: ${e.message}`) }
|
|
7295
|
+
const out = await res.json().catch(() => null)
|
|
7296
|
+
if (!res.ok) return toolError(`Could not ${pinned === false ? 'unpin' : 'pin'}: ${out?.detail ?? out?.error ?? res.status}`)
|
|
7297
|
+
const t = out.file?.title ?? file_id
|
|
7298
|
+
if (!out.changed) return { content: [{ type: 'text', text: `"${t}" was already ${pinned === false ? 'unpinned' : 'pinned'}.` }] }
|
|
7299
|
+
return { content: [{ type: 'text', text: pinned === false ? `Unpinned "${t}".` : `Pinned "${t}" — it now heads its page and the page's timeline.` }] }
|
|
7300
|
+
},
|
|
7301
|
+
)
|
|
7302
|
+
|
|
7303
|
+
const docFail = async (verb, res) => {
|
|
7304
|
+
const out = await res.json().catch(() => null)
|
|
7305
|
+
return toolError(`Could not ${verb}: ${out?.detail ?? out?.error ?? res.status}`)
|
|
7306
|
+
}
|
|
7307
|
+
|
|
7308
|
+
server.registerTool(
|
|
7309
|
+
'list_documents',
|
|
7310
|
+
{
|
|
7311
|
+
title: 'List the stored files on a page',
|
|
7312
|
+
description: 'The files kept in Agnoclast on a page (a spreadsheet, a Word document, a CSV…), pinned first, with each one\'s id, current version and whether it can be read as text. read_page also names them at the top of the page. Then read_document to read one, document_diff to see what changed, get_document to download it.',
|
|
7313
|
+
inputSchema: { page: z.string().describe('the page: its exact title, its `ref:` from read_page, or a document id') },
|
|
7314
|
+
},
|
|
7315
|
+
async ({ page }) => {
|
|
7316
|
+
let res
|
|
7317
|
+
try { res = await fetchCortex(`${BASE}/api/stored-files?${new URLSearchParams({ page })}`, { headers: { Authorization: `Bearer ${TOKEN}` } }) }
|
|
7318
|
+
catch (e) { return toolError(`Could not list files: ${e.message}`) }
|
|
7319
|
+
if (!res.ok) return docFail('list files', res)
|
|
7320
|
+
const out = await res.json()
|
|
7321
|
+
if (!out.files?.length) return { content: [{ type: 'text', text: `No stored files on "${out.page?.title ?? page}".` }] }
|
|
7322
|
+
const lines = [`Stored files on "${out.page?.title ?? page}":`]
|
|
7323
|
+
for (const f of out.files) {
|
|
7324
|
+
const v = f.versions?.[0]
|
|
7325
|
+
lines.push(`${f.pinned ? '📌' : '-'} "${f.title}" — id ${f.id} · v${f.latestVersion}${v ? ` · ${v.filename} · ${Math.round(v.sizeBytes / 1024)} KB · ${String(v.createdAt).slice(0, 10)}${v.readable === 'ok' ? '' : ' · not readable as text'}` : ''}`)
|
|
7326
|
+
}
|
|
7327
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
7328
|
+
},
|
|
7329
|
+
)
|
|
7330
|
+
|
|
7331
|
+
server.registerTool(
|
|
7332
|
+
'read_document',
|
|
7333
|
+
{
|
|
7334
|
+
title: 'Read a stored file as text',
|
|
7335
|
+
description: 'Read a file kept in Agnoclast (Excel, Word, CSV, plain text) as text. A workbook reads as one table per sheet, then each formula with its value — a formula marked "⚠ not calculated" has no value yet (the file was saved without recalculating), which is NOT zero. Long files come in pages: pass the returned offset to continue. Pass `sheet` to read one sheet, `version` for an older version, format "cells" for one line per cell.',
|
|
7336
|
+
inputSchema: {
|
|
7337
|
+
file_id: z.string().describe('the stored file\'s id (from read_page\'s 📌/📎 lines or list_documents)'),
|
|
7338
|
+
version: z.number().int().positive().optional().describe('a version number; default the latest'),
|
|
7339
|
+
sheet: z.string().optional().describe('a workbook: read only this sheet'),
|
|
7340
|
+
format: z.enum(['grid', 'cells']).optional().describe('a workbook: "grid" (default) or "cells" — one line per cell, Sheet!A1<TAB>value<TAB>=formula'),
|
|
7341
|
+
offset: z.number().int().min(0).optional().describe('continue from here (the previous call says where)'),
|
|
7342
|
+
},
|
|
7343
|
+
},
|
|
7344
|
+
async ({ file_id, version, sheet, format, offset }) => {
|
|
7345
|
+
const q = new URLSearchParams()
|
|
7346
|
+
if (version) q.set('version', String(version))
|
|
7347
|
+
if (sheet) q.set('sheet', sheet)
|
|
7348
|
+
if (format) q.set('format', format)
|
|
7349
|
+
if (offset) q.set('offset', String(offset))
|
|
7350
|
+
let res
|
|
7351
|
+
try { res = await fetchCortex(`${BASE}/api/stored-files/${encodeURIComponent(file_id)}/text?${q}`, { headers: { Authorization: `Bearer ${TOKEN}` } }) }
|
|
7352
|
+
catch (e) { return toolError(`Could not read the file: ${e.message}`) }
|
|
7353
|
+
if (!res.ok) return docFail('read the file', res)
|
|
7354
|
+
const o = await res.json()
|
|
7355
|
+
const head = [`# ${o.title} — ${o.filename}, v${o.version}${o.version !== o.latestVersion ? ` (latest is v${o.latestVersion})` : ''}`]
|
|
7356
|
+
if (o.sheets) head.push(`Sheets: ${o.sheets.map((n) => `"${n}"`).join(', ')}`)
|
|
7357
|
+
if (o.note) head.push(o.note)
|
|
7358
|
+
if (o.offset) head.push(`(continuing from character ${o.offset} of ${o.total})`)
|
|
7359
|
+
const tail = o.nextOffset != null
|
|
7360
|
+
? `\n\n— ${o.total - o.nextOffset} more characters. Continue with read_document file_id:"${o.id}" offset:${o.nextOffset}${version ? ` version:${version}` : ''}${sheet ? ` sheet:"${sheet}"` : ''}.`
|
|
7361
|
+
: ''
|
|
7362
|
+
return { content: [{ type: 'text', text: `${head.join('\n')}\n\n${o.text}${tail}` }] }
|
|
7363
|
+
},
|
|
7364
|
+
)
|
|
7365
|
+
|
|
7366
|
+
server.registerTool(
|
|
7367
|
+
'document_diff',
|
|
7368
|
+
{
|
|
7369
|
+
title: 'What changed in a stored file',
|
|
7370
|
+
description: 'Compare two versions of a stored file — by default the latest against the one before. A workbook is compared cell by cell (`~ Invoices!D21: "1,200" → "1,450"`), a document paragraph by paragraph. The first line says how big the change is, so you can tell a small edit from a rewrite. Use it when read_page warns that a file changed after the page was written, then decide whether what the page says about the file is still true — and fix the page with edit_page if not.',
|
|
7371
|
+
inputSchema: {
|
|
7372
|
+
file_id: z.string().describe('the stored file\'s id'),
|
|
7373
|
+
from: z.number().int().positive().optional().describe('older version; default the one before `to`'),
|
|
7374
|
+
to: z.number().int().positive().optional().describe('newer version; default the latest'),
|
|
7375
|
+
},
|
|
7376
|
+
},
|
|
7377
|
+
async ({ file_id, from, to }) => {
|
|
7378
|
+
const q = new URLSearchParams()
|
|
7379
|
+
if (from) q.set('from', String(from))
|
|
7380
|
+
if (to) q.set('to', String(to))
|
|
7381
|
+
let res
|
|
7382
|
+
try { res = await fetchCortex(`${BASE}/api/stored-files/${encodeURIComponent(file_id)}/diff?${q}`, { headers: { Authorization: `Bearer ${TOKEN}` } }) }
|
|
7383
|
+
catch (e) { return toolError(`Could not compare versions: ${e.message}`) }
|
|
7384
|
+
if (!res.ok) return docFail('compare versions', res)
|
|
7385
|
+
const o = await res.json()
|
|
7386
|
+
const lines = [`# ${o.title}: v${o.from} (${String(o.fromDate).slice(0, 10)}) → v${o.to} (${String(o.toDate).slice(0, 10)})`, `${o.magnitude === 'large' ? 'LARGE change' : o.magnitude === 'small' ? 'Small change' : 'No change'}: ${o.summary}`]
|
|
7387
|
+
if (o.lines?.length) lines.push('', ...o.lines)
|
|
7388
|
+
if (o.truncated) lines.push(`… and ${o.truncated} more changes not shown.`)
|
|
7389
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
7390
|
+
},
|
|
7391
|
+
)
|
|
7392
|
+
|
|
7393
|
+
server.registerTool(
|
|
7394
|
+
'get_document',
|
|
7395
|
+
{
|
|
7396
|
+
title: 'Download a stored file',
|
|
7397
|
+
description: 'Download the original of a file kept in Agnoclast (the actual .xlsx/.docx/…) to ~/Downloads/cortex/ and return its local path — for when you need the real file, e.g. to open it with a spreadsheet library. To just read it, read_document is faster.',
|
|
7398
|
+
inputSchema: {
|
|
7399
|
+
file_id: z.string().describe('the stored file\'s id'),
|
|
7400
|
+
version: z.number().int().positive().optional().describe('a version number; default the latest'),
|
|
7401
|
+
},
|
|
7402
|
+
},
|
|
7403
|
+
async ({ file_id, version }) => {
|
|
7404
|
+
let res
|
|
7405
|
+
try { res = await fetchCortex(`${BASE}/api/stored-files/${encodeURIComponent(file_id)}/download${version ? `?version=${version}` : ''}`, { headers: { Authorization: `Bearer ${TOKEN}` } }) }
|
|
7406
|
+
catch (e) { return toolError(`Could not download: ${e.message}`) }
|
|
7407
|
+
if (!res.ok) return docFail('download', res)
|
|
7408
|
+
const buf = Buffer.from(await res.arrayBuffer())
|
|
7409
|
+
const m = /filename="([^"]+)"/.exec(res.headers.get('content-disposition') ?? '')
|
|
7410
|
+
const name = (m ? m[1] : `agnoclast-file-${file_id}`).replace(/[^\w.\- ]/g, '_')
|
|
7411
|
+
const dir = join(homedir(), 'Downloads', 'cortex')
|
|
7412
|
+
mkdirSync(dir, { recursive: true })
|
|
7413
|
+
const path = join(dir, name)
|
|
7414
|
+
writeFileSync(path, buf)
|
|
7415
|
+
return { content: [{ type: 'text', text: `Saved v${res.headers.get('x-stored-file-version') ?? '?'} of the stored file to ${path} (${buf.length} bytes).` }] }
|
|
7416
|
+
},
|
|
7417
|
+
)
|
|
7418
|
+
|
|
7274
7419
|
// send_imessage — ask to send a text from the user's own Mac (ADR-0076). It no longer texts from this
|
|
7275
7420
|
// machine: it creates a request on Agnoclast that waits on /email → Waiting for the user's OK (or their
|
|
7276
7421
|
// web send rule approves it), and the user's Mac — the desktop app, through Messages — sends it and
|
package/package.json
CHANGED