@ziggs-ai/agent-sdk 0.1.3 → 0.1.5
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/README.md +3 -1
- package/package.json +9 -4
- package/src/AgentHost.ts +495 -0
- package/src/adapters/OpenAIAdapter.ts +146 -0
- package/src/agent/Agent.ts +101 -0
- package/src/cognition/validateContext.ts +95 -0
- package/src/context/applyEffects.ts +80 -0
- package/src/context/batch.ts +17 -0
- package/src/context/classifyEnvelope.ts +38 -0
- package/src/context/routingLabels.ts +46 -0
- package/src/defineAgent.ts +62 -0
- package/src/formatters/AgreementFormatter.ts +111 -0
- package/src/formatters/HistoryFormatter.ts +166 -0
- package/src/formatters/index.ts +2 -0
- package/src/index.ts +105 -0
- package/src/ingress/normalizeIncoming.ts +162 -0
- package/src/memory/MemoryStore.ts +104 -0
- package/src/pricing/fleetDefaults.ts +218 -0
- package/src/pricing/fleetEvalFree.ts +24 -0
- package/src/pricing/fleetFreeTierA.gen.ts +12 -0
- package/src/pricing/fleetTierByAgentId.gen.ts +1022 -0
- package/src/runtime/AgentMachine.ts +364 -0
- package/src/runtime/PromptBuilder.ts +463 -0
- package/src/runtime/buildOutcome.ts +518 -0
- package/src/runtime/defaults.ts +75 -0
- package/src/runtime/runTurn.ts +691 -0
- package/src/runtime/validateWorkflow.ts +181 -0
- package/src/server/ConnectionPool.ts +155 -0
- package/src/server/EventQueue.ts +133 -0
- package/src/server/InboxCatchUp.ts +251 -0
- package/src/server/OutboxBuffer.ts +90 -0
- package/src/server/SeenMessages.ts +27 -0
- package/src/server/ZiggsEffectHandler.ts +409 -0
- package/src/server/agreements/AgreementService.ts +117 -0
- package/src/server/createHealthServer.ts +85 -0
- package/src/server/proactive/ProactiveTrigger.ts +83 -0
- package/src/server/runLauncher.ts +146 -0
- package/src/server/tasks/TaskService.ts +110 -0
- package/src/server/tasks/index.ts +1 -0
- package/src/server/telemetryIngest.ts +91 -0
- package/src/server/tools/index.ts +46 -0
- package/src/server/tools/tier1/protocolRunner.ts +133 -0
- package/src/server/tools/tier1/protocolTools.ts +99 -0
- package/src/server/tools/tier2/connectionTools.ts +75 -0
- package/src/server/tools/tier2/contextTools.ts +74 -0
- package/src/server/tools/tier2/discoveryTools.ts +34 -0
- package/src/server/tools/tier2/marketplaceTools.ts +25 -0
- package/src/server/tools/tier2/paymentTools.ts +193 -0
- package/src/server/ziggsconnect/ZiggsConnectClient.ts +126 -0
- package/src/server/ziggscontext/ZiggsContextClient.ts +137 -0
- package/src/server/ziggspay/ZiggsPayClient.ts +193 -0
- package/src/shared/ids.ts +3 -0
- package/src/shared/runtimeLog.ts +72 -0
- package/src/shared/types.ts +29 -0
- package/src/tasks/protocolRegistry.ts +25 -0
- package/src/tools/ToolManager.ts +95 -0
- package/src/tools/{ToolProvider.js → ToolProvider.ts} +5 -15
- package/src/tools/defineTool.ts +90 -0
- package/src/tools/index.ts +5 -0
- package/src/types.ts +407 -0
- package/src/utils/jsonExtractor.ts +100 -0
- package/src/ConnectionPool.js +0 -133
- package/src/adapters/OpenAIAdapter.js +0 -73
- package/src/agent/Agent.js +0 -121
- package/src/agent/EventQueue.js +0 -68
- package/src/agent/OutboxBuffer.js +0 -62
- package/src/cognition/PromptBuilder.js +0 -312
- package/src/cognition/resolveActionTool.js +0 -12
- package/src/cognition/runTurn.js +0 -578
- package/src/context/applyEffects.js +0 -133
- package/src/context/batch.js +0 -25
- package/src/context/classifyEnvelope.js +0 -82
- package/src/context/routingLabels.js +0 -54
- package/src/createHealthServer.js +0 -28
- package/src/formatters/HistoryFormatter.js +0 -257
- package/src/formatters/TaskFormatter.js +0 -180
- package/src/formatters/index.js +0 -9
- package/src/index.js +0 -76
- package/src/ingress/normalizeIncoming.js +0 -70
- package/src/runLauncher.js +0 -159
- package/src/shared/ids.js +0 -7
- package/src/shared/types.js +0 -86
- package/src/tasks/TaskService.js +0 -247
- package/src/tasks/index.js +0 -9
- package/src/tasks/taskCore.js +0 -229
- package/src/tasks/taskProtocolRegistry.js +0 -22
- package/src/tasks/taskProtocolRunner.js +0 -107
- package/src/tasks/taskProtocolTools.js +0 -87
- package/src/tools/ToolManager.js +0 -79
- package/src/tools/defineTool.js +0 -82
- package/src/tools/index.js +0 -11
- package/src/utils/jsonExtractor.js +0 -139
- package/src/workflow/AgentMachine.js +0 -250
- package/src/workflow/WorkflowRuntime.js +0 -63
- package/src/workflow/dsl.js +0 -287
- package/src/workflow/motifs.js +0 -435
- package/src/ziggs/runtime.js +0 -192
- /package/src/adapters/{index.js → index.ts} +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContextReadClient,
|
|
3
|
+
ContextDiscoveryClient,
|
|
4
|
+
InboxClient,
|
|
5
|
+
getChatsForAgreement,
|
|
6
|
+
type InboxScopeEntry,
|
|
7
|
+
type InboxAck,
|
|
8
|
+
type Creds,
|
|
9
|
+
} from '@ziggs-ai/api-client';
|
|
10
|
+
import { SeenMessages } from './SeenMessages.js';
|
|
11
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
12
|
+
|
|
13
|
+
export interface InboxCatchUpDeps {
|
|
14
|
+
creds: Creds;
|
|
15
|
+
ownAgentId: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
seen: SeenMessages;
|
|
18
|
+
onMessage: (text: string, metadata: Record<string, unknown>) => Promise<void>;
|
|
19
|
+
onResourceEvent: (event: Record<string, unknown>) => Promise<void>;
|
|
20
|
+
label?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InboxCatchUpResult {
|
|
24
|
+
scopes: number;
|
|
25
|
+
messagesDelivered: number;
|
|
26
|
+
resourceEvents: number;
|
|
27
|
+
acked: number;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type WireRow = Record<string, unknown>;
|
|
32
|
+
|
|
33
|
+
const READ_PAGE = 100;
|
|
34
|
+
|
|
35
|
+
function log(label: string, msg: string): void {
|
|
36
|
+
runtimeLog.info(label, msg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function warn(label: string, msg: string): void {
|
|
40
|
+
runtimeLog.warn(label, msg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Map Mongo message row → wire metadata for normalizeIncomingEvent. */
|
|
44
|
+
function rowToMetadata(row: WireRow, chatId: string): Record<string, unknown> {
|
|
45
|
+
const senderRaw = row.sender as { agentId?: string; id?: string; type?: string } | undefined;
|
|
46
|
+
const senderId = String(senderRaw?.agentId ?? senderRaw?.id ?? '');
|
|
47
|
+
const receiver = row.receiver as { id?: string; type?: string } | undefined;
|
|
48
|
+
const senderType =
|
|
49
|
+
senderRaw?.type ??
|
|
50
|
+
(receiver?.type === 'agent' ? 'agent' : 'user');
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
chatId,
|
|
54
|
+
chat_id: chatId,
|
|
55
|
+
messageId: row.messageId,
|
|
56
|
+
sender: { id: senderId, type: senderType },
|
|
57
|
+
senderId,
|
|
58
|
+
senderType: String(senderType).toUpperCase(),
|
|
59
|
+
receiver: receiver ?? null,
|
|
60
|
+
receiverId: receiver?.id ?? null,
|
|
61
|
+
entryType: row.entryType ?? 'message',
|
|
62
|
+
content_type: row.content_type ?? row.contentType ?? 'text',
|
|
63
|
+
contentType: row.content_type ?? row.contentType ?? 'text',
|
|
64
|
+
task: row.task ?? null,
|
|
65
|
+
agreement: row.agreement ?? null,
|
|
66
|
+
operation: row.operation ?? null,
|
|
67
|
+
agreementId: row.agreementId ?? null,
|
|
68
|
+
timestamp: row.timestamp,
|
|
69
|
+
text: row.text,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function chatIdsForScope(
|
|
74
|
+
scope: InboxScopeEntry,
|
|
75
|
+
creds: Creds,
|
|
76
|
+
baseUrl: string | undefined,
|
|
77
|
+
discovery: ContextDiscoveryClient,
|
|
78
|
+
): Promise<string[]> {
|
|
79
|
+
const { kind, id } = scope.scope;
|
|
80
|
+
switch (kind) {
|
|
81
|
+
case 'chat':
|
|
82
|
+
return [id];
|
|
83
|
+
case 'agreement': {
|
|
84
|
+
const links = (await getChatsForAgreement(id, creds)) as Array<{ chatId?: string }>;
|
|
85
|
+
return [...new Set(links.map((l) => l.chatId).filter(Boolean) as string[])];
|
|
86
|
+
}
|
|
87
|
+
case 'org': {
|
|
88
|
+
const reach = await discovery.discover();
|
|
89
|
+
return [
|
|
90
|
+
...new Set(
|
|
91
|
+
reach
|
|
92
|
+
.filter((r) => r.scope.kind === 'chat')
|
|
93
|
+
.map((r) => r.scope.id),
|
|
94
|
+
),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function deliverMessagesForScope(
|
|
103
|
+
deps: InboxCatchUpDeps,
|
|
104
|
+
scope: InboxScopeEntry,
|
|
105
|
+
chatIds: string[],
|
|
106
|
+
reader: ContextReadClient,
|
|
107
|
+
countCap: number,
|
|
108
|
+
): Promise<number> {
|
|
109
|
+
let delivered = 0;
|
|
110
|
+
const label = deps.label ?? 'InboxCatchUp';
|
|
111
|
+
const expected = scope.newMessages;
|
|
112
|
+
let remaining = expected >= countCap ? Number.POSITIVE_INFINITY : expected;
|
|
113
|
+
|
|
114
|
+
for (const chatId of chatIds) {
|
|
115
|
+
if (remaining <= 0) break;
|
|
116
|
+
let after = scope.since;
|
|
117
|
+
let pages = 0;
|
|
118
|
+
const maxPages = 20;
|
|
119
|
+
|
|
120
|
+
while (remaining > 0 && pages < maxPages) {
|
|
121
|
+
const limit = Math.min(READ_PAGE, remaining === Number.POSITIVE_INFINITY ? READ_PAGE : remaining);
|
|
122
|
+
const page = await reader.read<WireRow>('messages', {
|
|
123
|
+
via: `chat:${chatId}`,
|
|
124
|
+
after,
|
|
125
|
+
direction: 'forward',
|
|
126
|
+
limit,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
for (const row of page.items) {
|
|
130
|
+
const messageId = String(row.messageId ?? '');
|
|
131
|
+
if (deps.seen.has(messageId)) continue;
|
|
132
|
+
|
|
133
|
+
const text = typeof row.text === 'string' ? row.text : '';
|
|
134
|
+
if (!text.trim()) continue;
|
|
135
|
+
|
|
136
|
+
const metadata = rowToMetadata(row, chatId);
|
|
137
|
+
await deps.onMessage(text, metadata);
|
|
138
|
+
delivered++;
|
|
139
|
+
if (remaining !== Number.POSITIVE_INFINITY) remaining--;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!page.hasMore || !page.latestSequence) break;
|
|
143
|
+
after = page.latestSequence;
|
|
144
|
+
pages++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (delivered < expected && expected >= countCap) {
|
|
149
|
+
warn(label, `scope ${scope.scope.kind}:${scope.scope.id} may be incomplete (count cap)`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return delivered;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Pull-based recovery after reconnect (ZIG-434 / ZIG-446 / ZIG-454).
|
|
157
|
+
* Inbox → read message deltas → deliver → ack. Push remains a hint;
|
|
158
|
+
* this path survives backend restarts that drop in-memory pending queues.
|
|
159
|
+
*/
|
|
160
|
+
export async function runInboxCatchUp(deps: InboxCatchUpDeps): Promise<InboxCatchUpResult> {
|
|
161
|
+
const label = deps.label ?? 'InboxCatchUp';
|
|
162
|
+
const inbox = new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
163
|
+
const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
164
|
+
const discovery = new ContextDiscoveryClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
165
|
+
|
|
166
|
+
const envelope = await inbox.getInbox();
|
|
167
|
+
const countCap = envelope.countCap;
|
|
168
|
+
|
|
169
|
+
let messagesDelivered = 0;
|
|
170
|
+
let resourceEvents = 0;
|
|
171
|
+
const toAck: InboxAck[] = [];
|
|
172
|
+
|
|
173
|
+
if (envelope.truncatedScopes > 0) {
|
|
174
|
+
warn(label, `inbox truncatedScopes=${envelope.truncatedScopes} — skipping ack until narrowed`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const scope of envelope.scopes) {
|
|
178
|
+
const chatIds = await chatIdsForScope(scope, deps.creds, deps.baseUrl, discovery);
|
|
179
|
+
if (!chatIds.length && (scope.newMessages > 0 || scope.newArtifacts > 0)) {
|
|
180
|
+
warn(
|
|
181
|
+
label,
|
|
182
|
+
`no chat ids for scope ${scope.scope.kind}:${scope.scope.id} (${scope.newMessages} msgs)`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (scope.newMessages > 0 && chatIds.length) {
|
|
187
|
+
const n = await deliverMessagesForScope(deps, scope, chatIds, reader, countCap);
|
|
188
|
+
messagesDelivered += n;
|
|
189
|
+
log(label, `scope ${scope.scope.kind}:${scope.scope.id} delivered ${n} message(s)`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (scope.newArtifacts > 0) {
|
|
193
|
+
for (const chatId of chatIds) {
|
|
194
|
+
await deps.onResourceEvent({
|
|
195
|
+
kind: 'artifact',
|
|
196
|
+
ts: scope.latestAt ?? new Date().toISOString(),
|
|
197
|
+
resourceId: chatId,
|
|
198
|
+
chatId,
|
|
199
|
+
change: 'created',
|
|
200
|
+
});
|
|
201
|
+
resourceEvents++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
envelope.truncatedScopes === 0 &&
|
|
207
|
+
scope.latestAt &&
|
|
208
|
+
(scope.newMessages > 0 || scope.newArtifacts > 0)
|
|
209
|
+
) {
|
|
210
|
+
toAck.push({
|
|
211
|
+
kind: scope.scope.kind,
|
|
212
|
+
id: scope.scope.id,
|
|
213
|
+
upTo: scope.latestAt,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const proposal of envelope.proposalsAwaitingMe) {
|
|
219
|
+
await deps.onResourceEvent({
|
|
220
|
+
kind: 'agreement',
|
|
221
|
+
ts: proposal.proposedAt ?? new Date().toISOString(),
|
|
222
|
+
resourceId: proposal.agreementId,
|
|
223
|
+
agreementId: proposal.agreementId,
|
|
224
|
+
change: 'created',
|
|
225
|
+
reason: 'proposal_pending',
|
|
226
|
+
});
|
|
227
|
+
resourceEvents++;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let acked = 0;
|
|
231
|
+
if (toAck.length) {
|
|
232
|
+
await inbox.ack(toAck);
|
|
233
|
+
acked = toAck.length;
|
|
234
|
+
log(label, `acked ${acked} scope(s)`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (messagesDelivered > 0 || resourceEvents > 0) {
|
|
238
|
+
log(
|
|
239
|
+
label,
|
|
240
|
+
`catch-up complete: ${messagesDelivered} message(s), ${resourceEvents} resource hint(s)`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
scopes: envelope.scopes.length,
|
|
246
|
+
messagesDelivered,
|
|
247
|
+
resourceEvents,
|
|
248
|
+
acked,
|
|
249
|
+
truncated: envelope.truncatedScopes > 0,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
|
+
|
|
3
|
+
interface PendingMessage {
|
|
4
|
+
text: string;
|
|
5
|
+
receiverId: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface OutboxBufferOptions {
|
|
10
|
+
ttlMs?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface HistoryEntryShape {
|
|
14
|
+
entryType?: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
sender?: { id?: string; type?: string };
|
|
17
|
+
receiver?: { id?: string };
|
|
18
|
+
timestamp?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tracks outbound messages locally until server history syncs them, then
|
|
23
|
+
* merges any unmatched ones into the next read context. Prevents the LLM
|
|
24
|
+
* from re-saying things it already said when the server is slow to publish.
|
|
25
|
+
*/
|
|
26
|
+
export class OutboxBuffer {
|
|
27
|
+
private _pending: Map<string, PendingMessage[]> = new Map();
|
|
28
|
+
private _ttlMs: number;
|
|
29
|
+
|
|
30
|
+
constructor(options: OutboxBufferOptions = {}) {
|
|
31
|
+
this._ttlMs = Number.isFinite(options.ttlMs) ? Math.max(1_000, options.ttlMs!) : 10 * 60 * 1000;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
track(chatId: string, msg: { text: string; receiverId: string }): void {
|
|
35
|
+
this._gcChat(chatId);
|
|
36
|
+
if (!this._pending.has(chatId)) this._pending.set(chatId, []);
|
|
37
|
+
this._pending.get(chatId)!.push({ text: msg.text, receiverId: msg.receiverId, timestamp: Date.now() });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
merge(chatId: string, history: HistoryEntryShape[], agentId: string | null): void {
|
|
41
|
+
this._gcChat(chatId);
|
|
42
|
+
const pending = this._pending.get(chatId);
|
|
43
|
+
if (!pending || pending.length === 0) return;
|
|
44
|
+
|
|
45
|
+
const matchedIndices = new Set<number>();
|
|
46
|
+
const remaining: PendingMessage[] = [];
|
|
47
|
+
|
|
48
|
+
for (const msg of pending) {
|
|
49
|
+
const matchIdx = history.findIndex((entry, idx) =>
|
|
50
|
+
!matchedIndices.has(idx) &&
|
|
51
|
+
entry.text === msg.text &&
|
|
52
|
+
entry.sender?.id === agentId,
|
|
53
|
+
);
|
|
54
|
+
if (matchIdx !== -1) matchedIndices.add(matchIdx);
|
|
55
|
+
else remaining.push(msg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const msg of remaining) {
|
|
59
|
+
history.push({
|
|
60
|
+
entryType: 'message',
|
|
61
|
+
text: msg.text,
|
|
62
|
+
sender: { id: agentId || undefined, type: 'agent' },
|
|
63
|
+
receiver: { id: msg.receiverId },
|
|
64
|
+
timestamp: msg.timestamp,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (remaining.length === 0) this._pending.delete(chatId);
|
|
69
|
+
else this._pending.set(chatId, remaining);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
gc(): void {
|
|
73
|
+
for (const chatId of this._pending.keys()) this._gcChat(chatId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _gcChat(chatId: string): void {
|
|
77
|
+
const pending = this._pending.get(chatId);
|
|
78
|
+
if (!pending?.length) return;
|
|
79
|
+
const cutoff = Date.now() - this._ttlMs;
|
|
80
|
+
const fresh = pending.filter(msg => msg.timestamp >= cutoff);
|
|
81
|
+
if (fresh.length === 0) {
|
|
82
|
+
this._pending.delete(chatId);
|
|
83
|
+
runtimeLog.warn('OutboxBuffer', `expired all pending messages chatId=${chatId}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (fresh.length !== pending.length) {
|
|
87
|
+
this._pending.set(chatId, fresh);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedupes delivery across push (WS flush) and pull (inbox catch-up).
|
|
3
|
+
* ZIG-434 / ZIG-454: inbox is reconciliation source; stable message ids
|
|
4
|
+
* prevent double-processing when both paths fire for the same row.
|
|
5
|
+
*/
|
|
6
|
+
export class SeenMessages {
|
|
7
|
+
private readonly order: string[] = [];
|
|
8
|
+
private readonly seen = new Set<string>();
|
|
9
|
+
|
|
10
|
+
constructor(private readonly maxSize = 5_000) {}
|
|
11
|
+
|
|
12
|
+
has(messageId: string | null | undefined): boolean {
|
|
13
|
+
if (!messageId || typeof messageId !== 'string') return false;
|
|
14
|
+
return this.seen.has(messageId);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
mark(messageId: string | null | undefined): void {
|
|
18
|
+
if (!messageId || typeof messageId !== 'string') return;
|
|
19
|
+
if (this.seen.has(messageId)) return;
|
|
20
|
+
this.seen.add(messageId);
|
|
21
|
+
this.order.push(messageId);
|
|
22
|
+
while (this.order.length > this.maxSize) {
|
|
23
|
+
const oldest = this.order.shift();
|
|
24
|
+
if (oldest) this.seen.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|