agentic-comments 0.5.0 → 0.6.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 +78 -19
- package/SKILL.md +5 -1
- package/dist/{chunk-HXXJNFEO.js → chunk-2NBFTJ47.js} +9 -2
- package/dist/{chunk-WQHJNHAR.js → chunk-ES3NZ7SX.js} +1 -1
- package/dist/{chunk-ABXAPWXD.js → chunk-SR2A5OZW.js} +12 -5
- package/dist/{chunk-NQWRFKJ6.js → chunk-T77DPNIV.js} +21 -1
- package/dist/{chunk-BSNBDKR2.js → chunk-VAVKBAHM.js} +278 -6
- package/dist/cli.js +29 -8
- package/dist/client.d.ts +29 -4
- package/dist/client.js +17 -5
- package/dist/errors-C-xvp24-.d.ts +7 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +18 -3
- package/dist/local.d.ts +2 -2
- package/dist/local.js +3 -3
- package/dist/mcp-JX5L6VQW.js +11 -0
- package/dist/mcp-bin.js +5 -5
- package/dist/{service-hpWbnSmc.d.ts → service-BvIE6t5n.d.ts} +24 -1
- package/dist/{suggestions-CvJwo6r4.d.ts → suggestions-CnkEcTuY.d.ts} +128 -1
- package/dist/worker.d.ts +13 -5
- package/dist/worker.js +51 -11
- package/llms.txt +1 -0
- package/package.json +2 -2
- package/dist/errors-CNgWMVQT.d.ts +0 -7
- package/dist/mcp-JMEXA2RI.js +0 -11
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-
|
|
3
|
-
import {
|
|
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';
|
|
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-
|
|
6
|
+
} from "./chunk-2NBFTJ47.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-T77DPNIV.js";
|
|
13
|
+
import "./chunk-VAVKBAHM.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-2NBFTJ47.js";
|
|
7
7
|
import {
|
|
8
8
|
startMcp
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SR2A5OZW.js";
|
|
10
10
|
import {
|
|
11
11
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-ES3NZ7SX.js";
|
|
13
|
+
import "./chunk-T77DPNIV.js";
|
|
14
|
+
import "./chunk-VAVKBAHM.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, P as PersonExport, a as PersonSuggestions, S as Suggestion } from './suggestions-CnkEcTuY.js';
|
|
3
3
|
|
|
4
4
|
/** Where a comment or suggestion points, in terms a person or agent can write down. */
|
|
5
5
|
type AnchorSpec = {
|
|
@@ -51,6 +51,29 @@ declare class CommentsService {
|
|
|
51
51
|
resolve(threadId: string): Thread;
|
|
52
52
|
reopen(threadId: string): Thread;
|
|
53
53
|
relocate(threadId: string): Thread;
|
|
54
|
+
editMessage(threadId: string, messageId: string, input: {
|
|
55
|
+
body: string;
|
|
56
|
+
mentions?: string[];
|
|
57
|
+
}): Message;
|
|
58
|
+
deleteMessage(threadId: string, messageId: string): {
|
|
59
|
+
threadId: string;
|
|
60
|
+
messageId: string;
|
|
61
|
+
};
|
|
62
|
+
deleteThread(threadId: string): {
|
|
63
|
+
threadId: string;
|
|
64
|
+
};
|
|
65
|
+
/** Everything the store holds that this person wrote: messages, status changes, and suggestions. */
|
|
66
|
+
exportPerson(person: string): PersonExport & {
|
|
67
|
+
suggestions: PersonSuggestions;
|
|
68
|
+
};
|
|
69
|
+
/** Tombstone everything this person wrote. Both parts are checked before either is written. */
|
|
70
|
+
erasePerson(person: string): {
|
|
71
|
+
messages: number;
|
|
72
|
+
statuses: number;
|
|
73
|
+
edits: number;
|
|
74
|
+
suggestions: number;
|
|
75
|
+
decisions: number;
|
|
76
|
+
};
|
|
54
77
|
suggestions(filter?: ListFilter): Suggestion[];
|
|
55
78
|
suggest(input: {
|
|
56
79
|
anchor: AnchorSpec;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
2
|
import { PrincipalKind } from 'agentic-identity';
|
|
3
3
|
|
|
4
|
+
/** The name an erased author shows. No principal may have it, so a tombstone belongs to no one. */
|
|
5
|
+
declare const ERASED_NAME = "erased";
|
|
6
|
+
|
|
4
7
|
/** What a thread keeps about who wrote a message. Scopes never reach storage. */
|
|
5
8
|
interface Author {
|
|
6
9
|
kind: PrincipalKind;
|
|
@@ -102,6 +105,11 @@ interface Message {
|
|
|
102
105
|
body: string;
|
|
103
106
|
/** Handles named in the body or listed on the input, without the `@`. */
|
|
104
107
|
mentions: string[];
|
|
108
|
+
/** Set when the message was edited. A message that was never edited has none. */
|
|
109
|
+
editedAt?: string;
|
|
110
|
+
editedBy?: Author;
|
|
111
|
+
/** True for a message that `erasePerson` replaced with a tombstone. */
|
|
112
|
+
erased?: true;
|
|
105
113
|
}
|
|
106
114
|
interface Thread {
|
|
107
115
|
id: string;
|
|
@@ -138,6 +146,34 @@ interface NewReply {
|
|
|
138
146
|
body: string;
|
|
139
147
|
mentions?: string[];
|
|
140
148
|
}
|
|
149
|
+
interface EditMessage {
|
|
150
|
+
body: string;
|
|
151
|
+
mentions?: string[];
|
|
152
|
+
}
|
|
153
|
+
/** What the store holds about one person in one document. It lists only that person's own words and acts. */
|
|
154
|
+
interface PersonExport {
|
|
155
|
+
person: string;
|
|
156
|
+
exportedAt: string;
|
|
157
|
+
threads: {
|
|
158
|
+
threadId: string;
|
|
159
|
+
anchor: Anchor;
|
|
160
|
+
messages: Message[];
|
|
161
|
+
/** Set when this person made the status change that stands now. */
|
|
162
|
+
statusChange?: {
|
|
163
|
+
status: Thread["status"];
|
|
164
|
+
at: string;
|
|
165
|
+
by: Author;
|
|
166
|
+
};
|
|
167
|
+
}[];
|
|
168
|
+
/** Messages by other people that this person edited. */
|
|
169
|
+
edits: {
|
|
170
|
+
threadId: string;
|
|
171
|
+
messageId: string;
|
|
172
|
+
at: string;
|
|
173
|
+
}[];
|
|
174
|
+
/** Stored entries with this person's name that the export cannot show. Not 0 means the export is not complete. */
|
|
175
|
+
unread: number;
|
|
176
|
+
}
|
|
141
177
|
interface ThreadFilter {
|
|
142
178
|
status?: Thread["status"];
|
|
143
179
|
anchorType?: string;
|
|
@@ -145,6 +181,11 @@ interface ThreadFilter {
|
|
|
145
181
|
mentioning?: string;
|
|
146
182
|
anchorState?: AnchorState["state"];
|
|
147
183
|
}
|
|
184
|
+
/** A scope that lets a principal edit, delete, export, or erase what another person wrote. A host gives it to an owner or admin. */
|
|
185
|
+
declare const MODERATE_SCOPE = "comments:moderate";
|
|
186
|
+
/** What an erased message shows in place of its body. The author shows `ERASED_NAME`. */
|
|
187
|
+
declare const ERASED_BODY = "[erased]";
|
|
188
|
+
|
|
148
189
|
/** Comment threads on a Yjs document. Replies and status merge across replicas. */
|
|
149
190
|
declare class Comments {
|
|
150
191
|
readonly ydoc: Y.Doc;
|
|
@@ -188,6 +229,62 @@ declare class Comments {
|
|
|
188
229
|
relocate(threadId: string, actor: Actor): Thread;
|
|
189
230
|
resolve(threadId: string, actor: Actor): Thread;
|
|
190
231
|
reopen(threadId: string, actor: Actor): Thread;
|
|
232
|
+
/** A principal may change what it wrote. A principal with the moderate scope may change what anyone wrote. A tombstone is nobody's. */
|
|
233
|
+
private mayChange;
|
|
234
|
+
private forbid;
|
|
235
|
+
/** Replace every stored copy of a message with `next`, or remove it when `next` is undefined. */
|
|
236
|
+
private rewrite;
|
|
237
|
+
private find;
|
|
238
|
+
/** Change the body of a message. Its author and time stay, and it records who edited it and when. */
|
|
239
|
+
editMessage(threadId: string, messageId: string, input: EditMessage, actor: Actor): Message;
|
|
240
|
+
private readEdited;
|
|
241
|
+
/**
|
|
242
|
+
* Remove one message, also from a resolved thread. The last message of a thread stays: delete the thread instead. Replicas that delete
|
|
243
|
+
* two messages at once can leave a thread with none. A moderator can still delete that thread.
|
|
244
|
+
*/
|
|
245
|
+
deleteMessage(threadId: string, messageId: string, actor: Actor): {
|
|
246
|
+
threadId: string;
|
|
247
|
+
messageId: string;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Remove a thread and all its messages. Its author may do it when every message is theirs. Once someone
|
|
251
|
+
* else replied, a message is erased, or a stored message cannot be read, it needs the moderate scope. A
|
|
252
|
+
* moderator can also remove a thread that cannot be read at all.
|
|
253
|
+
*/
|
|
254
|
+
deleteThread(threadId: string, actor: Actor): {
|
|
255
|
+
threadId: string;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Check who an export or erase is for, and that the actor may. A person may act on their own data: the same
|
|
259
|
+
* kind and name. A principal with the moderate scope may act on anyone's, and then every kind with that name is
|
|
260
|
+
* included. Returns the name and a test for an author. Used by `Suggestions`.
|
|
261
|
+
*/
|
|
262
|
+
checkPerson(person: unknown, actor: Actor): {
|
|
263
|
+
name: string;
|
|
264
|
+
match: (author: Author) => boolean;
|
|
265
|
+
};
|
|
266
|
+
/** The raw stored threads, for the code that must see data a read hides. `list` is missing when the message field is bad. */
|
|
267
|
+
private stored;
|
|
268
|
+
/**
|
|
269
|
+
* What a person wrote in this document: their messages, the status change of theirs that stands, and the
|
|
270
|
+
* edits they made to other people's messages. `unread` counts stored entries with their name that this read
|
|
271
|
+
* cannot show: a bad message, a thread that cannot be read, a message past the read limit, or a copy that
|
|
272
|
+
* lost a merge. When it is not 0 the export is not complete. A person may export their own data.
|
|
273
|
+
*/
|
|
274
|
+
exportPerson(person: string, actor: Actor): PersonExport;
|
|
275
|
+
/**
|
|
276
|
+
* Replace every message a person wrote with a tombstone: the body becomes "[erased]", the author name
|
|
277
|
+
* becomes "erased", and the mentions go. The author kind stays, so machine-made work stays marked. Thread
|
|
278
|
+
* shape, order, and times stay. A status change of theirs, and their name as the editor of another person's
|
|
279
|
+
* message, get the tombstone name. It works on the stored entries, so it reaches a bad message, a message past
|
|
280
|
+
* the read limit, a thread that cannot be read, and every copy that a merge hid. Names inside other people's
|
|
281
|
+
* messages are not touched. Returns how many messages, status changes, and edit stamps it erased.
|
|
282
|
+
*/
|
|
283
|
+
erasePerson(person: string, actor: Actor): {
|
|
284
|
+
messages: number;
|
|
285
|
+
statuses: number;
|
|
286
|
+
edits: number;
|
|
287
|
+
};
|
|
191
288
|
get(threadId: string): Thread;
|
|
192
289
|
/** Threads in the order they were started. A malformed thread is skipped; `problems()` names it. */
|
|
193
290
|
list(filter?: ThreadFilter): Thread[];
|
|
@@ -215,6 +312,17 @@ interface Suggestion {
|
|
|
215
312
|
decidedBy?: Author;
|
|
216
313
|
decidedAt?: string;
|
|
217
314
|
}
|
|
315
|
+
/** What a person wrote and decided among the suggestions of one document. */
|
|
316
|
+
interface PersonSuggestions {
|
|
317
|
+
authored: Suggestion[];
|
|
318
|
+
decided: {
|
|
319
|
+
id: string;
|
|
320
|
+
status: "accepted" | "rejected";
|
|
321
|
+
at: string;
|
|
322
|
+
}[];
|
|
323
|
+
/** Stored entries with this person's name that the export cannot show. Not 0 means it is not complete. */
|
|
324
|
+
unread: number;
|
|
325
|
+
}
|
|
218
326
|
interface ApplyContext {
|
|
219
327
|
suggestionId: string;
|
|
220
328
|
anchor: Anchor;
|
|
@@ -302,6 +410,25 @@ declare class Suggestions {
|
|
|
302
410
|
private index;
|
|
303
411
|
private read;
|
|
304
412
|
private fields;
|
|
413
|
+
/**
|
|
414
|
+
* The suggestions a person wrote, and the decisions they made, in this document. `unread` counts stored
|
|
415
|
+
* suggestions and decision records with their name that this export cannot show: a suggestion that cannot
|
|
416
|
+
* be read, a decision that lost a merge, a refused decision, or a decision with no suggestion. When it is not
|
|
417
|
+
* 0 the export is not complete. A person may export their own data.
|
|
418
|
+
*/
|
|
419
|
+
exportPerson(person: string, actor: Actor): PersonSuggestions;
|
|
420
|
+
private named;
|
|
421
|
+
/**
|
|
422
|
+
* Give every suggestion a person wrote a tombstone author and drop its summary. An accepted one keeps its
|
|
423
|
+
* change, because that change is already part of the content. A pending or rejected one loses its change, and
|
|
424
|
+
* can no longer be accepted. A decision they made gets a tombstone decider. A decision that a read refuses (a
|
|
425
|
+
* principal that decided its own work) is deleted, so erase never turns it into a valid one. Returns how many
|
|
426
|
+
* suggestions and decisions it changed.
|
|
427
|
+
*/
|
|
428
|
+
erasePerson(person: string, actor: Actor): {
|
|
429
|
+
suggestions: number;
|
|
430
|
+
decisions: number;
|
|
431
|
+
};
|
|
305
432
|
/** Propose a change. Anyone can suggest. The host stamps the author. */
|
|
306
433
|
add(input: NewSuggestion, actor: Actor): Suggestion;
|
|
307
434
|
/** A person decides. An agent needs the accept scope, and never decides its own suggestion. */
|
|
@@ -330,4 +457,4 @@ declare class Suggestions {
|
|
|
330
457
|
settle(): void;
|
|
331
458
|
}
|
|
332
459
|
|
|
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
|
|
460
|
+
export { Actor as A, textOf as B, Comments as C, ERASED_BODY as E, type Message as M, type NewReply as N, type PersonExport as P, type RelativePositionJson as R, type Suggestion as S, type Thread as T, type PersonSuggestions as a, type Author as b, Suggestions as c, ACCEPT_SCOPE as d, type Anchor as e, type AnchorKind as f, type AnchorState as g, type ApplyContext as h, type CanvasAnchor as i, type CellAnchor as j, type CommentsOptions as k, ERASED_NAME as l, type EditMessage as m, MODERATE_SCOPE as n, type NewSuggestion as o, type NewThread as p, type RevertContext as q, type SlideAnchor as r, type StampInput as s, type SuggestionFilter as t, type SuggestionHost as u, type TextAnchor as v, type ThreadFilter as w, authorLabel as x, stamp as y, textAnchor as z };
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { Auth, Store, PrincipalContext, Region } from 'agentic-identity';
|
|
3
|
+
import { RequirePrincipalOptions } from 'agentic-identity/http';
|
|
3
4
|
import { D1Like } from 'agentic-identity/cloudflare';
|
|
4
|
-
import { A as Actor } from './suggestions-
|
|
5
|
-
import { C as CommentsService } from './service-
|
|
5
|
+
import { A as Actor } from './suggestions-CnkEcTuY.js';
|
|
6
|
+
import { C as CommentsService } from './service-BvIE6t5n.js';
|
|
6
7
|
import 'yjs';
|
|
7
8
|
|
|
8
9
|
interface WorkerEnv {
|
|
@@ -12,8 +13,10 @@ interface WorkerEnv {
|
|
|
12
13
|
AGENTIC_COMMENTS_UNSAFE_REGION?: string;
|
|
13
14
|
/** D1 database: each document, with its threads, as a Yjs update. */
|
|
14
15
|
DB?: D1Like;
|
|
16
|
+
/** Set to "1" to read `AGENTIC_COMMENTS_TOKENS`. Without it the list never signs anyone in, and a request gets 503 when the list is set. A dev fallback: never set it in production. */
|
|
17
|
+
AGENTIC_COMMENTS_UNSAFE_STATIC_TOKENS?: string;
|
|
15
18
|
/**
|
|
16
|
-
* JSON object: bearer token (32 bytes
|
|
19
|
+
* Dev fallback, read only when `AGENTIC_COMMENTS_UNSAFE_STATIC_TOKENS` is "1". It is tried after the identity credentials. JSON object: bearer token (32 to 512 bytes of A-Z a-z 0-9 _ -) to `kind:name` (the name uses A-Z a-z 0-9 . _ -), or to `{ "as": "kind:name", "scopes": ["comments:write"] }`.
|
|
17
20
|
* Set it as a secret. A short token or a bad entry refuses the whole config.
|
|
18
21
|
*/
|
|
19
22
|
AGENTIC_COMMENTS_TOKENS?: string;
|
|
@@ -25,7 +28,7 @@ interface WorkerEnv {
|
|
|
25
28
|
declare function createTokenAuth(raw: string | undefined): Auth;
|
|
26
29
|
/** The documents, on the `Store` port. Each write loads the document, runs the change, and saves in one transaction. */
|
|
27
30
|
declare class CommentsDocs {
|
|
28
|
-
|
|
31
|
+
readonly store: Store;
|
|
29
32
|
constructor(store: Store);
|
|
30
33
|
private id;
|
|
31
34
|
private load;
|
|
@@ -37,8 +40,13 @@ declare class CommentsDocs {
|
|
|
37
40
|
/** The docs, or a function that makes them from the Worker bindings. */
|
|
38
41
|
type DocsSource = CommentsDocs | ((env: WorkerEnv) => Promise<CommentsDocs>);
|
|
39
42
|
interface AppOptions {
|
|
40
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The credential check. The default reads credentials and principals from the Store of the documents (`createStoreAuth`),
|
|
45
|
+
* then the static list when `AGENTIC_COMMENTS_UNSAFE_STATIC_TOKENS=1`. Pass any `Auth` from agentic-identity to replace it.
|
|
46
|
+
*/
|
|
41
47
|
auth?: Auth | ((env: WorkerEnv) => Auth);
|
|
48
|
+
/** Origins that may send a write with a session cookie. A bearer token needs none. Passed to `requirePrincipal`. */
|
|
49
|
+
origins?: RequirePrincipalOptions["origins"];
|
|
42
50
|
}
|
|
43
51
|
type AppEnv = {
|
|
44
52
|
Bindings: WorkerEnv;
|
package/dist/worker.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CommentsService,
|
|
3
3
|
TEXT_ROOT
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-T77DPNIV.js";
|
|
5
5
|
import {
|
|
6
6
|
ACCEPT_SCOPE,
|
|
7
7
|
CommentsError,
|
|
8
|
-
|
|
8
|
+
MODERATE_SCOPE,
|
|
9
9
|
exact,
|
|
10
10
|
invalid,
|
|
11
11
|
stamp
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-VAVKBAHM.js";
|
|
13
13
|
|
|
14
14
|
// src/worker.ts
|
|
15
15
|
import { Hono } from "hono";
|
|
16
|
-
import { StoreConflictError, assertRegion, isScope, parseRegion, hasScope } from "agentic-identity";
|
|
16
|
+
import { StoreConflictError, assertRegion, createStoreAuth, isScope, parseRegion, hasScope, parsePrincipal } from "agentic-identity";
|
|
17
|
+
import { requirePrincipal } from "agentic-identity/http";
|
|
17
18
|
import { D1_SCHEMA, createD1Store } from "agentic-identity/cloudflare";
|
|
18
19
|
import * as Y from "yjs";
|
|
19
20
|
var READ = "comments:read";
|
|
@@ -22,6 +23,7 @@ var WRITE = "comments:write";
|
|
|
22
23
|
var ADMIN = "comments:admin";
|
|
23
24
|
var DEFAULT_SCOPES = [COMMENT];
|
|
24
25
|
var MIN_TOKEN_BYTES = 32;
|
|
26
|
+
var TOKEN_SHAPE = /^[A-Za-z0-9_-]{1,512}$/;
|
|
25
27
|
var warned = /* @__PURE__ */ new Set();
|
|
26
28
|
function refuse(reason) {
|
|
27
29
|
if (!warned.has(reason)) {
|
|
@@ -40,9 +42,13 @@ function contextOf(entry) {
|
|
|
40
42
|
const who = typeof entry === "string" ? entry : object?.as;
|
|
41
43
|
const cut = typeof who === "string" ? who.indexOf(":") : -1;
|
|
42
44
|
if (typeof who !== "string" || cut < 1 || cut === who.length - 1) return void 0;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
let kind;
|
|
46
|
+
let name;
|
|
47
|
+
try {
|
|
48
|
+
({ kind, id: name } = parsePrincipal({ kind: who.slice(0, cut), id: who.slice(cut + 1) }));
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
46
52
|
const given = object?.scopes;
|
|
47
53
|
if (given !== void 0 && !(Array.isArray(given) && given.every(isScope))) return void 0;
|
|
48
54
|
try {
|
|
@@ -61,6 +67,7 @@ function createTokenAuth(raw) {
|
|
|
61
67
|
for (const [token, entry] of Object.entries(parsed)) {
|
|
62
68
|
const context = contextOf(entry);
|
|
63
69
|
if (new TextEncoder().encode(token).length < MIN_TOKEN_BYTES) throw new Error(`a token is shorter than ${MIN_TOKEN_BYTES} bytes`);
|
|
70
|
+
if (!TOKEN_SHAPE.test(token)) throw new Error("a token holds a character outside A-Z a-z 0-9 _ - or is over 512 bytes");
|
|
64
71
|
if (!context) throw new Error("an entry is not a valid kind:name or { as, scopes }");
|
|
65
72
|
table.set(token, context);
|
|
66
73
|
}
|
|
@@ -80,7 +87,7 @@ function createTokenAuth(raw) {
|
|
|
80
87
|
};
|
|
81
88
|
}
|
|
82
89
|
function actorOf(context) {
|
|
83
|
-
const scopes = hasScope(context.scopes, ADMIN) ? [ACCEPT_SCOPE] : [];
|
|
90
|
+
const scopes = hasScope(context.scopes, ADMIN) ? [ACCEPT_SCOPE, MODERATE_SCOPE] : [];
|
|
84
91
|
return stamp({ kind: context.kind, name: context.principalId, scopes });
|
|
85
92
|
}
|
|
86
93
|
var DOCS = "comments_docs";
|
|
@@ -149,6 +156,7 @@ var STATUS = {
|
|
|
149
156
|
INVALID_INPUT: 400,
|
|
150
157
|
INVALID_ANCHOR: 400,
|
|
151
158
|
NO_THREAD: 404,
|
|
159
|
+
NO_MESSAGE: 404,
|
|
152
160
|
NO_SUGGESTION: 404,
|
|
153
161
|
NO_DOCUMENT: 404,
|
|
154
162
|
THREAD_RESOLVED: 409,
|
|
@@ -185,9 +193,33 @@ function createApp(source, options = {}) {
|
|
|
185
193
|
});
|
|
186
194
|
app2.get("/health", (c) => c.json({ ok: true, pack: "agentic-comments" }));
|
|
187
195
|
app2.use("/api/v1/*", async (c, next) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
196
|
+
let auth;
|
|
197
|
+
if (typeof options.auth === "function") auth = options.auth(c.env ?? {});
|
|
198
|
+
else if (options.auth) auth = options.auth;
|
|
199
|
+
else {
|
|
200
|
+
const staticOn = c.env?.AGENTIC_COMMENTS_UNSAFE_STATIC_TOKENS === "1";
|
|
201
|
+
if (c.env?.AGENTIC_COMMENTS_TOKENS && !staticOn) {
|
|
202
|
+
return fail("NOT_CONFIGURED", "AGENTIC_COMMENTS_TOKENS is a dev fallback. Set AGENTIC_COMMENTS_UNSAFE_STATIC_TOKENS=1 to use it, or remove it and issue agentic-identity credentials", 503);
|
|
203
|
+
}
|
|
204
|
+
const store = (await docsOf(c)).store;
|
|
205
|
+
const auths = [createStoreAuth(store)];
|
|
206
|
+
if (staticOn) auths.push(createTokenAuth(c.env?.AGENTIC_COMMENTS_TOKENS));
|
|
207
|
+
auth = {
|
|
208
|
+
async authenticate(request) {
|
|
209
|
+
for (const each of auths) {
|
|
210
|
+
const found = await each.authenticate(request);
|
|
211
|
+
if (found) return found;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const context = await requirePrincipal(c.req.raw, auth, { origins: options.origins, scope: READ });
|
|
218
|
+
if (context instanceof Response) {
|
|
219
|
+
if (context.status === 401) return Response.json({ error: { code: "UNAUTHENTICATED", message: "send a valid bearer token" } }, { status: 401, headers: { "www-authenticate": "Bearer" } });
|
|
220
|
+
if (context.status === 403) return fail("FORBIDDEN", "this credential may not do this", 403);
|
|
221
|
+
return fail("INTERNAL", "the credential check is unavailable", context.status);
|
|
222
|
+
}
|
|
191
223
|
c.set("context", context);
|
|
192
224
|
await next();
|
|
193
225
|
});
|
|
@@ -227,6 +259,14 @@ function createApp(source, options = {}) {
|
|
|
227
259
|
for (const action of ["resolve", "reopen"]) {
|
|
228
260
|
app2.post(`${doc}/threads/:id/${action}`, (c) => act(c, COMMENT, true, (s) => s[action](c.req.param("id"))));
|
|
229
261
|
}
|
|
262
|
+
app2.patch(`${doc}/threads/:id/messages/:message`, async (c) => {
|
|
263
|
+
const input = await body(c);
|
|
264
|
+
return act(c, COMMENT, true, (s) => s.editMessage(c.req.param("id"), c.req.param("message"), input));
|
|
265
|
+
});
|
|
266
|
+
app2.delete(`${doc}/threads/:id/messages/:message`, (c) => act(c, COMMENT, true, (s) => s.deleteMessage(c.req.param("id"), c.req.param("message"))));
|
|
267
|
+
app2.delete(`${doc}/threads/:id`, (c) => act(c, COMMENT, true, (s) => s.deleteThread(c.req.param("id"))));
|
|
268
|
+
app2.get(`${doc}/people/:name/export`, (c) => act(c, READ, false, (s) => s.exportPerson(c.req.param("name"))));
|
|
269
|
+
app2.delete(`${doc}/people/:name`, (c) => act(c, COMMENT, true, (s) => s.erasePerson(c.req.param("name"))));
|
|
230
270
|
app2.post(`${doc}/threads/:id/relocate`, (c) => act(c, WRITE, true, (s) => s.relocate(c.req.param("id"))));
|
|
231
271
|
app2.get(`${doc}/suggestions`, (c) => act(c, READ, false, (s) => s.suggestions(c.req.query())));
|
|
232
272
|
app2.post(`${doc}/suggestions`, async (c) => {
|
package/llms.txt
CHANGED
|
@@ -7,6 +7,7 @@ Keywords: comments,threads,mentions,suggestions,annotations,collaboration,agents
|
|
|
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
9
|
- Threads live on a Yjs document and merge across replicas.
|
|
10
|
+
- 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.
|
|
10
11
|
- Events: new Notifications(comments, suggestions).subscribe(listener). Ids only, never text.
|
|
11
12
|
- CLI: agentic-comments <command>. MCP: agentic-comments mcp. Both use a local store file.
|
|
12
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Comment threads and suggestions anchored to any content, from people and agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comments",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
79
|
-
"agentic-identity": "^0.
|
|
79
|
+
"agentic-identity": "^0.7.0",
|
|
80
80
|
"hono": "^4.13.14",
|
|
81
81
|
"zod": "^3.25.76"
|
|
82
82
|
},
|
|
@@ -1,7 +0,0 @@
|
|
|
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 };
|