backeer-opencode 0.2.4 → 0.2.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to `backeer-opencode` are documented here.
4
4
 
5
+ ## 0.2.5 - 2026-09-01
6
+
7
+ - Group consecutive OpenCode conversation feedback into one ignored Backeer
8
+ message while preserving permission and question UI blocking.
9
+ - Render feedback with plain terminal-friendly text so OpenCode never exposes
10
+ raw Markdown markers such as `**Backeer**`.
11
+
5
12
  ## 0.2.4 - 2026-09-01
6
13
 
7
14
  - Retry permission, question, and todo publications after a terminal delivery
@@ -468,7 +468,7 @@ import { randomUUID } from "node:crypto";
468
468
  import { statSync } from "node:fs";
469
469
 
470
470
  // src/updates.js
471
- var PLUGIN_VERSION = "0.2.4";
471
+ var PLUGIN_VERSION = "0.2.5";
472
472
  var PACKAGE_NAME = "backeer-opencode";
473
473
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
474
474
  var CHECK_TIMEOUT_MS = 3e3;
@@ -709,9 +709,10 @@ async function publishTestNotification(config, reporter) {
709
709
  // src/feedback-session-store.js
710
710
  var MAX_QUEUED_MESSAGES = 20;
711
711
  var FeedbackSessionStore = class {
712
- constructor({ send, reporter }) {
712
+ constructor({ send, reporter, flushDelayMs = 150 }) {
713
713
  this.send = send;
714
714
  this.reporter = reporter;
715
+ this.flushDelayMs = flushDelayMs;
715
716
  this.sessions = /* @__PURE__ */ new Map();
716
717
  this.disposed = false;
717
718
  }
@@ -724,7 +725,8 @@ var FeedbackSessionStore = class {
724
725
  blockers: /* @__PURE__ */ new Set(),
725
726
  queue: [],
726
727
  queued: /* @__PURE__ */ new Set(),
727
- queueOverflowReported: false
728
+ queueOverflowReported: false,
729
+ flushTimer: void 0
728
730
  };
729
731
  rememberBounded(this.sessions, sessionID, state);
730
732
  }
@@ -733,23 +735,27 @@ var FeedbackSessionStore = class {
733
735
  hasReadyMessage(state) {
734
736
  return !this.disposed && state.idle && state.blockers.size === 0 && state.queue.length > 0;
735
737
  }
738
+ scheduleFlush(sessionID, state) {
739
+ if (state.flushTimer || !this.hasReadyMessage(state)) return;
740
+ state.flushTimer = setTimeout(() => {
741
+ state.flushTimer = void 0;
742
+ void this.flush(sessionID, state);
743
+ }, this.flushDelayMs);
744
+ }
736
745
  async flush(sessionID, state) {
737
746
  if (state.flushing || !this.hasReadyMessage(state)) return;
738
747
  state.flushing = true;
748
+ const items = state.queue.slice();
739
749
  try {
740
- while (this.hasReadyMessage(state)) {
741
- const item = state.queue[0];
742
- try {
743
- await this.send(sessionID, item.kind, item.message);
744
- state.queue.shift();
745
- state.queued.delete(item.key);
746
- } catch (error) {
747
- state.idle = false;
748
- this.reporter.debug("Conversation feedback could not be displayed", error);
749
- }
750
- }
750
+ await this.send(sessionID, items);
751
+ state.queue.splice(0, items.length);
752
+ for (const item of items) state.queued.delete(item.key);
753
+ } catch (error) {
754
+ state.idle = false;
755
+ this.reporter.debug("Conversation feedback could not be displayed", error);
751
756
  } finally {
752
757
  state.flushing = false;
758
+ this.scheduleFlush(sessionID, state);
753
759
  }
754
760
  }
755
761
  enqueue(kind, sessionID, message) {
@@ -767,13 +773,13 @@ var FeedbackSessionStore = class {
767
773
  this.reporter.warning("Conversation feedback queue full \u2014 oldest message discarded");
768
774
  }
769
775
  }
770
- if (state.idle && state.blockers.size === 0) void this.flush(sessionID, state);
776
+ this.scheduleFlush(sessionID, state);
771
777
  }
772
778
  setIdle(sessionID, idle) {
773
779
  if (typeof sessionID !== "string" || !sessionID) return;
774
780
  const state = this.stateFor(sessionID);
775
781
  state.idle = idle;
776
- if (idle) void this.flush(sessionID, state);
782
+ if (idle) this.scheduleFlush(sessionID, state);
777
783
  }
778
784
  beginInteraction(sessionID, key) {
779
785
  const state = this.stateFor(sessionID);
@@ -794,10 +800,15 @@ var FeedbackSessionStore = class {
794
800
  state.idle = false;
795
801
  }
796
802
  delete(sessionID) {
803
+ const state = this.sessions.get(sessionID);
804
+ if (state?.flushTimer) clearTimeout(state.flushTimer);
797
805
  this.sessions.delete(sessionID);
798
806
  }
799
807
  dispose() {
800
808
  this.disposed = true;
809
+ for (const state of this.sessions.values()) {
810
+ if (state.flushTimer) clearTimeout(state.flushTimer);
811
+ }
801
812
  this.sessions.clear();
802
813
  }
803
814
  };
@@ -874,15 +885,28 @@ async function sendSessionMessage(client, sessionID, text) {
874
885
  }
875
886
  function formatBackeerFeedback(kind, message) {
876
887
  const symbol = FEEDBACK_SYMBOLS[kind] ?? FEEDBACK_SYMBOLS.info;
877
- return `> **Backeer** ${symbol} ${limit(message, 300)}`;
888
+ return `\u25A3 Backeer | ${symbol} ${limit(message, 300)}`;
889
+ }
890
+ function formatBackeerFeedbackBatch(items) {
891
+ if (items.length === 1) {
892
+ return formatBackeerFeedback(items[0].kind, items[0].message);
893
+ }
894
+ return [
895
+ "\u25A3 Backeer",
896
+ ...items.map((item) => {
897
+ const symbol = FEEDBACK_SYMBOLS[item.kind] ?? FEEDBACK_SYMBOLS.info;
898
+ return `${symbol} ${limit(item.message, 300)}`;
899
+ })
900
+ ].join("\n");
878
901
  }
879
- function createConversationFeedback({ client, reporter }) {
902
+ function createConversationFeedback({ client, reporter, flushDelayMs = 150 }) {
880
903
  const sessions = new FeedbackSessionStore({
881
904
  reporter,
882
- send: (sessionID, kind, message) => sendSessionMessage(
905
+ flushDelayMs,
906
+ send: (sessionID, items) => sendSessionMessage(
883
907
  client,
884
908
  sessionID,
885
- formatBackeerFeedback(kind, message)
909
+ formatBackeerFeedbackBatch(items)
886
910
  )
887
911
  });
888
912
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backeer-opencode",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Backeer lifecycle notifications and remote permission decisions for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -3,9 +3,10 @@ import { limit, rememberBounded } from "./utilities.js"
3
3
  const MAX_QUEUED_MESSAGES = 20
4
4
 
5
5
  export class FeedbackSessionStore {
6
- constructor({ send, reporter }) {
6
+ constructor({ send, reporter, flushDelayMs = 150 }) {
7
7
  this.send = send
8
8
  this.reporter = reporter
9
+ this.flushDelayMs = flushDelayMs
9
10
  this.sessions = new Map()
10
11
  this.disposed = false
11
12
  }
@@ -20,6 +21,7 @@ export class FeedbackSessionStore {
20
21
  queue: [],
21
22
  queued: new Set(),
22
23
  queueOverflowReported: false,
24
+ flushTimer: undefined,
23
25
  }
24
26
  rememberBounded(this.sessions, sessionID, state)
25
27
  }
@@ -31,23 +33,28 @@ export class FeedbackSessionStore {
31
33
  state.blockers.size === 0 && state.queue.length > 0
32
34
  }
33
35
 
36
+ scheduleFlush(sessionID, state) {
37
+ if (state.flushTimer || !this.hasReadyMessage(state)) return
38
+ state.flushTimer = setTimeout(() => {
39
+ state.flushTimer = undefined
40
+ void this.flush(sessionID, state)
41
+ }, this.flushDelayMs)
42
+ }
43
+
34
44
  async flush(sessionID, state) {
35
45
  if (state.flushing || !this.hasReadyMessage(state)) return
36
46
  state.flushing = true
47
+ const items = state.queue.slice()
37
48
  try {
38
- while (this.hasReadyMessage(state)) {
39
- const item = state.queue[0]
40
- try {
41
- await this.send(sessionID, item.kind, item.message)
42
- state.queue.shift()
43
- state.queued.delete(item.key)
44
- } catch (error) {
45
- state.idle = false
46
- this.reporter.debug("Conversation feedback could not be displayed", error)
47
- }
48
- }
49
+ await this.send(sessionID, items)
50
+ state.queue.splice(0, items.length)
51
+ for (const item of items) state.queued.delete(item.key)
52
+ } catch (error) {
53
+ state.idle = false
54
+ this.reporter.debug("Conversation feedback could not be displayed", error)
49
55
  } finally {
50
56
  state.flushing = false
57
+ this.scheduleFlush(sessionID, state)
51
58
  }
52
59
  }
53
60
 
@@ -67,14 +74,14 @@ export class FeedbackSessionStore {
67
74
  this.reporter.warning("Conversation feedback queue full — oldest message discarded")
68
75
  }
69
76
  }
70
- if (state.idle && state.blockers.size === 0) void this.flush(sessionID, state)
77
+ this.scheduleFlush(sessionID, state)
71
78
  }
72
79
 
73
80
  setIdle(sessionID, idle) {
74
81
  if (typeof sessionID !== "string" || !sessionID) return
75
82
  const state = this.stateFor(sessionID)
76
83
  state.idle = idle
77
- if (idle) void this.flush(sessionID, state)
84
+ if (idle) this.scheduleFlush(sessionID, state)
78
85
  }
79
86
 
80
87
  beginInteraction(sessionID, key) {
@@ -100,11 +107,16 @@ export class FeedbackSessionStore {
100
107
  }
101
108
 
102
109
  delete(sessionID) {
110
+ const state = this.sessions.get(sessionID)
111
+ if (state?.flushTimer) clearTimeout(state.flushTimer)
103
112
  this.sessions.delete(sessionID)
104
113
  }
105
114
 
106
115
  dispose() {
107
116
  this.disposed = true
117
+ for (const state of this.sessions.values()) {
118
+ if (state.flushTimer) clearTimeout(state.flushTimer)
119
+ }
108
120
  this.sessions.clear()
109
121
  }
110
122
  }
@@ -82,16 +82,30 @@ export async function sendSessionMessage(client, sessionID, text) {
82
82
 
83
83
  export function formatBackeerFeedback(kind, message) {
84
84
  const symbol = FEEDBACK_SYMBOLS[kind] ?? FEEDBACK_SYMBOLS.info
85
- return `> **Backeer** ${symbol} ${limit(message, 300)}`
85
+ return `▣ Backeer | ${symbol} ${limit(message, 300)}`
86
86
  }
87
87
 
88
- export function createConversationFeedback({ client, reporter }) {
88
+ export function formatBackeerFeedbackBatch(items) {
89
+ if (items.length === 1) {
90
+ return formatBackeerFeedback(items[0].kind, items[0].message)
91
+ }
92
+ return [
93
+ "▣ Backeer",
94
+ ...items.map((item) => {
95
+ const symbol = FEEDBACK_SYMBOLS[item.kind] ?? FEEDBACK_SYMBOLS.info
96
+ return `${symbol} ${limit(item.message, 300)}`
97
+ }),
98
+ ].join("\n")
99
+ }
100
+
101
+ export function createConversationFeedback({ client, reporter, flushDelayMs = 150 }) {
89
102
  const sessions = new FeedbackSessionStore({
90
103
  reporter,
91
- send: (sessionID, kind, message) => sendSessionMessage(
104
+ flushDelayMs,
105
+ send: (sessionID, items) => sendSessionMessage(
92
106
  client,
93
107
  sessionID,
94
- formatBackeerFeedback(kind, message),
108
+ formatBackeerFeedbackBatch(items),
95
109
  ),
96
110
  })
97
111
 
package/src/updates.js CHANGED
@@ -1,4 +1,4 @@
1
- export const PLUGIN_VERSION = "0.2.4"
1
+ export const PLUGIN_VERSION = "0.2.5"
2
2
 
3
3
  const PACKAGE_NAME = "backeer-opencode"
4
4
  const NPM_REGISTRY_URL = "https://registry.npmjs.org"