paperclip-plugin-mattermost 0.11.1 → 0.11.3

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/dist/manifest.js CHANGED
@@ -2,7 +2,7 @@
2
2
  var manifest = {
3
3
  id: "mattermost-bridge",
4
4
  apiVersion: 1,
5
- version: "0.11.1",
5
+ version: "0.11.3",
6
6
  displayName: "Mattermost",
7
7
  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.",
8
8
  author: "Haseeb Azhar",
package/dist/worker.js CHANGED
@@ -45281,7 +45281,7 @@ var MattermostClient = class {
45281
45281
  body: body === void 0 ? void 0 : JSON.stringify(body),
45282
45282
  signal: AbortSignal.timeout(3e4)
45283
45283
  });
45284
- if (!r.ok) throw new Error(`Mattermost ${method} ${path3}: ${r.status} ${(await r.text()).slice(0, 200)}`);
45284
+ if (!r.ok) throw Object.assign(new Error(`Mattermost ${method} ${path3}: ${r.status} ${(await r.text()).slice(0, 200)}`), { status: r.status });
45285
45285
  return r.status === 204 ? null : await r.json();
45286
45286
  }
45287
45287
  async init() {
@@ -45541,18 +45541,19 @@ async function verifyUrl(url2, pluginKey) {
45541
45541
  }
45542
45542
 
45543
45543
  // src/roster.ts
45544
+ var notFound = (err) => err?.status === 404;
45544
45545
  var TOKEN_DESCRIPTION = "paperclip-plugin";
45545
45546
  var AgentBots = class {
45546
45547
  members = /* @__PURE__ */ new Map();
45547
45548
  manager;
45548
- url;
45549
+ newClient;
45549
45550
  listAgents;
45550
45551
  store;
45551
45552
  log;
45552
45553
  onPosted;
45553
- constructor(manager, url2, listAgents, store, log, onPosted) {
45554
+ constructor(manager, url2, listAgents, store, log, onPosted, newClient) {
45554
45555
  this.manager = manager;
45555
- this.url = url2;
45556
+ this.newClient = newClient ?? ((token) => new MattermostClient(url2, token, log));
45556
45557
  this.listAgents = listAgents;
45557
45558
  this.store = store;
45558
45559
  this.log = log;
@@ -45578,12 +45579,32 @@ var AgentBots = class {
45578
45579
  get size() {
45579
45580
  return this.members.size;
45580
45581
  }
45582
+ /**
45583
+ * The bot, or null only when Mattermost says it does not exist. Any other failure (the
45584
+ * server restarting, a timeout) throws, so the agent is skipped this round rather than
45585
+ * given a second bot: that is how Carma's Operations Agent became @operations-agent-agent
45586
+ * while @operations-agent, the one people mention, was left with nobody behind it.
45587
+ */
45581
45588
  async bot(id) {
45582
45589
  try {
45583
45590
  return await this.manager.request("GET", `/bots/${id}?include_deleted=true`);
45584
- } catch {
45585
- return null;
45591
+ } catch (err) {
45592
+ if (notFound(err)) return null;
45593
+ throw err;
45594
+ }
45595
+ }
45596
+ /** The bot at an agent's own username, if it is one of ours and no other agent uses it. */
45597
+ async reclaimable(agent, map2) {
45598
+ let user2;
45599
+ try {
45600
+ user2 = await this.manager.request("GET", `/users/username/${botUsername(agent.name)}`);
45601
+ } catch (err) {
45602
+ if (notFound(err)) return null;
45603
+ throw err;
45586
45604
  }
45605
+ if (!user2.is_bot || Object.entries(map2).some(([other, id]) => other !== agent.id && id === user2.id)) return null;
45606
+ const bot = await this.bot(user2.id);
45607
+ return bot && bot.owner_id === this.manager.botUserId ? bot : null;
45587
45608
  }
45588
45609
  async createBot(agent) {
45589
45610
  const base = botUsername(agent.name);
@@ -45614,9 +45635,19 @@ var AgentBots = class {
45614
45635
  try {
45615
45636
  let bot = map2[agent.id] ? await this.bot(map2[agent.id]) : null;
45616
45637
  if (!bot) {
45617
- bot = await this.createBot(agent);
45618
- this.log("created agent bot", { agent: agent.name, username: bot.username });
45619
- } else {
45638
+ const mine = await this.reclaimable(agent, map2);
45639
+ bot = mine ?? await this.createBot(agent);
45640
+ this.log(mine ? "reused the agent's existing bot" : "created agent bot", { agent: agent.name, username: bot.username });
45641
+ } else if (bot.username !== botUsername(agent.name)) {
45642
+ const original = await this.reclaimable(agent, map2);
45643
+ if (original && original.user_id !== bot.user_id) {
45644
+ await this.manager.request("POST", `/bots/${bot.user_id}/disable`).catch(() => {
45645
+ });
45646
+ this.log("moved agent back to its own bot", { agent: agent.name, from: bot.username, to: original.username });
45647
+ bot = original;
45648
+ }
45649
+ }
45650
+ {
45620
45651
  if (bot.delete_at > 0) await this.manager.request("POST", `/bots/${bot.user_id}/enable`);
45621
45652
  if (bot.display_name !== agent.name) {
45622
45653
  bot = await this.manager.request("PUT", `/bots/${bot.user_id}`, { username: bot.username, display_name: agent.name, description: agent.title || bot.description });
@@ -45644,7 +45675,7 @@ var AgentBots = class {
45644
45675
  continue;
45645
45676
  }
45646
45677
  current?.client.close();
45647
- const client = new MattermostClient(this.url, await this.mintToken(bot.user_id), this.log);
45678
+ const client = this.newClient(await this.mintToken(bot.user_id));
45648
45679
  await client.init();
45649
45680
  client.connect(this.onPosted);
45650
45681
  this.members.set(agent.id, { agentId: agent.id, agentName: agent.name, botUserId: bot.user_id, username: bot.username, chat: client, client });
@@ -45683,7 +45714,7 @@ var AgentBots = class {
45683
45714
  var manifest = {
45684
45715
  id: "mattermost-bridge",
45685
45716
  apiVersion: 1,
45686
- version: "0.11.1",
45717
+ version: "0.11.3",
45687
45718
  displayName: "Mattermost",
45688
45719
  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.",
45689
45720
  author: "Haseeb Azhar",
@@ -45875,12 +45906,19 @@ var Tenant = class {
45875
45906
  await manager.request("PUT", `/posts/${postId}/patch`, { message: message2, props: this.tag(props) });
45876
45907
  },
45877
45908
  async channel(name) {
45878
- for (const team of await manager.request("GET", "/users/me/teams")) {
45909
+ const wanted = name.replace(/^[#~]/, "").trim();
45910
+ const teams = await manager.request("GET", "/users/me/teams");
45911
+ for (const team of teams) {
45879
45912
  try {
45880
- return (await manager.request("GET", `/teams/${team.id}/channels/name/${encodeURIComponent(name.replace(/^[#~]/, ""))}`)).id;
45913
+ return (await manager.request("GET", `/teams/${team.id}/channels/name/${encodeURIComponent(wanted.toLowerCase())}`)).id;
45881
45914
  } catch {
45882
45915
  }
45883
45916
  }
45917
+ for (const team of teams) {
45918
+ const list = await manager.request("GET", `/teams/${team.id}/channels?per_page=200`).catch(() => []);
45919
+ const hit = list.find((ch) => ch.display_name.trim().toLowerCase() === wanted.toLowerCase());
45920
+ if (hit) return hit.id;
45921
+ }
45884
45922
  return null;
45885
45923
  },
45886
45924
  async email(userId) {
@@ -45995,11 +46033,12 @@ var Tenant = class {
45995
46033
  const who = req.user_name ? `@${req.user_name}` : "Someone";
45996
46034
  await this.manager.request("PUT", `/posts/${req.post_id}/patch`, { props: chosenProps(choice.choice, who) }).catch(() => {
45997
46035
  });
45998
- void this.bridge.handle({
46036
+ const bridge = this.bridge;
46037
+ detached(() => void bridge.handle({
45999
46038
  post: { id: `choice:${req.post_id}`, user_id: req.user_id, channel_id: choice.channelId, root_id: choice.rootId, message: choice.choice },
46000
46039
  channelType: choice.channelType,
46001
46040
  channelName: choice.channelName
46002
- });
46041
+ }));
46003
46042
  return;
46004
46043
  }
46005
46044
  if (this.approvals) await this.notify(req, await this.approvals.click(req).catch(failed));
@@ -46044,6 +46083,9 @@ async function saveBoot(companyId, config2) {
46044
46083
  await ctx.state.set(BOOT, all);
46045
46084
  }
46046
46085
  var plugin = definePlugin({
46086
+ // One worker serves every company, each with its own Mattermost (Tenant above).
46087
+ // Without this Paperclip refuses a second company's settings as a cross-tenant risk.
46088
+ multiCompanyConfig: true,
46047
46089
  async setup(c) {
46048
46090
  ctx = c;
46049
46091
  ctx.data.register("setup", async (params) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-mattermost",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
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",