paperclip-plugin-mattermost 0.15.2 → 0.16.1
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 -1
- package/dist/manifest.js +11 -1
- package/dist/worker.js +103 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Your [Paperclip](https://github.com/paperclipai/paperclip) agents as Mattermost
|
|
|
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
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
|
+
- **Steering.** A message you send in a thread while its agent is still working reaches that run: the agent reads it between steps (and an agent waiting at its computer stops waiting to read it), so "stop" or a correction lands mid-task. One it read is marked ✅ and not run again.
|
|
17
18
|
- **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
19
|
- **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
20
|
- **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.
|
|
@@ -83,7 +84,7 @@ PAPERCLIP_REF=<commit> node build.mjs # build for another Paperclip version
|
|
|
83
84
|
Bump `version` in `package.json` and `src/manifest.ts`, commit, then tag and push:
|
|
84
85
|
|
|
85
86
|
```bash
|
|
86
|
-
git tag -a v0.
|
|
87
|
+
git tag -a v0.16.1 -m v0.16.1 && git push origin main v0.16.1
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
GitHub Actions builds, tests and publishes to npm through npm trusted publishing, so no npm token is involved.
|
package/dist/manifest.js
CHANGED
|
@@ -32,6 +32,13 @@ tell whoever you are working with and carry on. A 403 says why something is not
|
|
|
32
32
|
- Do not write @channel, @all or @here unless everyone in the channel must be notified: it needs
|
|
33
33
|
approval.
|
|
34
34
|
|
|
35
|
+
## While you work
|
|
36
|
+
|
|
37
|
+
If people write in the thread you are answering while you work (a correction, "stop", "never mind"),
|
|
38
|
+
it reaches you: GET /mm/inbox returns those messages, and any other /mm call you make carries them
|
|
39
|
+
as \`newMessages\`. Check /mm/inbox between steps of anything that takes more than a minute or two,
|
|
40
|
+
and act on what you find then. Each message is handed to you once; it is not sent again afterwards.
|
|
41
|
+
|
|
35
42
|
## Calls
|
|
36
43
|
|
|
37
44
|
Reading (GET):
|
|
@@ -42,6 +49,7 @@ Reading (GET):
|
|
|
42
49
|
- /mm/search?q=words search messages in your channels
|
|
43
50
|
- /mm/people?q=name find people and agents by name
|
|
44
51
|
- /mm/requests/{id} an approval request you made
|
|
52
|
+
- /mm/inbox what people wrote in your thread while you were working
|
|
45
53
|
|
|
46
54
|
Talking (POST, JSON body):
|
|
47
55
|
- /mm/post {"channel": "...", "text": "...", "threadId": "optional post id to reply to"}
|
|
@@ -74,7 +82,7 @@ var route = (routeKey, method, path) => ({ routeKey, method, path, auth: "agent"
|
|
|
74
82
|
var manifest = {
|
|
75
83
|
id: "mattermost-bridge",
|
|
76
84
|
apiVersion: 1,
|
|
77
|
-
version: "0.
|
|
85
|
+
version: "0.16.1",
|
|
78
86
|
displayName: "Mattermost",
|
|
79
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.",
|
|
80
88
|
author: "Haseeb Azhar",
|
|
@@ -90,6 +98,7 @@ var manifest = {
|
|
|
90
98
|
"plugin.state.write",
|
|
91
99
|
"http.outbound",
|
|
92
100
|
"events.subscribe",
|
|
101
|
+
"events.emit",
|
|
93
102
|
"webhooks.receive",
|
|
94
103
|
"approvals.read",
|
|
95
104
|
"approvals.respond",
|
|
@@ -110,6 +119,7 @@ var manifest = {
|
|
|
110
119
|
// checks the caller is an agent and scopes it to that agent's company.
|
|
111
120
|
apiRoutes: [
|
|
112
121
|
route("help", "GET", "/mm/help"),
|
|
122
|
+
route("inbox", "GET", "/mm/inbox"),
|
|
113
123
|
route("channels", "GET", "/mm/channels"),
|
|
114
124
|
route("channel-posts", "GET", "/mm/channels/:channel/posts"),
|
|
115
125
|
route("thread", "GET", "/mm/posts/:postId/thread"),
|
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,7 +45186,7 @@ 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));
|
|
@@ -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() {
|
|
@@ -45456,6 +45459,13 @@ tell whoever you are working with and carry on. A 403 says why something is not
|
|
|
45456
45459
|
- Do not write @channel, @all or @here unless everyone in the channel must be notified: it needs
|
|
45457
45460
|
approval.
|
|
45458
45461
|
|
|
45462
|
+
## While you work
|
|
45463
|
+
|
|
45464
|
+
If people write in the thread you are answering while you work (a correction, "stop", "never mind"),
|
|
45465
|
+
it reaches you: GET /mm/inbox returns those messages, and any other /mm call you make carries them
|
|
45466
|
+
as \`newMessages\`. Check /mm/inbox between steps of anything that takes more than a minute or two,
|
|
45467
|
+
and act on what you find then. Each message is handed to you once; it is not sent again afterwards.
|
|
45468
|
+
|
|
45459
45469
|
## Calls
|
|
45460
45470
|
|
|
45461
45471
|
Reading (GET):
|
|
@@ -45466,6 +45476,7 @@ Reading (GET):
|
|
|
45466
45476
|
- /mm/search?q=words search messages in your channels
|
|
45467
45477
|
- /mm/people?q=name find people and agents by name
|
|
45468
45478
|
- /mm/requests/{id} an approval request you made
|
|
45479
|
+
- /mm/inbox what people wrote in your thread while you were working
|
|
45469
45480
|
|
|
45470
45481
|
Talking (POST, JSON body):
|
|
45471
45482
|
- /mm/post {"channel": "...", "text": "...", "threadId": "optional post id to reply to"}
|
|
@@ -45513,6 +45524,11 @@ function mentions(text2) {
|
|
|
45513
45524
|
function stripMention(text2, username) {
|
|
45514
45525
|
return text2.replace(new RegExp(`(^|[^\\w@])@${username.replace(/[.-]/g, "\\$&")}\\b[:,]?`, "gi"), "$1").replace(/\s{2,}/g, " ").trim();
|
|
45515
45526
|
}
|
|
45527
|
+
function absoluteLinks(text2, base) {
|
|
45528
|
+
if (!base) return text2;
|
|
45529
|
+
const root = base.replace(/\/+$/, "");
|
|
45530
|
+
return text2.replace(/\]\((\/(?!\/)[^)\s]*)\)/g, (_m, path3) => `](${root}${path3})`);
|
|
45531
|
+
}
|
|
45516
45532
|
function splitMessage(text2, max = MAX_POST) {
|
|
45517
45533
|
const parts = [];
|
|
45518
45534
|
let rest = text2;
|
|
@@ -45538,6 +45554,16 @@ var Bridge = class {
|
|
|
45538
45554
|
choiceButtons;
|
|
45539
45555
|
canTalk;
|
|
45540
45556
|
turnLimit;
|
|
45557
|
+
/** Paperclip's public URL, for agents' relative links. */
|
|
45558
|
+
linkBase = "";
|
|
45559
|
+
/**
|
|
45560
|
+
* Steering: messages people send in a thread while its agent is mid-run, kept for that agent to
|
|
45561
|
+
* read in the same run (`takeInbox`). One it read is not run again afterwards.
|
|
45562
|
+
*/
|
|
45563
|
+
inbox = /* @__PURE__ */ new Map();
|
|
45564
|
+
delivered = /* @__PURE__ */ new Set();
|
|
45565
|
+
/** Told when a person writes to an agent that is busy (the computer plugin wakes a waiting agent). */
|
|
45566
|
+
onSteer = null;
|
|
45541
45567
|
constructor(roster, directory, agents, store, log = () => {
|
|
45542
45568
|
}, files = null, choiceButtons = null, canTalk = null, turnLimit = DEFAULT_TURN_LIMIT) {
|
|
45543
45569
|
this.files = files;
|
|
@@ -45559,6 +45585,7 @@ var Bridge = class {
|
|
|
45559
45585
|
this.seen.add(evt.post.id);
|
|
45560
45586
|
if (this.seen.size > 5e3) this.seen.delete(this.seen.values().next().value);
|
|
45561
45587
|
const rootId = evt.post.root_id || evt.post.id;
|
|
45588
|
+
this.steer(evt, rootId);
|
|
45562
45589
|
const prev = this.queues.get(rootId) ?? Promise.resolve();
|
|
45563
45590
|
const next = prev.then(() => this.process(evt, rootId)).catch((err) => this.log("bridge error", { err: String(err) }));
|
|
45564
45591
|
this.queues.set(rootId, next);
|
|
@@ -45567,6 +45594,36 @@ var Bridge = class {
|
|
|
45567
45594
|
});
|
|
45568
45595
|
return next;
|
|
45569
45596
|
}
|
|
45597
|
+
/** A person writing in a thread whose agent is mid-run: offered to that run as well as queued. */
|
|
45598
|
+
steer(evt, rootId) {
|
|
45599
|
+
const { post } = evt;
|
|
45600
|
+
if (!post.root_id || post.type || post.props?.from_webhook === "true" || post.props?.from_paperclip === "true" || post.props?.from_bot === "true") return;
|
|
45601
|
+
if (this.roster().isBot(post.user_id) || !post.message?.trim()) return;
|
|
45602
|
+
for (const [agentId, at] of this.active) {
|
|
45603
|
+
if (at.rootId !== rootId) continue;
|
|
45604
|
+
const list2 = this.inbox.get(agentId) ?? [];
|
|
45605
|
+
list2.push({ id: post.id, rootId, userId: post.user_id, text: post.message.slice(0, 4e3), at: Date.now() });
|
|
45606
|
+
this.inbox.set(agentId, list2.slice(-20));
|
|
45607
|
+
this.onSteer?.(agentId, post.id);
|
|
45608
|
+
}
|
|
45609
|
+
}
|
|
45610
|
+
/** Whether the agent has unread messages from people in the thread it is answering. */
|
|
45611
|
+
hasInbox(agentId) {
|
|
45612
|
+
return (this.inbox.get(agentId)?.length ?? 0) > 0;
|
|
45613
|
+
}
|
|
45614
|
+
/** What people wrote in the agent's thread since its run started; each is handed over once. */
|
|
45615
|
+
async takeInbox(agentId) {
|
|
45616
|
+
const list2 = this.inbox.get(agentId) ?? [];
|
|
45617
|
+
this.inbox.delete(agentId);
|
|
45618
|
+
const out = [];
|
|
45619
|
+
for (const m of list2) {
|
|
45620
|
+
this.delivered.add(m.id);
|
|
45621
|
+
const who = await this.directory.userName(m.userId).catch(() => ({ username: m.userId, displayName: m.userId }));
|
|
45622
|
+
out.push({ from: `${who.displayName} (@${who.username})`, text: m.text, at: new Date(m.at).toISOString() });
|
|
45623
|
+
}
|
|
45624
|
+
if (this.delivered.size > 2e3) this.delivered.delete(this.delivered.values().next().value);
|
|
45625
|
+
return out;
|
|
45626
|
+
}
|
|
45570
45627
|
/** The thread an agent is answering right now, for posts its run causes (approvals). */
|
|
45571
45628
|
activeThread(agentId) {
|
|
45572
45629
|
return this.active.get(agentId) ?? null;
|
|
@@ -45644,6 +45701,11 @@ var Bridge = class {
|
|
|
45644
45701
|
if (!fromAgent && post.root_id) await this.resetTurns(rootId);
|
|
45645
45702
|
return;
|
|
45646
45703
|
}
|
|
45704
|
+
if (this.delivered.has(post.id)) {
|
|
45705
|
+
await member.chat.react(post.id, "white_check_mark").catch(() => {
|
|
45706
|
+
});
|
|
45707
|
+
return;
|
|
45708
|
+
}
|
|
45647
45709
|
if (!await this.allowAgentTurn(rootId, fromAgent, member, post.channel_id)) return;
|
|
45648
45710
|
const text2 = stripMention(post.message, member.username);
|
|
45649
45711
|
const fileIds = this.files ? post.file_ids ?? [] : [];
|
|
@@ -45680,7 +45742,7 @@ var Bridge = class {
|
|
|
45680
45742
|
const attached = fileIds.length ? await this.files.receive(member.chat, post.id, fileIds) : [];
|
|
45681
45743
|
const outbox = this.files ? await this.files.outbox(post.id).catch(() => null) : null;
|
|
45682
45744
|
const fromAgent = this.roster().isBot(post.user_id);
|
|
45683
|
-
const prompt = `${sender.displayName} (@${sender.username}${fromAgent ? ", another agent" : ""}) wrote to you (@${member.username}) in ${where} on Mattermost. Your reply is posted back there, so answer in Markdown. To offer choices, end your reply with one line \`choices: First | Second\` (2 to ${MAX_CHOICES} short options); they are shown as buttons, and the pick comes back as the next message. You can also read channels, post, message people, mention other agents and open work channels in Mattermost: GET <your Paperclip API URL>/api/plugins/mattermost-bridge/api/mm/help shows how.` + (history ? `
|
|
45745
|
+
const prompt = `${sender.displayName} (@${sender.username}${fromAgent ? ", another agent" : ""}) wrote to you (@${member.username}) in ${where} on Mattermost. Your reply is posted back there, so answer in Markdown. To offer choices, end your reply with one line \`choices: First | Second\` (2 to ${MAX_CHOICES} short options); they are shown as buttons, and the pick comes back as the next message. You can also read channels, post, message people, mention other agents and open work channels in Mattermost: GET <your Paperclip API URL>/api/plugins/mattermost-bridge/api/mm/help shows how. If this takes more than a minute or two, check GET \u2026/api/plugins/mattermost-bridge/api/mm/inbox between steps: it returns anything people write in this thread meanwhile (a correction, "stop", "never mind"); act on it then, as it is not sent to you again.` + (history ? `
|
|
45684
45746
|
|
|
45685
45747
|
Earlier in this thread (oldest first):
|
|
45686
45748
|
${history}
|
|
@@ -45709,11 +45771,13 @@ To send files back with your reply, save them in ${outbox}` : "");
|
|
|
45709
45771
|
}
|
|
45710
45772
|
} catch (err) {
|
|
45711
45773
|
if (this.active.get(member.agentId) === here) this.active.delete(member.agentId);
|
|
45774
|
+
this.inbox.delete(member.agentId);
|
|
45712
45775
|
await member.chat.createPost(post.channel_id, rootId, `:warning: ${member.agentName} could not answer: ${String(err?.message ?? err).slice(0, 500)}`);
|
|
45713
45776
|
return;
|
|
45714
45777
|
}
|
|
45715
45778
|
if (this.active.get(member.agentId) === here) this.active.delete(member.agentId);
|
|
45716
|
-
|
|
45779
|
+
this.inbox.delete(member.agentId);
|
|
45780
|
+
const { text: body, choices } = extractChoices(absoluteLinks(reply ?? "", this.linkBase));
|
|
45717
45781
|
const origin = { channelId: post.channel_id, rootId, channelType: evt.channelType, channelName: evt.channelName };
|
|
45718
45782
|
const buttons = choices.length && this.choiceButtons ? this.choiceButtons(origin, choices) : null;
|
|
45719
45783
|
const shown = choices.length && !buttons ? `${body}
|
|
@@ -46211,7 +46275,7 @@ var route = (routeKey, method, path3) => ({ routeKey, method, path: path3, auth:
|
|
|
46211
46275
|
var manifest = {
|
|
46212
46276
|
id: "mattermost-bridge",
|
|
46213
46277
|
apiVersion: 1,
|
|
46214
|
-
version: "0.
|
|
46278
|
+
version: "0.16.1",
|
|
46215
46279
|
displayName: "Mattermost",
|
|
46216
46280
|
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.",
|
|
46217
46281
|
author: "Haseeb Azhar",
|
|
@@ -46227,6 +46291,7 @@ var manifest = {
|
|
|
46227
46291
|
"plugin.state.write",
|
|
46228
46292
|
"http.outbound",
|
|
46229
46293
|
"events.subscribe",
|
|
46294
|
+
"events.emit",
|
|
46230
46295
|
"webhooks.receive",
|
|
46231
46296
|
"approvals.read",
|
|
46232
46297
|
"approvals.respond",
|
|
@@ -46247,6 +46312,7 @@ var manifest = {
|
|
|
46247
46312
|
// checks the caller is an agent and scopes it to that agent's company.
|
|
46248
46313
|
apiRoutes: [
|
|
46249
46314
|
route("help", "GET", "/mm/help"),
|
|
46315
|
+
route("inbox", "GET", "/mm/inbox"),
|
|
46250
46316
|
route("channels", "GET", "/mm/channels"),
|
|
46251
46317
|
route("channel-posts", "GET", "/mm/channels/:channel/posts"),
|
|
46252
46318
|
route("thread", "GET", "/mm/posts/:postId/thread"),
|
|
@@ -46488,6 +46554,11 @@ var Tenant = class {
|
|
|
46488
46554
|
onPosted = (evt) => {
|
|
46489
46555
|
void bridge.handle(evt);
|
|
46490
46556
|
};
|
|
46557
|
+
bridge.linkBase = paperclipUrl;
|
|
46558
|
+
bridge.onSteer = (agentId, postId) => {
|
|
46559
|
+
void ctx.events.emit("person-wrote", companyId, { agentId, postId }).catch(() => {
|
|
46560
|
+
});
|
|
46561
|
+
};
|
|
46491
46562
|
const approvers = parseApprovers(config2.approvers);
|
|
46492
46563
|
const approvalsChannel = typeof config2.approvalsChannel === "string" ? config2.approvalsChannel.trim() : "";
|
|
46493
46564
|
const agentName = (agentId) => agentId && roster.roster().byAgentId(agentId)?.agentName || "Paperclip";
|
|
@@ -46530,6 +46601,14 @@ var Tenant = class {
|
|
|
46530
46601
|
agents: () => roster.all().map((m) => ({ agentId: m.agentId, agentName: m.agentName, username: m.username, botUserId: m.botUserId })),
|
|
46531
46602
|
canTalk: async (userId, agentId) => !canTalk || canTalk(userId, agentId),
|
|
46532
46603
|
origin: (agentId) => bridge.activeThread(agentId),
|
|
46604
|
+
// The task this run works on (its execution or checkout run), and the thread it was asked for in.
|
|
46605
|
+
taskThread: async (agentId, runId, channelId) => {
|
|
46606
|
+
if (!runId) return null;
|
|
46607
|
+
const issues = await ctx.issues.list({ companyId, assigneeAgentId: agentId, limit: 200 });
|
|
46608
|
+
const issue2 = issues.find((i) => i.executionRunId === runId || i.checkoutRunId === runId);
|
|
46609
|
+
const at = issue2 ? await store.get(`issuethread:${issue2.id}`) : null;
|
|
46610
|
+
return at && at.channelId === channelId ? at.rootId : null;
|
|
46611
|
+
},
|
|
46533
46612
|
task: async (taskId) => await ctx.issues.get(taskId, companyId),
|
|
46534
46613
|
askApproval: async (req) => {
|
|
46535
46614
|
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 });
|
|
@@ -46600,9 +46679,9 @@ var Tenant = class {
|
|
|
46600
46679
|
return null;
|
|
46601
46680
|
}
|
|
46602
46681
|
/** An agent's call to one of the plugin's `/mm/...` routes. */
|
|
46603
|
-
async api(agentId, routeKey, params, input2) {
|
|
46682
|
+
async api(agentId, routeKey, params, input2, runId = null) {
|
|
46604
46683
|
if (!this.actions) return { status: 503, body: { error: `Mattermost is not connected for this company (${this.status})` } };
|
|
46605
|
-
return this.actions.handle(agentId, routeKey, params, input2);
|
|
46684
|
+
return this.actions.handle(agentId, routeKey, params, input2, runId);
|
|
46606
46685
|
}
|
|
46607
46686
|
/** The Agent computer plugin: an agent wants a person at its computer. Posted where the agent is working. */
|
|
46608
46687
|
async computerNeedsPerson(p) {
|
|
@@ -46758,6 +46837,14 @@ var plugin = definePlugin({
|
|
|
46758
46837
|
ctx.events.on("plugin.agent-computer.needs-person", (e) => quietly("could not post a computer help request", async () => {
|
|
46759
46838
|
await tenants.get(e.companyId)?.computerNeedsPerson(e.payload ?? {});
|
|
46760
46839
|
}));
|
|
46840
|
+
ctx.events.on("issue.created", (e) => quietly("could not link a task to its thread", async () => {
|
|
46841
|
+
const tenant = tenants.get(e.companyId);
|
|
46842
|
+
if (!tenant?.bridge || !e.entityId) return;
|
|
46843
|
+
const issue2 = await ctx.issues.get(e.entityId, e.companyId);
|
|
46844
|
+
const by = issue2?.createdByAgentId;
|
|
46845
|
+
const at = by ? tenant.bridge.activeThread(by) : null;
|
|
46846
|
+
if (at) await tenant.store().set(`issuethread:${e.entityId}`, at);
|
|
46847
|
+
}));
|
|
46761
46848
|
ctx.events.on("approval.decided", (e) => quietly("could not update approval", async () => {
|
|
46762
46849
|
if (e.entityId) await tenants.get(e.companyId)?.approvals?.decided(e.entityId);
|
|
46763
46850
|
}));
|
|
@@ -46793,7 +46880,14 @@ var plugin = definePlugin({
|
|
|
46793
46880
|
if (!tenant) return { status: 503, body: { error: "Mattermost is not set up for your company." } };
|
|
46794
46881
|
const query = Object.fromEntries(Object.entries(input2.query).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]));
|
|
46795
46882
|
const body = input2.body && typeof input2.body === "object" && !Array.isArray(input2.body) ? input2.body : {};
|
|
46796
|
-
|
|
46883
|
+
if (input2.routeKey === "inbox") {
|
|
46884
|
+
const messages = tenant.bridge ? await tenant.bridge.takeInbox(agentId) : [];
|
|
46885
|
+
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." } };
|
|
46886
|
+
}
|
|
46887
|
+
const res = await tenant.api(agentId, input2.routeKey, input2.params, { ...query, ...body }, input2.actor.runId ?? null);
|
|
46888
|
+
if (tenant.bridge?.hasInbox(agentId) && res.body && typeof res.body === "object" && !Array.isArray(res.body)) {
|
|
46889
|
+
res.body = { ...res.body, newMessages: await tenant.bridge.takeInbox(agentId) };
|
|
46890
|
+
}
|
|
46797
46891
|
return { status: res.status, body: res.body };
|
|
46798
46892
|
},
|
|
46799
46893
|
async onHealth() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-mattermost",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
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",
|