@nopeek/agent-bridge 0.5.5 → 0.5.6

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/bot.d.ts CHANGED
@@ -24,6 +24,8 @@ export declare class BotRunner {
24
24
  private allowed;
25
25
  private accessLoaded;
26
26
  private declined;
27
+ private chains;
28
+ private cantPost;
27
29
  private log;
28
30
  private logErr;
29
31
  constructor(info: BotInfo, cfg: BridgeConfig);
@@ -50,4 +52,7 @@ export declare class BotRunner {
50
52
  /** Bounded dedupe so a redelivered frame is never answered twice. */
51
53
  private remember;
52
54
  private handleMessage;
55
+ /** A FORBIDDEN post (broadcast channel, bot not an operator) fails for every
56
+ * future message too — mute the channel so the brain stops running there. */
57
+ private notePostFailure;
53
58
  }
package/dist/bot.js CHANGED
@@ -28,6 +28,13 @@ export class BotRunner {
28
28
  allowed = new Set();
29
29
  accessLoaded = false;
30
30
  declined = new Set(); // (senderId) already told "not authorized" once
31
+ // Per-channel serialization: two messages in one channel must be answered in
32
+ // order, one at a time — concurrent brain runs against the same agent session
33
+ // (e.g. one Hermes session per channel) deadlock or reply out of order.
34
+ chains = new Map();
35
+ // Channels the bot can't post to (e.g. broadcast, non-operator): after the
36
+ // first FORBIDDEN, skip the brain entirely — replies there can never land.
37
+ cantPost = new Set();
31
38
  log;
32
39
  logErr;
33
40
  constructor(info, cfg) {
@@ -153,8 +160,15 @@ export class BotRunner {
153
160
  this.log(`received channel key for ${p.channelId} (welcome ceremony completed)`);
154
161
  }));
155
162
  np.on("message", ((m) => {
156
- void this.handleMessage(m).catch((err) => {
163
+ const prev = this.chains.get(m.channelId) ?? Promise.resolve();
164
+ const next = prev.then(() => this.handleMessage(m).catch((err) => {
165
+ this.notePostFailure(m.channelId, err);
157
166
  this.logErr(`handler error for ${m.messageId}: ${err.message}`);
167
+ }));
168
+ this.chains.set(m.channelId, next);
169
+ void next.finally(() => {
170
+ if (this.chains.get(m.channelId) === next)
171
+ this.chains.delete(m.channelId);
158
172
  });
159
173
  }));
160
174
  await this.refreshAccess(); // load the allow list before we answer anyone
@@ -216,6 +230,8 @@ export class BotRunner {
216
230
  this.remember(m.messageId);
217
231
  if (m.senderUserId === this.info.userId)
218
232
  return; // never answer ourselves
233
+ if (this.cantPost.has(m.channelId))
234
+ return; // replies can't land here — don't burn a brain run
219
235
  if (m.decryptionFailed) {
220
236
  // Likely a missed welcome (message arrived before our key). Sync the key
221
237
  // so the NEXT message decrypts; this frame's plaintext is unrecoverable.
@@ -278,6 +294,7 @@ export class BotRunner {
278
294
  }
279
295
  streamRef.p = ch.stream();
280
296
  streamRef.p.catch((err) => {
297
+ this.notePostFailure(m.channelId, err);
281
298
  this.logErr(`stream open failed (falling back to a single send): ${err.message}`);
282
299
  });
283
300
  }
@@ -323,4 +340,14 @@ export class BotRunner {
323
340
  this.handled++;
324
341
  this.log(`${m.channelId} -> replied (${reply.trim().length} chars, handled=${this.handled})`);
325
342
  }
343
+ /** A FORBIDDEN post (broadcast channel, bot not an operator) fails for every
344
+ * future message too — mute the channel so the brain stops running there. */
345
+ notePostFailure(channelId, err) {
346
+ if (!/only operators can post|FORBIDDEN/i.test(err.message))
347
+ return;
348
+ if (this.cantPost.has(channelId))
349
+ return;
350
+ this.cantPost.add(channelId);
351
+ this.log(`muting ${channelId} — bot cannot post here (${err.message.slice(0, 80)})`);
352
+ }
326
353
  }
package/dist/bridge.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { BridgeConfig, BrainSpec } from "./config.js";
2
- export declare const VERSION = "0.5.5";
2
+ export declare const VERSION = "0.5.6";
3
3
  export interface PairRequest {
4
4
  pairingSecret: string;
5
5
  appId: string;
package/dist/bridge.js CHANGED
@@ -14,7 +14,7 @@ import { resolveBrain } from "./brain.js";
14
14
  import { provisionSoul, provisionHermesProfile } from "./backends.js";
15
15
  import { reportCapabilities } from "./capabilities.js";
16
16
  import { isBrainBackend } from "./config.js";
17
- export const VERSION = "0.5.5";
17
+ export const VERSION = "0.5.6";
18
18
  /** How often to re-probe + report brain availability to the server. */
19
19
  const CAPABILITIES_INTERVAL_MS = 5 * 60_000;
20
20
  export class PairError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nopeek/agent-bridge",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Run your own agents as E2EE NoPeek bots. Pairs with a one-time code, runs every bot you own, and pipes messages to any command or webhook.",
5
5
  "type": "module",
6
6
  "license": "MIT",