agentic-comments 0.1.0 → 0.2.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 +99 -4
- package/SKILL.md +2 -1
- package/dist/chunk-C3P5TAC3.js +770 -0
- package/dist/chunk-C43HLFQE.js +262 -0
- package/dist/chunk-RWMBGIAS.js +7 -0
- package/dist/chunk-UE5ULEXU.js +86 -0
- package/dist/chunk-ZCM2UDLN.js +172 -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 +112 -0
- package/dist/local.js +23 -0
- package/dist/mcp-BOJM5NE6.js +11 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +21 -0
- package/dist/suggestions-U8tXRLZ5.d.ts +334 -0
- package/llms.txt +2 -1
- package/package.json +19 -3
package/dist/local.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { b as Actor, s as Thread, M as Message, p as Suggestion, r as SuggestionHost } from './suggestions-U8tXRLZ5.js';
|
|
3
|
+
|
|
4
|
+
/** The most bytes a saved document holds. A save over this is refused, so a file never grows past what a load accepts. */
|
|
5
|
+
declare const MAX_FILE: number;
|
|
6
|
+
interface FileStoreOptions {
|
|
7
|
+
/** How long a write waits for another process. A whole number of ms, 0 or more. Default 5,000. */
|
|
8
|
+
lockTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Read the lock timeout from `AGENTIC_COMMENTS_LOCK_TIMEOUT_MS`. Anything but a whole number of ms is an error. */
|
|
11
|
+
declare function lockTimeoutFrom(env: Record<string, string | undefined>): number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* One file that holds a Yjs document, with a hash so damage is seen. A write takes a lock file, loads the
|
|
14
|
+
* newest state, runs, and saves with a rename. If the file changed while it ran, it runs again on the new
|
|
15
|
+
* state. This is built for processes on one machine that share one process table. A lock held across
|
|
16
|
+
* containers or machines cannot be checked for a dead holder.
|
|
17
|
+
*/
|
|
18
|
+
declare class FileStore {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
private readonly lockTimeoutMs;
|
|
21
|
+
constructor(path: string, options?: FileStoreOptions);
|
|
22
|
+
/** Create the file. `seed` fills the document before the first save. Refuses to overwrite, even when two run at once. */
|
|
23
|
+
create(seed?: (ydoc: Y.Doc) => void): void;
|
|
24
|
+
private load;
|
|
25
|
+
/** Read the newest saved state. Anything `fn` changes is not saved. */
|
|
26
|
+
read<T>(fn: (ydoc: Y.Doc) => T): T;
|
|
27
|
+
private acquire;
|
|
28
|
+
/**
|
|
29
|
+
* Clear a lock whose holder is dead (or is this process, with another mark), an empty one older than a
|
|
30
|
+
* short time, or any one more than a minute old or a minute in the future. The lock moves to a unique name
|
|
31
|
+
* first, so only one process clears it. If the moved lock is not the one we looked at, it goes back.
|
|
32
|
+
*/
|
|
33
|
+
private clearStale;
|
|
34
|
+
/** Lock, load the newest state, run `fn`, and save. When `fn` throws, nothing is saved. */
|
|
35
|
+
write<T>(fn: (ydoc: Y.Doc) => T): T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Where a comment or suggestion points, in terms a person or agent can write down. */
|
|
39
|
+
type AnchorSpec = {
|
|
40
|
+
type: "text";
|
|
41
|
+
quote: string;
|
|
42
|
+
occurrence?: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: "cell";
|
|
45
|
+
key: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: "slide";
|
|
48
|
+
elementId: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: "canvas";
|
|
51
|
+
elementId: string;
|
|
52
|
+
};
|
|
53
|
+
interface ListFilter {
|
|
54
|
+
status?: string;
|
|
55
|
+
anchorState?: string;
|
|
56
|
+
mentioning?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every action the CLI and MCP server offer, on a local file store. Each call takes the actor the operator
|
|
60
|
+
* stamped, and no input carries an author, so both faces return the same shapes and obey the same rules.
|
|
61
|
+
*/
|
|
62
|
+
declare class CommentsService {
|
|
63
|
+
private readonly store;
|
|
64
|
+
private readonly actor;
|
|
65
|
+
constructor(store: FileStore, actor: Actor);
|
|
66
|
+
private open;
|
|
67
|
+
private read;
|
|
68
|
+
private write;
|
|
69
|
+
threads(filter?: ListFilter): Thread[];
|
|
70
|
+
thread(id: string): Thread;
|
|
71
|
+
comment(input: {
|
|
72
|
+
anchor: AnchorSpec;
|
|
73
|
+
body: string;
|
|
74
|
+
mentions?: string[];
|
|
75
|
+
}): Thread;
|
|
76
|
+
reply(threadId: string, input: {
|
|
77
|
+
body: string;
|
|
78
|
+
mentions?: string[];
|
|
79
|
+
}): Message;
|
|
80
|
+
resolve(threadId: string): Thread;
|
|
81
|
+
reopen(threadId: string): Thread;
|
|
82
|
+
relocate(threadId: string): Thread;
|
|
83
|
+
suggestions(filter?: ListFilter): Suggestion[];
|
|
84
|
+
suggest(input: {
|
|
85
|
+
anchor: AnchorSpec;
|
|
86
|
+
change: unknown;
|
|
87
|
+
summary?: string;
|
|
88
|
+
}): Suggestion;
|
|
89
|
+
accept(suggestionId: string): Suggestion;
|
|
90
|
+
reject(suggestionId: string): Suggestion;
|
|
91
|
+
/** The content text of the store. */
|
|
92
|
+
text(): string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The root Y.Text that a local store keeps its content in. */
|
|
96
|
+
declare const TEXT_ROOT = "text";
|
|
97
|
+
/** The root Y.Map that holds values for cell, slide, canvas, and custom anchors. */
|
|
98
|
+
declare const VALUES_ROOT = "values";
|
|
99
|
+
/**
|
|
100
|
+
* The host for a local store. A text anchor takes `{ "replace": "text" }` and swaps the anchored range.
|
|
101
|
+
* Any other anchor takes `{ "value": <JSON> }` and sets that key in the values map.
|
|
102
|
+
*/
|
|
103
|
+
declare const localHost: SuggestionHost;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stamp the principal the operator set. `as` is `kind:name`, from a flag the operator wrote or from
|
|
107
|
+
* `AGENTIC_COMMENTS_AS`. Scopes come only from `AGENTIC_COMMENTS_SCOPES`, a comma list. A tool input
|
|
108
|
+
* never reaches this function, so an agent that calls a tool cannot pick its own kind or scopes.
|
|
109
|
+
*/
|
|
110
|
+
declare function operatorActor(as: string | undefined, scopes: string | undefined): Actor;
|
|
111
|
+
|
|
112
|
+
export { type AnchorSpec, CommentsService, FileStore, type FileStoreOptions, type ListFilter, MAX_FILE, TEXT_ROOT, VALUES_ROOT, localHost, lockTimeoutFrom, operatorActor };
|
package/dist/local.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileStore,
|
|
3
|
+
MAX_FILE,
|
|
4
|
+
lockTimeoutFrom,
|
|
5
|
+
operatorActor
|
|
6
|
+
} from "./chunk-C43HLFQE.js";
|
|
7
|
+
import {
|
|
8
|
+
CommentsService,
|
|
9
|
+
TEXT_ROOT,
|
|
10
|
+
VALUES_ROOT,
|
|
11
|
+
localHost
|
|
12
|
+
} from "./chunk-ZCM2UDLN.js";
|
|
13
|
+
import "./chunk-C3P5TAC3.js";
|
|
14
|
+
export {
|
|
15
|
+
CommentsService,
|
|
16
|
+
FileStore,
|
|
17
|
+
MAX_FILE,
|
|
18
|
+
TEXT_ROOT,
|
|
19
|
+
VALUES_ROOT,
|
|
20
|
+
localHost,
|
|
21
|
+
lockTimeoutFrom,
|
|
22
|
+
operatorActor
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/mcp-bin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
FileStore,
|
|
4
|
+
lockTimeoutFrom,
|
|
5
|
+
operatorActor
|
|
6
|
+
} from "./chunk-C43HLFQE.js";
|
|
7
|
+
import {
|
|
8
|
+
startMcp
|
|
9
|
+
} from "./chunk-UE5ULEXU.js";
|
|
10
|
+
import "./chunk-RWMBGIAS.js";
|
|
11
|
+
import "./chunk-ZCM2UDLN.js";
|
|
12
|
+
import "./chunk-C3P5TAC3.js";
|
|
13
|
+
|
|
14
|
+
// src/mcp-bin.ts
|
|
15
|
+
try {
|
|
16
|
+
const env = process.env;
|
|
17
|
+
await startMcp(new FileStore(env.AGENTIC_COMMENTS_FILE ?? "./comments.ydoc", { lockTimeoutMs: lockTimeoutFrom(env) }), operatorActor(env.AGENTIC_COMMENTS_AS, env.AGENTIC_COMMENTS_SCOPES));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(JSON.stringify({ error: { code: error.code ?? "INTERNAL", message: error instanceof Error ? error.message : String(error) } }));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
|
|
3
|
+
/** A Yjs relative position as JSON. It names an item, not an index, so it follows its text. */
|
|
4
|
+
interface RelativePositionJson {
|
|
5
|
+
type?: {
|
|
6
|
+
client: number;
|
|
7
|
+
clock: number;
|
|
8
|
+
};
|
|
9
|
+
tname?: string;
|
|
10
|
+
item?: {
|
|
11
|
+
client: number;
|
|
12
|
+
clock: number;
|
|
13
|
+
};
|
|
14
|
+
assoc?: number;
|
|
15
|
+
}
|
|
16
|
+
interface TextAnchor {
|
|
17
|
+
type: "text";
|
|
18
|
+
/** The text under the range when the comment was made. `Comments.add` checks it against the document. */
|
|
19
|
+
text: string;
|
|
20
|
+
start: RelativePositionJson;
|
|
21
|
+
end: RelativePositionJson;
|
|
22
|
+
}
|
|
23
|
+
interface CellAnchor {
|
|
24
|
+
type: "cell";
|
|
25
|
+
/** A cell key the host app defines, such as `Sheet1!B4` or a stable cell id. */
|
|
26
|
+
key: string;
|
|
27
|
+
}
|
|
28
|
+
interface SlideAnchor {
|
|
29
|
+
type: "slide";
|
|
30
|
+
elementId: string;
|
|
31
|
+
}
|
|
32
|
+
interface CanvasAnchor {
|
|
33
|
+
type: "canvas";
|
|
34
|
+
elementId: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Where a thread points. A custom kind (see `AnchorKind`) returns its own shape, so narrow on `type`
|
|
38
|
+
* before you read its fields.
|
|
39
|
+
*/
|
|
40
|
+
type Anchor = TextAnchor | CellAnchor | SlideAnchor | CanvasAnchor;
|
|
41
|
+
/** Teach `Comments` a new anchor type. `parse` returns a clean copy or throws. */
|
|
42
|
+
interface AnchorKind {
|
|
43
|
+
type: string;
|
|
44
|
+
parse(raw: Record<string, unknown>): {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether the content under an anchor is still there.
|
|
50
|
+
* `attached`: yes, as it was. `edited`: the range is there, with other text. `orphaned`: the content is
|
|
51
|
+
* gone. `unknown`: nothing can tell, as for a cell with no host answer.
|
|
52
|
+
*/
|
|
53
|
+
type AnchorState = {
|
|
54
|
+
state: "attached";
|
|
55
|
+
} | {
|
|
56
|
+
state: "edited";
|
|
57
|
+
currentText: string;
|
|
58
|
+
} | {
|
|
59
|
+
state: "orphaned";
|
|
60
|
+
} | {
|
|
61
|
+
state: "unknown";
|
|
62
|
+
};
|
|
63
|
+
/** The text of a Yjs text type. An embed counts as one character, so indexes match Yjs positions. */
|
|
64
|
+
declare function textOf(type: Y.Text | Y.XmlText): string;
|
|
65
|
+
/** Pin a range of a Yjs text. Later edits by any replica do not move the anchor off its text. */
|
|
66
|
+
declare function textAnchor(type: Y.Text | Y.XmlText, from: number, to: number): TextAnchor;
|
|
67
|
+
|
|
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
|
+
interface Message {
|
|
100
|
+
id: string;
|
|
101
|
+
at: string;
|
|
102
|
+
author: Author;
|
|
103
|
+
body: string;
|
|
104
|
+
/** Handles named in the body or listed on the input, without the `@`. */
|
|
105
|
+
mentions: string[];
|
|
106
|
+
}
|
|
107
|
+
interface Thread {
|
|
108
|
+
id: string;
|
|
109
|
+
anchor: Anchor;
|
|
110
|
+
status: "open" | "resolved";
|
|
111
|
+
/**
|
|
112
|
+
* The status change that won, and when. For a new thread, who opened it. Replicas that change the
|
|
113
|
+
* status at once agree on one change. Yjs picks it by client id, not by time.
|
|
114
|
+
*/
|
|
115
|
+
statusBy: Author;
|
|
116
|
+
statusAt: string;
|
|
117
|
+
messages: Message[];
|
|
118
|
+
/** Whether the anchored content is still there. A thread whose content is gone stays readable. */
|
|
119
|
+
anchorState: AnchorState;
|
|
120
|
+
/** How many stored entries this read did not show: invalid ones, repeated ids, and valid ones past the read limit. `problems()` names them. */
|
|
121
|
+
skipped: number;
|
|
122
|
+
}
|
|
123
|
+
interface CommentsOptions {
|
|
124
|
+
now?: () => Date;
|
|
125
|
+
/** More anchor types. A type that is already built in is refused. */
|
|
126
|
+
anchors?: AnchorKind[];
|
|
127
|
+
/**
|
|
128
|
+
* Tell whether the content a cell, slide, canvas, or custom anchor points at still exists. Return `true`
|
|
129
|
+
* or `false`. Any other answer, or a throw, means unknown. Text anchors do not need it.
|
|
130
|
+
*/
|
|
131
|
+
exists?: (anchor: Anchor) => boolean | undefined;
|
|
132
|
+
}
|
|
133
|
+
interface NewThread {
|
|
134
|
+
anchor: Anchor;
|
|
135
|
+
body: string;
|
|
136
|
+
mentions?: string[];
|
|
137
|
+
}
|
|
138
|
+
interface NewReply {
|
|
139
|
+
body: string;
|
|
140
|
+
mentions?: string[];
|
|
141
|
+
}
|
|
142
|
+
interface ThreadFilter {
|
|
143
|
+
status?: Thread["status"];
|
|
144
|
+
anchorType?: string;
|
|
145
|
+
/** A handle, with or without the `@`. */
|
|
146
|
+
mentioning?: string;
|
|
147
|
+
anchorState?: AnchorState["state"];
|
|
148
|
+
}
|
|
149
|
+
/** Comment threads on a Yjs document. Replies and status merge across replicas. */
|
|
150
|
+
declare class Comments {
|
|
151
|
+
readonly ydoc: Y.Doc;
|
|
152
|
+
private readonly kinds;
|
|
153
|
+
private readonly now;
|
|
154
|
+
private readonly exists?;
|
|
155
|
+
/** Every replica of a document must register the same custom anchor kinds. Others read those threads as malformed. */
|
|
156
|
+
constructor(ydoc: Y.Doc, options?: CommentsOptions);
|
|
157
|
+
private get map();
|
|
158
|
+
/** The Yjs map that holds the threads. `Notifications` watches it. */
|
|
159
|
+
get threadsMap(): Y.Map<Y.Map<unknown>>;
|
|
160
|
+
/** The clock, as an ISO time. Used by `Suggestions`. */
|
|
161
|
+
time(): string;
|
|
162
|
+
/** Whether the content under an anchor is still there. This never throws. */
|
|
163
|
+
anchorState(anchor: Anchor): AnchorState;
|
|
164
|
+
/** Read an anchor from stored data. This does not check the document. */
|
|
165
|
+
parseAnchor(raw: unknown): Anchor;
|
|
166
|
+
/** Parse an anchor from input and check it against this document. Throws `INVALID_ANCHOR` when it fails. */
|
|
167
|
+
checkAnchor(raw: unknown): Anchor;
|
|
168
|
+
/** A text anchor must name text in this document, in order, and match its quote. */
|
|
169
|
+
private checkInDocument;
|
|
170
|
+
/** Build a message, and refuse it unless a read would show it back. Nothing is written yet. */
|
|
171
|
+
private message;
|
|
172
|
+
private readMessage;
|
|
173
|
+
/** A thread with a bad anchor or status is malformed. A bad message is skipped and named in `issues`. */
|
|
174
|
+
private read;
|
|
175
|
+
private fields;
|
|
176
|
+
/** Start a thread with its first message. The anchor and body are checked before anything is written. */
|
|
177
|
+
add(input: NewThread, actor: Actor): Thread;
|
|
178
|
+
/**
|
|
179
|
+
* Add a message. The resolved check runs on this replica only. A reply and a resolve made at once
|
|
180
|
+
* on two replicas both stay, and the merged thread is resolved with the reply in it.
|
|
181
|
+
*/
|
|
182
|
+
reply(threadId: string, input: NewReply, actor: Actor): Message;
|
|
183
|
+
private setStatus;
|
|
184
|
+
/**
|
|
185
|
+
* Move a text anchor whose range is gone or edited to its quote, when the quote occurs exactly once in
|
|
186
|
+
* the same text. There is no fuzzy match. Throws `RELOCATE_FAILED` when it cannot say where. An attached
|
|
187
|
+
* thread is returned as it is.
|
|
188
|
+
*/
|
|
189
|
+
relocate(threadId: string, actor: Actor): Thread;
|
|
190
|
+
resolve(threadId: string, actor: Actor): Thread;
|
|
191
|
+
reopen(threadId: string, actor: Actor): Thread;
|
|
192
|
+
get(threadId: string): Thread;
|
|
193
|
+
/** Threads in the order they were started. A malformed thread is skipped; `problems()` names it. */
|
|
194
|
+
list(filter?: ThreadFilter): Thread[];
|
|
195
|
+
/** Threads or messages that could not be read, for example from a replica that wrote bad data. */
|
|
196
|
+
problems(): {
|
|
197
|
+
id: string;
|
|
198
|
+
reason: string;
|
|
199
|
+
}[];
|
|
200
|
+
private readAll;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The scope that lets an agent accept or reject a suggestion that another principal wrote. */
|
|
204
|
+
declare const ACCEPT_SCOPE = "comments:accept";
|
|
205
|
+
interface Suggestion {
|
|
206
|
+
id: string;
|
|
207
|
+
anchor: Anchor;
|
|
208
|
+
/** Whether the anchored content is still there. Accept refuses an orphaned or edited one. */
|
|
209
|
+
anchorState: AnchorState;
|
|
210
|
+
/** What the host app proposes. This package never reads it. The host's `apply` does. */
|
|
211
|
+
change: unknown;
|
|
212
|
+
summary?: string;
|
|
213
|
+
author: Author;
|
|
214
|
+
createdAt: string;
|
|
215
|
+
status: "pending" | "accepted" | "rejected";
|
|
216
|
+
decidedBy?: Author;
|
|
217
|
+
decidedAt?: string;
|
|
218
|
+
}
|
|
219
|
+
interface ApplyContext {
|
|
220
|
+
suggestionId: string;
|
|
221
|
+
anchor: Anchor;
|
|
222
|
+
/** Who wrote the suggestion. */
|
|
223
|
+
author: Author;
|
|
224
|
+
/** Who accepts it. Label the edit with this author. */
|
|
225
|
+
by: Author;
|
|
226
|
+
ydoc: Y.Doc;
|
|
227
|
+
}
|
|
228
|
+
interface RevertContext {
|
|
229
|
+
suggestionId: string;
|
|
230
|
+
anchor: Anchor;
|
|
231
|
+
/** Who wrote the suggestion. */
|
|
232
|
+
author: Author;
|
|
233
|
+
/** Who made the application being undone. */
|
|
234
|
+
by: Author;
|
|
235
|
+
ydoc: Y.Doc;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The host app's side. Each hook is synchronous, so the check and the edit stay in one step.
|
|
239
|
+
* Make every edit in `ydoc`, so it merges with the rest.
|
|
240
|
+
*/
|
|
241
|
+
interface SuggestionHost {
|
|
242
|
+
/** Check a change when it is suggested, with the anchor it is for. Return the clean change, or throw. */
|
|
243
|
+
parse?(change: unknown, context: {
|
|
244
|
+
anchor: Anchor;
|
|
245
|
+
}): unknown;
|
|
246
|
+
/**
|
|
247
|
+
* Make the edit. Throw before you change anything when the edit cannot apply, for example when the
|
|
248
|
+
* anchored content changed. Yjs cannot undo a partial edit. If `apply` edits and then fails, the
|
|
249
|
+
* decision is recorded as accepted with no receipt, and `problems()` reports it. Return a receipt,
|
|
250
|
+
* JSON data that lets `revert` undo this edit.
|
|
251
|
+
*/
|
|
252
|
+
apply(change: unknown, context: ApplyContext): unknown;
|
|
253
|
+
/**
|
|
254
|
+
* Two replicas can accept one suggestion before they meet. Each applies the edit. After a merge one
|
|
255
|
+
* application stays and `revert` undoes each other. Every replica runs it at the same time, and the
|
|
256
|
+
* results must merge to one outcome, as a Yjs delete of the same range does. Throw before you change
|
|
257
|
+
* anything. A receipt comes from stored data. A peer that can write comment data can choose it, so
|
|
258
|
+
* check it before you use it. The anchor and the author are stored data too, so a check against them
|
|
259
|
+
* helps against a mistake and not against a peer that can write comment data.
|
|
260
|
+
*/
|
|
261
|
+
revert?(receipt: unknown, context: RevertContext): void;
|
|
262
|
+
/** Say that applying the same change twice is harmless. Then `revert` is not needed, and a duplicate is not undone. */
|
|
263
|
+
idempotent?: boolean;
|
|
264
|
+
}
|
|
265
|
+
interface NewSuggestion {
|
|
266
|
+
anchor: Anchor;
|
|
267
|
+
change: unknown;
|
|
268
|
+
summary?: string;
|
|
269
|
+
}
|
|
270
|
+
interface SuggestionFilter {
|
|
271
|
+
status?: Suggestion["status"];
|
|
272
|
+
anchorState?: AnchorState["state"];
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Suggested changes to content, from people and agents. Nothing changes until a person, or an agent
|
|
276
|
+
* with the accept scope that did not write it, accepts. Accept and reject are safe across replicas.
|
|
277
|
+
*/
|
|
278
|
+
declare class Suggestions {
|
|
279
|
+
private readonly comments;
|
|
280
|
+
private readonly host;
|
|
281
|
+
private readonly onTransaction;
|
|
282
|
+
private failures;
|
|
283
|
+
constructor(comments: Comments, host: SuggestionHost);
|
|
284
|
+
/** Stop listening to the document. */
|
|
285
|
+
close(): void;
|
|
286
|
+
private get ydoc();
|
|
287
|
+
private get map();
|
|
288
|
+
/** The three Yjs maps that hold suggestions and their decisions. `Notifications` watches them. */
|
|
289
|
+
get maps(): {
|
|
290
|
+
suggestions: Y.Map<Y.Map<unknown>>;
|
|
291
|
+
applied: Y.Map<unknown>;
|
|
292
|
+
rejected: Y.Map<unknown>;
|
|
293
|
+
};
|
|
294
|
+
private get applied();
|
|
295
|
+
private get rejected();
|
|
296
|
+
private change;
|
|
297
|
+
/**
|
|
298
|
+
* Records of one kind for one suggestion, in client order. A record that is not valid is not counted:
|
|
299
|
+
* one with no author or time, a receipt that is not small JSON, or a decider who decided their own work.
|
|
300
|
+
*/
|
|
301
|
+
private records;
|
|
302
|
+
/** Group the entries of a decision map by suggestion id in one pass, so a read does not scan the map per suggestion. */
|
|
303
|
+
private index;
|
|
304
|
+
private read;
|
|
305
|
+
private fields;
|
|
306
|
+
/** Propose a change. Anyone can suggest. The host stamps the author. */
|
|
307
|
+
add(input: NewSuggestion, actor: Actor): Suggestion;
|
|
308
|
+
/** A person decides. An agent needs the accept scope, and never decides its own suggestion. */
|
|
309
|
+
private mayDecide;
|
|
310
|
+
/**
|
|
311
|
+
* Apply the change as the accepting author and record the decision, in one step. Accepting an
|
|
312
|
+
* accepted suggestion changes nothing. When `apply` throws, the suggestion stays pending.
|
|
313
|
+
*/
|
|
314
|
+
accept(suggestionId: string, actor: Actor): Suggestion;
|
|
315
|
+
/** Same right as accept. A person can withdraw their own suggestion. */
|
|
316
|
+
reject(suggestionId: string, actor: Actor): Suggestion;
|
|
317
|
+
get(suggestionId: string): Suggestion;
|
|
318
|
+
/** In the order they were made. A malformed suggestion is skipped, and `problems()` names it. */
|
|
319
|
+
list(filter?: SuggestionFilter): Suggestion[];
|
|
320
|
+
/** Suggestions that could not be read, and reverts that failed. */
|
|
321
|
+
problems(): {
|
|
322
|
+
id: string;
|
|
323
|
+
reason: string;
|
|
324
|
+
}[];
|
|
325
|
+
private readAll;
|
|
326
|
+
/**
|
|
327
|
+
* Two replicas can accept one suggestion before they meet. After a merge, the application of the
|
|
328
|
+
* lowest client id stays, and the host reverts each other one. Every replica reaches the same result.
|
|
329
|
+
* A revert that throws leaves its record, so the next merge tries again.
|
|
330
|
+
*/
|
|
331
|
+
settle(): void;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export { type Author as A, Comments as C, type Message as M, type NewReply as N, PRINCIPAL_KINDS as P, type RelativePositionJson as R, Suggestions as S, type TextAnchor as T, ACCEPT_SCOPE as a, Actor as b, type Anchor as c, type AnchorKind as d, type AnchorState as e, type ApplyContext as f, type CanvasAnchor as g, type CellAnchor as h, type CommentsOptions as i, type NewSuggestion as j, type NewThread as k, type PrincipalKind as l, type RevertContext as m, type SlideAnchor as n, type StampInput as o, type Suggestion as p, type SuggestionFilter as q, type SuggestionHost as r, type Thread as s, type ThreadFilter as t, authorLabel as u, stamp as v, textAnchor as w, textOf as x };
|
package/llms.txt
CHANGED
|
@@ -6,7 +6,8 @@ Keywords: comments,threads,mentions,suggestions,annotations,collaboration,agents
|
|
|
6
6
|
|
|
7
7
|
- SDK: import { Comments, Suggestions, textAnchor } from "agentic-comments". The host calls stamp(); an agent never does.
|
|
8
8
|
- Threads live on a Yjs document and merge across replicas.
|
|
9
|
-
-
|
|
9
|
+
- Events: new Notifications(comments, suggestions).subscribe(listener). Ids only, never text.
|
|
10
|
+
- CLI: agentic-comments <command>. MCP: agentic-comments mcp. Both use a local store file.
|
|
10
11
|
|
|
11
12
|
## Docs
|
|
12
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Comment threads and suggestions anchored to any content, from people and agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comments",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"annotations",
|
|
11
11
|
"collaboration",
|
|
12
12
|
"agents",
|
|
13
|
-
"yjs"
|
|
13
|
+
"yjs",
|
|
14
|
+
"mcp"
|
|
14
15
|
],
|
|
15
16
|
"license": "MIT",
|
|
16
17
|
"author": "Pooria Arab",
|
|
@@ -35,28 +36,43 @@
|
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
39
|
+
"mcpName": "io.github.pooriaarab/agentic-comments",
|
|
38
40
|
"main": "./dist/index.js",
|
|
39
41
|
"types": "./dist/index.d.ts",
|
|
40
42
|
"exports": {
|
|
41
43
|
".": {
|
|
42
44
|
"types": "./dist/index.d.ts",
|
|
43
45
|
"import": "./dist/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./local": {
|
|
48
|
+
"types": "./dist/local.d.ts",
|
|
49
|
+
"import": "./dist/local.js"
|
|
44
50
|
}
|
|
45
51
|
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"agentic-comments": "./dist/cli.js",
|
|
54
|
+
"agentic-comments-mcp": "./dist/mcp-bin.js"
|
|
55
|
+
},
|
|
46
56
|
"scripts": {
|
|
47
57
|
"build": "tsup",
|
|
48
58
|
"typecheck": "tsc --noEmit",
|
|
49
59
|
"test": "vitest run",
|
|
50
60
|
"clean": "rm -rf dist",
|
|
61
|
+
"prepack": "npm run build",
|
|
51
62
|
"ci:local": "npm run build && npm run typecheck && npm test"
|
|
52
63
|
},
|
|
53
64
|
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
66
|
+
"zod": "^3.25.76"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
54
69
|
"yjs": "^13.6.0"
|
|
55
70
|
},
|
|
56
71
|
"devDependencies": {
|
|
57
72
|
"@types/node": "^22.0.0",
|
|
58
73
|
"tsup": "^8.0.0",
|
|
59
74
|
"typescript": "^5.6.0",
|
|
60
|
-
"vitest": "^3.0.0"
|
|
75
|
+
"vitest": "^3.0.0",
|
|
76
|
+
"yjs": "^13.6.0"
|
|
61
77
|
}
|
|
62
78
|
}
|