drafted 1.19.43 → 1.19.45
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/mcp/server.mjs +96 -0
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -469,6 +469,9 @@ const TOOL_ANNOTATIONS = {
|
|
|
469
469
|
minion: { title: 'Minions', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Manage Minions: checklist-driven intake surfaces that guide a consumer through a checklist (via a shareable /c/<slug>-<token> link) and write a producible into the project. Dispatch by `action`: meta, list, get, create, update, enable, disable, delete. QA your own Minions with test_start/test_say/test_resolve — drive the checklist conversation yourself (works even when disabled). Requires the agent allowlist.' },
|
|
470
470
|
trigger: { title: 'Inbound triggers', readOnlyHint: false, destructiveHint: true, openWorldHint: true, description: 'Manage inbound webhook triggers for the ACTIVE PROJECT: an external system (AppSheet bot, GitHub, form tool) POSTs to the trigger URL and the server runs an agent conversation in the project from the stored prompt template + payload. Dispatch by `action`: create (returns URL + secret token ONCE — relay it to the user immediately, not retrievable later), list, update (enable/disable, edit template, daily limit, executor), rotate (new token), test (fire a synthetic delivery), deliveries (audit log), delete; for executor="queue" triggers, pending/claim/complete let a LOCAL agent poll and work queued deliveries. Requires the agent allowlist.' },
|
|
471
471
|
fs: { title: 'Filesystem', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Navigate Drafted like a local filesystem: /wiki/<path> pages, /skills/<slug> procedures, /projects/<folder?>/<project>/<layer>/<lane>/<file> frames. Verbs: ls, read, write, edit, mv, rm, search, link, unlink, links.' },
|
|
472
|
+
// Google Drive mirror triage. Mutating (resolve/retry_all/sync rewrite frame
|
|
473
|
+
// content and re-push files) and openWorld (it reaches Google).
|
|
474
|
+
drive: { title: 'Drive conflicts', readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
472
475
|
repo: { title: 'Git repos', readOnlyHint: false, destructiveHint: true, openWorldHint: true, description: 'Registered git repos — the org index of .agents/ skills + identities. A connected repo is the source of truth for its skills; EVERY repo connected to ANY folder in the org is searchable and usable org-wide (no org-level repo — the union is the library). Skills from connected repos are readable via fs(read, path="/skills/<slug>") — fetched from git at read time, always fresh. Authoring a Drafted skill whose slug collides with a repo-indexed skill returns 409 repo_owned pointing at the repo. Dispatch by `action`: list (paginated, compact mode), rescan (re-fetch the tracked branch), entries (search the index). Linking and unlinking are NOT here — a human does them in the Drafted UI. Content stays in git; Drafted keeps a read-only index.' },
|
|
473
476
|
};
|
|
474
477
|
|
|
@@ -4348,6 +4351,99 @@ tool('repo', 'Registered git repos — the org index of .agents/ skills + identi
|
|
|
4348
4351
|
}
|
|
4349
4352
|
return err(new Error(`unknown repo action: ${action}`));
|
|
4350
4353
|
});
|
|
4354
|
+
|
|
4355
|
+
tool('drive', 'The org\'s Google Drive mirror: its state, and the conflicts a human would otherwise clear by hand one button at a time. Org-wide (org members only) — dispatch by `action`:\n- `status` — connection mode (`dwd` = domain-wide delegation, `oauth` = per-user), root folder, sync/pull job state, and OPEN CONFLICT COUNTS BY TYPE. Start here.\n- `conflicts` — the open conflicts (paginated, `compact` mode). Each carries `conflictType`, the Drive error, the frame label and its ids, so you can decide per file instead of guessing.\n- `resolve` — decide ONE conflict: `keep_drafted` (re-push Drafted content to Drive), `keep_drive` (pull Drive content into the frame, with a version snapshot), `duplicate_both`, `retry` (re-check on the next sync, content untouched), `ignore_once`, `archive_mirror` (stop mirroring this file).\n- `retry_all` — close EVERY open `inaccessible_file` conflict as `retry` and queue a sync. This is the right move for a pile of access failures: a broken credential files one conflict per frame, and re-checking is not a decision anyone should make hundreds of times. Files that are genuinely gone come back as fresh, accurate conflicts.\n- `sync` — queue the org push, then re-read `status`.\n\nTRIAGE ORDER that avoids wasted decisions: `status` → if `inaccessible_file` dominates, `retry_all` → `sync` → `conflicts` again, and only then decide the survivors one by one. `archive_mirror` on a stale access failure deactivates a HEALTHY mirror — prefer `retry` unless the file is really gone.', {
|
|
4356
|
+
action: z.enum(['status', 'conflicts', 'resolve', 'retry_all', 'sync']).describe('status: connection + conflict counts; conflicts: list them; resolve: decide one; retry_all: bulk-retry access failures; sync: queue the org push.'),
|
|
4357
|
+
conflictId: z.string().optional().describe('[resolve] the conflict id from `conflicts`.'),
|
|
4358
|
+
resolution: z.enum(['keep_drafted', 'keep_drive', 'duplicate_both', 'retry', 'ignore_once', 'archive_mirror']).optional().describe('[resolve] what to do with it.'),
|
|
4359
|
+
conflictType: z.string().optional().describe('[conflicts] filter to one type, e.g. inaccessible_file, content_conflict, drive_deleted, drafted_deleted.'),
|
|
4360
|
+
limit: z.number().int().min(1).max(100).optional().describe('page size (default 25, max 100).'),
|
|
4361
|
+
offset: z.number().int().min(0).optional().describe('page offset (default 0).'),
|
|
4362
|
+
compact: z.boolean().optional().describe('[conflicts] return only {id,conflictType,label} (default false).'),
|
|
4363
|
+
org: z.string().optional().describe('Org (id or name) to scope this call; defaults to the session\'s working org.'),
|
|
4364
|
+
}, async (args) => {
|
|
4365
|
+
const { action, conflictId, resolution, conflictType, limit, offset, compact, org } = args;
|
|
4366
|
+
const orgHeader = org ? { 'X-Drafted-Org': org } : {};
|
|
4367
|
+
const cap = Math.min(Math.max(1, Math.floor(Number(limit) || 25)), 100);
|
|
4368
|
+
const start = Math.max(0, Math.floor(Number(offset) || 0));
|
|
4369
|
+
|
|
4370
|
+
const listConflicts = async () => {
|
|
4371
|
+
const data = await api('GET', '/api/google/drive/conflicts', undefined, orgHeader);
|
|
4372
|
+
const rows = Array.isArray(data?.conflicts) ? data.conflicts : [];
|
|
4373
|
+
return conflictType ? rows.filter((c) => c.conflictType === conflictType) : rows;
|
|
4374
|
+
};
|
|
4375
|
+
// The counts are the whole point of `status`: 744 open conflicts that are three
|
|
4376
|
+
// causes reads as 744 decisions, which is why nobody clears them.
|
|
4377
|
+
const countByType = (rows) => rows.reduce((acc, c) => {
|
|
4378
|
+
const k = c.conflictType || 'unknown';
|
|
4379
|
+
acc[k] = (acc[k] || 0) + 1;
|
|
4380
|
+
return acc;
|
|
4381
|
+
}, {});
|
|
4382
|
+
|
|
4383
|
+
if (action === 'status') {
|
|
4384
|
+
const [conn, sync, pull, rows] = await Promise.all([
|
|
4385
|
+
api('GET', '/api/google/status', undefined, orgHeader).catch(() => null),
|
|
4386
|
+
api('GET', '/api/google/drive/sync-status', undefined, orgHeader).catch(() => null),
|
|
4387
|
+
api('GET', '/api/google/drive/pull-status', undefined, orgHeader).catch(() => null),
|
|
4388
|
+
listConflicts().catch(() => []),
|
|
4389
|
+
]);
|
|
4390
|
+
return ok({
|
|
4391
|
+
connected: !!conn?.connected,
|
|
4392
|
+
connectionMode: conn?.connectionMode || null,
|
|
4393
|
+
googleEmail: conn?.googleEmail || null,
|
|
4394
|
+
driveRootFolderName: conn?.driveRootFolderName || null,
|
|
4395
|
+
syncEnabled: conn?.syncEnabled ?? null,
|
|
4396
|
+
openConflicts: rows.length,
|
|
4397
|
+
openConflictsByType: countByType(rows),
|
|
4398
|
+
lastSync: sync ? { status: sync.status, finishedAt: sync.finishedAt, error: sync.error } : null,
|
|
4399
|
+
lastPull: pull ? { status: pull.status, finishedAt: pull.finishedAt, error: pull.error } : null,
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
if (action === 'conflicts') {
|
|
4404
|
+
const rows = await listConflicts();
|
|
4405
|
+
const page = rows.slice(start, start + cap);
|
|
4406
|
+
return ok({
|
|
4407
|
+
totalAvailable: rows.length, offset: start, returned: page.length, truncated: start + page.length < rows.length,
|
|
4408
|
+
byType: countByType(rows),
|
|
4409
|
+
conflicts: page.map(compact ? (c => ({ id: c.id, conflictType: c.conflictType, label: c.details?.label || null })) : (c => ({
|
|
4410
|
+
id: c.id,
|
|
4411
|
+
conflictType: c.conflictType,
|
|
4412
|
+
label: c.details?.label || null,
|
|
4413
|
+
error: c.details?.error || null,
|
|
4414
|
+
frameId: c.frameId,
|
|
4415
|
+
projectId: c.projectId,
|
|
4416
|
+
driveFileId: c.driveFileId,
|
|
4417
|
+
createdAt: c.createdAt,
|
|
4418
|
+
}))),
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
if (action === 'resolve') {
|
|
4423
|
+
if (!conflictId) return err(new Error('resolve requires conflictId (get it from action="conflicts")'));
|
|
4424
|
+
if (!resolution) return err(new Error('resolve requires resolution: keep_drafted | keep_drive | duplicate_both | retry | ignore_once | archive_mirror'));
|
|
4425
|
+
const data = await api('POST', `/api/google/drive/conflicts/${encodeURIComponent(conflictId)}/resolve`, { action: resolution }, orgHeader);
|
|
4426
|
+
return ok({ resolved: 1, conflictId, resolution, conflict: data?.conflict || null });
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
if (action === 'retry_all') {
|
|
4430
|
+
const data = await api('POST', '/api/google/drive/conflicts/retry-all', undefined, orgHeader);
|
|
4431
|
+
return ok({
|
|
4432
|
+
retried: data?.retried || 0,
|
|
4433
|
+
note: data?.retried
|
|
4434
|
+
? 'A sync was queued. Re-read action="status" once it finishes: anything still broken comes back as a fresh conflict with an accurate reason.'
|
|
4435
|
+
: 'Nothing to retry — no open inaccessible_file conflicts.',
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
if (action === 'sync') {
|
|
4440
|
+
const job = await api('POST', '/api/google/drive/sync', undefined, orgHeader);
|
|
4441
|
+
return ok({ queued: true, jobId: job?.id || null, status: job?.status || null });
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
return err(new Error(`unknown drive action: ${action}`));
|
|
4445
|
+
});
|
|
4446
|
+
|
|
4351
4447
|
function normalizeMcpUpdatePolicy(policy) {
|
|
4352
4448
|
const severity = policy?.policy?.severity || 'unknown';
|
|
4353
4449
|
const updateAvailable = !!policy?.policy?.updateAvailable;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.45",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|