@agentvault/grok-bridge 0.4.24 → 0.4.26

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.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Room context for messages this agent reads but does not answer (2026-10-26).
3
+ *
4
+ * Under the #1107 rule an agent owes a reply only when it is @-mentioned (or
5
+ * @all / the owner rules in `owesRoomReply`). The bridge used to push EVERY room
6
+ * message into the model session anyway, and let the session suppress the
7
+ * reply. That bought the agent a full model turn per message it was never going
8
+ * to answer — measured on loopita (Opus, 1M context) at ~$0.27–0.31 per
9
+ * unaddressed message, ~$4–5 a day, for output that was thrown away.
10
+ *
11
+ * Now an unaddressed message costs nothing. It is held here, per room, and
12
+ * handed to the model as one block of context in front of the next message in
13
+ * that room that IS addressed to this agent — one turn, not N+1. Hermes reached
14
+ * the same place by a different road (`_record_room_message` +
15
+ * `_room_history_block` in plugin-hermes secure_channel.py): "read, not
16
+ * answered", kept, replayed on the turn it takes.
17
+ *
18
+ * The buffer is cleared once delivered. Unlike Hermes, these bridges drive a
19
+ * PERSISTENT model session, so once the block has been delivered the session
20
+ * remembers it — replaying it again would show the model the same messages
21
+ * twice.
22
+ *
23
+ * Bounded on purpose: per room by message count AND characters (oldest dropped
24
+ * first), and by number of rooms (least recently used room dropped). A room the
25
+ * agent is never addressed in must not grow without limit.
26
+ *
27
+ * ⚠️ The block lands INSIDE the turn that answers someone else — often the
28
+ * owner, and on some bridges a turn with tools. Every held message was written
29
+ * by another room member, possibly another tenant's agent, so nothing in it may
30
+ * be able to pass for bridge framing (PR #1458 review). Three layers:
31
+ * 1. sender names lose line breaks and brackets (`sanitizeLabel`);
32
+ * 2. every held text line is prefixed with "> ", so no held line can start
33
+ * with "[" and pose as a `[room … | owner]:` turn line;
34
+ * 3. the block is wrapped in delimiters carrying a fresh random id per turn,
35
+ * which a held message cannot know in advance and so cannot close early.
36
+ *
37
+ * COPIED from packages/claude-room-bridge/src/room-context.ts (2026-10-26).
38
+ * This file is OWNED by the Grok bridge — edit it freely. It is NOT
39
+ * shared, and a fix on the Claude side does NOT reach it.
40
+ *
41
+ * Unlike the Claude bridge there is no tool-capable worker lane here: every
42
+ * turn goes to the one persistent session, which before this change had seen
43
+ * each held message as its own turn. So the block is simply placed in front of
44
+ * the addressed message's text.
45
+ */
46
+ export interface RoomContextEntry {
47
+ senderName: string;
48
+ text: string;
49
+ /** ISO time the message was sent (from the emit), or received if absent. */
50
+ at: string;
51
+ }
52
+ export interface RoomContextLimits {
53
+ /** Messages kept per room. */
54
+ maxMessages: number;
55
+ /** Characters kept per room (sender + text), oldest dropped first. */
56
+ maxChars: number;
57
+ /** Rooms tracked at once; the least recently used room is dropped. */
58
+ maxRooms: number;
59
+ /** One message's text is cut to this many characters. */
60
+ maxEntryChars: number;
61
+ }
62
+ export declare const DEFAULT_ROOM_CONTEXT_LIMITS: RoomContextLimits;
63
+ /**
64
+ * Cut `s` to at most `n` UTF-16 units without splitting a surrogate pair — a
65
+ * lone high surrogate left at the cut is invalid text (and garbles the turn).
66
+ */
67
+ export declare function cutSafe(s: string, n: number): string;
68
+ /**
69
+ * A display name or room name made safe to sit inside a bracketed framing
70
+ * line: no control characters or line breaks (a name with a newline would start
71
+ * a line of its own), no square brackets (which would close the frame early).
72
+ * Used for held senders AND for the live turn prefix (`roomTurnPrefix`).
73
+ */
74
+ export declare function sanitizeLabel(name: string, max?: number): string;
75
+ /** A fresh per-turn delimiter id: 12 hex chars from 6 random bytes. */
76
+ export declare function newContextId(): string;
77
+ export declare class RoomContextBuffer {
78
+ private readonly limits;
79
+ /** Insertion order IS recency order: a touched room is re-inserted at the end. */
80
+ private rooms;
81
+ constructor(limits?: Partial<RoomContextLimits>);
82
+ add(roomId: string, entry: RoomContextEntry): void;
83
+ /** The held messages for a room, oldest first. Does not clear. */
84
+ peek(roomId: string): {
85
+ entries: readonly RoomContextEntry[];
86
+ dropped: number;
87
+ };
88
+ clear(roomId: string): void;
89
+ size(roomId: string): number;
90
+ chars(roomId: string): number;
91
+ roomCount(): number;
92
+ }
93
+ /**
94
+ * The block placed in front of an addressed message. Empty string when there
95
+ * is nothing held — an empty "earlier in this room" header would claim the room
96
+ * was silent when it only means this process saw nothing (e.g. a restart).
97
+ *
98
+ * `id` defaults to a fresh random one; pass it only to make a test deterministic.
99
+ */
100
+ export declare function formatRoomContext(entries: readonly RoomContextEntry[], roomLabel: string, dropped?: number, id?: string): string;
101
+ //# sourceMappingURL=room-context.d.ts.map
@@ -1,10 +1,38 @@
1
1
  import { type ServiceBackend } from "./backend.js";
2
+ /**
3
+ * #913 item 2 / 2026-10-25 — what it takes to remove this agent's service.
4
+ *
5
+ * `attachLifecycle` routes every terminal signal here, and most of them mean
6
+ * the device is ALIVE:
7
+ *
8
+ * session_replaced another session took over — the device still exists
9
+ * device_jwt_expired the JWT aged out — the device still exists
10
+ * reconnect_loop gave up reconnecting — the device still exists
11
+ *
12
+ * This used to key on the reason string `device_revoked`. That was not proof:
13
+ * on 2026-10-25 a PAUSED (suspended) device's reissue 403 was mapped to
14
+ * "device_revoked" and this function removed loopita's launchd service — the
15
+ * owner switched the agent back on and nothing was installed to resume.
16
+ *
17
+ * So it now keys on the channel's CONFIRMED verdict only. SecureChannel sets
18
+ * `deviceGone: true` on `auth_failed` exclusively after the public status
19
+ * endpoint answered REVOKED or the device row is gone (404). Any reason string
20
+ * without that verdict — including `device_revoked` — leaves the service alone.
21
+ * An older channel that never sets the flag therefore never uninstalls: the
22
+ * safe direction.
23
+ */
24
+ export interface TerminalVerdict {
25
+ reason: string;
26
+ /** Set by SecureChannel only when the status endpoint CONFIRMED revoked/deleted. */
27
+ deviceGone?: boolean;
28
+ }
2
29
  export interface SelfUninstallDeps {
3
30
  backend?: ServiceBackend;
4
31
  log?: (msg: string) => void;
5
32
  }
6
33
  /**
7
- * Remove this agent's supervised service after its device was revoked.
34
+ * Remove this agent's supervised service after its device was CONFIRMED
35
+ * revoked or deleted.
8
36
  *
9
37
  * Returns whether a service was actually removed. NEVER throws: this runs on
10
38
  * the shutdown path, and a cleanup that cannot happen must not stop the bridge
@@ -15,5 +43,5 @@ export interface SelfUninstallDeps {
15
43
  * and LEAVES the credentials on disk. A false positive therefore costs the
16
44
  * customer a re-install, not their identity.
17
45
  */
18
- export declare function selfUninstallOnTerminal(reason: string, env: NodeJS.ProcessEnv, deps?: SelfUninstallDeps): boolean;
46
+ export declare function selfUninstallOnTerminal(verdict: TerminalVerdict, env: NodeJS.ProcessEnv, deps?: SelfUninstallDeps): boolean;
19
47
  //# sourceMappingURL=self-uninstall.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"self-uninstall.d.ts","sourceRoot":"","sources":["../../src/service/self-uninstall.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAuBlE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAyCT"}
1
+ {"version":3,"file":"self-uninstall.d.ts","sourceRoot":"","sources":["../../src/service/self-uninstall.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAyCT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/grok-bridge",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "type": "module",
5
5
  "description": "AgentVault Grok Bridge — daemon bridging a Grok agent into E2E-encrypted AgentVault direct messages and rooms.",
6
6
  "main": "dist/index.js",