agentic-comments 0.6.0 → 0.7.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 +51 -5
- package/SKILL.md +5 -0
- package/dist/{chunk-VAVKBAHM.js → chunk-6MS4LASK.js} +432 -432
- package/dist/{chunk-T77DPNIV.js → chunk-B7LTOLUO.js} +1 -1
- package/dist/{chunk-ES3NZ7SX.js → chunk-EFO5PSJL.js} +1 -1
- package/dist/{chunk-2NBFTJ47.js → chunk-M77Q5CWI.js} +5 -2
- package/dist/{chunk-SR2A5OZW.js → chunk-OV2GGL3T.js} +3 -3
- package/dist/chunk-UEQL27KF.js +148 -0
- package/dist/cli.js +9 -7
- package/dist/client.js +4 -143
- package/dist/index.js +2 -2
- package/dist/local.js +3 -3
- package/dist/mcp-LP3X55C2.js +11 -0
- package/dist/mcp-bin.js +10 -10
- package/dist/ui.css +54 -0
- package/dist/ui.d.ts +46 -0
- package/dist/ui.js +332 -0
- package/dist/worker.js +2 -2
- package/llms.txt +1 -0
- package/package.json +37 -9
- package/dist/mcp-JX5L6VQW.js +0 -11
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
CommentsError,
|
|
3
3
|
PRINCIPAL_KINDS,
|
|
4
4
|
stamp
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6MS4LASK.js";
|
|
6
6
|
|
|
7
7
|
// src/file-store.ts
|
|
8
8
|
import { createHash } from "crypto";
|
|
@@ -248,7 +248,10 @@ import { PrincipalParseError, parsePrincipal } from "agentic-identity";
|
|
|
248
248
|
function operatorActor(as, scopes) {
|
|
249
249
|
const value = as?.trim();
|
|
250
250
|
const cut = value === void 0 ? -1 : value.indexOf(":");
|
|
251
|
-
if (value === void 0 ||
|
|
251
|
+
if (value === void 0 || value === "") {
|
|
252
|
+
throw new CommentsError("INVALID_AUTHOR", `no principal: set AGENTIC_COMMENTS_AS=kind:name (or pass --as), for example agent_of_human:scribe. Kinds: ${PRINCIPAL_KINDS.join(", ")}`);
|
|
253
|
+
}
|
|
254
|
+
if (cut < 1 || cut === value.length - 1) {
|
|
252
255
|
throw new CommentsError("INVALID_AUTHOR", `set the principal as kind:name, for example human:ada. Kinds: ${PRINCIPAL_KINDS.join(", ")}`);
|
|
253
256
|
}
|
|
254
257
|
const list = scopes === void 0 || scopes.trim() === "" ? [] : scopes.split(",").map((s) => s.trim());
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EFO5PSJL.js";
|
|
4
4
|
import {
|
|
5
5
|
CommentsService
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-B7LTOLUO.js";
|
|
7
7
|
import {
|
|
8
8
|
CommentsError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6MS4LASK.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp.ts
|
|
12
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var CODES = {
|
|
3
|
+
INVALID_AUTHOR: true,
|
|
4
|
+
INVALID_INPUT: true,
|
|
5
|
+
INVALID_ANCHOR: true,
|
|
6
|
+
NO_THREAD: true,
|
|
7
|
+
NO_MESSAGE: true,
|
|
8
|
+
THREAD_RESOLVED: true,
|
|
9
|
+
CORRUPT: true,
|
|
10
|
+
NO_SUGGESTION: true,
|
|
11
|
+
FORBIDDEN: true,
|
|
12
|
+
ALREADY_DECIDED: true,
|
|
13
|
+
APPLY_FAILED: true,
|
|
14
|
+
RELOCATE_FAILED: true,
|
|
15
|
+
STORE_MISSING: true,
|
|
16
|
+
STORE_EXISTS: true,
|
|
17
|
+
STORE_CORRUPT: true,
|
|
18
|
+
STORE_LOCKED: true,
|
|
19
|
+
STORE_FULL: true,
|
|
20
|
+
NO_DOCUMENT: true,
|
|
21
|
+
DOCUMENT_EXISTS: true,
|
|
22
|
+
CONFLICT: true,
|
|
23
|
+
MISCONFIGURED: true,
|
|
24
|
+
USAGE: true,
|
|
25
|
+
UNAUTHENTICATED: true,
|
|
26
|
+
NOT_FOUND: true,
|
|
27
|
+
INTERNAL: true,
|
|
28
|
+
UNKNOWN: true
|
|
29
|
+
};
|
|
30
|
+
var isKnownCode = (v) => typeof v === "string" && Object.hasOwn(CODES, v);
|
|
31
|
+
var CommentsApiError = class extends Error {
|
|
32
|
+
code;
|
|
33
|
+
status;
|
|
34
|
+
constructor(code, status, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "CommentsApiError";
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.status = status;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var KINDS = { human: true, agent_of_human: true, agent_of_agent: true, agent: true, user_token: true };
|
|
42
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
43
|
+
var isString = (v) => typeof v === "string";
|
|
44
|
+
var isAuthor = (v) => isObject(v) && typeof v.kind === "string" && Object.hasOwn(KINDS, v.kind) && isString(v.name);
|
|
45
|
+
var personThread = (v) => isObject(v) && isString(v.threadId) && isObject(v.anchor) && Array.isArray(v.messages) && v.messages.every(SHAPES.message) && (v.statusChange === void 0 || isObject(v.statusChange) && (v.statusChange.status === "open" || v.statusChange.status === "resolved") && isString(v.statusChange.at) && isAuthor(v.statusChange.by));
|
|
46
|
+
var SHAPES = {
|
|
47
|
+
me: (v) => isObject(v) && (typeof v.kind === "string" && Object.hasOwn(KINDS, v.kind)) && isString(v.name) && Array.isArray(v.scopes) && v.scopes.every(isString),
|
|
48
|
+
created: (v) => isObject(v) && isString(v.id),
|
|
49
|
+
document: (v) => isObject(v) && isString(v.id) && isString(v.text),
|
|
50
|
+
thread: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && isObject(v.anchorState) && isString(v.anchorState.state) && (v.status === "open" || v.status === "resolved") && isAuthor(v.statusBy) && isString(v.statusAt) && Array.isArray(v.messages) && v.messages.every(SHAPES.message) && typeof v.skipped === "number",
|
|
51
|
+
threads: (v) => Array.isArray(v) && v.every(SHAPES.thread),
|
|
52
|
+
message: (v) => isObject(v) && isString(v.id) && isString(v.at) && isString(v.body) && Array.isArray(v.mentions) && v.mentions.every(isString) && isAuthor(v.author) && (v.editedAt === void 0 || isString(v.editedAt)) && (v.editedBy === void 0 || isAuthor(v.editedBy)),
|
|
53
|
+
suggestion: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && isObject(v.anchorState) && isString(v.anchorState.state) && (v.status === "pending" || v.status === "accepted" || v.status === "rejected") && isAuthor(v.author) && isString(v.createdAt) && "change" in v && (v.summary === void 0 || isString(v.summary)) && (v.decidedBy === void 0 || isAuthor(v.decidedBy)) && (v.decidedAt === void 0 || isString(v.decidedAt)),
|
|
54
|
+
suggestions: (v) => Array.isArray(v) && v.every(SHAPES.suggestion),
|
|
55
|
+
deletedMessage: (v) => isObject(v) && isString(v.threadId) && isString(v.messageId),
|
|
56
|
+
deletedThread: (v) => isObject(v) && isString(v.threadId),
|
|
57
|
+
person: (v) => isObject(v) && isString(v.person) && isString(v.exportedAt) && Array.isArray(v.threads) && v.threads.every(personThread) && Array.isArray(v.edits) && v.edits.every((e) => isObject(e) && isString(e.threadId) && isString(e.messageId) && isString(e.at)) && typeof v.unread === "number" && isObject(v.suggestions) && Array.isArray(v.suggestions.authored) && v.suggestions.authored.every(SHAPES.suggestion) && Array.isArray(v.suggestions.decided) && v.suggestions.decided.every((d) => isObject(d) && isString(d.id) && (d.status === "accepted" || d.status === "rejected") && isString(d.at)) && typeof v.suggestions.unread === "number",
|
|
58
|
+
erased: (v) => isObject(v) && ["messages", "statuses", "edits", "suggestions", "decisions"].every((k) => typeof v[k] === "number")
|
|
59
|
+
};
|
|
60
|
+
function createClient(options) {
|
|
61
|
+
if (typeof options.baseUrl !== "string" || options.baseUrl === "") throw new TypeError("createClient needs a baseUrl");
|
|
62
|
+
if (typeof options.token !== "string" || options.token === "") throw new TypeError("createClient needs a token");
|
|
63
|
+
let origin;
|
|
64
|
+
try {
|
|
65
|
+
origin = new URL(options.baseUrl);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new TypeError("baseUrl must be an absolute URL");
|
|
68
|
+
}
|
|
69
|
+
if (origin.protocol !== "https:" && origin.protocol !== "http:") throw new TypeError("baseUrl must be an http or https URL");
|
|
70
|
+
if (origin.search !== "" || origin.hash !== "" || origin.username !== "" || origin.password !== "") {
|
|
71
|
+
throw new TypeError("baseUrl must have no query, hash, or credentials");
|
|
72
|
+
}
|
|
73
|
+
const root = `${origin.origin}${origin.pathname.replace(/\/+$/, "")}/api/v1`;
|
|
74
|
+
const token = options.token;
|
|
75
|
+
const send = (input, init) => {
|
|
76
|
+
const run = options.fetch ?? globalThis.fetch;
|
|
77
|
+
return run(input, init);
|
|
78
|
+
};
|
|
79
|
+
const seg = (value) => {
|
|
80
|
+
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 ..");
|
|
81
|
+
return encodeURIComponent(value);
|
|
82
|
+
};
|
|
83
|
+
const query = (filter) => {
|
|
84
|
+
const params = new URLSearchParams();
|
|
85
|
+
for (const [key, value] of Object.entries(filter ?? {})) if (value !== void 0) params.set(key, String(value));
|
|
86
|
+
const text = params.toString();
|
|
87
|
+
return text === "" ? "" : `?${text}`;
|
|
88
|
+
};
|
|
89
|
+
async function call(method, path, body, callOptions, shape) {
|
|
90
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
91
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
92
|
+
const response = await send(`${root}${path}`, {
|
|
93
|
+
method,
|
|
94
|
+
headers,
|
|
95
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
96
|
+
signal: callOptions?.signal,
|
|
97
|
+
// Never follow a redirect: it would send a write to another host.
|
|
98
|
+
redirect: "manual"
|
|
99
|
+
});
|
|
100
|
+
const text = await response.text();
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = text === "" ? void 0 : JSON.parse(text);
|
|
104
|
+
} catch {
|
|
105
|
+
parsed = void 0;
|
|
106
|
+
}
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const error = parsed?.error;
|
|
109
|
+
const code = isKnownCode(error?.code) ? error.code : "UNKNOWN";
|
|
110
|
+
const message = typeof error?.message === "string" ? error.message : `HTTP ${response.status}`;
|
|
111
|
+
throw new CommentsApiError(code, response.status, message);
|
|
112
|
+
}
|
|
113
|
+
if (!SHAPES[shape](parsed)) throw new CommentsApiError("UNKNOWN", response.status, `the reply is not a ${shape}`);
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
const doc = (id) => `/docs/${seg(id)}`;
|
|
117
|
+
const thread = (id, threadId) => `${doc(id)}/threads/${seg(threadId)}`;
|
|
118
|
+
const suggestion = (id, suggestionId) => `${doc(id)}/suggestions/${seg(suggestionId)}`;
|
|
119
|
+
const methods = {
|
|
120
|
+
me: (o) => call("GET", "/me", void 0, o, "me"),
|
|
121
|
+
createDocument: (input, o) => call("POST", "/docs", input, o, "created"),
|
|
122
|
+
getDocument: (id, o) => call("GET", doc(id), void 0, o, "document"),
|
|
123
|
+
listThreads: (id, filter, o) => call("GET", `${doc(id)}/threads${query(filter)}`, void 0, o, "threads"),
|
|
124
|
+
getThread: (id, threadId, o) => call("GET", thread(id, threadId), void 0, o, "thread"),
|
|
125
|
+
addThread: (id, input, o) => call("POST", `${doc(id)}/threads`, input, o, "thread"),
|
|
126
|
+
reply: (id, threadId, input, o) => call("POST", `${thread(id, threadId)}/replies`, input, o, "message"),
|
|
127
|
+
resolve: (id, threadId, o) => call("POST", `${thread(id, threadId)}/resolve`, void 0, o, "thread"),
|
|
128
|
+
reopen: (id, threadId, o) => call("POST", `${thread(id, threadId)}/reopen`, void 0, o, "thread"),
|
|
129
|
+
relocate: (id, threadId, o) => call("POST", `${thread(id, threadId)}/relocate`, void 0, o, "thread"),
|
|
130
|
+
editMessage: (id, threadId, messageId, input, o) => call("PATCH", `${thread(id, threadId)}/messages/${seg(messageId)}`, input, o, "message"),
|
|
131
|
+
deleteMessage: (id, threadId, messageId, o) => call("DELETE", `${thread(id, threadId)}/messages/${seg(messageId)}`, void 0, o, "deletedMessage"),
|
|
132
|
+
deleteThread: (id, threadId, o) => call("DELETE", thread(id, threadId), void 0, o, "deletedThread"),
|
|
133
|
+
exportPerson: (id, person, o) => call("GET", `${doc(id)}/people/${seg(person)}/export`, void 0, o, "person"),
|
|
134
|
+
erasePerson: (id, person, o) => call("DELETE", `${doc(id)}/people/${seg(person)}`, void 0, o, "erased"),
|
|
135
|
+
listSuggestions: (id, filter, o) => call("GET", `${doc(id)}/suggestions${query(filter)}`, void 0, o, "suggestions"),
|
|
136
|
+
suggest: (id, input, o) => call("POST", `${doc(id)}/suggestions`, input, o, "suggestion"),
|
|
137
|
+
accept: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/accept`, void 0, o, "suggestion"),
|
|
138
|
+
reject: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/reject`, void 0, o, "suggestion")
|
|
139
|
+
};
|
|
140
|
+
return Object.fromEntries(
|
|
141
|
+
Object.entries(methods).map(([name, method]) => [name, async (...args) => method(...args)])
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
CommentsApiError,
|
|
147
|
+
createClient
|
|
148
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-EFO5PSJL.js";
|
|
2
5
|
import {
|
|
3
6
|
FileStore,
|
|
4
7
|
lockTimeoutFrom,
|
|
5
8
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
VERSION
|
|
9
|
-
} from "./chunk-ES3NZ7SX.js";
|
|
9
|
+
} from "./chunk-M77Q5CWI.js";
|
|
10
10
|
import {
|
|
11
11
|
CommentsService,
|
|
12
12
|
TEXT_ROOT
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-B7LTOLUO.js";
|
|
14
14
|
import {
|
|
15
15
|
CommentsError
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-6MS4LASK.js";
|
|
17
17
|
|
|
18
18
|
// src/cli.ts
|
|
19
19
|
import { readFileSync, realpathSync } from "fs";
|
|
@@ -69,6 +69,8 @@ function parseArgs(argv) {
|
|
|
69
69
|
if (command === void 0 || command === "--help" || command === "-h") return { command: "help", flags: {}, many: {} };
|
|
70
70
|
if (command === "--version" || command === "-V") return { command: "version", flags: {}, many: {} };
|
|
71
71
|
if (!COMMANDS.includes(command)) throw new UsageError(`unknown command "${command}"`);
|
|
72
|
+
if (command === "mcp" && rest.some((a) => a === "--help" || a === "-h")) return { command: "help", flags: {}, many: {} };
|
|
73
|
+
if (command === "mcp" && rest.some((a) => a === "--version" || a === "-V" || a === "-v")) return { command: "version", flags: {}, many: {} };
|
|
72
74
|
const parsed = { command, flags: {}, many: {} };
|
|
73
75
|
for (let i = 0; i < rest.length; i++) {
|
|
74
76
|
const arg = rest[i];
|
|
@@ -176,7 +178,7 @@ async function runCli(argv, io) {
|
|
|
176
178
|
return 0;
|
|
177
179
|
}
|
|
178
180
|
if (p.command === "mcp") {
|
|
179
|
-
const { startMcp } = await import("./mcp-
|
|
181
|
+
const { startMcp } = await import("./mcp-LP3X55C2.js");
|
|
180
182
|
await startMcp(store, operatorActor(p.flags["--as"] ?? io.env.AGENTIC_COMMENTS_AS, io.env.AGENTIC_COMMENTS_SCOPES));
|
|
181
183
|
return 0;
|
|
182
184
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,146 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
INVALID_ANCHOR: true,
|
|
6
|
-
NO_THREAD: true,
|
|
7
|
-
NO_MESSAGE: true,
|
|
8
|
-
THREAD_RESOLVED: true,
|
|
9
|
-
CORRUPT: true,
|
|
10
|
-
NO_SUGGESTION: true,
|
|
11
|
-
FORBIDDEN: true,
|
|
12
|
-
ALREADY_DECIDED: true,
|
|
13
|
-
APPLY_FAILED: true,
|
|
14
|
-
RELOCATE_FAILED: true,
|
|
15
|
-
STORE_MISSING: true,
|
|
16
|
-
STORE_EXISTS: true,
|
|
17
|
-
STORE_CORRUPT: true,
|
|
18
|
-
STORE_LOCKED: true,
|
|
19
|
-
STORE_FULL: true,
|
|
20
|
-
NO_DOCUMENT: true,
|
|
21
|
-
DOCUMENT_EXISTS: true,
|
|
22
|
-
CONFLICT: true,
|
|
23
|
-
MISCONFIGURED: true,
|
|
24
|
-
USAGE: true,
|
|
25
|
-
UNAUTHENTICATED: true,
|
|
26
|
-
NOT_FOUND: true,
|
|
27
|
-
INTERNAL: true,
|
|
28
|
-
UNKNOWN: true
|
|
29
|
-
};
|
|
30
|
-
var isKnownCode = (v) => typeof v === "string" && Object.hasOwn(CODES, v);
|
|
31
|
-
var CommentsApiError = class extends Error {
|
|
32
|
-
code;
|
|
33
|
-
status;
|
|
34
|
-
constructor(code, status, message) {
|
|
35
|
-
super(message);
|
|
36
|
-
this.name = "CommentsApiError";
|
|
37
|
-
this.code = code;
|
|
38
|
-
this.status = status;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
var KINDS = { human: true, agent_of_human: true, agent_of_agent: true, agent: true, user_token: true };
|
|
42
|
-
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
43
|
-
var isString = (v) => typeof v === "string";
|
|
44
|
-
var isAuthor = (v) => isObject(v) && typeof v.kind === "string" && Object.hasOwn(KINDS, v.kind) && isString(v.name);
|
|
45
|
-
var personThread = (v) => isObject(v) && isString(v.threadId) && isObject(v.anchor) && Array.isArray(v.messages) && v.messages.every(SHAPES.message) && (v.statusChange === void 0 || isObject(v.statusChange) && (v.statusChange.status === "open" || v.statusChange.status === "resolved") && isString(v.statusChange.at) && isAuthor(v.statusChange.by));
|
|
46
|
-
var SHAPES = {
|
|
47
|
-
me: (v) => isObject(v) && (typeof v.kind === "string" && Object.hasOwn(KINDS, v.kind)) && isString(v.name) && Array.isArray(v.scopes) && v.scopes.every(isString),
|
|
48
|
-
created: (v) => isObject(v) && isString(v.id),
|
|
49
|
-
document: (v) => isObject(v) && isString(v.id) && isString(v.text),
|
|
50
|
-
thread: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && isObject(v.anchorState) && isString(v.anchorState.state) && (v.status === "open" || v.status === "resolved") && isAuthor(v.statusBy) && isString(v.statusAt) && Array.isArray(v.messages) && v.messages.every(SHAPES.message) && typeof v.skipped === "number",
|
|
51
|
-
threads: (v) => Array.isArray(v) && v.every(SHAPES.thread),
|
|
52
|
-
message: (v) => isObject(v) && isString(v.id) && isString(v.at) && isString(v.body) && Array.isArray(v.mentions) && v.mentions.every(isString) && isAuthor(v.author) && (v.editedAt === void 0 || isString(v.editedAt)) && (v.editedBy === void 0 || isAuthor(v.editedBy)),
|
|
53
|
-
suggestion: (v) => isObject(v) && isString(v.id) && isObject(v.anchor) && isObject(v.anchorState) && isString(v.anchorState.state) && (v.status === "pending" || v.status === "accepted" || v.status === "rejected") && isAuthor(v.author) && isString(v.createdAt) && "change" in v && (v.summary === void 0 || isString(v.summary)) && (v.decidedBy === void 0 || isAuthor(v.decidedBy)) && (v.decidedAt === void 0 || isString(v.decidedAt)),
|
|
54
|
-
suggestions: (v) => Array.isArray(v) && v.every(SHAPES.suggestion),
|
|
55
|
-
deletedMessage: (v) => isObject(v) && isString(v.threadId) && isString(v.messageId),
|
|
56
|
-
deletedThread: (v) => isObject(v) && isString(v.threadId),
|
|
57
|
-
person: (v) => isObject(v) && isString(v.person) && isString(v.exportedAt) && Array.isArray(v.threads) && v.threads.every(personThread) && Array.isArray(v.edits) && v.edits.every((e) => isObject(e) && isString(e.threadId) && isString(e.messageId) && isString(e.at)) && typeof v.unread === "number" && isObject(v.suggestions) && Array.isArray(v.suggestions.authored) && v.suggestions.authored.every(SHAPES.suggestion) && Array.isArray(v.suggestions.decided) && v.suggestions.decided.every((d) => isObject(d) && isString(d.id) && (d.status === "accepted" || d.status === "rejected") && isString(d.at)) && typeof v.suggestions.unread === "number",
|
|
58
|
-
erased: (v) => isObject(v) && ["messages", "statuses", "edits", "suggestions", "decisions"].every((k) => typeof v[k] === "number")
|
|
59
|
-
};
|
|
60
|
-
function createClient(options) {
|
|
61
|
-
if (typeof options.baseUrl !== "string" || options.baseUrl === "") throw new TypeError("createClient needs a baseUrl");
|
|
62
|
-
if (typeof options.token !== "string" || options.token === "") throw new TypeError("createClient needs a token");
|
|
63
|
-
let origin;
|
|
64
|
-
try {
|
|
65
|
-
origin = new URL(options.baseUrl);
|
|
66
|
-
} catch {
|
|
67
|
-
throw new TypeError("baseUrl must be an absolute URL");
|
|
68
|
-
}
|
|
69
|
-
if (origin.protocol !== "https:" && origin.protocol !== "http:") throw new TypeError("baseUrl must be an http or https URL");
|
|
70
|
-
if (origin.search !== "" || origin.hash !== "" || origin.username !== "" || origin.password !== "") {
|
|
71
|
-
throw new TypeError("baseUrl must have no query, hash, or credentials");
|
|
72
|
-
}
|
|
73
|
-
const root = `${origin.origin}${origin.pathname.replace(/\/+$/, "")}/api/v1`;
|
|
74
|
-
const token = options.token;
|
|
75
|
-
const send = (input, init) => {
|
|
76
|
-
const run = options.fetch ?? globalThis.fetch;
|
|
77
|
-
return run(input, init);
|
|
78
|
-
};
|
|
79
|
-
const seg = (value) => {
|
|
80
|
-
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 ..");
|
|
81
|
-
return encodeURIComponent(value);
|
|
82
|
-
};
|
|
83
|
-
const query = (filter) => {
|
|
84
|
-
const params = new URLSearchParams();
|
|
85
|
-
for (const [key, value] of Object.entries(filter ?? {})) if (value !== void 0) params.set(key, String(value));
|
|
86
|
-
const text = params.toString();
|
|
87
|
-
return text === "" ? "" : `?${text}`;
|
|
88
|
-
};
|
|
89
|
-
async function call(method, path, body, callOptions, shape) {
|
|
90
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
91
|
-
if (body !== void 0) headers["content-type"] = "application/json";
|
|
92
|
-
const response = await send(`${root}${path}`, {
|
|
93
|
-
method,
|
|
94
|
-
headers,
|
|
95
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
96
|
-
signal: callOptions?.signal,
|
|
97
|
-
// Never follow a redirect: it would send a write to another host.
|
|
98
|
-
redirect: "manual"
|
|
99
|
-
});
|
|
100
|
-
const text = await response.text();
|
|
101
|
-
let parsed;
|
|
102
|
-
try {
|
|
103
|
-
parsed = text === "" ? void 0 : JSON.parse(text);
|
|
104
|
-
} catch {
|
|
105
|
-
parsed = void 0;
|
|
106
|
-
}
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
const error = parsed?.error;
|
|
109
|
-
const code = isKnownCode(error?.code) ? error.code : "UNKNOWN";
|
|
110
|
-
const message = typeof error?.message === "string" ? error.message : `HTTP ${response.status}`;
|
|
111
|
-
throw new CommentsApiError(code, response.status, message);
|
|
112
|
-
}
|
|
113
|
-
if (!SHAPES[shape](parsed)) throw new CommentsApiError("UNKNOWN", response.status, `the reply is not a ${shape}`);
|
|
114
|
-
return parsed;
|
|
115
|
-
}
|
|
116
|
-
const doc = (id) => `/docs/${seg(id)}`;
|
|
117
|
-
const thread = (id, threadId) => `${doc(id)}/threads/${seg(threadId)}`;
|
|
118
|
-
const suggestion = (id, suggestionId) => `${doc(id)}/suggestions/${seg(suggestionId)}`;
|
|
119
|
-
const methods = {
|
|
120
|
-
me: (o) => call("GET", "/me", void 0, o, "me"),
|
|
121
|
-
createDocument: (input, o) => call("POST", "/docs", input, o, "created"),
|
|
122
|
-
getDocument: (id, o) => call("GET", doc(id), void 0, o, "document"),
|
|
123
|
-
listThreads: (id, filter, o) => call("GET", `${doc(id)}/threads${query(filter)}`, void 0, o, "threads"),
|
|
124
|
-
getThread: (id, threadId, o) => call("GET", thread(id, threadId), void 0, o, "thread"),
|
|
125
|
-
addThread: (id, input, o) => call("POST", `${doc(id)}/threads`, input, o, "thread"),
|
|
126
|
-
reply: (id, threadId, input, o) => call("POST", `${thread(id, threadId)}/replies`, input, o, "message"),
|
|
127
|
-
resolve: (id, threadId, o) => call("POST", `${thread(id, threadId)}/resolve`, void 0, o, "thread"),
|
|
128
|
-
reopen: (id, threadId, o) => call("POST", `${thread(id, threadId)}/reopen`, void 0, o, "thread"),
|
|
129
|
-
relocate: (id, threadId, o) => call("POST", `${thread(id, threadId)}/relocate`, void 0, o, "thread"),
|
|
130
|
-
editMessage: (id, threadId, messageId, input, o) => call("PATCH", `${thread(id, threadId)}/messages/${seg(messageId)}`, input, o, "message"),
|
|
131
|
-
deleteMessage: (id, threadId, messageId, o) => call("DELETE", `${thread(id, threadId)}/messages/${seg(messageId)}`, void 0, o, "deletedMessage"),
|
|
132
|
-
deleteThread: (id, threadId, o) => call("DELETE", thread(id, threadId), void 0, o, "deletedThread"),
|
|
133
|
-
exportPerson: (id, person, o) => call("GET", `${doc(id)}/people/${seg(person)}/export`, void 0, o, "person"),
|
|
134
|
-
erasePerson: (id, person, o) => call("DELETE", `${doc(id)}/people/${seg(person)}`, void 0, o, "erased"),
|
|
135
|
-
listSuggestions: (id, filter, o) => call("GET", `${doc(id)}/suggestions${query(filter)}`, void 0, o, "suggestions"),
|
|
136
|
-
suggest: (id, input, o) => call("POST", `${doc(id)}/suggestions`, input, o, "suggestion"),
|
|
137
|
-
accept: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/accept`, void 0, o, "suggestion"),
|
|
138
|
-
reject: (id, suggestionId, o) => call("POST", `${suggestion(id, suggestionId)}/reject`, void 0, o, "suggestion")
|
|
139
|
-
};
|
|
140
|
-
return Object.fromEntries(
|
|
141
|
-
Object.entries(methods).map(([name, method]) => [name, async (...args) => method(...args)])
|
|
142
|
-
);
|
|
143
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
CommentsApiError,
|
|
3
|
+
createClient
|
|
4
|
+
} from "./chunk-UEQL27KF.js";
|
|
144
5
|
export {
|
|
145
6
|
CommentsApiError,
|
|
146
7
|
createClient
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EFO5PSJL.js";
|
|
4
4
|
import {
|
|
5
5
|
ACCEPT_SCOPE,
|
|
6
6
|
Actor,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
textAnchor,
|
|
18
18
|
textOf,
|
|
19
19
|
validDate
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-6MS4LASK.js";
|
|
21
21
|
|
|
22
22
|
// src/notifications.ts
|
|
23
23
|
import * as Y from "yjs";
|
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-M77Q5CWI.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-B7LTOLUO.js";
|
|
13
|
+
import "./chunk-6MS4LASK.js";
|
|
14
14
|
export {
|
|
15
15
|
CommentsService,
|
|
16
16
|
FileStore,
|
package/dist/mcp-bin.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
FileStore,
|
|
4
|
-
lockTimeoutFrom,
|
|
5
|
-
operatorActor
|
|
6
|
-
} from "./chunk-2NBFTJ47.js";
|
|
7
2
|
import {
|
|
8
3
|
startMcp
|
|
9
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OV2GGL3T.js";
|
|
10
5
|
import {
|
|
11
6
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import
|
|
14
|
-
|
|
7
|
+
} from "./chunk-EFO5PSJL.js";
|
|
8
|
+
import {
|
|
9
|
+
FileStore,
|
|
10
|
+
lockTimeoutFrom,
|
|
11
|
+
operatorActor
|
|
12
|
+
} from "./chunk-M77Q5CWI.js";
|
|
13
|
+
import "./chunk-B7LTOLUO.js";
|
|
14
|
+
import "./chunk-6MS4LASK.js";
|
|
15
15
|
|
|
16
16
|
// src/mcp-bin.ts
|
|
17
17
|
var MCP_HELP = `agentic-comments-mcp ${VERSION}
|
|
@@ -27,7 +27,7 @@ Usage: agentic-comments-mcp [--help | --version]`;
|
|
|
27
27
|
var args = process.argv.slice(2);
|
|
28
28
|
if (args.includes("--help") || args.includes("-h")) {
|
|
29
29
|
console.log(MCP_HELP);
|
|
30
|
-
} else if (args.includes("--version") || args.includes("-v")) {
|
|
30
|
+
} else if (args.includes("--version") || args.includes("-V") || args.includes("-v")) {
|
|
31
31
|
console.log(VERSION);
|
|
32
32
|
} else {
|
|
33
33
|
try {
|
package/dist/ui.css
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* The comments panel. It uses the agentic-design tokens and its own agd-comments classes. */
|
|
2
|
+
.agd-comments {
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-columns: minmax(0, 1fr);
|
|
5
|
+
gap: var(--agd-space-16);
|
|
6
|
+
max-width: 44rem;
|
|
7
|
+
margin-inline: auto;
|
|
8
|
+
padding: var(--agd-space-16);
|
|
9
|
+
color: var(--agd-color-text);
|
|
10
|
+
font-family: var(--agd-font-family-sans);
|
|
11
|
+
font-size: var(--agd-font-size-md);
|
|
12
|
+
line-height: var(--agd-line-height-normal);
|
|
13
|
+
}
|
|
14
|
+
.agd-comments__top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--agd-space-8); }
|
|
15
|
+
.agd-comments__title { margin: 0; font-size: var(--agd-font-size-xl); font-weight: var(--agd-font-weight-semibold); line-height: var(--agd-line-height-tight); }
|
|
16
|
+
.agd-comments__me { display: flex; align-items: center; gap: var(--agd-space-8); margin: 0; color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); }
|
|
17
|
+
.agd-comments__section { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-12, 12px); }
|
|
18
|
+
.agd-comments__heading { display: flex; align-items: center; gap: var(--agd-space-8); margin: 0; font-size: var(--agd-font-size-lg); font-weight: var(--agd-font-weight-semibold); }
|
|
19
|
+
.agd-comments__count { min-width: 1.5rem; padding: 0 var(--agd-space-6); border-radius: var(--agd-radius-full); background: var(--agd-color-surface-sunken); color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); font-weight: var(--agd-font-weight-medium); text-align: center; }
|
|
20
|
+
.agd-comments__views { display: flex; flex-wrap: wrap; gap: var(--agd-space-8); }
|
|
21
|
+
.agd-comments__list, .agd-comments__messages { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-12, 12px); margin: 0; padding: 0; list-style: none; }
|
|
22
|
+
.agd-comments__thread { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-8); padding: var(--agd-space-16); border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-lg); background: var(--agd-color-surface); }
|
|
23
|
+
.agd-comments__thread > article { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-8); }
|
|
24
|
+
.agd-comments__item-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--agd-space-8); }
|
|
25
|
+
.agd-comments__item-title { display: flex; flex-wrap: wrap; align-items: center; gap: var(--agd-space-8); margin: 0; font-size: var(--agd-font-size-md); font-weight: var(--agd-font-weight-semibold); overflow-wrap: anywhere; }
|
|
26
|
+
.agd-comments__status { font-size: var(--agd-font-size-xs); }
|
|
27
|
+
.agd-comments__status--resolved, .agd-comments__status--accepted { background: var(--agd-color-success-subtle); color: var(--agd-color-success-text); }
|
|
28
|
+
.agd-comments__status--rejected { background: var(--agd-color-danger-subtle); color: var(--agd-color-danger-text); }
|
|
29
|
+
.agd-comments__status--pending, .agd-comments__status--open { background: var(--agd-color-accent-subtle); color: var(--agd-color-accent-text); }
|
|
30
|
+
.agd-comments__anchor { margin: 0; }
|
|
31
|
+
.agd-comments__label { color: var(--agd-color-text-muted); font-size: var(--agd-font-size-xs); font-weight: var(--agd-font-weight-medium); letter-spacing: 0.04em; text-transform: uppercase; }
|
|
32
|
+
.agd-comments__quote { margin: var(--agd-space-4) 0 0; padding: var(--agd-space-4) var(--agd-space-12, 12px); border-inline-start: 3px solid var(--agd-color-border-strong); background: var(--agd-color-surface-sunken); border-radius: var(--agd-radius-sm); overflow-wrap: anywhere; }
|
|
33
|
+
.agd-comments__state { margin: 0; padding: var(--agd-space-8) var(--agd-space-12, 12px); border-radius: var(--agd-radius-md); background: var(--agd-color-warning-subtle); color: var(--agd-color-warning-text); font-size: var(--agd-font-size-sm); overflow-wrap: anywhere; }
|
|
34
|
+
.agd-comments__message { display: grid; gap: var(--agd-space-4); padding-top: var(--agd-space-8); border-top: 1px solid var(--agd-color-border); }
|
|
35
|
+
.agd-comments__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--agd-space-8); }
|
|
36
|
+
.agd-comments__who { display: inline-flex; align-items: center; gap: var(--agd-space-8); }
|
|
37
|
+
.agd-comments__name { font-weight: var(--agd-font-weight-semibold); overflow-wrap: anywhere; }
|
|
38
|
+
.agd-comments__time { color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); }
|
|
39
|
+
.agd-comments__body { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
40
|
+
.agd-comments__change { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-4); margin: 0; }
|
|
41
|
+
.agd-comments__code { padding: var(--agd-space-4) var(--agd-space-8); border-radius: var(--agd-radius-sm); background: var(--agd-color-surface-sunken); font-family: var(--agd-font-family-mono); font-size: var(--agd-font-size-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
42
|
+
.agd-comments__note, .agd-comments__empty { margin: 0; color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); }
|
|
43
|
+
.agd-comments__reply, .agd-comments__new { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--agd-space-8); }
|
|
44
|
+
.agd-comments__new { padding: var(--agd-space-16); border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-lg); background: var(--agd-color-surface-raised); }
|
|
45
|
+
.agd-comments__reply .agd-button, .agd-comments__new .agd-button { justify-self: start; }
|
|
46
|
+
.agd-comments__actions { display: flex; flex-wrap: wrap; gap: var(--agd-space-8); }
|
|
47
|
+
.agd-comments__error { margin: 0; padding: var(--agd-space-8) var(--agd-space-12, 12px); border: 1px solid var(--agd-color-danger); border-radius: var(--agd-radius-md); background: var(--agd-color-danger-subtle); color: var(--agd-color-danger-text); overflow-wrap: anywhere; }
|
|
48
|
+
@media (max-width: 480px) {
|
|
49
|
+
.agd-comments { padding: var(--agd-space-12, 12px); }
|
|
50
|
+
.agd-comments__thread { padding: var(--agd-space-12, 12px); }
|
|
51
|
+
}
|
|
52
|
+
/* On a narrow screen the badge wraps, so its words are never cut off. */
|
|
53
|
+
.agd-comments .agd-agent-badge { flex-wrap: wrap; row-gap: 0; }
|
|
54
|
+
.agd-comments .agd-agent-badge__label, .agd-comments .agd-agent-badge__meta { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CommentsClient, Me } from './client.js';
|
|
3
|
+
export { createClient } from './client.js';
|
|
4
|
+
import { b as Author, S as Suggestion } from './suggestions-CnkEcTuY.js';
|
|
5
|
+
import './service-BvIE6t5n.js';
|
|
6
|
+
import 'yjs';
|
|
7
|
+
import './errors-C-xvp24-.js';
|
|
8
|
+
import 'agentic-identity';
|
|
9
|
+
|
|
10
|
+
/** The calls the panel makes. A `CommentsClient` fits, and so does any object with these methods. */
|
|
11
|
+
type PanelClient = Pick<CommentsClient, "me" | "listThreads" | "addThread" | "reply" | "resolve" | "reopen" | "listSuggestions" | "accept" | "reject">;
|
|
12
|
+
interface CommentsPanelProps {
|
|
13
|
+
client: PanelClient;
|
|
14
|
+
/** The document whose threads the panel shows. */
|
|
15
|
+
docId: string;
|
|
16
|
+
/** The heading. Defaults to "Comments". */
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Show the form that starts a thread on a quote. On by default. A page that makes its own anchors turns it off. */
|
|
19
|
+
newThread?: boolean;
|
|
20
|
+
/** Name the person an agent acts for, when the host knows. The badge says "for unknown" without it. */
|
|
21
|
+
actingFor?: (author: Author) => string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A threaded comments panel: thread list, reply box, resolve and reopen, the quote each thread points at, an agent or
|
|
25
|
+
* person mark on every message, and accept and reject buttons for suggestions. The buttons follow the product rule: an
|
|
26
|
+
* agent never decides its own suggestion. The server still checks every call, so a hidden button is a help and not a lock.
|
|
27
|
+
* It needs the `agentic-design/styles.css` and `agentic-comments/ui.css` styles on the page.
|
|
28
|
+
*/
|
|
29
|
+
declare function CommentsPanel({ client, docId, title, newThread, actingFor }: CommentsPanelProps): react.JSX.Element;
|
|
30
|
+
|
|
31
|
+
type Need = "read" | "comment" | "write";
|
|
32
|
+
/** Whether the principal holds a scope at least as strong as `need`. */
|
|
33
|
+
declare function holds(me: Pick<Me, "scopes"> | undefined, need: Need): boolean;
|
|
34
|
+
interface Decision {
|
|
35
|
+
allowed: boolean;
|
|
36
|
+
/** Plain words that say why not. Set when `allowed` is false. */
|
|
37
|
+
reason?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether the signed-in principal may accept a suggestion, and whether it may reject one. The Worker checks the
|
|
41
|
+
* same rule, so this only decides what the page offers. A person may decide any suggestion. An agent needs the
|
|
42
|
+
* `comments:admin` scope or the `comments:accept` right, and never decides its own suggestion.
|
|
43
|
+
*/
|
|
44
|
+
declare function mayDecide(me: Me | undefined, suggestion: Pick<Suggestion, "author" | "status">): Decision;
|
|
45
|
+
|
|
46
|
+
export { CommentsClient, CommentsPanel, type CommentsPanelProps, type Decision, Me, type Need, type PanelClient, holds, mayDecide };
|