agentic-drive 0.8.0 → 0.9.1
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 +28 -0
- package/README.md +20 -4
- package/dist/{chunk-R2DUVOJT.js → chunk-622NAJ6K.js} +123 -11
- package/dist/{chunk-6UQ6CDLJ.js → chunk-D6KW6NVM.js} +1 -1
- package/dist/{chunk-LELTSSZF.js → chunk-SEHT5Q5L.js} +4 -0
- package/dist/cli.js +12 -3
- package/dist/client.d.ts +7 -2
- package/dist/client.js +1 -1
- package/dist/{drive-Dxy0gL0_.d.ts → drive-BRg1Uj_3.d.ts} +25 -1
- package/dist/embed.css +152 -0
- package/dist/embed.js +283 -97
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +3 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +7 -1
- package/package.json +6 -4
- package/ui-dist/assets/index-BiYPMlrf.js +49 -0
- package/ui-dist/assets/index-oybSrFGW.css +1 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-GxwjdcYH.css +0 -1
- package/ui-dist/assets/index-mHs8RJgV.js +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
No change to the SDK, CLI, MCP or HTTP shape. Two breaking changes for hosts that pin `agentic-design` or `agentic-identity`, see Breaking.
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- **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.
|
|
10
|
+
- 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`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `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.
|
|
15
|
+
- `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.
|
|
16
|
+
- The README embed section names `agentic-design@^0.5.0`.
|
|
17
|
+
|
|
18
|
+
## 0.9.0
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **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.
|
|
23
|
+
- `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.
|
|
24
|
+
- 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.
|
|
25
|
+
- `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.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **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.
|
|
30
|
+
|
|
3
31
|
## 0.8.0
|
|
4
32
|
|
|
5
33
|
Read the breaking changes before you upgrade.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
Store, sort, and share files and folders.
|
|
9
9
|
|
|
10
|
-
> Status: 0.
|
|
10
|
+
> Status: 0.9.1, 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.
|
|
@@ -203,7 +203,7 @@ drive.update({ baseUrl, getToken }); // use another server or callback
|
|
|
203
203
|
drive.unmount(); // remove the UI
|
|
204
204
|
```
|
|
205
205
|
|
|
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.
|
|
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.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
207
|
|
|
208
208
|
### Serve the UI from the package
|
|
209
209
|
|
|
@@ -270,6 +270,7 @@ export default { fetch: app.fetch };
|
|
|
270
270
|
- Paths that start with `/`. Anything else is an item id.
|
|
271
271
|
- Move, rename, trash, restore, and purge. Trashing a folder hides its whole tree.
|
|
272
272
|
- Names compare without regard to case or Unicode form. A clash fails, or renames, or replaces.
|
|
273
|
+
- 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
274
|
- Each item records its size, type, owner, and who made and last changed it, with the actor kind.
|
|
274
275
|
- 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
276
|
- 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 +315,21 @@ export default { fetch: app.fetch };
|
|
|
314
315
|
- 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
316
|
- 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
317
|
- A person who is not an owner cannot move an item into their own drive: the move fails with `CROSS_DRIVE`.
|
|
318
|
+
|
|
319
|
+
## Run locally
|
|
320
|
+
|
|
321
|
+
```sh
|
|
322
|
+
bun install # or: npm ci
|
|
323
|
+
bun run dev # or: npm run dev
|
|
324
|
+
curl http://localhost:8787/health
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
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.
|
|
328
|
+
|
|
329
|
+
The scripts call `npm` and `node` (Node 22 or newer), so install both, even if you use bun. They need a POSIX shell.
|
|
330
|
+
|
|
331
|
+
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.
|
|
332
|
+
|
|
317
333
|
## Develop
|
|
318
334
|
|
|
319
335
|
```sh
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/drive.ts
|
|
2
|
-
import { EraseRefusedError, StoreConflictError, MAX_FOLDER_DEPTH, PRINCIPAL_KINDS, ROLES, SUBJECT_KINDS } from "agentic-identity";
|
|
2
|
+
import { EraseRefusedError, isMachineMade, StoreConflictError, MAX_FOLDER_DEPTH, PRINCIPAL_KINDS, ROLES, SUBJECT_KINDS } from "agentic-identity";
|
|
3
3
|
var ROOT_ID = "root";
|
|
4
4
|
var NODES = "drive_nodes";
|
|
5
5
|
var NAMES = "drive_names";
|
|
6
|
+
var ACTIVITY = "drive_activity";
|
|
6
7
|
var ACTOR_KINDS = PRINCIPAL_KINDS;
|
|
7
8
|
var DECIDED = /* @__PURE__ */ Symbol("decided");
|
|
8
9
|
var ERROR_STATUS = Object.freeze({
|
|
@@ -131,6 +132,17 @@ function parseSubject(input) {
|
|
|
131
132
|
return { kind: s.kind, id: s.id };
|
|
132
133
|
}
|
|
133
134
|
var SHARE_ERROR = /^(invalid|give an|expiresAt|expiresInMs|resource exists|the last owner)/;
|
|
135
|
+
var ACTIVITY_ACTIONS = ["create", "rename", "move", "update", "share", "unshare", "delete", "restore"];
|
|
136
|
+
var publicActivity = ({ owner: _owner, actorId: _actorId, subjectId: _subjectId, subjectKind: _subjectKind, ...entry }) => ({
|
|
137
|
+
...entry,
|
|
138
|
+
actor: { id: entry.actor.id, kind: entry.principalKind === "human" ? "human" : "agent" }
|
|
139
|
+
});
|
|
140
|
+
var keepVia = (entry) => {
|
|
141
|
+
const via = entry.detail?.via;
|
|
142
|
+
if (via === void 0) delete entry.detail;
|
|
143
|
+
else entry.detail = { via };
|
|
144
|
+
};
|
|
145
|
+
var shown = (doc) => typeof doc.id === "string" && typeof doc.itemId === "string" && typeof doc.at === "number" && Number.isFinite(doc.at) && ACTIVITY_ACTIONS.includes(doc.action) && typeof doc.actor === "object" && doc.actor !== null && typeof doc.actor.id === "string" && PRINCIPAL_KINDS.includes(doc.principalKind);
|
|
134
146
|
function createDrive(deps) {
|
|
135
147
|
const { store, blobs } = deps;
|
|
136
148
|
let last = 0;
|
|
@@ -183,6 +195,29 @@ function createDrive(deps) {
|
|
|
183
195
|
const who = (ctx) => ({ id: ctx.actor.id, kind: ctx.actor.kind });
|
|
184
196
|
const stamp = (ctx, node) => ({ createdById: node.createdById ?? node.createdBy.id, updatedBy: who(ctx), updatedById: ctx.actor.id });
|
|
185
197
|
const resourceDoc = async (node, parent) => ({ id: node.id, type: "drive", parentId: await resourceOf(parent) });
|
|
198
|
+
const activityDoc = (ctx, node, action, detail) => {
|
|
199
|
+
if (!PRINCIPAL_KINDS.includes(ctx.actor.kind)) throw new DriveError("FORBIDDEN", `an actor needs a kind: ${PRINCIPAL_KINDS.join(", ")}`);
|
|
200
|
+
return {
|
|
201
|
+
id: newId(),
|
|
202
|
+
itemId: node.id,
|
|
203
|
+
owner: node.owner,
|
|
204
|
+
action,
|
|
205
|
+
actor: { id: ctx.actor.id, kind: isMachineMade(ctx.actor.kind) ? "agent" : "human" },
|
|
206
|
+
actorId: ctx.actor.id,
|
|
207
|
+
principalKind: ctx.actor.kind,
|
|
208
|
+
at: now(),
|
|
209
|
+
...detail ? { detail } : {},
|
|
210
|
+
...typeof detail?.subjectId === "string" ? { subjectId: detail.subjectId, subjectKind: detail.subjectKind } : {}
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
const logDoc = (ctx, node, action, detail) => {
|
|
214
|
+
const doc = activityDoc(ctx, node, action, detail);
|
|
215
|
+
return [doc.id, doc];
|
|
216
|
+
};
|
|
217
|
+
const logIn = (w, ctx, node, action, detail) => {
|
|
218
|
+
const doc = activityDoc(ctx, node, action, detail);
|
|
219
|
+
w.put(ACTIVITY, doc.id, doc);
|
|
220
|
+
};
|
|
186
221
|
async function rawNode(ops, id, owner, by, withMarked = false) {
|
|
187
222
|
const node = id === ROOT_ID ? rootNode(owner, by) : await ops.get(NODES, id);
|
|
188
223
|
return node && (withMarked || node.purging !== true) ? node : null;
|
|
@@ -328,6 +363,7 @@ function createDrive(deps) {
|
|
|
328
363
|
if (onConflict === "replace" && slot.node?.kind === "file" && fields.kind === "file") {
|
|
329
364
|
const node2 = { ...slot.node, ...fields, updatedAt: now(), ...stamp(ctx, slot.node) };
|
|
330
365
|
w.put(NODES, node2.id, node2);
|
|
366
|
+
logIn(w, ctx, node2, "update", { size: node2.size, hash: node2.hash });
|
|
331
367
|
return { node: node2, replacedBlob: slot.node.blobKey };
|
|
332
368
|
}
|
|
333
369
|
if (onConflict !== "rename") throw new DriveError("NAME_TAKEN", `"${name}" already exists`);
|
|
@@ -365,6 +401,7 @@ function createDrive(deps) {
|
|
|
365
401
|
...fields
|
|
366
402
|
};
|
|
367
403
|
w.put(NODES, node.id, node);
|
|
404
|
+
logIn(w, ctx, node, "create", { name: node.name, kind: node.kind });
|
|
368
405
|
w.put(NAMES, await claimId(parent.owner, parent.id, key), { nodeId: node.id, owner: parent.owner, parentId: parent.id });
|
|
369
406
|
if (sharing) w.put("resources", node.id, await resourceDoc(node, parent));
|
|
370
407
|
return { node, replacedBlob: null };
|
|
@@ -402,6 +439,8 @@ function createDrive(deps) {
|
|
|
402
439
|
}
|
|
403
440
|
const moved = { ...node, parentId: dest.id, name, nameKey: key, updatedAt: now(), ...stamp(ctx, node) };
|
|
404
441
|
w.put(NODES, moved.id, moved);
|
|
442
|
+
if (moved.name !== node.name) logIn(w, ctx, moved, "rename", { from: node.name, to: moved.name });
|
|
443
|
+
if (moved.parentId !== node.parentId) logIn(w, ctx, moved, "move", { from: node.parentId, to: moved.parentId });
|
|
405
444
|
const [from, to] = [await claimId(node.owner, node.parentId, node.nameKey), await claimId(dest.owner, dest.id, key)];
|
|
406
445
|
if (from !== to) {
|
|
407
446
|
if ((await ops.get(NAMES, from))?.nodeId === node.id) w.remove(NAMES, from);
|
|
@@ -514,6 +553,7 @@ function createDrive(deps) {
|
|
|
514
553
|
const all = await rawOwnerNodes(owner, true);
|
|
515
554
|
if (all.length === 0) {
|
|
516
555
|
await finishResource(principal, await rootResource(owner));
|
|
556
|
+
for (const entry of await allWhere(store, ACTIVITY, { itemId: ROOT_ID, owner })) await store.delete(ACTIVITY, entry.id);
|
|
517
557
|
return removed;
|
|
518
558
|
}
|
|
519
559
|
removed += await removeNodes(all, void 0, principal);
|
|
@@ -592,6 +632,7 @@ function createDrive(deps) {
|
|
|
592
632
|
const chunk = rows.slice(i, i + size);
|
|
593
633
|
for (const node of chunk) if (node.blobKey) await blobs.delete(node.blobKey);
|
|
594
634
|
for (const node of chunk) await finishResource(principal, node.id);
|
|
635
|
+
for (const node of chunk) for (const entry of await allWhere(store, ACTIVITY, { itemId: node.id, owner: node.owner })) await store.delete(ACTIVITY, entry.id);
|
|
595
636
|
await transact(async (tx) => {
|
|
596
637
|
const held = [];
|
|
597
638
|
for (const node of chunk) {
|
|
@@ -619,6 +660,30 @@ function createDrive(deps) {
|
|
|
619
660
|
async stat(ctx, loc) {
|
|
620
661
|
return resolve(store, ctx, loc);
|
|
621
662
|
},
|
|
663
|
+
/**
|
|
664
|
+
* The changes made to one item, newest first: who (a person or an agent), what, and when. Anyone who may read
|
|
665
|
+
* the item may read its log. A trashed item can be named by id, so its log can be read before a restore.
|
|
666
|
+
*/
|
|
667
|
+
async listActivity(ctx, loc, opts = {}) {
|
|
668
|
+
const limit = opts.limit ?? 100;
|
|
669
|
+
if (!Number.isInteger(limit) || limit < 1) throw new RangeError("limit must be an integer of 1 or more");
|
|
670
|
+
const node = loc.includes("/") ? await resolve(store, ctx, loc) : await fetch(store, ctx, loc);
|
|
671
|
+
if (!node) throw new DriveError("NOT_FOUND", `no item ${loc}`);
|
|
672
|
+
const owner = node.owner === ctx.owner;
|
|
673
|
+
const out = [];
|
|
674
|
+
for (let cursor; out.length < limit; ) {
|
|
675
|
+
const page = await store.query(ACTIVITY, { where: { itemId: node.id, owner: node.owner }, orderBy: { field: "at", direction: "desc" }, limit: Math.min(limit - out.length, 100), cursor });
|
|
676
|
+
for (const e of page.items) {
|
|
677
|
+
if (!shown(e.data)) continue;
|
|
678
|
+
const entry = publicActivity(e.data);
|
|
679
|
+
if (!owner && (entry.action === "share" || entry.action === "unshare")) keepVia(entry);
|
|
680
|
+
out.push(entry);
|
|
681
|
+
}
|
|
682
|
+
if (!page.nextCursor) break;
|
|
683
|
+
cursor = page.nextCursor;
|
|
684
|
+
}
|
|
685
|
+
return out;
|
|
686
|
+
},
|
|
622
687
|
/** Live children, sorted by name. */
|
|
623
688
|
async list(ctx, loc = "/") {
|
|
624
689
|
const folder = await resolve(store, ctx, loc);
|
|
@@ -735,6 +800,8 @@ function createDrive(deps) {
|
|
|
735
800
|
const t = now();
|
|
736
801
|
const trashed = { ...node, trashed: true, trashedAt: t, updatedAt: t, ...stamp(ctx2, node) };
|
|
737
802
|
await tx.put(NODES, node.id, trashed);
|
|
803
|
+
const entry = activityDoc(ctx2, node, "delete");
|
|
804
|
+
await tx.put(ACTIVITY, entry.id, entry);
|
|
738
805
|
if (held?.nodeId === node.id) await tx.delete(NAMES, claim);
|
|
739
806
|
return trashed;
|
|
740
807
|
});
|
|
@@ -788,6 +855,8 @@ function createDrive(deps) {
|
|
|
788
855
|
}
|
|
789
856
|
const restored = { ...node, parentId: parent.id, name, nameKey: key, trashed: false, trashedAt: null, updatedAt: now(), ...stamp(ctx2, node) };
|
|
790
857
|
await tx.put(NODES, restored.id, restored);
|
|
858
|
+
const entry = activityDoc(ctx2, restored, "restore");
|
|
859
|
+
await tx.put(ACTIVITY, entry.id, entry);
|
|
791
860
|
await tx.put(NAMES, await claimId(parent.owner, parent.id, key), { nodeId: restored.id, owner: parent.owner, parentId: parent.id });
|
|
792
861
|
if (sharing && parent.id !== node.parentId) await tx.put("resources", restored.id, await resourceDoc(restored, parent));
|
|
793
862
|
return restored;
|
|
@@ -938,6 +1007,7 @@ function createDrive(deps) {
|
|
|
938
1007
|
if (removed === 0 && !gone) console.error(`claimRef could not remove ${node.id} after a failed share. Purge it.`);
|
|
939
1008
|
throw error;
|
|
940
1009
|
}
|
|
1010
|
+
await store.put(ACTIVITY, ...logDoc(ctx, node, "share", { subjectKind: subject.kind, subjectId: subject.id, role: claim.role }));
|
|
941
1011
|
return node;
|
|
942
1012
|
},
|
|
943
1013
|
/** An app deleted a document. Its references in this drive go to the trash. Returns how many. */
|
|
@@ -966,7 +1036,7 @@ function createDrive(deps) {
|
|
|
966
1036
|
async exportPerson(ctx) {
|
|
967
1037
|
await need(ctx, "read", rootNode(ctx.owner, ctx.actor));
|
|
968
1038
|
const subject = subjectOf(ctx);
|
|
969
|
-
if (!isInPerson(ctx)) return { subject, partial: true, authored: [], agents: [], shares: { held: [], invitesCreated: [] }, audit: [] };
|
|
1039
|
+
if (!isInPerson(ctx)) return { subject, partial: true, authored: [], agents: [], shares: { held: [], invitesCreated: [] }, audit: [], activity: [] };
|
|
970
1040
|
const { actors, ownedAgents } = await actorsOf(store, subject.id);
|
|
971
1041
|
const authored = (await madeBy(store, actors)).filter((n) => n.owner !== subject.id && n.purging !== true).map((n) => ({
|
|
972
1042
|
id: n.id,
|
|
@@ -995,7 +1065,18 @@ function createDrive(deps) {
|
|
|
995
1065
|
}
|
|
996
1066
|
}
|
|
997
1067
|
audit.sort((x, y) => x.at - y.at);
|
|
998
|
-
|
|
1068
|
+
const activity = [];
|
|
1069
|
+
for (const actor of actors) {
|
|
1070
|
+
for (const e of await allWhere(store, ACTIVITY, { actorId: actor })) {
|
|
1071
|
+
if (!shown(e.data)) continue;
|
|
1072
|
+
const doc = e.data;
|
|
1073
|
+
const entry = publicActivity(doc);
|
|
1074
|
+
if (doc.owner !== subject.id && !ownedAgents.includes(doc.owner)) keepVia(entry);
|
|
1075
|
+
activity.push(entry);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
activity.sort((x, y) => x.at - y.at || (x.id < y.id ? -1 : 1));
|
|
1079
|
+
return { subject, partial: false, authored, agents, shares: { held, invitesCreated }, audit, activity };
|
|
999
1080
|
},
|
|
1000
1081
|
/**
|
|
1001
1082
|
* Erases a person for good. Only the person in person may. Their drive and its shares go. So do the shares
|
|
@@ -1043,16 +1124,38 @@ function createDrive(deps) {
|
|
|
1043
1124
|
}
|
|
1044
1125
|
});
|
|
1045
1126
|
}
|
|
1127
|
+
for (const actor of actors) {
|
|
1128
|
+
const mine = await allWhere(store, ACTIVITY, { actorId: actor });
|
|
1129
|
+
for (let i = 0; i < mine.length; i += CHUNK) {
|
|
1130
|
+
await transact(async (tx) => {
|
|
1131
|
+
for (const e of mine.slice(i, i + CHUNK)) await tx.put(ACTIVITY, e.id, { ...e.data, actorId: ERASED, actor: { ...e.data.actor, id: ERASED } });
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
for (const [actor, kind] of heldBy(subjectOf(ctx), ownedAgents)) {
|
|
1136
|
+
for (const e of await allWhere(store, ACTIVITY, { subjectId: actor, subjectKind: kind })) {
|
|
1137
|
+
await store.put(ACTIVITY, e.id, { ...e.data, subjectId: ERASED, detail: { ...e.data.detail, subjectId: ERASED } });
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1046
1140
|
return { removed, grants, invites, anonymized: todo.length };
|
|
1047
1141
|
},
|
|
1048
1142
|
/** Gives a person or an agent a role on an item and everything under it. Only an owner may. */
|
|
1049
1143
|
async shareWith(ctx, loc, subject, role) {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1144
|
+
const node = await resolve(store, ctx, loc);
|
|
1145
|
+
const resource = await resourceOf(node);
|
|
1146
|
+
const target = parseSubject(subject);
|
|
1147
|
+
const entry = logDoc(ctx, node, "share", { subjectKind: target.kind, subjectId: target.id, role });
|
|
1148
|
+
await viaSharing(ctx, (s, p) => s.grant(p, resource, target, role));
|
|
1149
|
+
await store.put(ACTIVITY, ...entry);
|
|
1052
1150
|
},
|
|
1053
1151
|
async unshare(ctx, loc, subject) {
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1152
|
+
const node = await resolve(store, ctx, loc);
|
|
1153
|
+
const resource = await resourceOf(node);
|
|
1154
|
+
const target = parseSubject(subject);
|
|
1155
|
+
const entry = logDoc(ctx, node, "unshare", { subjectKind: target.kind, subjectId: target.id });
|
|
1156
|
+
const held = (await allWhere(store, "grants", { resourceId: resource, subjectId: target.id, subjectKind: target.kind })).length > 0;
|
|
1157
|
+
await viaSharing(ctx, (s, p) => s.revokeGrant(p, resource, target));
|
|
1158
|
+
if (held) await store.put(ACTIVITY, ...entry);
|
|
1056
1159
|
},
|
|
1057
1160
|
/** Invite by `email` (a person) or `handle` (an agent). The invite lasts `expiresInMs`, if given. It sends no message. */
|
|
1058
1161
|
async invite(ctx, loc, input) {
|
|
@@ -1061,7 +1164,16 @@ function createDrive(deps) {
|
|
|
1061
1164
|
},
|
|
1062
1165
|
/** `identity` is the verified email of a person, or the handle of an agent. */
|
|
1063
1166
|
async acceptInvite(ctx, inviteId, identity) {
|
|
1167
|
+
const invite = await store.get("invites", inviteId);
|
|
1168
|
+
const node = typeof invite?.resourceId === "string" ? await store.get(NODES, invite.resourceId) : null;
|
|
1169
|
+
const subjectKind = node && (ctx.actor.kind === "human" || ctx.actor.kind === "agent") ? subjectKindOf(ctx.actor.kind) : null;
|
|
1170
|
+
const roleOf = async () => node && subjectKind ? (await allWhere(store, "grants", { resourceId: node.id, subjectId: ctx.actor.id, subjectKind }))[0]?.data.role ?? null : null;
|
|
1171
|
+
const before = await roleOf();
|
|
1064
1172
|
await viaSharing(ctx, (s, p) => s.acceptInvite(p, inviteId, identity));
|
|
1173
|
+
const after = await roleOf();
|
|
1174
|
+
if (node && subjectKind && after !== null && after !== before) {
|
|
1175
|
+
await store.put(ACTIVITY, ...logDoc(ctx, node, "share", { subjectKind, subjectId: ctx.actor.id, role: after, via: "invite" }));
|
|
1176
|
+
}
|
|
1065
1177
|
},
|
|
1066
1178
|
/** The token comes back once. Only its hash is stored. */
|
|
1067
1179
|
async createLink(ctx, loc, input) {
|
|
@@ -1316,7 +1428,7 @@ async function buildExport(drive, ctx, now = Date.now) {
|
|
|
1316
1428
|
};
|
|
1317
1429
|
});
|
|
1318
1430
|
const person = await drive.exportPerson(ctx);
|
|
1319
|
-
const manifest = { format: "agentic-drive-export", version: 2, exportedAt: now(), owner: ctx.owner, subject: person.subject, partial: person.partial, items, authored: person.authored, agents: person.agents, shares: person.shares, audit: person.audit };
|
|
1431
|
+
const manifest = { format: "agentic-drive-export", version: 2, exportedAt: now(), owner: ctx.owner, subject: person.subject, partial: person.partial, items, authored: person.authored, agents: person.agents, shares: person.shares, audit: person.audit, activity: person.activity };
|
|
1320
1432
|
return writeZip([{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest, null, 2)) }, ...entries], now());
|
|
1321
1433
|
}
|
|
1322
1434
|
|
|
@@ -1398,7 +1510,7 @@ function backfillResources(store, options = {}) {
|
|
|
1398
1510
|
import { createHash } from "crypto";
|
|
1399
1511
|
import { existsSync } from "fs";
|
|
1400
1512
|
import { join } from "path";
|
|
1401
|
-
import { createSharing, isMachineMade, isScope, issueCredential, revokeCredential } from "agentic-identity";
|
|
1513
|
+
import { createSharing, isMachineMade as isMachineMade2, isScope, issueCredential, revokeCredential } from "agentic-identity";
|
|
1402
1514
|
import { createFileBlobs, createFileStore } from "agentic-identity/file";
|
|
1403
1515
|
function openLocal(dir, opts = {}) {
|
|
1404
1516
|
if (!opts.beforeImport && existsSync(join(dir, "db", "store.jsonl")) && !existsSync(join(dir, "store.json"))) {
|
|
@@ -1462,8 +1574,8 @@ async function revokeKey(store, token) {
|
|
|
1462
1574
|
}
|
|
1463
1575
|
function publicNode(node, path) {
|
|
1464
1576
|
const { nameKey: _nameKey, blobKey: _blobKey, purging: _purging, ...rest } = node;
|
|
1465
|
-
const
|
|
1466
|
-
return path === void 0 ?
|
|
1577
|
+
const shown2 = { ...rest, machineMade: isMachineMade2(node.createdBy.kind) };
|
|
1578
|
+
return path === void 0 ? shown2 : { path, ...shown2 };
|
|
1467
1579
|
}
|
|
1468
1580
|
|
|
1469
1581
|
export {
|
|
@@ -48,6 +48,8 @@ var ROLES = ["viewer", "commenter", "editor", "owner"];
|
|
|
48
48
|
var oneOf = (list) => (v) => typeof v === "string" && list.includes(v);
|
|
49
49
|
var actor = (v) => record(v) && text(v.id) && oneOf(Object.keys(ACTOR_KINDS))(v.kind);
|
|
50
50
|
var isItem = (v) => record(v) && text(v.id) && text(v.owner) && text(v.parentId) && text(v.name) && (v.kind === "folder" || v.kind === "file" || v.kind === "ref") && num(v.size) && orNull(text)(v.mime) && orNull(text)(v.hash) && orNull(text)(v.ref) && num(v.createdAt) && num(v.updatedAt) && actor(v.createdBy) && actor(v.updatedBy) && text(v.createdById) && text(v.updatedById) && typeof v.trashed === "boolean" && orNull(num)(v.trashedAt) && typeof v.machineMade === "boolean";
|
|
51
|
+
var ACTIVITY_ACTIONS = ["create", "rename", "move", "update", "share", "unshare", "delete", "restore"];
|
|
52
|
+
var isActivity = (v) => record(v) && text(v.id) && text(v.itemId) && oneOf(ACTIVITY_ACTIONS)(v.action) && record(v.actor) && text(v.actor.id) && oneOf(["human", "agent"])(v.actor.kind) && oneOf(Object.keys(ACTOR_KINDS))(v.principalKind) && num(v.at) && (v.detail === void 0 || record(v.detail));
|
|
51
53
|
var isPathItem = (v) => isItem(v) && typeof v.path === "string";
|
|
52
54
|
var listOf = (check) => (v) => Array.isArray(v) && v.every(check);
|
|
53
55
|
var shape = (test) => (v) => record(v) && test(v);
|
|
@@ -186,6 +188,8 @@ function createClient(options) {
|
|
|
186
188
|
return {
|
|
187
189
|
/** The live items of a folder. */
|
|
188
190
|
list: (path = "/", o = {}) => json("GET", `/items${query({ path })}`, o, listOf(isItem)),
|
|
191
|
+
/** The changes made to an item, newest first. `idOrPath` is an id or a path. */
|
|
192
|
+
listActivity: (idOrPath, opts = {}) => json("GET", `/items/${encodeURIComponent(idOrPath)}/activity${query({ limit: opts.limit })}`, opts, listOf(isActivity)),
|
|
189
193
|
stat: (path, o = {}) => json("GET", `/stat${query({ path })}`, o, isItem),
|
|
190
194
|
mkdir: (path, opts = {}, o = {}) => json("POST", "/folders", { json: { path, parents: opts.parents === true }, ...o }, isItem),
|
|
191
195
|
/** Writes a file. A `ReadableStream` body streams in runtimes that allow it. */
|
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-
|
|
7
|
+
} from "./chunk-D6KW6NVM.js";
|
|
8
8
|
import {
|
|
9
9
|
AuthError,
|
|
10
10
|
DriveError,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
revokeKey,
|
|
21
21
|
revokePrincipal,
|
|
22
22
|
upload
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-622NAJ6K.js";
|
|
24
24
|
|
|
25
25
|
// src/cli.ts
|
|
26
26
|
import { createReadStream, realpathSync, writeFileSync } from "fs";
|
|
@@ -36,6 +36,7 @@ Usage: agentic-drive <command> [flags]
|
|
|
36
36
|
Commands:
|
|
37
37
|
ls [path] List a folder.
|
|
38
38
|
stat <path|id> Show one item.
|
|
39
|
+
activity <path|id> [--limit] List the changes to an item: who (person or AI agent), what, when.
|
|
39
40
|
mkdir <path> [-p] Make a folder.
|
|
40
41
|
upload <local> <remote> Upload a file. End remote with "/" to keep the name.
|
|
41
42
|
download <remote> [out] Save a file. Use "-" for stdout.
|
|
@@ -78,7 +79,7 @@ Flags:
|
|
|
78
79
|
|
|
79
80
|
Exit codes: 0 done, 1 error, 2 bad usage, 3 no principal or an invalid key.
|
|
80
81
|
`;
|
|
81
|
-
var COMMANDS = ["ls", "stat", "mkdir", "upload", "download", "mv", "rm", "trash", "restore", "purge", "search", "export", "home", "register", "find-ref", "claim-ref", "forget", "shares", "share", "unshare", "invite", "accept-invite", "link-create", "link-revoke", "delete-all", "mcp", "principal", "key"];
|
|
82
|
+
var COMMANDS = ["ls", "stat", "activity", "mkdir", "upload", "download", "mv", "rm", "trash", "restore", "purge", "search", "export", "home", "register", "find-ref", "claim-ref", "forget", "shares", "share", "unshare", "invite", "accept-invite", "link-create", "link-revoke", "delete-all", "mcp", "principal", "key"];
|
|
82
83
|
var UsageError = class extends Error {
|
|
83
84
|
};
|
|
84
85
|
var options = {
|
|
@@ -166,6 +167,14 @@ async function runCli(argv, io) {
|
|
|
166
167
|
need(1, "stat <path|id>");
|
|
167
168
|
print(publicNode(await drive.stat(ctx, args[0])));
|
|
168
169
|
break;
|
|
170
|
+
case "activity":
|
|
171
|
+
need(1, "activity <path|id> [--limit n]");
|
|
172
|
+
{
|
|
173
|
+
const limit = num(values.limit, "limit");
|
|
174
|
+
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1)) throw new UsageError("--limit needs a whole number of 1 or more");
|
|
175
|
+
print(await drive.listActivity(ctx, args[0], { limit }));
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
169
178
|
case "mkdir":
|
|
170
179
|
need(1, "mkdir <path> [-p]");
|
|
171
180
|
print(publicNode(await drive.mkdir(ctx, args[0], { parents: values.parents })));
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { E as ErrorCode, N as Node, S as SearchQuery } from './drive-
|
|
1
|
+
import { E as ErrorCode, N as Node, A as ActivityEntry, S as SearchQuery } from './drive-BRg1Uj_3.js';
|
|
2
|
+
export { a as ActivityAction } from './drive-BRg1Uj_3.js';
|
|
2
3
|
import 'agentic-identity';
|
|
3
4
|
|
|
4
5
|
type Role = "viewer" | "commenter" | "editor" | "owner";
|
|
@@ -49,6 +50,10 @@ interface Downloaded {
|
|
|
49
50
|
declare function createClient(options: ClientOptions): {
|
|
50
51
|
/** The live items of a folder. */
|
|
51
52
|
list: (path?: string, o?: CallOptions) => Promise<Item[]>;
|
|
53
|
+
/** The changes made to an item, newest first. `idOrPath` is an id or a path. */
|
|
54
|
+
listActivity: (idOrPath: string, opts?: {
|
|
55
|
+
limit?: number;
|
|
56
|
+
} & CallOptions) => Promise<ActivityEntry[]>;
|
|
52
57
|
stat: (path: string, o?: CallOptions) => Promise<Item>;
|
|
53
58
|
mkdir: (path: string, opts?: {
|
|
54
59
|
parents?: boolean;
|
|
@@ -186,4 +191,4 @@ declare function createClient(options: ClientOptions): {
|
|
|
186
191
|
};
|
|
187
192
|
type DriveClient = ReturnType<typeof createClient>;
|
|
188
193
|
|
|
189
|
-
export { type ApiErrorCode, type CallOptions, type ClientErrorCode, type ClientOptions, type Downloaded, DriveApiError, type DriveClient, ErrorCode, type Item, Node, type OnConflict, type PathItem, type Role, type Subject, type SubjectKind, type UploadBody, createClient };
|
|
194
|
+
export { ActivityEntry, type ApiErrorCode, type CallOptions, type ClientErrorCode, type ClientOptions, type Downloaded, DriveApiError, type DriveClient, ErrorCode, type Item, Node, type OnConflict, type PathItem, type Role, type Subject, type SubjectKind, type UploadBody, createClient };
|
package/dist/client.js
CHANGED
|
@@ -8,6 +8,22 @@ type Actor = {
|
|
|
8
8
|
id: string;
|
|
9
9
|
kind: ActorKind;
|
|
10
10
|
};
|
|
11
|
+
type ActivityAction = "create" | "rename" | "move" | "update" | "share" | "unshare" | "delete" | "restore";
|
|
12
|
+
/** One change to an item. `actor.kind` is `agent` for every principal that is not a person, so a reader can label machine-made changes. */
|
|
13
|
+
type ActivityEntry = {
|
|
14
|
+
id: string;
|
|
15
|
+
itemId: string;
|
|
16
|
+
action: ActivityAction;
|
|
17
|
+
actor: {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: "human" | "agent";
|
|
20
|
+
};
|
|
21
|
+
/** The kind of principal behind the change, such as `agent_of_human`. */
|
|
22
|
+
principalKind: ActorKind;
|
|
23
|
+
at: number;
|
|
24
|
+
/** What changed, such as `{ from, to }` for a rename. Never file content. */
|
|
25
|
+
detail?: Record<string, string | number | null>;
|
|
26
|
+
};
|
|
11
27
|
/**
|
|
12
28
|
* `owner` is the drive's owner, and the root of the caller's chain. `actor` is who acts for them.
|
|
13
29
|
* `principal` is the resolved caller. Drive needs it to check roles when `sharing` is set.
|
|
@@ -98,6 +114,13 @@ declare function subjectKindOf(kind: unknown): SubjectKind;
|
|
|
98
114
|
declare function parseSubject(input: unknown): Subject;
|
|
99
115
|
declare function createDrive(deps: DriveDeps): {
|
|
100
116
|
stat(ctx: Ctx, loc: string): Promise<Node>;
|
|
117
|
+
/**
|
|
118
|
+
* The changes made to one item, newest first: who (a person or an agent), what, and when. Anyone who may read
|
|
119
|
+
* the item may read its log. A trashed item can be named by id, so its log can be read before a restore.
|
|
120
|
+
*/
|
|
121
|
+
listActivity(ctx: Ctx, loc: string, opts?: {
|
|
122
|
+
limit?: number;
|
|
123
|
+
}): Promise<ActivityEntry[]>;
|
|
101
124
|
/** Live children, sorted by name. */
|
|
102
125
|
list(ctx: Ctx, loc?: string): Promise<Node[]>;
|
|
103
126
|
mkdir(ctx: Ctx, path: string, opts?: {
|
|
@@ -274,6 +297,7 @@ declare function createDrive(deps: DriveDeps): {
|
|
|
274
297
|
principalKind: unknown;
|
|
275
298
|
machineMade: unknown;
|
|
276
299
|
}[];
|
|
300
|
+
activity: ActivityEntry[];
|
|
277
301
|
}>;
|
|
278
302
|
/**
|
|
279
303
|
* Erases a person for good. Only the person in person may. Their drive and its shares go. So do the shares
|
|
@@ -356,4 +380,4 @@ declare function createDrive(deps: DriveDeps): {
|
|
|
356
380
|
}>;
|
|
357
381
|
};
|
|
358
382
|
|
|
359
|
-
export { type
|
|
383
|
+
export { type ActivityEntry as A, type Ctx as C, type Drive as D, type ErrorCode as E, type Node as N, type OnConflict as O, ROOT_ID as R, type SearchQuery as S, type ActivityAction as a, type Actor as b, ACTOR_KINDS as c, type Action as d, type ActorKind as e, type Can as f, type DriveDeps as g, DriveError as h, ERROR_STATUS as i, createDrive as j, parseSubject as k, parseName as p, subjectKindOf as s };
|