@batalabs/virlow-mcp-core 3.11.4
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 +15 -0
- package/dist/api.d.ts +118 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +109 -0
- package/dist/api.js.map +1 -0
- package/dist/embedding-cache.d.ts +34 -0
- package/dist/embedding-cache.d.ts.map +1 -0
- package/dist/embedding-cache.js +108 -0
- package/dist/embedding-cache.js.map +1 -0
- package/dist/exposure.d.ts +42 -0
- package/dist/exposure.d.ts.map +1 -0
- package/dist/exposure.js +74 -0
- package/dist/exposure.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/memories-enabled.d.ts +16 -0
- package/dist/memories-enabled.d.ts.map +1 -0
- package/dist/memories-enabled.js +29 -0
- package/dist/memories-enabled.js.map +1 -0
- package/dist/memories.d.ts +86 -0
- package/dist/memories.d.ts.map +1 -0
- package/dist/memories.js +350 -0
- package/dist/memories.js.map +1 -0
- package/dist/memory-note.d.ts +45 -0
- package/dist/memory-note.d.ts.map +1 -0
- package/dist/memory-note.js +144 -0
- package/dist/memory-note.js.map +1 -0
- package/dist/memory-tree.d.ts +28 -0
- package/dist/memory-tree.d.ts.map +1 -0
- package/dist/memory-tree.js +68 -0
- package/dist/memory-tree.js.map +1 -0
- package/dist/notes.d.ts +109 -0
- package/dist/notes.d.ts.map +1 -0
- package/dist/notes.js +234 -0
- package/dist/notes.js.map +1 -0
- package/dist/vault.d.ts +35 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +73 -0
- package/dist/vault.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
/** Serialisation order, so a rewrite of unchanged metadata is a no-op diff. */
|
|
3
|
+
const KEY_ORDER = ['source', 'tags', 'confidence', 'created'];
|
|
4
|
+
const FENCE = '---';
|
|
5
|
+
/** The app's code editor stores content as this envelope; `code` is what the
|
|
6
|
+
* user sees and edits in Monaco. */
|
|
7
|
+
export function toCodeEnvelope(body) {
|
|
8
|
+
return JSON.stringify({ language: 'markdown', code: body, lastExecution: null });
|
|
9
|
+
}
|
|
10
|
+
function unwrapEnvelope(content) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(content);
|
|
13
|
+
if (parsed !== null &&
|
|
14
|
+
typeof parsed === 'object' &&
|
|
15
|
+
typeof parsed.code === 'string') {
|
|
16
|
+
return parsed.code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Not JSON at all — a hand-written note, which is a perfectly good memory.
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
/** Keep only keys we understand, and only when their value has the right
|
|
25
|
+
* shape. One malformed key never discards its neighbours. */
|
|
26
|
+
function coerceMeta(raw) {
|
|
27
|
+
const meta = {};
|
|
28
|
+
if (typeof raw.source === 'string')
|
|
29
|
+
meta.source = raw.source;
|
|
30
|
+
if (Array.isArray(raw.tags) &&
|
|
31
|
+
raw.tags.every((t) => typeof t === 'string')) {
|
|
32
|
+
meta.tags = [...raw.tags];
|
|
33
|
+
}
|
|
34
|
+
// YAML turns `confidence: 0.8` into a number; the field is free-form text.
|
|
35
|
+
if (typeof raw.confidence === 'string') {
|
|
36
|
+
meta.confidence = raw.confidence;
|
|
37
|
+
}
|
|
38
|
+
else if (typeof raw.confidence === 'number') {
|
|
39
|
+
meta.confidence = String(raw.confidence);
|
|
40
|
+
}
|
|
41
|
+
if (typeof raw.created === 'string') {
|
|
42
|
+
meta.created = raw.created;
|
|
43
|
+
}
|
|
44
|
+
else if (raw.created instanceof Date) {
|
|
45
|
+
// YAML parses unquoted ISO timestamps into Dates.
|
|
46
|
+
meta.created = raw.created.toISOString();
|
|
47
|
+
}
|
|
48
|
+
return meta;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Split a decrypted note body into its fact and metadata.
|
|
52
|
+
*
|
|
53
|
+
* Total by construction: a missing fence, unparseable YAML, frontmatter that
|
|
54
|
+
* is not a mapping, or a note that was never a memory note at all all yield
|
|
55
|
+
* `{ fact: <the whole body>, meta: {} }`. A human edits these by hand, so a
|
|
56
|
+
* syntax error must cost them a bit of metadata, never the memory.
|
|
57
|
+
*/
|
|
58
|
+
export function parseMemoryNote(decryptedContent) {
|
|
59
|
+
// The note's `type` is deliberately not consulted: an envelope is accepted
|
|
60
|
+
// wherever one is found, so a memory whose type was changed in the app — or
|
|
61
|
+
// a plain note dropped into the folder by hand — still reads correctly.
|
|
62
|
+
const body = unwrapEnvelope(decryptedContent);
|
|
63
|
+
if (!body.startsWith(`${FENCE}\n`))
|
|
64
|
+
return { fact: body, meta: {} };
|
|
65
|
+
// Only the first fence pair is frontmatter; --- lines inside the fact are
|
|
66
|
+
// ordinary content.
|
|
67
|
+
const close = body.indexOf(`\n${FENCE}\n`, FENCE.length);
|
|
68
|
+
if (close === -1)
|
|
69
|
+
return { fact: body, meta: {} };
|
|
70
|
+
const front = body.slice(FENCE.length + 1, close);
|
|
71
|
+
const fact = body.slice(close + FENCE.length + 2);
|
|
72
|
+
// An empty fence is the marker `serializeMemoryBody` writes when a fact
|
|
73
|
+
// would otherwise be misread as its own frontmatter. Strip it and keep the
|
|
74
|
+
// fact whole.
|
|
75
|
+
if (front.trim() === '')
|
|
76
|
+
return { fact, meta: {} };
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = parseYaml(front);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { fact: body, meta: {} };
|
|
83
|
+
}
|
|
84
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
85
|
+
return { fact: body, meta: {} };
|
|
86
|
+
}
|
|
87
|
+
return { fact, meta: coerceMeta(raw) };
|
|
88
|
+
}
|
|
89
|
+
function yamlScalar(value) {
|
|
90
|
+
// Quote anything YAML would otherwise reinterpret (or that would break the
|
|
91
|
+
// line), so a round-trip returns the same string.
|
|
92
|
+
return /^[A-Za-z0-9][\w .:@/+-]*$/.test(value) && !value.includes(': ')
|
|
93
|
+
? value
|
|
94
|
+
: JSON.stringify(value);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Canonical memory body: frontmatter then the fact, or just the fact when
|
|
98
|
+
* there is no metadata to record.
|
|
99
|
+
*/
|
|
100
|
+
export function serializeMemoryBody(fact, meta) {
|
|
101
|
+
const lines = [];
|
|
102
|
+
for (const key of KEY_ORDER) {
|
|
103
|
+
const value = meta[key];
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
if (key === 'tags') {
|
|
107
|
+
const tags = value;
|
|
108
|
+
if (tags.length === 0)
|
|
109
|
+
continue;
|
|
110
|
+
lines.push(`tags: [${tags.map(yamlScalar).join(', ')}]`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
lines.push(`${key}: ${yamlScalar(value)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (lines.length === 0) {
|
|
117
|
+
// A fact that opens with its own fence (a pasted document with
|
|
118
|
+
// frontmatter, say) would be read back as metadata and lose its first
|
|
119
|
+
// lines. An empty fence disambiguates it.
|
|
120
|
+
return fact.startsWith(`${FENCE}\n`) ? `${FENCE}\n${FENCE}\n${fact}` : fact;
|
|
121
|
+
}
|
|
122
|
+
return `${FENCE}\n${lines.join('\n')}\n${FENCE}\n${fact}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Metadata for an update: named keys win, unnamed keys carry forward, and
|
|
126
|
+
* `created` is pinned to the first write so an in-place dedupe merge cannot
|
|
127
|
+
* make an old memory look new.
|
|
128
|
+
*/
|
|
129
|
+
export function mergeMeta(existing, changes) {
|
|
130
|
+
const merged = { ...existing };
|
|
131
|
+
if (changes.source !== undefined)
|
|
132
|
+
merged.source = changes.source;
|
|
133
|
+
if (changes.tags !== undefined)
|
|
134
|
+
merged.tags = [...changes.tags];
|
|
135
|
+
if (changes.confidence !== undefined)
|
|
136
|
+
merged.confidence = changes.confidence;
|
|
137
|
+
// Pinned to the first write, so an in-place dedupe merge cannot make an old
|
|
138
|
+
// memory look newly created.
|
|
139
|
+
if (changes.created !== undefined && existing.created === undefined) {
|
|
140
|
+
merged.created = changes.created;
|
|
141
|
+
}
|
|
142
|
+
return merged;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=memory-note.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-note.js","sourceRoot":"","sources":["../src/memory-note.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type VirlowApi } from './api';
|
|
2
|
+
/** Name given to the memories root when the server has to create it. The root
|
|
3
|
+
* is identified by `kind`, never by this name — the user is free to rename it. */
|
|
4
|
+
export declare const MEMORIES_ROOT_NAME = "Memories";
|
|
5
|
+
export interface MemoryTree {
|
|
6
|
+
rootId: string;
|
|
7
|
+
/** namespace name -> subfolder id (direct children of the root) */
|
|
8
|
+
namespaces: Map<string, string>;
|
|
9
|
+
/** folder id -> namespace name; undefined for the root itself */
|
|
10
|
+
namespaceOf(folderId: string): string | undefined;
|
|
11
|
+
/** every folder id whose notes are memories, root first */
|
|
12
|
+
folderIds(): string[];
|
|
13
|
+
/** Record a subfolder created after the tree was resolved. Used by
|
|
14
|
+
* `ensureNamespace`; callers outside this module have no reason to. */
|
|
15
|
+
attach(folderId: string, name: string): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the memories folder tree, creating the root if the user has none.
|
|
19
|
+
* The root is whichever folder carries `kind: 'memories'` — the API allows
|
|
20
|
+
* only one per user.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveMemoryTree(api: VirlowApi): Promise<MemoryTree>;
|
|
23
|
+
/**
|
|
24
|
+
* Folder id for `name`, creating the namespace subfolder if it does not exist
|
|
25
|
+
* yet. Mutates `tree` so a later call in the same session is a cache hit.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ensureNamespace(api: VirlowApi, tree: MemoryTree, name: string): Promise<string>;
|
|
28
|
+
//# sourceMappingURL=memory-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-tree.d.ts","sourceRoot":"","sources":["../src/memory-tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjD;kFACkF;AAClF,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAClD,2DAA2D;IAC3D,SAAS,IAAI,MAAM,EAAE,CAAC;IACtB;2EACuE;IACvE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAgCD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAiB3E;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAOjB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ApiError } from './api';
|
|
2
|
+
/** Name given to the memories root when the server has to create it. The root
|
|
3
|
+
* is identified by `kind`, never by this name — the user is free to rename it. */
|
|
4
|
+
export const MEMORIES_ROOT_NAME = 'Memories';
|
|
5
|
+
function buildTree(rootId, folders) {
|
|
6
|
+
const namespaces = new Map();
|
|
7
|
+
const byFolderId = new Map();
|
|
8
|
+
const attach = (folderId, name) => {
|
|
9
|
+
byFolderId.set(folderId, name);
|
|
10
|
+
// Two subfolders may share a name — that is the user's organising choice.
|
|
11
|
+
// Both count as that namespace; writes go to the first.
|
|
12
|
+
if (!namespaces.has(name))
|
|
13
|
+
namespaces.set(name, folderId);
|
|
14
|
+
};
|
|
15
|
+
// Only direct children of the root are namespaces. Anything deeper is an
|
|
16
|
+
// ordinary folder the user nested for their own reasons; its notes are not
|
|
17
|
+
// memories, so it never reaches byFolderId and the sync walk skips it.
|
|
18
|
+
for (const folder of folders) {
|
|
19
|
+
if (folder.parentId === rootId)
|
|
20
|
+
attach(folder.id, folder.name);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
rootId,
|
|
24
|
+
namespaces,
|
|
25
|
+
namespaceOf: (folderId) => byFolderId.get(folderId),
|
|
26
|
+
folderIds: () => [rootId, ...byFolderId.keys()],
|
|
27
|
+
attach,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the memories folder tree, creating the root if the user has none.
|
|
32
|
+
* The root is whichever folder carries `kind: 'memories'` — the API allows
|
|
33
|
+
* only one per user.
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveMemoryTree(api) {
|
|
36
|
+
const { folders } = await api.listFolders();
|
|
37
|
+
const existing = folders.find((f) => f.kind === 'memories');
|
|
38
|
+
if (existing)
|
|
39
|
+
return buildTree(existing.id, folders);
|
|
40
|
+
try {
|
|
41
|
+
const created = await api.createFolder(MEMORIES_ROOT_NAME, null, 'memories');
|
|
42
|
+
return buildTree(created.id, [...folders, created]);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// 409 MEMORIES_FOLDER_EXISTS: another client created the root between our
|
|
46
|
+
// listing and our POST. Re-list and use the winner.
|
|
47
|
+
if (!(err instanceof ApiError) || err.status !== 409)
|
|
48
|
+
throw err;
|
|
49
|
+
const retry = await api.listFolders();
|
|
50
|
+
const winner = retry.folders.find((f) => f.kind === 'memories');
|
|
51
|
+
if (!winner)
|
|
52
|
+
throw err;
|
|
53
|
+
return buildTree(winner.id, retry.folders);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Folder id for `name`, creating the namespace subfolder if it does not exist
|
|
58
|
+
* yet. Mutates `tree` so a later call in the same session is a cache hit.
|
|
59
|
+
*/
|
|
60
|
+
export async function ensureNamespace(api, tree, name) {
|
|
61
|
+
const existing = tree.namespaces.get(name);
|
|
62
|
+
if (existing)
|
|
63
|
+
return existing;
|
|
64
|
+
const created = await api.createFolder(name, tree.rootId);
|
|
65
|
+
tree.attach(created.id, name);
|
|
66
|
+
return created.id;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=memory-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-tree.js","sourceRoot":"","sources":["../src/memory-tree.ts"],"names":[],"mappings":"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"}
|
package/dist/notes.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type Exposure } from './exposure';
|
|
2
|
+
import type { VirlowApi } from './api';
|
|
3
|
+
import type { Vault } from './vault';
|
|
4
|
+
/** Hard ceiling on how many notes searchNotes() will decrypt+scan in a
|
|
5
|
+
* single call. Keeps a single search bounded even for very large accounts;
|
|
6
|
+
* `truncated` tells the caller when the account holds more than this. */
|
|
7
|
+
export declare const SEARCH_SCAN_CAP = 500;
|
|
8
|
+
export declare class NotesService {
|
|
9
|
+
private readonly api;
|
|
10
|
+
private readonly vault;
|
|
11
|
+
/** Per-unlock cache of decrypted (title, content) keyed by `id:updatedAt`
|
|
12
|
+
* so re-reading/re-scanning the same note twice in one unlock doesn't
|
|
13
|
+
* re-run AES-GCM decryption. Cleared whenever the vault locks. */
|
|
14
|
+
private readonly decryptCache;
|
|
15
|
+
/** Bumped every time the vault locks (see handleLock()). decryptRow()
|
|
16
|
+
* captures this before its await on decryptNoteFields() and re-checks it
|
|
17
|
+
* afterward, so a decrypt that was still in flight when the vault locked
|
|
18
|
+
* never writes decrypted plaintext into the cache after the lock. */
|
|
19
|
+
private epoch;
|
|
20
|
+
/** Serializes updateNote() calls so two overlapping partial edits to the
|
|
21
|
+
* same note (e.g. one changing title, one changing content) never race
|
|
22
|
+
* their read-merge-encrypt-write cycle — without this, both could read
|
|
23
|
+
* the same pre-edit row and the second write would silently clobber the
|
|
24
|
+
* first's change. Mirrors MemoryStore's enqueue()/mutationChain pattern. */
|
|
25
|
+
private mutationChain;
|
|
26
|
+
constructor(api: VirlowApi, vault: Vault);
|
|
27
|
+
private handleLock;
|
|
28
|
+
listNotes(params: {
|
|
29
|
+
folderId?: string;
|
|
30
|
+
starred?: boolean;
|
|
31
|
+
archived?: boolean;
|
|
32
|
+
page?: number;
|
|
33
|
+
limit?: number;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
notes: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
folderId: string | null;
|
|
39
|
+
starred: boolean;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}>;
|
|
42
|
+
pagination: {
|
|
43
|
+
page: number;
|
|
44
|
+
limit: number;
|
|
45
|
+
total: number;
|
|
46
|
+
pages: number;
|
|
47
|
+
};
|
|
48
|
+
/** How many otherwise-visible notes were withheld, split by why — the
|
|
49
|
+
* remedy differs. Reported so a filtered list can never be mistaken for an
|
|
50
|
+
* empty one. */
|
|
51
|
+
withheld: {
|
|
52
|
+
inClosedFolders: number;
|
|
53
|
+
unfiled: number;
|
|
54
|
+
};
|
|
55
|
+
exposure: Exposure;
|
|
56
|
+
}>;
|
|
57
|
+
readNote(id: string): Promise<{
|
|
58
|
+
id: string;
|
|
59
|
+
title: string;
|
|
60
|
+
content: string;
|
|
61
|
+
folderId: string | null;
|
|
62
|
+
}>;
|
|
63
|
+
createNote(title: string, content: string, folderId?: string | null, type?: string): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
}>;
|
|
66
|
+
updateNote(id: string, changes: {
|
|
67
|
+
title?: string;
|
|
68
|
+
content?: string;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
searchNotes(query: string, limit?: number): Promise<{
|
|
71
|
+
results: Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
title: string;
|
|
74
|
+
snippet: string;
|
|
75
|
+
}>;
|
|
76
|
+
scanned: number;
|
|
77
|
+
truncated: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
moveNote(id: string, folderId: string | null): Promise<void>;
|
|
80
|
+
setStar(id: string, starred: boolean): Promise<void>;
|
|
81
|
+
archiveNote(id: string, archived: boolean): Promise<void>;
|
|
82
|
+
trashNote(id: string): Promise<void>;
|
|
83
|
+
listFolders(): Promise<{
|
|
84
|
+
folders: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
name: string;
|
|
87
|
+
parentId: string | null;
|
|
88
|
+
}>;
|
|
89
|
+
withheld: number;
|
|
90
|
+
}>;
|
|
91
|
+
/** Folder exposure for this request. Not cached across calls: the user can
|
|
92
|
+
* flip the switch in the app at any moment and the next tool call must
|
|
93
|
+
* respect it. */
|
|
94
|
+
private exposure;
|
|
95
|
+
createFolder(name: string, parentId?: string | null): Promise<{
|
|
96
|
+
id: string;
|
|
97
|
+
}>;
|
|
98
|
+
/** Decrypt (with per-unlock caching) or pass through a legacy plaintext
|
|
99
|
+
* row. Legacy rows (no encryptedTitle) carry their real content directly
|
|
100
|
+
* in the plaintext title/content columns. */
|
|
101
|
+
private decryptRow;
|
|
102
|
+
private buildSnippet;
|
|
103
|
+
private requireSession;
|
|
104
|
+
/** Chains `fn` onto the mutation queue so overlapping calls run one at a
|
|
105
|
+
* time; the `.catch(() => {})` guard keeps the internal chain alive after
|
|
106
|
+
* a rejection while the promise returned to the caller still carries it. */
|
|
107
|
+
private enqueue;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=notes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"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"}
|
package/dist/notes.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { bytesToBase64, decryptNoteFields, encryptNoteFields } from '@batalabs/virlow-crypto';
|
|
2
|
+
import { computeExposure } from './exposure';
|
|
3
|
+
/** Hard ceiling on how many notes searchNotes() will decrypt+scan in a
|
|
4
|
+
* single call. Keeps a single search bounded even for very large accounts;
|
|
5
|
+
* `truncated` tells the caller when the account holds more than this. */
|
|
6
|
+
export const SEARCH_SCAN_CAP = 500;
|
|
7
|
+
const SEARCH_PAGE_LIMIT = 50;
|
|
8
|
+
/** Window radius (chars) either side of the match start when building a
|
|
9
|
+
* content snippet; total window is up to 2*SNIPPET_RADIUS. */
|
|
10
|
+
const SNIPPET_RADIUS = 80;
|
|
11
|
+
export class NotesService {
|
|
12
|
+
api;
|
|
13
|
+
vault;
|
|
14
|
+
/** Per-unlock cache of decrypted (title, content) keyed by `id:updatedAt`
|
|
15
|
+
* so re-reading/re-scanning the same note twice in one unlock doesn't
|
|
16
|
+
* re-run AES-GCM decryption. Cleared whenever the vault locks. */
|
|
17
|
+
decryptCache = new Map();
|
|
18
|
+
/** Bumped every time the vault locks (see handleLock()). decryptRow()
|
|
19
|
+
* captures this before its await on decryptNoteFields() and re-checks it
|
|
20
|
+
* afterward, so a decrypt that was still in flight when the vault locked
|
|
21
|
+
* never writes decrypted plaintext into the cache after the lock. */
|
|
22
|
+
epoch = 0;
|
|
23
|
+
/** Serializes updateNote() calls so two overlapping partial edits to the
|
|
24
|
+
* same note (e.g. one changing title, one changing content) never race
|
|
25
|
+
* their read-merge-encrypt-write cycle — without this, both could read
|
|
26
|
+
* the same pre-edit row and the second write would silently clobber the
|
|
27
|
+
* first's change. Mirrors MemoryStore's enqueue()/mutationChain pattern. */
|
|
28
|
+
mutationChain = Promise.resolve();
|
|
29
|
+
constructor(api, vault) {
|
|
30
|
+
this.api = api;
|
|
31
|
+
this.vault = vault;
|
|
32
|
+
this.vault.onLock = () => this.handleLock();
|
|
33
|
+
}
|
|
34
|
+
handleLock() {
|
|
35
|
+
this.decryptCache.clear();
|
|
36
|
+
this.epoch++;
|
|
37
|
+
}
|
|
38
|
+
async listNotes(params) {
|
|
39
|
+
const key = this.requireSession();
|
|
40
|
+
const { notes, pagination } = await this.api.listNotes({ ...params, deleted: false });
|
|
41
|
+
// Hidden notes are segregated client-side by the web app (see ApiNote's
|
|
42
|
+
// `hidden` doc comment); the MCP server mirrors that here so they never
|
|
43
|
+
// surface in list/search results. readNote() still allows fetching one
|
|
44
|
+
// by exact id — that's a deliberate, explicit request.
|
|
45
|
+
const exposure = await this.exposure();
|
|
46
|
+
const notHidden = notes.filter((row) => row.hidden !== true);
|
|
47
|
+
// Folders the user has not opened to AI tools are filtered here rather
|
|
48
|
+
// than at the API, so the count of what was withheld can be reported.
|
|
49
|
+
const visible = notHidden.filter((row) => exposure.allows(row.folderId));
|
|
50
|
+
const withheld = {
|
|
51
|
+
inClosedFolders: notHidden.filter((row) => row.folderId !== null && !exposure.allows(row.folderId)).length,
|
|
52
|
+
unfiled: notHidden.filter((row) => row.folderId === null).length,
|
|
53
|
+
};
|
|
54
|
+
const mapped = await Promise.all(visible.map(async (row) => {
|
|
55
|
+
const { title } = await this.decryptRow(key, row);
|
|
56
|
+
return {
|
|
57
|
+
id: row.id,
|
|
58
|
+
title,
|
|
59
|
+
folderId: row.folderId,
|
|
60
|
+
starred: row.starred === true,
|
|
61
|
+
updatedAt: row.updatedAt,
|
|
62
|
+
};
|
|
63
|
+
}));
|
|
64
|
+
return { notes: mapped, pagination, withheld, exposure };
|
|
65
|
+
}
|
|
66
|
+
async readNote(id) {
|
|
67
|
+
const key = this.requireSession();
|
|
68
|
+
const row = await this.api.getNote(id);
|
|
69
|
+
// Reading by exact id is still a read: the switch says the assistant may
|
|
70
|
+
// not read this folder, and knowing the id does not change that.
|
|
71
|
+
const exposure = await this.exposure();
|
|
72
|
+
if (!exposure.allows(row.folderId)) {
|
|
73
|
+
throw new Error(`Note ${id} is in a folder that is not exposed to MCP. Turn on "Expose to MCP" in that folder's settings in the Virlow app to read it here.`);
|
|
74
|
+
}
|
|
75
|
+
const { title, content } = await this.decryptRow(key, row);
|
|
76
|
+
return { id: row.id, title, content, folderId: row.folderId };
|
|
77
|
+
}
|
|
78
|
+
async createNote(title, content, folderId, type) {
|
|
79
|
+
const key = this.requireSession();
|
|
80
|
+
const salt = bytesToBase64(crypto.getRandomValues(new Uint8Array(32)));
|
|
81
|
+
const { encryptedTitle, encryptedContent, iv } = await encryptNoteFields(key, title, content);
|
|
82
|
+
const body = {
|
|
83
|
+
title: '[ENCRYPTED]',
|
|
84
|
+
content: '[ENCRYPTED]',
|
|
85
|
+
encryptedTitle,
|
|
86
|
+
encryptedContent,
|
|
87
|
+
iv,
|
|
88
|
+
salt,
|
|
89
|
+
};
|
|
90
|
+
if (folderId !== undefined)
|
|
91
|
+
body.folderId = folderId;
|
|
92
|
+
if (type !== undefined)
|
|
93
|
+
body.type = type;
|
|
94
|
+
const row = await this.api.createNote(body);
|
|
95
|
+
return { id: row.id };
|
|
96
|
+
}
|
|
97
|
+
updateNote(id, changes) {
|
|
98
|
+
return this.enqueue(async () => {
|
|
99
|
+
const key = this.requireSession();
|
|
100
|
+
const existingRow = await this.api.getNote(id);
|
|
101
|
+
const current = await this.decryptRow(key, existingRow);
|
|
102
|
+
const title = changes.title ?? current.title;
|
|
103
|
+
const content = changes.content ?? current.content;
|
|
104
|
+
const salt = bytesToBase64(crypto.getRandomValues(new Uint8Array(32)));
|
|
105
|
+
const { encryptedTitle, encryptedContent, iv } = await encryptNoteFields(key, title, content);
|
|
106
|
+
await this.api.updateNote(id, {
|
|
107
|
+
title: '[ENCRYPTED]',
|
|
108
|
+
content: '[ENCRYPTED]',
|
|
109
|
+
encryptedTitle,
|
|
110
|
+
encryptedContent,
|
|
111
|
+
iv,
|
|
112
|
+
salt,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async searchNotes(query, limit = 20) {
|
|
117
|
+
const key = this.requireSession();
|
|
118
|
+
const q = query.toLowerCase();
|
|
119
|
+
const results = [];
|
|
120
|
+
let scanned = 0;
|
|
121
|
+
let total = 0;
|
|
122
|
+
let page = 1;
|
|
123
|
+
scan: for (;;) {
|
|
124
|
+
const { notes, pagination } = await this.api.listNotes({ page, limit: SEARCH_PAGE_LIMIT, deleted: false });
|
|
125
|
+
total = pagination.total;
|
|
126
|
+
for (const row of notes) {
|
|
127
|
+
if (row.hidden === true)
|
|
128
|
+
continue;
|
|
129
|
+
if (scanned >= SEARCH_SCAN_CAP)
|
|
130
|
+
break scan;
|
|
131
|
+
scanned++;
|
|
132
|
+
const { title, content } = await this.decryptRow(key, row);
|
|
133
|
+
const contentIdx = content.toLowerCase().indexOf(q);
|
|
134
|
+
const titleMatch = title.toLowerCase().includes(q);
|
|
135
|
+
if (titleMatch || contentIdx !== -1) {
|
|
136
|
+
results.push({ id: row.id, title, snippet: this.buildSnippet(title, content, contentIdx) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (scanned >= SEARCH_SCAN_CAP)
|
|
140
|
+
break;
|
|
141
|
+
if (notes.length === 0 || page >= pagination.pages)
|
|
142
|
+
break;
|
|
143
|
+
page++;
|
|
144
|
+
}
|
|
145
|
+
return { results: results.slice(0, limit), scanned, truncated: total > SEARCH_SCAN_CAP };
|
|
146
|
+
}
|
|
147
|
+
async moveNote(id, folderId) {
|
|
148
|
+
this.requireSession();
|
|
149
|
+
await this.api.updateNote(id, { folderId });
|
|
150
|
+
}
|
|
151
|
+
async setStar(id, starred) {
|
|
152
|
+
this.requireSession();
|
|
153
|
+
await this.api.updateNote(id, { starred });
|
|
154
|
+
}
|
|
155
|
+
async archiveNote(id, archived) {
|
|
156
|
+
this.requireSession();
|
|
157
|
+
await this.api.updateNote(id, { archived });
|
|
158
|
+
}
|
|
159
|
+
async trashNote(id) {
|
|
160
|
+
this.requireSession();
|
|
161
|
+
await this.api.updateNote(id, { deleted: true });
|
|
162
|
+
}
|
|
163
|
+
async listFolders() {
|
|
164
|
+
this.requireSession();
|
|
165
|
+
const { folders } = await this.api.listFolders();
|
|
166
|
+
const exposure = computeExposure(folders);
|
|
167
|
+
const visible = folders.filter((f) => exposure.allows(f.id));
|
|
168
|
+
return {
|
|
169
|
+
// Names are withheld along with contents: a folder the user has not
|
|
170
|
+
// opened should not be inventoried either, only counted.
|
|
171
|
+
folders: visible.map((f) => ({ id: f.id, name: f.name, parentId: f.parentId })),
|
|
172
|
+
withheld: folders.length - visible.length,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Folder exposure for this request. Not cached across calls: the user can
|
|
176
|
+
* flip the switch in the app at any moment and the next tool call must
|
|
177
|
+
* respect it. */
|
|
178
|
+
async exposure() {
|
|
179
|
+
const { folders } = await this.api.listFolders();
|
|
180
|
+
return computeExposure(folders);
|
|
181
|
+
}
|
|
182
|
+
async createFolder(name, parentId) {
|
|
183
|
+
this.requireSession();
|
|
184
|
+
const folder = await this.api.createFolder(name, parentId);
|
|
185
|
+
return { id: folder.id };
|
|
186
|
+
}
|
|
187
|
+
/** Decrypt (with per-unlock caching) or pass through a legacy plaintext
|
|
188
|
+
* row. Legacy rows (no encryptedTitle) carry their real content directly
|
|
189
|
+
* in the plaintext title/content columns. */
|
|
190
|
+
async decryptRow(key, row) {
|
|
191
|
+
if (!row.encryptedTitle) {
|
|
192
|
+
return { title: row.title, content: row.content };
|
|
193
|
+
}
|
|
194
|
+
const cacheKey = `${row.id}:${row.updatedAt}`;
|
|
195
|
+
const cached = this.decryptCache.get(cacheKey);
|
|
196
|
+
if (cached)
|
|
197
|
+
return cached;
|
|
198
|
+
const startEpoch = this.epoch;
|
|
199
|
+
const fields = await decryptNoteFields(key, row.encryptedTitle, row.encryptedContent ?? '', row.iv ?? '');
|
|
200
|
+
// If the vault locked while this decrypt was in flight, the epoch moved
|
|
201
|
+
// on and decryptCache was already cleared — don't write plaintext back
|
|
202
|
+
// into it after the fact.
|
|
203
|
+
if (this.epoch === startEpoch) {
|
|
204
|
+
this.decryptCache.set(cacheKey, fields);
|
|
205
|
+
}
|
|
206
|
+
return fields;
|
|
207
|
+
}
|
|
208
|
+
buildSnippet(title, content, contentMatchIndex) {
|
|
209
|
+
if (contentMatchIndex === -1) {
|
|
210
|
+
return title;
|
|
211
|
+
}
|
|
212
|
+
const windowSize = SNIPPET_RADIUS * 2;
|
|
213
|
+
let start = Math.max(0, contentMatchIndex - SNIPPET_RADIUS);
|
|
214
|
+
const end = Math.min(content.length, start + windowSize);
|
|
215
|
+
if (end - start < windowSize) {
|
|
216
|
+
start = Math.max(0, end - windowSize);
|
|
217
|
+
}
|
|
218
|
+
return content.slice(start, end);
|
|
219
|
+
}
|
|
220
|
+
requireSession() {
|
|
221
|
+
const key = this.vault.session.key;
|
|
222
|
+
this.vault.touch();
|
|
223
|
+
return key;
|
|
224
|
+
}
|
|
225
|
+
/** Chains `fn` onto the mutation queue so overlapping calls run one at a
|
|
226
|
+
* time; the `.catch(() => {})` guard keeps the internal chain alive after
|
|
227
|
+
* a rejection while the promise returned to the caller still carries it. */
|
|
228
|
+
enqueue(fn) {
|
|
229
|
+
const next = this.mutationChain.then(fn, fn);
|
|
230
|
+
this.mutationChain = next.catch(() => { });
|
|
231
|
+
return next;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=notes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notes.js","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AAI5D;;yEAEyE;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B;8DAC8D;AAC9D,MAAM,cAAc,GAAG,EAAE,CAAC;AAO1B,MAAM,OAAO,YAAY;IAoBJ;IACA;IApBnB;;sEAEkE;IACjD,YAAY,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEnE;;;yEAGqE;IAC7D,KAAK,GAAG,CAAC,CAAC;IAElB;;;;gFAI4E;IACpE,aAAa,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAE5D,YACmB,GAAc,EACd,KAAY;QADZ,QAAG,GAAH,GAAG,CAAW;QACd,UAAK,GAAL,KAAK,CAAO;QAE7B,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;IAC9C,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAMf;QASC,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAClC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACtF,wEAAwE;QACxE,wEAAwE;QACxE,uEAAuE;QACvE,uDAAuD;QACvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;QAC7D,uEAAuE;QACvE,sEAAsE;QACtE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG;YACf,eAAe,EAAE,SAAS,CAAC,MAAM,CAC/B,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CACjE,CAAC,MAAM;YACR,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,MAAM;SACjE,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACxB,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAClD,OAAO;gBACL,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,KAAK;gBACL,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI;gBAC7B,SAAS,EAAE,GAAG,CAAC,SAAS;aACzB,CAAC;QACJ,CAAC,CAAC,CACH,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAClC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvC,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,QAAQ,EAAE,kIAAkI,CAC7I,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3D,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,UAAU,CACd,KAAa,EACb,OAAe,EACf,QAAwB,EACxB,IAAa;QAEb,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,EAAE,EAAE,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9F,MAAM,IAAI,GAAc;YACtB,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,aAAa;YACtB,cAAc;YACd,gBAAgB;YAChB,EAAE;YACF,IAAI;SACL,CAAC;QACF,IAAI,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACrD,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACzC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;IACxB,CAAC;IAED,UAAU,CAAC,EAAU,EAAE,OAA6C;QAClE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YAClC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;YAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;YACnD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACvE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,EAAE,EAAE,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAC9F,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE;gBAC5B,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,aAAa;gBACtB,cAAc;gBACd,gBAAgB;gBAChB,EAAE;gBACF,IAAI;aACL,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW,CACf,KAAa,EACb,KAAK,GAAG,EAAE;QAEV,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAA0D,EAAE,CAAC;QAC1E,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,IAAI,GAAG,CAAC,CAAC;QAEb,IAAI,EAAE,SAAS,CAAC;YACd,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3G,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;YACzB,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;gBACxB,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI;oBAAE,SAAS;gBAClC,IAAI,OAAO,IAAI,eAAe;oBAAE,MAAM,IAAI,CAAC;gBAC3C,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACpD,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACnD,IAAI,UAAU,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;oBACpC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;gBAC9F,CAAC;YACH,CAAC;YACD,IAAI,OAAO,IAAI,eAAe;gBAAE,MAAM;YACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,IAAI,UAAU,CAAC,KAAK;gBAAE,MAAM;YAC1D,IAAI,EAAE,CAAC;QACT,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,eAAe,EAAE,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU,EAAE,QAAuB;QAChD,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,OAAgB;QACxC,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,QAAiB;QAC7C,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,EAAU;QACxB,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,WAAW;QAIf,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,OAAO;YACL,oEAAoE;YACpE,yDAAyD;YACzD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/E,QAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM;SAC1C,CAAC;IACJ,CAAC;IAED;;qBAEiB;IACT,KAAK,CAAC,QAAQ;QACpB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAY,EAAE,QAAwB;QACvD,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;IAC3B,CAAC;IAED;;iDAE6C;IACrC,KAAK,CAAC,UAAU,CAAC,GAAc,EAAE,GAAY;QACnD,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QACpD,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,gBAAgB,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1G,wEAAwE;QACxE,uEAAuE;QACvE,0BAA0B;QAC1B,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,YAAY,CAAC,KAAa,EAAE,OAAe,EAAE,iBAAyB;QAC5E,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,UAAU,GAAG,cAAc,GAAG,CAAC,CAAC;QACtC,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,GAAG,UAAU,CAAC,CAAC;QACzD,IAAI,GAAG,GAAG,KAAK,GAAG,UAAU,EAAE,CAAC;YAC7B,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC;IAEO,cAAc;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;gFAE4E;IACpE,OAAO,CAAI,EAAoB;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|