@securecoms/agent-bridge 0.23.0 → 0.24.0

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,68 @@
1
+ /** The body of one reply part, as the intent stores it (sealed at rest). */
2
+ export type ReplyBody = {
3
+ kind: "text";
4
+ text: string;
5
+ } | {
6
+ kind: "a2a";
7
+ a2a: {
8
+ a2a_version: string;
9
+ kind: "message" | "task" | "artifact";
10
+ object: Record<string, unknown>;
11
+ };
12
+ };
13
+ /** Where an intent lives: its scope (what it answers) and its part within it. */
14
+ export interface ReplyIntentKey {
15
+ scope: string;
16
+ part: string;
17
+ inReplyTo: string | null;
18
+ }
19
+ /** Longest idempotency_key accepted (mirrors the in_reply_to bound). */
20
+ export declare const MAX_IDEMPOTENCY_KEY_LENGTH = 200;
21
+ /** The HMAC key for reply ids and body MACs, derived from the identity seed. */
22
+ export declare function deriveReplyIdKey(identitySeed: Uint8Array): Buffer;
23
+ /**
24
+ * The first 16 bytes of `digest` as an RFC 4122-shaped UUID (version nibble 4,
25
+ * variant 10). The backend parses message_id with `uuid.UUID()` and the
26
+ * envelope schema only requires `format: uuid`; this is the encoding the voice
27
+ * fast path's `fastReplyId` already ships with.
28
+ */
29
+ export declare function uuidFromDigest(digest: Uint8Array): string;
30
+ /**
31
+ * The deterministic message_id of one reply part. JSON array encoding keeps the
32
+ * input injective (no separator can be smuggled through a free-text key).
33
+ */
34
+ export declare function replyMessageId(key: Uint8Array, identityId: string, channelId: string, intent: Pick<ReplyIntentKey, "scope" | "part">): string;
35
+ /** Canonical bytes of a reply body (what is sealed and what is MACed). */
36
+ export declare function encodeReplyBody(body: ReplyBody): Uint8Array;
37
+ export declare function decodeReplyBody(bytes: Uint8Array): ReplyBody;
38
+ /**
39
+ * Keyed MAC binding a body to its reply id and channel. Compares retries
40
+ * without storing a plain digest, and (PR #716 review LOW-6) makes a body moved
41
+ * to another row, or a row whose channel was edited, fail verification.
42
+ */
43
+ export declare function replyBodyMac(key: Uint8Array, replyId: string, channelId: string, body: Uint8Array): string;
44
+ /** The ordinal of an unkeyed reply part (`p:<n>`), or null for a keyed part. */
45
+ export declare function partOrdinal(part: string): number | null;
46
+ /**
47
+ * The intent key for a send. `in_reply_to` wins the scope; an explicit
48
+ * idempotency_key names the part (else the per-delivery ordinal does). An
49
+ * idempotency_key alone is its own scope with a single part.
50
+ */
51
+ export declare function replyIntentKey(opts: {
52
+ inReplyTo?: string;
53
+ idempotencyKey?: string;
54
+ ordinal: () => number;
55
+ }): ReplyIntentKey | null;
56
+ /** A caller reused an explicit idempotency_key for a different body. */
57
+ export declare class IdempotencyConflictError extends Error {
58
+ constructor();
59
+ }
60
+ /**
61
+ * PR #716 review LOW-6: a stored reply intent failed verification (its id does
62
+ * not derive from its own identity, channel, scope and part, its body does not
63
+ * match its MAC, or its cached envelope names another id or channel). It is
64
+ * dropped and never published.
65
+ */
66
+ export declare class ReplyIntentIntegrityError extends Error {
67
+ constructor();
68
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const BRIDGE_VERSION = "0.23.0";
1
+ export declare const BRIDGE_VERSION = "0.24.0";
@@ -1,13 +1,23 @@
1
1
  import type { FastPathConfig } from "./persona";
2
2
  import type { ChatMessage } from "./fast-prompt";
3
+ /**
4
+ * SEC-06 (#12): the response body cap. A fast completion is at most 400
5
+ * tokens (a few KB of JSON), and the backend proxy enforces the same 64 KiB.
6
+ */
7
+ export declare const FAST_LLM_MAX_RESPONSE_BYTES: number;
3
8
  export declare class FastLlmError extends Error {
4
- readonly kind: "timeout" | "http" | "shape" | "network";
5
- constructor(message: string, kind: "timeout" | "http" | "shape" | "network");
9
+ readonly kind: "timeout" | "http" | "shape" | "network" | "size";
10
+ constructor(message: string, kind: "timeout" | "http" | "shape" | "network" | "size");
6
11
  }
7
12
  export declare class FastLlmClient {
8
13
  private readonly cfg;
9
14
  private readonly fetchImpl;
10
15
  private readonly key;
11
16
  constructor(cfg: FastPathConfig, fetchImpl: typeof fetch);
17
+ /**
18
+ * One deadline (`cfg.timeoutMs`) covers the whole call: connect, headers
19
+ * AND the body. The timer stays armed until the body is read, so a peer that
20
+ * sends headers and then stalls or trickles cannot hold the voice turn.
21
+ */
12
22
  complete(messages: ChatMessage[]): Promise<string>;
13
23
  }
@@ -16,12 +16,19 @@
16
16
  * { "type":"error", error, message, detail? } // canonical codes (see errors.ts):
17
17
  * // channel_pending | rate_limited |
18
18
  * // invalid_request | agent_paused |
19
- * // send_forbidden (the last two are
19
+ * // send_forbidden (those two are
20
20
  * // TERMINAL: the in_reply_to message
21
- * // is acked by the daemon, no retry)
21
+ * // is acked by the daemon, no retry) |
22
+ * // group_superseded (NOT terminal:
23
+ * // the reply is republished after
24
+ * // the agent rejoins)
22
25
  * Agent → daemon:
23
26
  * { "type":"send", channel_id, text?, a2a?, idempotency_key?,
24
- * in_reply_to? } // exactly one of text | a2a; correlation is metadata-only
27
+ * in_reply_to? } // exactly one of text | a2a. SEC-06 #18: in_reply_to
28
+ * // and/or idempotency_key make the send idempotent — a
29
+ * // deterministic keyed message_id and a durable reply
30
+ * // intent written before the publish, so a retry or a
31
+ * // redelivery republishes the SAME reply
25
32
  * { "type":"status", channel_id, state, detail? }
26
33
  * { "type":"ack", message_id } // confirms a delivered `message` was consumed
27
34
  * { "type":"nack", message_id, reason? } // final-wave I1(b): THIS message's turn
@@ -38,7 +45,9 @@
38
45
  * Inbound delivery is at-least-once. A delivered `message` is held in-flight
39
46
  * until the agent sends a matching `ack`; if the socket drops first, the frame
40
47
  * is re-buffered and redelivered to the next consumer. The agent dedups by
41
- * message_id (a fresh UUID minted per send; server-side dedup is on replay_nonce).
48
+ * message_id. SEC-06 #18: a redelivered message whose reply already has a
49
+ * durable intent is NOT handed to the agent again; the daemon republishes that
50
+ * reply itself (same message_id), so the model does not run twice.
42
51
  *
43
52
  * Bounded work (Phase 3):
44
53
  * - F2 ordering: at most ONE message per channel is in flight on a socket;
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "type": "module",
5
+ "engines": {
6
+ "node": ">=22.5.0"
7
+ },
5
8
  "main": "dist/index.js",
6
9
  "types": "dist/index.d.ts",
7
10
  "bin": {