@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.
@@ -73,18 +73,40 @@ export declare const AGENT_HOP_QUIET_MS: number;
73
73
  *
74
74
  * In memory and bounded (oldest channel evicted first): a restart resets the
75
75
  * counts, which can at worst allow one more bounded run of hops.
76
+ *
77
+ * Idempotent per message: delivery is at-least-once (a failed ack redelivers
78
+ * the row, and custody serves it again), so the same message can reach
79
+ * {@link noteInbound} more than once. Each (channel, message id) is counted
80
+ * once, and a repeat returns the hop count its first sighting got, so the
81
+ * repeat gets the same verdict and leaves the run untouched. Counting a repeat
82
+ * inflated the run until the NEXT agent message was suppressed and acked, a
83
+ * silent loss. The key is sound because a message id is globally unique at the
84
+ * backend (`mls_messages_pkey`): a second, different message under a reused id
85
+ * is refused (`message_id_conflict`) and never fanned out, so a peer cannot
86
+ * use it to slip past the cap.
76
87
  */
77
88
  export declare class AgentHopTracker {
78
89
  private readonly maxChannels;
79
90
  private readonly hops;
91
+ /** `${channelId}\u0000${messageId}` → the hop count its first sighting got. Map order = age. */
92
+ private readonly seen;
80
93
  private readonly now;
81
94
  private readonly quietMs;
95
+ private readonly maxSeenIds;
82
96
  constructor(maxChannels?: number, opts?: {
83
97
  now?: () => number;
84
98
  quietMs?: number;
99
+ maxSeenIds?: number;
85
100
  });
86
- /** Record an inbound message; returns the consecutive agent hops including it. */
87
- noteInbound(channelId: string, senderIsAgent: boolean | undefined): number;
101
+ /**
102
+ * Record an inbound message; returns the consecutive agent hops including it.
103
+ * `messageId` makes the call idempotent (see the class comment). Without one
104
+ * the message is counted every time, the conservative direction for a loop
105
+ * guard.
106
+ */
107
+ noteInbound(channelId: string, senderIsAgent: boolean | undefined, messageId?: string): number;
108
+ /** Test seam: how many message ids are remembered. */
109
+ seenIdCount(): number;
88
110
  /**
89
111
  * Record one of this agent's own SUBSTANTIVE sends (an agent turn in the
90
112
  * channel). Callers do not report status frames, daily-limit or dead-letter
package/dist/errors.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * WebSocket returns `{type:"error", error, message, detail?}`. A client gets the
7
7
  * same `error` code regardless of which transport it used. (A2A §9.5/§10.6 pattern.)
8
8
  */
9
- export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "unknown_action_run" | "vault_integrity_failed" | "internal";
9
+ export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "group_superseded" | "unknown_action_run" | "vault_integrity_failed" | "internal";
10
10
  export interface BridgeErrorSpec {
11
11
  /** HTTP status this code maps to (the WS binding ignores it). */
12
12
  http: number;
@@ -53,32 +53,32 @@ export declare const memoryRecordSchema: z.ZodEffects<z.ZodObject<{
53
53
  message_id: z.ZodOptional<z.ZodString>;
54
54
  idempotency_key: z.ZodOptional<z.ZodString>;
55
55
  }, "strip", z.ZodTypeAny, {
56
- role: "agent" | "peer";
57
56
  text: string;
57
+ role: "agent" | "peer";
58
58
  conversation_id: string;
59
59
  modality: "voice" | "text";
60
60
  message_id?: string | undefined;
61
61
  idempotency_key?: string | undefined;
62
62
  peer_id?: string | undefined;
63
63
  }, {
64
- role: "agent" | "peer";
65
64
  text: string;
65
+ role: "agent" | "peer";
66
66
  conversation_id: string;
67
67
  modality: "voice" | "text";
68
68
  message_id?: string | undefined;
69
69
  idempotency_key?: string | undefined;
70
70
  peer_id?: string | undefined;
71
71
  }>, {
72
- role: "agent" | "peer";
73
72
  text: string;
73
+ role: "agent" | "peer";
74
74
  conversation_id: string;
75
75
  modality: "voice" | "text";
76
76
  message_id?: string | undefined;
77
77
  idempotency_key?: string | undefined;
78
78
  peer_id?: string | undefined;
79
79
  }, {
80
- role: "agent" | "peer";
81
80
  text: string;
81
+ role: "agent" | "peer";
82
82
  conversation_id: string;
83
83
  modality: "voice" | "text";
84
84
  message_id?: string | undefined;