drafted 1.19.24 → 1.19.25

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 CHANGED
@@ -2218,7 +2218,32 @@ if (!isRemote) tool('auth', 'Sign in to Drafted.\n\n`action=get_link` ALWAYS ret
2218
2218
  });
2219
2219
  if (res.ok) {
2220
2220
  const me = await res.json();
2221
- return ok({ status: 'already_authenticated', userId: me.userId, email: me.userEmail, org: me.currentOrg?.name });
2221
+ // "Signed in" has to mean THIS MACHINE is signed in, not "this process
2222
+ // happens to hold a session". The two came apart the first time anything
2223
+ // deleted ~/.drafted/auth.json underneath a running MCP (the desktop's
2224
+ // credential burn does exactly that, by design): the in-process session
2225
+ // stayed valid, so login answered already_authenticated — truthfully, and
2226
+ // uselessly — while every OTHER reader on the machine stayed signed out.
2227
+ // The desktop's focus listener is one of those readers, so an agent asking
2228
+ // for a human reached nobody and the tool that exists to fix it reported
2229
+ // success.
2230
+ let restoredAuthFile = false;
2231
+ if (!getBootstrapSessionId()) {
2232
+ try {
2233
+ persistAuthSession({ sessionId: existing, userId: me.userId, orgId: me.currentOrg?.id });
2234
+ restoredAuthFile = true;
2235
+ } catch { /* read-only home: report the truth below rather than throwing */ }
2236
+ }
2237
+ return ok({
2238
+ status: 'already_authenticated',
2239
+ userId: me.userId,
2240
+ email: me.userEmail,
2241
+ org: me.currentOrg?.name,
2242
+ ...(restoredAuthFile ? {
2243
+ restoredAuthFile: true,
2244
+ note: 'This session was signed in but ~/.drafted/auth.json was missing, so other readers on this machine (the desktop app, the CLI) were not. Rewrote it from the live session.',
2245
+ } : {}),
2246
+ });
2222
2247
  }
2223
2248
  } catch { /* session invalid, proceed with login */ }
2224
2249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.19.24",
3
+ "version": "1.19.25",
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": [
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ONE definition of the root-URL grammar, shared by the server, the client
3
+ * router and the tests:
4
+ *
5
+ * /o/<org>[/<folder chain>]/wiki|skills|tasks|projects[/<path>]
6
+ *
7
+ * The regex is a literal copy of the four root routes in server/server.mjs. It
8
+ * must stay that way: the client router uses it to decide what it may handle
9
+ * without a round trip, so any divergence renders a different page than the URL
10
+ * names.
11
+ *
12
+ * The source form carries ZERO BACKSLASHES on purpose. This function is
13
+ * embedded into client JS via `String(parseRootPath)` inside a template literal
14
+ * (see AGENTS.md's escape trap: a `\/` written inside a template literal
15
+ * collapses to `/`). Interpolating a runtime string is safe either way, but a
16
+ * body with no backslashes cannot be broken by a later copy-paste into a
17
+ * literal region.
18
+ */
19
+
20
+ export const ROOTS = ['wiki', 'skills', 'tasks', 'projects'];
21
+
22
+ /**
23
+ * @param {string} pathname
24
+ * @returns {{orgParam: string, chain: string, root: string, path: string}|null}
25
+ * `null` means "not a root URL" — the caller must let the server decide.
26
+ * `chain` is decoded ('' = the org itself); `path` is left RAW/encoded,
27
+ * exactly as the server's `req.params[2]` is, so both halves agree.
28
+ */
29
+ export function parseRootPath(pathname) {
30
+ var re = new RegExp('^/o/([^/]+)/(?:(.+?)/)?(wiki|skills|tasks|projects)(?:/(.*))?$');
31
+ var m = re.exec(String(pathname || ''));
32
+ if (!m) return null;
33
+ var path = m[4] || '';
34
+ while (path.length && path.charAt(path.length - 1) === '/') path = path.slice(0, -1);
35
+ // The projects root takes NO sub-path on the server, because
36
+ // /o/<org>/projects/<slug> is the PROJECT CANVAS. Claiming it here would make
37
+ // the client router swallow every project link and render the folder list.
38
+ if (m[3] === 'projects' && path) return null;
39
+ return {
40
+ orgParam: decodeURIComponent(m[1]),
41
+ chain: m[2] ? decodeURIComponent(m[2]) : '',
42
+ root: m[3],
43
+ path: path,
44
+ };
45
+ }