paperclip-plugin-mattermost 0.13.0 → 0.14.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 +2 -2
- package/dist/manifest.js +76 -2
- package/dist/ui/index.js +20 -2
- package/dist/worker.js +31 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Your [Paperclip](https://github.com/paperclipai/paperclip) agents as Mattermost
|
|
|
13
13
|
- **Confirmations and suggested tasks** get Accept / Reject buttons.
|
|
14
14
|
- **Single-choice questions** get a button per option. Your pick is posted on the task as a comment from you, which wakes the agent.
|
|
15
15
|
- **Access control.** Choose who may talk to agents: everyone, or only the people you pick. Each person can be limited to some agents, and people outside Paperclip can be added by email.
|
|
16
|
-
- **Agents work in Mattermost themselves.** Through plugin API routes that only agents can call, each agent reads the channels its bot is in, posts, reacts, searches, and DMs the people allowed to talk to it, always as its own bot.
|
|
16
|
+
- **Agents work in Mattermost themselves.** Through plugin API routes that only agents can call, each agent reads the channels its bot is in, posts, reacts, searches, and DMs the people allowed to talk to it, always as its own bot. Each company gets a Mattermost skill that tells agents how, and saving the settings adds it to every agent with a bot, so they can message people from tasks and scheduled runs too.
|
|
17
17
|
- **Agents work with agents.** An agent mentioning another agent starts it, and it answers in the thread. After a set number of agent-to-agent messages in a thread (10 by default, set in settings), they pause until a person replies.
|
|
18
18
|
- **Work channels.** For an investigation, incident or project, an agent opens a private channel (`inc-checkout-5xx`), pulls in the people and agents it needs, and ties it to a Paperclip task. Task comments and status changes show up in the channel. When the task is done or cancelled, the channel is archived and the outcome is posted back where the work was asked for.
|
|
19
19
|
- **Ask first.** Some actions wait for an approver to click Approve: `@channel`, `@all` and `@here`, public channels, and adding people to a channel the agent didn't create. Agents can't change or archive channels they didn't create, remove anyone, or touch team and server settings.
|
|
@@ -82,7 +82,7 @@ PAPERCLIP_REF=<commit> node build.mjs # build for another Paperclip version
|
|
|
82
82
|
Bump `version` in `package.json` and `src/manifest.ts`, commit, then tag and push:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
git tag -a v0.
|
|
85
|
+
git tag -a v0.14.0 -m v0.14.0 && git push origin main v0.14.0
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
GitHub Actions builds, tests and publishes to npm through npm trusted publishing, so no npm token is involved.
|
package/dist/manifest.js
CHANGED
|
@@ -1,9 +1,80 @@
|
|
|
1
|
+
// src/help.ts
|
|
2
|
+
var HELP = `# Working in Mattermost
|
|
3
|
+
|
|
4
|
+
You have your own Mattermost account (a bot). Everything below runs as you, in your company's
|
|
5
|
+
Mattermost, and you can only see and use channels you are a member of. Use it whenever you need
|
|
6
|
+
to reach a person or another agent in Mattermost, including from a task or a scheduled run: to
|
|
7
|
+
ping someone, DM them, or post in a channel. Call it with your Paperclip API credentials:
|
|
8
|
+
|
|
9
|
+
B="\${PAPERCLIP_API_URL%/}"; B="\${B%/api}/api/plugins/mattermost-bridge/api"
|
|
10
|
+
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" "$B/mm/channels"
|
|
11
|
+
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" -H "content-type: application/json" \\
|
|
12
|
+
-d '{"person":"username","text":"..."}' "$B/mm/dm"
|
|
13
|
+
|
|
14
|
+
Answers are JSON. A 202 with "waiting_for_approval" means a person has been asked to approve;
|
|
15
|
+
tell whoever you are working with and carry on. A 403 says why something is not allowed.
|
|
16
|
+
|
|
17
|
+
## How to work
|
|
18
|
+
|
|
19
|
+
- **Pick the size of the work.** A quick question: answer in the thread. A task: create a Paperclip
|
|
20
|
+
task (assign it to yourself or delegate it) and say so in the thread with its identifier. An
|
|
21
|
+
investigation, incident or project, or anything involving several agents or more than a day:
|
|
22
|
+
create the task, then open a work channel for it (below), with the person who asked and whoever
|
|
23
|
+
you need. Name it for what it is: "inc checkout 5xx", "inv q3 conversion drop", "proj crm migration".
|
|
24
|
+
- **Announce a work channel where the request came in**, in one message: what it is for, who you
|
|
25
|
+
pulled in, and that you will report back there. When the task is done or cancelled the plugin
|
|
26
|
+
archives the channel and posts the outcome back where you asked; you do not need to.
|
|
27
|
+
- **Hand work to another agent by mentioning it** (@cto) in a channel or thread it is in; invite it
|
|
28
|
+
first if it is not. Give it the context it needs in that message. It replies in the thread.
|
|
29
|
+
Mention an agent only when you want it to act: every mention starts it.
|
|
30
|
+
- **Say something before going quiet.** If an answer will take a while, post one line ("On it,
|
|
31
|
+
checking what shipped this morning") and then do the work.
|
|
32
|
+
- Do not write @channel, @all or @here unless everyone in the channel must be notified: it needs
|
|
33
|
+
approval.
|
|
34
|
+
|
|
35
|
+
## Calls
|
|
36
|
+
|
|
37
|
+
Reading (GET):
|
|
38
|
+
- /mm/channels channels you are in
|
|
39
|
+
- /mm/channels/{channel}/posts?limit=30 recent posts in a channel (oldest first)
|
|
40
|
+
- /mm/posts/{postId}/thread a whole thread
|
|
41
|
+
- /mm/channels/{channel}/members who is in a channel (people and agents)
|
|
42
|
+
- /mm/search?q=words search messages in your channels
|
|
43
|
+
- /mm/people?q=name find people and agents by name
|
|
44
|
+
- /mm/requests/{id} an approval request you made
|
|
45
|
+
|
|
46
|
+
Talking (POST, JSON body):
|
|
47
|
+
- /mm/post {"channel": "...", "text": "...", "threadId": "optional post id to reply to"}
|
|
48
|
+
- /mm/react {"postId": "...", "emoji": "white_check_mark"}
|
|
49
|
+
- /mm/dm {"person": "username", "text": "..."} only people allowed to talk to you
|
|
50
|
+
|
|
51
|
+
Channels (POST, JSON body):
|
|
52
|
+
- /mm/channels {"name": "inc checkout 5xx", "purpose": "...", "people": ["username"],
|
|
53
|
+
"agents": ["cto"], "taskId": "BAD-123 or task id", "private": true}
|
|
54
|
+
Private by default. A public channel needs approval. With taskId, the channel is tied to the
|
|
55
|
+
task: its comments and status show up there, and it is archived when the task closes.
|
|
56
|
+
- /mm/channels/{channel}/invite {"people": [...], "agents": [...]} free on channels you created
|
|
57
|
+
- /mm/channels/{channel}/update {"purpose": "...", "header": "...", "name": "..."} yours only
|
|
58
|
+
- /mm/channels/{channel}/archive {} yours only
|
|
59
|
+
|
|
60
|
+
{channel} is a channel's id, its URL name (inc-checkout-5xx) or its display name.
|
|
61
|
+
`;
|
|
62
|
+
var SKILL = `---
|
|
63
|
+
name: mattermost
|
|
64
|
+
description: >
|
|
65
|
+
Use to talk to people and agents in Mattermost as your own bot: DM someone, post or reply in
|
|
66
|
+
a channel, read and search channels you are in, hand work to another agent by mentioning it,
|
|
67
|
+
and open a work channel for an investigation, incident or project tied to a Paperclip task.
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
${HELP}`;
|
|
71
|
+
|
|
1
72
|
// src/manifest.ts
|
|
2
73
|
var route = (routeKey, method, path) => ({ routeKey, method, path, auth: "agent", capability: "api.routes.register" });
|
|
3
74
|
var manifest = {
|
|
4
75
|
id: "mattermost-bridge",
|
|
5
76
|
apiVersion: 1,
|
|
6
|
-
version: "0.
|
|
77
|
+
version: "0.14.0",
|
|
7
78
|
displayName: "Mattermost",
|
|
8
79
|
description: "Every agent becomes its own Mattermost bot. DM an agent or mention it in a channel and it answers in the thread, showing its steps as it works; follow-ups continue the conversation. Paperclip approvals arrive with Approve / Reject buttons.",
|
|
9
80
|
author: "Haseeb Azhar",
|
|
@@ -29,7 +100,8 @@ var manifest = {
|
|
|
29
100
|
"issue.comments.create",
|
|
30
101
|
"issue.comments.create_human_attributed",
|
|
31
102
|
"issue.comments.read",
|
|
32
|
-
"api.routes.register"
|
|
103
|
+
"api.routes.register",
|
|
104
|
+
"skills.managed"
|
|
33
105
|
],
|
|
34
106
|
entrypoints: { worker: "./dist/worker.js", ui: "./dist/ui" },
|
|
35
107
|
// Replaces the generated form: approvers picked from members, URL detected and checked.
|
|
@@ -53,6 +125,8 @@ var manifest = {
|
|
|
53
125
|
route("update-channel", "POST", "/mm/channels/:channel/update"),
|
|
54
126
|
route("archive", "POST", "/mm/channels/:channel/archive")
|
|
55
127
|
],
|
|
128
|
+
// Installed in each company's skill library; the settings page adds it to every agent with a bot.
|
|
129
|
+
skills: [{ skillKey: "mattermost", displayName: "Mattermost", slug: "mattermost", description: "Talk to people and agents in Mattermost as your own bot.", markdown: SKILL }],
|
|
56
130
|
webhooks: [
|
|
57
131
|
{ endpointKey: "actions", displayName: "Mattermost buttons", description: "Approve / Reject clicks on approval posts. Every click carries a signature the plugin checks." }
|
|
58
132
|
],
|
package/dist/ui/index.js
CHANGED
|
@@ -203,6 +203,8 @@ function SettingsPage({ context }) {
|
|
|
203
203
|
const [saved, setSaved] = useState(false);
|
|
204
204
|
const [check, setCheck] = useState(null);
|
|
205
205
|
const verify = usePluginAction("verify-url");
|
|
206
|
+
const skill = usePluginAction("skill");
|
|
207
|
+
const [skillNote, setSkillNote] = useState(null);
|
|
206
208
|
useEffect(() => {
|
|
207
209
|
if (!companyId) return;
|
|
208
210
|
Promise.all([
|
|
@@ -264,8 +266,22 @@ function SettingsPage({ context }) {
|
|
|
264
266
|
else next.add(id);
|
|
265
267
|
setSelected(next);
|
|
266
268
|
};
|
|
269
|
+
async function giveSkill(targets) {
|
|
270
|
+
try {
|
|
271
|
+
const { key } = await skill({ companyId });
|
|
272
|
+
if (!key) return "The Mattermost skill is not installed yet; save again in a minute.";
|
|
273
|
+
const failed = [];
|
|
274
|
+
for (const a of targets) {
|
|
275
|
+
await api(`/api/agents/${a.id}/skills/sync`, { method: "POST", body: JSON.stringify({ mode: "add", desiredSkills: [key] }) }).catch(() => failed.push(a.name));
|
|
276
|
+
}
|
|
277
|
+
return failed.length ? `Could not give the Mattermost skill to ${failed.join(", ")}; add it on their Skills tab.` : null;
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return `Could not give agents the Mattermost skill: ${String(e?.message ?? e).slice(0, 200)}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
267
282
|
async function save() {
|
|
268
283
|
setSaveError(null);
|
|
284
|
+
setSkillNote(null);
|
|
269
285
|
const errors = validate(config);
|
|
270
286
|
if (agentsMode === "selected" && !pickedAgents.size) errors.agents = "Tick at least one agent, or choose All agents.";
|
|
271
287
|
if (access === "selected" && !grants.size) errors.access = "Tick at least one person, or choose Everyone.";
|
|
@@ -283,6 +299,7 @@ function SettingsPage({ context }) {
|
|
|
283
299
|
await api(`/api/plugins/${PLUGIN}/config`, { method: "POST", body: JSON.stringify({ companyId, configJson: next }) });
|
|
284
300
|
setConfig(next);
|
|
285
301
|
setSaved(true);
|
|
302
|
+
setSkillNote(await giveSkill(agentsMode === "selected" ? agents.filter((a) => pickedAgents.has(a.id)) : agents));
|
|
286
303
|
} catch (e) {
|
|
287
304
|
setSaveError(saveErrorText(String(e?.message ?? e)));
|
|
288
305
|
} finally {
|
|
@@ -334,7 +351,7 @@ function SettingsPage({ context }) {
|
|
|
334
351
|
onChange: (e) => set({ agentTurnLimit: Math.min(100, Math.max(1, Math.round(Number(e.target.value) || 10))) })
|
|
335
352
|
}
|
|
336
353
|
) }),
|
|
337
|
-
/* @__PURE__ */ jsxs(Field, { label: "Agents in Mattermost", error: fieldErrors.agents, help: "Each agent shown here gets its own Mattermost bot. Unticking an agent switches its bot off but keeps it, with its history, for when you tick it again.", children: [
|
|
354
|
+
/* @__PURE__ */ jsxs(Field, { label: "Agents in Mattermost", error: fieldErrors.agents, help: "Each agent shown here gets its own Mattermost bot, and saving gives it the Mattermost skill so it can message people from any run. Unticking an agent switches its bot off but keeps it, with its history, for when you tick it again.", children: [
|
|
338
355
|
/* @__PURE__ */ jsx(
|
|
339
356
|
Modes,
|
|
340
357
|
{
|
|
@@ -380,7 +397,8 @@ function SettingsPage({ context }) {
|
|
|
380
397
|
/* @__PURE__ */ jsxs("div", { style: s.row, children: [
|
|
381
398
|
/* @__PURE__ */ jsx("button", { style: s.primary, onClick: save, disabled: saving, children: saving ? "Saving\u2026" : "Save" }),
|
|
382
399
|
saved ? /* @__PURE__ */ jsx("span", { style: s.ok, children: "Saved" }) : null,
|
|
383
|
-
saveError ? /* @__PURE__ */ jsx("span", { style: s.bad, children: saveError }) : null
|
|
400
|
+
saveError ? /* @__PURE__ */ jsx("span", { style: s.bad, children: saveError }) : null,
|
|
401
|
+
skillNote ? /* @__PURE__ */ jsx("span", { style: s.bad, children: skillNote }) : null
|
|
384
402
|
] })
|
|
385
403
|
] });
|
|
386
404
|
}
|
package/dist/worker.js
CHANGED
|
@@ -45426,10 +45426,14 @@ _~${name} is archived._`
|
|
|
45426
45426
|
var HELP = `# Working in Mattermost
|
|
45427
45427
|
|
|
45428
45428
|
You have your own Mattermost account (a bot). Everything below runs as you, in your company's
|
|
45429
|
-
Mattermost, and you can only see and use channels you are a member of.
|
|
45430
|
-
|
|
45429
|
+
Mattermost, and you can only see and use channels you are a member of. Use it whenever you need
|
|
45430
|
+
to reach a person or another agent in Mattermost, including from a task or a scheduled run: to
|
|
45431
|
+
ping someone, DM them, or post in a channel. Call it with your Paperclip API credentials:
|
|
45431
45432
|
|
|
45432
|
-
|
|
45433
|
+
B="\${PAPERCLIP_API_URL%/}"; B="\${B%/api}/api/plugins/mattermost-bridge/api"
|
|
45434
|
+
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" "$B/mm/channels"
|
|
45435
|
+
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" -H "content-type: application/json" \\
|
|
45436
|
+
-d '{"person":"username","text":"..."}' "$B/mm/dm"
|
|
45433
45437
|
|
|
45434
45438
|
Answers are JSON. A 202 with "waiting_for_approval" means a person has been asked to approve;
|
|
45435
45439
|
tell whoever you are working with and carry on. A 403 says why something is not allowed.
|
|
@@ -45479,6 +45483,15 @@ Channels (POST, JSON body):
|
|
|
45479
45483
|
|
|
45480
45484
|
{channel} is a channel's id, its URL name (inc-checkout-5xx) or its display name.
|
|
45481
45485
|
`;
|
|
45486
|
+
var SKILL = `---
|
|
45487
|
+
name: mattermost
|
|
45488
|
+
description: >
|
|
45489
|
+
Use to talk to people and agents in Mattermost as your own bot: DM someone, post or reply in
|
|
45490
|
+
a channel, read and search channels you are in, hand work to another agent by mentioning it,
|
|
45491
|
+
and open a work channel for an investigation, incident or project tied to a Paperclip task.
|
|
45492
|
+
---
|
|
45493
|
+
|
|
45494
|
+
${HELP}`;
|
|
45482
45495
|
|
|
45483
45496
|
// src/worker.ts
|
|
45484
45497
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -46198,7 +46211,7 @@ var route = (routeKey, method, path3) => ({ routeKey, method, path: path3, auth:
|
|
|
46198
46211
|
var manifest = {
|
|
46199
46212
|
id: "mattermost-bridge",
|
|
46200
46213
|
apiVersion: 1,
|
|
46201
|
-
version: "0.
|
|
46214
|
+
version: "0.14.0",
|
|
46202
46215
|
displayName: "Mattermost",
|
|
46203
46216
|
description: "Every agent becomes its own Mattermost bot. DM an agent or mention it in a channel and it answers in the thread, showing its steps as it works; follow-ups continue the conversation. Paperclip approvals arrive with Approve / Reject buttons.",
|
|
46204
46217
|
author: "Haseeb Azhar",
|
|
@@ -46224,7 +46237,8 @@ var manifest = {
|
|
|
46224
46237
|
"issue.comments.create",
|
|
46225
46238
|
"issue.comments.create_human_attributed",
|
|
46226
46239
|
"issue.comments.read",
|
|
46227
|
-
"api.routes.register"
|
|
46240
|
+
"api.routes.register",
|
|
46241
|
+
"skills.managed"
|
|
46228
46242
|
],
|
|
46229
46243
|
entrypoints: { worker: "./dist/worker.js", ui: "./dist/ui" },
|
|
46230
46244
|
// Replaces the generated form: approvers picked from members, URL detected and checked.
|
|
@@ -46248,6 +46262,8 @@ var manifest = {
|
|
|
46248
46262
|
route("update-channel", "POST", "/mm/channels/:channel/update"),
|
|
46249
46263
|
route("archive", "POST", "/mm/channels/:channel/archive")
|
|
46250
46264
|
],
|
|
46265
|
+
// Installed in each company's skill library; the settings page adds it to every agent with a bot.
|
|
46266
|
+
skills: [{ skillKey: "mattermost", displayName: "Mattermost", slug: "mattermost", description: "Talk to people and agents in Mattermost as your own bot.", markdown: SKILL }],
|
|
46251
46267
|
webhooks: [
|
|
46252
46268
|
{ endpointKey: "actions", displayName: "Mattermost buttons", description: "Approve / Reject clicks on approval posts. Every click carries a signature the plugin checks." }
|
|
46253
46269
|
],
|
|
@@ -46551,6 +46567,7 @@ var Tenant = class {
|
|
|
46551
46567
|
});
|
|
46552
46568
|
void sync();
|
|
46553
46569
|
this.timers.push(setInterval(sync, SYNC_EVERY_MS));
|
|
46570
|
+
void ensureSkill(companyId).catch((err) => warn("could not install the Mattermost skill", { err: errText(err) }));
|
|
46554
46571
|
if (cards) {
|
|
46555
46572
|
const poll = () => cards.poll().catch((err) => warn("decision cards poll failed", { err: errText(err) }));
|
|
46556
46573
|
this.timers.push(setTimeout(poll, 2e4), setInterval(poll, CARDS_EVERY_MS));
|
|
@@ -46675,6 +46692,11 @@ function configure(companyId, config2) {
|
|
|
46675
46692
|
});
|
|
46676
46693
|
return run;
|
|
46677
46694
|
}
|
|
46695
|
+
async function ensureSkill(companyId) {
|
|
46696
|
+
let r = await ctx.skills.managed.reconcile("mattermost", companyId);
|
|
46697
|
+
if (r.skill && r.defaultDrift?.changedFiles.length) r = await ctx.skills.managed.reset("mattermost", companyId);
|
|
46698
|
+
return r.skill?.key ?? null;
|
|
46699
|
+
}
|
|
46678
46700
|
async function saveBoot(companyId, config2) {
|
|
46679
46701
|
const all = await ctx.state.get(BOOT) ?? {};
|
|
46680
46702
|
all[companyId] = config2;
|
|
@@ -46704,6 +46726,10 @@ var plugin = definePlugin({
|
|
|
46704
46726
|
}
|
|
46705
46727
|
return { detectedUrl: await detectPublicUrl(), connected: Boolean(manager), status: tenant?.status ?? null, versionNote, channels, accounts };
|
|
46706
46728
|
});
|
|
46729
|
+
ctx.actions.register("skill", async (params) => {
|
|
46730
|
+
if (typeof params.companyId !== "string") throw new Error("companyId is required");
|
|
46731
|
+
return { key: await ensureSkill(params.companyId) };
|
|
46732
|
+
});
|
|
46707
46733
|
ctx.actions.register("verify-url", async (params) => verifyUrl(String(params.url ?? ""), PLUGIN_KEY));
|
|
46708
46734
|
const quietly = (what, fn) => fn().catch((err) => ctx.logger.warn(what, { err: errText(err) }));
|
|
46709
46735
|
ctx.events.on("approval.created", (e) => quietly("could not post approval", async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-mattermost",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Paperclip agents in Mattermost: a bot per agent, live progress, files, approval and decision buttons, and per-person access, for each Paperclip company.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|