agentic-comments 0.1.0 → 0.3.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 +162 -4
- package/SKILL.md +2 -1
- package/dist/chunk-GMA5I5L6.js +7 -0
- package/dist/chunk-GSAJOCDV.js +768 -0
- package/dist/chunk-IB6ANO32.js +172 -0
- package/dist/chunk-TTTX3R6M.js +86 -0
- package/dist/chunk-YU5U4P22.js +262 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +205 -0
- package/dist/index.d.ts +83 -310
- package/dist/index.js +181 -719
- package/dist/local.d.ts +56 -0
- package/dist/local.js +23 -0
- package/dist/mcp-YERA77HT.js +11 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +40 -0
- package/dist/service-BmMQMT_8.d.ts +66 -0
- package/dist/suggestions-D4ZJFzzR.d.ts +334 -0
- package/dist/worker.d.ts +58 -0
- package/dist/worker.js +287 -0
- package/llms.txt +2 -1
- package/package.json +28 -4
package/README.md
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
Comment threads and suggestions anchored to any content, from people and agents.
|
|
8
8
|
|
|
9
|
-
> Status:
|
|
10
|
-
>
|
|
9
|
+
> Status: see [npm](https://www.npmjs.com/package/agentic-comments) for the latest released version. The
|
|
10
|
+
> `main` branch can have changes that npm does not have yet.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
Requires Node.js
|
|
14
|
+
Requires Node.js 22 or later. `yjs` is a peer dependency. Your app and this package must use the same
|
|
15
|
+
copy of `yjs`, because the package checks the Yjs types of your document. Install both:
|
|
15
16
|
|
|
16
17
|
```sh
|
|
17
18
|
npm install agentic-comments yjs
|
|
@@ -126,6 +127,155 @@ suggestions.accept(s.id, ada);
|
|
|
126
127
|
record is, and it can win settle, so every replica undoes the real edit. The sync layer must decide who
|
|
127
128
|
may write.
|
|
128
129
|
|
|
130
|
+
## CLI and MCP server
|
|
131
|
+
|
|
132
|
+
Both run on a local store: one file that holds a Yjs document. The text is in the `text` root, and
|
|
133
|
+
non-text values are in the `values` map. A file starts with a magic mark and a hash, so a damaged or zero-filled file is refused and not read as empty. A write
|
|
134
|
+
takes a lock file, saves with a rename, and runs again if the file changed meanwhile, so parallel processes
|
|
135
|
+
on one machine lose nothing. A full disk or a file size limit fails the write and leaves the old file. A lock left by a dead process, or by this process under another mark, is cleared. So is an empty lock older
|
|
136
|
+
than two seconds and any lock older than a minute. The store is for one
|
|
137
|
+
machine. It cannot check a lock held in another container or on another machine. Two containers that
|
|
138
|
+
share a volume can have the same process id, and each then clears the other's lock, so they can lose writes. A save that
|
|
139
|
+
would pass 50 MB is refused.
|
|
140
|
+
|
|
141
|
+
If the version you install has no `agentic-comments` command, build from source (see [Develop](#develop)).
|
|
142
|
+
In the cloned repo, run `npm run build` and use `node dist/cli.js` where these examples say
|
|
143
|
+
`npx agentic-comments`.
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
export AGENTIC_COMMENTS_FILE=./comments.ydoc
|
|
147
|
+
export AGENTIC_COMMENTS_AS=human:ada # kind:name. The operator sets this.
|
|
148
|
+
npx agentic-comments init --text "Hello brave world"
|
|
149
|
+
npx agentic-comments comment --text-quote brave --body "Word choice? @scribe"
|
|
150
|
+
npx agentic-comments list --status open
|
|
151
|
+
AGENTIC_COMMENTS_AS=agent_of_human:scribe npx agentic-comments suggest --text-quote brave --replace bold
|
|
152
|
+
npx agentic-comments accept <id>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- Commands: `init`, `list`, `show`, `comment`, `reply`, `resolve`, `reopen`, `relocate`, `suggestions`,
|
|
156
|
+
`suggest`, `accept`, `reject`, `mcp`. Run `agentic-comments --help` for flags.
|
|
157
|
+
- Every result is JSON. An error is JSON on stderr with a `code`, and the exit code is 1. A usage error
|
|
158
|
+
exits 2.
|
|
159
|
+
- The principal comes from `--as` or `AGENTIC_COMMENTS_AS`. Scopes come only from
|
|
160
|
+
`AGENTIC_COMMENTS_SCOPES`, a comma list such as `comments:accept`. No flag takes a scope or an author,
|
|
161
|
+
and a flag that does not apply to a command is refused. `AGENTIC_COMMENTS_LOCK_TIMEOUT_MS` sets the wait
|
|
162
|
+
for a lock, in whole milliseconds.
|
|
163
|
+
- `npx agentic-comments mcp` (or the `agentic-comments-mcp` binary, which reads the same environment) starts
|
|
164
|
+
the MCP server on stdio. The operator fixes the principal and the
|
|
165
|
+
scopes when the server starts. No tool takes an author or a scope. A call with any key a tool does not list is refused, so an agent
|
|
166
|
+
cannot pick its own kind or rights. Tools: `list_threads`, `get_thread`, `add_comment`, `reply_to_thread`, `resolve_thread`,
|
|
167
|
+
`reopen_thread`, `relocate_thread`, `list_suggestions`, `suggest_change`, `accept_suggestion`,
|
|
168
|
+
`reject_suggestion`.
|
|
169
|
+
- A text change is `{"replace": "new text"}`. A change for a cell, slide, or canvas anchor is
|
|
170
|
+
`{"value": <JSON>}`.
|
|
171
|
+
- From code: `import { FileStore, CommentsService, operatorActor } from "agentic-comments/local"`.
|
|
172
|
+
|
|
173
|
+
## HTTP API
|
|
174
|
+
|
|
175
|
+
`agentic-comments/worker` is a Cloudflare Worker (Hono) with REST routes under `/api/v1`. It keeps each
|
|
176
|
+
document in a D1 database through the `Store` port of `agentic-identity`. It checks every request with the
|
|
177
|
+
`Auth` port, so the host picks who the caller is and no request carries an author.
|
|
178
|
+
|
|
179
|
+
Set the principals as a secret. It is JSON from a bearer token (32 bytes or more) to `kind:name`, or to
|
|
180
|
+
`{ "as": "kind:name", "scopes": [...] }`. One bad entry refuses the whole table. A request with no valid
|
|
181
|
+
token gets `401`.
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
wrangler secret put AGENTIC_COMMENTS_TOKENS
|
|
185
|
+
# {"<openssl rand -hex 32>": {"as": "human:ada", "scopes": ["comments:write"]}, "<another>": "agent_of_human:scribe"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Also set `AGENTIC_COMMENTS_REGION` to `eu` or `us`. Without it every document route fails with `500 MISCONFIGURED`. Use `unknown` with
|
|
189
|
+
`AGENTIC_COMMENTS_UNSAFE_REGION=1` only under `wrangler dev`. The `wrangler.jsonc` in the repo shows the D1 binding.
|
|
190
|
+
|
|
191
|
+
| Scope | Allows |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `comments:read` | Read documents, threads, and suggestions. |
|
|
194
|
+
| `comments:comment` | Also add threads, reply, resolve, reopen, and suggest. This is the default for a token with no scopes. |
|
|
195
|
+
| `comments:write` | Also create documents, relocate, and accept or reject. |
|
|
196
|
+
| `comments:admin` | Also let an agent accept or reject (the `comments:accept` right). |
|
|
197
|
+
|
|
198
|
+
| Route | Action |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `GET /api/v1/me` | Who the token stands for. |
|
|
201
|
+
| `POST /api/v1/docs` `{id, text?}` | Create a document. |
|
|
202
|
+
| `GET /api/v1/docs/:doc` | Read the content text. |
|
|
203
|
+
| `GET`, `POST /api/v1/docs/:doc/threads` | List (`?status=`, `?anchorState=`, `?mentioning=`) or add. |
|
|
204
|
+
| `GET /api/v1/docs/:doc/threads/:id` | Read one thread. |
|
|
205
|
+
| `POST /api/v1/docs/:doc/threads/:id/replies` | Reply. |
|
|
206
|
+
| `POST .../threads/:id/resolve`, `reopen`, `relocate` | Change a thread. |
|
|
207
|
+
| `GET`, `POST /api/v1/docs/:doc/suggestions` | List or suggest. |
|
|
208
|
+
| `POST .../suggestions/:id/accept`, `reject` | Decide. An agent never decides its own suggestion. |
|
|
209
|
+
|
|
210
|
+
Bodies are the same as the MCP tool inputs. An error is `{"error": {"code", "message"}}`. A body with an
|
|
211
|
+
`author` key is refused with `400`.
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
curl -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
|
|
215
|
+
-d '{"anchor":{"type":"text","quote":"brave"},"body":"Word choice?"}' \
|
|
216
|
+
http://127.0.0.1:8787/api/v1/docs/d1/threads
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Live demo
|
|
220
|
+
|
|
221
|
+
A demo Worker runs at <https://agentic-comments-demo.hello-fd1.workers.dev>. It is a staging site. Check it with
|
|
222
|
+
`GET /health`. Every `/api/v1` route needs a bearer token, and the demo does not publish one, so a call
|
|
223
|
+
with no token gets `401`. The data lives in a D1 database in the EU (`weur`), and the Worker sets the
|
|
224
|
+
region to `eu`.
|
|
225
|
+
|
|
226
|
+
To deploy your own copy, log in with `wrangler login` or set `CLOUDFLARE_API_TOKEN`, then:
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
npx wrangler d1 create agentic-comments-demo --location weur # once. Put the id in wrangler.demo.jsonc.
|
|
230
|
+
npx wrangler secret put AGENTIC_COMMENTS_TOKENS --config wrangler.demo.jsonc # see HTTP API
|
|
231
|
+
npm run deploy:demo
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`wrangler.demo.jsonc` sets no secret. Deploys run by hand, and no CI job holds a Cloudflare token.
|
|
235
|
+
|
|
236
|
+
## Notifications
|
|
237
|
+
|
|
238
|
+
`Notifications` turns changes into events that your app can send to a notifier. It watches the data, so it
|
|
239
|
+
sees changes from other replicas too. It replays nothing from before you created it. A first load into an
|
|
240
|
+
empty document looks like new changes, so create the stream with `muted: true` and call `unmute()` after
|
|
241
|
+
that load.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { Notifications } from "agentic-comments";
|
|
245
|
+
|
|
246
|
+
const notifications = new Notifications(comments, suggestions, { muted: true, onError: (error, source) => report(error, source) });
|
|
247
|
+
// ...load the document or connect the provider, then:
|
|
248
|
+
notifications.unmute();
|
|
249
|
+
const stop = notifications.subscribe(async (event) => {
|
|
250
|
+
if (event.type === "mention") await notify(event.mention, event.threadId); // fetch the text with the ids
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- Events: `thread.created`, `message.added`, `mention` (one per handle), `thread.resolved`,
|
|
255
|
+
`thread.reopened`, `suggestion.created`, `suggestion.accepted`, `suggestion.rejected`.
|
|
256
|
+
- An event holds ids, the actor, `machineMade`, the anchor type, `source` (`local` or `remote`), and a time.
|
|
257
|
+
It never holds the text of a comment, a suggestion, or a change. Fetch that with the ids.
|
|
258
|
+
- The same change has the same `id` on every replica. So a notifier that must
|
|
259
|
+
send once keeps the ids it has sent. Within one replica, an id is delivered at most once, and a listener
|
|
260
|
+
that throws is not retried. A replica remembers its last 10,000 ids (`seenLimit`) and drops a
|
|
261
|
+
repeat within that window. Two accepts at once give one id, and the actor can differ between replicas. A `suggestion.rejected` event
|
|
262
|
+
can be overturned: if another replica accepted at the same time, the merge ends as accepted, and a
|
|
263
|
+
`suggestion.accepted` event follows.
|
|
264
|
+
An id for a status change includes the clock time, so a clock coarser than the changes can merge two.
|
|
265
|
+
- Use `source` to decide where to send. If every replica runs a notifier, filter on `local`, or each
|
|
266
|
+
replica sends its own copy of every remote change.
|
|
267
|
+
- Events are delivered after the Yjs transaction that caused them has finished, so a listener can write to
|
|
268
|
+
the document. A listener that throws, or returns a rejected promise, cannot break a write or another
|
|
269
|
+
listener. The
|
|
270
|
+
error goes to `onError`, or to `errors()`.
|
|
271
|
+
- Write a thread in one Yjs transaction, as `add` does. A thread whose map arrives empty and gets its fields
|
|
272
|
+
and messages in one later transaction is treated as created then. A thread built in more steps than that
|
|
273
|
+
can make no event.
|
|
274
|
+
- A peer that deletes a thread or a suggestion and adds it back under the same id makes a `created` event
|
|
275
|
+
again once the id has left the `seenLimit` window.
|
|
276
|
+
- A change that arrives as a whole thread, with its replies and status inside, makes an event for each
|
|
277
|
+
message and for a resolved status. A reopen inside such a thread makes none. A message past the thread's read limit makes no event.
|
|
278
|
+
|
|
129
279
|
## Author kinds
|
|
130
280
|
|
|
131
281
|
| Kind | Meaning | Label |
|
|
@@ -138,7 +288,7 @@ suggestions.accept(s.id, ada);
|
|
|
138
288
|
|
|
139
289
|
## Limits
|
|
140
290
|
|
|
141
|
-
-
|
|
291
|
+
- The file store is for one machine. It is not a sync server. The Worker keeps each document as one D1 row and refuses a document past 600,000 bytes of state.
|
|
142
292
|
- `add` checks that a text anchor points into this document and matches its quote.
|
|
143
293
|
- The library trusts the host to call `stamp()` only for a principal it authenticated.
|
|
144
294
|
- A reply to a resolved thread is refused on the replica that sees the resolve. A reply and a
|
|
@@ -149,6 +299,14 @@ suggestions.accept(s.id, ada);
|
|
|
149
299
|
on every read, so keep it fast.
|
|
150
300
|
- A local reply stops at 1,000 messages. Replicas that reply at once can pass that. A read shows the first
|
|
151
301
|
2,000 valid messages. `skipped` and `problems()` report the rest.
|
|
302
|
+
- If several processes clear one stale lock at the same moment, two writes can very rarely overlap. The
|
|
303
|
+
save check catches almost every such case. A symlink as the store path is followed. A filesystem with no
|
|
304
|
+
hard links makes `init` use a check and a rename, which can race.
|
|
305
|
+
- The CLI trusts its caller: a process that can set `AGENTIC_COMMENTS_AS` and `AGENTIC_COMMENTS_SCOPES` can
|
|
306
|
+
act as anyone. Give an agent the MCP server, where the operator fixes both. Do not give it a shell with
|
|
307
|
+
the store file and the CLI.
|
|
308
|
+
- Events come from the data and not from the calls, so a status change that two replicas make at once gives
|
|
309
|
+
each replica its own event, and a thread that arrives whole gives events for its current state only.
|
|
152
310
|
- Every replica of a document must register the same custom anchor kinds.
|
|
153
311
|
- A repeated message id hides every copy. A peer that appends a copy of a thread's only message makes
|
|
154
312
|
the thread unreadable.
|
package/SKILL.md
CHANGED
|
@@ -25,7 +25,8 @@ Check `thread.anchorState`. An `orphaned` thread has lost its content. Read it,
|
|
|
25
25
|
Suggest a change with `suggestions.add({ anchor, change, summary }, actor)`. Accept needs a person or
|
|
26
26
|
the `comments:accept` scope. You never accept or reject your own suggestion.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Over MCP, call `list_threads`, `add_comment`, `reply_to_thread`, `resolve_thread`, `suggest_change`,
|
|
29
|
+
`accept_suggestion`, and `reject_suggestion`. You never pass an author or a scope.
|
|
29
30
|
|
|
30
31
|
## Rules
|
|
31
32
|
|