drafted 1.12.7 → 1.12.8
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 +46 -12
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -806,7 +806,7 @@ async function cloneSession() {
|
|
|
806
806
|
// instead of churning a fresh session every process. (Org is per-request, so a
|
|
807
807
|
// shared child session does NOT cause cross-agent org clobbering.)
|
|
808
808
|
const agentKey = (process.env.DRAFTED_AGENT_NAME || '').trim() || bootstrapId;
|
|
809
|
-
const res = await
|
|
809
|
+
const res = await serverFetch(`${getServerUrl()}/auth/session/clone`, {
|
|
810
810
|
method: 'POST',
|
|
811
811
|
headers: { 'Content-Type': 'application/json', Cookie: `gc_session=${bootstrapId}` },
|
|
812
812
|
body: JSON.stringify({ agentKey }),
|
|
@@ -838,7 +838,7 @@ async function restoreBoundOrg(clonedOrgId) {
|
|
|
838
838
|
const want = sess.boundOrgId;
|
|
839
839
|
if (!want || want === clonedOrgId) return;
|
|
840
840
|
try {
|
|
841
|
-
const res = await
|
|
841
|
+
const res = await serverFetch(`${getServerUrl()}/auth/switch-org`, {
|
|
842
842
|
method: 'POST',
|
|
843
843
|
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
|
844
844
|
body: JSON.stringify({ orgId: want }),
|
|
@@ -868,6 +868,37 @@ const MIME_MAP = {
|
|
|
868
868
|
};
|
|
869
869
|
function mimeFromExt(ext) { return MIME_MAP[ext?.toLowerCase()] || 'application/octet-stream'; }
|
|
870
870
|
|
|
871
|
+
// TLS-trust failures from antivirus/corporate HTTPS interception (Norton,
|
|
872
|
+
// Zscaler, ...) surface from undici as a bare "fetch failed" — agents then tell
|
|
873
|
+
// users "network error / you're offline" for a day (real incident, 2026-07-02).
|
|
874
|
+
// Node does not use the OS certificate store, so the interceptor's re-signed
|
|
875
|
+
// chain is untrusted. Translate the code into the actual fix.
|
|
876
|
+
const TLS_TRUST_CODES = new Set([
|
|
877
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'SELF_SIGNED_CERT_IN_CHAIN',
|
|
878
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
879
|
+
'UNABLE_TO_GET_ISSUER_CERT', 'CERT_UNTRUSTED',
|
|
880
|
+
]);
|
|
881
|
+
function enrichFetchError(error, url) {
|
|
882
|
+
const code = String(error?.cause?.code || error?.code || '').toUpperCase();
|
|
883
|
+
if (!TLS_TRUST_CODES.has(code)) return error;
|
|
884
|
+
const e = new Error(
|
|
885
|
+
`TLS interception detected reaching ${url} (${code}): antivirus or a corporate proxy ` +
|
|
886
|
+
`(Norton, Zscaler, Netskope, ...) is re-signing HTTPS, and Node does not use the OS ` +
|
|
887
|
+
`certificate store, so it rejects the interceptor's certificate. This is NOT a Drafted ` +
|
|
888
|
+
`outage and NOT a sign-out. Fixes, best first: (1) exclude drafted.live and *.drafted.live ` +
|
|
889
|
+
`from the interceptor's HTTPS/SSL scanning; (2) export the interceptor's root certificate ` +
|
|
890
|
+
`to a PEM file and set NODE_EXTRA_CA_CERTS=<path> in this MCP server's env; (3) on Node ` +
|
|
891
|
+
`>= 22.15, set NODE_OPTIONS=--use-system-ca so Node trusts the OS store. NEVER set ` +
|
|
892
|
+
`NODE_TLS_REJECT_UNAUTHORIZED=0 (disables all TLS verification).`
|
|
893
|
+
);
|
|
894
|
+
e.cause = error;
|
|
895
|
+
return e;
|
|
896
|
+
}
|
|
897
|
+
async function serverFetch(url, opts) {
|
|
898
|
+
try { return await fetch(url, opts); }
|
|
899
|
+
catch (e) { throw enrichFetchError(e, url); }
|
|
900
|
+
}
|
|
901
|
+
|
|
871
902
|
async function api(method, path, body, extraHeaders = {}, _retried = false) {
|
|
872
903
|
await ensureSession();
|
|
873
904
|
const pid = getState().projectId;
|
|
@@ -899,7 +930,7 @@ async function api(method, path, body, extraHeaders = {}, _retried = false) {
|
|
|
899
930
|
opts.body = JSON.stringify(body);
|
|
900
931
|
}
|
|
901
932
|
|
|
902
|
-
const res = await
|
|
933
|
+
const res = await serverFetch(url, opts);
|
|
903
934
|
const text = await res.text();
|
|
904
935
|
|
|
905
936
|
// Session expired after server restart, or a browser approval just completed
|
|
@@ -1399,7 +1430,7 @@ async function consumePendingDeviceCode() {
|
|
|
1399
1430
|
if (!pending?.deviceCode) return false;
|
|
1400
1431
|
|
|
1401
1432
|
try {
|
|
1402
|
-
const res = await
|
|
1433
|
+
const res = await serverFetch(`${getServerUrl()}/auth/device/token`, {
|
|
1403
1434
|
method: 'POST',
|
|
1404
1435
|
headers: { 'Content-Type': 'application/json' },
|
|
1405
1436
|
body: JSON.stringify({ deviceCode: pending.deviceCode }),
|
|
@@ -1435,7 +1466,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1435
1466
|
}, async ({ action }) => {
|
|
1436
1467
|
try {
|
|
1437
1468
|
if (action === 'get_link') {
|
|
1438
|
-
const codeRes = await
|
|
1469
|
+
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
1439
1470
|
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
1440
1471
|
const data = await codeRes.json();
|
|
1441
1472
|
persistPendingDeviceCode(data);
|
|
@@ -1449,7 +1480,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1449
1480
|
const existing = getState().sessionId || getBootstrapSessionId();
|
|
1450
1481
|
if (existing) {
|
|
1451
1482
|
try {
|
|
1452
|
-
const res = await
|
|
1483
|
+
const res = await serverFetch(`${getServerUrl()}/auth/me`, {
|
|
1453
1484
|
headers: { Cookie: `gc_session=${existing}` },
|
|
1454
1485
|
});
|
|
1455
1486
|
if (res.ok) {
|
|
@@ -1467,7 +1498,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1467
1498
|
({ deviceCode, verificationUrl, expiresIn } = pending);
|
|
1468
1499
|
reusingPending = true;
|
|
1469
1500
|
} else {
|
|
1470
|
-
const codeRes = await
|
|
1501
|
+
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
1471
1502
|
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
1472
1503
|
({ deviceCode, verificationUrl, expiresIn } = await codeRes.json());
|
|
1473
1504
|
}
|
|
@@ -1491,7 +1522,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1491
1522
|
const deadline = Date.now() + (expiresIn * 1000);
|
|
1492
1523
|
while (Date.now() < deadline) {
|
|
1493
1524
|
await new Promise(r => setTimeout(r, 4000));
|
|
1494
|
-
const res = await
|
|
1525
|
+
const res = await serverFetch(`${getServerUrl()}/auth/device/token`, {
|
|
1495
1526
|
method: 'POST',
|
|
1496
1527
|
headers: { 'Content-Type': 'application/json' },
|
|
1497
1528
|
body: JSON.stringify({ deviceCode }),
|
|
@@ -1543,15 +1574,18 @@ async function sessionSurfaceBlock() {
|
|
|
1543
1574
|
const cookieSid = sessionId || getBootstrapSessionId();
|
|
1544
1575
|
let me = null;
|
|
1545
1576
|
let unreachable = false;
|
|
1577
|
+
let unreachableWhy = null;
|
|
1546
1578
|
if (cookieSid) {
|
|
1547
1579
|
try {
|
|
1548
|
-
const res = await
|
|
1580
|
+
const res = await serverFetch(`${getServerUrl()}/auth/me`, { headers: { Cookie: `gc_session=${cookieSid}` } });
|
|
1549
1581
|
if (res.ok) me = await res.json();
|
|
1550
|
-
} catch {
|
|
1582
|
+
} catch (e) {
|
|
1551
1583
|
// Transport failure, NOT a sign-out. Conflating the two made agents tell
|
|
1552
1584
|
// users "you're signed out" during a network blip and start needless
|
|
1553
|
-
// re-logins — surface the distinction instead.
|
|
1585
|
+
// re-logins — surface the distinction instead. serverFetch already
|
|
1586
|
+
// translates TLS-interception codes into the actual fix.
|
|
1554
1587
|
unreachable = true;
|
|
1588
|
+
if (String(e?.message || '').startsWith('TLS interception')) unreachableWhy = e.message;
|
|
1555
1589
|
}
|
|
1556
1590
|
}
|
|
1557
1591
|
return {
|
|
@@ -1566,7 +1600,7 @@ async function sessionSurfaceBlock() {
|
|
|
1566
1600
|
alive: false,
|
|
1567
1601
|
...(unreachable ? {
|
|
1568
1602
|
serverUnreachable: true,
|
|
1569
|
-
note: `Could not reach ${getServerUrl()} — identity UNKNOWN, not signed out. This is a network/transport failure: do not tell the user they are logged out and do not start a new login; retry when connectivity is back.`,
|
|
1603
|
+
note: unreachableWhy || `Could not reach ${getServerUrl()} — identity UNKNOWN, not signed out. This is a network/transport failure: do not tell the user they are logged out and do not start a new login; retry when connectivity is back.`,
|
|
1570
1604
|
} : {}),
|
|
1571
1605
|
};
|
|
1572
1606
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.8",
|
|
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": [
|