agentic-drive 0.9.0 → 0.10.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,7 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Added
6
+
7
+ - **Account plug-in.** `driveAccount` (from `agentic-drive/account`, also exported from the root) has `exportAccount` and `eraseAccount`, so a host runs a GDPR export and erase without reading the drive storage layout. The export has `items`, `authored`, `agents`, `grants`, `invites`, `audit`, and `activity`. The erase returns counts, and a second erase counts zero.
8
+
9
+ ### Changed
10
+
11
+ - Person exports include held grants and sent invites for drive resources only. They no longer include shares for other apps. This also affects the export manifest in the SDK, CLI, MCP, and HTTP API.
12
+ - Reading or exporting an empty drive no longer creates its root. When no root or items exist, a delegated caller needs the `drive:read` scope.
13
+
14
+ ## 0.9.1
15
+
16
+ No change to the SDK, CLI, MCP or HTTP shape. Two breaking changes for hosts that pin `agentic-design` or `agentic-identity`, see Breaking.
17
+
18
+ ### Breaking
19
+
20
+ - **A host that pins `agentic-design` to `^0.4.0` must move its pin to `^0.5.0`.** The 0.9.0 README told hosts to add `agentic-design@^0.4.0` to their own dependencies. This release depends on `agentic-design` `^0.5.0`, so a host that keeps `^0.4.0` gets two copies. The two copies do not share React context. A host that wraps `DriveBrowser` in its own `ToastProvider` or `TooltipProvider` from `agentic-design/react` on 0.4 gets the error `useToast needs a ToastProvider above it`. `mount()` brings its own providers and is not affected.
21
+ - A host that also pins `agentic-identity` to `^0.6.0` gets two copies of it, and the identity error classes and types from one copy do not match the other. Move the host's pin to `^0.9.0`.
22
+
23
+ ### Changed
24
+
25
+ - `agentic-identity` is `^0.9.0`. It was `^0.6.0`. The lockfile follows. The 0.7.0 to 0.9.0 additions are not used by the drive package.
26
+ - `agentic-design` is `^0.5.0`. It was `^0.4.0`. The design release changes values only: colors, borders, shadows, the large radius, control height, heading sizes, dialog and menu padding, and the disabled and danger button styles. The token names and the `agd-` prefix do not change. The web UI and the embedded UI take the new look. The look of a page that the host renders follows the host's own `agentic-design` pin. The screenshots in `docs/ui` show the 0.4 look until they are refreshed.
27
+ - The README embed section names `agentic-design@^0.5.0`.
28
+
3
29
  ## 0.9.0
4
30
 
31
+ ### Added
32
+
33
+ - **An activity log for each item.** Drive now keeps an append-only entry for each create, rename, move, content update, share, unshare, delete (to the trash), and restore of an item. Each entry has the actor (`{ id, kind }`, where `kind` is `human` or `agent`), the time, and what changed (names and ids, never content). Every principal that is not a person counts as an agent. So a later change by an agent to an item that a person made carries the label. The entry of a create, rename, move, update, delete, or restore is written in the same transaction as the change. A share entry follows the grant, also for `claimRef` and for an accepted invite (but not an invite to a drive root). An unshare entry follows a revoke only when the subject held a role. Only the owner of the drive sees who got a role in a log. A share of the drive root is logged under the id `root`. The log uses the existing `Store` port, in the `drive_activity` collection. Items made before 0.9.0 have no entries until they change.
34
+ - `listActivity(ctx, idOrPath, { limit })` in the SDK, `GET /api/v1/drive/items/:id/activity` in the HTTP API (and `listActivity` in the client), `activity <path|id> [--limit n]` in the CLI, and `drive_activity` in MCP. Anyone who may read an item may read its log. A purge deletes the log of the item.
35
+ - The UI has an Activity page for an item (the Activity entry of the item menu). An entry made by an agent shows an "AI agent" label.
36
+ - `buildExport` adds `activity` to the manifest: the entries made by the person and their agents, in any drive. In a drive that the person does not own, the entries have no names. `erasePerson` changes the actor id of those entries, and the id of a person named in a share entry, to `erased`. It keeps the kind.
37
+
5
38
  ### Changed
6
39
 
7
40
  - **The web UI has a calm list view.** Files show in a table with sortable Name and Modified columns, a search that opens from a button, and a Share button next to the title of a folder. Select rows with the check boxes to see a bar at the bottom of the page that moves them all to the trash. A search with no match has its own empty state. The design classes in the list view are now `drive-*` classes of the app, not `agd-file-*` classes.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Store, sort, and share files and folders.
9
9
 
10
- > Status: 0.9.0, early. The library, CLI, MCP server, and HTTP API work, with sharing in the SDK, the HTTP API, the CLI, and the MCP server.
10
+ > Status: 0.10.0, early. The library, CLI, MCP server, and HTTP API work, with sharing in the SDK, the HTTP API, the CLI, and the MCP server.
11
11
  > A small web UI works. See [UI](#ui).
12
12
 
13
13
  ## Live demo
@@ -92,7 +92,7 @@ curl -H "authorization: Bearer <token>" "$URL/api/v1/drive/items?path=/"
92
92
 
93
93
  Routes: `GET /items`, `GET /stat`, `POST /folders`, `PUT /files`, `GET /files`, `GET /url`,
94
94
  `POST /move`, `POST /trash`, `GET /trash`, `POST /restore`, `POST /purge`, `GET /search`,
95
- `GET /export`, `DELETE /account?confirm=yes`, `GET /home`, `POST /documents`, `DELETE /documents?ref=`, `GET /trash/paths`, `GET /refs?ref=`, `POST /refs`.
95
+ `GET /items/:id/activity`, `GET /export`, `DELETE /account?confirm=yes`, `GET /home`, `POST /documents`, `DELETE /documents?ref=`, `GET /trash/paths`, `GET /refs?ref=`, `POST /refs`.
96
96
  Sign in with a bearer token from agentic-identity. The token is a credential in the `credentials` collection of
97
97
  the same D1 database, and it names a principal in `principals`. The Worker resolves the whole delegation chain on
98
98
  every request. A missing, expired, or revoked credential, or a revoked or expired principal (or one above it), gets 401.
@@ -125,7 +125,7 @@ try {
125
125
  }
126
126
  ```
127
127
 
128
- - One method for each bearer route and each shared route: `list`, `stat`, `mkdir`, `upload`, `download`, `url`, `move`, `trash`, `listTrash`, `restore`, `purge`, `search`, `export`, `deleteAccount`, `home`, `registerDocument`, `forgetDocument`, `findByRef`, `listTrashWithPaths`, `claimRef`, `shares`, `share`, `unshare`, `invite`, `acceptInvite`, `createLink`, `revokeLink`, `openShared`, `listShared`, and `readShared`.
128
+ - One method for each bearer route and each shared route: `list`, `stat`, `mkdir`, `upload`, `download`, `url`, `move`, `trash`, `listTrash`, `listActivity`, `restore`, `purge`, `search`, `export`, `deleteAccount`, `home`, `registerDocument`, `forgetDocument`, `findByRef`, `listTrashWithPaths`, `claimRef`, `shares`, `share`, `unshare`, `invite`, `acceptInvite`, `createLink`, `revokeLink`, `openShared`, `listShared`, and `readShared`.
129
129
  - Each method has typed input and output. The client checks the shape of every answer, and `download` checks the bytes against their hash (it needs WebCrypto). `readShared` does not: the Worker sends no hash there. An answer that fails a check throws `BAD_RESPONSE`. `url` gives a signed link that you fetch yourself. The last argument takes `{ signal }` to stop the call with an `AbortSignal`.
130
130
  - A failed call throws `DriveApiError`. It has the `code` of the API and the HTTP `status`. A call that gets no answer has the code `NETWORK_ERROR` and status 0. An abort stays the abort error of `fetch`. A code that the client does not know gives `BAD_RESPONSE`.
131
131
  - The client never sends a write again. `retries` (default 0) sends a read (GET) again after a network error or a 502, 503, or 504, with a short wait. A redirect is an error (`BAD_RESPONSE`, with the status of the redirect, or 0 in a browser): set `baseUrl` to the final URL.
@@ -163,6 +163,12 @@ The root export, `agentic-drive`, also exports these names. The package typings
163
163
  The local functions trust their caller. Local data is the trust boundary: anyone who can write the folder
164
164
  can act as any principal in it.
165
165
 
166
+ ## Account export and erase
167
+
168
+ A host that runs account export and erase across many packages calls `driveAccount` (from `agentic-drive/account`). It has `exportAccount(subject, { drive, ctx })` and `eraseAccount(subject, { drive, ctx })`, so the host does not read the storage layout. `subject` is an id and a kind (`human`, or `agent` with its own identity), and `ctx` is the drive context of that subject. A context for someone else throws `FORBIDDEN`. A bad subject, or a missing `drive` or `ctx`, throws a `TypeError`.
169
+
170
+ The checks of an export write access-log rows to `audit`, as every read does. That log is the record of access to personal data. The export writes no other data of the person. The export is plain JSON with `items` (your drive, trashed items flagged, file bytes as base64 in `data`), `authored` (items you made in other drives), `agents`, `grants` (shares you hold on drive items), `invites` (invites you sent on drive items), `audit`, and `activity` (the entries of your changes to items, with your kind). Only an agent with its own identity can be a subject: it gets its own `items` and empty collections. An agent that acts for a person is refused with `FORBIDDEN`. The erase removes your drive with its bytes, the shares you hold on drive items, and the invites you sent on drive items. It returns counts of `items`, `grants`, `invites`, and `anonymized`. `invites` counts only the invites sent on other drives: the invites on your own items go with your drive and are not counted. What you made in other drives stays, with your id set to `erased` and the kind kept. Only the person, not an agent, may erase: an agent subject gets `FORBIDDEN`. A second erase counts zero. The shape is provisional until `agentic-identity` adds its `AccountPackage` contract.
171
+
166
172
  ## UI
167
173
 
168
174
  The Worker serves a small web UI. It uses the [agentic-design](https://www.npmjs.com/package/agentic-design) package and the typed client of this package (`agentic-drive/client`). It has a folder tree, breadcrumbs, a file list and grid, upload by button or by drag and drop, new folder, rename, move, trash and restore, and a share dialog with roles and a link. An item that an agent made shows a "Created by agent" label while a person is its last editor. "Edited by" names the last editor, a person or an agent. It works with the keyboard. An automated axe check for WCAG 2.2 AA passes on each view in the light and dark themes.
@@ -203,7 +209,7 @@ drive.update({ baseUrl, getToken }); // use another server or callback
203
209
  drive.unmount(); // remove the UI
204
210
  ```
205
211
 
206
- `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.4.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.
212
+ `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.5.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.
207
213
 
208
214
  ### Serve the UI from the package
209
215
 
@@ -270,6 +276,7 @@ export default { fetch: app.fetch };
270
276
  - Paths that start with `/`. Anything else is an item id.
271
277
  - Move, rename, trash, restore, and purge. Trashing a folder hides its whole tree.
272
278
  - Names compare without regard to case or Unicode form. A clash fails, or renames, or replaces.
279
+ - Each item has an activity log: create, rename, move, content update, share, unshare, delete, and restore, each with the actor (`{ id, kind: "human" | "agent" }`) and the time. Read it with `listActivity`, `GET /items/:id/activity`, `agentic-drive activity <path|id>`, or the MCP tool `drive_activity`. The UI shows it with an "AI agent" label on each change that an agent made. The export holds the activity of the person and their agents. Purge deletes the log of the item. Items from before the log have no entries until they change.
273
280
  - Each item records its size, type, owner, and who made and last changed it, with the actor kind.
274
281
  - Storage sits behind the `Store` and `Blobs` ports of `agentic-identity`. Tests use its memory adapters. The CLI uses a local file adapter that passes the same contract suites. The Worker uses its D1 and R2 adapters.
275
282
  - Roles: viewer, commenter, editor, owner. A role on a folder reaches everything under it, and follows an item that moves. Identity decides every request through `can()`, which also writes the audit entry. A caller who cannot read an item gets `NOT_FOUND`. A caller who can read it but not change it gets `FORBIDDEN`. Editors write and trash. An item takes the roles of its new folder, so moving an item into a folder where the caller is owner needs owner on the item. Only owners share, purge, and read the share list.
@@ -314,6 +321,21 @@ export default { fetch: app.fetch };
314
321
  - Every access check writes one audit row, and a transaction that runs again writes its rows again. The audit holds ids and kinds only: no emails and no content.
315
322
  - Data made before sharing has no `resources` docs, so its owner gets `NOT_FOUND`. Run `backfillResources(store, { maxPages: 1 })` on that store, paged the same way, once the old Worker has stopped writing. See CHANGELOG.md for the order of the upgrade steps.
316
323
  - A person who is not an owner cannot move an item into their own drive: the move fails with `CROSS_DRIVE`.
324
+
325
+ ## Run locally
326
+
327
+ ```sh
328
+ bun install # or: npm ci
329
+ bun run dev # or: npm run dev
330
+ curl http://localhost:8787/health
331
+ ```
332
+
333
+ The script builds the UI, then runs `wrangler dev --local` with `DRIVE_PUBLIC_URL=http://127.0.0.1:8787` and `DRIVE_REGION=unknown`. `/health` needs no token and no secret. Every `/api/v1/drive` call answers 503 until you set a secret: copy `.dev.vars.example` to `.dev.vars` and set `DRIVE_URL_SECRET` to a random value of 32 bytes or more (`openssl rand -hex 32`). `.dev.vars` stays out of git. If you change the port, also pass `--var DRIVE_PUBLIC_URL:http://127.0.0.1:<port>`, or the signed links point at port 8787. A fresh local database has no tokens: see `e2e/serve.mjs` to seed one.
334
+
335
+ The scripts call `npm` and `node` (Node 22 or newer), so install both, even if you use bun. They need a POSIX shell.
336
+
337
+ Run `npm run dev:smoke` to check this path. It starts the dev server on port 8799, waits for `GET /health` to answer 200, and stops the server. CI runs it as the "dev smoke" step. Set `DEV_SMOKE_PORT` to use another port.
338
+
317
339
  ## Develop
318
340
 
319
341
  ```sh
@@ -0,0 +1,28 @@
1
+ import { b as Actor, D as Drive, C as Ctx } from './drive-BhMTeQ-s.js';
2
+ import 'agentic-identity';
3
+
4
+ interface DriveAccountDeps {
5
+ drive: Drive;
6
+ /** The subject's own context. The package checks that it names the subject, in person. */
7
+ ctx: Ctx;
8
+ }
9
+ type JsonRecord = Record<string, unknown>;
10
+ interface DriveAccount {
11
+ id: "drive";
12
+ /**
13
+ * Collections: `items` (the subject's own drive, trashed ones flagged, a file with its bytes as base64 in `data`),
14
+ * `authored` (ids of items the subject made or changed in other drives), `agents` (the drives of agents
15
+ * that belong to the subject, with no bytes), `grants` (shares the subject holds on drive items), `invites` (invites the subject sent on drive items),
16
+ * `audit` (what the subject did), and `activity` (the entries of the subject's changes to items). Only an agent with its own identity can be a subject: it gets its own `items` and empty collections, and the export does not say that these are partial. An agent that acts for a person has a context for that person, and is refused.
17
+ */
18
+ exportAccount(subject: Actor, deps: DriveAccountDeps): Promise<Record<"items" | "authored" | "agents" | "grants" | "invites" | "audit" | "activity", JsonRecord[]>>;
19
+ /**
20
+ * Erases the subject's drive with all its bytes, the shares they hold, and the invites they sent, on drive items only.
21
+ * What the subject made in other drives stays, with the id set to "erased" and the kind kept. Counts by `items`, `grants`,
22
+ * `invites`, and `anonymized`. `invites` counts only the invites on items of other drives: the ones on the subject's own items go with the drive, and are not counted. A repeat counts what is left, so it is zero after a full run. Only the person, not an agent, may erase: an agent subject is refused.
23
+ */
24
+ eraseAccount(subject: Actor, deps: DriveAccountDeps): Promise<Record<"items" | "grants" | "invites" | "anonymized", number>>;
25
+ }
26
+ declare const driveAccount: DriveAccount;
27
+
28
+ export { type DriveAccount, type DriveAccountDeps, driveAccount };
@@ -0,0 +1,7 @@
1
+ import {
2
+ driveAccount
3
+ } from "./chunk-ECVUDAHL.js";
4
+ import "./chunk-DB4F5EWD.js";
5
+ export {
6
+ driveAccount
7
+ };