drafted 1.7.25 → 1.7.26

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.
Files changed (2) hide show
  1. package/mcp/server.mjs +35 -13
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -614,34 +614,50 @@ function schedulePendingAuthPoll(delayMs = 2000) {
614
614
  if (typeof pendingAuthPollTimer.unref === 'function') pendingAuthPollTimer.unref();
615
615
  }
616
616
 
617
+ // Cookie for normal tool operations. Deliberately uses ONLY this instance's
618
+ // per-process session (getState().sessionId) and never the shared root login in
619
+ // ~/.drafted/auth.json. The root is a *credential for minting child sessions*
620
+ // (see cloneSession): every agent process on the machine reads the same root, so
621
+ // operating on it directly is exactly what let one agent's get_org switch move
622
+ // every other agent's active org. ensureSession() guarantees a per-instance
623
+ // session before any operation runs.
617
624
  function getAuthHeaders() {
618
- const sid = getState().sessionId || getBootstrapSessionId();
625
+ const sid = getState().sessionId;
619
626
  if (sid) return { Cookie: `gc_session=${sid}` };
620
627
  return {};
621
628
  }
622
629
 
630
+ // Mint a fresh per-instance child session from the shared root login and bind it
631
+ // to this process. The root session id (getBootstrapSessionId) is shared by every
632
+ // agent on this machine via ~/.drafted/auth.json; the clone gives THIS process its
633
+ // own server-side session so its active org/project stay isolated from other
634
+ // agents. Returns true once a per-instance session is set.
623
635
  async function cloneSession() {
636
+ if (getState().sessionId) return true;
624
637
  const bootstrapId = getBootstrapSessionId();
625
- if (!bootstrapId) return;
638
+ if (!bootstrapId) return false;
626
639
 
627
640
  try {
628
- const url = `${getServerUrl()}/auth/session/clone`;
629
- const res = await fetch(url, {
641
+ const res = await fetch(`${getServerUrl()}/auth/session/clone`, {
630
642
  method: 'POST',
631
643
  headers: { Cookie: `gc_session=${bootstrapId}` },
632
644
  });
633
- if (!res.ok) return;
634
- const data = await res.json();
635
- if (data.sessionId) {
636
- getState().sessionId = data.sessionId;
645
+ if (res.ok) {
646
+ const data = await res.json();
647
+ if (data.sessionId) {
648
+ getState().sessionId = data.sessionId;
649
+ return true;
650
+ }
637
651
  }
638
- } catch { /* server may not be ready yet, will retry on first API call */ }
652
+ } catch { /* server may not be ready yet; caller retries on next API call */ }
653
+ return false;
639
654
  }
640
655
 
641
656
  async function ensureSession() {
642
657
  if (getState().sessionId) return;
643
- await consumePendingDeviceCode();
644
- if (getState().sessionId) return;
658
+ // A pending device-code login (from `auth get_link`) takes priority; consuming
659
+ // it mints this instance's own session. Otherwise clone the saved root login.
660
+ if (await consumePendingDeviceCode()) return;
645
661
  await cloneSession();
646
662
  }
647
663
 
@@ -1130,10 +1146,14 @@ async function consumePendingDeviceCode() {
1130
1146
  if (!res.ok) return false;
1131
1147
  const data = await res.json();
1132
1148
  if (data.status === 'approved' && data.sessionId) {
1149
+ // data.sessionId is the shared ROOT login. Persist it as the machine-level
1150
+ // credential, then mint our OWN per-instance child session from it (mirrors
1151
+ // the `auth login` tool) so this process never operates on the shared root.
1133
1152
  persistAuthSession(data);
1134
- getState().sessionId = data.sessionId;
1135
1153
  clearPendingDeviceCode();
1136
- return true;
1154
+ getState().sessionId = null;
1155
+ await cloneSession();
1156
+ return !!getState().sessionId;
1137
1157
  }
1138
1158
  if (data.status === 'expired') {
1139
1159
  clearPendingDeviceCode();
@@ -1468,6 +1488,7 @@ tool('screenshot', 'Render a PNG via headless browser. `scope=frame` captures a
1468
1488
  frameId = frame.id;
1469
1489
  }
1470
1490
  const url = `${getServerUrl()}/api/screenshot/${frameId}?width=${width}&height=${height}&fullPage=${fullPage}`;
1491
+ await ensureSession();
1471
1492
  const res = await fetch(url, { headers: getAuthHeaders() });
1472
1493
  if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
1473
1494
  const buffer = await res.arrayBuffer();
@@ -1486,6 +1507,7 @@ tool('screenshot', 'Render a PNG via headless browser. `scope=frame` captures a
1486
1507
  targetSlug = active.slug;
1487
1508
  }
1488
1509
  const url = `${getServerUrl()}/api/canvas-screenshot/${encodeURIComponent(targetSlug)}?layer=${encodeURIComponent(layer)}&width=${width}&height=${height}`;
1510
+ await ensureSession();
1489
1511
  const res = await fetch(url, { headers: getAuthHeaders() });
1490
1512
  if (!res.ok) throw new Error(`Canvas screenshot failed: ${res.status} ${await res.text()}`);
1491
1513
  const buffer = await res.arrayBuffer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.7.25",
3
+ "version": "1.7.26",
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": [