@nopeek/agent-bridge 0.7.2 → 0.7.4

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
@@ -25,6 +25,8 @@ export declare class BotRunner {
25
25
  private channelCache;
26
26
  private allowed;
27
27
  private accessLoaded;
28
+ private dmAllowed;
29
+ private mutedDmPrivate;
28
30
  private declined;
29
31
  private ownerPresence;
30
32
  private mutedNoOwner;
@@ -50,6 +52,9 @@ export declare class BotRunner {
50
52
  refreshAccess(): Promise<void>;
51
53
  /** Sender permitted to talk to this bot? Fail closed if access never loaded. */
52
54
  private isAllowed;
55
+ /** Sender permitted to DM this bot? Server-provided dmAllowedUserIds when we
56
+ * have it; otherwise (older backend) accountable owner + nominal owner. */
57
+ private isDmAllowed;
53
58
  /** Merge live per-channel reply-mode updates (from a bot_config_changed
54
59
  * control frame). Additive: only the listed channels change. */
55
60
  applyChannelModes(modes: Record<string, ChannelMode>): void;
package/dist/bot.js CHANGED
@@ -32,6 +32,14 @@ export class BotRunner {
32
32
  // brain — so a stranger who finds the @handle can't drive the owner's agent.
33
33
  allowed = new Set();
34
34
  accessLoaded = false;
35
+ // DM PRIVACY: DMs with a bot are OWNER-ONLY unless the owner explicitly
36
+ // granted DM access. `dmAllowed` mirrors the server's dmAllowedUserIds
37
+ // (accountable owner + any explicit DM grants); null = the server didn't
38
+ // send the field (older backend) → fall back to accountable owner +
39
+ // info.ownerId. Enforced in kind=direct channels only — this also covers
40
+ // PRE-EXISTING DMs a non-owner opened before the server-side create guard.
41
+ dmAllowed = null;
42
+ mutedDmPrivate = new Set(); // DM channels already logged as owner-only
35
43
  declined = new Set(); // (senderId) already told "not authorized" once
36
44
  // OWNER-PRESENT POLICY (multi-party channels): other users may interact with
37
45
  // the bot in a group ONLY while the bot's owner is ALSO a member of that
@@ -118,6 +126,7 @@ export class BotRunner {
118
126
  throw new Error(`access HTTP ${res.status}`);
119
127
  const j = (await res.json());
120
128
  this.allowed = new Set(j.allowedUserIds ?? []);
129
+ this.dmAllowed = Array.isArray(j.dmAllowedUserIds) ? new Set(j.dmAllowedUserIds) : null;
121
130
  // Channel reply modes: the access response is a full snapshot — replace
122
131
  // the map (a channel the server no longer lists reverts to "everyone").
123
132
  const modes = new Map();
@@ -150,6 +159,15 @@ export class BotRunner {
150
159
  return false; // fail closed until we know the list
151
160
  return this.allowed.has(senderUserId);
152
161
  }
162
+ /** Sender permitted to DM this bot? Server-provided dmAllowedUserIds when we
163
+ * have it; otherwise (older backend) accountable owner + nominal owner. */
164
+ isDmAllowed(senderUserId) {
165
+ if (this.dmAllowed)
166
+ return this.dmAllowed.has(senderUserId);
167
+ return (senderUserId === this.effectiveOwnerId() ||
168
+ senderUserId === this.info.ownerId ||
169
+ senderUserId === this.runtimeOwnerId);
170
+ }
153
171
  /** Merge live per-channel reply-mode updates (from a bot_config_changed
154
172
  * control frame). Additive: only the listed channels change. */
155
173
  applyChannelModes(modes) {
@@ -391,7 +409,22 @@ export class BotRunner {
391
409
  const effOwner = this.effectiveOwnerId();
392
410
  if (m.senderUserId !== effOwner) {
393
411
  const ch = await this.getChannel(m.channelId);
394
- if (ch.record.kind !== "direct") {
412
+ if (ch.record.kind === "direct" || ch.record.kind === "dm") {
413
+ // DM PRIVACY: DMs with a bot are OWNER-ONLY (plus explicit DM grants
414
+ // from dmAllowedUserIds). A non-listed sender gets SILENCE — no brain
415
+ // run, no decline notice — even in a DM channel that predates the
416
+ // server-side create guard. Groups are handled below.
417
+ if (!this.isDmAllowed(m.senderUserId)) {
418
+ if (!this.mutedDmPrivate.has(m.channelId)) {
419
+ this.mutedDmPrivate.add(m.channelId);
420
+ this.log(`muting DM ${m.channelId} — sender ${m.senderUserId} is not in dmAllowedUserIds (bot DMs are owner-only); staying silent`);
421
+ }
422
+ return;
423
+ }
424
+ this.mutedDmPrivate.delete(m.channelId);
425
+ }
426
+ else {
427
+ // OWNER-PRESENT POLICY (groups)
395
428
  if (!(await this.ownerIsChannelMember(m.channelId))) {
396
429
  if (!this.mutedNoOwner.has(m.channelId)) {
397
430
  this.mutedNoOwner.add(m.channelId);
package/dist/bridge.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { BridgeConfig, Pairing, BrainSpec, BrainBackend } from "./config.js";
2
- export declare const VERSION = "0.7.2";
2
+ export declare const VERSION = "0.7.4";
3
3
  export interface PairRequest {
4
4
  pairingSecret: string;
5
5
  appId: string;
package/dist/bridge.js CHANGED
@@ -17,7 +17,7 @@ import { resolveBrain } from "./brain.js";
17
17
  import { provisionSoul, provisionHermesProfile } from "./backends.js";
18
18
  import { reportCapabilities } from "./capabilities.js";
19
19
  import { isBrainBackend } from "./config.js";
20
- export const VERSION = "0.7.2";
20
+ export const VERSION = "0.7.4";
21
21
  /** How often to re-probe + report brain availability to the server. */
22
22
  const CAPABILITIES_INTERVAL_MS = 5 * 60_000;
23
23
  export class PairError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nopeek/agent-bridge",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Run your own agents as E2EE NoPeek bots. Pairs with one-time codes (multiple accounts per computer), runs every bot each account owns, and pipes messages to any command or webhook.",
5
5
  "type": "module",
6
6
  "license": "MIT",