paperclip-plugin-mattermost 0.16.0 → 0.17.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 +1 -1
- package/dist/manifest.js +2 -1
- package/dist/worker.js +41 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ PAPERCLIP_REF=<commit> node build.mjs # build for another Paperclip version
|
|
|
84
84
|
Bump `version` in `package.json` and `src/manifest.ts`, commit, then tag and push:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
git tag -a v0.
|
|
87
|
+
git tag -a v0.17.0 -m v0.17.0 && git push origin main v0.17.0
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
GitHub Actions builds, tests and publishes to npm through npm trusted publishing, so no npm token is involved.
|
package/dist/manifest.js
CHANGED
|
@@ -82,13 +82,14 @@ var route = (routeKey, method, path) => ({ routeKey, method, path, auth: "agent"
|
|
|
82
82
|
var manifest = {
|
|
83
83
|
id: "mattermost-bridge",
|
|
84
84
|
apiVersion: 1,
|
|
85
|
-
version: "0.
|
|
85
|
+
version: "0.17.0",
|
|
86
86
|
displayName: "Mattermost",
|
|
87
87
|
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.",
|
|
88
88
|
author: "Haseeb Azhar",
|
|
89
89
|
categories: ["connector"],
|
|
90
90
|
capabilities: [
|
|
91
91
|
"agents.read",
|
|
92
|
+
"companies.read",
|
|
92
93
|
"agent.sessions.create",
|
|
93
94
|
"agent.sessions.send",
|
|
94
95
|
"agent.sessions.list",
|
package/dist/worker.js
CHANGED
|
@@ -45032,7 +45032,9 @@ var Actions = class {
|
|
|
45032
45032
|
this.d = deps;
|
|
45033
45033
|
}
|
|
45034
45034
|
/** One route call. `params` are the route's path parameters, `input` its query or body. */
|
|
45035
|
-
|
|
45035
|
+
runId = null;
|
|
45036
|
+
async handle(agentId, routeKey, params, input2, runId = null) {
|
|
45037
|
+
this.runId = runId;
|
|
45036
45038
|
const bot = this.d.bot(agentId);
|
|
45037
45039
|
if (!bot) return bad(409, "You have no Mattermost bot in this company yet (your agent may be switched off in the plugin's settings).");
|
|
45038
45040
|
try {
|
|
@@ -45184,13 +45186,13 @@ var Actions = class {
|
|
|
45184
45186
|
const text2 = str3(input2.text);
|
|
45185
45187
|
if (!text2) return bad(400, "text is empty");
|
|
45186
45188
|
const ch = await this.channel(bot, str3(input2.channel));
|
|
45187
|
-
const threadId = str3(input2.threadId);
|
|
45189
|
+
const threadId = str3(input2.threadId) || await this.d.taskThread?.(agentId, this.runId, ch.id).catch(() => null) || "";
|
|
45188
45190
|
const waiting = await this.gate(agentId, canPost(text2), "post", `post in ~${ch.name}: "${text2.slice(0, 200)}"`, { channelId: ch.id, threadId, text: text2 });
|
|
45189
45191
|
if (waiting) return waiting;
|
|
45190
45192
|
return ok(await this.doPost(bot, ch.id, threadId, text2));
|
|
45191
45193
|
}
|
|
45192
45194
|
async doPost(bot, channelId, threadId, text2) {
|
|
45193
|
-
const p = await bot.request("POST", "/posts", { channel_id: channelId, root_id: threadId, message: text2 });
|
|
45195
|
+
const p = await bot.request("POST", "/posts", { channel_id: channelId, root_id: threadId, message: this.d.format ? this.d.format(text2) : text2 });
|
|
45194
45196
|
return { posted: p.id, threadId: p.root_id || p.id };
|
|
45195
45197
|
}
|
|
45196
45198
|
async react(bot, input2) {
|
|
@@ -45207,7 +45209,8 @@ var Actions = class {
|
|
|
45207
45209
|
const verdict = canDm(await this.d.canTalk(user2.id, agentId));
|
|
45208
45210
|
if (!verdict.ok) return bad(403, `Not allowed: ${verdict.why}.`);
|
|
45209
45211
|
const channel2 = await bot.request("POST", "/channels/direct", [bot.botUserId, user2.id]);
|
|
45210
|
-
|
|
45212
|
+
const threadId = str3(input2.threadId) || await this.d.taskThread?.(agentId, this.runId, channel2.id).catch(() => null) || "";
|
|
45213
|
+
return ok(await this.doPost(bot, channel2.id, threadId, text2));
|
|
45211
45214
|
}
|
|
45212
45215
|
// ---- channels ----------------------------------------------------------------------
|
|
45213
45216
|
async created() {
|
|
@@ -45526,6 +45529,13 @@ function absoluteLinks(text2, base) {
|
|
|
45526
45529
|
const root = base.replace(/\/+$/, "");
|
|
45527
45530
|
return text2.replace(/\]\((\/(?!\/)[^)\s]*)\)/g, (_m, path3) => `](${root}${path3})`);
|
|
45528
45531
|
}
|
|
45532
|
+
function linkTasks(text2, base, prefix) {
|
|
45533
|
+
if (!base || !/^[A-Z][A-Z0-9]{0,15}$/.test(prefix)) return text2;
|
|
45534
|
+
const root = base.replace(/\/+$/, "");
|
|
45535
|
+
const skip = /(```[\s\S]*?```|`[^`\n]*`|\[[^\]]*\]\([^)]*\)|<[^>\s]+>|https?:\/\/[^\s)]+)/;
|
|
45536
|
+
const id = new RegExp(`(?<![\\w/-])(${prefix}-\\d+)(?![\\w-])`, "g");
|
|
45537
|
+
return text2.split(skip).map((part, i) => i % 2 ? part : part.replace(id, (m) => `[${m}](${root}/${prefix}/issues/${m})`)).join("");
|
|
45538
|
+
}
|
|
45529
45539
|
function splitMessage(text2, max = MAX_POST) {
|
|
45530
45540
|
const parts = [];
|
|
45531
45541
|
let rest = text2;
|
|
@@ -45551,8 +45561,9 @@ var Bridge = class {
|
|
|
45551
45561
|
choiceButtons;
|
|
45552
45562
|
canTalk;
|
|
45553
45563
|
turnLimit;
|
|
45554
|
-
/** Paperclip's public URL, for agents' relative links. */
|
|
45564
|
+
/** Paperclip's public URL, for agents' relative links, and the company's task prefix (BAD). */
|
|
45555
45565
|
linkBase = "";
|
|
45566
|
+
taskPrefix = "";
|
|
45556
45567
|
/**
|
|
45557
45568
|
* Steering: messages people send in a thread while its agent is mid-run, kept for that agent to
|
|
45558
45569
|
* read in the same run (`takeInbox`). One it read is not run again afterwards.
|
|
@@ -45774,7 +45785,7 @@ To send files back with your reply, save them in ${outbox}` : "");
|
|
|
45774
45785
|
}
|
|
45775
45786
|
if (this.active.get(member.agentId) === here) this.active.delete(member.agentId);
|
|
45776
45787
|
this.inbox.delete(member.agentId);
|
|
45777
|
-
const { text: body, choices } = extractChoices(absoluteLinks(reply ?? "", this.linkBase));
|
|
45788
|
+
const { text: body, choices } = extractChoices(linkTasks(absoluteLinks(reply ?? "", this.linkBase), this.linkBase, this.taskPrefix));
|
|
45778
45789
|
const origin = { channelId: post.channel_id, rootId, channelType: evt.channelType, channelName: evt.channelName };
|
|
45779
45790
|
const buttons = choices.length && this.choiceButtons ? this.choiceButtons(origin, choices) : null;
|
|
45780
45791
|
const shown = choices.length && !buttons ? `${body}
|
|
@@ -46272,13 +46283,14 @@ var route = (routeKey, method, path3) => ({ routeKey, method, path: path3, auth:
|
|
|
46272
46283
|
var manifest = {
|
|
46273
46284
|
id: "mattermost-bridge",
|
|
46274
46285
|
apiVersion: 1,
|
|
46275
|
-
version: "0.
|
|
46286
|
+
version: "0.17.0",
|
|
46276
46287
|
displayName: "Mattermost",
|
|
46277
46288
|
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.",
|
|
46278
46289
|
author: "Haseeb Azhar",
|
|
46279
46290
|
categories: ["connector"],
|
|
46280
46291
|
capabilities: [
|
|
46281
46292
|
"agents.read",
|
|
46293
|
+
"companies.read",
|
|
46282
46294
|
"agent.sessions.create",
|
|
46283
46295
|
"agent.sessions.send",
|
|
46284
46296
|
"agent.sessions.list",
|
|
@@ -46552,6 +46564,8 @@ var Tenant = class {
|
|
|
46552
46564
|
void bridge.handle(evt);
|
|
46553
46565
|
};
|
|
46554
46566
|
bridge.linkBase = paperclipUrl;
|
|
46567
|
+
const company = await ctx.companies.get(companyId).catch(() => null);
|
|
46568
|
+
bridge.taskPrefix = company?.issuePrefix ?? "";
|
|
46555
46569
|
bridge.onSteer = (agentId, postId) => {
|
|
46556
46570
|
void ctx.events.emit("person-wrote", companyId, { agentId, postId }).catch(() => {
|
|
46557
46571
|
});
|
|
@@ -46598,6 +46612,15 @@ var Tenant = class {
|
|
|
46598
46612
|
agents: () => roster.all().map((m) => ({ agentId: m.agentId, agentName: m.agentName, username: m.username, botUserId: m.botUserId })),
|
|
46599
46613
|
canTalk: async (userId, agentId) => !canTalk || canTalk(userId, agentId),
|
|
46600
46614
|
origin: (agentId) => bridge.activeThread(agentId),
|
|
46615
|
+
format: (text2) => linkTasks(absoluteLinks(text2, paperclipUrl), paperclipUrl, bridge.taskPrefix),
|
|
46616
|
+
// The task this run works on (its execution or checkout run), and the thread it was asked for in.
|
|
46617
|
+
taskThread: async (agentId, runId, channelId) => {
|
|
46618
|
+
if (!runId) return null;
|
|
46619
|
+
const issues = await ctx.issues.list({ companyId, assigneeAgentId: agentId, limit: 200 });
|
|
46620
|
+
const issue2 = issues.find((i) => i.executionRunId === runId || i.checkoutRunId === runId);
|
|
46621
|
+
const at = issue2 ? await store.get(`issuethread:${issue2.id}`) : null;
|
|
46622
|
+
return at && at.channelId === channelId ? at.rootId : null;
|
|
46623
|
+
},
|
|
46601
46624
|
task: async (taskId) => await ctx.issues.get(taskId, companyId),
|
|
46602
46625
|
askApproval: async (req) => {
|
|
46603
46626
|
if (!paperclipUrl || !approvers.size) throw Object.assign(new Error("this needs a person's approval, and nobody can approve from Mattermost yet (the plugin needs its Paperclip public URL and approvers set)"), { status: 403 });
|
|
@@ -46668,9 +46691,9 @@ var Tenant = class {
|
|
|
46668
46691
|
return null;
|
|
46669
46692
|
}
|
|
46670
46693
|
/** An agent's call to one of the plugin's `/mm/...` routes. */
|
|
46671
|
-
async api(agentId, routeKey, params, input2) {
|
|
46694
|
+
async api(agentId, routeKey, params, input2, runId = null) {
|
|
46672
46695
|
if (!this.actions) return { status: 503, body: { error: `Mattermost is not connected for this company (${this.status})` } };
|
|
46673
|
-
return this.actions.handle(agentId, routeKey, params, input2);
|
|
46696
|
+
return this.actions.handle(agentId, routeKey, params, input2, runId);
|
|
46674
46697
|
}
|
|
46675
46698
|
/** The Agent computer plugin: an agent wants a person at its computer. Posted where the agent is working. */
|
|
46676
46699
|
async computerNeedsPerson(p) {
|
|
@@ -46826,6 +46849,14 @@ var plugin = definePlugin({
|
|
|
46826
46849
|
ctx.events.on("plugin.agent-computer.needs-person", (e) => quietly("could not post a computer help request", async () => {
|
|
46827
46850
|
await tenants.get(e.companyId)?.computerNeedsPerson(e.payload ?? {});
|
|
46828
46851
|
}));
|
|
46852
|
+
ctx.events.on("issue.created", (e) => quietly("could not link a task to its thread", async () => {
|
|
46853
|
+
const tenant = tenants.get(e.companyId);
|
|
46854
|
+
if (!tenant?.bridge || !e.entityId) return;
|
|
46855
|
+
const issue2 = await ctx.issues.get(e.entityId, e.companyId);
|
|
46856
|
+
const by = issue2?.createdByAgentId;
|
|
46857
|
+
const at = by ? tenant.bridge.activeThread(by) : null;
|
|
46858
|
+
if (at) await tenant.store().set(`issuethread:${e.entityId}`, at);
|
|
46859
|
+
}));
|
|
46829
46860
|
ctx.events.on("approval.decided", (e) => quietly("could not update approval", async () => {
|
|
46830
46861
|
if (e.entityId) await tenants.get(e.companyId)?.approvals?.decided(e.entityId);
|
|
46831
46862
|
}));
|
|
@@ -46865,7 +46896,7 @@ var plugin = definePlugin({
|
|
|
46865
46896
|
const messages = tenant.bridge ? await tenant.bridge.takeInbox(agentId) : [];
|
|
46866
46897
|
return { status: 200, body: { messages, note: messages.length ? "Written to you while you were working. Act on them now: they are not sent to you again." : "Nothing new." } };
|
|
46867
46898
|
}
|
|
46868
|
-
const res = await tenant.api(agentId, input2.routeKey, input2.params, { ...query, ...body });
|
|
46899
|
+
const res = await tenant.api(agentId, input2.routeKey, input2.params, { ...query, ...body }, input2.actor.runId ?? null);
|
|
46869
46900
|
if (tenant.bridge?.hasInbox(agentId) && res.body && typeof res.body === "object" && !Array.isArray(res.body)) {
|
|
46870
46901
|
res.body = { ...res.body, newMessages: await tenant.bridge.takeInbox(agentId) };
|
|
46871
46902
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-mattermost",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|