paperclip-plugin-mattermost 0.18.0 → 0.18.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 CHANGED
@@ -85,7 +85,7 @@ PAPERCLIP_REF=<commit> node build.mjs # build for another Paperclip version
85
85
  Bump `version` in `package.json` and `src/manifest.ts`, commit, then tag and push:
86
86
 
87
87
  ```bash
88
- git tag -a v0.18.0 -m v0.18.0 && git push origin main v0.18.0
88
+ git tag -a v0.18.1 -m v0.18.1 && git push origin main v0.18.1
89
89
  ```
90
90
 
91
91
  GitHub Actions builds, tests and publishes to npm through npm trusted publishing, so no npm token is involved.
package/dist/manifest.js CHANGED
@@ -84,7 +84,7 @@ var route = (routeKey, method, path) => ({ routeKey, method, path, auth: "agent"
84
84
  var manifest = {
85
85
  id: "mattermost-bridge",
86
86
  apiVersion: 1,
87
- version: "0.18.0",
87
+ version: "0.18.1",
88
88
  displayName: "Mattermost",
89
89
  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.",
90
90
  author: "Haseeb Azhar",
package/dist/worker.js CHANGED
@@ -45530,6 +45530,9 @@ ${HELP}`;
45530
45530
  import { randomUUID as randomUUID2 } from "node:crypto";
45531
45531
 
45532
45532
  // src/bridge.ts
45533
+ var IN_FLIGHT = "inflight";
45534
+ var IN_FLIGHT_MAX_AGE = 3 * 60 * 60 * 1e3;
45535
+ var RESUME_PROMPT = "The Mattermost plugin restarted while you were working on the conversation above, so your reply did not reach the thread. When you have finished, give your answer to the latest message again; it is posted in the thread.";
45533
45536
  var DEFAULT_TURN_LIMIT = 10;
45534
45537
  var TYPING_EVERY_MS = 3e3;
45535
45538
  var OLD_PLACEHOLDER = /^_(thinking…|.+ is working on it…)_/;
@@ -45790,25 +45793,82 @@ To send files back with your reply, save them in ${outbox}` : "");
45790
45793
  this.active.set(member.agentId, here);
45791
45794
  try {
45792
45795
  let sessionId = thread && thread.agentId === member.agentId ? thread.sessionId : null;
45796
+ const flight = (id) => this.inFlight(rootId, { agentId: member.agentId, sessionId: id, channelId: post.channel_id, channelType: evt.channelType, channelName: evt.channelName, at: Date.now() });
45793
45797
  try {
45794
45798
  if (!sessionId) throw new Error("no session yet");
45799
+ await flight(sessionId);
45795
45800
  reply = await this.agents.ask(sessionId, prompt);
45796
- } catch (first2) {
45797
- if (sessionId) this.log("session unusable, starting a new one", { sessionId, err: String(first2) });
45801
+ } catch (first) {
45802
+ if (sessionId) this.log("session unusable, starting a new one", { sessionId, err: String(first) });
45798
45803
  sessionId = await this.agents.createSession(member.agentId, `mattermost:${rootId}`);
45799
45804
  await this.store.set(threadKey, { agentId: member.agentId, sessionId });
45805
+ await flight(sessionId);
45800
45806
  reply = await this.agents.ask(sessionId, prompt);
45801
45807
  }
45802
45808
  } catch (err) {
45803
45809
  if (this.active.get(member.agentId) === here) this.active.delete(member.agentId);
45804
45810
  this.inbox.delete(member.agentId);
45811
+ await this.inFlight(rootId, null);
45805
45812
  await member.chat.createPost(post.channel_id, rootId, `:warning: ${member.agentName} could not answer: ${String(err?.message ?? err).slice(0, 500)}`);
45806
45813
  return;
45807
45814
  }
45808
45815
  if (this.active.get(member.agentId) === here) this.active.delete(member.agentId);
45809
45816
  this.inbox.delete(member.agentId);
45817
+ await this.inFlight(rootId, null);
45818
+ await this.postReply(member, { channelId: post.channel_id, rootId, channelType: evt.channelType, channelName: evt.channelName }, reply, outbox);
45819
+ }
45820
+ /** Records (or with null clears) the reply this thread is waiting for. */
45821
+ async inFlight(rootId, f) {
45822
+ const all = await this.store.get(IN_FLIGHT) ?? {};
45823
+ if (f) all[rootId] = f;
45824
+ else delete all[rootId];
45825
+ await this.store.set(IN_FLIGHT, all);
45826
+ }
45827
+ /**
45828
+ * After a plugin restart: every reply that was being waited for is asked for again from the same
45829
+ * agent session (which queues behind the run still going), and posted in its thread when it comes.
45830
+ */
45831
+ async resume() {
45832
+ const all = await this.store.get(IN_FLIGHT) ?? {};
45833
+ let n = 0;
45834
+ for (const [rootId, f] of Object.entries(all)) {
45835
+ const member = this.roster().byAgentId(f.agentId);
45836
+ if (!member || Date.now() - f.at > IN_FLIGHT_MAX_AGE) {
45837
+ await this.inFlight(rootId, null);
45838
+ continue;
45839
+ }
45840
+ n++;
45841
+ const prev = this.queues.get(rootId) ?? Promise.resolve();
45842
+ const next = prev.then(async () => {
45843
+ const typing = () => {
45844
+ void member.chat.typing(f.channelId, rootId).catch(() => {
45845
+ });
45846
+ };
45847
+ typing();
45848
+ const timer = setInterval(typing, TYPING_EVERY_MS);
45849
+ try {
45850
+ const reply = await this.agents.ask(f.sessionId, RESUME_PROMPT);
45851
+ await this.inFlight(rootId, null);
45852
+ await this.postReply(member, { channelId: f.channelId, rootId, channelType: f.channelType, channelName: f.channelName }, reply, null);
45853
+ } catch (err) {
45854
+ await this.inFlight(rootId, null);
45855
+ await member.chat.createPost(f.channelId, rootId, `:warning: ${member.agentName}'s reply was lost when the plugin restarted: ${String(err?.message ?? err).slice(0, 300)}. Ask again here.`).catch(() => {
45856
+ });
45857
+ } finally {
45858
+ clearInterval(timer);
45859
+ }
45860
+ }).catch((err) => this.log("resume failed", { err: String(err) }));
45861
+ this.queues.set(rootId, next);
45862
+ void next.finally(() => {
45863
+ if (this.queues.get(rootId) === next) this.queues.delete(rootId);
45864
+ });
45865
+ }
45866
+ return n;
45867
+ }
45868
+ async postReply(member, origin, reply, outbox) {
45869
+ const post = { channel_id: origin.channelId };
45870
+ const rootId = origin.rootId;
45810
45871
  const { text: body, choices } = extractChoices(linkTasks(absoluteLinks(reply ?? "", this.linkBase), this.linkBase, this.taskPrefix));
45811
- const origin = { channelId: post.channel_id, rootId, channelType: evt.channelType, channelName: evt.channelName };
45812
45872
  const buttons = choices.length && this.choiceButtons ? this.choiceButtons(origin, choices) : null;
45813
45873
  const shown = choices.length && !buttons ? `${body}
45814
45874
 
@@ -46305,7 +46365,7 @@ var route = (routeKey, method, path3) => ({ routeKey, method, path: path3, auth:
46305
46365
  var manifest = {
46306
46366
  id: "mattermost-bridge",
46307
46367
  apiVersion: 1,
46308
- version: "0.18.0",
46368
+ version: "0.18.1",
46309
46369
  displayName: "Mattermost",
46310
46370
  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.",
46311
46371
  author: "Haseeb Azhar",
@@ -46681,7 +46741,9 @@ var Tenant = class {
46681
46741
  this.status = `agent bot sync failed: ${errText(err)}`;
46682
46742
  ctx.logger.error(this.status, { companyId });
46683
46743
  });
46684
- void sync();
46744
+ void sync().then(() => bridge.resume()).then((n) => {
46745
+ if (n) ctx.logger.info("resuming replies interrupted by a restart", { companyId, threads: n });
46746
+ }).catch((err) => warn("could not resume interrupted replies", { err: errText(err) }));
46685
46747
  this.timers.push(setInterval(sync, SYNC_EVERY_MS));
46686
46748
  void ensureSkill(companyId).catch((err) => warn("could not install the Mattermost skill", { err: errText(err) }));
46687
46749
  if (cards) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-mattermost",
3
- "version": "0.18.0",
3
+ "version": "0.18.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",