paperclip-plugin-mattermost 0.13.0 → 0.15.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 +3 -2
- package/dist/manifest.js +76 -2
- package/dist/ui/index.js +20 -2
- package/dist/worker.js +48 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,10 +13,11 @@ 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.
|
|
20
|
+
- **Agent computer requests.** With the Agent computer plugin installed, an agent that needs a person in its browser (a login, 2FA, a password) posts in the thread it is working in, or the approvals channel, with a link to take over.
|
|
20
21
|
- **One install, many companies.** Each Paperclip company connects its own Mattermost, with its own settings, bots and state.
|
|
21
22
|
|
|
22
23
|
## Requirements
|
|
@@ -82,7 +83,7 @@ PAPERCLIP_REF=<commit> node build.mjs # build for another Paperclip version
|
|
|
82
83
|
Bump `version` in `package.json` and `src/manifest.ts`, commit, then tag and push:
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
|
-
git tag -a v0.
|
|
86
|
+
git tag -a v0.15.0 -m v0.15.0 && git push origin main v0.15.0
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
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.15.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.15.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
|
],
|
|
@@ -46350,6 +46366,8 @@ var Tenant = class {
|
|
|
46350
46366
|
work = null;
|
|
46351
46367
|
key = "";
|
|
46352
46368
|
approvers = /* @__PURE__ */ new Map();
|
|
46369
|
+
paperclipUrl = "";
|
|
46370
|
+
approvalsChannel = "";
|
|
46353
46371
|
timers = [];
|
|
46354
46372
|
agents() {
|
|
46355
46373
|
const companyId = this.companyId;
|
|
@@ -46539,7 +46557,7 @@ var Tenant = class {
|
|
|
46539
46557
|
task: async (issueId) => await ctx.issues.get(issueId, companyId),
|
|
46540
46558
|
comments: async (issueId) => (await ctx.issues.listComments(issueId, companyId)).map((c) => ({ id: c.id, body: c.body, authorAgentId: c.authorAgentId }))
|
|
46541
46559
|
});
|
|
46542
|
-
Object.assign(this, { manager, bots: roster, bridge, approvals, cards, actions, work, key, approvers });
|
|
46560
|
+
Object.assign(this, { manager, bots: roster, bridge, approvals, cards, actions, work, key, approvers, paperclipUrl, approvalsChannel });
|
|
46543
46561
|
this.status = `connecting to ${url2} as @${manager.botUsername}`;
|
|
46544
46562
|
detached(() => {
|
|
46545
46563
|
manager.connect(onPosted);
|
|
@@ -46551,6 +46569,7 @@ var Tenant = class {
|
|
|
46551
46569
|
});
|
|
46552
46570
|
void sync();
|
|
46553
46571
|
this.timers.push(setInterval(sync, SYNC_EVERY_MS));
|
|
46572
|
+
void ensureSkill(companyId).catch((err) => warn("could not install the Mattermost skill", { err: errText(err) }));
|
|
46554
46573
|
if (cards) {
|
|
46555
46574
|
const poll = () => cards.poll().catch((err) => warn("decision cards poll failed", { err: errText(err) }));
|
|
46556
46575
|
this.timers.push(setTimeout(poll, 2e4), setInterval(poll, CARDS_EVERY_MS));
|
|
@@ -46585,6 +46604,17 @@ var Tenant = class {
|
|
|
46585
46604
|
if (!this.actions) return { status: 503, body: { error: `Mattermost is not connected for this company (${this.status})` } };
|
|
46586
46605
|
return this.actions.handle(agentId, routeKey, params, input2);
|
|
46587
46606
|
}
|
|
46607
|
+
/** The Agent computer plugin: an agent wants a person in its browser. Posted where the agent is working. */
|
|
46608
|
+
async computerNeedsPerson(p) {
|
|
46609
|
+
if (!this.manager || !this.bots || !this.bridge || !p.agentId) return;
|
|
46610
|
+
const poster = this.poster(this.manager, this.bots);
|
|
46611
|
+
const where = this.bridge.activeThread(p.agentId) ?? (this.approvalsChannel ? await poster.channel(this.approvalsChannel).then((id) => id ? { channelId: id, rootId: "" } : null) : null);
|
|
46612
|
+
if (!where) return;
|
|
46613
|
+
const link = this.paperclipUrl && p.path ? `${this.paperclipUrl}${p.path}` : null;
|
|
46614
|
+
const on = p.url ? ` on ${p.url}` : "";
|
|
46615
|
+
const message2 = p.kind === "password" ? `:key: I ${p.reason ?? "need a password"}${on}. ${link ? `[Enter it here](${link})` : "Enter it from Computers in Paperclip"}: it goes straight into the page, and I never see it.` : `:raising_hand: I need a person in my browser${on}: ${p.reason ?? "I am stuck"}. ${link ? `[Take over](${link})` : "Take over from Computers in Paperclip"}, then hand it back.`;
|
|
46616
|
+
await poster.post(p.agentId, where, message2, { ...FROM_PAPERCLIP });
|
|
46617
|
+
}
|
|
46588
46618
|
async notify(req, notice) {
|
|
46589
46619
|
if (notice && req.user_id && req.channel_id) {
|
|
46590
46620
|
await this.manager?.request("POST", "/posts/ephemeral", { user_id: req.user_id, post: { channel_id: req.channel_id, message: notice } }).catch(() => {
|
|
@@ -46675,6 +46705,11 @@ function configure(companyId, config2) {
|
|
|
46675
46705
|
});
|
|
46676
46706
|
return run;
|
|
46677
46707
|
}
|
|
46708
|
+
async function ensureSkill(companyId) {
|
|
46709
|
+
let r = await ctx.skills.managed.reconcile("mattermost", companyId);
|
|
46710
|
+
if (r.skill && r.defaultDrift?.changedFiles.length) r = await ctx.skills.managed.reset("mattermost", companyId);
|
|
46711
|
+
return r.skill?.key ?? null;
|
|
46712
|
+
}
|
|
46678
46713
|
async function saveBoot(companyId, config2) {
|
|
46679
46714
|
const all = await ctx.state.get(BOOT) ?? {};
|
|
46680
46715
|
all[companyId] = config2;
|
|
@@ -46704,6 +46739,10 @@ var plugin = definePlugin({
|
|
|
46704
46739
|
}
|
|
46705
46740
|
return { detectedUrl: await detectPublicUrl(), connected: Boolean(manager), status: tenant?.status ?? null, versionNote, channels, accounts };
|
|
46706
46741
|
});
|
|
46742
|
+
ctx.actions.register("skill", async (params) => {
|
|
46743
|
+
if (typeof params.companyId !== "string") throw new Error("companyId is required");
|
|
46744
|
+
return { key: await ensureSkill(params.companyId) };
|
|
46745
|
+
});
|
|
46707
46746
|
ctx.actions.register("verify-url", async (params) => verifyUrl(String(params.url ?? ""), PLUGIN_KEY));
|
|
46708
46747
|
const quietly = (what, fn) => fn().catch((err) => ctx.logger.warn(what, { err: errText(err) }));
|
|
46709
46748
|
ctx.events.on("approval.created", (e) => quietly("could not post approval", async () => {
|
|
@@ -46716,6 +46755,9 @@ var plugin = definePlugin({
|
|
|
46716
46755
|
ctx.events.on("issue.updated", (e) => quietly("could not follow a task update", async () => {
|
|
46717
46756
|
if (e.entityId) await tenants.get(e.companyId)?.work?.updated(e.entityId);
|
|
46718
46757
|
}));
|
|
46758
|
+
ctx.events.on("plugin.agent-computer.needs-person", (e) => quietly("could not post a computer help request", async () => {
|
|
46759
|
+
await tenants.get(e.companyId)?.computerNeedsPerson(e.payload ?? {});
|
|
46760
|
+
}));
|
|
46719
46761
|
ctx.events.on("approval.decided", (e) => quietly("could not update approval", async () => {
|
|
46720
46762
|
if (e.entityId) await tenants.get(e.companyId)?.approvals?.decided(e.entityId);
|
|
46721
46763
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-mattermost",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|