agentic-comments 0.3.0 → 0.5.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 +55 -0
- package/dist/anchoring.d.ts +74 -0
- package/dist/anchoring.js +122 -0
- package/dist/{chunk-TTTX3R6M.js → chunk-ABXAPWXD.js} +3 -3
- package/dist/{chunk-GSAJOCDV.js → chunk-BSNBDKR2.js} +1 -1
- package/dist/{chunk-YU5U4P22.js → chunk-HXXJNFEO.js} +1 -1
- package/dist/{chunk-IB6ANO32.js → chunk-NQWRFKJ6.js} +1 -1
- package/dist/{chunk-GMA5I5L6.js → chunk-WQHJNHAR.js} +1 -1
- package/dist/cli.js +5 -5
- package/dist/client.d.ts +72 -0
- package/dist/client.js +135 -0
- package/dist/errors-CNgWMVQT.d.ts +7 -0
- package/dist/index.d.ts +5 -9
- package/dist/index.js +2 -2
- package/dist/local.d.ts +3 -2
- package/dist/local.js +3 -3
- package/dist/mcp-JMEXA2RI.js +11 -0
- package/dist/mcp-bin.js +5 -5
- package/dist/{service-BmMQMT_8.d.ts → service-hpWbnSmc.d.ts} +1 -1
- package/dist/{suggestions-D4ZJFzzR.d.ts → suggestions-CvJwo6r4.d.ts} +31 -32
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +2 -2
- package/llms.txt +1 -0
- package/package.json +9 -1
- package/dist/mcp-YERA77HT.js +0 -11
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`:
|
|
@@ -216,6 +243,34 @@ curl -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
|
|
|
216
243
|
http://127.0.0.1:8787/api/v1/docs/d1/threads
|
|
217
244
|
```
|
|
218
245
|
|
|
246
|
+
## HTTP client
|
|
247
|
+
|
|
248
|
+
`agentic-comments/client` is a typed client for the Worker routes. It uses only `fetch`, so it runs in browsers,
|
|
249
|
+
Workers, and Node.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import { createClient, CommentsApiError } from "agentic-comments/client";
|
|
253
|
+
|
|
254
|
+
const comments = createClient({ baseUrl: "https://comments.example.com", token });
|
|
255
|
+
await comments.createDocument({ id: "d1", text: "The quick brown fox" });
|
|
256
|
+
const thread = await comments.addThread("d1", { anchor: { type: "text", quote: "fox" }, body: "Which fox?" });
|
|
257
|
+
await comments.reply("d1", thread.id, { body: "This one." });
|
|
258
|
+
try {
|
|
259
|
+
await comments.accept("d1", "missing");
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof CommentsApiError) console.error(error.code, error.status);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- The methods match the routes: `me`, `createDocument`, `getDocument`, `listThreads`, `getThread`, `addThread`, `reply`,
|
|
266
|
+
`resolve`, `reopen`, `relocate`, `listSuggestions`, `suggest`, `accept`, `reject`. Each takes an optional `{ signal }`.
|
|
267
|
+
- `baseUrl` is the Worker URL, `http` or `https`, with no query, hash, or credentials.
|
|
268
|
+
- The client never sends an author. The Worker stamps one from the token.
|
|
269
|
+
- A failed call throws `CommentsApiError`. `code` is the API error code, or `UNKNOWN` when the reply is not an API
|
|
270
|
+
error. `status` is the HTTP status. A `200` reply with the wrong shape also fails as `UNKNOWN`.
|
|
271
|
+
- The client never follows a redirect and never retries. A write that you abort after the client sends it can still apply:
|
|
272
|
+
read the thread before you try again.
|
|
273
|
+
|
|
219
274
|
## Live demo
|
|
220
275
|
|
|
221
276
|
A demo Worker runs at <https://agentic-comments-demo.hello-fd1.workers.dev>. It is a staging site. Check it with
|
|
@@ -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
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WQHJNHAR.js";
|
|
4
4
|
import {
|
|
5
5
|
CommentsService
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NQWRFKJ6.js";
|
|
7
7
|
import {
|
|
8
8
|
CommentsError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BSNBDKR2.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp.ts
|
|
12
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -114,7 +114,7 @@ function onlyOccurrence(type, quote) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// src/principal.ts
|
|
117
|
-
|
|
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 {
|
package/dist/cli.js
CHANGED
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
FileStore,
|
|
4
4
|
lockTimeoutFrom,
|
|
5
5
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HXXJNFEO.js";
|
|
7
7
|
import {
|
|
8
8
|
VERSION
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WQHJNHAR.js";
|
|
10
10
|
import {
|
|
11
11
|
CommentsService,
|
|
12
12
|
TEXT_ROOT
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-NQWRFKJ6.js";
|
|
14
14
|
import {
|
|
15
15
|
CommentsError
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
158
|
+
const { startMcp } = await import("./mcp-JMEXA2RI.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/client.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { L as ListFilter, A as AnchorSpec } from './service-hpWbnSmc.js';
|
|
2
|
+
import { C as CommentsErrorCode } from './errors-CNgWMVQT.js';
|
|
3
|
+
import { PrincipalKind } from 'agentic-identity';
|
|
4
|
+
export { PrincipalKind } from 'agentic-identity';
|
|
5
|
+
import { T as Thread, N as NewReply, M as Message, S as Suggestion } from './suggestions-CvJwo6r4.js';
|
|
6
|
+
import 'yjs';
|
|
7
|
+
|
|
8
|
+
/** Codes the comment rules return, plus the ones the Worker and its auth return. */
|
|
9
|
+
type ApiErrorCode = CommentsErrorCode | "UNAUTHENTICATED" | "NOT_FOUND" | "INTERNAL" | "UNKNOWN";
|
|
10
|
+
/** Thrown for every reply that is not a success. `code` is the API error code and `status` is the HTTP status. */
|
|
11
|
+
declare class CommentsApiError extends Error {
|
|
12
|
+
readonly code: ApiErrorCode;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
constructor(code: ApiErrorCode, status: number, message: string);
|
|
15
|
+
}
|
|
16
|
+
interface ClientOptions {
|
|
17
|
+
/** The Worker origin, for example https://comments.example.com. The client adds /api/v1. */
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
/** A bearer credential that the Worker's auth accepts. */
|
|
20
|
+
token: string;
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every method takes an optional signal. An aborted call rejects with the signal reason, not a
|
|
25
|
+
* CommentsApiError. A write that was aborted after it was sent may still have applied: read the
|
|
26
|
+
* thread before you try again.
|
|
27
|
+
*/
|
|
28
|
+
interface CallOptions {
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
interface Me {
|
|
32
|
+
kind: PrincipalKind;
|
|
33
|
+
name: string;
|
|
34
|
+
scopes: string[];
|
|
35
|
+
}
|
|
36
|
+
type ThreadFilter = ListFilter;
|
|
37
|
+
type SuggestionListFilter = Pick<ListFilter, "status" | "anchorState">;
|
|
38
|
+
interface CommentsClient {
|
|
39
|
+
me(options?: CallOptions): Promise<Me>;
|
|
40
|
+
createDocument(input: {
|
|
41
|
+
id: string;
|
|
42
|
+
text?: string;
|
|
43
|
+
}, options?: CallOptions): Promise<{
|
|
44
|
+
id: string;
|
|
45
|
+
}>;
|
|
46
|
+
getDocument(docId: string, options?: CallOptions): Promise<{
|
|
47
|
+
id: string;
|
|
48
|
+
text: string;
|
|
49
|
+
}>;
|
|
50
|
+
listThreads(docId: string, filter?: ThreadFilter, options?: CallOptions): Promise<Thread[]>;
|
|
51
|
+
getThread(docId: string, threadId: string, options?: CallOptions): Promise<Thread>;
|
|
52
|
+
addThread(docId: string, input: {
|
|
53
|
+
anchor: AnchorSpec;
|
|
54
|
+
body: string;
|
|
55
|
+
mentions?: string[];
|
|
56
|
+
}, options?: CallOptions): Promise<Thread>;
|
|
57
|
+
reply(docId: string, threadId: string, input: NewReply, options?: CallOptions): Promise<Message>;
|
|
58
|
+
resolve(docId: string, threadId: string, options?: CallOptions): Promise<Thread>;
|
|
59
|
+
reopen(docId: string, threadId: string, options?: CallOptions): Promise<Thread>;
|
|
60
|
+
relocate(docId: string, threadId: string, options?: CallOptions): Promise<Thread>;
|
|
61
|
+
listSuggestions(docId: string, filter?: SuggestionListFilter, options?: CallOptions): Promise<Suggestion[]>;
|
|
62
|
+
suggest(docId: string, input: {
|
|
63
|
+
anchor: AnchorSpec;
|
|
64
|
+
change: unknown;
|
|
65
|
+
summary?: string;
|
|
66
|
+
}, options?: CallOptions): Promise<Suggestion>;
|
|
67
|
+
accept(docId: string, suggestionId: string, options?: CallOptions): Promise<Suggestion>;
|
|
68
|
+
reject(docId: string, suggestionId: string, options?: CallOptions): Promise<Suggestion>;
|
|
69
|
+
}
|
|
70
|
+
declare function createClient(options: ClientOptions): CommentsClient;
|
|
71
|
+
|
|
72
|
+
export { AnchorSpec, type ApiErrorCode, type CallOptions, type ClientOptions, CommentsApiError, type CommentsClient, CommentsErrorCode, ListFilter, type Me, Message, NewReply, Suggestion, type SuggestionListFilter, Thread, type ThreadFilter, createClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var CODES = {
|
|
3
|
+
INVALID_AUTHOR: true,
|
|
4
|
+
INVALID_INPUT: true,
|
|
5
|
+
INVALID_ANCHOR: true,
|
|
6
|
+
NO_THREAD: true,
|
|
7
|
+
THREAD_RESOLVED: true,
|
|
8
|
+
CORRUPT: true,
|
|
9
|
+
NO_SUGGESTION: true,
|
|
10
|
+
FORBIDDEN: true,
|
|
11
|
+
ALREADY_DECIDED: true,
|
|
12
|
+
APPLY_FAILED: true,
|
|
13
|
+
RELOCATE_FAILED: true,
|
|
14
|
+
STORE_MISSING: true,
|
|
15
|
+
STORE_EXISTS: true,
|
|
16
|
+
STORE_CORRUPT: true,
|
|
17
|
+
STORE_LOCKED: true,
|
|
18
|
+
STORE_FULL: true,
|
|
19
|
+
NO_DOCUMENT: true,
|
|
20
|
+
DOCUMENT_EXISTS: true,
|
|
21
|
+
CONFLICT: true,
|
|
22
|
+
MISCONFIGURED: true,
|
|
23
|
+
USAGE: true,
|
|
24
|
+
UNAUTHENTICATED: true,
|
|
25
|
+
NOT_FOUND: true,
|
|
26
|
+
INTERNAL: true,
|
|
27
|
+
UNKNOWN: true
|
|
28
|
+
};
|
|
29
|
+
var isKnownCode = (v) => typeof v === "string" && Object.hasOwn(CODES, v);
|
|
30
|
+
var CommentsApiError = class extends Error {
|
|
31
|
+
code;
|
|
32
|
+
status;
|
|
33
|
+
constructor(code, status, message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "CommentsApiError";
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.status = status;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var KINDS = { human: true, agent_of_human: true, agent_of_agent: true, agent: true, user_token: true };
|
|
41
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
42
|
+
var isString = (v) => typeof v === "string";
|
|
43
|
+
var SHAPES = {
|
|
44
|
+
me: (v) => isObject(v) && (typeof v.kind === "string" && Object.hasOwn(KINDS, v.kind)) && isString(v.name) && Array.isArray(v.scopes),
|
|
45
|
+
created: (v) => isObject(v) && isString(v.id),
|
|
46
|
+
document: (v) => isObject(v) && isString(v.id) && isString(v.text),
|
|
47
|
+
thread: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && (v.status === "open" || v.status === "resolved") && Array.isArray(v.messages),
|
|
48
|
+
threads: (v) => Array.isArray(v) && v.every(SHAPES.thread),
|
|
49
|
+
message: (v) => isObject(v) && isString(v.id) && isString(v.body) && isObject(v.author),
|
|
50
|
+
suggestion: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && isString(v.status) && isObject(v.author),
|
|
51
|
+
suggestions: (v) => Array.isArray(v) && v.every(SHAPES.suggestion)
|
|
52
|
+
};
|
|
53
|
+
function createClient(options) {
|
|
54
|
+
if (typeof options.baseUrl !== "string" || options.baseUrl === "") throw new TypeError("createClient needs a baseUrl");
|
|
55
|
+
if (typeof options.token !== "string" || options.token === "") throw new TypeError("createClient needs a token");
|
|
56
|
+
let origin;
|
|
57
|
+
try {
|
|
58
|
+
origin = new URL(options.baseUrl);
|
|
59
|
+
} catch {
|
|
60
|
+
throw new TypeError("baseUrl must be an absolute URL");
|
|
61
|
+
}
|
|
62
|
+
if (origin.protocol !== "https:" && origin.protocol !== "http:") throw new TypeError("baseUrl must be an http or https URL");
|
|
63
|
+
if (origin.search !== "" || origin.hash !== "" || origin.username !== "" || origin.password !== "") {
|
|
64
|
+
throw new TypeError("baseUrl must have no query, hash, or credentials");
|
|
65
|
+
}
|
|
66
|
+
const root = `${origin.origin}${origin.pathname.replace(/\/+$/, "")}/api/v1`;
|
|
67
|
+
const token = options.token;
|
|
68
|
+
const send = (input, init) => {
|
|
69
|
+
const run = options.fetch ?? globalThis.fetch;
|
|
70
|
+
return run(input, init);
|
|
71
|
+
};
|
|
72
|
+
const seg = (value) => {
|
|
73
|
+
if (typeof value !== "string" || /^(?:\.|%2e){0,2}$/i.test(value)) throw new TypeError("an id must be a non-empty string that is not . or ..");
|
|
74
|
+
return encodeURIComponent(value);
|
|
75
|
+
};
|
|
76
|
+
const query = (filter) => {
|
|
77
|
+
const params = new URLSearchParams();
|
|
78
|
+
for (const [key, value] of Object.entries(filter ?? {})) if (value !== void 0) params.set(key, String(value));
|
|
79
|
+
const text = params.toString();
|
|
80
|
+
return text === "" ? "" : `?${text}`;
|
|
81
|
+
};
|
|
82
|
+
async function call(method, path, body, callOptions, shape) {
|
|
83
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
84
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
85
|
+
const response = await send(`${root}${path}`, {
|
|
86
|
+
method,
|
|
87
|
+
headers,
|
|
88
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
89
|
+
signal: callOptions?.signal,
|
|
90
|
+
// Never follow a redirect: it would send a write to another host.
|
|
91
|
+
redirect: "manual"
|
|
92
|
+
});
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = text === "" ? void 0 : JSON.parse(text);
|
|
97
|
+
} catch {
|
|
98
|
+
parsed = void 0;
|
|
99
|
+
}
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const error = parsed?.error;
|
|
102
|
+
const code = isKnownCode(error?.code) ? error.code : "UNKNOWN";
|
|
103
|
+
const message = typeof error?.message === "string" ? error.message : `HTTP ${response.status}`;
|
|
104
|
+
throw new CommentsApiError(code, response.status, message);
|
|
105
|
+
}
|
|
106
|
+
if (!SHAPES[shape](parsed)) throw new CommentsApiError("UNKNOWN", response.status, `the reply is not a ${shape}`);
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
const doc = (id) => `/docs/${seg(id)}`;
|
|
110
|
+
const thread = (id, threadId) => `${doc(id)}/threads/${seg(threadId)}`;
|
|
111
|
+
const suggestion = (id, suggestionId) => `${doc(id)}/suggestions/${seg(suggestionId)}`;
|
|
112
|
+
const methods = {
|
|
113
|
+
me: (o) => call("GET", "/me", void 0, o, "me"),
|
|
114
|
+
createDocument: (input, o) => call("POST", "/docs", input, o, "created"),
|
|
115
|
+
getDocument: (id, o) => call("GET", doc(id), void 0, o, "document"),
|
|
116
|
+
listThreads: (id, filter, o) => call("GET", `${doc(id)}/threads${query(filter)}`, void 0, o, "threads"),
|
|
117
|
+
getThread: (id, threadId, o) => call("GET", thread(id, threadId), void 0, o, "thread"),
|
|
118
|
+
addThread: (id, input, o) => call("POST", `${doc(id)}/threads`, input, o, "thread"),
|
|
119
|
+
reply: (id, threadId, input, o) => call("POST", `${thread(id, threadId)}/replies`, input, o, "message"),
|
|
120
|
+
resolve: (id, threadId, o) => call("POST", `${thread(id, threadId)}/resolve`, void 0, o, "thread"),
|
|
121
|
+
reopen: (id, threadId, o) => call("POST", `${thread(id, threadId)}/reopen`, void 0, o, "thread"),
|
|
122
|
+
relocate: (id, threadId, o) => call("POST", `${thread(id, threadId)}/relocate`, void 0, o, "thread"),
|
|
123
|
+
listSuggestions: (id, filter, o) => call("GET", `${doc(id)}/suggestions${query(filter)}`, void 0, o, "suggestions"),
|
|
124
|
+
suggest: (id, input, o) => call("POST", `${doc(id)}/suggestions`, input, o, "suggestion"),
|
|
125
|
+
accept: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/accept`, void 0, o, "suggestion"),
|
|
126
|
+
reject: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/reject`, void 0, o, "suggestion")
|
|
127
|
+
};
|
|
128
|
+
return Object.fromEntries(
|
|
129
|
+
Object.entries(methods).map(([name, method]) => [name, async (...args) => method(...args)])
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
CommentsApiError,
|
|
134
|
+
createClient
|
|
135
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type CommentsErrorCode = "INVALID_AUTHOR" | "INVALID_INPUT" | "INVALID_ANCHOR" | "NO_THREAD" | "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
|
+
declare class CommentsError extends Error {
|
|
3
|
+
readonly code: CommentsErrorCode;
|
|
4
|
+
constructor(code: CommentsErrorCode, message: string);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export { type CommentsErrorCode as C, CommentsError as a };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { a as Author, C as Comments, b as Suggestions } from './suggestions-
|
|
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,
|
|
1
|
+
import { a as Author, C as Comments, b as Suggestions } from './suggestions-CvJwo6r4.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-CvJwo6r4.js';
|
|
3
|
+
export { a as CommentsError, C as CommentsErrorCode } from './errors-CNgWMVQT.js';
|
|
4
|
+
export { PRINCIPAL_KINDS, PrincipalKind } from 'agentic-identity';
|
|
3
5
|
import 'yjs';
|
|
4
6
|
|
|
5
7
|
declare const VERSION: string;
|
|
6
8
|
|
|
7
|
-
type CommentsErrorCode = "INVALID_AUTHOR" | "INVALID_INPUT" | "INVALID_ANCHOR" | "NO_THREAD" | "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";
|
|
8
|
-
declare class CommentsError extends Error {
|
|
9
|
-
readonly code: CommentsErrorCode;
|
|
10
|
-
constructor(code: CommentsErrorCode, message: string);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
type CommentEventType = "thread.created" | "message.added" | "mention" | "thread.resolved" | "thread.reopened" | "suggestion.created" | "suggestion.accepted" | "suggestion.rejected";
|
|
14
10
|
/**
|
|
15
11
|
* One change, in terms a notifier can route. It holds ids and never the text of a comment, a suggestion, or
|
|
@@ -102,4 +98,4 @@ declare class Notifications {
|
|
|
102
98
|
private decided;
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
export { Author, type CommentEvent, type CommentEventType, type CommentListener, Comments,
|
|
101
|
+
export { Author, type CommentEvent, type CommentEventType, type CommentListener, Comments, Notifications, type NotificationsOptions, Suggestions, VERSION };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WQHJNHAR.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-
|
|
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-
|
|
3
|
-
import {
|
|
2
|
+
export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-hpWbnSmc.js';
|
|
3
|
+
import { q as SuggestionHost, A as Actor } from './suggestions-CvJwo6r4.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-
|
|
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-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-NQWRFKJ6.js";
|
|
13
|
+
import "./chunk-BSNBDKR2.js";
|
|
14
14
|
export {
|
|
15
15
|
CommentsService,
|
|
16
16
|
FileStore,
|
package/dist/mcp-bin.js
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
FileStore,
|
|
4
4
|
lockTimeoutFrom,
|
|
5
5
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HXXJNFEO.js";
|
|
7
7
|
import {
|
|
8
8
|
startMcp
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ABXAPWXD.js";
|
|
10
10
|
import {
|
|
11
11
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-WQHJNHAR.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-
|
|
2
|
+
import { A as Actor, T as Thread, M as Message, S as Suggestion } from './suggestions-CvJwo6r4.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,34 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
|
+
import { PrincipalKind } from 'agentic-identity';
|
|
3
|
+
|
|
4
|
+
/** What a thread keeps about who wrote a message. Scopes never reach storage. */
|
|
5
|
+
interface Author {
|
|
6
|
+
kind: PrincipalKind;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
interface StampInput {
|
|
10
|
+
kind: PrincipalKind;
|
|
11
|
+
name: string;
|
|
12
|
+
/** Rights the host granted this principal, such as `comments:accept`. Used to decide, never stored. */
|
|
13
|
+
scopes?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The only author a write accepts. The host makes one from the principal it authenticated, and an
|
|
17
|
+
* operation never reads an author from its input. An object that `stamp()` did not make is refused,
|
|
18
|
+
* and a stamped one cannot change.
|
|
19
|
+
*/
|
|
20
|
+
declare class Actor implements Author {
|
|
21
|
+
readonly kind: PrincipalKind;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly scopes: readonly string[];
|
|
24
|
+
private constructor();
|
|
25
|
+
static of(input: StampInput): Actor;
|
|
26
|
+
static is(value: unknown): value is Actor;
|
|
27
|
+
}
|
|
28
|
+
/** Make the author for one authenticated principal. Throws when the kind or name is missing or wrong. */
|
|
29
|
+
declare const stamp: (input: StampInput) => Actor;
|
|
30
|
+
/** The label a UI shows. Agent work stays marked as machine-made. */
|
|
31
|
+
declare function authorLabel(author: Author): "person" | "person via agent" | "agent";
|
|
2
32
|
|
|
3
33
|
/** A Yjs relative position as JSON. It names an item, not an index, so it follows its text. */
|
|
4
34
|
interface RelativePositionJson {
|
|
@@ -65,37 +95,6 @@ declare function textOf(type: Y.Text | Y.XmlText): string;
|
|
|
65
95
|
/** Pin a range of a Yjs text. Later edits by any replica do not move the anchor off its text. */
|
|
66
96
|
declare function textAnchor(type: Y.Text | Y.XmlText, from: number, to: number): TextAnchor;
|
|
67
97
|
|
|
68
|
-
declare const PRINCIPAL_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
|
|
69
|
-
type PrincipalKind = (typeof PRINCIPAL_KINDS)[number];
|
|
70
|
-
/** What a thread keeps about who wrote a message. Scopes never reach storage. */
|
|
71
|
-
interface Author {
|
|
72
|
-
kind: PrincipalKind;
|
|
73
|
-
name: string;
|
|
74
|
-
}
|
|
75
|
-
interface StampInput {
|
|
76
|
-
kind: PrincipalKind;
|
|
77
|
-
name: string;
|
|
78
|
-
/** Rights the host granted this principal, such as `comments:accept`. Used to decide, never stored. */
|
|
79
|
-
scopes?: string[];
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* The only author a write accepts. The host makes one from the principal it authenticated, and an
|
|
83
|
-
* operation never reads an author from its input. An object that `stamp()` did not make is refused,
|
|
84
|
-
* and a stamped one cannot change.
|
|
85
|
-
*/
|
|
86
|
-
declare class Actor implements Author {
|
|
87
|
-
readonly kind: PrincipalKind;
|
|
88
|
-
readonly name: string;
|
|
89
|
-
readonly scopes: readonly string[];
|
|
90
|
-
private constructor();
|
|
91
|
-
static of(input: StampInput): Actor;
|
|
92
|
-
static is(value: unknown): value is Actor;
|
|
93
|
-
}
|
|
94
|
-
/** Make the author for one authenticated principal. Throws when the kind or name is missing or wrong. */
|
|
95
|
-
declare const stamp: (input: StampInput) => Actor;
|
|
96
|
-
/** The label a UI shows. Agent work stays marked as machine-made. */
|
|
97
|
-
declare function authorLabel(author: Author): "person" | "person via agent" | "agent";
|
|
98
|
-
|
|
99
98
|
interface Message {
|
|
100
99
|
id: string;
|
|
101
100
|
at: string;
|
|
@@ -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,
|
|
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-
|
|
5
|
-
import { C as CommentsService } from './service-
|
|
4
|
+
import { A as Actor } from './suggestions-CvJwo6r4.js';
|
|
5
|
+
import { C as CommentsService } from './service-hpWbnSmc.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-
|
|
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-
|
|
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
|
+
"version": "0.5.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,14 @@
|
|
|
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
|
+
},
|
|
51
|
+
"./client": {
|
|
52
|
+
"types": "./dist/client.d.ts",
|
|
53
|
+
"import": "./dist/client.js"
|
|
54
|
+
},
|
|
47
55
|
"./local": {
|
|
48
56
|
"types": "./dist/local.d.ts",
|
|
49
57
|
"import": "./dist/local.js"
|