agentic-drive 0.11.1 → 0.12.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ - Add typed embedded navigation with `DriveLocation`, `location`, and `onNavigate`.
6
+ Mounted views expose `setLocation` and `getLocation` for host routing.
7
+ File links download in place. Explicit download locations return with a
8
+ history replace hint, and navigation cancels pending downloads.
9
+ - Keep the current location on option updates. Route embedded activity actions
10
+ through the host navigation API.
11
+
3
12
  ## 0.11.1
4
13
 
5
14
  - Allow `agentic-design` and `agentic-identity` releases below 1.0.0 while keeping the existing lower bounds. Test with identity 0.12.0 and design 0.10.1.
package/README.md CHANGED
@@ -248,7 +248,7 @@ view.unmount();
248
248
  ```
249
249
 
250
250
  `mount` returns a `MountedDrive` handle. `DriveBrowser` is the React component;
251
- `DriveUiOptions` is its configuration. `OnOpenItem` is the reference-open callback type, and `Item` is its argument type. The CSS path supplies its styles.
251
+ `DriveUiOptions` is its configuration. `DriveLocation` is the folder, trash, activity, or file location type. `DriveNavigationOptions` describes host history updates. `OnOpenItem` is the reference-open callback type, and `Item` is its argument type. The CSS path supplies its styles.
252
252
 
253
253
  ### CLI, MCP, and adapters
254
254
 
@@ -323,6 +323,62 @@ drive.update({ baseUrl: location.origin, getToken: () => session.token, onOpenIt
323
323
 
324
324
  `DriveUiOptions` exports this optional hook for both `mount` and `DriveBrowser`. `update()` can replace or remove it. React hosts can change the `onOpenItem` prop without resetting the current folder. The callback is synchronous.
325
325
 
326
+ ### Embed navigation
327
+
328
+ Use the exported `DriveLocation` type to select a folder, trash, item activity,
329
+ or file download. A folder location has `{ kind: "folder", path: "/" }`.
330
+ Its `path` accepts a folder id, an absolute path such as `"/Work/Plans"`,
331
+ or a path from a folder id. Other locations use `{ kind: "trash" }`,
332
+ `{ kind: "activity", itemId }`, or `{ kind: "file", itemId }`.
333
+ The server checks access for each request. An inaccessible folder shows an error.
334
+
335
+ `mount` starts at `options.location`, or root when it is absent.
336
+ `setLocation` changes the view without remounting the drive.
337
+ `getLocation` returns a copy of the active location. For a folder, pass its
338
+ `path` as `folder` to the client's `registerDocument` method.
339
+ Trash, activity, and downloads have no active folder.
340
+
341
+ ```ts
342
+ import { mount, type DriveLocation } from "agentic-drive/ui";
343
+
344
+ const readLocation = (): DriveLocation =>
345
+ JSON.parse(new URL(location.href).searchParams.get("drive") ??
346
+ '{"kind":"folder","path":"/"}'); // Validate URL data in your app.
347
+ const drive = mount(document.getElementById("drive")!, {
348
+ baseUrl: location.origin,
349
+ getToken: () => session.token,
350
+ location: readLocation(),
351
+ onNavigate(next, { replace }) {
352
+ const url = new URL(location.href);
353
+ url.searchParams.set("drive", JSON.stringify(next));
354
+ if (replace) history.replaceState(null, "", url);
355
+ else history.pushState(null, "", url);
356
+ },
357
+ });
358
+ addEventListener("popstate", () => drive.setLocation(readLocation()));
359
+ const active = drive.getLocation();
360
+ if (active.kind === "folder") {
361
+ await client.registerDocument({ name: "Plan", ref: "docs:plan", folder: active.path });
362
+ }
363
+ ```
364
+
365
+ `onNavigate` reports folder, breadcrumb, trash, and activity navigation with
366
+ `replace: false`. File links download in place without changing the location or
367
+ notifying the host. Errors leave the folder open. Navigation cancels pending
368
+ downloads, so a late response cannot move the view or start a download.
369
+ An explicit `file` location returns to its parent with `replace: true`. Use
370
+ `replaceState` for that return. Root returns use `"/"`.
371
+ Assigning a location through initial render, `setLocation`, or `update` does not
372
+ call the callback itself. An explicit file location starts a download; its completed return
373
+ does call the callback. An explicit file location that fails shows an error and
374
+ stays selected; the host can set a folder location to leave it.
375
+ `update` keeps the current location unless it receives a new `location`.
376
+ In React, `DriveBrowser location={current} onNavigate={setCurrent}` is controlled:
377
+ the host must apply the next location. Without `location`, React keeps it locally.
378
+ Changes to server or token options keep the location and reload its data.
379
+ The embed never changes the host URL itself.
380
+
381
+
326
382
  `getToken` returns 1 to 512 letters, digits, `-` or `_`, or `undefined` to send no `authorization` header. A bad `baseUrl` or a `getToken` that is not a function makes `mount` throw a `TypeError`. The server still checks every request against the token. The embed shows files and trash with upload, rename, move, share, and the "Created by agent" label. It has no sign-in form, because your app owns sign-in. It does not change the page address, and it has no theme switch. `agentic-design/styles.css` sets base styles for the whole page, and it follows the dark mode of the system unless your page sets `data-theme`. Add `agentic-design@">=0.8.0 <1.0.0"` to your own dependencies, because your page imports its files. The Worker sends no CORS headers, so the server must be on the origin of your page. Set `baseUrl` to the same address as `DRIVE_PUBLIC_URL`, or the UI refuses its download links. `unmount` and `update` abort the requests that run and stop an upload that has more files to send. The UI keeps the list or grid choice in `localStorage` under `agentic-drive-view`, and it has its own heading for each view. Dialogs, menus, and messages render in `document.body`, outside your element. A click with Ctrl, Cmd, Shift, or the middle button on a folder link does not open the folder. The class names start with `drive-`. The design-system classes start with `agd-`. `DriveBrowser` is the same UI as a React component. It closes its requests when it unmounts. Wrap it in `TooltipProvider` and `ToastProvider` from `agentic-design/react`. `react` and `react-dom` (version 19 or later) are optional peer dependencies. Install them in your project when you use `agentic-drive/ui`. The web UI that the Worker serves uses the same views.
327
383
 
328
384
  ### Serve the UI from the package
package/dist/account.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as Actor, D as Drive, C as Ctx } from './drive-CS1CLfqo.js';
1
+ import { A as Actor, D as Drive, C as Ctx } from './drive-9cZ_9xU4.js';
2
2
  import 'agentic-identity';
3
3
 
4
4
  interface DriveAccountDeps {
@@ -1,6 +1,6 @@
1
1
  // src/version.ts
2
2
  import { createRequire } from "module";
3
- var VERSION = true ? "0.11.1" : readPackageVersion();
3
+ var VERSION = true ? "0.12.0" : readPackageVersion();
4
4
 
5
5
  export {
6
6
  VERSION
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-AB2RTTHJ.js";
5
5
  import {
6
6
  VERSION
7
- } from "./chunk-W27ZQYPT.js";
7
+ } from "./chunk-J2EPNUYS.js";
8
8
  import {
9
9
  buildExport,
10
10
  upload
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { i as ErrorCode, N as Node, d as ActivityEntry, S as SearchQuery } from './drive-CS1CLfqo.js';
2
- export { c as ActivityAction } from './drive-CS1CLfqo.js';
1
+ import { i as ErrorCode, N as Node, d as ActivityEntry, S as SearchQuery } from './drive-9cZ_9xU4.js';
2
+ export { c as ActivityAction } from './drive-9cZ_9xU4.js';
3
3
  import 'agentic-identity';
4
4
 
5
5
  type Role = "viewer" | "commenter" | "editor" | "owner";
@@ -248,7 +248,7 @@ declare function createDrive(deps: DriveDeps): {
248
248
  exportPerson(ctx: Ctx): Promise<{
249
249
  subject: {
250
250
  id: string;
251
- kind: "human" | "agent_of_human" | "agent_of_agent" | "agent" | "user_token" | null;
251
+ kind: "human" | "agent" | "agent_of_human" | "agent_of_agent" | "user_token" | null;
252
252
  };
253
253
  partial: boolean;
254
254
  authored: {
package/dist/embed.d.ts CHANGED
@@ -1,12 +1,34 @@
1
1
  import * as react from 'react';
2
2
  import { Item } from './client.js';
3
- import './drive-CS1CLfqo.js';
3
+ import './drive-9cZ_9xU4.js';
4
4
  import 'agentic-identity';
5
5
 
6
6
  /** Return true to handle a reference open; false or void keeps the download action. */
7
7
  type OnOpenItem = (item: Item) => boolean | void;
8
8
 
9
+ /** Folder locators follow the SDK rules: "/" is root; use an id or an absolute path. */
10
+ type DriveLocation = {
11
+ kind: "folder";
12
+ path: string;
13
+ } | {
14
+ kind: "trash";
15
+ } | {
16
+ kind: "activity";
17
+ itemId: string;
18
+ } | {
19
+ kind: "file";
20
+ itemId: string;
21
+ };
22
+ /** Replace the current host history entry for a transient navigation. */
23
+ interface DriveNavigationOptions {
24
+ replace: boolean;
25
+ }
9
26
  interface DriveUiOptions {
27
+ /** Controlled in React. In mount/update, selects the location; omission preserves it. */
28
+ location?: DriveLocation;
29
+ /** Internal navigation. Controlled React hosts must apply the next location.
30
+ * Use replaceState when options.replace is true (an explicit download returns). */
31
+ onNavigate?: (location: DriveLocation, options: DriveNavigationOptions) => void;
10
32
  /** Runs when a person activates a reference item. Return true to skip the built-in download. */
11
33
  onOpenItem?: OnOpenItem;
12
34
  /** The address of the drive server, for example `https://drive.example.com`. Only its origin is used. */
@@ -15,15 +37,19 @@ interface DriveUiOptions {
15
37
  getToken: () => string | undefined | Promise<string | undefined>;
16
38
  }
17
39
  /** The folders and trash of the signed-in caller, with upload, share, and the machine-made label. It needs the tooltip and toast providers above it. `mount` adds them.
18
- * It shows the root again when `baseUrl` changes. A bad `baseUrl` or `getToken` throws. Pass a stable `getToken`, because a new function makes a new client. */
19
- declare function DriveBrowser({ baseUrl, getToken, onOpenItem }: DriveUiOptions): react.JSX.Element | null;
40
+ * It keeps its location when `baseUrl` changes. A bad `baseUrl` or `getToken` throws. Pass a stable `getToken`, because a new function makes a new client. */
41
+ declare function DriveBrowser({ baseUrl, getToken, onOpenItem, location, onNavigate }: DriveUiOptions): react.JSX.Element | null;
20
42
  interface MountedDrive {
21
- /** Show the drive with another address, token callback, or item-open callback. The folder the person has open resets. */
43
+ /** Show the drive with another address, token callback, or item-open callback. The current location stays unless options.location is supplied. */
22
44
  update(options: DriveUiOptions): void;
45
+ /** Select a location without remounting the drive. Does not call onNavigate. */
46
+ setLocation(location: DriveLocation): void;
47
+ /** Current location. A folder path is suitable for SDK registerDocument({ folder }). */
48
+ getLocation(): DriveLocation;
23
49
  /** Remove the UI. A request that runs is aborted, and no later request starts or calls `getToken`. Calling it again does nothing. */
24
50
  unmount(): void;
25
51
  }
26
52
  /** Render the drive into an element, for an app that does not use React itself. A bad `baseUrl` or `getToken` throws a `TypeError`. */
27
53
  declare function mount(element: HTMLElement, options: DriveUiOptions): MountedDrive;
28
54
 
29
- export { DriveBrowser, type DriveUiOptions, Item, type MountedDrive, type OnOpenItem, mount };
55
+ export { DriveBrowser, type DriveLocation, type DriveNavigationOptions, type DriveUiOptions, Item, type MountedDrive, type OnOpenItem, mount };
package/dist/embed.js CHANGED
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-SEHT5Q5L.js";
5
5
 
6
6
  // ui/embed.tsx
7
- import { useEffect as useEffect3, useState as useState3 } from "react";
7
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
8
8
  import { createRoot } from "react-dom/client";
9
- import { FolderIcon as FolderIcon3, SidebarNav as SidebarNav2, ToastProvider, TooltipProvider } from "agentic-design/react";
9
+ import { FolderIcon as FolderIcon3, SidebarNav as SidebarNav2, ToastProvider, TooltipProvider, useToast as useToast2 } from "agentic-design/react";
10
10
 
11
11
  // ui/App.tsx
12
12
  import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
@@ -183,11 +183,9 @@ function useLoad(fn) {
183
183
  const [state, set] = useState2({ status: "loading" });
184
184
  const [tick, setTick] = useState2(0);
185
185
  useEffect2(() => {
186
- let live = true;
187
- fn().then((data) => live && set({ status: "ok", data }), (e) => live && set({ status: "error", error: errorText(e) }));
188
- return () => {
189
- live = false;
190
- };
186
+ const pending = new AbortController();
187
+ fn(pending.signal).then((data) => !pending.signal.aborted && set({ status: "ok", data }), (e) => !pending.signal.aborted && set({ status: "error", error: errorText(e) }));
188
+ return () => pending.abort();
191
189
  }, [tick]);
192
190
  return [state, () => setTick((n) => n + 1)];
193
191
  }
@@ -200,18 +198,14 @@ function Status({ state, retry, children }) {
200
198
  return /* @__PURE__ */ jsx3(Fragment, { children });
201
199
  }
202
200
  var routeParts = (hash) => hash.replace(/^#\/?/, "").split("/");
203
- var goHash = (loc) => {
204
- history.replaceState(null, "", `#/${loc}`);
205
- dispatchEvent(new HashChangeEvent("hashchange"));
206
- };
207
- function Routes({ hash, client, origin, go, onOpenItem }) {
201
+ function Routes({ hash, client, origin, go, onOpenItem, folderLoc }) {
208
202
  const parts = routeParts(hash);
209
203
  if (parts[0] === "trash") return /* @__PURE__ */ jsx3(TrashView, { client }, "trash");
210
204
  if (parts[0] === "a" && parts[1]) return /* @__PURE__ */ jsx3(ActivityView, { client, id: parts[1] }, hash);
211
205
  if (parts[0] === "f" && parts[1]) return /* @__PURE__ */ jsx3(Download, { client, id: parts[1], origin, go }, hash);
212
- return /* @__PURE__ */ jsx3(Browser, { client, loc: parts[0] || "/", origin, onOpenItem }, hash);
206
+ return /* @__PURE__ */ jsx3(Browser, { client, loc: folderLoc ?? (parts[0] || "/"), go, origin, onOpenItem }, hash);
213
207
  }
214
- function Browser({ client, loc, origin, onOpenItem }) {
208
+ function Browser({ client, loc, origin, onOpenItem, go }) {
215
209
  const openItem = (e, item) => {
216
210
  if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || item.kind !== "ref") return;
217
211
  if (onOpenItem?.(item) === true) e.preventDefault();
@@ -299,7 +293,7 @@ function Browser({ client, loc, origin, onOpenItem }) {
299
293
  /* @__PURE__ */ jsx3(Menu, { align: "end", trigger: /* @__PURE__ */ jsx3(IconButton, { label: `Actions for ${item.name}`, icon: /* @__PURE__ */ jsx3(MoreIcon, {}), variant: "ghost" }), items: [
300
294
  { label: "Rename", onSelect: () => setDlg({ kind: "rename", item }) },
301
295
  { label: "Move", onSelect: () => setDlg({ kind: "move", item }) },
302
- { label: "Activity", onSelect: () => goHash(`a/${item.id}`) },
296
+ { label: "Activity", onSelect: () => go(`a/${item.id}`) },
303
297
  { label: "Share", onSelect: () => setDlg({ kind: "share", item }) },
304
298
  "separator",
305
299
  { label: "Move to trash", danger: true, onSelect: () => void act(() => client.trash(item.id), `Moved ${item.name} to trash`) }
@@ -508,16 +502,23 @@ function ActivityView({ client, id }) {
508
502
  /* @__PURE__ */ jsx3(ActivityList, { entries: state.data.entries })
509
503
  ] }) }) });
510
504
  }
505
+ async function downloadItem(client, id, origin, signal) {
506
+ const item = await client.stat(id, { signal });
507
+ signal.throwIfAborted();
508
+ const { url } = await client.url(id, void 0, { signal });
509
+ signal.throwIfAborted();
510
+ if (new URL(url, origin).origin !== origin) throw new Error("The download link points to another site.");
511
+ const a = document.createElement("a");
512
+ a.href = url;
513
+ a.download = item.name;
514
+ a.click();
515
+ return item;
516
+ }
511
517
  function Download({ client, id, origin, go }) {
512
- const [state, retry] = useLoad(async () => {
513
- const item = await client.stat(id);
514
- const { url } = await client.url(id);
515
- if (new URL(url, origin).origin !== origin) throw new Error("The download link points to another site.");
516
- const a = document.createElement("a");
517
- a.href = url;
518
- a.download = item.name;
519
- a.click();
520
- go(item.parentId);
518
+ const [state, retry] = useLoad(async (signal) => {
519
+ const item = await downloadItem(client, id, origin, signal);
520
+ signal.throwIfAborted();
521
+ go(item.parentId, { replace: true });
521
522
  return item;
522
523
  });
523
524
  return /* @__PURE__ */ jsx3(Status, { state, retry, children: null });
@@ -525,6 +526,22 @@ function Download({ client, id, origin, go }) {
525
526
 
526
527
  // ui/embed.tsx
527
528
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
529
+ var ROOT = { kind: "folder", path: "/" };
530
+ function locationHash(location2) {
531
+ if (location2.kind === "folder" && typeof location2.path === "string" && location2.path.length)
532
+ return `#/folder/${encodeURIComponent(location2.path)}`;
533
+ if (location2.kind === "trash") return "#/trash";
534
+ if ((location2.kind === "activity" || location2.kind === "file") && typeof location2.itemId === "string" && location2.itemId.length)
535
+ return `#/${location2.kind === "activity" ? "a" : "f"}/${encodeURIComponent(location2.itemId)}`;
536
+ throw new TypeError("Invalid drive location");
537
+ }
538
+ function fromHash(hash) {
539
+ const path = hash.slice(2);
540
+ if (path === "trash") return { kind: "trash" };
541
+ if (path.startsWith("a/")) return { kind: "activity", itemId: path.slice(2) };
542
+ if (path.startsWith("f/")) return { kind: "file", itemId: path.slice(2) };
543
+ return { kind: "folder", path: path || "/" };
544
+ }
528
545
  var PLACEHOLDER = "from-get-token";
529
546
  function makeClient({ baseUrl, getToken }) {
530
547
  if (typeof getToken !== "function") throw new TypeError("getToken must be a function");
@@ -543,24 +560,48 @@ function makeClient({ baseUrl, getToken }) {
543
560
  return { client: createClient({ baseUrl, token: PLACEHOLDER, fetch: fetchWithToken }), close: () => closed.abort() };
544
561
  }
545
562
  var clients = 0;
546
- function DriveBrowser({ baseUrl, getToken, onOpenItem }) {
563
+ function DriveBrowser({ baseUrl, getToken, onOpenItem, location: location2, onNavigate }) {
564
+ const [local, setLocal] = useState3(ROOT);
565
+ const active = location2 ?? local;
566
+ locationHash(active);
567
+ const navigate = (next, options = { replace: false }) => {
568
+ if (location2 === void 0) setLocal(next);
569
+ onNavigate?.(next, options);
570
+ };
547
571
  const [made, setMade] = useState3();
548
572
  useEffect3(() => {
549
573
  const next = makeClient({ baseUrl, getToken });
550
574
  setMade({ id: ++clients, client: next.client, origin: new URL(baseUrl).origin });
551
575
  return () => next.close();
552
576
  }, [baseUrl, getToken]);
553
- return made ? /* @__PURE__ */ jsx4(DriveView, { client: made.client, origin: made.origin, onOpenItem }, made.id) : null;
577
+ return made ? /* @__PURE__ */ jsx4(DriveView, { client: made.client, origin: made.origin, onOpenItem, location: active, onNavigate: navigate }, made.id) : null;
554
578
  }
555
- function DriveView({ client, origin, onOpenItem }) {
556
- const [hash, setHash] = useState3("#/");
557
- const go = (loc) => setHash(`#/${loc === "/" ? "" : loc}`);
579
+ function DriveView({ client, origin, onOpenItem, location: location2, onNavigate }) {
580
+ const hash = locationHash(location2);
581
+ const toast = useToast2();
582
+ const pending = useRef3(new AbortController());
583
+ useEffect3(() => {
584
+ const controller = new AbortController();
585
+ pending.current = controller;
586
+ return () => controller.abort();
587
+ }, [hash]);
588
+ const download = async (id) => {
589
+ const signal = pending.current.signal;
590
+ try {
591
+ await downloadItem(client, id, origin, signal);
592
+ } catch (e) {
593
+ if (!signal.aborted) toast({ title: "Could not download", description: e instanceof DriveApiError ? `${e.message} (${e.code})` : e instanceof Error ? e.message : "Download failed", tone: "danger" });
594
+ }
595
+ };
596
+ const go = (loc, options = { replace: false }) => onNavigate(fromHash(`#/${loc === "/" || loc === "root" ? "" : loc}`), options);
558
597
  const follow = (e) => {
559
598
  if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
560
599
  const link = e.target.closest?.("a[href^='#/']");
561
600
  if (!link || !e.currentTarget.contains(link)) return;
562
601
  e.preventDefault();
563
- setHash(link.getAttribute("href"));
602
+ const next = fromHash(link.getAttribute("href"));
603
+ if (next.kind === "file") void download(next.itemId);
604
+ else onNavigate(next, { replace: false });
564
605
  };
565
606
  const place = hash.startsWith("#/trash") ? "trash" : "files";
566
607
  return /* @__PURE__ */ jsxs4("div", { className: "drive-root", onClick: follow, children: [
@@ -568,26 +609,45 @@ function DriveView({ client, origin, onOpenItem }) {
568
609
  { href: "#/", label: "My files", icon: /* @__PURE__ */ jsx4(FolderIcon3, { "aria-hidden": "true" }), current: place === "files" },
569
610
  { href: "#/trash", label: "Trash", current: place === "trash" }
570
611
  ] }),
571
- /* @__PURE__ */ jsx4("div", { className: "drive-main", children: /* @__PURE__ */ jsx4(Routes, { hash, client, origin, go, onOpenItem }) })
612
+ /* @__PURE__ */ jsx4("div", { className: "drive-main", children: /* @__PURE__ */ jsx4(Routes, { hash, client, origin, go, onOpenItem, folderLoc: location2.kind === "folder" ? location2.path : void 0 }) })
572
613
  ] });
573
614
  }
574
615
  function mount(element, options) {
616
+ let active = { ...options.location ?? ROOT };
617
+ locationHash(active);
618
+ let settings = options;
575
619
  let current = makeClient(options);
576
620
  const root = createRoot(element);
577
621
  let open = true;
578
622
  let version = 0;
623
+ const navigate = (next, options2 = { replace: false }) => {
624
+ active = { ...next };
625
+ render(settings, current.client);
626
+ settings.onNavigate?.({ ...active }, options2);
627
+ };
579
628
  const render = (next, client) => root.render(
580
- /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsx4(ToastProvider, { children: /* @__PURE__ */ jsx4(DriveView, { client, origin: new URL(next.baseUrl).origin, onOpenItem: next.onOpenItem }, version++) }) })
629
+ /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsx4(ToastProvider, { children: /* @__PURE__ */ jsx4(DriveView, { client, origin: new URL(next.baseUrl).origin, onOpenItem: next.onOpenItem, location: active, onNavigate: navigate }, version) }) })
581
630
  );
582
631
  render(options, current.client);
583
632
  return {
584
633
  update: (next) => {
585
634
  if (!open) return;
635
+ if (next.location !== void 0) locationHash(next.location);
586
636
  const made = makeClient(next);
637
+ settings = next;
638
+ if (next.location !== void 0) active = { ...next.location };
639
+ version++;
587
640
  current.close();
588
641
  current = made;
589
642
  render(next, made.client);
590
643
  },
644
+ setLocation: (next) => {
645
+ if (!open) return;
646
+ locationHash(next);
647
+ active = { ...next };
648
+ render(settings, current.client);
649
+ },
650
+ getLocation: () => ({ ...active }),
591
651
  unmount: () => {
592
652
  if (!open) return;
593
653
  open = false;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as Actor } from './drive-CS1CLfqo.js';
2
- export { a as ACTOR_KINDS, b as Action, c as ActivityAction, d as ActivityEntry, e as ActorKind, f as Can, g as DriveDeps, h as DriveError, E as ERROR_STATUS, i as ErrorCode, R as ROOT_ID, S as SearchQuery, j as createDrive, p as parseName, k as parseSubject, s as subjectKindOf } from './drive-CS1CLfqo.js';
1
+ import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as Actor } from './drive-9cZ_9xU4.js';
2
+ export { a as ACTOR_KINDS, b as Action, c as ActivityAction, d as ActivityEntry, e as ActorKind, f as Can, g as DriveDeps, h as DriveError, E as ERROR_STATUS, i as ErrorCode, R as ROOT_ID, S as SearchQuery, j as createDrive, p as parseName, k as parseSubject, s as subjectKindOf } from './drive-9cZ_9xU4.js';
3
3
  import { Store, Blobs, Doc } from 'agentic-identity';
4
4
  export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
5
5
  export { DriveAccount, DriveAccountDeps, driveAccount } from './account.js';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-AB2RTTHJ.js";
7
7
  import {
8
8
  VERSION
9
- } from "./chunk-W27ZQYPT.js";
9
+ } from "./chunk-J2EPNUYS.js";
10
10
  import {
11
11
  DEFAULT_MAX_BYTES,
12
12
  buildExport,
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { D as Drive, C as Ctx } from './drive-CS1CLfqo.js';
2
+ import { D as Drive, C as Ctx } from './drive-9cZ_9xU4.js';
3
3
  import { Store } from 'agentic-identity';
4
4
 
5
5
  declare function createMcpServer(drive: Drive, ctx: Ctx, exportDir: string, store: Store): McpServer;
package/dist/mcp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-W27ZQYPT.js";
3
+ } from "./chunk-J2EPNUYS.js";
4
4
  import {
5
5
  buildExport,
6
6
  upload
package/dist/worker.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Hono } from 'hono';
2
2
  import { D1Like, R2Like } from 'agentic-identity/cloudflare';
3
3
  import { Store, Blobs } from 'agentic-identity';
4
- import { C as Ctx, D as Drive } from './drive-CS1CLfqo.js';
5
- export { E as ERROR_STATUS, i as ErrorCode, N as Node } from './drive-CS1CLfqo.js';
4
+ import { C as Ctx, D as Drive } from './drive-9cZ_9xU4.js';
5
+ export { E as ERROR_STATUS, i as ErrorCode, N as Node } from './drive-9cZ_9xU4.js';
6
6
  export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
7
7
 
8
8
  /**
package/llms.txt CHANGED
@@ -105,8 +105,8 @@ view.unmount();
105
105
  ```
106
106
 
107
107
  `mount` returns a `MountedDrive` handle. `DriveBrowser` is the React component;
108
- `DriveUiOptions` is its configuration. The CSS path supplies its styles. The handle
109
- has `update` and `unmount` methods. The `getToken` callback runs before each request
108
+ `DriveUiOptions` is its configuration. `DriveLocation` is the folder, trash, activity, or file location type. `DriveNavigationOptions` describes host history updates. The CSS path supplies its styles. The handle
109
+ has `update`, `unmount`, `setLocation`, and `getLocation` methods. The `getToken` callback runs before each request
110
110
  and may return undefined. The UI does not change the page address or theme.
111
111
  The optional `onOpenItem` hook receives the full `Item` for a reference link, by pointer or Enter in list and grid views. Return true to skip the download; false or void keeps it. `OnOpenItem` and `Item` are exported types from `agentic-drive/ui`. Both `update` and React props can replace the hook.
112
112
  React 19 and React DOM 19 are optional peer dependencies.
@@ -122,3 +122,23 @@ The CLI uses `AGENTIC_DRIVE_TOKEN`. The MCP command serves tools over stdio;
122
122
  call `drive_ls` with a path. Storage adapters are provided by `agentic-identity`,
123
123
  not by a drive subpath. For example: `import { createFileStore, createFileBlobs } from "agentic-identity/file"`;
124
124
  pass `createFileStore({ path: "./data/db" })` and `createFileBlobs({ dir: "./data/blobs" })` to `createDrive`. The Worker uses D1 and R2 adapters by default.
125
+
126
+
127
+ ### Embedded navigation
128
+
129
+ Set the location option to a folder with kind "folder" and path "/" (root),
130
+ a folder id, or an absolute path. Use kind "trash" for trash. Activity and file
131
+ locations use kind "activity" or "file" with itemId.
132
+
133
+ The mounted handle also has setLocation and getLocation methods. getLocation
134
+ returns a copy of the active location. A folder path can be passed as folder to
135
+ registerDocument. update keeps the location unless its options supply one.
136
+ React hosts can control location and apply onNavigate to their own state.
137
+
138
+ onNavigate receives the next location and history options. Use pushState when
139
+ replace is false, and replaceState when replace is true. File links download in place
140
+ without changing location or notifying the host, including on error. Navigation
141
+ cancels pending downloads. Explicit file locations return to the parent with
142
+ replace true on success; an error leaves that explicit location selected.
143
+ Root returns use "/". A direct location assignment does not notify the host,
144
+ but a completed download does. Apply popstate with setLocation or React props.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-drive",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Store, sort, and share files and folders.",
5
5
  "keywords": [
6
6
  "drive",