agentic-comments 0.3.0 → 0.4.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
@@ -55,6 +55,33 @@ normal sync, and every replica sees the same threads.
55
55
  - A malformed thread from a bad replica is skipped. A bad message is left out, and `skipped` on the
56
56
  thread counts those. `problems()` names them.
57
57
 
58
+ ## Anchors for plain text
59
+
60
+ `agentic-comments/anchor` pins a range of a plain string. It needs no Yjs and no framework.
61
+ Offsets are UTF-16 code units, like `slice`.
62
+
63
+ ```ts
64
+ import { createAnchor, resolveAnchor } from "agentic-comments/anchor";
65
+
66
+ const anchor = createAnchor("Hello brave world", 6, 11);
67
+ const edits = [{ from: 0, to: 0, insert: "Oh, " }];
68
+ resolveAnchor(anchor, "Oh, Hello brave world", edits, 17);
69
+ // { state: "attached", from: 10, to: 15, method: "mapped" }
70
+ ```
71
+
72
+ - An edit is `{ from, to, insert }`. Edits apply one after another, each in the text left by the one before.
73
+ - `resolveAnchor(anchor, newText, edits?, originalLength?)` maps the range through the edits. Pass
74
+ `originalLength`, the length of the old text, so it can check that the edits fit both texts.
75
+ - With edits, the edits are the truth, and the quote is not searched. A range that holds other text is `edited`.
76
+ - With no edits, it searches for the quote. One match wins. With many, the best fit of the text around the
77
+ quote wins. On a tie, the match at the old offsets wins, or else the anchor is `orphaned`, because a wrong
78
+ place is worse than none. More than 500 matches orphan too.
79
+ - `edited` means the range exists but holds other text. `orphaned` means the content is gone, the quote is ambiguous, the edits do not fit the text, or the anchor is bad data.
80
+ - An insert at an edge of the range stays outside it. Deleting the whole range orphans it.
81
+ - `createAnchor` throws a `RangeError` for an empty range, a range outside the text, or a range that cuts a
82
+ surrogate pair in two. It does not check grapheme boundaries, so a flag or a joined emoji can still be cut.
83
+ - `mapPosition` and `mapRange` move offsets through edits on their own.
84
+
58
85
  ## When the content changes
59
86
 
60
87
  Every thread and suggestion has an `anchorState`:
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Plain-text anchors that survive edits. No Yjs, no framework. Offsets are UTF-16 code units, like
3
+ * `String.prototype.slice`. An anchor keeps its quote and some text around it, so it can be found again
4
+ * when you have no edit list.
5
+ */
6
+ /** One edit: replace `text.slice(from, to)` with `insert`. A pure insert has `from === to`. */
7
+ interface Edit {
8
+ from: number;
9
+ to: number;
10
+ insert: string;
11
+ }
12
+ interface TextRangeAnchor {
13
+ /** The text under the range when the anchor was made. */
14
+ quote: string;
15
+ /** Text just before the range. */
16
+ prefix: string;
17
+ /** Text just after the range. */
18
+ suffix: string;
19
+ /** The range at creation, in the text it was made for. */
20
+ from: number;
21
+ to: number;
22
+ }
23
+ interface OffsetRange {
24
+ from: number;
25
+ to: number;
26
+ }
27
+ /**
28
+ * `attached`: the range holds the quote. `edited`: the range still exists but holds other text.
29
+ * `orphaned`: the content is gone, or the quote is too ambiguous to trust.
30
+ */
31
+ type Resolution = {
32
+ state: "attached";
33
+ from: number;
34
+ to: number;
35
+ method: "mapped" | "quote";
36
+ } | {
37
+ state: "edited";
38
+ from: number;
39
+ to: number;
40
+ } | {
41
+ state: "orphaned";
42
+ };
43
+ declare const CONTEXT_LENGTH = 32;
44
+ declare const MAX_QUOTE_LENGTH = 2000;
45
+ /** The most copies of a quote that `resolveAnchor` ranks. With more, the anchor is orphaned. */
46
+ declare const MAX_MATCHES = 500;
47
+ /** Pin the range `from..to` of `text`. Throws a `RangeError` when the range is empty, outside the text, or splits a character. */
48
+ declare function createAnchor(text: string, from: number, to: number): TextRangeAnchor;
49
+ /**
50
+ * Move one position through edits made one after another. `assoc` 1 puts a position that sits at an
51
+ * insert after the insert. `assoc` -1 puts it before. A position inside a replaced range moves to the
52
+ * end of the insert with `assoc` 1, and to the start of the insert with `assoc` -1. `length` is the text length before the first edit.
53
+ */
54
+ declare function mapPosition(pos: number, edits: readonly Edit[], assoc?: 1 | -1, length?: number): number;
55
+ /**
56
+ * Move a range through edits. An insert at either edge stays outside the range. Returns null when the
57
+ * edits delete the whole range.
58
+ */
59
+ declare function mapRange(range: OffsetRange, edits: readonly Edit[], length?: number): OffsetRange | null;
60
+ /**
61
+ * Find an anchor in `newText`.
62
+ *
63
+ * With `edits` (the changes from the text the anchor was made for to `newText`), the edits are the truth:
64
+ * the mapped range is `attached` if it holds the quote, `edited` if it holds other text, and `orphaned`
65
+ * if the edits delete it or it falls outside the text. The quote is not searched. Pass `originalLength`
66
+ * (the length of the old text) to also check that the edits fit both texts.
67
+ *
68
+ * With no edits, search for the quote. One match wins. With several, the match whose surrounding text
69
+ * fits best wins. On a tie, the match at the old offsets wins, else the anchor is `orphaned`: a wrong
70
+ * place is worse than none. A bad anchor, a half character, and too many matches also orphan.
71
+ */
72
+ declare function resolveAnchor(anchor: TextRangeAnchor, newText: string, edits?: readonly Edit[], originalLength?: number): Resolution;
73
+
74
+ export { CONTEXT_LENGTH, type Edit, MAX_MATCHES, MAX_QUOTE_LENGTH, type OffsetRange, type Resolution, type TextRangeAnchor, createAnchor, mapPosition, mapRange, resolveAnchor };
@@ -0,0 +1,122 @@
1
+ // src/anchoring.ts
2
+ var CONTEXT_LENGTH = 32;
3
+ var MAX_QUOTE_LENGTH = 2e3;
4
+ var MAX_MATCHES = 500;
5
+ var isHigh = (c) => c >= 55296 && c <= 56319;
6
+ var isLow = (c) => c >= 56320 && c <= 57343;
7
+ var isLoneHalf = (code, atStart) => atStart ? isLow(code) : isHigh(code);
8
+ function splits(text, index) {
9
+ return index > 0 && index < text.length && isHigh(text.charCodeAt(index - 1)) && isLow(text.charCodeAt(index));
10
+ }
11
+ var whole = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
12
+ function createAnchor(text, from, to) {
13
+ if (typeof text !== "string") throw new TypeError("text must be a string");
14
+ if (!whole(from) || !whole(to) || to <= from || to > text.length) {
15
+ throw new RangeError(`the range ${String(from)}..${String(to)} is not inside the text, which has ${text.length} characters`);
16
+ }
17
+ if (splits(text, from) || splits(text, to)) throw new RangeError("the range cuts a character in two");
18
+ if (isLow(text.charCodeAt(from)) || isHigh(text.charCodeAt(to - 1))) throw new RangeError("the range starts or ends inside a character");
19
+ if (to - from > MAX_QUOTE_LENGTH) throw new RangeError(`a quote holds up to ${MAX_QUOTE_LENGTH} characters`);
20
+ let before = Math.max(0, from - CONTEXT_LENGTH);
21
+ if (splits(text, before)) before += 1;
22
+ let after = Math.min(text.length, to + CONTEXT_LENGTH);
23
+ if (splits(text, after)) after -= 1;
24
+ return { quote: text.slice(from, to), prefix: text.slice(before, from), suffix: text.slice(to, after), from, to };
25
+ }
26
+ function checkEdit(edit, length) {
27
+ if (typeof edit !== "object" || edit === null) throw new RangeError("an edit must be an object");
28
+ if (!whole(edit.from) || !whole(edit.to) || edit.to < edit.from || edit.to > length || typeof edit.insert !== "string") {
29
+ throw new RangeError(`the edit ${JSON.stringify([edit.from, edit.to])} is not inside a text of ${length} characters`);
30
+ }
31
+ }
32
+ function mapPosition(pos, edits, assoc = 1, length = Infinity) {
33
+ if (!whole(pos) || !Array.isArray(edits) || assoc !== 1 && assoc !== -1) throw new RangeError("the position must be a whole number, the edits a list, and assoc 1 or -1");
34
+ if (length !== Infinity && (!whole(length) || pos > length)) throw new RangeError("the position must be inside a text of a whole length");
35
+ let len = length;
36
+ let p = pos;
37
+ for (const edit of edits) {
38
+ checkEdit(edit, len);
39
+ const removed = edit.to - edit.from;
40
+ const added = edit.insert.length;
41
+ if (p > edit.to) p += added - removed;
42
+ else if (p >= edit.from) {
43
+ if (removed === 0) p = assoc === 1 ? p + added : p;
44
+ else if (p === edit.from) p = edit.from;
45
+ else if (p === edit.to) p = edit.from + added;
46
+ else p = assoc === 1 ? edit.from + added : edit.from;
47
+ }
48
+ len = len === Infinity ? len : len - removed + added;
49
+ }
50
+ return p;
51
+ }
52
+ function mapRange(range, edits, length = Infinity) {
53
+ if (!Array.isArray(edits)) throw new RangeError("the edits must be a list");
54
+ if (!whole(range?.from) || !whole(range?.to) || range.to <= range.from) throw new RangeError("the range must be two whole numbers, from before to");
55
+ let { from, to } = range;
56
+ let len = length;
57
+ for (const edit of edits) {
58
+ from = mapPosition(from, [edit], 1, len);
59
+ to = mapPosition(to, [edit], -1, len);
60
+ if (to <= from) return null;
61
+ len = len === Infinity ? len : len - (edit.to - edit.from) + edit.insert.length;
62
+ }
63
+ return { from, to };
64
+ }
65
+ function occurrences(text, quote, max) {
66
+ const out = [];
67
+ for (let i = text.indexOf(quote); i >= 0 && out.length < max; i = text.indexOf(quote, i + 1)) out.push(i);
68
+ return out;
69
+ }
70
+ var commonSuffix = (a, b) => {
71
+ let n = 0;
72
+ while (n < a.length && n < b.length && a[a.length - 1 - n] === b[b.length - 1 - n]) n++;
73
+ return n;
74
+ };
75
+ var commonPrefix = (a, b) => {
76
+ let n = 0;
77
+ while (n < a.length && n < b.length && a[n] === b[n]) n++;
78
+ return n;
79
+ };
80
+ function resolveAnchor(anchor, newText, edits, originalLength) {
81
+ if (typeof newText !== "string") throw new TypeError("text must be a string");
82
+ if (typeof anchor?.quote !== "string" || anchor.quote === "") return { state: "orphaned" };
83
+ const quote = anchor.quote;
84
+ if (quote.length > MAX_QUOTE_LENGTH || edits !== void 0 && !Array.isArray(edits)) return { state: "orphaned" };
85
+ if (!whole(anchor.from) || !whole(anchor.to) || anchor.to - anchor.from !== quote.length) return { state: "orphaned" };
86
+ if (isLoneHalf(quote.charCodeAt(0), true) || isLoneHalf(quote.charCodeAt(quote.length - 1), false)) return { state: "orphaned" };
87
+ if (originalLength !== void 0) {
88
+ const final = (edits ?? []).reduce((n, e) => n - (e?.to - e?.from) + (typeof e?.insert === "string" ? e.insert.length : NaN), originalLength);
89
+ if (!whole(originalLength) || anchor.to > originalLength || edits && final !== newText.length) return { state: "orphaned" };
90
+ }
91
+ let mapped;
92
+ try {
93
+ mapped = edits ? mapRange({ from: anchor.from, to: anchor.to }, edits, originalLength) : { from: anchor.from, to: anchor.to };
94
+ } catch (error) {
95
+ if (error instanceof RangeError) return { state: "orphaned" };
96
+ throw error;
97
+ }
98
+ if (edits && mapped === null) return { state: "orphaned" };
99
+ const valid = mapped && mapped.to <= newText.length && !splits(newText, mapped.from) && !splits(newText, mapped.to);
100
+ if (edits) {
101
+ if (!valid || !mapped) return { state: "orphaned" };
102
+ return newText.slice(mapped.from, mapped.to) === quote ? { state: "attached", from: mapped.from, to: mapped.to, method: "mapped" } : { state: "edited", from: mapped.from, to: mapped.to };
103
+ }
104
+ const found = occurrences(newText, quote, MAX_MATCHES + 1).filter((at) => !splits(newText, at) && !splits(newText, at + quote.length));
105
+ if (found.length === 0 || found.length > MAX_MATCHES) return { state: "orphaned" };
106
+ const result = (at) => ({ state: "attached", from: at, to: at + quote.length, method: at === anchor.from ? "mapped" : "quote" });
107
+ if (found.length === 1) return result(found[0]);
108
+ const score = (at) => commonSuffix(anchor.prefix ?? "", newText.slice(Math.max(0, at - CONTEXT_LENGTH), at)) + commonPrefix(anchor.suffix ?? "", newText.slice(at + quote.length, at + quote.length + CONTEXT_LENGTH));
109
+ const ranked = found.map((at) => ({ at, score: score(at) })).sort((a, b) => b.score - a.score);
110
+ const best = ranked.filter((r) => r.score === ranked[0].score);
111
+ if (best.length === 1) return result(best[0].at);
112
+ return best.some((r) => r.at === anchor.from) ? result(anchor.from) : { state: "orphaned" };
113
+ }
114
+ export {
115
+ CONTEXT_LENGTH,
116
+ MAX_MATCHES,
117
+ MAX_QUOTE_LENGTH,
118
+ createAnchor,
119
+ mapPosition,
120
+ mapRange,
121
+ resolveAnchor
122
+ };
@@ -114,7 +114,7 @@ function onlyOccurrence(type, quote) {
114
114
  }
115
115
 
116
116
  // src/principal.ts
117
- var PRINCIPAL_KINDS = ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
117
+ import { PRINCIPAL_KINDS } from "agentic-identity";
118
118
  var MAX_NAME = 128;
119
119
  var issued = /* @__PURE__ */ new WeakSet();
120
120
  var Actor = class _Actor {
@@ -1,6 +1,6 @@
1
1
  // src/version.ts
2
2
  import { createRequire } from "module";
3
- var VERSION = true ? "0.3.0" : readPackageVersion();
3
+ var VERSION = true ? "0.4.0" : readPackageVersion();
4
4
 
5
5
  export {
6
6
  VERSION
@@ -2,7 +2,7 @@ import {
2
2
  CommentsError,
3
3
  PRINCIPAL_KINDS,
4
4
  stamp
5
- } from "./chunk-GSAJOCDV.js";
5
+ } from "./chunk-BSNBDKR2.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-GSAJOCDV.js";
10
+ } from "./chunk-BSNBDKR2.js";
11
11
 
12
12
  // src/local-host.ts
13
13
  import * as Y from "yjs";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-GMA5I5L6.js";
3
+ } from "./chunk-DRACF66Y.js";
4
4
  import {
5
5
  CommentsService
6
- } from "./chunk-IB6ANO32.js";
6
+ } from "./chunk-NQWRFKJ6.js";
7
7
  import {
8
8
  CommentsError
9
- } from "./chunk-GSAJOCDV.js";
9
+ } from "./chunk-BSNBDKR2.js";
10
10
 
11
11
  // src/mcp.ts
12
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/cli.js CHANGED
@@ -3,17 +3,17 @@ import {
3
3
  FileStore,
4
4
  lockTimeoutFrom,
5
5
  operatorActor
6
- } from "./chunk-YU5U4P22.js";
6
+ } from "./chunk-HXXJNFEO.js";
7
7
  import {
8
8
  VERSION
9
- } from "./chunk-GMA5I5L6.js";
9
+ } from "./chunk-DRACF66Y.js";
10
10
  import {
11
11
  CommentsService,
12
12
  TEXT_ROOT
13
- } from "./chunk-IB6ANO32.js";
13
+ } from "./chunk-NQWRFKJ6.js";
14
14
  import {
15
15
  CommentsError
16
- } from "./chunk-GSAJOCDV.js";
16
+ } from "./chunk-BSNBDKR2.js";
17
17
 
18
18
  // src/cli.ts
19
19
  import { readFileSync, realpathSync } from "fs";
@@ -155,7 +155,7 @@ async function runCli(argv, io) {
155
155
  return 0;
156
156
  }
157
157
  if (p.command === "mcp") {
158
- const { startMcp } = await import("./mcp-YERA77HT.js");
158
+ const { startMcp } = await import("./mcp-ACC4QIEM.js");
159
159
  await startMcp(store, operatorActor(p.flags["--as"] ?? io.env.AGENTIC_COMMENTS_AS, io.env.AGENTIC_COMMENTS_SCOPES));
160
160
  return 0;
161
161
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { a as Author, C as Comments, b as Suggestions } from './suggestions-D4ZJFzzR.js';
2
- export { c as ACCEPT_SCOPE, A as Actor, d as Anchor, e as AnchorKind, f as AnchorState, g as ApplyContext, h as CanvasAnchor, i as CellAnchor, j as CommentsOptions, M as Message, N as NewReply, k as NewSuggestion, l as NewThread, P as PRINCIPAL_KINDS, m as PrincipalKind, R as RelativePositionJson, n as RevertContext, o as SlideAnchor, p as StampInput, S as Suggestion, q as SuggestionFilter, r as SuggestionHost, s as TextAnchor, T as Thread, t as ThreadFilter, u as authorLabel, v as stamp, w as textAnchor, x as textOf } from './suggestions-D4ZJFzzR.js';
1
+ import { a as Author, C as Comments, b as Suggestions } from './suggestions-oj8fd-nG.js';
2
+ export { c as ACCEPT_SCOPE, A as Actor, d as Anchor, e as AnchorKind, f as AnchorState, g as ApplyContext, h as CanvasAnchor, i as CellAnchor, j as CommentsOptions, M as Message, N as NewReply, k as NewSuggestion, l as NewThread, R as RelativePositionJson, m as RevertContext, n as SlideAnchor, o as StampInput, S as Suggestion, p as SuggestionFilter, q as SuggestionHost, r as TextAnchor, T as Thread, s as ThreadFilter, t as authorLabel, u as stamp, v as textAnchor, w as textOf } from './suggestions-oj8fd-nG.js';
3
+ export { PRINCIPAL_KINDS, PrincipalKind } from 'agentic-identity';
3
4
  import 'yjs';
4
5
 
5
6
  declare const VERSION: string;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-GMA5I5L6.js";
3
+ } from "./chunk-DRACF66Y.js";
4
4
  import {
5
5
  ACCEPT_SCOPE,
6
6
  Actor,
@@ -14,7 +14,7 @@ import {
14
14
  textAnchor,
15
15
  textOf,
16
16
  validDate
17
- } from "./chunk-GSAJOCDV.js";
17
+ } from "./chunk-BSNBDKR2.js";
18
18
 
19
19
  // src/notifications.ts
20
20
  import * as Y from "yjs";
package/dist/local.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as Y from 'yjs';
2
- export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-BmMQMT_8.js';
3
- import { r as SuggestionHost, A as Actor } from './suggestions-D4ZJFzzR.js';
2
+ export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-Dxw9TVxY.js';
3
+ import { q as SuggestionHost, A as Actor } from './suggestions-oj8fd-nG.js';
4
+ import 'agentic-identity';
4
5
 
5
6
  /** The most bytes a saved document holds. A save over this is refused, so a file never grows past what a load accepts. */
6
7
  declare const MAX_FILE: number;
package/dist/local.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  MAX_FILE,
4
4
  lockTimeoutFrom,
5
5
  operatorActor
6
- } from "./chunk-YU5U4P22.js";
6
+ } from "./chunk-HXXJNFEO.js";
7
7
  import {
8
8
  CommentsService,
9
9
  TEXT_ROOT,
10
10
  VALUES_ROOT,
11
11
  localHost
12
- } from "./chunk-IB6ANO32.js";
13
- import "./chunk-GSAJOCDV.js";
12
+ } from "./chunk-NQWRFKJ6.js";
13
+ import "./chunk-BSNBDKR2.js";
14
14
  export {
15
15
  CommentsService,
16
16
  FileStore,
@@ -0,0 +1,11 @@
1
+ import {
2
+ createMcpServer,
3
+ startMcp
4
+ } from "./chunk-R4LH36VG.js";
5
+ import "./chunk-DRACF66Y.js";
6
+ import "./chunk-NQWRFKJ6.js";
7
+ import "./chunk-BSNBDKR2.js";
8
+ export {
9
+ createMcpServer,
10
+ startMcp
11
+ };
package/dist/mcp-bin.js CHANGED
@@ -3,15 +3,15 @@ import {
3
3
  FileStore,
4
4
  lockTimeoutFrom,
5
5
  operatorActor
6
- } from "./chunk-YU5U4P22.js";
6
+ } from "./chunk-HXXJNFEO.js";
7
7
  import {
8
8
  startMcp
9
- } from "./chunk-TTTX3R6M.js";
9
+ } from "./chunk-R4LH36VG.js";
10
10
  import {
11
11
  VERSION
12
- } from "./chunk-GMA5I5L6.js";
13
- import "./chunk-IB6ANO32.js";
14
- import "./chunk-GSAJOCDV.js";
12
+ } from "./chunk-DRACF66Y.js";
13
+ import "./chunk-NQWRFKJ6.js";
14
+ import "./chunk-BSNBDKR2.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, S as Suggestion } from './suggestions-D4ZJFzzR.js';
2
+ import { A as Actor, T as Thread, M as Message, S as Suggestion } from './suggestions-oj8fd-nG.js';
3
3
 
4
4
  /** Where a comment or suggestion points, in terms a person or agent can write down. */
5
5
  type AnchorSpec = {
@@ -1,4 +1,5 @@
1
1
  import * as Y from 'yjs';
2
+ import { PrincipalKind } from 'agentic-identity';
2
3
 
3
4
  /** A Yjs relative position as JSON. It names an item, not an index, so it follows its text. */
4
5
  interface RelativePositionJson {
@@ -65,8 +66,6 @@ declare function textOf(type: Y.Text | Y.XmlText): string;
65
66
  /** Pin a range of a Yjs text. Later edits by any replica do not move the anchor off its text. */
66
67
  declare function textAnchor(type: Y.Text | Y.XmlText, from: number, to: number): TextAnchor;
67
68
 
68
- declare const PRINCIPAL_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
69
- type PrincipalKind = (typeof PRINCIPAL_KINDS)[number];
70
69
  /** What a thread keeps about who wrote a message. Scopes never reach storage. */
71
70
  interface Author {
72
71
  kind: PrincipalKind;
@@ -331,4 +330,4 @@ declare class Suggestions {
331
330
  settle(): void;
332
331
  }
333
332
 
334
- export { Actor as A, Comments as C, type Message as M, type NewReply as N, PRINCIPAL_KINDS as P, type RelativePositionJson as R, type Suggestion as S, type Thread as T, type Author as a, Suggestions as b, ACCEPT_SCOPE as c, type Anchor as d, type AnchorKind as e, type AnchorState as f, type ApplyContext as g, type CanvasAnchor as h, type CellAnchor as i, type CommentsOptions as j, type NewSuggestion as k, type NewThread as l, type PrincipalKind as m, type RevertContext as n, type SlideAnchor as o, type StampInput as p, type SuggestionFilter as q, type SuggestionHost as r, type TextAnchor as s, type ThreadFilter as t, authorLabel as u, stamp as v, textAnchor as w, textOf as x };
333
+ export { Actor as A, Comments as C, type Message as M, type NewReply as N, type RelativePositionJson as R, type Suggestion as S, type Thread as T, type Author as a, Suggestions as b, ACCEPT_SCOPE as c, type Anchor as d, type AnchorKind as e, type AnchorState as f, type ApplyContext as g, type CanvasAnchor as h, type CellAnchor as i, type CommentsOptions as j, type NewSuggestion as k, type NewThread as l, type RevertContext as m, type SlideAnchor as n, type StampInput as o, type SuggestionFilter as p, type SuggestionHost as q, type TextAnchor as r, type ThreadFilter as s, authorLabel as t, stamp as u, textAnchor as v, textOf as w };
package/dist/worker.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Hono } from 'hono';
2
2
  import { Auth, Store, PrincipalContext, Region } from 'agentic-identity';
3
3
  import { D1Like } from 'agentic-identity/cloudflare';
4
- import { A as Actor } from './suggestions-D4ZJFzzR.js';
5
- import { C as CommentsService } from './service-BmMQMT_8.js';
4
+ import { A as Actor } from './suggestions-oj8fd-nG.js';
5
+ import { C as CommentsService } from './service-Dxw9TVxY.js';
6
6
  import 'yjs';
7
7
 
8
8
  interface WorkerEnv {
package/dist/worker.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CommentsService,
3
3
  TEXT_ROOT
4
- } from "./chunk-IB6ANO32.js";
4
+ } from "./chunk-NQWRFKJ6.js";
5
5
  import {
6
6
  ACCEPT_SCOPE,
7
7
  CommentsError,
@@ -9,7 +9,7 @@ import {
9
9
  exact,
10
10
  invalid,
11
11
  stamp
12
- } from "./chunk-GSAJOCDV.js";
12
+ } from "./chunk-BSNBDKR2.js";
13
13
 
14
14
  // src/worker.ts
15
15
  import { Hono } from "hono";
package/llms.txt CHANGED
@@ -5,6 +5,7 @@ Comment threads and suggestions anchored to any content, from people and agents.
5
5
  Keywords: comments,threads,mentions,suggestions,annotations,collaboration,agents,yjs.
6
6
 
7
7
  - SDK: import { Comments, Suggestions, textAnchor } from "agentic-comments". The host calls stamp(); an agent never does.
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.
8
9
  - Threads live on a Yjs document and merge across replicas.
9
10
  - Events: new Notifications(comments, suggestions).subscribe(listener). Ids only, never text.
10
11
  - CLI: agentic-comments <command>. MCP: agentic-comments mcp. Both use a local store file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-comments",
3
- "version": "0.3.0",
3
+ "version": "0.4.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
+ "./anchor": {
48
+ "types": "./dist/anchoring.d.ts",
49
+ "import": "./dist/anchoring.js"
50
+ },
47
51
  "./local": {
48
52
  "types": "./dist/local.d.ts",
49
53
  "import": "./dist/local.js"
@@ -1,11 +0,0 @@
1
- import {
2
- createMcpServer,
3
- startMcp
4
- } from "./chunk-TTTX3R6M.js";
5
- import "./chunk-GMA5I5L6.js";
6
- import "./chunk-IB6ANO32.js";
7
- import "./chunk-GSAJOCDV.js";
8
- export {
9
- createMcpServer,
10
- startMcp
11
- };