agentic-comments 0.7.0 → 0.8.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/README.md CHANGED
@@ -86,6 +86,36 @@ erased message or a thread that holds one.
86
86
  Replicas that edit one message at once leave a copy each. When the copies agree on the author kind and time,
87
87
  the read keeps the erased copy, or else the latest edit. An edit and a delete made at once keep the edit.
88
88
 
89
+ ## Account export and erase
90
+
91
+ `import { commentsAccount } from "agentic-comments/account"` lets a host export and erase one person's data
92
+ without reading the storage layout. Pass the subject (`{ kind, name }`) and `{ documents, actor }`. Each
93
+ document is `{ id, comments, suggestions }`. The actor is made with `stamp()` and must name the subject.
94
+ `exportAccount` returns JSON records in `messages`, `statusChanges`, `edits`, `suggestions`, `decisions`,
95
+ `documents`, and `unread`. `eraseAccount` tombstones the same data as `erasePerson` and returns counts. A second
96
+ erase counts 0, except `unread`: see below.
97
+
98
+ The package keeps no grants or audit rows. A host that runs the HTTP worker passes `worker: docs` (the
99
+ `CommentsDocs` of the worker). Then the run also covers every stored document: the subject's messages and
100
+ suggestions in it, and the creator that the worker keeps on each row. The creators are listed under `documents`
101
+ and get the tombstone name on erase. Without `worker`, that data stays. A stored document is erased in one
102
+ transaction. A stored document that `documents` also lists is refused, before any write. The check compares ids
103
+ only. A row whose creator cannot be read shows in `unread` of the export, and in the `unread` count of the erase,
104
+ which leaves the row in place. `unread` counts every row it cannot read, whoever made it, so it can stay above 0 on a repeat. A store that loses a race gives `CONFLICT`: run the erase again.
105
+
106
+ A run covers only the subject's own kind: `human:ada` does not reach `agent_of_human:ada`. Names and mentions of
107
+ the subject inside other people's messages are neither exported nor erased. Records can name other people, such
108
+ as the moderator in `editedBy` or the person in `decidedBy`. A record in `unread` means the export is not
109
+ complete: the store holds entries of the subject that a read cannot show. A missing `documents` list is an
110
+ error, not an empty run. Erase goes document by document. If one fails, the error code tells why, the message names the document when the store allows, and the earlier
111
+ documents stay erased. A failed document in `documents` may be partly erased (its threads and its suggestions
112
+ are two steps). A repeat counts only what is left. The counts of a failed run are lost.
113
+
114
+ The ids in `documents` and the ids of the worker share one namespace. Erase can make a state a little larger, so a stored
115
+ document near the size limit may stop the run with `STORE_FULL`: the host must shrink or delete that document, and
116
+ run the erase again. The run reads the list of stored documents once. Block the subject before the erase, so no
117
+ document is made during it. A repeat erase finishes what a race left.
118
+
89
119
  ## Anchors for plain text
90
120
 
91
121
  `agentic-comments/anchor` pins a range of a plain string. It needs no Yjs and no framework.
@@ -0,0 +1,62 @@
1
+ import { C as Comments, c as Suggestions, b as Author, A as Actor } from './suggestions-G1seGf5d.js';
2
+ import 'yjs';
3
+ import 'agentic-identity';
4
+
5
+ /** One document the host holds comments on. The host loads it; the package never lists documents. */
6
+ interface CommentsAccountDocument {
7
+ /** A stable id for the document. It goes in each exported record. */
8
+ id: string;
9
+ comments: Comments;
10
+ suggestions: Suggestions;
11
+ }
12
+ /** What `CommentsDocs` offers the plug-in. */
13
+ interface AccountWorker {
14
+ /** The id of each stored document. */
15
+ accountDocumentIds(): Promise<string[]>;
16
+ /** Runs `fn` on each stored document. With `save`, the state is written back in the same transaction. A document deleted meanwhile is skipped. `committed` is called with the id after its transaction commits. */
17
+ eachAccountDocument(fn: (doc: CommentsAccountDocument) => void, save: boolean, committed?: (id: string) => void): Promise<void>;
18
+ /** The documents the subject created. `unread` names the rows whose creator cannot be read, so the export is not complete for them. */
19
+ exportCreated(author: Author): Promise<{
20
+ documents: {
21
+ documentId: string;
22
+ createdAt: unknown;
23
+ }[];
24
+ unread: string[];
25
+ }>;
26
+ /** `count` is the creators it tombstoned. `unread` names the rows whose creator cannot be read, which it leaves in place. */
27
+ eraseCreated(author: Author): Promise<{
28
+ count: number;
29
+ unread: string[];
30
+ }>;
31
+ }
32
+ interface CommentsAccountDeps {
33
+ /** Every document that may hold data of the subject. */
34
+ documents: Iterable<CommentsAccountDocument>;
35
+ /** Made with `stamp()`. It must name the subject: the same kind and name. A moderate scope does not widen the run. */
36
+ actor: Actor;
37
+ /**
38
+ * The document store of the HTTP worker (`CommentsDocs`), when the host runs it. Its rows hold the threads of each
39
+ * document and the creator of each document, outside what `documents` can reach. Pass it, and the run covers every
40
+ * stored document. Without it, a worker host leaves that data in place.
41
+ */
42
+ worker?: AccountWorker;
43
+ }
44
+ type JsonRecord = Record<string, unknown>;
45
+ interface CommentsAccount {
46
+ id: "comments";
47
+ /**
48
+ * Collections, each record tagged with `documentId`: `messages` (with the thread anchor), `statusChanges`,
49
+ * `edits` (the subject's edits of other people's messages), `suggestions` (the ones the subject wrote),
50
+ * `decisions` (accepts and rejects by the subject), `documents` (the worker documents the subject created, only
51
+ * when `deps.worker` is set), and `unread` (stored entries of the subject that a read
52
+ * cannot show; the export is not complete when this has a record). The package keeps no grants or audit rows. Its
53
+ * one owner-like record is the creator of a worker document, which `deps.worker` covers. A host that runs the
54
+ * worker and omits `worker` leaves that creator in place.
55
+ */
56
+ exportAccount(subject: Author, deps: CommentsAccountDeps): Promise<Record<"messages" | "statusChanges" | "edits" | "suggestions" | "decisions" | "documents" | "unread", JsonRecord[]>>;
57
+ /** Replaces what the subject wrote with a tombstone (see `Comments.erasePerson`). Counts by kind, summed over documents. The thread shape stays. A repeat finds nothing and counts 0. A decision that the subject made on their own suggestion is deleted and not counted, while `unread` in the export counts it. */
58
+ eraseAccount(subject: Author, deps: CommentsAccountDeps): Promise<Record<"messages" | "statuses" | "edits" | "suggestions" | "decisions" | "documents" | "unread", number>>;
59
+ }
60
+ declare const commentsAccount: CommentsAccount;
61
+
62
+ export { type AccountWorker, type CommentsAccount, type CommentsAccountDeps, type CommentsAccountDocument, commentsAccount };
@@ -0,0 +1,114 @@
1
+ import {
2
+ Actor,
3
+ Comments,
4
+ CommentsError,
5
+ Suggestions,
6
+ assertActor,
7
+ readAuthor,
8
+ shown
9
+ } from "./chunk-4G5BDTBA.js";
10
+
11
+ // src/account.ts
12
+ import { StoreConflictError } from "agentic-identity";
13
+ var plain = (value) => JSON.parse(JSON.stringify(value));
14
+ function check(subject, deps) {
15
+ const who = readAuthor(subject);
16
+ if (!who) throw new CommentsError("INVALID_INPUT", "subject needs a known kind and a name");
17
+ const actor = assertActor(deps?.actor);
18
+ if (actor.kind !== who.kind || actor.name !== who.name) throw new CommentsError("FORBIDDEN", "actor does not match the subject");
19
+ if (typeof deps?.documents?.[Symbol.iterator] !== "function") throw new CommentsError("INVALID_INPUT", "deps.documents must be a list of documents");
20
+ const documents = [...deps.documents];
21
+ const ids = /* @__PURE__ */ new Set();
22
+ const seen = /* @__PURE__ */ new Set();
23
+ const bad = (why) => {
24
+ throw new CommentsError("INVALID_INPUT", `deps: ${why}`);
25
+ };
26
+ if (deps.worker !== void 0 && ["accountDocumentIds", "eachAccountDocument", "exportCreated", "eraseCreated"].some((m) => typeof deps.worker?.[m] !== "function")) bad("worker needs accountDocumentIds, eachAccountDocument, exportCreated, and eraseCreated");
27
+ for (const d of documents) {
28
+ if (typeof d?.id !== "string" || d.id === "" || ids.has(d.id)) bad("each document needs a unique id");
29
+ if (!(d.comments instanceof Comments) || !(d.suggestions instanceof Suggestions)) bad("each document needs Comments and Suggestions made by this package");
30
+ if (d.suggestions.comments.ydoc !== d.comments.ydoc) bad("suggestions must be made on the same comments");
31
+ if (seen.has(d.comments.ydoc)) bad("two documents share one Yjs document");
32
+ ids.add(d.id);
33
+ seen.add(d.comments.ydoc);
34
+ }
35
+ return { documents, own: Actor.of(who) };
36
+ }
37
+ function collect({ id: documentId, comments, suggestions }, own, out) {
38
+ const c = comments.exportPerson(own.name, own);
39
+ const s = suggestions.exportPerson(own.name, own);
40
+ for (const t of c.threads) {
41
+ for (const message of t.messages) out.messages.push({ ...message, documentId, threadId: t.threadId, anchor: t.anchor });
42
+ if (t.statusChange) out.statusChanges.push({ ...t.statusChange, documentId, threadId: t.threadId });
43
+ }
44
+ for (const e of c.edits) out.edits.push({ ...e, documentId });
45
+ for (const x of s.authored) out.suggestions.push({ ...x, documentId });
46
+ for (const d of s.decided) out.decisions.push({ documentId, suggestionId: d.id, status: d.status, at: d.at });
47
+ const unread = c.unread + s.unread;
48
+ if (unread > 0) out.unread.push({ documentId, count: unread });
49
+ }
50
+ async function stopAt(where, run) {
51
+ try {
52
+ return await run();
53
+ } catch (cause) {
54
+ const known = cause instanceof CommentsError;
55
+ const reason = cause instanceof Error ? cause.message : "unknown error";
56
+ if (!known && !(cause instanceof StoreConflictError)) throw cause;
57
+ throw new CommentsError(known ? cause.code : "CONFLICT", `erase stopped at ${where}: ${reason}`, { cause });
58
+ }
59
+ }
60
+ async function refuseOverlap(documents, worker) {
61
+ const listed = new Set(documents.map((d) => d.id));
62
+ if ((await worker.accountDocumentIds()).some((id) => listed.has(id))) throw new CommentsError("INVALID_INPUT", "deps.worker holds a document that deps.documents also lists");
63
+ }
64
+ function wipe({ comments, suggestions }, own, counts) {
65
+ const c = comments.erasePerson(own.name, own);
66
+ const s = suggestions.erasePerson(own.name, own);
67
+ counts.messages += c.messages;
68
+ counts.statuses += c.statuses;
69
+ counts.edits += c.edits;
70
+ counts.suggestions += s.suggestions;
71
+ counts.decisions += s.decisions;
72
+ }
73
+ var commentsAccount = {
74
+ id: "comments",
75
+ async exportAccount(subject, deps) {
76
+ const { documents, own } = check(subject, deps);
77
+ const out = { messages: [], statusChanges: [], edits: [], suggestions: [], decisions: [], documents: [], unread: [] };
78
+ if (deps.worker) await refuseOverlap(documents, deps.worker);
79
+ for (const doc of documents) collect(doc, own, out);
80
+ if (deps.worker) {
81
+ await deps.worker.eachAccountDocument((doc) => collect(doc, own, out), false);
82
+ const created = await deps.worker.exportCreated(own);
83
+ out.documents.push(...created.documents);
84
+ for (const documentId of created.unread) out.unread.push({ documentId, count: 1 });
85
+ }
86
+ return plain(out);
87
+ },
88
+ async eraseAccount(subject, deps) {
89
+ const { documents, own } = check(subject, deps);
90
+ if (deps.worker) await refuseOverlap(documents, deps.worker);
91
+ const counts = { messages: 0, statuses: 0, edits: 0, suggestions: 0, decisions: 0, documents: 0, unread: 0 };
92
+ for (const doc of documents) await stopAt(`document ${shown(doc.id)}`, () => wipe(doc, own, counts));
93
+ if (deps.worker) {
94
+ const each = /* @__PURE__ */ new Map();
95
+ const done = /* @__PURE__ */ new Set();
96
+ await stopAt(
97
+ "a worker document",
98
+ () => deps.worker.eachAccountDocument((doc) => {
99
+ const one = { messages: 0, statuses: 0, edits: 0, suggestions: 0, decisions: 0, documents: 0, unread: 0 };
100
+ wipe(doc, own, one);
101
+ each.set(doc.id, one);
102
+ }, true, (id) => void done.add(id))
103
+ );
104
+ for (const [id, c] of each) if (done.has(id)) for (const k of Object.keys(counts)) counts[k] += c[k];
105
+ const creators = await stopAt("the worker creators", () => deps.worker.eraseCreated(own));
106
+ counts.documents = creators.count;
107
+ counts.unread = creators.unread.length;
108
+ }
109
+ return counts;
110
+ }
111
+ };
112
+ export {
113
+ commentsAccount
114
+ };
@@ -1,16 +1,13 @@
1
1
  // src/errors.ts
2
2
  var CommentsError = class extends Error {
3
- constructor(code, message) {
4
- super(message);
3
+ constructor(code, message, options) {
4
+ super(message, options);
5
5
  this.code = code;
6
6
  this.name = "CommentsError";
7
7
  }
8
8
  code;
9
9
  };
10
10
 
11
- // src/anchor.ts
12
- import * as Y from "yjs";
13
-
14
11
  // src/text.ts
15
12
  var HIDDEN = /[\p{Cc}\p{Cf}]/u;
16
13
  var ID = /^[\w-]{1,64}$/;
@@ -21,6 +18,7 @@ var shown = (value) => JSON.stringify(safeId(value));
21
18
  var safeText = (value) => String(value).replace(/[^\p{L}\p{N}\p{P}\p{S} ]/gu, "?").slice(0, 200);
22
19
 
23
20
  // src/anchor.ts
21
+ import * as Y from "yjs";
24
22
  var MAX_QUOTE = 2e3;
25
23
  var MAX_ANCHOR = 4096;
26
24
  var MAX_KEY = 256;
@@ -1020,6 +1018,7 @@ var Suggestions = class {
1020
1018
 
1021
1019
  export {
1022
1020
  CommentsError,
1021
+ shown,
1023
1022
  textOf,
1024
1023
  textAnchor,
1025
1024
  textRange,
@@ -1027,6 +1026,7 @@ export {
1027
1026
  ERASED_NAME,
1028
1027
  Actor,
1029
1028
  stamp,
1029
+ assertActor,
1030
1030
  readAuthor,
1031
1031
  authorLabel,
1032
1032
  invalid,
@@ -1,6 +1,6 @@
1
1
  // src/version.ts
2
2
  import { createRequire } from "module";
3
- var VERSION = true ? "0.7.0" : readPackageVersion();
3
+ var VERSION = true ? "0.8.0" : readPackageVersion();
4
4
 
5
5
  export {
6
6
  VERSION
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-EFO5PSJL.js";
3
+ } from "./chunk-6UQ6CDLJ.js";
4
4
  import {
5
5
  CommentsService
6
- } from "./chunk-B7LTOLUO.js";
6
+ } from "./chunk-TTNT27PG.js";
7
7
  import {
8
8
  CommentsError
9
- } from "./chunk-6MS4LASK.js";
9
+ } from "./chunk-4G5BDTBA.js";
10
10
 
11
11
  // src/mcp.ts
12
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -2,7 +2,7 @@ import {
2
2
  CommentsError,
3
3
  PRINCIPAL_KINDS,
4
4
  stamp
5
- } from "./chunk-6MS4LASK.js";
5
+ } from "./chunk-4G5BDTBA.js";
6
6
 
7
7
  // src/file-store.ts
8
8
  import { createHash } from "crypto";
@@ -7,7 +7,7 @@ import {
7
7
  textAnchor,
8
8
  textOf,
9
9
  textRange
10
- } from "./chunk-6MS4LASK.js";
10
+ } from "./chunk-4G5BDTBA.js";
11
11
 
12
12
  // src/local-host.ts
13
13
  import * as Y from "yjs";
package/dist/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  VERSION
4
- } from "./chunk-EFO5PSJL.js";
4
+ } from "./chunk-6UQ6CDLJ.js";
5
5
  import {
6
6
  FileStore,
7
7
  lockTimeoutFrom,
8
8
  operatorActor
9
- } from "./chunk-M77Q5CWI.js";
9
+ } from "./chunk-LFYTCHXS.js";
10
10
  import {
11
11
  CommentsService,
12
12
  TEXT_ROOT
13
- } from "./chunk-B7LTOLUO.js";
13
+ } from "./chunk-TTNT27PG.js";
14
14
  import {
15
15
  CommentsError
16
- } from "./chunk-6MS4LASK.js";
16
+ } from "./chunk-4G5BDTBA.js";
17
17
 
18
18
  // src/cli.ts
19
19
  import { readFileSync, realpathSync } from "fs";
@@ -178,7 +178,7 @@ async function runCli(argv, io) {
178
178
  return 0;
179
179
  }
180
180
  if (p.command === "mcp") {
181
- const { startMcp } = await import("./mcp-LP3X55C2.js");
181
+ const { startMcp } = await import("./mcp-IFQQHOA2.js");
182
182
  await startMcp(store, operatorActor(p.flags["--as"] ?? io.env.AGENTIC_COMMENTS_AS, io.env.AGENTIC_COMMENTS_SCOPES));
183
183
  return 0;
184
184
  }
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { L as ListFilter, A as AnchorSpec } from './service-BvIE6t5n.js';
2
- import { C as CommentsErrorCode } from './errors-C-xvp24-.js';
1
+ import { L as ListFilter, A as AnchorSpec } from './service-ZdZPWrwI.js';
2
+ import { C as CommentsErrorCode } from './errors-Cin-719h.js';
3
3
  import { PrincipalKind } from 'agentic-identity';
4
4
  export { PrincipalKind } from 'agentic-identity';
5
- import { T as Thread, N as NewReply, M as Message, P as PersonExport, a as PersonSuggestions, S as Suggestion } from './suggestions-CnkEcTuY.js';
5
+ import { T as Thread, N as NewReply, M as Message, P as PersonExport, a as PersonSuggestions, S as Suggestion } from './suggestions-G1seGf5d.js';
6
6
  import 'yjs';
7
7
 
8
8
  /** Codes the comment rules return, plus the ones the Worker and its auth return. */
@@ -1,7 +1,9 @@
1
1
  type CommentsErrorCode = "INVALID_AUTHOR" | "INVALID_INPUT" | "INVALID_ANCHOR" | "NO_THREAD" | "NO_MESSAGE" | "THREAD_RESOLVED" | "CORRUPT" | "NO_SUGGESTION" | "FORBIDDEN" | "ALREADY_DECIDED" | "APPLY_FAILED" | "RELOCATE_FAILED" | "STORE_MISSING" | "STORE_EXISTS" | "STORE_CORRUPT" | "STORE_LOCKED" | "STORE_FULL" | "NO_DOCUMENT" | "DOCUMENT_EXISTS" | "CONFLICT" | "MISCONFIGURED" | "USAGE";
2
2
  declare class CommentsError extends Error {
3
3
  readonly code: CommentsErrorCode;
4
- constructor(code: CommentsErrorCode, message: string);
4
+ constructor(code: CommentsErrorCode, message: string, options?: {
5
+ cause?: unknown;
6
+ });
5
7
  }
6
8
 
7
9
  export { type CommentsErrorCode as C, CommentsError as a };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { b as Author, C as Comments, c as Suggestions } from './suggestions-CnkEcTuY.js';
2
- export { d as ACCEPT_SCOPE, A as Actor, e as Anchor, f as AnchorKind, g as AnchorState, h as ApplyContext, i as CanvasAnchor, j as CellAnchor, k as CommentsOptions, E as ERASED_BODY, l as ERASED_NAME, m as EditMessage, n as MODERATE_SCOPE, M as Message, N as NewReply, o as NewSuggestion, p as NewThread, P as PersonExport, R as RelativePositionJson, q as RevertContext, r as SlideAnchor, s as StampInput, S as Suggestion, t as SuggestionFilter, u as SuggestionHost, v as TextAnchor, T as Thread, w as ThreadFilter, x as authorLabel, y as stamp, z as textAnchor, B as textOf } from './suggestions-CnkEcTuY.js';
3
- export { a as CommentsError, C as CommentsErrorCode } from './errors-C-xvp24-.js';
1
+ import { b as Author, C as Comments, c as Suggestions } from './suggestions-G1seGf5d.js';
2
+ export { d as ACCEPT_SCOPE, A as Actor, e as Anchor, f as AnchorKind, g as AnchorState, h as ApplyContext, i as CanvasAnchor, j as CellAnchor, k as CommentsOptions, E as ERASED_BODY, l as ERASED_NAME, m as EditMessage, n as MODERATE_SCOPE, M as Message, N as NewReply, o as NewSuggestion, p as NewThread, P as PersonExport, R as RelativePositionJson, q as RevertContext, r as SlideAnchor, s as StampInput, S as Suggestion, t as SuggestionFilter, u as SuggestionHost, v as TextAnchor, T as Thread, w as ThreadFilter, x as authorLabel, y as stamp, z as textAnchor, B as textOf } from './suggestions-G1seGf5d.js';
3
+ export { a as CommentsError, C as CommentsErrorCode } from './errors-Cin-719h.js';
4
4
  export { PRINCIPAL_KINDS, PrincipalKind } from 'agentic-identity';
5
5
  import 'yjs';
6
6
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-EFO5PSJL.js";
3
+ } from "./chunk-6UQ6CDLJ.js";
4
4
  import {
5
5
  ACCEPT_SCOPE,
6
6
  Actor,
@@ -17,7 +17,7 @@ import {
17
17
  textAnchor,
18
18
  textOf,
19
19
  validDate
20
- } from "./chunk-6MS4LASK.js";
20
+ } from "./chunk-4G5BDTBA.js";
21
21
 
22
22
  // src/notifications.ts
23
23
  import * as Y from "yjs";
package/dist/local.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as Y from 'yjs';
2
- export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-BvIE6t5n.js';
3
- import { u as SuggestionHost, A as Actor } from './suggestions-CnkEcTuY.js';
2
+ export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-ZdZPWrwI.js';
3
+ import { u as SuggestionHost, A as Actor } from './suggestions-G1seGf5d.js';
4
4
  import 'agentic-identity';
5
5
 
6
6
  /** The most bytes a saved document holds. A save over this is refused, so a file never grows past what a load accepts. */
package/dist/local.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  MAX_FILE,
4
4
  lockTimeoutFrom,
5
5
  operatorActor
6
- } from "./chunk-M77Q5CWI.js";
6
+ } from "./chunk-LFYTCHXS.js";
7
7
  import {
8
8
  CommentsService,
9
9
  TEXT_ROOT,
10
10
  VALUES_ROOT,
11
11
  localHost
12
- } from "./chunk-B7LTOLUO.js";
13
- import "./chunk-6MS4LASK.js";
12
+ } from "./chunk-TTNT27PG.js";
13
+ import "./chunk-4G5BDTBA.js";
14
14
  export {
15
15
  CommentsService,
16
16
  FileStore,
@@ -0,0 +1,11 @@
1
+ import {
2
+ createMcpServer,
3
+ startMcp
4
+ } from "./chunk-J6JGWDUP.js";
5
+ import "./chunk-6UQ6CDLJ.js";
6
+ import "./chunk-TTNT27PG.js";
7
+ import "./chunk-4G5BDTBA.js";
8
+ export {
9
+ createMcpServer,
10
+ startMcp
11
+ };
package/dist/mcp-bin.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcp
4
- } from "./chunk-OV2GGL3T.js";
4
+ } from "./chunk-J6JGWDUP.js";
5
5
  import {
6
6
  VERSION
7
- } from "./chunk-EFO5PSJL.js";
7
+ } from "./chunk-6UQ6CDLJ.js";
8
8
  import {
9
9
  FileStore,
10
10
  lockTimeoutFrom,
11
11
  operatorActor
12
- } from "./chunk-M77Q5CWI.js";
13
- import "./chunk-B7LTOLUO.js";
14
- import "./chunk-6MS4LASK.js";
12
+ } from "./chunk-LFYTCHXS.js";
13
+ import "./chunk-TTNT27PG.js";
14
+ import "./chunk-4G5BDTBA.js";
15
15
 
16
16
  // src/mcp-bin.ts
17
17
  var MCP_HELP = `agentic-comments-mcp ${VERSION}
@@ -1,5 +1,5 @@
1
1
  import * as Y from 'yjs';
2
- import { A as Actor, T as Thread, M as Message, P as PersonExport, a as PersonSuggestions, S as Suggestion } from './suggestions-CnkEcTuY.js';
2
+ import { A as Actor, T as Thread, M as Message, P as PersonExport, a as PersonSuggestions, S as Suggestion } from './suggestions-G1seGf5d.js';
3
3
 
4
4
  /** Where a comment or suggestion points, in terms a person or agent can write down. */
5
5
  type AnchorSpec = {
@@ -383,11 +383,14 @@ interface SuggestionFilter {
383
383
  * with the accept scope that did not write it, accepts. Accept and reject are safe across replicas.
384
384
  */
385
385
  declare class Suggestions {
386
- private readonly comments;
386
+ /** The threads these suggestions anchor to. */
387
+ readonly comments: Comments;
387
388
  private readonly host;
388
389
  private readonly onTransaction;
389
390
  private failures;
390
- constructor(comments: Comments, host: SuggestionHost);
391
+ constructor(
392
+ /** The threads these suggestions anchor to. */
393
+ comments: Comments, host: SuggestionHost);
391
394
  /** Stop listening to the document. */
392
395
  close(): void;
393
396
  private get ydoc();
package/dist/ui.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { CommentsClient, Me } from './client.js';
3
3
  export { createClient } from './client.js';
4
- import { b as Author, S as Suggestion } from './suggestions-CnkEcTuY.js';
5
- import './service-BvIE6t5n.js';
4
+ import { b as Author, S as Suggestion } from './suggestions-G1seGf5d.js';
5
+ import './service-ZdZPWrwI.js';
6
6
  import 'yjs';
7
- import './errors-C-xvp24-.js';
7
+ import './errors-Cin-719h.js';
8
8
  import 'agentic-identity';
9
9
 
10
10
  /** The calls the panel makes. A `CommentsClient` fits, and so does any object with these methods. */
package/dist/worker.d.ts CHANGED
@@ -2,8 +2,9 @@ import { Hono } from 'hono';
2
2
  import { Auth, Store, PrincipalContext, Region } from 'agentic-identity';
3
3
  import { RequirePrincipalOptions } from 'agentic-identity/http';
4
4
  import { D1Like } from 'agentic-identity/cloudflare';
5
- import { A as Actor } from './suggestions-CnkEcTuY.js';
6
- import { C as CommentsService } from './service-BvIE6t5n.js';
5
+ import { CommentsAccountDocument } from './account.js';
6
+ import { A as Actor, b as Author } from './suggestions-G1seGf5d.js';
7
+ import { C as CommentsService } from './service-ZdZPWrwI.js';
7
8
  import 'yjs';
8
9
 
9
10
  interface WorkerEnv {
@@ -36,6 +37,29 @@ declare class CommentsDocs {
36
37
  create(id: string, text: string, actor: Actor): Promise<void>;
37
38
  read<T>(id: string, actor: Actor, run: (service: CommentsService) => T): Promise<T>;
38
39
  write<T>(id: string, actor: Actor, run: (service: CommentsService) => T): Promise<T>;
40
+ /** For the account plug-in: the id of each stored document. */
41
+ accountDocumentIds(): Promise<string[]>;
42
+ private ids;
43
+ /**
44
+ * For the account plug-in: runs `fn` on each stored document, with its `Comments` and `Suggestions`. With `save`,
45
+ * each document runs in one transaction and the state is written back there. A throw leaves it as it was. A document deleted since the list was read is skipped.
46
+ * A damaged document stops the run with CORRUPT.
47
+ */
48
+ eachAccountDocument(fn: (doc: CommentsAccountDocument) => void, save: boolean, committed?: (id: string) => void): Promise<void>;
49
+ private scan;
50
+ /** The documents this subject created: the creator is kept on the row. For the account plug-in. */
51
+ exportCreated(author: Author): Promise<{
52
+ documents: {
53
+ documentId: string;
54
+ createdAt: unknown;
55
+ }[];
56
+ unread: string[];
57
+ }>;
58
+ /** Give the creator of each document this subject made the tombstone name. Returns how many, and the rows it could not read. A repeat finds none. */
59
+ eraseCreated(author: Author): Promise<{
60
+ count: number;
61
+ unread: string[];
62
+ }>;
39
63
  }
40
64
  /** The docs, or a function that makes them from the Worker bindings. */
41
65
  type DocsSource = CommentsDocs | ((env: WorkerEnv) => Promise<CommentsDocs>);
package/dist/worker.js CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
2
  CommentsService,
3
- TEXT_ROOT
4
- } from "./chunk-B7LTOLUO.js";
3
+ TEXT_ROOT,
4
+ localHost
5
+ } from "./chunk-TTNT27PG.js";
5
6
  import {
6
7
  ACCEPT_SCOPE,
8
+ Comments,
7
9
  CommentsError,
10
+ ERASED_NAME,
8
11
  MODERATE_SCOPE,
12
+ Suggestions,
9
13
  exact,
10
14
  invalid,
15
+ readAuthor,
11
16
  stamp
12
- } from "./chunk-6MS4LASK.js";
17
+ } from "./chunk-4G5BDTBA.js";
13
18
 
14
19
  // src/worker.ts
15
20
  import { Hono } from "hono";
@@ -150,6 +155,89 @@ var CommentsDocs = class {
150
155
  return result;
151
156
  });
152
157
  }
158
+ /** For the account plug-in: the id of each stored document. */
159
+ async accountDocumentIds() {
160
+ return this.ids();
161
+ }
162
+ async ids() {
163
+ const ids = [];
164
+ let cursor;
165
+ do {
166
+ const page = await this.store.query(DOCS, { limit: 100, ...cursor ? { cursor } : {} });
167
+ for (const { id } of page.items) ids.push(id);
168
+ cursor = page.nextCursor;
169
+ } while (cursor);
170
+ return ids;
171
+ }
172
+ /**
173
+ * For the account plug-in: runs `fn` on each stored document, with its `Comments` and `Suggestions`. With `save`,
174
+ * each document runs in one transaction and the state is written back there. A throw leaves it as it was. A document deleted since the list was read is skipped.
175
+ * A damaged document stops the run with CORRUPT.
176
+ */
177
+ async eachAccountDocument(fn, save, committed) {
178
+ const open = (id, row) => {
179
+ const ydoc = this.load(row, id);
180
+ ydoc.getText(TEXT_ROOT);
181
+ const comments = new Comments(ydoc);
182
+ return { ydoc, doc: { id, comments, suggestions: new Suggestions(comments, localHost) } };
183
+ };
184
+ for (const id of await this.ids()) {
185
+ if (!save) {
186
+ const row = await this.store.get(DOCS, id);
187
+ if (row !== null) fn(open(id, row).doc);
188
+ continue;
189
+ }
190
+ const wrote = await this.store.transaction(async (tx) => {
191
+ const row = await tx.get(DOCS, id);
192
+ if (row === null) return false;
193
+ const { ydoc, doc } = open(id, row);
194
+ fn(doc);
195
+ const state = Y.encodeStateAsUpdate(ydoc);
196
+ if (state.length > MAX_STATE) throw new CommentsError("STORE_FULL", `document "${id}" would pass ${MAX_STATE} bytes of state`);
197
+ await tx.put(DOCS, id, { state: toBase64(state), createdBy: row?.createdBy ?? null, createdAt: row?.createdAt ?? null });
198
+ return true;
199
+ });
200
+ if (wrote) committed?.(id);
201
+ }
202
+ }
203
+ async scan(author, each, unread) {
204
+ let cursor;
205
+ do {
206
+ const page = await this.store.query(DOCS, { limit: 100, ...cursor ? { cursor } : {} });
207
+ for (const { id, data } of page.items) {
208
+ const raw = data?.createdBy;
209
+ const by = readAuthor(raw);
210
+ if (!by && raw !== null && raw !== void 0) unread?.push(id);
211
+ else if (by && by.kind === author.kind && by.name === author.name) await each(id, data);
212
+ }
213
+ cursor = page.nextCursor;
214
+ } while (cursor);
215
+ }
216
+ /** The documents this subject created: the creator is kept on the row. For the account plug-in. */
217
+ async exportCreated(author) {
218
+ const documents = [];
219
+ const unread = [];
220
+ await this.scan(author, async (id, row) => void documents.push({ documentId: id, createdAt: row.createdAt ?? null }), unread);
221
+ return { documents, unread };
222
+ }
223
+ /** Give the creator of each document this subject made the tombstone name. Returns how many, and the rows it could not read. A repeat finds none. */
224
+ async eraseCreated(author) {
225
+ const ids = [];
226
+ const unread = [];
227
+ await this.scan(author, async (id) => void ids.push(id), unread);
228
+ let count = 0;
229
+ for (const id of ids) {
230
+ const changed = await this.store.transaction(async (tx) => {
231
+ const row = await tx.get(DOCS, id);
232
+ const by = readAuthor(row?.createdBy);
233
+ if (!row || !by || by.kind !== author.kind || by.name !== author.name) return false;
234
+ await tx.put(DOCS, id, { ...row, createdBy: { kind: by.kind, name: ERASED_NAME } });
235
+ return true;
236
+ });
237
+ if (changed) count++;
238
+ }
239
+ return { count, unread };
240
+ }
153
241
  };
154
242
  var STATUS = {
155
243
  INVALID_AUTHOR: 400,
package/llms.txt CHANGED
@@ -6,6 +6,7 @@ Keywords: comments,threads,mentions,suggestions,annotations,collaboration,agents
6
6
 
7
7
  - SDK: import { Comments, Suggestions, textAnchor } from "agentic-comments". The host calls stamp(); an agent never does.
8
8
  - Plain text anchors: import { createAnchor, resolveAnchor, mapRange } from "agentic-comments/anchor". No Yjs. resolveAnchor maps through edits, then falls back to quote + context, else orphaned.
9
+ - Account export and erase: import { commentsAccount } from "agentic-comments/account". exportAccount(subject, { documents, actor }) returns JSON records of one subject (pass worker: docs to cover every stored worker document and its creator). eraseAccount tombstones them and returns counts. The actor is made with stamp() and names the subject.
9
10
  - Threads live on a Yjs document and merge across replicas.
10
11
  - Lifecycle: comments.editMessage, deleteMessage, deleteThread, exportPerson, erasePerson (and the same on the CLI, MCP, HTTP API, and client). Only the author may change their own messages; comments:moderate may change anyone's.
11
12
  - React panel: import { CommentsPanel, createClient, mayDecide } from "agentic-comments/ui" and "agentic-comments/ui.css" (with agentic-design/styles.css). Threads, quotes, replies, resolve and reopen, agent badge, accept and reject. An agent never decides its own suggestion. Peer: react, react-dom.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-comments",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Comment threads and suggestions anchored to any content, from people and agents.",
5
5
  "keywords": [
6
6
  "comments",
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/index.d.ts",
45
45
  "import": "./dist/index.js"
46
46
  },
47
+ "./account": {
48
+ "types": "./dist/account.d.ts",
49
+ "import": "./dist/account.js"
50
+ },
47
51
  "./anchor": {
48
52
  "types": "./dist/anchoring.d.ts",
49
53
  "import": "./dist/anchoring.js"
@@ -1,11 +0,0 @@
1
- import {
2
- createMcpServer,
3
- startMcp
4
- } from "./chunk-OV2GGL3T.js";
5
- import "./chunk-EFO5PSJL.js";
6
- import "./chunk-B7LTOLUO.js";
7
- import "./chunk-6MS4LASK.js";
8
- export {
9
- createMcpServer,
10
- startMcp
11
- };