opencode-graphiti 0.0.0-development
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/LICENSE +21 -0
- package/README.md +358 -0
- package/esm/_dnt.polyfills.d.ts +166 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +177 -0
- package/esm/_dnt.shims.d.ts +6 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/deno.d.ts +45 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +39 -0
- package/esm/mod.d.ts +3 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/config.d.ts +20 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +246 -0
- package/esm/src/handlers/chat.d.ts +14 -0
- package/esm/src/handlers/chat.d.ts.map +1 -0
- package/esm/src/handlers/chat.js +60 -0
- package/esm/src/handlers/compacting.d.ts +9 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -0
- package/esm/src/handlers/compacting.js +30 -0
- package/esm/src/handlers/event.d.ts +22 -0
- package/esm/src/handlers/event.d.ts.map +1 -0
- package/esm/src/handlers/event.js +287 -0
- package/esm/src/handlers/messages.d.ts +9 -0
- package/esm/src/handlers/messages.d.ts.map +1 -0
- package/esm/src/handlers/messages.js +93 -0
- package/esm/src/index.d.ts +5 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +153 -0
- package/esm/src/services/batch-drain.d.ts +23 -0
- package/esm/src/services/batch-drain.d.ts.map +1 -0
- package/esm/src/services/batch-drain.js +217 -0
- package/esm/src/services/connection-manager.d.ts +104 -0
- package/esm/src/services/connection-manager.d.ts.map +1 -0
- package/esm/src/services/connection-manager.js +621 -0
- package/esm/src/services/constants.d.ts +7 -0
- package/esm/src/services/constants.d.ts.map +1 -0
- package/esm/src/services/constants.js +6 -0
- package/esm/src/services/context-limit.d.ts +3 -0
- package/esm/src/services/context-limit.d.ts.map +1 -0
- package/esm/src/services/context-limit.js +44 -0
- package/esm/src/services/event-extractor.d.ts +29 -0
- package/esm/src/services/event-extractor.d.ts.map +1 -0
- package/esm/src/services/event-extractor.js +659 -0
- package/esm/src/services/graphiti-async.d.ts +22 -0
- package/esm/src/services/graphiti-async.d.ts.map +1 -0
- package/esm/src/services/graphiti-async.js +219 -0
- package/esm/src/services/graphiti-mcp.d.ts +57 -0
- package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
- package/esm/src/services/graphiti-mcp.js +194 -0
- package/esm/src/services/logger.d.ts +9 -0
- package/esm/src/services/logger.d.ts.map +1 -0
- package/esm/src/services/logger.js +104 -0
- package/esm/src/services/opencode-warning.d.ts +8 -0
- package/esm/src/services/opencode-warning.d.ts.map +1 -0
- package/esm/src/services/opencode-warning.js +104 -0
- package/esm/src/services/redis-cache.d.ts +27 -0
- package/esm/src/services/redis-cache.d.ts.map +1 -0
- package/esm/src/services/redis-cache.js +215 -0
- package/esm/src/services/redis-client.d.ts +89 -0
- package/esm/src/services/redis-client.d.ts.map +1 -0
- package/esm/src/services/redis-client.js +906 -0
- package/esm/src/services/redis-events.d.ts +46 -0
- package/esm/src/services/redis-events.d.ts.map +1 -0
- package/esm/src/services/redis-events.js +517 -0
- package/esm/src/services/redis-snapshot.d.ts +16 -0
- package/esm/src/services/redis-snapshot.d.ts.map +1 -0
- package/esm/src/services/redis-snapshot.js +184 -0
- package/esm/src/services/render-utils.d.ts +23 -0
- package/esm/src/services/render-utils.d.ts.map +1 -0
- package/esm/src/services/render-utils.js +149 -0
- package/esm/src/services/runtime-teardown.d.ts +23 -0
- package/esm/src/services/runtime-teardown.d.ts.map +1 -0
- package/esm/src/services/runtime-teardown.js +119 -0
- package/esm/src/services/sdk-normalize.d.ts +55 -0
- package/esm/src/services/sdk-normalize.d.ts.map +1 -0
- package/esm/src/services/sdk-normalize.js +61 -0
- package/esm/src/session.d.ts +74 -0
- package/esm/src/session.d.ts.map +1 -0
- package/esm/src/session.js +694 -0
- package/esm/src/types/index.d.ts +120 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +28 -0
- package/esm/src/utils.d.ts +27 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +76 -0
- package/package.json +59 -0
- package/script/_dnt.polyfills.d.ts +166 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +180 -0
- package/script/_dnt.shims.d.ts +6 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +65 -0
- package/script/deno.d.ts +45 -0
- package/script/deno.d.ts.map +1 -0
- package/script/deno.js +41 -0
- package/script/mod.d.ts +3 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +6 -0
- package/script/package.json +3 -0
- package/script/src/config.d.ts +20 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +256 -0
- package/script/src/handlers/chat.d.ts +14 -0
- package/script/src/handlers/chat.d.ts.map +1 -0
- package/script/src/handlers/chat.js +63 -0
- package/script/src/handlers/compacting.d.ts +9 -0
- package/script/src/handlers/compacting.d.ts.map +1 -0
- package/script/src/handlers/compacting.js +33 -0
- package/script/src/handlers/event.d.ts +22 -0
- package/script/src/handlers/event.d.ts.map +1 -0
- package/script/src/handlers/event.js +290 -0
- package/script/src/handlers/messages.d.ts +9 -0
- package/script/src/handlers/messages.d.ts.map +1 -0
- package/script/src/handlers/messages.js +96 -0
- package/script/src/index.d.ts +5 -0
- package/script/src/index.d.ts.map +1 -0
- package/script/src/index.js +159 -0
- package/script/src/services/batch-drain.d.ts +23 -0
- package/script/src/services/batch-drain.d.ts.map +1 -0
- package/script/src/services/batch-drain.js +221 -0
- package/script/src/services/connection-manager.d.ts +104 -0
- package/script/src/services/connection-manager.d.ts.map +1 -0
- package/script/src/services/connection-manager.js +635 -0
- package/script/src/services/constants.d.ts +7 -0
- package/script/src/services/constants.d.ts.map +1 -0
- package/script/src/services/constants.js +9 -0
- package/script/src/services/context-limit.d.ts +3 -0
- package/script/src/services/context-limit.d.ts.map +1 -0
- package/script/src/services/context-limit.js +47 -0
- package/script/src/services/event-extractor.d.ts +29 -0
- package/script/src/services/event-extractor.d.ts.map +1 -0
- package/script/src/services/event-extractor.js +669 -0
- package/script/src/services/graphiti-async.d.ts +22 -0
- package/script/src/services/graphiti-async.d.ts.map +1 -0
- package/script/src/services/graphiti-async.js +223 -0
- package/script/src/services/graphiti-mcp.d.ts +57 -0
- package/script/src/services/graphiti-mcp.d.ts.map +1 -0
- package/script/src/services/graphiti-mcp.js +198 -0
- package/script/src/services/logger.d.ts +9 -0
- package/script/src/services/logger.d.ts.map +1 -0
- package/script/src/services/logger.js +142 -0
- package/script/src/services/opencode-warning.d.ts +8 -0
- package/script/src/services/opencode-warning.d.ts.map +1 -0
- package/script/src/services/opencode-warning.js +114 -0
- package/script/src/services/redis-cache.d.ts +27 -0
- package/script/src/services/redis-cache.d.ts.map +1 -0
- package/script/src/services/redis-cache.js +219 -0
- package/script/src/services/redis-client.d.ts +89 -0
- package/script/src/services/redis-client.d.ts.map +1 -0
- package/script/src/services/redis-client.js +943 -0
- package/script/src/services/redis-events.d.ts +46 -0
- package/script/src/services/redis-events.d.ts.map +1 -0
- package/script/src/services/redis-events.js +535 -0
- package/script/src/services/redis-snapshot.d.ts +16 -0
- package/script/src/services/redis-snapshot.d.ts.map +1 -0
- package/script/src/services/redis-snapshot.js +189 -0
- package/script/src/services/render-utils.d.ts +23 -0
- package/script/src/services/render-utils.d.ts.map +1 -0
- package/script/src/services/render-utils.js +165 -0
- package/script/src/services/runtime-teardown.d.ts +23 -0
- package/script/src/services/runtime-teardown.d.ts.map +1 -0
- package/script/src/services/runtime-teardown.js +155 -0
- package/script/src/services/sdk-normalize.d.ts +55 -0
- package/script/src/services/sdk-normalize.d.ts.map +1 -0
- package/script/src/services/sdk-normalize.js +67 -0
- package/script/src/session.d.ts +74 -0
- package/script/src/session.d.ts.map +1 -0
- package/script/src/session.js +698 -0
- package/script/src/types/index.d.ts +120 -0
- package/script/src/types/index.d.ts.map +1 -0
- package/script/src/types/index.js +33 -0
- package/script/src/utils.d.ts +27 -0
- package/script/src/utils.d.ts.map +1 -0
- package/script/src/utils.js +87 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ClaimedDrainBatch, DrainQueueEntry, SessionEvent } from "../types/index.js";
|
|
2
|
+
import type { RedisClient } from "./redis-client.js";
|
|
3
|
+
export declare const sessionEventsKey: (sessionId: string) => string;
|
|
4
|
+
export declare const sessionSnapshotKey: (sessionId: string) => string;
|
|
5
|
+
export declare const memoryCacheKey: (groupId: string) => string;
|
|
6
|
+
export declare const memoryCacheMetaKey: (groupId: string) => string;
|
|
7
|
+
export declare const drainPendingKey: (groupId: string) => string;
|
|
8
|
+
export declare const drainCursorKey: (groupId: string) => string;
|
|
9
|
+
export declare const drainDeadKey: (groupId: string) => string;
|
|
10
|
+
export declare const drainRetryKey: (groupId: string, batchKey: string) => string;
|
|
11
|
+
export declare const drainClaimKey: (groupId: string, claimToken: string) => string;
|
|
12
|
+
export declare const drainClaimCheckpointKey: (groupId: string, claimToken: string) => string;
|
|
13
|
+
export declare const drainClaimActiveKey: (groupId: string) => string;
|
|
14
|
+
export declare const drainClaimLockKey: (groupId: string) => string;
|
|
15
|
+
export declare const buildDrainEpisodeBody: (entry: DrainQueueEntry) => string;
|
|
16
|
+
export declare const getDrainEpisodeBodyBytes: (entry: DrainQueueEntry) => number;
|
|
17
|
+
export interface RedisEventsServiceOptions {
|
|
18
|
+
sessionTtlSeconds: number;
|
|
19
|
+
claimLockTtlSeconds?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class RedisEventsService {
|
|
22
|
+
private readonly redis;
|
|
23
|
+
private readonly options;
|
|
24
|
+
private warnedInvalidClaimLockTtl;
|
|
25
|
+
constructor(redis: RedisClient, options: RedisEventsServiceOptions);
|
|
26
|
+
getClaimLockTtlSeconds(): number;
|
|
27
|
+
recordEvent(sessionId: string, groupId: string, event: SessionEvent): Promise<number>;
|
|
28
|
+
private isDurableDrainMutationUnavailable;
|
|
29
|
+
private isRedisUnavailable;
|
|
30
|
+
getRecentSessionEvents(sessionId: string, limit?: number, chronological?: boolean): Promise<SessionEvent[]>;
|
|
31
|
+
touchSessionEvents(sessionId: string): Promise<void>;
|
|
32
|
+
recallSessionEvents(sessionId: string, query: string, options?: {
|
|
33
|
+
scanLimit?: number;
|
|
34
|
+
resultLimit?: number;
|
|
35
|
+
}): Promise<SessionEvent[]>;
|
|
36
|
+
getPendingCount(groupId: string): Promise<number>;
|
|
37
|
+
getPendingBatch(groupId: string, maxItems: number, maxBytes: number): Promise<ClaimedDrainBatch | null>;
|
|
38
|
+
refreshClaimLease(groupId: string, claimToken: string, ttlSeconds?: number): Promise<boolean>;
|
|
39
|
+
private cleanupStaleClaimIfConnected;
|
|
40
|
+
markBatchSuccess(groupId: string, claimToken: string, entries: DrainQueueEntry[]): Promise<void>;
|
|
41
|
+
moveBatchToDeadLetter(groupId: string, entries: DrainQueueEntry[]): Promise<void>;
|
|
42
|
+
releaseClaim(groupId: string, claimToken: string): Promise<void>;
|
|
43
|
+
markClaimEntrySuccess(groupId: string, claimToken: string, entry: DrainQueueEntry): Promise<void>;
|
|
44
|
+
recoverAbandonedClaim(groupId: string): Promise<boolean>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=redis-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-events.d.ts","sourceRoot":"","sources":["../../../src/src/services/redis-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisEventsService = exports.getDrainEpisodeBodyBytes = exports.buildDrainEpisodeBody = exports.drainClaimLockKey = exports.drainClaimActiveKey = exports.drainClaimCheckpointKey = exports.drainClaimKey = exports.drainRetryKey = exports.drainDeadKey = exports.drainCursorKey = exports.drainPendingKey = exports.memoryCacheMetaKey = exports.memoryCacheKey = exports.sessionSnapshotKey = exports.sessionEventsKey = void 0;
|
|
4
|
+
const index_js_1 = require("../types/index.js");
|
|
5
|
+
const logger_js_1 = require("./logger.js");
|
|
6
|
+
const render_utils_js_1 = require("./render-utils.js");
|
|
7
|
+
const SESSION_EVENT_LIMIT = 40;
|
|
8
|
+
const SESSION_RECALL_SCAN_LIMIT = 120;
|
|
9
|
+
const SESSION_RECALL_RESULT_LIMIT = 12;
|
|
10
|
+
const DRAIN_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
11
|
+
const DEAD_LETTER_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
12
|
+
const CLAIM_LOCK_TTL_SECONDS = 60;
|
|
13
|
+
const RECALL_ELIGIBLE_CATEGORIES = new Set([
|
|
14
|
+
"task.create",
|
|
15
|
+
"task.update",
|
|
16
|
+
"task.complete",
|
|
17
|
+
"decision",
|
|
18
|
+
"preference",
|
|
19
|
+
"rule.load",
|
|
20
|
+
"file.read",
|
|
21
|
+
"file.write",
|
|
22
|
+
"file.edit",
|
|
23
|
+
"file.search",
|
|
24
|
+
"error",
|
|
25
|
+
"git.activity",
|
|
26
|
+
"subagent.start",
|
|
27
|
+
"subagent.finish",
|
|
28
|
+
"intent",
|
|
29
|
+
]);
|
|
30
|
+
const RECALL_STOP_WORDS = new Set([
|
|
31
|
+
"about",
|
|
32
|
+
"after",
|
|
33
|
+
"again",
|
|
34
|
+
"always",
|
|
35
|
+
"been",
|
|
36
|
+
"before",
|
|
37
|
+
"between",
|
|
38
|
+
"could",
|
|
39
|
+
"from",
|
|
40
|
+
"have",
|
|
41
|
+
"into",
|
|
42
|
+
"keep",
|
|
43
|
+
"more",
|
|
44
|
+
"please",
|
|
45
|
+
"should",
|
|
46
|
+
"that",
|
|
47
|
+
"the",
|
|
48
|
+
"their",
|
|
49
|
+
"them",
|
|
50
|
+
"there",
|
|
51
|
+
"these",
|
|
52
|
+
"this",
|
|
53
|
+
"those",
|
|
54
|
+
"with",
|
|
55
|
+
"without",
|
|
56
|
+
]);
|
|
57
|
+
const sessionEventsKey = (sessionId) => `session:${sessionId}:events`;
|
|
58
|
+
exports.sessionEventsKey = sessionEventsKey;
|
|
59
|
+
const sessionSnapshotKey = (sessionId) => `session:${sessionId}:snapshot`;
|
|
60
|
+
exports.sessionSnapshotKey = sessionSnapshotKey;
|
|
61
|
+
const memoryCacheKey = (groupId) => `memory-cache:${groupId}`;
|
|
62
|
+
exports.memoryCacheKey = memoryCacheKey;
|
|
63
|
+
const memoryCacheMetaKey = (groupId) => `memory-cache:${groupId}:meta`;
|
|
64
|
+
exports.memoryCacheMetaKey = memoryCacheMetaKey;
|
|
65
|
+
const drainPendingKey = (groupId) => `drain:pending:${groupId}`;
|
|
66
|
+
exports.drainPendingKey = drainPendingKey;
|
|
67
|
+
const drainCursorKey = (groupId) => `drain:cursor:${groupId}`;
|
|
68
|
+
exports.drainCursorKey = drainCursorKey;
|
|
69
|
+
const drainDeadKey = (groupId) => `drain:dead:${groupId}`;
|
|
70
|
+
exports.drainDeadKey = drainDeadKey;
|
|
71
|
+
const drainRetryKey = (groupId, batchKey) => `drain:retry:${groupId}:${batchKey}`;
|
|
72
|
+
exports.drainRetryKey = drainRetryKey;
|
|
73
|
+
const drainClaimKey = (groupId, claimToken) => `drain:claim:${groupId}:${claimToken}`;
|
|
74
|
+
exports.drainClaimKey = drainClaimKey;
|
|
75
|
+
const drainClaimCheckpointKey = (groupId, claimToken) => `drain:claim-checkpoint:${groupId}:${claimToken}`;
|
|
76
|
+
exports.drainClaimCheckpointKey = drainClaimCheckpointKey;
|
|
77
|
+
const drainClaimActiveKey = (groupId) => `drain:claim-active:${groupId}`;
|
|
78
|
+
exports.drainClaimActiveKey = drainClaimActiveKey;
|
|
79
|
+
const drainClaimLockKey = (groupId) => `drain:claim-lock:${groupId}`;
|
|
80
|
+
exports.drainClaimLockKey = drainClaimLockKey;
|
|
81
|
+
const makeClaimToken = () => crypto.randomUUID();
|
|
82
|
+
const textEncoder = new TextEncoder();
|
|
83
|
+
const DURABLE_DRAIN_MUTATION_UNAVAILABLE = "Redis hot tier unavailable for durable drain-state mutation";
|
|
84
|
+
const parseEntry = (raw) => {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const parseSessionEvent = (raw) => {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(raw);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const buildDrainEpisodeBody = (entry) => {
|
|
101
|
+
const refs = entry.event.refs?.length
|
|
102
|
+
? `\nRefs: ${entry.event.refs.join(", ")}`
|
|
103
|
+
: "";
|
|
104
|
+
const keywords = entry.event.keywords?.length
|
|
105
|
+
? `\nKeywords: ${entry.event.keywords.join(", ")}`
|
|
106
|
+
: "";
|
|
107
|
+
return (0, render_utils_js_1.sanitizeMemoryInput)((0, render_utils_js_1.stripInjectedMemoryBlocks)([
|
|
108
|
+
`Category: ${entry.event.category}`,
|
|
109
|
+
`Role: ${entry.event.role}`,
|
|
110
|
+
`Summary: ${entry.event.summary}`,
|
|
111
|
+
entry.event.detail ? `Detail: ${entry.event.detail}` : "",
|
|
112
|
+
entry.event.continuityText
|
|
113
|
+
? `Continuity: ${entry.event.continuityText}`
|
|
114
|
+
: (0, index_js_1.getSessionEventRecallText)(entry.event),
|
|
115
|
+
entry.event.body ? `Body: ${entry.event.body}` : "",
|
|
116
|
+
keywords,
|
|
117
|
+
refs,
|
|
118
|
+
].filter(Boolean).join("\n")));
|
|
119
|
+
};
|
|
120
|
+
exports.buildDrainEpisodeBody = buildDrainEpisodeBody;
|
|
121
|
+
const getDrainEpisodeBodyBytes = (entry) => textEncoder.encode((0, exports.buildDrainEpisodeBody)(entry)).length;
|
|
122
|
+
exports.getDrainEpisodeBodyBytes = getDrainEpisodeBodyBytes;
|
|
123
|
+
const sanitizeStoredValue = (value) => {
|
|
124
|
+
if (typeof value === "string") {
|
|
125
|
+
const sanitized = (0, render_utils_js_1.sanitizeMemoryInput)(value);
|
|
126
|
+
return sanitized || undefined;
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
return value.map((item) => sanitizeStoredValue(item)).filter((item) => item !== undefined);
|
|
130
|
+
}
|
|
131
|
+
if (value && typeof value === "object") {
|
|
132
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => {
|
|
133
|
+
const sanitized = sanitizeStoredValue(entry);
|
|
134
|
+
return sanitized === undefined ? [] : [[key, sanitized]];
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
};
|
|
139
|
+
const sanitizedStoredString = (value) => {
|
|
140
|
+
const sanitized = sanitizeStoredValue(value);
|
|
141
|
+
return typeof sanitized === "string" ? sanitized : undefined;
|
|
142
|
+
};
|
|
143
|
+
const sanitizedStoredStringArray = (value) => {
|
|
144
|
+
const sanitized = sanitizeStoredValue(value);
|
|
145
|
+
return Array.isArray(sanitized) ? sanitized : undefined;
|
|
146
|
+
};
|
|
147
|
+
const sanitizedStoredMetadata = (value) => {
|
|
148
|
+
const sanitized = sanitizeStoredValue(value);
|
|
149
|
+
return sanitized && typeof sanitized === "object" && !Array.isArray(sanitized)
|
|
150
|
+
? sanitized
|
|
151
|
+
: undefined;
|
|
152
|
+
};
|
|
153
|
+
const sanitizeStoredEvent = (event) => ({
|
|
154
|
+
...event,
|
|
155
|
+
summary: (0, render_utils_js_1.sanitizeMemoryInput)(event.summary),
|
|
156
|
+
body: sanitizedStoredString(event.body),
|
|
157
|
+
detail: sanitizedStoredString(event.detail),
|
|
158
|
+
continuityText: sanitizedStoredString(event.continuityText),
|
|
159
|
+
refs: sanitizedStoredStringArray(event.refs),
|
|
160
|
+
keywords: sanitizedStoredStringArray(event.keywords),
|
|
161
|
+
metadata: sanitizedStoredMetadata(event.metadata),
|
|
162
|
+
});
|
|
163
|
+
const tokenizeRecallQuery = (query) => {
|
|
164
|
+
const matches = query.toLowerCase().match(/[a-z0-9._/-]{3,}/g) ?? [];
|
|
165
|
+
return [...new Set(matches.filter((token) => !RECALL_STOP_WORDS.has(token)))];
|
|
166
|
+
};
|
|
167
|
+
const scoreSessionEventRecall = (event, query, tokens) => {
|
|
168
|
+
if (!RECALL_ELIGIBLE_CATEGORIES.has(event.category))
|
|
169
|
+
return 0;
|
|
170
|
+
const summary = event.summary.toLowerCase();
|
|
171
|
+
const continuity = (event.continuityText ?? "").toLowerCase();
|
|
172
|
+
const detail = (event.detail ?? "").toLowerCase();
|
|
173
|
+
const refs = (event.refs ?? []).join(" ").toLowerCase();
|
|
174
|
+
const keywords = (event.keywords ?? []).join(" ").toLowerCase();
|
|
175
|
+
const recallText = (0, index_js_1.getSessionEventRecallText)(event).toLowerCase();
|
|
176
|
+
let score = 0;
|
|
177
|
+
if (summary.includes(query))
|
|
178
|
+
score += 8;
|
|
179
|
+
else if (continuity.includes(query))
|
|
180
|
+
score += 7;
|
|
181
|
+
else if (detail.includes(query))
|
|
182
|
+
score += 5;
|
|
183
|
+
else if (recallText.includes(query))
|
|
184
|
+
score += 4;
|
|
185
|
+
for (const token of tokens) {
|
|
186
|
+
if (summary.includes(token))
|
|
187
|
+
score += 4;
|
|
188
|
+
if (continuity.includes(token))
|
|
189
|
+
score += 4;
|
|
190
|
+
if (detail.includes(token))
|
|
191
|
+
score += 3;
|
|
192
|
+
if (refs.includes(token))
|
|
193
|
+
score += 3;
|
|
194
|
+
if (keywords.includes(token))
|
|
195
|
+
score += 3;
|
|
196
|
+
if (recallText.includes(token))
|
|
197
|
+
score += 1;
|
|
198
|
+
}
|
|
199
|
+
return score;
|
|
200
|
+
};
|
|
201
|
+
class RedisEventsService {
|
|
202
|
+
constructor(redis, options) {
|
|
203
|
+
Object.defineProperty(this, "redis", {
|
|
204
|
+
enumerable: true,
|
|
205
|
+
configurable: true,
|
|
206
|
+
writable: true,
|
|
207
|
+
value: redis
|
|
208
|
+
});
|
|
209
|
+
Object.defineProperty(this, "options", {
|
|
210
|
+
enumerable: true,
|
|
211
|
+
configurable: true,
|
|
212
|
+
writable: true,
|
|
213
|
+
value: options
|
|
214
|
+
});
|
|
215
|
+
Object.defineProperty(this, "warnedInvalidClaimLockTtl", {
|
|
216
|
+
enumerable: true,
|
|
217
|
+
configurable: true,
|
|
218
|
+
writable: true,
|
|
219
|
+
value: false
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
getClaimLockTtlSeconds() {
|
|
223
|
+
const configured = this.options.claimLockTtlSeconds;
|
|
224
|
+
if (configured === undefined)
|
|
225
|
+
return CLAIM_LOCK_TTL_SECONDS;
|
|
226
|
+
if (!Number.isFinite(configured) || configured <= 0) {
|
|
227
|
+
if (!this.warnedInvalidClaimLockTtl) {
|
|
228
|
+
logger_js_1.logger.warn("Invalid drain claim TTL; falling back to default", {
|
|
229
|
+
configuredClaimLockTtlSeconds: configured,
|
|
230
|
+
effectiveClaimLockTtlSeconds: CLAIM_LOCK_TTL_SECONDS,
|
|
231
|
+
});
|
|
232
|
+
this.warnedInvalidClaimLockTtl = true;
|
|
233
|
+
}
|
|
234
|
+
return CLAIM_LOCK_TTL_SECONDS;
|
|
235
|
+
}
|
|
236
|
+
const normalized = Math.max(1, Math.ceil(configured));
|
|
237
|
+
if (normalized !== configured && !this.warnedInvalidClaimLockTtl) {
|
|
238
|
+
logger_js_1.logger.warn("Raised drain claim TTL to a sane minimum", {
|
|
239
|
+
configuredClaimLockTtlSeconds: configured,
|
|
240
|
+
effectiveClaimLockTtlSeconds: normalized,
|
|
241
|
+
});
|
|
242
|
+
this.warnedInvalidClaimLockTtl = true;
|
|
243
|
+
}
|
|
244
|
+
return normalized;
|
|
245
|
+
}
|
|
246
|
+
async recordEvent(sessionId, groupId, event) {
|
|
247
|
+
const sanitizedEvent = sanitizeStoredEvent(event);
|
|
248
|
+
const queueEntry = {
|
|
249
|
+
sessionId,
|
|
250
|
+
groupId,
|
|
251
|
+
event: sanitizedEvent,
|
|
252
|
+
};
|
|
253
|
+
await this.redis.prependToList((0, exports.sessionEventsKey)(sessionId), JSON.stringify(sanitizedEvent), this.options.sessionTtlSeconds);
|
|
254
|
+
try {
|
|
255
|
+
return await this.redis.prependToList((0, exports.drainPendingKey)(groupId), JSON.stringify(queueEntry), DRAIN_TTL_SECONDS);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (!this.isDurableDrainMutationUnavailable(error)) {
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
logger_js_1.logger.warn("Durable drain queue unavailable; skipping enqueue", {
|
|
262
|
+
groupId,
|
|
263
|
+
sessionId,
|
|
264
|
+
eventId: sanitizedEvent.id,
|
|
265
|
+
category: sanitizedEvent.category,
|
|
266
|
+
});
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
isDurableDrainMutationUnavailable(error) {
|
|
271
|
+
return error instanceof Error &&
|
|
272
|
+
error.message === DURABLE_DRAIN_MUTATION_UNAVAILABLE;
|
|
273
|
+
}
|
|
274
|
+
isRedisUnavailable(error) {
|
|
275
|
+
return error instanceof Error &&
|
|
276
|
+
(error.message === DURABLE_DRAIN_MUTATION_UNAVAILABLE ||
|
|
277
|
+
error.message.includes("redis unavailable"));
|
|
278
|
+
}
|
|
279
|
+
async getRecentSessionEvents(sessionId, limit = SESSION_EVENT_LIMIT, chronological = true) {
|
|
280
|
+
const raw = await this.redis.getRecentList((0, exports.sessionEventsKey)(sessionId), limit);
|
|
281
|
+
const events = raw.flatMap((item) => {
|
|
282
|
+
try {
|
|
283
|
+
return [JSON.parse(item)];
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
return chronological ? [...events].reverse() : events;
|
|
290
|
+
}
|
|
291
|
+
async touchSessionEvents(sessionId) {
|
|
292
|
+
await this.redis.touch((0, exports.sessionEventsKey)(sessionId), this.options.sessionTtlSeconds);
|
|
293
|
+
}
|
|
294
|
+
async recallSessionEvents(sessionId, query, options = {}) {
|
|
295
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
296
|
+
if (!normalizedQuery)
|
|
297
|
+
return [];
|
|
298
|
+
const tokens = tokenizeRecallQuery(normalizedQuery);
|
|
299
|
+
if (tokens.length === 0 && normalizedQuery.length < 3)
|
|
300
|
+
return [];
|
|
301
|
+
const raw = await this.redis.getListRange((0, exports.sessionEventsKey)(sessionId), 0, Math.max((options.scanLimit ?? SESSION_RECALL_SCAN_LIMIT) - 1, 0));
|
|
302
|
+
return raw
|
|
303
|
+
.flatMap((item) => {
|
|
304
|
+
const event = parseSessionEvent(item);
|
|
305
|
+
if (!event)
|
|
306
|
+
return [];
|
|
307
|
+
const score = scoreSessionEventRecall(event, normalizedQuery, tokens);
|
|
308
|
+
return score > 0 ? [{ event, score }] : [];
|
|
309
|
+
})
|
|
310
|
+
.sort((left, right) => {
|
|
311
|
+
if (right.score !== left.score)
|
|
312
|
+
return right.score - left.score;
|
|
313
|
+
if (right.event.ts !== left.event.ts) {
|
|
314
|
+
return right.event.ts - left.event.ts;
|
|
315
|
+
}
|
|
316
|
+
return left.event.id.localeCompare(right.event.id);
|
|
317
|
+
})
|
|
318
|
+
.slice(0, options.resultLimit ?? SESSION_RECALL_RESULT_LIMIT)
|
|
319
|
+
.map(({ event }) => event);
|
|
320
|
+
}
|
|
321
|
+
async getPendingCount(groupId) {
|
|
322
|
+
return await this.redis.getListLength((0, exports.drainPendingKey)(groupId));
|
|
323
|
+
}
|
|
324
|
+
async getPendingBatch(groupId, maxItems, maxBytes) {
|
|
325
|
+
if (maxItems <= 0)
|
|
326
|
+
return null;
|
|
327
|
+
await this.recoverAbandonedClaim(groupId);
|
|
328
|
+
const pendingKey = (0, exports.drainPendingKey)(groupId);
|
|
329
|
+
if (await this.redis.getListLength(pendingKey) === 0)
|
|
330
|
+
return null;
|
|
331
|
+
const claimToken = makeClaimToken();
|
|
332
|
+
const claimKey = (0, exports.drainClaimKey)(groupId, claimToken);
|
|
333
|
+
const checkpointKey = (0, exports.drainClaimCheckpointKey)(groupId, claimToken);
|
|
334
|
+
const lockAcquired = await this.redis.setStringIfAbsent((0, exports.drainClaimLockKey)(groupId), claimToken, this.getClaimLockTtlSeconds());
|
|
335
|
+
if (!lockAcquired)
|
|
336
|
+
return null;
|
|
337
|
+
const selected = [];
|
|
338
|
+
let totalBytes = 0;
|
|
339
|
+
try {
|
|
340
|
+
await this.redis.setString((0, exports.drainClaimActiveKey)(groupId), claimToken, DRAIN_TTL_SECONDS);
|
|
341
|
+
while (selected.length < maxItems) {
|
|
342
|
+
const raw = await this.redis.moveListItem(pendingKey, claimKey, "RIGHT", "RIGHT");
|
|
343
|
+
if (!raw)
|
|
344
|
+
break;
|
|
345
|
+
await this.redis.touch(claimKey, DRAIN_TTL_SECONDS);
|
|
346
|
+
const entry = parseEntry(raw);
|
|
347
|
+
if (!entry) {
|
|
348
|
+
await this.redis.moveListItem(claimKey, (0, exports.drainDeadKey)(groupId), "RIGHT", "RIGHT");
|
|
349
|
+
await this.redis.touch((0, exports.drainDeadKey)(groupId), DEAD_LETTER_TTL_SECONDS);
|
|
350
|
+
logger_js_1.logger.warn("Dead-lettered malformed claimed drain payload", {
|
|
351
|
+
groupId,
|
|
352
|
+
claimToken,
|
|
353
|
+
raw,
|
|
354
|
+
});
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const bytes = (0, exports.getDrainEpisodeBodyBytes)(entry);
|
|
358
|
+
if (bytes > maxBytes) {
|
|
359
|
+
await this.redis.moveListItem(claimKey, (0, exports.drainDeadKey)(groupId), "RIGHT", "RIGHT");
|
|
360
|
+
await this.redis.touch((0, exports.drainDeadKey)(groupId), DEAD_LETTER_TTL_SECONDS);
|
|
361
|
+
logger_js_1.logger.warn("Dead-lettered oversized claimed drain payload", {
|
|
362
|
+
groupId,
|
|
363
|
+
claimToken,
|
|
364
|
+
eventId: entry.event.id,
|
|
365
|
+
eventBytes: bytes,
|
|
366
|
+
batchMaxBytes: maxBytes,
|
|
367
|
+
});
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (selected.length > 0 && totalBytes + bytes > maxBytes) {
|
|
371
|
+
await this.redis.moveListItem(claimKey, pendingKey, "RIGHT", "RIGHT");
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
selected.push(entry);
|
|
375
|
+
totalBytes += bytes;
|
|
376
|
+
}
|
|
377
|
+
if (selected.length === 0) {
|
|
378
|
+
await this.redis.deleteKey(claimKey);
|
|
379
|
+
await this.redis.deleteKey(checkpointKey);
|
|
380
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimActiveKey)(groupId), claimToken);
|
|
381
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimLockKey)(groupId), claimToken);
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
claimToken,
|
|
386
|
+
claimKey,
|
|
387
|
+
lockTtlSeconds: this.getClaimLockTtlSeconds(),
|
|
388
|
+
entries: selected,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
await this.releaseClaim(groupId, claimToken);
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async refreshClaimLease(groupId, claimToken, ttlSeconds = this.getClaimLockTtlSeconds()) {
|
|
397
|
+
try {
|
|
398
|
+
const lockRefreshed = await this.redis.compareAndTouch((0, exports.drainClaimLockKey)(groupId), claimToken, ttlSeconds);
|
|
399
|
+
if (!lockRefreshed) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const activeRefreshed = await this.redis.compareAndTouch((0, exports.drainClaimActiveKey)(groupId), claimToken, DRAIN_TTL_SECONDS);
|
|
403
|
+
if (!activeRefreshed) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
await this.redis.touch((0, exports.drainClaimKey)(groupId, claimToken), DRAIN_TTL_SECONDS);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
if (!this.isRedisUnavailable(error))
|
|
411
|
+
throw error;
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async cleanupStaleClaimIfConnected(groupId, claimToken) {
|
|
416
|
+
if (!this.redis.isConnected())
|
|
417
|
+
return false;
|
|
418
|
+
const activeKey = (0, exports.drainClaimActiveKey)(groupId);
|
|
419
|
+
const lockKey = (0, exports.drainClaimLockKey)(groupId);
|
|
420
|
+
const claimKey = (0, exports.drainClaimKey)(groupId, claimToken);
|
|
421
|
+
const checkpointKey = (0, exports.drainClaimCheckpointKey)(groupId, claimToken);
|
|
422
|
+
let activeToken;
|
|
423
|
+
let lockToken;
|
|
424
|
+
let claimLength;
|
|
425
|
+
let checkpointLength;
|
|
426
|
+
try {
|
|
427
|
+
[activeToken, lockToken, claimLength, checkpointLength] = await Promise
|
|
428
|
+
.all([
|
|
429
|
+
this.redis.getString(activeKey),
|
|
430
|
+
this.redis.getString(lockKey),
|
|
431
|
+
this.redis.getListLength(claimKey),
|
|
432
|
+
this.redis.getListLength(checkpointKey),
|
|
433
|
+
]);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
if (!this.isRedisUnavailable(error))
|
|
437
|
+
throw error;
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
if (claimLength === 0 && checkpointLength === 0)
|
|
441
|
+
return false;
|
|
442
|
+
const missingLockForSameActive = activeToken === claimToken &&
|
|
443
|
+
lockToken === null;
|
|
444
|
+
const missingActiveForSameLock = lockToken === claimToken &&
|
|
445
|
+
activeToken !== claimToken;
|
|
446
|
+
const orphanedPointers = activeToken === null && lockToken === null;
|
|
447
|
+
if (!missingLockForSameActive &&
|
|
448
|
+
!missingActiveForSameLock &&
|
|
449
|
+
!orphanedPointers) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
await this.releaseClaim(groupId, claimToken);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (!this.isRedisUnavailable(error))
|
|
458
|
+
throw error;
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async markBatchSuccess(groupId, claimToken, entries) {
|
|
463
|
+
if (entries.length === 0)
|
|
464
|
+
return;
|
|
465
|
+
await this.redis.deleteKey((0, exports.drainClaimKey)(groupId, claimToken));
|
|
466
|
+
await this.redis.deleteKey((0, exports.drainClaimCheckpointKey)(groupId, claimToken));
|
|
467
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimActiveKey)(groupId), claimToken);
|
|
468
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimLockKey)(groupId), claimToken);
|
|
469
|
+
await this.redis.setString((0, exports.drainCursorKey)(groupId), entries.at(-1)?.event.id ?? "", DRAIN_TTL_SECONDS);
|
|
470
|
+
}
|
|
471
|
+
async moveBatchToDeadLetter(groupId, entries) {
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
await this.redis.appendToList((0, exports.drainDeadKey)(groupId), JSON.stringify(entry), DEAD_LETTER_TTL_SECONDS);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async releaseClaim(groupId, claimToken) {
|
|
477
|
+
const pendingKey = (0, exports.drainPendingKey)(groupId);
|
|
478
|
+
const claimKey = (0, exports.drainClaimKey)(groupId, claimToken);
|
|
479
|
+
const checkpointKey = (0, exports.drainClaimCheckpointKey)(groupId, claimToken);
|
|
480
|
+
while (true) {
|
|
481
|
+
const raw = await this.redis.moveListItem(claimKey, pendingKey, "RIGHT", "RIGHT");
|
|
482
|
+
if (!raw)
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
await this.redis.deleteKey(claimKey);
|
|
486
|
+
await this.redis.deleteKey(checkpointKey);
|
|
487
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimActiveKey)(groupId), claimToken);
|
|
488
|
+
await this.redis.deleteKeyIfValue((0, exports.drainClaimLockKey)(groupId), claimToken);
|
|
489
|
+
}
|
|
490
|
+
async markClaimEntrySuccess(groupId, claimToken, entry) {
|
|
491
|
+
const checkpointKey = (0, exports.drainClaimCheckpointKey)(groupId, claimToken);
|
|
492
|
+
const checkpointed = await this.redis.getListRange(checkpointKey, 0, -1);
|
|
493
|
+
if (checkpointed.some((raw) => parseEntry(raw)?.event.id === entry.event.id)) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const raw = await this.redis.moveListItem((0, exports.drainClaimKey)(groupId, claimToken), checkpointKey, "LEFT", "RIGHT");
|
|
497
|
+
if (!raw)
|
|
498
|
+
return;
|
|
499
|
+
const claimedEntry = parseEntry(raw);
|
|
500
|
+
if (claimedEntry?.event.id !== entry.event.id) {
|
|
501
|
+
throw new Error(`Drain claim checkpoint order mismatch for event ${entry.event.id}`);
|
|
502
|
+
}
|
|
503
|
+
await this.redis.touch(checkpointKey, DRAIN_TTL_SECONDS);
|
|
504
|
+
await this.redis.setString((0, exports.drainCursorKey)(groupId), entry.event.id, DRAIN_TTL_SECONDS);
|
|
505
|
+
}
|
|
506
|
+
async recoverAbandonedClaim(groupId) {
|
|
507
|
+
if (!this.redis.isConnected())
|
|
508
|
+
return false;
|
|
509
|
+
let activeToken;
|
|
510
|
+
let lockToken;
|
|
511
|
+
try {
|
|
512
|
+
[activeToken, lockToken] = await Promise.all([
|
|
513
|
+
this.redis.getString((0, exports.drainClaimActiveKey)(groupId)),
|
|
514
|
+
this.redis.getString((0, exports.drainClaimLockKey)(groupId)),
|
|
515
|
+
]);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (!this.isRedisUnavailable(error))
|
|
519
|
+
throw error;
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
const claimTokens = [activeToken, lockToken].filter((token) => {
|
|
523
|
+
return typeof token === "string" && token.length > 0;
|
|
524
|
+
});
|
|
525
|
+
if (claimTokens.length === 0)
|
|
526
|
+
return false;
|
|
527
|
+
for (const claimToken of new Set(claimTokens)) {
|
|
528
|
+
if (await this.cleanupStaleClaimIfConnected(groupId, claimToken)) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
exports.RedisEventsService = RedisEventsService;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SessionEvent } from "../types/index.js";
|
|
2
|
+
import type { RedisClient } from "./redis-client.js";
|
|
3
|
+
export declare const buildSessionSnapshotXml: (sessionId: string, events: SessionEvent[]) => string;
|
|
4
|
+
export interface RedisSnapshotServiceOptions {
|
|
5
|
+
ttlSeconds: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class RedisSnapshotService {
|
|
8
|
+
private readonly redis;
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(redis: RedisClient, options: RedisSnapshotServiceOptions);
|
|
11
|
+
getSnapshot(sessionId: string): Promise<string | null>;
|
|
12
|
+
saveSnapshot(sessionId: string, snapshot: string): Promise<void>;
|
|
13
|
+
touchSnapshot(sessionId: string): Promise<void>;
|
|
14
|
+
rebuildAndSave(sessionId: string, events: SessionEvent[]): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=redis-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-snapshot.d.ts","sourceRoot":"","sources":["../../../src/src/services/redis-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgCrD,eAAO,MAAM,uBAAuB,GAClC,WAAW,MAAM,EACjB,QAAQ,YAAY,EAAE,KACrB,MAiOF,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,2BAA2B;IAGjD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAItD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/C,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC;CAKnB"}
|