drafted 1.11.5 → 1.11.7
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/cli/drafted.mjs +2 -2
- package/mcp/server.mjs +37 -20
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -1434,7 +1434,7 @@ skillCmd
|
|
|
1434
1434
|
|
|
1435
1435
|
skillCmd
|
|
1436
1436
|
.command('update')
|
|
1437
|
-
.description('Update a Drafted skill (--id or --slug) from stdin JSON {name?,description?,content?,triggerPatterns?,tags?,setup?}; auto-forks into your org if the target is read-only (global/other-org)')
|
|
1437
|
+
.description('Update a Drafted skill (--id or --slug) from stdin JSON {name?,description?,content?,triggerPatterns?,tags?,setup?,reason?,source?,metadata?}; auto-forks into your org if the target is read-only (global/other-org)')
|
|
1438
1438
|
.option('--id <id>', 'skill id')
|
|
1439
1439
|
.option('--slug <slug>', 'skill slug (resolved to id)')
|
|
1440
1440
|
.option('--org <org>', 'fork into / resolve against this Drafted org (id or name)')
|
|
@@ -1454,7 +1454,7 @@ skillCmd
|
|
|
1454
1454
|
id = lj.id; slug = lj.slug;
|
|
1455
1455
|
}
|
|
1456
1456
|
if (!id) { emitSkillResult(opts.format, { status: 'error', error: 'update requires --id or --slug' }); process.exit(1); }
|
|
1457
|
-
const payload = JSON.stringify({ name: p.name, description: p.description, content: p.content, tags: p.tags, triggerPatterns: p.triggerPatterns, setup: p.setup });
|
|
1457
|
+
const payload = JSON.stringify({ name: p.name, description: p.description, content: p.content, tags: p.tags, triggerPatterns: p.triggerPatterns, setup: p.setup, reason: p.reason, source: p.source, metadata: p.metadata });
|
|
1458
1458
|
const putSkill = (sid) => authFetch(`${server}/api/skills/${sid}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', ...orgHeaders }, body: payload });
|
|
1459
1459
|
|
|
1460
1460
|
const res = await putSkill(id);
|
package/mcp/server.mjs
CHANGED
|
@@ -85,6 +85,7 @@ function getOrCreateSessionState(sid) {
|
|
|
85
85
|
s = {
|
|
86
86
|
activeProjectId: null,
|
|
87
87
|
activeProjectMeta: null,
|
|
88
|
+
boundOrgId: null,
|
|
88
89
|
loadedSkillIds: new Set(),
|
|
89
90
|
gates: createGateState(),
|
|
90
91
|
cachedOrgId: null,
|
|
@@ -699,6 +700,7 @@ async function cloneSession() {
|
|
|
699
700
|
const data = await res.json();
|
|
700
701
|
if (data.sessionId) {
|
|
701
702
|
getState().sessionId = data.sessionId;
|
|
703
|
+
await restoreBoundOrg(data.orgId);
|
|
702
704
|
return true;
|
|
703
705
|
}
|
|
704
706
|
}
|
|
@@ -706,6 +708,33 @@ async function cloneSession() {
|
|
|
706
708
|
return false;
|
|
707
709
|
}
|
|
708
710
|
|
|
711
|
+
// A fresh clone inherits the ROOT login's current org. If this MCP session was
|
|
712
|
+
// already working in a different org (bound via project open or get_org switch),
|
|
713
|
+
// re-assert it on the new session so a WS-drop / 401 / server-bounce recovery is
|
|
714
|
+
// transparent. Otherwise the recovered session silently lands on the root's org,
|
|
715
|
+
// the next request appends the active project's id while scoped to the wrong org,
|
|
716
|
+
// the API returns "project not found", and the api() error handler clears the
|
|
717
|
+
// active project — the churn that shows up as active-project "flapping" after a
|
|
718
|
+
// server restart. Best-effort: a raw fetch (not api()) avoids recursing back
|
|
719
|
+
// through cloneSession on 401; if it fails, that stale-project clear is the
|
|
720
|
+
// backstop, same as before this fix.
|
|
721
|
+
async function restoreBoundOrg(clonedOrgId) {
|
|
722
|
+
const sess = getSessionState();
|
|
723
|
+
const want = sess.boundOrgId;
|
|
724
|
+
if (!want || want === clonedOrgId) return;
|
|
725
|
+
try {
|
|
726
|
+
const res = await fetch(`${getServerUrl()}/auth/switch-org`, {
|
|
727
|
+
method: 'POST',
|
|
728
|
+
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
|
729
|
+
body: JSON.stringify({ orgId: want }),
|
|
730
|
+
});
|
|
731
|
+
if (res.ok) {
|
|
732
|
+
sess.cachedOrgId = null;
|
|
733
|
+
sess.cachedOrgIdTime = 0;
|
|
734
|
+
}
|
|
735
|
+
} catch { /* best-effort; the api() stale-project clear is the backstop */ }
|
|
736
|
+
}
|
|
737
|
+
|
|
709
738
|
async function ensureSession() {
|
|
710
739
|
if (getState().sessionId) return;
|
|
711
740
|
// A pending device-code login (from `auth get_link`) takes priority; consuming
|
|
@@ -922,6 +951,11 @@ function setMcpActiveProject(projectId, meta = null) {
|
|
|
922
951
|
const sess = getSessionState();
|
|
923
952
|
sess.activeProjectId = projectId;
|
|
924
953
|
sess.activeProjectMeta = meta;
|
|
954
|
+
// Remember the org this session is working in so session recovery (a re-clone
|
|
955
|
+
// after a WS drop / 401 / server bounce) can restore it instead of passively
|
|
956
|
+
// inheriting the root login's current org. Only set when known — a (null,null)
|
|
957
|
+
// clear must NOT wipe the bound org (the switch handler sets it explicitly).
|
|
958
|
+
if (meta?.orgId) sess.boundOrgId = meta.orgId;
|
|
925
959
|
}
|
|
926
960
|
|
|
927
961
|
// Clear the active project if its orgId no longer matches the current org.
|
|
@@ -1110,26 +1144,6 @@ function normalizeWikiPath(input) {
|
|
|
1110
1144
|
return input.replace(/^\/+/, '').replace(/\.md$/, '');
|
|
1111
1145
|
}
|
|
1112
1146
|
|
|
1113
|
-
function applyHashlineOps(content, operations) {
|
|
1114
|
-
let lines = content.split('\n');
|
|
1115
|
-
const lineToHash = {};
|
|
1116
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1117
|
-
lineToHash[i] = createHash('sha256').update(lines[i] || '').digest('hex').slice(0, 12);
|
|
1118
|
-
}
|
|
1119
|
-
const sorted = [...operations].reverse();
|
|
1120
|
-
for (const op of sorted) {
|
|
1121
|
-
const matches = Object.entries(lineToHash).filter(([, h]) => h === op.lineHash).map(([idx]) => parseInt(idx));
|
|
1122
|
-
if (matches.length === 0) throw new Error(`Line with hash "${op.lineHash}" not found`);
|
|
1123
|
-
if (matches.length > 1) throw new Error(`Ambiguous hash "${op.lineHash}" matches ${matches.length} lines`);
|
|
1124
|
-
const idx = matches[0];
|
|
1125
|
-
if (op.type === 'replace') { lines[idx] = op.newContent; }
|
|
1126
|
-
else if (op.type === 'delete') { lines.splice(idx, 1); }
|
|
1127
|
-
else if (op.type === 'insertAfter') { lines.splice(idx + 1, 0, op.newContent); }
|
|
1128
|
-
else if (op.type === 'insertBefore') { lines.splice(idx, 0, op.newContent); }
|
|
1129
|
-
}
|
|
1130
|
-
return lines.join('\n');
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
1147
|
async function getTreeAsMap() {
|
|
1134
1148
|
const tree = await api('GET', '/api/wiki/tree');
|
|
1135
1149
|
const pages = tree.pages || [];
|
|
@@ -1860,6 +1874,9 @@ tool('get_org', {
|
|
|
1860
1874
|
// Clear active project too — projects are scoped to orgs, so the
|
|
1861
1875
|
// previous one isn't valid in the new org.
|
|
1862
1876
|
setMcpActiveProject(null, null);
|
|
1877
|
+
// Bind this session to the chosen org so session recovery re-asserts it
|
|
1878
|
+
// (set AFTER the clear above, which would otherwise leave it unchanged).
|
|
1879
|
+
sess.boundOrgId = args.orgId;
|
|
1863
1880
|
const me = await api('GET', '/auth/me');
|
|
1864
1881
|
const orgs = (await api('GET', '/api/orgs')).orgs || [];
|
|
1865
1882
|
const activeOrg = (orgs || []).map(o => ({ id: o.orgId || o.id, name: o.orgName || o.name })).find(o => o.id === me?.orgId) || null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.7",
|
|
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": [
|