@stll/anonymize-mcp 2.7.8 → 2.8.1
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 +198 -13
- package/NOTICE +20 -0
- package/dist/local.mjs +1 -4
- package/dist/local.mjs.map +1 -1
- package/package.json +7 -5
package/LICENSE
CHANGED
|
@@ -1,17 +1,202 @@
|
|
|
1
|
-
Apache License
|
|
2
|
-
Version 2.0, January 2004
|
|
3
|
-
https://www.apache.org/licenses/LICENSE-2.0
|
|
4
1
|
|
|
5
|
-
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
you may not use this file except in compliance with the License.
|
|
9
|
-
You may obtain a copy of the License at
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
1. Definitions.
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
anonymize
|
|
2
|
+
Copyright 2026 stella labs, s.r.o.
|
|
3
|
+
|
|
4
|
+
This product includes software developed at stella labs, s.r.o.
|
|
5
|
+
(https://stll.app/).
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
not use this product except in compliance with the License. You may
|
|
9
|
+
obtain a copy of the License in the LICENSE file distributed with this product
|
|
10
|
+
or at https://www.apache.org/licenses/LICENSE-2.0.
|
|
11
|
+
|
|
12
|
+
--------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
Third-party notices
|
|
15
|
+
|
|
16
|
+
This product bundles and builds on third-party components distributed
|
|
17
|
+
under their own licenses (including MIT). Those components and their
|
|
18
|
+
attributions are listed in the ATTRIBUTION.md files under packages/ and
|
|
19
|
+
crates/, and the browser build's runtime attributions are listed in
|
|
20
|
+
packages/anonymize/wasm/README.md.
|
package/dist/local.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { CAPABILITY_MANIFEST } from "@stll/anonymize";
|
|
3
|
-
import { preloadNativeBinding } from "@stll/anonymize/native-runtime";
|
|
4
3
|
import { AnonymizeSurfaceError, classifyToEnvelope } from "@stll/anonymize/agent-surface";
|
|
5
4
|
import { FEEDBACK_KINDS, MAX_FEEDBACK_BODY_CHARS, MAX_FEEDBACK_TITLE_CHARS, buildFeedbackSubmission } from "@stll/anonymize/feedback";
|
|
6
5
|
import { DOCX_ARCHIVE_MAX_BYTES, DOCX_COVERAGE_MODES, anonymizeDocx, extractDocxText, restoreDocxText } from "@stll/anonymize-docx";
|
|
@@ -11,7 +10,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
11
10
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
11
|
import * as z from "zod/v4";
|
|
13
12
|
//#region package.json
|
|
14
|
-
var version = "2.
|
|
13
|
+
var version = "2.8.1";
|
|
15
14
|
//#endregion
|
|
16
15
|
//#region src/durable-sessions.ts
|
|
17
16
|
const SESSION_ARCHIVE_KEY_BYTES = 32;
|
|
@@ -688,7 +687,6 @@ var LocalAnonymizeService = class {
|
|
|
688
687
|
if (this.#state !== "open") throw new Error("MCP anonymize service is closing or closed");
|
|
689
688
|
this.#activeOperations += 1;
|
|
690
689
|
try {
|
|
691
|
-
await preloadNativeBinding();
|
|
692
690
|
return await operation();
|
|
693
691
|
} finally {
|
|
694
692
|
this.#activeOperations -= 1;
|
|
@@ -1195,7 +1193,6 @@ const guard = async (produce) => {
|
|
|
1195
1193
|
}
|
|
1196
1194
|
};
|
|
1197
1195
|
const capabilitiesResult = async (service) => {
|
|
1198
|
-
await preloadNativeBinding();
|
|
1199
1196
|
const value = {
|
|
1200
1197
|
capabilityManifest: CAPABILITY_MANIFEST,
|
|
1201
1198
|
runtimeVersion: nativeNodeSurface.native_package_version(),
|
package/dist/local.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.mjs","names":["SESSION_ID","READ_CHUNK_BYTES","sameFile","fsConstants","readHandleBounded","#directory","#faultInjector","#key","#lockHandle","#validateInventory","#syncDirectory","#assertOpen","#closePromise","#state","#mutationTail","#withMutation","#load","#path","#assertDirectory","#save","#inject","#delete","#roots","fsConstants","#scope","#sessionInitializations","#sessions","#durableSessions","#faults","#pdfProvider","#closePromise","#state","#activeOperations","#operationsDrained","#runOperation","#session","#rollbackSession","#persistSession","#readSession","#anonymizeText","#serializePdfOperation","#anonymizePdf","#pdfOperationTail","#commitSession","#restoreText","#anonymizeTextWithExternalDetections","#anonymizeDocx","#restoreDocx","#inspectDocx","pkg.version"],"sources":["../package.json","../src/durable-sessions.ts","../src/local.ts"],"sourcesContent":["","import { createHash, randomUUID } from \"node:crypto\";\nimport {\n constants as fsConstants,\n lstat,\n open,\n readdir,\n realpath,\n rename,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nexport const SESSION_ARCHIVE_KEY_BYTES = 32;\nexport const SESSION_ARCHIVE_MAX_BYTES = 16 * 1024 * 1024 + 57;\nexport const SESSION_ARCHIVE_MAX_COUNT = 256;\nexport const SESSION_ARCHIVE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;\n\nconst ARCHIVE_NAME = /^[a-f0-9]{64}\\.stlasess$/u;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst STAGING_NAME =\n /^[a-f0-9]{64}\\.stlasess\\.tmp\\.[0-9a-f]{8}-[0-9a-f-]{27}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\nconst LOCK_FILE_NAME = \".stella-session.lock\";\n\nexport const DURABLE_SESSION_FAULT_POINTS = {\n beforeDirectoryFsync: \"before-directory-fsync\",\n beforeRename: \"before-rename\",\n beforeStagingFsync: \"before-staging-fsync\",\n beforeStagingWrite: \"before-staging-write\",\n} as const;\n\nexport type DurableSessionFaultPoint =\n (typeof DURABLE_SESSION_FAULT_POINTS)[keyof typeof DURABLE_SESSION_FAULT_POINTS];\n\ntype FileIdentity = {\n dev: number;\n ino: number;\n};\n\ntype DirectoryIdentity = FileIdentity & {\n path: string;\n};\n\ntype SessionInventory = {\n archiveCount: number;\n totalBytes: number;\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst assertOwner = (uid: number, label: string): void => {\n if (typeof process.getuid === \"function\" && uid !== process.getuid()) {\n throw new Error(`${label} must be owned by the current user`);\n }\n};\n\nconst assertPrivateMode = (mode: number, label: string): void => {\n if ((mode & 0o077) !== 0) {\n throw new Error(`${label} must not grant group or other permissions`);\n }\n};\n\nconst assertPosixDurabilitySupport = (): void => {\n if (\n (process.platform !== \"darwin\" && process.platform !== \"linux\") ||\n typeof process.getuid !== \"function\" ||\n typeof fsConstants.O_NOFOLLOW !== \"number\" ||\n fsConstants.O_NOFOLLOW === 0 ||\n typeof fsConstants.O_DIRECTORY !== \"number\" ||\n fsConstants.O_DIRECTORY === 0\n ) {\n throw new Error(\n \"Encrypted durable MCP sessions require supported POSIX owner, nofollow, directory-fsync, and advisory-lock semantics on macOS or Linux\",\n );\n }\n};\n\nconst acquireDirectoryLock = async (\n directoryPath: string,\n): Promise<FileHandle> => {\n const path = join(directoryPath, LOCK_FILE_NAME);\n const handle = await open(\n path,\n fsConstants.O_RDWR |\n fsConstants.O_CREAT |\n fsConstants.O_NOFOLLOW |\n fsConstants.O_NONBLOCK,\n 0o600,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"MCP session lock must be a regular file\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n // Loaded lazily because Bun is used as a repository test/build tool but\n // cannot safely load this Node native addon. The shipped MCP runtime is\n // Node; Node integration tests exercise this path.\n const { tryLock } = await import(\"fs-native-extensions\");\n if (!tryLock(handle.fd)) {\n throw new Error(\n \"MCP session directory is already locked by another server\",\n );\n }\n return handle;\n } catch (error) {\n await handle.close().catch(() => undefined);\n throw error;\n }\n};\n\nconst canonicalAbsolutePath = async (\n path: string,\n label: string,\n): Promise<string> => {\n if (!isAbsolute(path)) {\n throw new Error(`${label} must be an absolute path`);\n }\n const normalized = resolve(path);\n const canonical = await realpath(normalized);\n if (canonical !== normalized) {\n throw new Error(`${label} must not contain symbolic links`);\n }\n return canonical;\n};\n\nconst readHandleBounded = async (\n handle: Pick<Awaited<ReturnType<typeof open>>, \"read\">,\n maximumBytes: number,\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const remaining = maximumBytes - total;\n const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining + 1));\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\nconst readKey = async (path: string): Promise<Uint8Array> => {\n const canonical = await canonicalAbsolutePath(path, \"MCP session key file\");\n const linkedMetadata = await lstat(canonical);\n if (!linkedMetadata.isFile() || linkedMetadata.isSymbolicLink()) {\n throw new Error(\"MCP session key file must be a regular file\");\n }\n assertOwner(linkedMetadata.uid, \"MCP session key file\");\n assertPrivateMode(linkedMetadata.mode, \"MCP session key file\");\n if (linkedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES) {\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const handle = await open(\n canonical,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n let keyBuffer: Buffer | undefined;\n try {\n const openedMetadata = await handle.stat();\n if (\n !openedMetadata.isFile() ||\n !sameFile(linkedMetadata, openedMetadata) ||\n openedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES\n ) {\n throw new Error(\"MCP session key file changed while it was validated\");\n }\n keyBuffer = Buffer.alloc(SESSION_ARCHIVE_KEY_BYTES + 1);\n let keyLength = 0;\n for (;;) {\n const { bytesRead } = await handle.read(\n keyBuffer,\n keyLength,\n keyBuffer.byteLength - keyLength,\n null,\n );\n if (bytesRead === 0 || keyLength + bytesRead === keyBuffer.byteLength) {\n keyLength += bytesRead;\n break;\n }\n keyLength += bytesRead;\n }\n if (keyLength !== SESSION_ARCHIVE_KEY_BYTES) {\n keyBuffer.fill(0);\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const currentMetadata = await stat(canonical);\n if (!sameFile(openedMetadata, currentMetadata)) {\n keyBuffer.fill(0);\n throw new Error(\"MCP session key file changed while it was read\");\n }\n const key = new Uint8Array(SESSION_ARCHIVE_KEY_BYTES);\n key.set(keyBuffer.subarray(0, SESSION_ARCHIVE_KEY_BYTES));\n keyBuffer.fill(0);\n return key;\n } finally {\n keyBuffer?.fill(0);\n await handle.close();\n }\n};\n\nconst archiveName = (sessionId: string): string =>\n `${createHash(\"sha256\").update(sessionId, \"utf8\").digest(\"hex\")}.stlasess`;\n\nconst assertSessionId = (sessionId: string): void => {\n if (!SESSION_ID.test(sessionId)) {\n throw new Error(\"MCP session ID is invalid\");\n }\n};\n\nexport type DurableSessionStoreOptions = {\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void;\n keyFile: string;\n sessionDirectory: string;\n};\n\nexport type StoredSessionArchive = {\n bytes: Uint8Array;\n};\n\nexport type EncryptableSession = {\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n};\n\nexport type EncryptedSessionRestorer<Session> = {\n restoreEncryptedRedactionSession(options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }): Session;\n};\n\nexport type RestoreStoredSessionOptions<Session> = {\n archive: Uint8Array;\n expectedSessionId: string;\n observedAtEpochSeconds: number;\n restorer: EncryptedSessionRestorer<Session>;\n};\n\nexport class DurableSessionStore {\n #closePromise: Promise<void> | undefined;\n readonly #directory: DirectoryIdentity;\n readonly #faultInjector:\n | ((point: DurableSessionFaultPoint) => Promise<void> | void)\n | undefined;\n readonly #key: Uint8Array;\n readonly #lockHandle: FileHandle;\n #mutationTail: Promise<void> = Promise.resolve();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n\n private constructor(\n directory: DirectoryIdentity,\n key: Uint8Array,\n lockHandle: FileHandle,\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void,\n ) {\n this.#directory = directory;\n this.#key = key;\n this.#lockHandle = lockHandle;\n this.#faultInjector = faultInjector;\n }\n\n static async create({\n keyFile,\n sessionDirectory,\n faultInjector,\n }: DurableSessionStoreOptions): Promise<DurableSessionStore> {\n assertPosixDurabilitySupport();\n const directoryPath = await canonicalAbsolutePath(\n sessionDirectory,\n \"MCP session directory\",\n );\n const metadata = await lstat(directoryPath);\n if (!metadata.isDirectory() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory must be a directory\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n const key = await readKey(keyFile);\n let lockHandle: FileHandle | undefined;\n try {\n lockHandle = await acquireDirectoryLock(directoryPath);\n const store = new DurableSessionStore(\n { dev: metadata.dev, ino: metadata.ino, path: directoryPath },\n key,\n lockHandle,\n faultInjector,\n );\n await store.#validateInventory({ removeStagingFiles: true });\n await store.#syncDirectory();\n return store;\n } catch (error) {\n key.fill(0);\n await lockHandle?.close().catch(() => undefined);\n throw error;\n }\n }\n\n seal(\n session: EncryptableSession,\n observedAtEpochSeconds: number,\n ): Uint8Array {\n this.#assertOpen();\n return session.toEncryptedArchiveAt(this.#key, observedAtEpochSeconds);\n }\n\n restore<Session>({\n archive,\n expectedSessionId,\n observedAtEpochSeconds,\n restorer,\n }: RestoreStoredSessionOptions<Session>): Session {\n this.#assertOpen();\n return restorer.restoreEncryptedRedactionSession({\n archive,\n expectedSessionId,\n key: this.#key,\n observedAtEpochSeconds,\n });\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n await this.#mutationTail;\n this.#key.fill(0);\n await this.#lockHandle.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n this.#assertOpen();\n return this.#withMutation(() => this.#load(sessionId));\n }\n\n async #load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n assertSessionId(sessionId);\n await this.#validateInventory({ removeStagingFiles: false });\n const path = this.#path(sessionId);\n let handle: Awaited<ReturnType<typeof open>>;\n try {\n handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw new Error(\"Encrypted session archive could not be opened\", {\n cause: error,\n });\n }\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"Encrypted session archive must be a regular file\");\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const bytes = await readHandleBounded(handle, SESSION_ARCHIVE_MAX_BYTES);\n await this.#assertDirectory();\n const currentMetadata = await lstat(path);\n if (!currentMetadata.isFile() || !sameFile(metadata, currentMetadata)) {\n throw new Error(\"Encrypted session archive changed while it was read\");\n }\n return { bytes };\n } finally {\n await handle.close();\n }\n }\n\n async save(sessionId: string, archive: Uint8Array): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#save(sessionId, archive));\n }\n\n async #save(sessionId: string, archive: Uint8Array): Promise<void> {\n assertSessionId(sessionId);\n if (archive.byteLength > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const inventory = await this.#validateInventory({\n removeStagingFiles: false,\n });\n await this.#assertDirectory();\n const destination = this.#path(sessionId);\n const temporary = `${destination}.tmp.${randomUUID()}`;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o600,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Encrypted session staging path is not a regular file\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingWrite);\n await handle.writeFile(archive);\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingFsync);\n await handle.sync();\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(openedMetadata, stagedMetadata)\n ) {\n throw new Error(\n \"Encrypted session staging file changed before publication\",\n );\n }\n await this.#assertDirectory();\n const existing = await lstat(destination).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (\n existing !== undefined &&\n (!existing.isFile() || existing.isSymbolicLink())\n ) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n const nextCount =\n inventory.archiveCount + (existing === undefined ? 1 : 0);\n const nextTotalBytes =\n inventory.totalBytes - (existing?.size ?? 0) + archive.byteLength;\n if (nextCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (nextTotalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeRename);\n await rename(temporary, destination);\n await this.#assertDirectory();\n const published = await lstat(destination);\n if (!published.isFile() || !sameFile(openedMetadata, published)) {\n throw new Error(\"Encrypted session archive publication was not atomic\");\n }\n await this.#syncDirectory();\n } finally {\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n }\n }\n\n async delete(sessionId: string): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#delete(sessionId));\n }\n\n async #delete(sessionId: string): Promise<void> {\n assertSessionId(sessionId);\n await this.#assertDirectory();\n const path = this.#path(sessionId);\n const metadata = await lstat(path).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (metadata === undefined) {\n return;\n }\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n await unlink(path);\n await this.#syncDirectory();\n await this.#assertDirectory();\n }\n\n #path(sessionId: string): string {\n return join(this.#directory.path, archiveName(sessionId));\n }\n\n #assertOpen(): void {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP durable session store is closing or closed\");\n }\n }\n\n async #withMutation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#mutationTail;\n let release = (): void => undefined;\n const current = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n this.#mutationTail = previous.then(() => current);\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #assertDirectory(): Promise<void> {\n const canonical = await realpath(this.#directory.path);\n const metadata = await lstat(this.#directory.path);\n if (\n canonical !== this.#directory.path ||\n !metadata.isDirectory() ||\n metadata.isSymbolicLink() ||\n !sameFile(this.#directory, metadata)\n ) {\n throw new Error(\"MCP session directory changed while it was being used\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n }\n\n async #syncDirectory(): Promise<void> {\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeDirectoryFsync);\n const handle = await open(\n this.#directory.path,\n fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isDirectory() || !sameFile(this.#directory, metadata)) {\n throw new Error(\"MCP session directory changed before synchronization\");\n }\n await handle.sync();\n } finally {\n await handle.close();\n }\n }\n\n async #inject(point: DurableSessionFaultPoint): Promise<void> {\n await this.#faultInjector?.(point);\n }\n\n async #validateInventory({\n removeStagingFiles,\n }: {\n removeStagingFiles: boolean;\n }): Promise<SessionInventory> {\n await this.#assertDirectory();\n const entries = await readdir(this.#directory.path, {\n withFileTypes: true,\n });\n let archiveCount = 0;\n let totalBytes = 0;\n for (const entry of entries) {\n const path = join(this.#directory.path, entry.name);\n if (entry.name === LOCK_FILE_NAME) {\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory contains an unsafe lock path\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n continue;\n }\n if (STAGING_NAME.test(entry.name)) {\n if (!removeStagingFiles) {\n throw new Error(\"MCP session directory contains a partial archive\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe staging path\",\n );\n }\n await unlink(path);\n await this.#syncDirectory();\n continue;\n }\n if (\n !ARCHIVE_NAME.test(entry.name) ||\n !entry.isFile() ||\n entry.isSymbolicLink()\n ) {\n throw new Error(\"MCP session directory contains an unsupported entry\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe archive path\",\n );\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n archiveCount += 1;\n totalBytes += metadata.size;\n if (archiveCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (totalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n }\n await this.#assertDirectory();\n return { archiveCount, totalBytes };\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n CAPABILITY_MANIFEST,\n type NativeCallerDetection,\n type NativeTextReplacement,\n type PreparedNativePipeline,\n} from \"@stll/anonymize\";\nimport { preloadNativeBinding } from \"@stll/anonymize/native-runtime\";\nimport {\n AnonymizeSurfaceError,\n classifyToEnvelope,\n type AnonymizeErrorCode,\n type AnonymizeErrorEnvelope,\n} from \"@stll/anonymize/agent-surface\";\nimport {\n FEEDBACK_KINDS,\n MAX_FEEDBACK_BODY_CHARS,\n MAX_FEEDBACK_TITLE_CHARS,\n buildFeedbackSubmission,\n} from \"@stll/anonymize/feedback\";\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n extractDocxText,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\nimport {\n PDF_DOCUMENT_MAX_BYTES,\n anonymizePdfRaster,\n renderPdfWithPopplerTesseract,\n} from \"@stll/anonymize-pdf\";\nimport * as nativeNode from \"@stll/anonymize/native-node\";\nimport {\n constants as fsConstants,\n link,\n lstat,\n open,\n realpath,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n dirname,\n extname,\n isAbsolute,\n relative,\n resolve,\n sep,\n} from \"node:path\";\nimport * as z from \"zod/v4\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nimport { DurableSessionStore } from \"./durable-sessions\";\n\nconst TEXT_MAX_BYTES = 64 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_MAX_BYTES = 16 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_VERSION = 1 as const;\nconst PATH_MAX_CHARACTERS = 32_768;\nconst SESSION_MAX_COUNT = 256;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\n\n/** Build an agent-surface error carrying a stable code, message, and hint. */\nconst surfaceError = (\n code: AnonymizeErrorCode,\n message: string,\n hint: string,\n retryable = false,\n): AnonymizeSurfaceError =>\n new AnonymizeSurfaceError(code, message, { hint, retryable });\n\nconst nodeErrorCode = (error: unknown): string | undefined =>\n typeof error === \"object\" && error !== null && \"code\" in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n\n/**\n * The local PDF provider collapses a missing/failed pdftoppm or tesseract into a\n * `PdfLocalProviderError` with code `executable-failed`; treat it as a missing\n * dependency so agents get an actionable install hint instead of a generic\n * internal error. Duck-typed to avoid importing the provider's internals.\n */\nconst isPdfToolchainUnavailable = (error: unknown): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n (error as { name?: unknown }).name === \"PdfLocalProviderError\" &&\n (error as { code?: unknown }).code === \"executable-failed\";\n\n/**\n * The docx package refuses under requireFull coverage with a\n * `DocxAnonymizationError` (code `incomplete-coverage`). Map it to a\n * validation_error so the agent gets the actionable allowPartialCoverage hint\n * instead of a generic internal_error; pass anything else through unchanged.\n * Duck-typed to avoid importing the docx error class.\n */\nconst mapDocxCoverageError = (error: unknown): unknown => {\n if (\n typeof error === \"object\" &&\n error !== null &&\n (error as { name?: unknown }).name === \"DocxAnonymizationError\" &&\n (error as { code?: unknown }).code === \"incomplete-coverage\"\n ) {\n // Preserve the docx package's specific (content-free) coverage message; add\n // the stable code and the actionable hint.\n return new AnonymizeSurfaceError(\n \"validation_error\",\n (error as Error).message,\n {\n hint: \"Re-run with allowPartialCoverage: true to accept partial coverage.\",\n cause: error,\n },\n );\n }\n return error;\n};\n\nconst observedAtEpochSeconds = (): number => {\n const seconds = Math.floor(Date.now() / 1000);\n if (seconds < 0 || seconds > 0xff_ff_ff_ff) {\n throw new Error(\"The current time is outside the supported session range\");\n }\n return seconds;\n};\n\nexport const MCP_SESSION_MODES = {\n durableEncrypted: \"durable-encrypted\",\n memory: \"memory\",\n} as const;\n\nexport type McpSessionMode =\n (typeof MCP_SESSION_MODES)[keyof typeof MCP_SESSION_MODES];\n\nexport type AuditSafeResult = {\n operation: \"anonymize\" | \"inspect\" | \"restore\";\n format: \"docx\" | \"pdf\" | \"text\";\n outputCreated: boolean;\n sessionId?: string;\n entityCount?: number;\n blockCount?: number;\n rewrittenBlockCount?: number;\n restoredPlaceholderCount?: number;\n coverageStatus?: \"full\" | \"partial\";\n externalDetectionBatchStatus?: \"accepted\";\n externalDetectionCount?: number;\n retainedExternalDetectionCount?: number;\n pageCount?: number;\n mappedRegionCount?: number;\n structurePixelRewriteVerified?: true;\n piiCleanGuaranteed?: false;\n};\n\nconst EXTERNAL_DETECTION_FAILURES = {\n batchRejected: {\n code: \"EXTERNAL_DETECTION_BATCH_REJECTED\",\n message: \"The external detection batch was rejected.\",\n },\n documentRejected: {\n code: \"EXTERNAL_DETECTION_DOCUMENT_REJECTED\",\n message: \"The external detection document was rejected.\",\n },\n inputRejected: {\n code: \"EXTERNAL_DETECTION_INPUT_REJECTED\",\n message: \"The external detection request paths were rejected.\",\n },\n operationFailed: {\n code: \"EXTERNAL_DETECTION_OPERATION_FAILED\",\n message: \"The external detection operation failed safely.\",\n },\n sessionRejected: {\n code: \"EXTERNAL_DETECTION_SESSION_REJECTED\",\n message: \"The external detection session was rejected.\",\n },\n} as const;\n\ntype ExternalDetectionFailure =\n (typeof EXTERNAL_DETECTION_FAILURES)[keyof typeof EXTERNAL_DETECTION_FAILURES];\n\nclass ExternalDetectionAuditError extends Error {\n readonly code: ExternalDetectionFailure[\"code\"];\n\n constructor(failure: ExternalDetectionFailure) {\n super(failure.message);\n this.name = \"ExternalDetectionAuditError\";\n this.code = failure.code;\n }\n}\n\nconst externalDetectionFailure = (\n error: unknown,\n failure: ExternalDetectionFailure,\n): ExternalDetectionAuditError =>\n error instanceof ExternalDetectionAuditError\n ? error\n : new ExternalDetectionAuditError(failure);\n\nconst externalDetectionStep = async <Result>(\n failure: ExternalDetectionFailure,\n operation: () => Result | Promise<Result>,\n): Promise<Result> => {\n try {\n return await operation();\n } catch (error) {\n throw externalDetectionFailure(error, failure);\n }\n};\n\nexport type LocalAnonymizeServiceFaults = {\n beforeOutputPublish?: () => void | Promise<void>;\n};\n\nconst inside = (root: string, target: string): boolean => {\n const path = relative(root, target);\n return (\n path === \"\" ||\n (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path))\n );\n};\n\ntype ReadInputOptions = {\n path: string;\n extension: \".docx\" | \".json\" | \".pdf\" | \".txt\";\n maximumBytes: number;\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\";\n};\n\ntype ScopedInput = {\n bytes: Uint8Array;\n path: string;\n};\n\ntype ReadableFileHandle = Pick<Awaited<ReturnType<typeof open>>, \"read\">;\n\nconst readHandleBounded = async (\n handle: ReadableFileHandle,\n maximumBytes: number,\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\",\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const chunk = Buffer.allocUnsafe(\n Math.min(READ_CHUNK_BYTES, maximumBytes - total + 1),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw surfaceError(\n \"validation_error\",\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n \"Split or shrink the input below the size limit and retry.\",\n );\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\ntype DirectoryIdentity = {\n dev: number;\n ino: number;\n path: string;\n};\n\ntype FileIdentity = Pick<DirectoryIdentity, \"dev\" | \"ino\">;\n\nclass ScopedOutput {\n readonly parent: DirectoryIdentity;\n readonly path: string;\n\n constructor(path: string, parent: DirectoryIdentity) {\n this.path = path;\n this.parent = parent;\n }\n\n async write(bytes: Uint8Array | string): Promise<void> {\n await safeWrite(this, bytes);\n }\n}\n\nexport class PathScope {\n readonly #roots: readonly string[];\n\n private constructor(roots: readonly string[]) {\n this.#roots = roots;\n }\n\n static async create(roots: readonly string[]): Promise<PathScope> {\n if (roots.length === 0) {\n throw new Error(\"At least one --root directory is required\");\n }\n const canonical = await Promise.all(\n roots.map(async (root) => {\n if (!isAbsolute(root)) {\n throw new Error(\"MCP roots must be absolute paths\");\n }\n const path = await realpath(root);\n const metadata = await stat(path);\n if (!metadata.isDirectory()) {\n throw new Error(\"Every MCP root must be a directory\");\n }\n return path;\n }),\n );\n return new PathScope([...new Set(canonical)]);\n }\n\n async readInput({\n path,\n extension,\n maximumBytes,\n label,\n }: ReadInputOptions): Promise<ScopedInput> {\n if (!isAbsolute(path)) {\n throw surfaceError(\n \"validation_error\",\n `Input must be an absolute ${extension} path`,\n \"Pass an absolute path to an existing file inside a configured --root.\",\n );\n }\n if (extname(path).toLowerCase() !== extension) {\n throw surfaceError(\n \"unsupported_format\",\n `Input must be an absolute ${extension} path`,\n `Provide a file with the ${extension} extension.`,\n );\n }\n const initiallyCanonical = await this.canonicalInput(path);\n if (!this.#roots.some((root) => inside(root, initiallyCanonical))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Input is outside the configured roots\",\n \"Move the input under a configured --root, or add its directory with --root.\",\n );\n }\n const initiallyRequestedMetadata = await lstat(path);\n if (!initiallyRequestedMetadata.isFile()) {\n throw surfaceError(\n \"validation_error\",\n \"Input must be a regular file\",\n \"Point at a regular file, not a directory, symlink, or special file.\",\n );\n }\n const handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw surfaceError(\n \"validation_error\",\n \"Input must be a regular file\",\n \"Point at a regular file, not a directory, symlink, or special file.\",\n );\n }\n const canonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, canonical))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Input is outside the configured roots\",\n \"Move the input under a configured --root, or add its directory with --root.\",\n );\n }\n const currentMetadata = await stat(canonical);\n if (\n currentMetadata.dev !== openedMetadata.dev ||\n currentMetadata.ino !== openedMetadata.ino\n ) {\n throw new Error(\"Input changed while it was being validated\");\n }\n if (openedMetadata.size > maximumBytes) {\n throw surfaceError(\n \"validation_error\",\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n \"Split or shrink the input below the size limit and retry.\",\n );\n }\n const bytes = await readHandleBounded(handle, maximumBytes, label);\n return { bytes, path: canonical };\n } finally {\n await handle.close();\n }\n }\n\n /**\n * Canonicalize an input path, mapping a missing path to a `not_found` surface\n * error instead of a raw fs `ENOENT` so agents get a stable code.\n */\n private async canonicalInput(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch (error) {\n if (nodeErrorCode(error) === \"ENOENT\") {\n throw surfaceError(\n \"not_found\",\n \"Input path does not exist\",\n \"Create the file first, or pass an existing path inside a configured --root.\",\n );\n }\n throw error;\n }\n }\n\n async output(\n path: string,\n extension: \".docx\" | \".pdf\" | \".txt\",\n ): Promise<ScopedOutput> {\n if (!isAbsolute(path)) {\n throw surfaceError(\n \"validation_error\",\n `Output must be an absolute ${extension} path`,\n \"Pass an absolute output path inside a configured --root.\",\n );\n }\n if (extname(path).toLowerCase() !== extension) {\n throw surfaceError(\n \"unsupported_format\",\n `Output must be an absolute ${extension} path`,\n `Name the output with the ${extension} extension.`,\n );\n }\n const normalized = resolve(path);\n let parent: string;\n try {\n parent = await realpath(dirname(normalized));\n } catch (error) {\n if (nodeErrorCode(error) === \"ENOENT\") {\n throw surfaceError(\n \"not_found\",\n \"Output directory does not exist\",\n \"Create the output directory first, inside a configured --root.\",\n );\n }\n throw error;\n }\n if (!this.#roots.some((root) => inside(root, parent))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Output is outside the configured roots\",\n \"Choose an output directory under a configured --root.\",\n );\n }\n const parentMetadata = await stat(parent);\n try {\n await lstat(normalized);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return new ScopedOutput(normalized, {\n dev: parentMetadata.dev,\n ino: parentMetadata.ino,\n path: parent,\n });\n }\n throw new Error(\"Output availability could not be verified\", {\n cause: error,\n });\n }\n throw surfaceError(\n \"output_exists\",\n \"Output already exists; overwriting is not supported\",\n \"Pick a new output path; anonymize never overwrites an existing file.\",\n );\n }\n}\n\ntype SessionEntry = {\n language: string | undefined;\n session: RedactionSession;\n restoreSession: (plaintextJson: string) => RedactionSession;\n status: \"busy\" | \"initializing\" | \"ready\";\n};\n\ntype SessionLease = {\n entry: SessionEntry;\n sessionId: string;\n rollback:\n | { type: \"delete\" }\n | { type: \"release\" }\n | { type: \"restore\"; checkpoint: string };\n};\n\ntype RedactionSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n redact_text(text: string): {\n redaction: { redactedText: string; entityCount: number };\n };\n restoreText(text: string): string;\n toPlaintextJson(): string;\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n };\n\ntype NativeNodeSurface = {\n convert_external_detection_batch: (\n document: Uint8Array,\n batch: string,\n ) => NativeCallerDetection[];\n getDefaultNativePipeline: (options: { language?: string }) => {\n createRedactionSession: (sessionId: string) => RedactionSession;\n restoreRedactionSession: (plaintextJson: string) => RedactionSession;\n restoreEncryptedRedactionSession: (options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }) => RedactionSession;\n } & Pick<\n PreparedNativePipeline,\n \"redactText\" | \"redactTextWithCallerDetections\"\n >;\n native_package_version: () => string;\n};\n\nconst nativeNodeSurface = nativeNode as unknown as NativeNodeSurface; // SAFETY: These native-node runtime exports are public, but their generated declarations are minified during workspace builds.\n\nconst assertOutputParent = async ({\n parent,\n path,\n}: ScopedOutput): Promise<void> => {\n const canonical = await realpath(dirname(path));\n const metadata = await stat(canonical);\n if (\n canonical !== parent.path ||\n metadata.dev !== parent.dev ||\n metadata.ino !== parent.ino\n ) {\n throw new Error(\"Output directory changed while it was being used\");\n }\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst safeWrite = async (\n output: ScopedOutput,\n bytes: Uint8Array | string,\n): Promise<void> => {\n await assertOutputParent(output);\n const temporary = `${output.path}.stella-${randomUUID()}.tmp`;\n let published = false;\n let committed = false;\n let temporaryMetadata: FileIdentity | undefined;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o400,\n );\n try {\n temporaryMetadata = await handle.stat();\n const canonicalTemporary = await realpath(temporary);\n const currentTemporaryMetadata = await lstat(temporary);\n if (\n dirname(canonicalTemporary) !== output.parent.path ||\n !currentTemporaryMetadata.isFile() ||\n !sameFile(temporaryMetadata, currentTemporaryMetadata)\n ) {\n throw new Error(\"Output staging file changed while it was being used\");\n }\n await handle.writeFile(bytes);\n await handle.sync();\n await assertOutputParent(output);\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(temporaryMetadata, stagedMetadata)\n ) {\n throw new Error(\"Output staging file changed before publication\");\n }\n await link(temporary, output.path);\n published = true;\n const publishedMetadata = await lstat(output.path);\n if (\n !publishedMetadata.isFile() ||\n !sameFile(temporaryMetadata, publishedMetadata)\n ) {\n throw new Error(\"Published output does not match the staged file\");\n }\n await assertOutputParent(output);\n await handle.chmod(0o600);\n await handle.sync();\n committed = true;\n } finally {\n if (!committed) {\n await handle.truncate(0).catch(() => undefined);\n await handle.sync().catch(() => undefined);\n await handle.chmod(0o000).catch(() => undefined);\n }\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n if (published && !committed && temporaryMetadata !== undefined) {\n const publishedMetadata = await lstat(output.path).catch(() => undefined);\n if (\n publishedMetadata !== undefined &&\n sameFile(temporaryMetadata, publishedMetadata)\n ) {\n await unlink(output.path).catch(() => undefined);\n }\n }\n }\n};\n\nconst decodeText = (bytes: Uint8Array): string => {\n return decodeUtf8(bytes, \"Text inputs\");\n};\n\nconst decodeUtf8 = (bytes: Uint8Array, label: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"validation_error\",\n `${label} must contain valid UTF-8`,\n {\n hint: \"Provide UTF-8 encoded text; re-encode the file and retry.\",\n cause: error,\n },\n );\n }\n};\n\nconst applyTextReplacements = (\n text: string,\n replacements: readonly NativeTextReplacement[],\n): string => {\n const parts: string[] = [];\n let cursor = 0;\n for (const replacement of replacements) {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < cursor ||\n replacement.end <= replacement.start ||\n replacement.end > text.length ||\n !isUtf16Boundary(text, replacement.start) ||\n !isUtf16Boundary(text, replacement.end)\n ) {\n throw new Error(\n \"Native caller-detection plan returned invalid replacements\",\n );\n }\n parts.push(text.slice(cursor, replacement.start), replacement.replacement);\n cursor = replacement.end;\n }\n parts.push(text.slice(cursor));\n return parts.join(\"\");\n};\n\nconst isUtf16Boundary = (text: string, offset: number): boolean => {\n if (offset <= 0 || offset >= text.length) {\n return true;\n }\n const before = text.charCodeAt(offset - 1);\n const after = text.charCodeAt(offset);\n return !(\n before >= 0xd800 &&\n before <= 0xdbff &&\n after >= 0xdc00 &&\n after <= 0xdfff\n );\n};\n\nconst assertDifferentPaths = (input: string, output: string): void => {\n if (input === output) {\n throw surfaceError(\n \"validation_error\",\n \"Input and output paths must differ\",\n \"Choose a distinct output path so the input is never overwritten.\",\n );\n }\n};\n\nconst textInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n language: z.string().min(2).max(35).optional(),\n});\n\nconst externalDetectionTextInput = textInput.extend({\n detectionBatchPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n});\n\nconst restoreInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n});\n\nconst docxRestoreInput = restoreInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst docxInput = textInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst pdfInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n ocrLanguage: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/u),\n detectionLanguage: z.string().min(2).max(35).optional(),\n dpi: z.number().int().min(72).max(600).optional().default(300),\n timeoutMs: z.number().int().min(100).max(300_000).optional().default(120_000),\n fillRgb: z\n .tuple([\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n ])\n .optional()\n .default([0, 0, 0]),\n});\n\nexport type LocalPdfProviderConfiguration = {\n pdftoppmPath?: string | undefined;\n tesseractPath?: string | undefined;\n};\n\nexport type LocalAnonymizeServiceOptions = {\n durableSessions?: DurableSessionStore | undefined;\n faults?: LocalAnonymizeServiceFaults | undefined;\n pdfProvider?: LocalPdfProviderConfiguration | undefined;\n};\n\nexport class LocalAnonymizeService {\n #activeOperations = 0;\n #closePromise: Promise<void> | undefined;\n #operationsDrained: (() => void) | undefined;\n readonly #scope: PathScope;\n #pdfOperationTail: Promise<void> = Promise.resolve();\n readonly #sessionInitializations = new Set<string>();\n readonly #sessions = new Map<string, SessionEntry>();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n readonly #durableSessions: DurableSessionStore | undefined;\n readonly #faults: LocalAnonymizeServiceFaults;\n readonly #pdfProvider: LocalPdfProviderConfiguration;\n\n constructor(scope: PathScope, options: LocalAnonymizeServiceOptions = {}) {\n if (options instanceof DurableSessionStore) {\n throw new TypeError(\n \"LocalAnonymizeService requires { durableSessions } as its second argument\",\n );\n }\n const { durableSessions, faults = {}, pdfProvider = {} } = options;\n this.#scope = scope;\n this.#durableSessions = durableSessions;\n this.#faults = faults;\n this.#pdfProvider = pdfProvider;\n }\n\n get sessionMode(): McpSessionMode {\n return this.#durableSessions === undefined\n ? MCP_SESSION_MODES.memory\n : MCP_SESSION_MODES.durableEncrypted;\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n if (this.#activeOperations > 0) {\n await new Promise<void>((resolvePromise) => {\n this.#operationsDrained = resolvePromise;\n });\n }\n this.#sessions.clear();\n await this.#durableSessions?.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async #runOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP anonymize service is closing or closed\");\n }\n this.#activeOperations += 1;\n try {\n // Count the operation before the asynchronous preload so close() cannot\n // pass its drain barrier while this call is waiting for the binding.\n // The preload installs wasm under Bun before any inline native load in\n // the docx or pdf package; it is a no-op on Node.\n await preloadNativeBinding();\n return await operation();\n } finally {\n this.#activeOperations -= 1;\n if (this.#activeOperations === 0) {\n this.#operationsDrained?.();\n this.#operationsDrained = undefined;\n }\n }\n }\n\n async #session(sessionId: string, language?: string): Promise<SessionLease> {\n if (this.#durableSessions !== undefined && language !== undefined) {\n throw new Error(\n \"Durable sessions use the full all-language pipeline; omit language\",\n );\n }\n const existing = this.#sessions.get(sessionId);\n if (existing !== undefined) {\n if (language !== undefined && existing.language !== language) {\n throw surfaceError(\n \"validation_error\",\n \"A session cannot change language\",\n \"Use a new session id for a different language, or drop the language override.\",\n );\n }\n if (existing.status === \"initializing\") {\n throw new Error(\"The requested session is still initializing\");\n }\n if (existing.status === \"busy\") {\n throw new Error(\"The requested session is handling another operation\");\n }\n const checkpoint = existing.session.toPlaintextJson();\n existing.status = \"busy\";\n return {\n entry: existing,\n sessionId,\n rollback: { type: \"restore\", checkpoint },\n };\n }\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n if (this.#sessions.size >= SESSION_MAX_COUNT) {\n throw new Error(`MCP sessions must not exceed ${SESSION_MAX_COUNT}`);\n }\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n language === undefined ? {} : { language },\n );\n const durableSessions = this.#durableSessions;\n const stored =\n durableSessions === undefined\n ? undefined\n : await durableSessions.load(sessionId);\n let session: RedactionSession;\n if (stored === undefined) {\n session = pipeline.createRedactionSession(sessionId);\n } else {\n if (durableSessions === undefined) {\n throw surfaceError(\n \"session_unavailable\",\n \"Durable session storage is unavailable\",\n \"Start the server with --session-dir and --key-file to enable restores.\",\n );\n }\n try {\n session = durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"session_unavailable\",\n \"The requested durable session is unavailable\",\n {\n hint: \"Confirm the session id and key file match the archive that created it.\",\n cause: error,\n },\n );\n }\n }\n const entry: SessionEntry = {\n language,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"initializing\",\n };\n this.#sessions.set(sessionId, entry);\n return {\n entry,\n sessionId,\n rollback:\n stored === undefined\n ? { type: \"delete\" }\n : { type: \"restore\", checkpoint: session.toPlaintextJson() },\n };\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n\n #commitSession({ entry }: SessionLease): void {\n entry.status = \"ready\";\n }\n\n async #rollbackSession({\n entry,\n rollback,\n sessionId,\n }: SessionLease): Promise<void> {\n if (this.#sessions.get(sessionId) !== entry) {\n return;\n }\n if (rollback.type === \"delete\") {\n this.#sessions.delete(sessionId);\n await this.#durableSessions?.delete(sessionId).catch(() => undefined);\n return;\n }\n if (rollback.type === \"release\") {\n entry.status = \"ready\";\n return;\n }\n try {\n entry.session = entry.restoreSession(rollback.checkpoint);\n entry.status = \"ready\";\n await this.#persistSession(sessionId, entry.session);\n } catch {\n this.#sessions.delete(sessionId);\n }\n }\n\n async #readSession(sessionId: string): Promise<SessionLease> {\n let entry = this.#sessions.get(sessionId);\n if (entry === undefined && this.#durableSessions !== undefined) {\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n const pipeline = nativeNodeSurface.getDefaultNativePipeline({});\n const stored = await this.#durableSessions.load(sessionId);\n if (stored !== undefined) {\n try {\n const session = this.#durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n entry = {\n language: undefined,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"ready\",\n };\n this.#sessions.set(sessionId, entry);\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"session_unavailable\",\n \"The requested durable session is unavailable\",\n {\n hint: \"Confirm the session id and key file match the archive that created it.\",\n cause: error,\n },\n );\n }\n }\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n if (entry === undefined) {\n throw surfaceError(\n \"session_unavailable\",\n \"The requested session is unavailable\",\n \"Anonymize with this session id first, or start the server with a durable session store.\",\n );\n }\n if (entry.status !== \"ready\") {\n throw surfaceError(\n \"session_unavailable\",\n \"The requested in-memory session is unavailable\",\n \"Wait for the prior operation on this session to finish, then retry.\",\n true,\n );\n }\n entry.status = \"busy\";\n return { entry, sessionId, rollback: { type: \"release\" } };\n }\n\n async #persistSession(\n sessionId: string,\n session: RedactionSession,\n ): Promise<void> {\n if (this.#durableSessions === undefined) {\n return;\n }\n const archive = this.#durableSessions.seal(\n session,\n observedAtEpochSeconds(),\n );\n await this.#durableSessions.save(sessionId, archive);\n }\n\n async anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeText(input));\n }\n\n async anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() =>\n this.#serializePdfOperation(() => this.#anonymizePdf(input)),\n );\n }\n\n async #serializePdfOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#pdfOperationTail;\n let release = (): void => undefined;\n this.#pdfOperationTail = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".pdf\",\n maximumBytes: PDF_DOCUMENT_MAX_BYTES,\n label: \"PDF\",\n });\n const destination = await this.#scope.output(input.outputPath, \".pdf\");\n assertDifferentPaths(source.path, destination.path);\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n input.detectionLanguage === undefined\n ? {}\n : { language: input.detectionLanguage },\n );\n let observed: Awaited<ReturnType<typeof renderPdfWithPopplerTesseract>>;\n try {\n observed = await renderPdfWithPopplerTesseract({\n document: source.bytes,\n ocrLanguage: input.ocrLanguage,\n dpi: input.dpi,\n timeoutMs: input.timeoutMs,\n ...this.#pdfProvider,\n });\n } catch (error) {\n if (isPdfToolchainUnavailable(error)) {\n throw new AnonymizeSurfaceError(\n \"dependency_missing\",\n \"The local PDF toolchain is unavailable\",\n {\n hint: \"Install Poppler (pdftoppm) and Tesseract on PATH, or pass --pdftoppm/--tesseract.\",\n cause: error,\n },\n );\n }\n throw error;\n }\n const anonymized = anonymizePdfRaster({\n document: source.bytes,\n pipeline,\n provider: observed.provider,\n pages: observed.pages,\n fillRgb: input.fillRgb,\n });\n if (\n anonymized.certificate.structurePixelRewriteVerified !== true ||\n anonymized.certificate.piiCleanGuaranteed !== false\n ) {\n throw new Error(\"PDF raster verification did not satisfy MCP policy\");\n }\n await this.#faults.beforeOutputPublish?.();\n await destination.write(anonymized.document);\n return {\n operation: \"anonymize\",\n format: \"pdf\",\n outputCreated: true,\n pageCount: anonymized.certificate.pageCount,\n entityCount: anonymized.certificate.detectionCount,\n mappedRegionCount: anonymized.certificate.mappedRegionCount,\n structurePixelRewriteVerified: true,\n piiCleanGuaranteed: false,\n };\n }\n\n async #anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = lease.entry.session.redact_text(text);\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.redaction.redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.redaction.entityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreText(input));\n }\n\n async anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n try {\n return await this.#runOperation(() =>\n this.#anonymizeTextWithExternalDetections(input),\n );\n } catch (error) {\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n const { batch, destination, source } = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.inputRejected,\n async () => {\n const scopedSource = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const scopedBatch = await this.#scope.readInput({\n path: input.detectionBatchPath,\n extension: \".json\",\n maximumBytes: EXTERNAL_DETECTION_BATCH_MAX_BYTES,\n label: \"External detection batch\",\n });\n const scopedDestination = await this.#scope.output(\n input.outputPath,\n \".txt\",\n );\n assertDifferentPaths(scopedSource.path, scopedDestination.path);\n assertDifferentPaths(scopedBatch.path, scopedDestination.path);\n assertDifferentPaths(scopedSource.path, scopedBatch.path);\n return {\n batch: scopedBatch,\n destination: scopedDestination,\n source: scopedSource,\n };\n },\n );\n const text = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.documentRejected,\n () => decodeText(source.bytes),\n );\n const detections = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.batchRejected,\n () =>\n nativeNodeSurface.convert_external_detection_batch(\n source.bytes,\n decodeUtf8(batch.bytes, \"External detection batches\"),\n ),\n );\n const lease = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.sessionRejected,\n () => this.#session(input.sessionId, input.language),\n );\n try {\n const plan = lease.entry.session.planTextBatchWithCallerDetections({\n inputs: [{ fullText: text, detections }],\n });\n const block = plan.blocks.at(0);\n if (plan.blocks.length !== 1 || block === undefined) {\n throw new Error(\n \"Native caller-detection plan did not match the text input\",\n );\n }\n const redactedText = applyTextReplacements(text, block.replacements);\n plan.commit();\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: block.entityCount,\n externalDetectionBatchStatus: \"accepted\",\n externalDetectionCount: detections.length,\n retainedExternalDetectionCount: block.callerEntityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#readSession(input.sessionId);\n try {\n const restored = lease.entry.session.restoreText(text);\n await destination.write(restored);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeDocx(input));\n }\n\n async #anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = anonymizeDocx({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n policy: {\n coverage: {\n mode: input.allowPartialCoverage\n ? DOCX_COVERAGE_MODES.allowPartial\n : DOCX_COVERAGE_MODES.requireFull,\n },\n },\n });\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.summary.entityCount,\n blockCount: result.summary.blockCount,\n rewrittenBlockCount: result.summary.rewrittenBlockCount,\n coverageStatus: result.summary.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw mapDocxCoverageError(error);\n }\n }\n\n async restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreDocx(input));\n }\n\n async #restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#readSession(input.sessionId);\n try {\n const result = restoreDocxText({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n });\n if (result.coverage.status === \"partial\" && !input.allowPartialCoverage) {\n throw surfaceError(\n \"validation_error\",\n \"DOCX restoration has partial coverage; set allowPartialCoverage to publish it\",\n \"Re-run with allowPartialCoverage: true to accept partial restoration.\",\n );\n }\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n rewrittenBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverageStatus: result.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#inspectDocx(inputPath));\n }\n\n async #inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const extraction = extractDocxText(source.bytes);\n const unsupported = extraction.coverage.parts.some(\n (part) => part.status === \"unsupported\",\n );\n const structuralGap =\n extraction.coverage.hyperlinkTextSegmentCount > 0 ||\n extraction.coverage.revisionTextSegmentCount > 0 ||\n extraction.coverage.unsupportedAlternateContentCount > 0 ||\n extraction.coverage.unsupportedFieldInstructionCount > 0 ||\n extraction.coverage.unsupportedSymbolCount > 0;\n return {\n operation: \"inspect\",\n format: \"docx\",\n outputCreated: false,\n blockCount: extraction.blocks.length,\n coverageStatus: unsupported || structuralGap ? \"partial\" : \"full\",\n };\n }\n}\n\nconst result = (value: AuditSafeResult): CallToolResult => ({\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n});\n\nconst MCP_TOOL_NAMES = [\n \"anonymize_docx_file\",\n \"anonymize_pdf_file\",\n \"anonymize_text_file\",\n \"anonymize_text_file_with_external_detections\",\n \"capabilities\",\n \"inspect_docx_file\",\n \"restore_docx_file\",\n \"restore_text_file\",\n \"send_feedback\",\n] as const;\n\n/**\n * Map the external-detection failure taxonomy onto the shared agent-surface\n * codes. The specific failure identity is preserved in the envelope `message`;\n * `code` gives the agent the coarse, branchable class.\n */\nconst EXTERNAL_DETECTION_ENVELOPE: Record<\n ExternalDetectionFailure[\"code\"],\n { code: AnonymizeErrorCode; hint: string; retryable: boolean }\n> = {\n EXTERNAL_DETECTION_BATCH_REJECTED: {\n code: \"validation_error\",\n hint: \"Fix the ExternalDetectionBatch v1 sidecar to match the schema and retry.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_DOCUMENT_REJECTED: {\n code: \"validation_error\",\n hint: \"Align the sidecar's document metadata with the input, then retry.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_INPUT_REJECTED: {\n code: \"validation_error\",\n hint: \"Use distinct absolute paths inside a configured --root for input, sidecar, and output.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_OPERATION_FAILED: {\n code: \"internal_error\",\n hint: \"Retry; if it persists, file it with the send_feedback tool.\",\n retryable: true,\n },\n EXTERNAL_DETECTION_SESSION_REJECTED: {\n code: \"session_unavailable\",\n hint: \"Use a fresh session id, or confirm the session store and key file match.\",\n retryable: false,\n },\n};\n\nconst toEnvelope = (error: unknown): AnonymizeErrorEnvelope => {\n if (error instanceof ExternalDetectionAuditError) {\n const mapped = EXTERNAL_DETECTION_ENVELOPE[error.code];\n return {\n error: {\n code: mapped.code,\n message: error.message,\n hint: mapped.hint,\n retryable: mapped.retryable,\n },\n };\n }\n return classifyToEnvelope(error);\n};\n\nconst errorResult = (error: unknown): CallToolResult => {\n const envelope = toEnvelope(error);\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: JSON.stringify(envelope) }],\n structuredContent: { ...envelope },\n };\n};\n\n/**\n * Run a tool body, rendering any thrown error as the structured envelope so\n * every tool fails the same, agent-legible way instead of surfacing a raw\n * protocol error.\n */\nconst guard = async (\n produce: () => CallToolResult | Promise<CallToolResult>,\n): Promise<CallToolResult> => {\n try {\n return await produce();\n } catch (error) {\n return errorResult(error);\n }\n};\n\nconst capabilitiesResult = async (\n service: LocalAnonymizeService,\n): Promise<CallToolResult> => {\n await preloadNativeBinding();\n const value = {\n capabilityManifest: CAPABILITY_MANIFEST,\n runtimeVersion: nativeNodeSurface.native_package_version(),\n mcp: {\n externalDetectionBatch: {\n ingestion: \"path-only\" as const,\n version: EXTERNAL_DETECTION_BATCH_VERSION,\n },\n formats: [\"docx\", \"pdf\", \"text\"] as const,\n sessionMode: service.sessionMode,\n tools: MCP_TOOL_NAMES,\n transport: \"stdio\" as const,\n },\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nconst feedbackInput = z.object({\n kind: z.enum(FEEDBACK_KINDS),\n title: z.string().min(1).max(MAX_FEEDBACK_TITLE_CHARS),\n body: z.string().min(1).max(MAX_FEEDBACK_BODY_CHARS),\n});\n\nconst feedbackResult = (\n input: z.infer<typeof feedbackInput>,\n): CallToolResult => {\n const submission = buildFeedbackSubmission(input);\n const value = {\n channel: \"github\" as const,\n redactions: submission.redactions,\n title: submission.title,\n sanitizedBody: submission.sanitizedBody,\n issueUrl: submission.issueUrl,\n ghCommand: submission.ghCommand,\n note: \"Nothing was sent. Review the sanitized content, then open the URL (or run the gh command) to submit the issue under your own GitHub account.\",\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n };\n};\n\n/**\n * Server `instructions` handed to MCP clients at connect time. Kept terse and\n * factual; the char budget is asserted in `instructions.test.ts` to guard drift.\n */\nexport const MCP_INSTRUCTIONS_MAX_CHARS = 1200;\nexport const MCP_INSTRUCTIONS = `stella-anonymize redacts PII in local text, DOCX, and PDF files. Every tool reads and writes local paths only, inside the directories passed as --root; it never returns document text or session mappings, and it never overwrites, so outputs must be new paths.\n\nErrors: a failed tool returns a single text content of {\"error\":{\"code\",\"message\",\"hint\",\"retryable\"}} with isError set. Branch on code (validation_error, path_not_allowed, not_found, unsupported_format, output_exists, session_unavailable, dependency_missing, internal_error); hint states the next step. Nothing here is destructive: there is no delete and existing files are never overwritten, so no confirm step is needed.\n\nSessions: reversible replace mode uses a session; a restore needs the same session id, plus a durable store (server started with --session-dir and --key-file) to survive a restart.\n\nHit a bug or a gap? Use send_feedback: it sanitizes your text locally and returns a prefilled GitHub issue URL you open and submit yourself. It sends nothing over the network.`;\n\nexport const createAnonymizeMcpServer = (\n service: LocalAnonymizeService,\n): McpServer => {\n // Synchronous by contract (public factory). The server version comes from the\n // package manifest, not a native call, so construction touches no binding; the\n // wasm binding is preloaded lazily on the first operation (see `#runOperation`).\n const server = new McpServer(\n {\n name: \"stella-anonymize-local\",\n version: pkg.version,\n },\n {\n instructions: MCP_INSTRUCTIONS,\n },\n );\n server.registerTool(\n \"capabilities\",\n {\n description:\n \"Return the public runtime capability manifest and MCP surface metadata.\",\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async () => guard(() => capabilitiesResult(service)),\n );\n server.registerTool(\n \"anonymize_text_file\",\n {\n description: \"Anonymize a local UTF-8 text file into a new local file.\",\n inputSchema: textInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizeText(input))),\n );\n server.registerTool(\n \"restore_text_file\",\n {\n description: \"Restore a text file using the configured session store.\",\n inputSchema: restoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.restoreText(input))),\n );\n server.registerTool(\n \"anonymize_text_file_with_external_detections\",\n {\n description:\n \"Anonymize a local UTF-8 text file with a provider-neutral ExternalDetectionBatch v1 JSON sidecar into a new local file.\",\n inputSchema: externalDetectionTextInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () =>\n result(await service.anonymizeTextWithExternalDetections(input)),\n ),\n );\n server.registerTool(\n \"anonymize_docx_file\",\n {\n description:\n \"Structure-preservingly anonymize a local DOCX into a new local DOCX.\",\n inputSchema: docxInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizeDocx(input))),\n );\n server.registerTool(\n \"anonymize_pdf_file\",\n {\n description:\n \"Destructively raster-anonymize a local PDF into a fresh image-only PDF. Returns aggregate verification only; it does not claim perfect OCR or detector recall.\",\n inputSchema: pdfInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizePdf(input))),\n );\n server.registerTool(\n \"restore_docx_file\",\n {\n description: \"Restore a DOCX using the configured session store.\",\n inputSchema: docxRestoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.restoreDocx(input))),\n );\n server.registerTool(\n \"inspect_docx_file\",\n {\n description:\n \"Return only aggregate DOCX coverage and block counts; never document text.\",\n inputSchema: z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n }),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async ({ inputPath }) =>\n guard(async () => result(await service.inspectDocx(inputPath))),\n );\n server.registerTool(\n \"send_feedback\",\n {\n description:\n \"File a bug, feature request, or docs issue with the stella-anonymize maintainers. Sanitizes the title and body locally (emails, ids, secrets, URLs, IPs are redacted) and returns a prefilled GitHub new-issue URL and a gh command that you open and submit under your own account. It sends nothing over the network and publishes nothing on its own. Never include document text, client names, ids, or secrets; describe the problem, steps, and expected vs actual result.\",\n inputSchema: feedbackInput,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async (input) => guard(() => feedbackResult(input)),\n );\n return server;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local.mjs","names":["SESSION_ID","READ_CHUNK_BYTES","sameFile","fsConstants","readHandleBounded","#directory","#faultInjector","#key","#lockHandle","#validateInventory","#syncDirectory","#assertOpen","#closePromise","#state","#mutationTail","#withMutation","#load","#path","#assertDirectory","#save","#inject","#delete","#roots","fsConstants","#scope","#sessionInitializations","#sessions","#durableSessions","#faults","#pdfProvider","#closePromise","#state","#activeOperations","#operationsDrained","#runOperation","#session","#rollbackSession","#persistSession","#readSession","#anonymizeText","#serializePdfOperation","#anonymizePdf","#pdfOperationTail","#commitSession","#restoreText","#anonymizeTextWithExternalDetections","#anonymizeDocx","#restoreDocx","#inspectDocx","pkg.version"],"sources":["../package.json","../src/durable-sessions.ts","../src/local.ts"],"sourcesContent":["","import { createHash, randomUUID } from \"node:crypto\";\nimport {\n constants as fsConstants,\n lstat,\n open,\n readdir,\n realpath,\n rename,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nexport const SESSION_ARCHIVE_KEY_BYTES = 32;\nexport const SESSION_ARCHIVE_MAX_BYTES = 16 * 1024 * 1024 + 57;\nexport const SESSION_ARCHIVE_MAX_COUNT = 256;\nexport const SESSION_ARCHIVE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;\n\nconst ARCHIVE_NAME = /^[a-f0-9]{64}\\.stlasess$/u;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst STAGING_NAME =\n /^[a-f0-9]{64}\\.stlasess\\.tmp\\.[0-9a-f]{8}-[0-9a-f-]{27}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\nconst LOCK_FILE_NAME = \".stella-session.lock\";\n\nexport const DURABLE_SESSION_FAULT_POINTS = {\n beforeDirectoryFsync: \"before-directory-fsync\",\n beforeRename: \"before-rename\",\n beforeStagingFsync: \"before-staging-fsync\",\n beforeStagingWrite: \"before-staging-write\",\n} as const;\n\nexport type DurableSessionFaultPoint =\n (typeof DURABLE_SESSION_FAULT_POINTS)[keyof typeof DURABLE_SESSION_FAULT_POINTS];\n\ntype FileIdentity = {\n dev: number;\n ino: number;\n};\n\ntype DirectoryIdentity = FileIdentity & {\n path: string;\n};\n\ntype SessionInventory = {\n archiveCount: number;\n totalBytes: number;\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst assertOwner = (uid: number, label: string): void => {\n if (typeof process.getuid === \"function\" && uid !== process.getuid()) {\n throw new Error(`${label} must be owned by the current user`);\n }\n};\n\nconst assertPrivateMode = (mode: number, label: string): void => {\n if ((mode & 0o077) !== 0) {\n throw new Error(`${label} must not grant group or other permissions`);\n }\n};\n\nconst assertPosixDurabilitySupport = (): void => {\n if (\n (process.platform !== \"darwin\" && process.platform !== \"linux\") ||\n typeof process.getuid !== \"function\" ||\n typeof fsConstants.O_NOFOLLOW !== \"number\" ||\n fsConstants.O_NOFOLLOW === 0 ||\n typeof fsConstants.O_DIRECTORY !== \"number\" ||\n fsConstants.O_DIRECTORY === 0\n ) {\n throw new Error(\n \"Encrypted durable MCP sessions require supported POSIX owner, nofollow, directory-fsync, and advisory-lock semantics on macOS or Linux\",\n );\n }\n};\n\nconst acquireDirectoryLock = async (\n directoryPath: string,\n): Promise<FileHandle> => {\n const path = join(directoryPath, LOCK_FILE_NAME);\n const handle = await open(\n path,\n fsConstants.O_RDWR |\n fsConstants.O_CREAT |\n fsConstants.O_NOFOLLOW |\n fsConstants.O_NONBLOCK,\n 0o600,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"MCP session lock must be a regular file\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n // Loaded lazily because Bun is used as a repository test/build tool but\n // cannot safely load this Node native addon. The shipped MCP runtime is\n // Node; Node integration tests exercise this path.\n const { tryLock } = await import(\"fs-native-extensions\");\n if (!tryLock(handle.fd)) {\n throw new Error(\n \"MCP session directory is already locked by another server\",\n );\n }\n return handle;\n } catch (error) {\n await handle.close().catch(() => undefined);\n throw error;\n }\n};\n\nconst canonicalAbsolutePath = async (\n path: string,\n label: string,\n): Promise<string> => {\n if (!isAbsolute(path)) {\n throw new Error(`${label} must be an absolute path`);\n }\n const normalized = resolve(path);\n const canonical = await realpath(normalized);\n if (canonical !== normalized) {\n throw new Error(`${label} must not contain symbolic links`);\n }\n return canonical;\n};\n\nconst readHandleBounded = async (\n handle: Pick<Awaited<ReturnType<typeof open>>, \"read\">,\n maximumBytes: number,\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const remaining = maximumBytes - total;\n const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining + 1));\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\nconst readKey = async (path: string): Promise<Uint8Array> => {\n const canonical = await canonicalAbsolutePath(path, \"MCP session key file\");\n const linkedMetadata = await lstat(canonical);\n if (!linkedMetadata.isFile() || linkedMetadata.isSymbolicLink()) {\n throw new Error(\"MCP session key file must be a regular file\");\n }\n assertOwner(linkedMetadata.uid, \"MCP session key file\");\n assertPrivateMode(linkedMetadata.mode, \"MCP session key file\");\n if (linkedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES) {\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const handle = await open(\n canonical,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n let keyBuffer: Buffer | undefined;\n try {\n const openedMetadata = await handle.stat();\n if (\n !openedMetadata.isFile() ||\n !sameFile(linkedMetadata, openedMetadata) ||\n openedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES\n ) {\n throw new Error(\"MCP session key file changed while it was validated\");\n }\n keyBuffer = Buffer.alloc(SESSION_ARCHIVE_KEY_BYTES + 1);\n let keyLength = 0;\n for (;;) {\n const { bytesRead } = await handle.read(\n keyBuffer,\n keyLength,\n keyBuffer.byteLength - keyLength,\n null,\n );\n if (bytesRead === 0 || keyLength + bytesRead === keyBuffer.byteLength) {\n keyLength += bytesRead;\n break;\n }\n keyLength += bytesRead;\n }\n if (keyLength !== SESSION_ARCHIVE_KEY_BYTES) {\n keyBuffer.fill(0);\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const currentMetadata = await stat(canonical);\n if (!sameFile(openedMetadata, currentMetadata)) {\n keyBuffer.fill(0);\n throw new Error(\"MCP session key file changed while it was read\");\n }\n const key = new Uint8Array(SESSION_ARCHIVE_KEY_BYTES);\n key.set(keyBuffer.subarray(0, SESSION_ARCHIVE_KEY_BYTES));\n keyBuffer.fill(0);\n return key;\n } finally {\n keyBuffer?.fill(0);\n await handle.close();\n }\n};\n\nconst archiveName = (sessionId: string): string =>\n `${createHash(\"sha256\").update(sessionId, \"utf8\").digest(\"hex\")}.stlasess`;\n\nconst assertSessionId = (sessionId: string): void => {\n if (!SESSION_ID.test(sessionId)) {\n throw new Error(\"MCP session ID is invalid\");\n }\n};\n\nexport type DurableSessionStoreOptions = {\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void;\n keyFile: string;\n sessionDirectory: string;\n};\n\nexport type StoredSessionArchive = {\n bytes: Uint8Array;\n};\n\nexport type EncryptableSession = {\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n};\n\nexport type EncryptedSessionRestorer<Session> = {\n restoreEncryptedRedactionSession(options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }): Session;\n};\n\nexport type RestoreStoredSessionOptions<Session> = {\n archive: Uint8Array;\n expectedSessionId: string;\n observedAtEpochSeconds: number;\n restorer: EncryptedSessionRestorer<Session>;\n};\n\nexport class DurableSessionStore {\n #closePromise: Promise<void> | undefined;\n readonly #directory: DirectoryIdentity;\n readonly #faultInjector:\n | ((point: DurableSessionFaultPoint) => Promise<void> | void)\n | undefined;\n readonly #key: Uint8Array;\n readonly #lockHandle: FileHandle;\n #mutationTail: Promise<void> = Promise.resolve();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n\n private constructor(\n directory: DirectoryIdentity,\n key: Uint8Array,\n lockHandle: FileHandle,\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void,\n ) {\n this.#directory = directory;\n this.#key = key;\n this.#lockHandle = lockHandle;\n this.#faultInjector = faultInjector;\n }\n\n static async create({\n keyFile,\n sessionDirectory,\n faultInjector,\n }: DurableSessionStoreOptions): Promise<DurableSessionStore> {\n assertPosixDurabilitySupport();\n const directoryPath = await canonicalAbsolutePath(\n sessionDirectory,\n \"MCP session directory\",\n );\n const metadata = await lstat(directoryPath);\n if (!metadata.isDirectory() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory must be a directory\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n const key = await readKey(keyFile);\n let lockHandle: FileHandle | undefined;\n try {\n lockHandle = await acquireDirectoryLock(directoryPath);\n const store = new DurableSessionStore(\n { dev: metadata.dev, ino: metadata.ino, path: directoryPath },\n key,\n lockHandle,\n faultInjector,\n );\n await store.#validateInventory({ removeStagingFiles: true });\n await store.#syncDirectory();\n return store;\n } catch (error) {\n key.fill(0);\n await lockHandle?.close().catch(() => undefined);\n throw error;\n }\n }\n\n seal(\n session: EncryptableSession,\n observedAtEpochSeconds: number,\n ): Uint8Array {\n this.#assertOpen();\n return session.toEncryptedArchiveAt(this.#key, observedAtEpochSeconds);\n }\n\n restore<Session>({\n archive,\n expectedSessionId,\n observedAtEpochSeconds,\n restorer,\n }: RestoreStoredSessionOptions<Session>): Session {\n this.#assertOpen();\n return restorer.restoreEncryptedRedactionSession({\n archive,\n expectedSessionId,\n key: this.#key,\n observedAtEpochSeconds,\n });\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n await this.#mutationTail;\n this.#key.fill(0);\n await this.#lockHandle.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n this.#assertOpen();\n return this.#withMutation(() => this.#load(sessionId));\n }\n\n async #load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n assertSessionId(sessionId);\n await this.#validateInventory({ removeStagingFiles: false });\n const path = this.#path(sessionId);\n let handle: Awaited<ReturnType<typeof open>>;\n try {\n handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw new Error(\"Encrypted session archive could not be opened\", {\n cause: error,\n });\n }\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"Encrypted session archive must be a regular file\");\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const bytes = await readHandleBounded(handle, SESSION_ARCHIVE_MAX_BYTES);\n await this.#assertDirectory();\n const currentMetadata = await lstat(path);\n if (!currentMetadata.isFile() || !sameFile(metadata, currentMetadata)) {\n throw new Error(\"Encrypted session archive changed while it was read\");\n }\n return { bytes };\n } finally {\n await handle.close();\n }\n }\n\n async save(sessionId: string, archive: Uint8Array): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#save(sessionId, archive));\n }\n\n async #save(sessionId: string, archive: Uint8Array): Promise<void> {\n assertSessionId(sessionId);\n if (archive.byteLength > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const inventory = await this.#validateInventory({\n removeStagingFiles: false,\n });\n await this.#assertDirectory();\n const destination = this.#path(sessionId);\n const temporary = `${destination}.tmp.${randomUUID()}`;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o600,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Encrypted session staging path is not a regular file\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingWrite);\n await handle.writeFile(archive);\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingFsync);\n await handle.sync();\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(openedMetadata, stagedMetadata)\n ) {\n throw new Error(\n \"Encrypted session staging file changed before publication\",\n );\n }\n await this.#assertDirectory();\n const existing = await lstat(destination).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (\n existing !== undefined &&\n (!existing.isFile() || existing.isSymbolicLink())\n ) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n const nextCount =\n inventory.archiveCount + (existing === undefined ? 1 : 0);\n const nextTotalBytes =\n inventory.totalBytes - (existing?.size ?? 0) + archive.byteLength;\n if (nextCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (nextTotalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeRename);\n await rename(temporary, destination);\n await this.#assertDirectory();\n const published = await lstat(destination);\n if (!published.isFile() || !sameFile(openedMetadata, published)) {\n throw new Error(\"Encrypted session archive publication was not atomic\");\n }\n await this.#syncDirectory();\n } finally {\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n }\n }\n\n async delete(sessionId: string): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#delete(sessionId));\n }\n\n async #delete(sessionId: string): Promise<void> {\n assertSessionId(sessionId);\n await this.#assertDirectory();\n const path = this.#path(sessionId);\n const metadata = await lstat(path).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (metadata === undefined) {\n return;\n }\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n await unlink(path);\n await this.#syncDirectory();\n await this.#assertDirectory();\n }\n\n #path(sessionId: string): string {\n return join(this.#directory.path, archiveName(sessionId));\n }\n\n #assertOpen(): void {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP durable session store is closing or closed\");\n }\n }\n\n async #withMutation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#mutationTail;\n let release = (): void => undefined;\n const current = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n this.#mutationTail = previous.then(() => current);\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #assertDirectory(): Promise<void> {\n const canonical = await realpath(this.#directory.path);\n const metadata = await lstat(this.#directory.path);\n if (\n canonical !== this.#directory.path ||\n !metadata.isDirectory() ||\n metadata.isSymbolicLink() ||\n !sameFile(this.#directory, metadata)\n ) {\n throw new Error(\"MCP session directory changed while it was being used\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n }\n\n async #syncDirectory(): Promise<void> {\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeDirectoryFsync);\n const handle = await open(\n this.#directory.path,\n fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isDirectory() || !sameFile(this.#directory, metadata)) {\n throw new Error(\"MCP session directory changed before synchronization\");\n }\n await handle.sync();\n } finally {\n await handle.close();\n }\n }\n\n async #inject(point: DurableSessionFaultPoint): Promise<void> {\n await this.#faultInjector?.(point);\n }\n\n async #validateInventory({\n removeStagingFiles,\n }: {\n removeStagingFiles: boolean;\n }): Promise<SessionInventory> {\n await this.#assertDirectory();\n const entries = await readdir(this.#directory.path, {\n withFileTypes: true,\n });\n let archiveCount = 0;\n let totalBytes = 0;\n for (const entry of entries) {\n const path = join(this.#directory.path, entry.name);\n if (entry.name === LOCK_FILE_NAME) {\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory contains an unsafe lock path\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n continue;\n }\n if (STAGING_NAME.test(entry.name)) {\n if (!removeStagingFiles) {\n throw new Error(\"MCP session directory contains a partial archive\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe staging path\",\n );\n }\n await unlink(path);\n await this.#syncDirectory();\n continue;\n }\n if (\n !ARCHIVE_NAME.test(entry.name) ||\n !entry.isFile() ||\n entry.isSymbolicLink()\n ) {\n throw new Error(\"MCP session directory contains an unsupported entry\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe archive path\",\n );\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n archiveCount += 1;\n totalBytes += metadata.size;\n if (archiveCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (totalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n }\n await this.#assertDirectory();\n return { archiveCount, totalBytes };\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n CAPABILITY_MANIFEST,\n type NativeCallerDetection,\n type NativeTextReplacement,\n type PreparedNativePipeline,\n} from \"@stll/anonymize\";\nimport {\n AnonymizeSurfaceError,\n classifyToEnvelope,\n type AnonymizeErrorCode,\n type AnonymizeErrorEnvelope,\n} from \"@stll/anonymize/agent-surface\";\nimport {\n FEEDBACK_KINDS,\n MAX_FEEDBACK_BODY_CHARS,\n MAX_FEEDBACK_TITLE_CHARS,\n buildFeedbackSubmission,\n} from \"@stll/anonymize/feedback\";\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n extractDocxText,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\nimport {\n PDF_DOCUMENT_MAX_BYTES,\n anonymizePdfRaster,\n renderPdfWithPopplerTesseract,\n} from \"@stll/anonymize-pdf\";\nimport * as nativeNode from \"@stll/anonymize/native-node\";\nimport {\n constants as fsConstants,\n link,\n lstat,\n open,\n realpath,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n dirname,\n extname,\n isAbsolute,\n relative,\n resolve,\n sep,\n} from \"node:path\";\nimport * as z from \"zod/v4\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nimport { DurableSessionStore } from \"./durable-sessions\";\n\nconst TEXT_MAX_BYTES = 64 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_MAX_BYTES = 16 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_VERSION = 1 as const;\nconst PATH_MAX_CHARACTERS = 32_768;\nconst SESSION_MAX_COUNT = 256;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\n\n/** Build an agent-surface error carrying a stable code, message, and hint. */\nconst surfaceError = (\n code: AnonymizeErrorCode,\n message: string,\n hint: string,\n retryable = false,\n): AnonymizeSurfaceError =>\n new AnonymizeSurfaceError(code, message, { hint, retryable });\n\nconst nodeErrorCode = (error: unknown): string | undefined =>\n typeof error === \"object\" && error !== null && \"code\" in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n\n/**\n * The local PDF provider collapses a missing/failed pdftoppm or tesseract into a\n * `PdfLocalProviderError` with code `executable-failed`; treat it as a missing\n * dependency so agents get an actionable install hint instead of a generic\n * internal error. Duck-typed to avoid importing the provider's internals.\n */\nconst isPdfToolchainUnavailable = (error: unknown): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n (error as { name?: unknown }).name === \"PdfLocalProviderError\" &&\n (error as { code?: unknown }).code === \"executable-failed\";\n\n/**\n * The docx package refuses under requireFull coverage with a\n * `DocxAnonymizationError` (code `incomplete-coverage`). Map it to a\n * validation_error so the agent gets the actionable allowPartialCoverage hint\n * instead of a generic internal_error; pass anything else through unchanged.\n * Duck-typed to avoid importing the docx error class.\n */\nconst mapDocxCoverageError = (error: unknown): unknown => {\n if (\n typeof error === \"object\" &&\n error !== null &&\n (error as { name?: unknown }).name === \"DocxAnonymizationError\" &&\n (error as { code?: unknown }).code === \"incomplete-coverage\"\n ) {\n // Preserve the docx package's specific (content-free) coverage message; add\n // the stable code and the actionable hint.\n return new AnonymizeSurfaceError(\n \"validation_error\",\n (error as Error).message,\n {\n hint: \"Re-run with allowPartialCoverage: true to accept partial coverage.\",\n cause: error,\n },\n );\n }\n return error;\n};\n\nconst observedAtEpochSeconds = (): number => {\n const seconds = Math.floor(Date.now() / 1000);\n if (seconds < 0 || seconds > 0xff_ff_ff_ff) {\n throw new Error(\"The current time is outside the supported session range\");\n }\n return seconds;\n};\n\nexport const MCP_SESSION_MODES = {\n durableEncrypted: \"durable-encrypted\",\n memory: \"memory\",\n} as const;\n\nexport type McpSessionMode =\n (typeof MCP_SESSION_MODES)[keyof typeof MCP_SESSION_MODES];\n\nexport type AuditSafeResult = {\n operation: \"anonymize\" | \"inspect\" | \"restore\";\n format: \"docx\" | \"pdf\" | \"text\";\n outputCreated: boolean;\n sessionId?: string;\n entityCount?: number;\n blockCount?: number;\n rewrittenBlockCount?: number;\n restoredPlaceholderCount?: number;\n coverageStatus?: \"full\" | \"partial\";\n externalDetectionBatchStatus?: \"accepted\";\n externalDetectionCount?: number;\n retainedExternalDetectionCount?: number;\n pageCount?: number;\n mappedRegionCount?: number;\n structurePixelRewriteVerified?: true;\n piiCleanGuaranteed?: false;\n};\n\nconst EXTERNAL_DETECTION_FAILURES = {\n batchRejected: {\n code: \"EXTERNAL_DETECTION_BATCH_REJECTED\",\n message: \"The external detection batch was rejected.\",\n },\n documentRejected: {\n code: \"EXTERNAL_DETECTION_DOCUMENT_REJECTED\",\n message: \"The external detection document was rejected.\",\n },\n inputRejected: {\n code: \"EXTERNAL_DETECTION_INPUT_REJECTED\",\n message: \"The external detection request paths were rejected.\",\n },\n operationFailed: {\n code: \"EXTERNAL_DETECTION_OPERATION_FAILED\",\n message: \"The external detection operation failed safely.\",\n },\n sessionRejected: {\n code: \"EXTERNAL_DETECTION_SESSION_REJECTED\",\n message: \"The external detection session was rejected.\",\n },\n} as const;\n\ntype ExternalDetectionFailure =\n (typeof EXTERNAL_DETECTION_FAILURES)[keyof typeof EXTERNAL_DETECTION_FAILURES];\n\nclass ExternalDetectionAuditError extends Error {\n readonly code: ExternalDetectionFailure[\"code\"];\n\n constructor(failure: ExternalDetectionFailure) {\n super(failure.message);\n this.name = \"ExternalDetectionAuditError\";\n this.code = failure.code;\n }\n}\n\nconst externalDetectionFailure = (\n error: unknown,\n failure: ExternalDetectionFailure,\n): ExternalDetectionAuditError =>\n error instanceof ExternalDetectionAuditError\n ? error\n : new ExternalDetectionAuditError(failure);\n\nconst externalDetectionStep = async <Result>(\n failure: ExternalDetectionFailure,\n operation: () => Result | Promise<Result>,\n): Promise<Result> => {\n try {\n return await operation();\n } catch (error) {\n throw externalDetectionFailure(error, failure);\n }\n};\n\nexport type LocalAnonymizeServiceFaults = {\n beforeOutputPublish?: () => void | Promise<void>;\n};\n\nconst inside = (root: string, target: string): boolean => {\n const path = relative(root, target);\n return (\n path === \"\" ||\n (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path))\n );\n};\n\ntype ReadInputOptions = {\n path: string;\n extension: \".docx\" | \".json\" | \".pdf\" | \".txt\";\n maximumBytes: number;\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\";\n};\n\ntype ScopedInput = {\n bytes: Uint8Array;\n path: string;\n};\n\ntype ReadableFileHandle = Pick<Awaited<ReturnType<typeof open>>, \"read\">;\n\nconst readHandleBounded = async (\n handle: ReadableFileHandle,\n maximumBytes: number,\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\",\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const chunk = Buffer.allocUnsafe(\n Math.min(READ_CHUNK_BYTES, maximumBytes - total + 1),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw surfaceError(\n \"validation_error\",\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n \"Split or shrink the input below the size limit and retry.\",\n );\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\ntype DirectoryIdentity = {\n dev: number;\n ino: number;\n path: string;\n};\n\ntype FileIdentity = Pick<DirectoryIdentity, \"dev\" | \"ino\">;\n\nclass ScopedOutput {\n readonly parent: DirectoryIdentity;\n readonly path: string;\n\n constructor(path: string, parent: DirectoryIdentity) {\n this.path = path;\n this.parent = parent;\n }\n\n async write(bytes: Uint8Array | string): Promise<void> {\n await safeWrite(this, bytes);\n }\n}\n\nexport class PathScope {\n readonly #roots: readonly string[];\n\n private constructor(roots: readonly string[]) {\n this.#roots = roots;\n }\n\n static async create(roots: readonly string[]): Promise<PathScope> {\n if (roots.length === 0) {\n throw new Error(\"At least one --root directory is required\");\n }\n const canonical = await Promise.all(\n roots.map(async (root) => {\n if (!isAbsolute(root)) {\n throw new Error(\"MCP roots must be absolute paths\");\n }\n const path = await realpath(root);\n const metadata = await stat(path);\n if (!metadata.isDirectory()) {\n throw new Error(\"Every MCP root must be a directory\");\n }\n return path;\n }),\n );\n return new PathScope([...new Set(canonical)]);\n }\n\n async readInput({\n path,\n extension,\n maximumBytes,\n label,\n }: ReadInputOptions): Promise<ScopedInput> {\n if (!isAbsolute(path)) {\n throw surfaceError(\n \"validation_error\",\n `Input must be an absolute ${extension} path`,\n \"Pass an absolute path to an existing file inside a configured --root.\",\n );\n }\n if (extname(path).toLowerCase() !== extension) {\n throw surfaceError(\n \"unsupported_format\",\n `Input must be an absolute ${extension} path`,\n `Provide a file with the ${extension} extension.`,\n );\n }\n const initiallyCanonical = await this.canonicalInput(path);\n if (!this.#roots.some((root) => inside(root, initiallyCanonical))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Input is outside the configured roots\",\n \"Move the input under a configured --root, or add its directory with --root.\",\n );\n }\n const initiallyRequestedMetadata = await lstat(path);\n if (!initiallyRequestedMetadata.isFile()) {\n throw surfaceError(\n \"validation_error\",\n \"Input must be a regular file\",\n \"Point at a regular file, not a directory, symlink, or special file.\",\n );\n }\n const handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw surfaceError(\n \"validation_error\",\n \"Input must be a regular file\",\n \"Point at a regular file, not a directory, symlink, or special file.\",\n );\n }\n const canonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, canonical))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Input is outside the configured roots\",\n \"Move the input under a configured --root, or add its directory with --root.\",\n );\n }\n const currentMetadata = await stat(canonical);\n if (\n currentMetadata.dev !== openedMetadata.dev ||\n currentMetadata.ino !== openedMetadata.ino\n ) {\n throw new Error(\"Input changed while it was being validated\");\n }\n if (openedMetadata.size > maximumBytes) {\n throw surfaceError(\n \"validation_error\",\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n \"Split or shrink the input below the size limit and retry.\",\n );\n }\n const bytes = await readHandleBounded(handle, maximumBytes, label);\n return { bytes, path: canonical };\n } finally {\n await handle.close();\n }\n }\n\n /**\n * Canonicalize an input path, mapping a missing path to a `not_found` surface\n * error instead of a raw fs `ENOENT` so agents get a stable code.\n */\n private async canonicalInput(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch (error) {\n if (nodeErrorCode(error) === \"ENOENT\") {\n throw surfaceError(\n \"not_found\",\n \"Input path does not exist\",\n \"Create the file first, or pass an existing path inside a configured --root.\",\n );\n }\n throw error;\n }\n }\n\n async output(\n path: string,\n extension: \".docx\" | \".pdf\" | \".txt\",\n ): Promise<ScopedOutput> {\n if (!isAbsolute(path)) {\n throw surfaceError(\n \"validation_error\",\n `Output must be an absolute ${extension} path`,\n \"Pass an absolute output path inside a configured --root.\",\n );\n }\n if (extname(path).toLowerCase() !== extension) {\n throw surfaceError(\n \"unsupported_format\",\n `Output must be an absolute ${extension} path`,\n `Name the output with the ${extension} extension.`,\n );\n }\n const normalized = resolve(path);\n let parent: string;\n try {\n parent = await realpath(dirname(normalized));\n } catch (error) {\n if (nodeErrorCode(error) === \"ENOENT\") {\n throw surfaceError(\n \"not_found\",\n \"Output directory does not exist\",\n \"Create the output directory first, inside a configured --root.\",\n );\n }\n throw error;\n }\n if (!this.#roots.some((root) => inside(root, parent))) {\n throw surfaceError(\n \"path_not_allowed\",\n \"Output is outside the configured roots\",\n \"Choose an output directory under a configured --root.\",\n );\n }\n const parentMetadata = await stat(parent);\n try {\n await lstat(normalized);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return new ScopedOutput(normalized, {\n dev: parentMetadata.dev,\n ino: parentMetadata.ino,\n path: parent,\n });\n }\n throw new Error(\"Output availability could not be verified\", {\n cause: error,\n });\n }\n throw surfaceError(\n \"output_exists\",\n \"Output already exists; overwriting is not supported\",\n \"Pick a new output path; anonymize never overwrites an existing file.\",\n );\n }\n}\n\ntype SessionEntry = {\n language: string | undefined;\n session: RedactionSession;\n restoreSession: (plaintextJson: string) => RedactionSession;\n status: \"busy\" | \"initializing\" | \"ready\";\n};\n\ntype SessionLease = {\n entry: SessionEntry;\n sessionId: string;\n rollback:\n | { type: \"delete\" }\n | { type: \"release\" }\n | { type: \"restore\"; checkpoint: string };\n};\n\ntype RedactionSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n redact_text(text: string): {\n redaction: { redactedText: string; entityCount: number };\n };\n restoreText(text: string): string;\n toPlaintextJson(): string;\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n };\n\ntype NativeNodeSurface = {\n convert_external_detection_batch: (\n document: Uint8Array,\n batch: string,\n ) => NativeCallerDetection[];\n getDefaultNativePipeline: (options: { language?: string }) => {\n createRedactionSession: (sessionId: string) => RedactionSession;\n restoreRedactionSession: (plaintextJson: string) => RedactionSession;\n restoreEncryptedRedactionSession: (options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }) => RedactionSession;\n } & Pick<\n PreparedNativePipeline,\n \"redactText\" | \"redactTextWithCallerDetections\"\n >;\n native_package_version: () => string;\n};\n\nconst nativeNodeSurface = nativeNode as unknown as NativeNodeSurface; // SAFETY: These native-node runtime exports are public, but their generated declarations are minified during workspace builds.\n\nconst assertOutputParent = async ({\n parent,\n path,\n}: ScopedOutput): Promise<void> => {\n const canonical = await realpath(dirname(path));\n const metadata = await stat(canonical);\n if (\n canonical !== parent.path ||\n metadata.dev !== parent.dev ||\n metadata.ino !== parent.ino\n ) {\n throw new Error(\"Output directory changed while it was being used\");\n }\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst safeWrite = async (\n output: ScopedOutput,\n bytes: Uint8Array | string,\n): Promise<void> => {\n await assertOutputParent(output);\n const temporary = `${output.path}.stella-${randomUUID()}.tmp`;\n let published = false;\n let committed = false;\n let temporaryMetadata: FileIdentity | undefined;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o400,\n );\n try {\n temporaryMetadata = await handle.stat();\n const canonicalTemporary = await realpath(temporary);\n const currentTemporaryMetadata = await lstat(temporary);\n if (\n dirname(canonicalTemporary) !== output.parent.path ||\n !currentTemporaryMetadata.isFile() ||\n !sameFile(temporaryMetadata, currentTemporaryMetadata)\n ) {\n throw new Error(\"Output staging file changed while it was being used\");\n }\n await handle.writeFile(bytes);\n await handle.sync();\n await assertOutputParent(output);\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(temporaryMetadata, stagedMetadata)\n ) {\n throw new Error(\"Output staging file changed before publication\");\n }\n await link(temporary, output.path);\n published = true;\n const publishedMetadata = await lstat(output.path);\n if (\n !publishedMetadata.isFile() ||\n !sameFile(temporaryMetadata, publishedMetadata)\n ) {\n throw new Error(\"Published output does not match the staged file\");\n }\n await assertOutputParent(output);\n await handle.chmod(0o600);\n await handle.sync();\n committed = true;\n } finally {\n if (!committed) {\n await handle.truncate(0).catch(() => undefined);\n await handle.sync().catch(() => undefined);\n await handle.chmod(0o000).catch(() => undefined);\n }\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n if (published && !committed && temporaryMetadata !== undefined) {\n const publishedMetadata = await lstat(output.path).catch(() => undefined);\n if (\n publishedMetadata !== undefined &&\n sameFile(temporaryMetadata, publishedMetadata)\n ) {\n await unlink(output.path).catch(() => undefined);\n }\n }\n }\n};\n\nconst decodeText = (bytes: Uint8Array): string => {\n return decodeUtf8(bytes, \"Text inputs\");\n};\n\nconst decodeUtf8 = (bytes: Uint8Array, label: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"validation_error\",\n `${label} must contain valid UTF-8`,\n {\n hint: \"Provide UTF-8 encoded text; re-encode the file and retry.\",\n cause: error,\n },\n );\n }\n};\n\nconst applyTextReplacements = (\n text: string,\n replacements: readonly NativeTextReplacement[],\n): string => {\n const parts: string[] = [];\n let cursor = 0;\n for (const replacement of replacements) {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < cursor ||\n replacement.end <= replacement.start ||\n replacement.end > text.length ||\n !isUtf16Boundary(text, replacement.start) ||\n !isUtf16Boundary(text, replacement.end)\n ) {\n throw new Error(\n \"Native caller-detection plan returned invalid replacements\",\n );\n }\n parts.push(text.slice(cursor, replacement.start), replacement.replacement);\n cursor = replacement.end;\n }\n parts.push(text.slice(cursor));\n return parts.join(\"\");\n};\n\nconst isUtf16Boundary = (text: string, offset: number): boolean => {\n if (offset <= 0 || offset >= text.length) {\n return true;\n }\n const before = text.charCodeAt(offset - 1);\n const after = text.charCodeAt(offset);\n return !(\n before >= 0xd800 &&\n before <= 0xdbff &&\n after >= 0xdc00 &&\n after <= 0xdfff\n );\n};\n\nconst assertDifferentPaths = (input: string, output: string): void => {\n if (input === output) {\n throw surfaceError(\n \"validation_error\",\n \"Input and output paths must differ\",\n \"Choose a distinct output path so the input is never overwritten.\",\n );\n }\n};\n\nconst textInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n language: z.string().min(2).max(35).optional(),\n});\n\nconst externalDetectionTextInput = textInput.extend({\n detectionBatchPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n});\n\nconst restoreInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n});\n\nconst docxRestoreInput = restoreInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst docxInput = textInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst pdfInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n ocrLanguage: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/u),\n detectionLanguage: z.string().min(2).max(35).optional(),\n dpi: z.number().int().min(72).max(600).optional().default(300),\n timeoutMs: z.number().int().min(100).max(300_000).optional().default(120_000),\n fillRgb: z\n .tuple([\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n ])\n .optional()\n .default([0, 0, 0]),\n});\n\nexport type LocalPdfProviderConfiguration = {\n pdftoppmPath?: string | undefined;\n tesseractPath?: string | undefined;\n};\n\nexport type LocalAnonymizeServiceOptions = {\n durableSessions?: DurableSessionStore | undefined;\n faults?: LocalAnonymizeServiceFaults | undefined;\n pdfProvider?: LocalPdfProviderConfiguration | undefined;\n};\n\nexport class LocalAnonymizeService {\n #activeOperations = 0;\n #closePromise: Promise<void> | undefined;\n #operationsDrained: (() => void) | undefined;\n readonly #scope: PathScope;\n #pdfOperationTail: Promise<void> = Promise.resolve();\n readonly #sessionInitializations = new Set<string>();\n readonly #sessions = new Map<string, SessionEntry>();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n readonly #durableSessions: DurableSessionStore | undefined;\n readonly #faults: LocalAnonymizeServiceFaults;\n readonly #pdfProvider: LocalPdfProviderConfiguration;\n\n constructor(scope: PathScope, options: LocalAnonymizeServiceOptions = {}) {\n if (options instanceof DurableSessionStore) {\n throw new TypeError(\n \"LocalAnonymizeService requires { durableSessions } as its second argument\",\n );\n }\n const { durableSessions, faults = {}, pdfProvider = {} } = options;\n this.#scope = scope;\n this.#durableSessions = durableSessions;\n this.#faults = faults;\n this.#pdfProvider = pdfProvider;\n }\n\n get sessionMode(): McpSessionMode {\n return this.#durableSessions === undefined\n ? MCP_SESSION_MODES.memory\n : MCP_SESSION_MODES.durableEncrypted;\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n if (this.#activeOperations > 0) {\n await new Promise<void>((resolvePromise) => {\n this.#operationsDrained = resolvePromise;\n });\n }\n this.#sessions.clear();\n await this.#durableSessions?.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async #runOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP anonymize service is closing or closed\");\n }\n this.#activeOperations += 1;\n try {\n return await operation();\n } finally {\n this.#activeOperations -= 1;\n if (this.#activeOperations === 0) {\n this.#operationsDrained?.();\n this.#operationsDrained = undefined;\n }\n }\n }\n\n async #session(sessionId: string, language?: string): Promise<SessionLease> {\n if (this.#durableSessions !== undefined && language !== undefined) {\n throw new Error(\n \"Durable sessions use the full all-language pipeline; omit language\",\n );\n }\n const existing = this.#sessions.get(sessionId);\n if (existing !== undefined) {\n if (language !== undefined && existing.language !== language) {\n throw surfaceError(\n \"validation_error\",\n \"A session cannot change language\",\n \"Use a new session id for a different language, or drop the language override.\",\n );\n }\n if (existing.status === \"initializing\") {\n throw new Error(\"The requested session is still initializing\");\n }\n if (existing.status === \"busy\") {\n throw new Error(\"The requested session is handling another operation\");\n }\n const checkpoint = existing.session.toPlaintextJson();\n existing.status = \"busy\";\n return {\n entry: existing,\n sessionId,\n rollback: { type: \"restore\", checkpoint },\n };\n }\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n if (this.#sessions.size >= SESSION_MAX_COUNT) {\n throw new Error(`MCP sessions must not exceed ${SESSION_MAX_COUNT}`);\n }\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n language === undefined ? {} : { language },\n );\n const durableSessions = this.#durableSessions;\n const stored =\n durableSessions === undefined\n ? undefined\n : await durableSessions.load(sessionId);\n let session: RedactionSession;\n if (stored === undefined) {\n session = pipeline.createRedactionSession(sessionId);\n } else {\n if (durableSessions === undefined) {\n throw surfaceError(\n \"session_unavailable\",\n \"Durable session storage is unavailable\",\n \"Start the server with --session-dir and --key-file to enable restores.\",\n );\n }\n try {\n session = durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"session_unavailable\",\n \"The requested durable session is unavailable\",\n {\n hint: \"Confirm the session id and key file match the archive that created it.\",\n cause: error,\n },\n );\n }\n }\n const entry: SessionEntry = {\n language,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"initializing\",\n };\n this.#sessions.set(sessionId, entry);\n return {\n entry,\n sessionId,\n rollback:\n stored === undefined\n ? { type: \"delete\" }\n : { type: \"restore\", checkpoint: session.toPlaintextJson() },\n };\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n\n #commitSession({ entry }: SessionLease): void {\n entry.status = \"ready\";\n }\n\n async #rollbackSession({\n entry,\n rollback,\n sessionId,\n }: SessionLease): Promise<void> {\n if (this.#sessions.get(sessionId) !== entry) {\n return;\n }\n if (rollback.type === \"delete\") {\n this.#sessions.delete(sessionId);\n await this.#durableSessions?.delete(sessionId).catch(() => undefined);\n return;\n }\n if (rollback.type === \"release\") {\n entry.status = \"ready\";\n return;\n }\n try {\n entry.session = entry.restoreSession(rollback.checkpoint);\n entry.status = \"ready\";\n await this.#persistSession(sessionId, entry.session);\n } catch {\n this.#sessions.delete(sessionId);\n }\n }\n\n async #readSession(sessionId: string): Promise<SessionLease> {\n let entry = this.#sessions.get(sessionId);\n if (entry === undefined && this.#durableSessions !== undefined) {\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n const pipeline = nativeNodeSurface.getDefaultNativePipeline({});\n const stored = await this.#durableSessions.load(sessionId);\n if (stored !== undefined) {\n try {\n const session = this.#durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n entry = {\n language: undefined,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"ready\",\n };\n this.#sessions.set(sessionId, entry);\n } catch (error) {\n throw new AnonymizeSurfaceError(\n \"session_unavailable\",\n \"The requested durable session is unavailable\",\n {\n hint: \"Confirm the session id and key file match the archive that created it.\",\n cause: error,\n },\n );\n }\n }\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n if (entry === undefined) {\n throw surfaceError(\n \"session_unavailable\",\n \"The requested session is unavailable\",\n \"Anonymize with this session id first, or start the server with a durable session store.\",\n );\n }\n if (entry.status !== \"ready\") {\n throw surfaceError(\n \"session_unavailable\",\n \"The requested in-memory session is unavailable\",\n \"Wait for the prior operation on this session to finish, then retry.\",\n true,\n );\n }\n entry.status = \"busy\";\n return { entry, sessionId, rollback: { type: \"release\" } };\n }\n\n async #persistSession(\n sessionId: string,\n session: RedactionSession,\n ): Promise<void> {\n if (this.#durableSessions === undefined) {\n return;\n }\n const archive = this.#durableSessions.seal(\n session,\n observedAtEpochSeconds(),\n );\n await this.#durableSessions.save(sessionId, archive);\n }\n\n async anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeText(input));\n }\n\n async anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() =>\n this.#serializePdfOperation(() => this.#anonymizePdf(input)),\n );\n }\n\n async #serializePdfOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#pdfOperationTail;\n let release = (): void => undefined;\n this.#pdfOperationTail = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".pdf\",\n maximumBytes: PDF_DOCUMENT_MAX_BYTES,\n label: \"PDF\",\n });\n const destination = await this.#scope.output(input.outputPath, \".pdf\");\n assertDifferentPaths(source.path, destination.path);\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n input.detectionLanguage === undefined\n ? {}\n : { language: input.detectionLanguage },\n );\n let observed: Awaited<ReturnType<typeof renderPdfWithPopplerTesseract>>;\n try {\n observed = await renderPdfWithPopplerTesseract({\n document: source.bytes,\n ocrLanguage: input.ocrLanguage,\n dpi: input.dpi,\n timeoutMs: input.timeoutMs,\n ...this.#pdfProvider,\n });\n } catch (error) {\n if (isPdfToolchainUnavailable(error)) {\n throw new AnonymizeSurfaceError(\n \"dependency_missing\",\n \"The local PDF toolchain is unavailable\",\n {\n hint: \"Install Poppler (pdftoppm) and Tesseract on PATH, or pass --pdftoppm/--tesseract.\",\n cause: error,\n },\n );\n }\n throw error;\n }\n const anonymized = anonymizePdfRaster({\n document: source.bytes,\n pipeline,\n provider: observed.provider,\n pages: observed.pages,\n fillRgb: input.fillRgb,\n });\n if (\n anonymized.certificate.structurePixelRewriteVerified !== true ||\n anonymized.certificate.piiCleanGuaranteed !== false\n ) {\n throw new Error(\"PDF raster verification did not satisfy MCP policy\");\n }\n await this.#faults.beforeOutputPublish?.();\n await destination.write(anonymized.document);\n return {\n operation: \"anonymize\",\n format: \"pdf\",\n outputCreated: true,\n pageCount: anonymized.certificate.pageCount,\n entityCount: anonymized.certificate.detectionCount,\n mappedRegionCount: anonymized.certificate.mappedRegionCount,\n structurePixelRewriteVerified: true,\n piiCleanGuaranteed: false,\n };\n }\n\n async #anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = lease.entry.session.redact_text(text);\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.redaction.redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.redaction.entityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreText(input));\n }\n\n async anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n try {\n return await this.#runOperation(() =>\n this.#anonymizeTextWithExternalDetections(input),\n );\n } catch (error) {\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n const { batch, destination, source } = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.inputRejected,\n async () => {\n const scopedSource = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const scopedBatch = await this.#scope.readInput({\n path: input.detectionBatchPath,\n extension: \".json\",\n maximumBytes: EXTERNAL_DETECTION_BATCH_MAX_BYTES,\n label: \"External detection batch\",\n });\n const scopedDestination = await this.#scope.output(\n input.outputPath,\n \".txt\",\n );\n assertDifferentPaths(scopedSource.path, scopedDestination.path);\n assertDifferentPaths(scopedBatch.path, scopedDestination.path);\n assertDifferentPaths(scopedSource.path, scopedBatch.path);\n return {\n batch: scopedBatch,\n destination: scopedDestination,\n source: scopedSource,\n };\n },\n );\n const text = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.documentRejected,\n () => decodeText(source.bytes),\n );\n const detections = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.batchRejected,\n () =>\n nativeNodeSurface.convert_external_detection_batch(\n source.bytes,\n decodeUtf8(batch.bytes, \"External detection batches\"),\n ),\n );\n const lease = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.sessionRejected,\n () => this.#session(input.sessionId, input.language),\n );\n try {\n const plan = lease.entry.session.planTextBatchWithCallerDetections({\n inputs: [{ fullText: text, detections }],\n });\n const block = plan.blocks.at(0);\n if (plan.blocks.length !== 1 || block === undefined) {\n throw new Error(\n \"Native caller-detection plan did not match the text input\",\n );\n }\n const redactedText = applyTextReplacements(text, block.replacements);\n plan.commit();\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: block.entityCount,\n externalDetectionBatchStatus: \"accepted\",\n externalDetectionCount: detections.length,\n retainedExternalDetectionCount: block.callerEntityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#readSession(input.sessionId);\n try {\n const restored = lease.entry.session.restoreText(text);\n await destination.write(restored);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeDocx(input));\n }\n\n async #anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = anonymizeDocx({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n policy: {\n coverage: {\n mode: input.allowPartialCoverage\n ? DOCX_COVERAGE_MODES.allowPartial\n : DOCX_COVERAGE_MODES.requireFull,\n },\n },\n });\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.summary.entityCount,\n blockCount: result.summary.blockCount,\n rewrittenBlockCount: result.summary.rewrittenBlockCount,\n coverageStatus: result.summary.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw mapDocxCoverageError(error);\n }\n }\n\n async restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreDocx(input));\n }\n\n async #restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#readSession(input.sessionId);\n try {\n const result = restoreDocxText({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n });\n if (result.coverage.status === \"partial\" && !input.allowPartialCoverage) {\n throw surfaceError(\n \"validation_error\",\n \"DOCX restoration has partial coverage; set allowPartialCoverage to publish it\",\n \"Re-run with allowPartialCoverage: true to accept partial restoration.\",\n );\n }\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n rewrittenBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverageStatus: result.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#inspectDocx(inputPath));\n }\n\n async #inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const extraction = extractDocxText(source.bytes);\n const unsupported = extraction.coverage.parts.some(\n (part) => part.status === \"unsupported\",\n );\n const structuralGap =\n extraction.coverage.hyperlinkTextSegmentCount > 0 ||\n extraction.coverage.revisionTextSegmentCount > 0 ||\n extraction.coverage.unsupportedAlternateContentCount > 0 ||\n extraction.coverage.unsupportedFieldInstructionCount > 0 ||\n extraction.coverage.unsupportedSymbolCount > 0;\n return {\n operation: \"inspect\",\n format: \"docx\",\n outputCreated: false,\n blockCount: extraction.blocks.length,\n coverageStatus: unsupported || structuralGap ? \"partial\" : \"full\",\n };\n }\n}\n\nconst result = (value: AuditSafeResult): CallToolResult => ({\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n});\n\nconst MCP_TOOL_NAMES = [\n \"anonymize_docx_file\",\n \"anonymize_pdf_file\",\n \"anonymize_text_file\",\n \"anonymize_text_file_with_external_detections\",\n \"capabilities\",\n \"inspect_docx_file\",\n \"restore_docx_file\",\n \"restore_text_file\",\n \"send_feedback\",\n] as const;\n\n/**\n * Map the external-detection failure taxonomy onto the shared agent-surface\n * codes. The specific failure identity is preserved in the envelope `message`;\n * `code` gives the agent the coarse, branchable class.\n */\nconst EXTERNAL_DETECTION_ENVELOPE: Record<\n ExternalDetectionFailure[\"code\"],\n { code: AnonymizeErrorCode; hint: string; retryable: boolean }\n> = {\n EXTERNAL_DETECTION_BATCH_REJECTED: {\n code: \"validation_error\",\n hint: \"Fix the ExternalDetectionBatch v1 sidecar to match the schema and retry.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_DOCUMENT_REJECTED: {\n code: \"validation_error\",\n hint: \"Align the sidecar's document metadata with the input, then retry.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_INPUT_REJECTED: {\n code: \"validation_error\",\n hint: \"Use distinct absolute paths inside a configured --root for input, sidecar, and output.\",\n retryable: false,\n },\n EXTERNAL_DETECTION_OPERATION_FAILED: {\n code: \"internal_error\",\n hint: \"Retry; if it persists, file it with the send_feedback tool.\",\n retryable: true,\n },\n EXTERNAL_DETECTION_SESSION_REJECTED: {\n code: \"session_unavailable\",\n hint: \"Use a fresh session id, or confirm the session store and key file match.\",\n retryable: false,\n },\n};\n\nconst toEnvelope = (error: unknown): AnonymizeErrorEnvelope => {\n if (error instanceof ExternalDetectionAuditError) {\n const mapped = EXTERNAL_DETECTION_ENVELOPE[error.code];\n return {\n error: {\n code: mapped.code,\n message: error.message,\n hint: mapped.hint,\n retryable: mapped.retryable,\n },\n };\n }\n return classifyToEnvelope(error);\n};\n\nconst errorResult = (error: unknown): CallToolResult => {\n const envelope = toEnvelope(error);\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: JSON.stringify(envelope) }],\n structuredContent: { ...envelope },\n };\n};\n\n/**\n * Run a tool body, rendering any thrown error as the structured envelope so\n * every tool fails the same, agent-legible way instead of surfacing a raw\n * protocol error.\n */\nconst guard = async (\n produce: () => CallToolResult | Promise<CallToolResult>,\n): Promise<CallToolResult> => {\n try {\n return await produce();\n } catch (error) {\n return errorResult(error);\n }\n};\n\nconst capabilitiesResult = async (\n service: LocalAnonymizeService,\n): Promise<CallToolResult> => {\n const value = {\n capabilityManifest: CAPABILITY_MANIFEST,\n runtimeVersion: nativeNodeSurface.native_package_version(),\n mcp: {\n externalDetectionBatch: {\n ingestion: \"path-only\" as const,\n version: EXTERNAL_DETECTION_BATCH_VERSION,\n },\n formats: [\"docx\", \"pdf\", \"text\"] as const,\n sessionMode: service.sessionMode,\n tools: MCP_TOOL_NAMES,\n transport: \"stdio\" as const,\n },\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nconst feedbackInput = z.object({\n kind: z.enum(FEEDBACK_KINDS),\n title: z.string().min(1).max(MAX_FEEDBACK_TITLE_CHARS),\n body: z.string().min(1).max(MAX_FEEDBACK_BODY_CHARS),\n});\n\nconst feedbackResult = (\n input: z.infer<typeof feedbackInput>,\n): CallToolResult => {\n const submission = buildFeedbackSubmission(input);\n const value = {\n channel: \"github\" as const,\n redactions: submission.redactions,\n title: submission.title,\n sanitizedBody: submission.sanitizedBody,\n issueUrl: submission.issueUrl,\n ghCommand: submission.ghCommand,\n note: \"Nothing was sent. Review the sanitized content, then open the URL (or run the gh command) to submit the issue under your own GitHub account.\",\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n };\n};\n\n/**\n * Server `instructions` handed to MCP clients at connect time. Kept terse and\n * factual; the char budget is asserted in `instructions.test.ts` to guard drift.\n */\nexport const MCP_INSTRUCTIONS_MAX_CHARS = 1200;\nexport const MCP_INSTRUCTIONS = `stella-anonymize redacts PII in local text, DOCX, and PDF files. Every tool reads and writes local paths only, inside the directories passed as --root; it never returns document text or session mappings, and it never overwrites, so outputs must be new paths.\n\nErrors: a failed tool returns a single text content of {\"error\":{\"code\",\"message\",\"hint\",\"retryable\"}} with isError set. Branch on code (validation_error, path_not_allowed, not_found, unsupported_format, output_exists, session_unavailable, dependency_missing, internal_error); hint states the next step. Nothing here is destructive: there is no delete and existing files are never overwritten, so no confirm step is needed.\n\nSessions: reversible replace mode uses a session; a restore needs the same session id, plus a durable store (server started with --session-dir and --key-file) to survive a restart.\n\nHit a bug or a gap? Use send_feedback: it sanitizes your text locally and returns a prefilled GitHub issue URL you open and submit yourself. It sends nothing over the network.`;\n\nexport const createAnonymizeMcpServer = (\n service: LocalAnonymizeService,\n): McpServer => {\n // Synchronous by contract (public factory). The server version comes from the\n // package manifest, not a native call, so construction touches no binding.\n // Operations that need the native runtime load its platform N-API package.\n const server = new McpServer(\n {\n name: \"stella-anonymize-local\",\n version: pkg.version,\n },\n {\n instructions: MCP_INSTRUCTIONS,\n },\n );\n server.registerTool(\n \"capabilities\",\n {\n description:\n \"Return the public runtime capability manifest and MCP surface metadata.\",\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async () => guard(() => capabilitiesResult(service)),\n );\n server.registerTool(\n \"anonymize_text_file\",\n {\n description: \"Anonymize a local UTF-8 text file into a new local file.\",\n inputSchema: textInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizeText(input))),\n );\n server.registerTool(\n \"restore_text_file\",\n {\n description: \"Restore a text file using the configured session store.\",\n inputSchema: restoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.restoreText(input))),\n );\n server.registerTool(\n \"anonymize_text_file_with_external_detections\",\n {\n description:\n \"Anonymize a local UTF-8 text file with a provider-neutral ExternalDetectionBatch v1 JSON sidecar into a new local file.\",\n inputSchema: externalDetectionTextInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () =>\n result(await service.anonymizeTextWithExternalDetections(input)),\n ),\n );\n server.registerTool(\n \"anonymize_docx_file\",\n {\n description:\n \"Structure-preservingly anonymize a local DOCX into a new local DOCX.\",\n inputSchema: docxInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizeDocx(input))),\n );\n server.registerTool(\n \"anonymize_pdf_file\",\n {\n description:\n \"Destructively raster-anonymize a local PDF into a fresh image-only PDF. Returns aggregate verification only; it does not claim perfect OCR or detector recall.\",\n inputSchema: pdfInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.anonymizePdf(input))),\n );\n server.registerTool(\n \"restore_docx_file\",\n {\n description: \"Restore a DOCX using the configured session store.\",\n inputSchema: docxRestoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) =>\n guard(async () => result(await service.restoreDocx(input))),\n );\n server.registerTool(\n \"inspect_docx_file\",\n {\n description:\n \"Return only aggregate DOCX coverage and block counts; never document text.\",\n inputSchema: z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n }),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async ({ inputPath }) =>\n guard(async () => result(await service.inspectDocx(inputPath))),\n );\n server.registerTool(\n \"send_feedback\",\n {\n description:\n \"File a bug, feature request, or docs issue with the stella-anonymize maintainers. Sanitizes the title and body locally (emails, ids, secrets, URLs, IPs are redacted) and returns a prefilled GitHub new-issue URL and a gh command that you open and submit under your own account. It sends nothing over the network and publishes nothing on its own. Never include document text, client names, ids, or secrets; describe the problem, steps, and expected vs actual result.\",\n inputSchema: feedbackInput,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async (input) => guard(() => feedbackResult(input)),\n );\n return server;\n};\n"],"mappings":";;;;;;;;;;;;;;;ACcA,MAAa,4BAA4B;AACzC,MAAa,4BAA4B;AACzC,MAAa,4BAA4B;AACzC,MAAa,kCAAkC,MAAM,OAAO;AAE5D,MAAM,eAAe;AACrB,MAAMA,eAAa;AACnB,MAAM,eACJ;AACF,MAAMC,qBAAmB,KAAK;AAC9B,MAAM,iBAAiB;AAEvB,MAAa,+BAA+B;CAC1C,sBAAsB;CACtB,cAAc;CACd,oBAAoB;CACpB,oBAAoB;AACtB;AAmBA,MAAMC,cAAY,MAAoB,UACpC,KAAK,QAAQ,MAAM,OAAO,KAAK,QAAQ,MAAM;AAE/C,MAAM,eAAe,KAAa,UAAwB;CACxD,IAAI,OAAO,QAAQ,WAAW,cAAc,QAAQ,QAAQ,OAAO,GACjE,MAAM,IAAI,MAAM,GAAG,MAAM,mCAAmC;AAEhE;AAEA,MAAM,qBAAqB,MAAc,UAAwB;CAC/D,KAAK,OAAO,QAAW,GACrB,MAAM,IAAI,MAAM,GAAG,MAAM,2CAA2C;AAExE;AAEA,MAAM,qCAA2C;CAC/C,IACG,QAAQ,aAAa,YAAY,QAAQ,aAAa,WACvD,OAAO,QAAQ,WAAW,cAC1B,OAAOC,UAAY,eAAe,YAClCA,UAAY,eAAe,KAC3B,OAAOA,UAAY,gBAAgB,YACnCA,UAAY,gBAAgB,GAE5B,MAAM,IAAI,MACR,wIACF;AAEJ;AAEA,MAAM,uBAAuB,OAC3B,kBACwB;CAExB,MAAM,SAAS,MAAM,KADR,KAAK,eAAe,cAE5B,GACHA,UAAY,SACVA,UAAY,UACZA,UAAY,aACZA,UAAY,YACd,GACF;CACA,IAAI;EACF,MAAM,WAAW,MAAM,OAAO,KAAK;EACnC,IAAI,CAAC,SAAS,OAAO,GACnB,MAAM,IAAI,MAAM,yCAAyC;EAE3D,YAAY,SAAS,KAAK,kBAAkB;EAC5C,kBAAkB,SAAS,MAAM,kBAAkB;EAInD,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,IAAI,CAAC,QAAQ,OAAO,EAAE,GACpB,MAAM,IAAI,MACR,2DACF;EAEF,OAAO;CACT,SAAS,OAAO;EACd,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;EAC1C,MAAM;CACR;AACF;AAEA,MAAM,wBAAwB,OAC5B,MACA,UACoB;CACpB,IAAI,CAAC,WAAW,IAAI,GAClB,MAAM,IAAI,MAAM,GAAG,MAAM,0BAA0B;CAErD,MAAM,aAAa,QAAQ,IAAI;CAC/B,MAAM,YAAY,MAAM,SAAS,UAAU;CAC3C,IAAI,cAAc,YAChB,MAAM,IAAI,MAAM,GAAG,MAAM,iCAAiC;CAE5D,OAAO;AACT;AAEA,MAAMC,sBAAoB,OACxB,QACA,iBACwB;CACxB,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,SAAS;EACP,MAAM,YAAY,eAAe;EACjC,MAAM,QAAQ,OAAO,YAAY,KAAK,IAAIH,oBAAkB,YAAY,CAAC,CAAC;EAC1E,MAAM,EAAE,cAAc,MAAM,OAAO,KAAK,OAAO,GAAG,MAAM,YAAY,IAAI;EACxE,IAAI,cAAc,GAChB,OAAO,OAAO,OAAO,QAAQ,KAAK;EAEpC,SAAS;EACT,IAAI,QAAQ,cACV,MAAM,IAAI,MAAM,kDAAkD;EAEpE,OAAO,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;CAC1C;AACF;AAEA,MAAM,UAAU,OAAO,SAAsC;CAC3D,MAAM,YAAY,MAAM,sBAAsB,MAAM,sBAAsB;CAC1E,MAAM,iBAAiB,MAAM,MAAM,SAAS;CAC5C,IAAI,CAAC,eAAe,OAAO,KAAK,eAAe,eAAe,GAC5D,MAAM,IAAI,MAAM,6CAA6C;CAE/D,YAAY,eAAe,KAAK,sBAAsB;CACtD,kBAAkB,eAAe,MAAM,sBAAsB;CAC7D,IAAI,eAAe,SAAA,IACjB,MAAM,IAAI,MACR,wDACF;CAEF,MAAM,SAAS,MAAM,KACnB,WACAE,UAAY,WAAWA,UAAY,aAAaA,UAAY,UAC9D;CACA,IAAI;CACJ,IAAI;EACF,MAAM,iBAAiB,MAAM,OAAO,KAAK;EACzC,IACE,CAAC,eAAe,OAAO,KACvB,CAACD,WAAS,gBAAgB,cAAc,KACxC,eAAe,SAAA,IAEf,MAAM,IAAI,MAAM,qDAAqD;EAEvE,YAAY,OAAO,MAAM,EAA6B;EACtD,IAAI,YAAY;EAChB,SAAS;GACP,MAAM,EAAE,cAAc,MAAM,OAAO,KACjC,WACA,WACA,UAAU,aAAa,WACvB,IACF;GACA,IAAI,cAAc,KAAK,YAAY,cAAc,UAAU,YAAY;IACrE,aAAa;IACb;GACF;GACA,aAAa;EACf;EACA,IAAI,cAAA,IAAyC;GAC3C,UAAU,KAAK,CAAC;GAChB,MAAM,IAAI,MACR,wDACF;EACF;EACA,MAAM,kBAAkB,MAAM,KAAK,SAAS;EAC5C,IAAI,CAACA,WAAS,gBAAgB,eAAe,GAAG;GAC9C,UAAU,KAAK,CAAC;GAChB,MAAM,IAAI,MAAM,gDAAgD;EAClE;EACA,MAAM,sBAAM,IAAI,WAAA,EAAoC;EACpD,IAAI,IAAI,UAAU,SAAS,GAAA,EAA4B,CAAC;EACxD,UAAU,KAAK,CAAC;EAChB,OAAO;CACT,UAAU;EACR,WAAW,KAAK,CAAC;EACjB,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,MAAM,eAAe,cACnB,GAAG,WAAW,QAAQ,CAAC,CAAC,OAAO,WAAW,MAAM,CAAC,CAAC,OAAO,KAAK,EAAE;AAElE,MAAM,mBAAmB,cAA4B;CACnD,IAAI,CAACF,aAAW,KAAK,SAAS,GAC5B,MAAM,IAAI,MAAM,2BAA2B;AAE/C;AAmCA,IAAa,sBAAb,MAAa,oBAAoB;CAC/B;CACA;CACA;CAGA;CACA;CACA,gBAA+B,QAAQ,QAAQ;CAC/C,SAAwC;CAExC,YACE,WACA,KACA,YACA,eACA;EACA,KAAKK,aAAa;EAClB,KAAKE,OAAO;EACZ,KAAKC,cAAc;EACnB,KAAKF,iBAAiB;CACxB;CAEA,aAAa,OAAO,EAClB,SACA,kBACA,iBAC2D;EAC3D,6BAA6B;EAC7B,MAAM,gBAAgB,MAAM,sBAC1B,kBACA,uBACF;EACA,MAAM,WAAW,MAAM,MAAM,aAAa;EAC1C,IAAI,CAAC,SAAS,YAAY,KAAK,SAAS,eAAe,GACrD,MAAM,IAAI,MAAM,2CAA2C;EAE7D,YAAY,SAAS,KAAK,uBAAuB;EACjD,kBAAkB,SAAS,MAAM,uBAAuB;EACxD,MAAM,MAAM,MAAM,QAAQ,OAAO;EACjC,IAAI;EACJ,IAAI;GACF,aAAa,MAAM,qBAAqB,aAAa;GACrD,MAAM,QAAQ,IAAI,oBAChB;IAAE,KAAK,SAAS;IAAK,KAAK,SAAS;IAAK,MAAM;GAAc,GAC5D,KACA,YACA,aACF;GACA,MAAM,MAAMG,mBAAmB,EAAE,oBAAoB,KAAK,CAAC;GAC3D,MAAM,MAAMC,eAAe;GAC3B,OAAO;EACT,SAAS,OAAO;GACd,IAAI,KAAK,CAAC;GACV,MAAM,YAAY,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;GAC/C,MAAM;EACR;CACF;CAEA,KACE,SACA,wBACY;EACZ,KAAKC,YAAY;EACjB,OAAO,QAAQ,qBAAqB,KAAKJ,MAAM,sBAAsB;CACvE;CAEA,QAAiB,EACf,SACA,mBACA,wBACA,YACgD;EAChD,KAAKI,YAAY;EACjB,OAAO,SAAS,iCAAiC;GAC/C;GACA;GACA,KAAK,KAAKJ;GACV;EACF,CAAC;CACH;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAKK,kBAAkB,KAAA,GACzB,OAAO,KAAKA;EAEd,KAAKC,SAAS;EACd,KAAKD,iBAAiB,YAAY;GAChC,MAAM,KAAKE;GACX,KAAKP,KAAK,KAAK,CAAC;GAChB,MAAM,KAAKC,YAAY,MAAM;GAC7B,KAAKK,SAAS;EAChB,EAAA,CAAG;EACH,OAAO,KAAKD;CACd;CAEA,MAAM,KAAK,WAA8D;EACvE,KAAKD,YAAY;EACjB,OAAO,KAAKI,oBAAoB,KAAKC,MAAM,SAAS,CAAC;CACvD;CAEA,MAAMA,MAAM,WAA8D;EACxE,gBAAgB,SAAS;EACzB,MAAM,KAAKP,mBAAmB,EAAE,oBAAoB,MAAM,CAAC;EAC3D,MAAM,OAAO,KAAKQ,MAAM,SAAS;EACjC,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,KACb,MACAd,UAAY,WAAWA,UAAY,aAAaA,UAAY,UAC9D;EACF,SAAS,OAAO;GACd,IAAK,MAAgC,SAAS,UAC5C;GAEF,MAAM,IAAI,MAAM,iDAAiD,EAC/D,OAAO,MACT,CAAC;EACH;EACA,IAAI;GACF,MAAM,WAAW,MAAM,OAAO,KAAK;GACnC,IAAI,CAAC,SAAS,OAAO,GACnB,MAAM,IAAI,MAAM,kDAAkD;GAEpE,YAAY,SAAS,KAAK,2BAA2B;GACrD,kBAAkB,SAAS,MAAM,2BAA2B;GAC5D,IAAI,SAAS,OAAA,UACX,MAAM,IAAI,MAAM,kDAAkD;GAEpE,MAAM,QAAQ,MAAMC,oBAAkB,QAAQ,yBAAyB;GACvE,MAAM,KAAKc,iBAAiB;GAC5B,MAAM,kBAAkB,MAAM,MAAM,IAAI;GACxC,IAAI,CAAC,gBAAgB,OAAO,KAAK,CAAChB,WAAS,UAAU,eAAe,GAClE,MAAM,IAAI,MAAM,qDAAqD;GAEvE,OAAO,EAAE,MAAM;EACjB,UAAU;GACR,MAAM,OAAO,MAAM;EACrB;CACF;CAEA,MAAM,KAAK,WAAmB,SAAoC;EAChE,KAAKS,YAAY;EACjB,OAAO,KAAKI,oBAAoB,KAAKI,MAAM,WAAW,OAAO,CAAC;CAChE;CAEA,MAAMA,MAAM,WAAmB,SAAoC;EACjE,gBAAgB,SAAS;EACzB,IAAI,QAAQ,aAAA,UACV,MAAM,IAAI,MAAM,kDAAkD;EAEpE,MAAM,YAAY,MAAM,KAAKV,mBAAmB,EAC9C,oBAAoB,MACtB,CAAC;EACD,MAAM,KAAKS,iBAAiB;EAC5B,MAAM,cAAc,KAAKD,MAAM,SAAS;EACxC,MAAM,YAAY,GAAG,YAAY,OAAO,WAAW;EACnD,MAAM,SAAS,MAAM,KACnB,WACAd,UAAY,WACVA,UAAY,UACZA,UAAY,SACZA,UAAY,YACd,GACF;EACA,IAAI;GACF,MAAM,iBAAiB,MAAM,OAAO,KAAK;GACzC,IAAI,CAAC,eAAe,OAAO,GACzB,MAAM,IAAI,MAAM,sDAAsD;GAExE,MAAM,KAAKiB,QAAQ,6BAA6B,kBAAkB;GAClE,MAAM,OAAO,UAAU,OAAO;GAC9B,MAAM,KAAKA,QAAQ,6BAA6B,kBAAkB;GAClE,MAAM,OAAO,KAAK;GAClB,MAAM,iBAAiB,MAAM,MAAM,SAAS;GAC5C,IACE,CAAC,eAAe,OAAO,KACvB,CAAClB,WAAS,gBAAgB,cAAc,GAExC,MAAM,IAAI,MACR,2DACF;GAEF,MAAM,KAAKgB,iBAAiB;GAC5B,MAAM,WAAW,MAAM,MAAM,WAAW,CAAC,CAAC,OAAO,UAAmB;IAClE,IAAK,MAAgC,SAAS,UAC5C;IAEF,MAAM;GACR,CAAC;GACD,IACE,aAAa,KAAA,MACZ,CAAC,SAAS,OAAO,KAAK,SAAS,eAAe,IAE/C,MAAM,IAAI,MAAM,sDAAsD;GAExE,MAAM,YACJ,UAAU,gBAAgB,aAAa,KAAA,IAAY,IAAI;GACzD,MAAM,iBACJ,UAAU,cAAc,UAAU,QAAQ,KAAK,QAAQ;GACzD,IAAI,YAAA,KACF,MAAM,IAAI,MACR,0CACF;GAEF,IAAI,iBAAA,WACF,MAAM,IAAI,MAAM,sDAAsD;GAExE,MAAM,KAAKE,QAAQ,6BAA6B,YAAY;GAC5D,MAAM,OAAO,WAAW,WAAW;GACnC,MAAM,KAAKF,iBAAiB;GAC5B,MAAM,YAAY,MAAM,MAAM,WAAW;GACzC,IAAI,CAAC,UAAU,OAAO,KAAK,CAAChB,WAAS,gBAAgB,SAAS,GAC5D,MAAM,IAAI,MAAM,sDAAsD;GAExE,MAAM,KAAKQ,eAAe;EAC5B,UAAU;GACR,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;GAC1C,MAAM,OAAO,SAAS,CAAC,CAAC,YAAY,KAAA,CAAS;EAC/C;CACF;CAEA,MAAM,OAAO,WAAkC;EAC7C,KAAKC,YAAY;EACjB,OAAO,KAAKI,oBAAoB,KAAKM,QAAQ,SAAS,CAAC;CACzD;CAEA,MAAMA,QAAQ,WAAkC;EAC9C,gBAAgB,SAAS;EACzB,MAAM,KAAKH,iBAAiB;EAC5B,MAAM,OAAO,KAAKD,MAAM,SAAS;EACjC,MAAM,WAAW,MAAM,MAAM,IAAI,CAAC,CAAC,OAAO,UAAmB;GAC3D,IAAK,MAAgC,SAAS,UAC5C;GAEF,MAAM;EACR,CAAC;EACD,IAAI,aAAa,KAAA,GACf;EAEF,IAAI,CAAC,SAAS,OAAO,KAAK,SAAS,eAAe,GAChD,MAAM,IAAI,MAAM,sDAAsD;EAExE,MAAM,OAAO,IAAI;EACjB,MAAM,KAAKP,eAAe;EAC1B,MAAM,KAAKQ,iBAAiB;CAC9B;CAEA,MAAM,WAA2B;EAC/B,OAAO,KAAK,KAAKb,WAAW,MAAM,YAAY,SAAS,CAAC;CAC1D;CAEA,cAAoB;EAClB,IAAI,KAAKQ,WAAW,QAClB,MAAM,IAAI,MAAM,gDAAgD;CAEpE;CAEA,MAAME,cACJ,WACiB;EACjB,MAAM,WAAW,KAAKD;EACtB,IAAI,gBAAsB,KAAA;EAC1B,MAAM,UAAU,IAAI,SAAe,mBAAmB;GACpD,UAAU;EACZ,CAAC;EACD,KAAKA,gBAAgB,SAAS,WAAW,OAAO;EAChD,MAAM;EACN,IAAI;GACF,OAAO,MAAM,UAAU;EACzB,UAAU;GACR,QAAQ;EACV;CACF;CAEA,MAAMI,mBAAkC;EACtC,MAAM,YAAY,MAAM,SAAS,KAAKb,WAAW,IAAI;EACrD,MAAM,WAAW,MAAM,MAAM,KAAKA,WAAW,IAAI;EACjD,IACE,cAAc,KAAKA,WAAW,QAC9B,CAAC,SAAS,YAAY,KACtB,SAAS,eAAe,KACxB,CAACH,WAAS,KAAKG,YAAY,QAAQ,GAEnC,MAAM,IAAI,MAAM,uDAAuD;EAEzE,YAAY,SAAS,KAAK,uBAAuB;EACjD,kBAAkB,SAAS,MAAM,uBAAuB;CAC1D;CAEA,MAAMK,iBAAgC;EACpC,MAAM,KAAKU,QAAQ,6BAA6B,oBAAoB;EACpE,MAAM,SAAS,MAAM,KACnB,KAAKf,WAAW,MAChBF,UAAY,WAAWA,UAAY,cAAcA,UAAY,UAC/D;EACA,IAAI;GACF,MAAM,WAAW,MAAM,OAAO,KAAK;GACnC,IAAI,CAAC,SAAS,YAAY,KAAK,CAACD,WAAS,KAAKG,YAAY,QAAQ,GAChE,MAAM,IAAI,MAAM,sDAAsD;GAExE,MAAM,OAAO,KAAK;EACpB,UAAU;GACR,MAAM,OAAO,MAAM;EACrB;CACF;CAEA,MAAMe,QAAQ,OAAgD;EAC5D,MAAM,KAAKd,iBAAiB,KAAK;CACnC;CAEA,MAAMG,mBAAmB,EACvB,sBAG4B;EAC5B,MAAM,KAAKS,iBAAiB;EAC5B,MAAM,UAAU,MAAM,QAAQ,KAAKb,WAAW,MAAM,EAClD,eAAe,KACjB,CAAC;EACD,IAAI,eAAe;EACnB,IAAI,aAAa;EACjB,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,KAAK,KAAKA,WAAW,MAAM,MAAM,IAAI;GAClD,IAAI,MAAM,SAAS,gBAAgB;IACjC,MAAM,WAAW,MAAM,MAAM,IAAI;IACjC,IAAI,CAAC,SAAS,OAAO,KAAK,SAAS,eAAe,GAChD,MAAM,IAAI,MAAM,oDAAoD;IAEtE,YAAY,SAAS,KAAK,kBAAkB;IAC5C,kBAAkB,SAAS,MAAM,kBAAkB;IACnD;GACF;GACA,IAAI,aAAa,KAAK,MAAM,IAAI,GAAG;IACjC,IAAI,CAAC,oBACH,MAAM,IAAI,MAAM,kDAAkD;IAEpE,MAAM,WAAW,MAAM,MAAM,IAAI;IACjC,IAAI,CAAC,SAAS,OAAO,KAAK,SAAS,eAAe,GAChD,MAAM,IAAI,MACR,uDACF;IAEF,MAAM,OAAO,IAAI;IACjB,MAAM,KAAKK,eAAe;IAC1B;GACF;GACA,IACE,CAAC,aAAa,KAAK,MAAM,IAAI,KAC7B,CAAC,MAAM,OAAO,KACd,MAAM,eAAe,GAErB,MAAM,IAAI,MAAM,qDAAqD;GAEvE,MAAM,WAAW,MAAM,MAAM,IAAI;GACjC,IAAI,CAAC,SAAS,OAAO,KAAK,SAAS,eAAe,GAChD,MAAM,IAAI,MACR,uDACF;GAEF,YAAY,SAAS,KAAK,2BAA2B;GACrD,kBAAkB,SAAS,MAAM,2BAA2B;GAC5D,IAAI,SAAS,OAAA,UACX,MAAM,IAAI,MAAM,kDAAkD;GAEpE,gBAAgB;GAChB,cAAc,SAAS;GACvB,IAAI,eAAA,KACF,MAAM,IAAI,MACR,0CACF;GAEF,IAAI,aAAA,WACF,MAAM,IAAI,MAAM,sDAAsD;EAE1E;EACA,MAAM,KAAKQ,iBAAiB;EAC5B,OAAO;GAAE;GAAc;EAAW;CACpC;AACF;;;AChkBA,MAAM,iBAAiB,KAAK,OAAO;AACnC,MAAM,qCAAqC,KAAK,OAAO;AACvD,MAAM,mCAAmC;AACzC,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,aAAa;AACnB,MAAM,mBAAmB,KAAK;;AAG9B,MAAM,gBACJ,MACA,SACA,MACA,YAAY,UAEZ,IAAI,sBAAsB,MAAM,SAAS;CAAE;CAAM;AAAU,CAAC;AAE9D,MAAM,iBAAiB,UACrB,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,QACpD,MAAgC,OACjC,KAAA;;;;;;;AAQN,MAAM,6BAA6B,UACjC,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,2BACtC,MAA6B,SAAS;;;;;;;;AASzC,MAAM,wBAAwB,UAA4B;CACxD,IACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,4BACtC,MAA6B,SAAS,uBAIvC,OAAO,IAAI,sBACT,oBACC,MAAgB,SACjB;EACE,MAAM;EACN,OAAO;CACT,CACF;CAEF,OAAO;AACT;AAEA,MAAM,+BAAuC;CAC3C,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;CAC5C,IAAI,UAAU,KAAK,UAAU,YAC3B,MAAM,IAAI,MAAM,yDAAyD;CAE3E,OAAO;AACT;AAEA,MAAa,oBAAoB;CAC/B,kBAAkB;CAClB,QAAQ;AACV;AAwBA,MAAM,8BAA8B;CAClC,eAAe;EACb,MAAM;EACN,SAAS;CACX;CACA,kBAAkB;EAChB,MAAM;EACN,SAAS;CACX;CACA,eAAe;EACb,MAAM;EACN,SAAS;CACX;CACA,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;CACA,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;AACF;AAKA,IAAM,8BAAN,cAA0C,MAAM;CAC9C;CAEA,YAAY,SAAmC;EAC7C,MAAM,QAAQ,OAAO;EACrB,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;CACtB;AACF;AAEA,MAAM,4BACJ,OACA,YAEA,iBAAiB,8BACb,QACA,IAAI,4BAA4B,OAAO;AAE7C,MAAM,wBAAwB,OAC5B,SACA,cACoB;CACpB,IAAI;EACF,OAAO,MAAM,UAAU;CACzB,SAAS,OAAO;EACd,MAAM,yBAAyB,OAAO,OAAO;CAC/C;AACF;AAMA,MAAM,UAAU,MAAc,WAA4B;CACxD,MAAM,OAAO,SAAS,MAAM,MAAM;CAClC,OACE,SAAS,MACR,SAAS,QAAQ,CAAC,KAAK,WAAW,KAAK,KAAK,KAAK,CAAC,WAAW,IAAI;AAEtE;AAgBA,MAAM,oBAAoB,OACxB,QACA,cACA,UACwB;CACxB,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,SAAS;EACP,MAAM,QAAQ,OAAO,YACnB,KAAK,IAAI,kBAAkB,eAAe,QAAQ,CAAC,CACrD;EACA,MAAM,EAAE,cAAc,MAAM,OAAO,KAAK,OAAO,GAAG,MAAM,YAAY,IAAI;EACxE,IAAI,cAAc,GAChB,OAAO,OAAO,OAAO,QAAQ,KAAK;EAEpC,SAAS;EACT,IAAI,QAAQ,cACV,MAAM,aACJ,oBACA,GAAG,MAAM,0BAA0B,aAAa,SAChD,2DACF;EAEF,OAAO,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;CAC1C;AACF;AAUA,IAAM,eAAN,MAAmB;CACjB;CACA;CAEA,YAAY,MAAc,QAA2B;EACnD,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;CAEA,MAAM,MAAM,OAA2C;EACrD,MAAM,UAAU,MAAM,KAAK;CAC7B;AACF;AAEA,IAAa,YAAb,MAAa,UAAU;CACrB;CAEA,YAAoB,OAA0B;EAC5C,KAAKI,SAAS;CAChB;CAEA,aAAa,OAAO,OAA8C;EAChE,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,2CAA2C;EAE7D,MAAM,YAAY,MAAM,QAAQ,IAC9B,MAAM,IAAI,OAAO,SAAS;GACxB,IAAI,CAAC,WAAW,IAAI,GAClB,MAAM,IAAI,MAAM,kCAAkC;GAEpD,MAAM,OAAO,MAAM,SAAS,IAAI;GAEhC,IAAI,EAAC,MADkB,KAAK,IAAI,EAAA,CAClB,YAAY,GACxB,MAAM,IAAI,MAAM,oCAAoC;GAEtD,OAAO;EACT,CAAC,CACH;EACA,OAAO,IAAI,UAAU,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,CAAC;CAC9C;CAEA,MAAM,UAAU,EACd,MACA,WACA,cACA,SACyC;EACzC,IAAI,CAAC,WAAW,IAAI,GAClB,MAAM,aACJ,oBACA,6BAA6B,UAAU,QACvC,uEACF;EAEF,IAAI,QAAQ,IAAI,CAAC,CAAC,YAAY,MAAM,WAClC,MAAM,aACJ,sBACA,6BAA6B,UAAU,QACvC,2BAA2B,UAAU,YACvC;EAEF,MAAM,qBAAqB,MAAM,KAAK,eAAe,IAAI;EACzD,IAAI,CAAC,KAAKA,OAAO,MAAM,SAAS,OAAO,MAAM,kBAAkB,CAAC,GAC9D,MAAM,aACJ,oBACA,yCACA,6EACF;EAGF,IAAI,EAAC,MADoC,MAAM,IAAI,EAAA,CACnB,OAAO,GACrC,MAAM,aACJ,oBACA,gCACA,qEACF;EAEF,MAAM,SAAS,MAAM,KACnB,MACAC,UAAY,WAAWA,UAAY,aAAaA,UAAY,UAC9D;EACA,IAAI;GACF,MAAM,iBAAiB,MAAM,OAAO,KAAK;GACzC,IAAI,CAAC,eAAe,OAAO,GACzB,MAAM,aACJ,oBACA,gCACA,qEACF;GAEF,MAAM,YAAY,MAAM,SAAS,IAAI;GACrC,IAAI,CAAC,KAAKD,OAAO,MAAM,SAAS,OAAO,MAAM,SAAS,CAAC,GACrD,MAAM,aACJ,oBACA,yCACA,6EACF;GAEF,MAAM,kBAAkB,MAAM,KAAK,SAAS;GAC5C,IACE,gBAAgB,QAAQ,eAAe,OACvC,gBAAgB,QAAQ,eAAe,KAEvC,MAAM,IAAI,MAAM,4CAA4C;GAE9D,IAAI,eAAe,OAAO,cACxB,MAAM,aACJ,oBACA,GAAG,MAAM,0BAA0B,aAAa,SAChD,2DACF;GAGF,OAAO;IAAE,OAAA,MADW,kBAAkB,QAAQ,cAAc,KAAK;IACjD,MAAM;GAAU;EAClC,UAAU;GACR,MAAM,OAAO,MAAM;EACrB;CACF;;;;;CAMA,MAAc,eAAe,MAA+B;EAC1D,IAAI;GACF,OAAO,MAAM,SAAS,IAAI;EAC5B,SAAS,OAAO;GACd,IAAI,cAAc,KAAK,MAAM,UAC3B,MAAM,aACJ,aACA,6BACA,6EACF;GAEF,MAAM;EACR;CACF;CAEA,MAAM,OACJ,MACA,WACuB;EACvB,IAAI,CAAC,WAAW,IAAI,GAClB,MAAM,aACJ,oBACA,8BAA8B,UAAU,QACxC,0DACF;EAEF,IAAI,QAAQ,IAAI,CAAC,CAAC,YAAY,MAAM,WAClC,MAAM,aACJ,sBACA,8BAA8B,UAAU,QACxC,4BAA4B,UAAU,YACxC;EAEF,MAAM,aAAa,QAAQ,IAAI;EAC/B,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,SAAS,QAAQ,UAAU,CAAC;EAC7C,SAAS,OAAO;GACd,IAAI,cAAc,KAAK,MAAM,UAC3B,MAAM,aACJ,aACA,mCACA,gEACF;GAEF,MAAM;EACR;EACA,IAAI,CAAC,KAAKA,OAAO,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC,GAClD,MAAM,aACJ,oBACA,0CACA,uDACF;EAEF,MAAM,iBAAiB,MAAM,KAAK,MAAM;EACxC,IAAI;GACF,MAAM,MAAM,UAAU;EACxB,SAAS,OAAO;GACd,IAAK,MAAgC,SAAS,UAC5C,OAAO,IAAI,aAAa,YAAY;IAClC,KAAK,eAAe;IACpB,KAAK,eAAe;IACpB,MAAM;GACR,CAAC;GAEH,MAAM,IAAI,MAAM,6CAA6C,EAC3D,OAAO,MACT,CAAC;EACH;EACA,MAAM,aACJ,iBACA,uDACA,sEACF;CACF;AACF;AAoDA,MAAM,oBAAoB;AAE1B,MAAM,qBAAqB,OAAO,EAChC,QACA,WACiC;CACjC,MAAM,YAAY,MAAM,SAAS,QAAQ,IAAI,CAAC;CAC9C,MAAM,WAAW,MAAM,KAAK,SAAS;CACrC,IACE,cAAc,OAAO,QACrB,SAAS,QAAQ,OAAO,OACxB,SAAS,QAAQ,OAAO,KAExB,MAAM,IAAI,MAAM,kDAAkD;AAEtE;AAEA,MAAM,YAAY,MAAoB,UACpC,KAAK,QAAQ,MAAM,OAAO,KAAK,QAAQ,MAAM;AAE/C,MAAM,YAAY,OAChB,QACA,UACkB;CAClB,MAAM,mBAAmB,MAAM;CAC/B,MAAM,YAAY,GAAG,OAAO,KAAK,UAAU,WAAW,EAAE;CACxD,IAAI,YAAY;CAChB,IAAI,YAAY;CAChB,IAAI;CACJ,MAAM,SAAS,MAAM,KACnB,WACAC,UAAY,WACVA,UAAY,UACZA,UAAY,SACZA,UAAY,YACd,GACF;CACA,IAAI;EACF,oBAAoB,MAAM,OAAO,KAAK;EACtC,MAAM,qBAAqB,MAAM,SAAS,SAAS;EACnD,MAAM,2BAA2B,MAAM,MAAM,SAAS;EACtD,IACE,QAAQ,kBAAkB,MAAM,OAAO,OAAO,QAC9C,CAAC,yBAAyB,OAAO,KACjC,CAAC,SAAS,mBAAmB,wBAAwB,GAErD,MAAM,IAAI,MAAM,qDAAqD;EAEvE,MAAM,OAAO,UAAU,KAAK;EAC5B,MAAM,OAAO,KAAK;EAClB,MAAM,mBAAmB,MAAM;EAC/B,MAAM,iBAAiB,MAAM,MAAM,SAAS;EAC5C,IACE,CAAC,eAAe,OAAO,KACvB,CAAC,SAAS,mBAAmB,cAAc,GAE3C,MAAM,IAAI,MAAM,gDAAgD;EAElE,MAAM,KAAK,WAAW,OAAO,IAAI;EACjC,YAAY;EACZ,MAAM,oBAAoB,MAAM,MAAM,OAAO,IAAI;EACjD,IACE,CAAC,kBAAkB,OAAO,KAC1B,CAAC,SAAS,mBAAmB,iBAAiB,GAE9C,MAAM,IAAI,MAAM,iDAAiD;EAEnE,MAAM,mBAAmB,MAAM;EAC/B,MAAM,OAAO,MAAM,GAAK;EACxB,MAAM,OAAO,KAAK;EAClB,YAAY;CACd,UAAU;EACR,IAAI,CAAC,WAAW;GACd,MAAM,OAAO,SAAS,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;GAC9C,MAAM,OAAO,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;GACzC,MAAM,OAAO,MAAM,CAAK,CAAC,CAAC,YAAY,KAAA,CAAS;EACjD;EACA,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;EAC1C,MAAM,OAAO,SAAS,CAAC,CAAC,YAAY,KAAA,CAAS;EAC7C,IAAI,aAAa,CAAC,aAAa,sBAAsB,KAAA,GAAW;GAC9D,MAAM,oBAAoB,MAAM,MAAM,OAAO,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;GACxE,IACE,sBAAsB,KAAA,KACtB,SAAS,mBAAmB,iBAAiB,GAE7C,MAAM,OAAO,OAAO,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;EAEnD;CACF;AACF;AAEA,MAAM,cAAc,UAA8B;CAChD,OAAO,WAAW,OAAO,aAAa;AACxC;AAEA,MAAM,cAAc,OAAmB,UAA0B;CAC/D,IAAI;EACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK;CAC/D,SAAS,OAAO;EACd,MAAM,IAAI,sBACR,oBACA,GAAG,MAAM,4BACT;GACE,MAAM;GACN,OAAO;EACT,CACF;CACF;AACF;AAEA,MAAM,yBACJ,MACA,iBACW;CACX,MAAM,QAAkB,CAAC;CACzB,IAAI,SAAS;CACb,KAAK,MAAM,eAAe,cAAc;EACtC,IACE,CAAC,OAAO,cAAc,YAAY,KAAK,KACvC,CAAC,OAAO,cAAc,YAAY,GAAG,KACrC,YAAY,QAAQ,UACpB,YAAY,OAAO,YAAY,SAC/B,YAAY,MAAM,KAAK,UACvB,CAAC,gBAAgB,MAAM,YAAY,KAAK,KACxC,CAAC,gBAAgB,MAAM,YAAY,GAAG,GAEtC,MAAM,IAAI,MACR,4DACF;EAEF,MAAM,KAAK,KAAK,MAAM,QAAQ,YAAY,KAAK,GAAG,YAAY,WAAW;EACzE,SAAS,YAAY;CACvB;CACA,MAAM,KAAK,KAAK,MAAM,MAAM,CAAC;CAC7B,OAAO,MAAM,KAAK,EAAE;AACtB;AAEA,MAAM,mBAAmB,MAAc,WAA4B;CACjE,IAAI,UAAU,KAAK,UAAU,KAAK,QAChC,OAAO;CAET,MAAM,SAAS,KAAK,WAAW,SAAS,CAAC;CACzC,MAAM,QAAQ,KAAK,WAAW,MAAM;CACpC,OAAO,EACL,UAAU,SACV,UAAU,SACV,SAAS,SACT,SAAS;AAEb;AAEA,MAAM,wBAAwB,OAAe,WAAyB;CACpE,IAAI,UAAU,QACZ,MAAM,aACJ,oBACA,sCACA,kEACF;AAEJ;AAEA,MAAM,YAAY,EAAE,OAAO;CACzB,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACpD,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACrD,WAAW,EAAE,OAAO,CAAC,CAAC,MAAM,UAAU;CACtC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS;AAC/C,CAAC;AAED,MAAM,6BAA6B,UAAU,OAAO,EAClD,oBAAoB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,EAC/D,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACpD,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACrD,WAAW,EAAE,OAAO,CAAC,CAAC,MAAM,UAAU;AACxC,CAAC;AAED,MAAM,mBAAmB,aAAa,OAAO,EAC3C,sBAAsB,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,KAAK,EAC5D,CAAC;AAED,MAAM,YAAY,UAAU,OAAO,EACjC,sBAAsB,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,KAAK,EAC5D,CAAC;AAED,MAAM,WAAW,EAAE,OAAO;CACxB,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACpD,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB;CACrD,aAAa,EAAE,OAAO,CAAC,CAAC,MAAM,+BAA+B;CAC7D,mBAAmB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS;CACtD,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,GAAG;CAC7D,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAO,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,IAAO;CAC5E,SAAS,EACN,MAAM;EACL,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;EAC/B,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;EAC/B,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACjC,CAAC,CAAC,CACD,SAAS,CAAC,CACV,QAAQ;EAAC;EAAG;EAAG;CAAC,CAAC;AACtB,CAAC;AAaD,IAAa,wBAAb,MAAmC;CACjC,oBAAoB;CACpB;CACA;CACA;CACA,oBAAmC,QAAQ,QAAQ;CACnD,0CAAmC,IAAI,IAAY;CACnD,4BAAqB,IAAI,IAA0B;CACnD,SAAwC;CACxC;CACA;CACA;CAEA,YAAY,OAAkB,UAAwC,CAAC,GAAG;EACxE,IAAI,mBAAmB,qBACrB,MAAM,IAAI,UACR,2EACF;EAEF,MAAM,EAAE,iBAAiB,SAAS,CAAC,GAAG,cAAc,CAAC,MAAM;EAC3D,KAAKC,SAAS;EACd,KAAKG,mBAAmB;EACxB,KAAKC,UAAU;EACf,KAAKC,eAAe;CACtB;CAEA,IAAI,cAA8B;EAChC,OAAO,KAAKF,qBAAqB,KAAA,IAC7B,kBAAkB,SAClB,kBAAkB;CACxB;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAKG,kBAAkB,KAAA,GACzB,OAAO,KAAKA;EAEd,KAAKC,SAAS;EACd,KAAKD,iBAAiB,YAAY;GAChC,IAAI,KAAKE,oBAAoB,GAC3B,MAAM,IAAI,SAAe,mBAAmB;IAC1C,KAAKC,qBAAqB;GAC5B,CAAC;GAEH,KAAKP,UAAU,MAAM;GACrB,MAAM,KAAKC,kBAAkB,MAAM;GACnC,KAAKI,SAAS;EAChB,EAAA,CAAG;EACH,OAAO,KAAKD;CACd;CAEA,MAAMI,cACJ,WACiB;EACjB,IAAI,KAAKH,WAAW,QAClB,MAAM,IAAI,MAAM,4CAA4C;EAE9D,KAAKC,qBAAqB;EAC1B,IAAI;GACF,OAAO,MAAM,UAAU;EACzB,UAAU;GACR,KAAKA,qBAAqB;GAC1B,IAAI,KAAKA,sBAAsB,GAAG;IAChC,KAAKC,qBAAqB;IAC1B,KAAKA,qBAAqB,KAAA;GAC5B;EACF;CACF;CAEA,MAAME,SAAS,WAAmB,UAA0C;EAC1E,IAAI,KAAKR,qBAAqB,KAAA,KAAa,aAAa,KAAA,GACtD,MAAM,IAAI,MACR,oEACF;EAEF,MAAM,WAAW,KAAKD,UAAU,IAAI,SAAS;EAC7C,IAAI,aAAa,KAAA,GAAW;GAC1B,IAAI,aAAa,KAAA,KAAa,SAAS,aAAa,UAClD,MAAM,aACJ,oBACA,oCACA,+EACF;GAEF,IAAI,SAAS,WAAW,gBACtB,MAAM,IAAI,MAAM,6CAA6C;GAE/D,IAAI,SAAS,WAAW,QACtB,MAAM,IAAI,MAAM,qDAAqD;GAEvE,MAAM,aAAa,SAAS,QAAQ,gBAAgB;GACpD,SAAS,SAAS;GAClB,OAAO;IACL,OAAO;IACP;IACA,UAAU;KAAE,MAAM;KAAW;IAAW;GAC1C;EACF;EACA,IAAI,KAAKD,wBAAwB,IAAI,SAAS,GAC5C,MAAM,IAAI,MAAM,6CAA6C;EAE/D,KAAKA,wBAAwB,IAAI,SAAS;EAC1C,IAAI;GACF,IAAI,KAAKC,UAAU,QAAQ,mBACzB,MAAM,IAAI,MAAM,gCAAgC,mBAAmB;GAErE,MAAM,WAAW,kBAAkB,yBACjC,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,CAC3C;GACA,MAAM,kBAAkB,KAAKC;GAC7B,MAAM,SACJ,oBAAoB,KAAA,IAChB,KAAA,IACA,MAAM,gBAAgB,KAAK,SAAS;GAC1C,IAAI;GACJ,IAAI,WAAW,KAAA,GACb,UAAU,SAAS,uBAAuB,SAAS;QAC9C;IACL,IAAI,oBAAoB,KAAA,GACtB,MAAM,aACJ,uBACA,0CACA,wEACF;IAEF,IAAI;KACF,UAAU,gBAAgB,QAAQ;MAChC,SAAS,OAAO;MAChB,mBAAmB;MACnB,wBAAwB,uBAAuB;MAC/C,UAAU;KACZ,CAAC;IACH,SAAS,OAAO;KACd,MAAM,IAAI,sBACR,uBACA,gDACA;MACE,MAAM;MACN,OAAO;KACT,CACF;IACF;GACF;GACA,MAAM,QAAsB;IAC1B;IACA;IACA,iBAAiB,kBACf,SAAS,wBAAwB,aAAa;IAChD,QAAQ;GACV;GACA,KAAKD,UAAU,IAAI,WAAW,KAAK;GACnC,OAAO;IACL;IACA;IACA,UACE,WAAW,KAAA,IACP,EAAE,MAAM,SAAS,IACjB;KAAE,MAAM;KAAW,YAAY,QAAQ,gBAAgB;IAAE;GACjE;EACF,UAAU;GACR,KAAKD,wBAAwB,OAAO,SAAS;EAC/C;CACF;CAEA,eAAe,EAAE,SAA6B;EAC5C,MAAM,SAAS;CACjB;CAEA,MAAMW,iBAAiB,EACrB,OACA,UACA,aAC8B;EAC9B,IAAI,KAAKV,UAAU,IAAI,SAAS,MAAM,OACpC;EAEF,IAAI,SAAS,SAAS,UAAU;GAC9B,KAAKA,UAAU,OAAO,SAAS;GAC/B,MAAM,KAAKC,kBAAkB,OAAO,SAAS,CAAC,CAAC,YAAY,KAAA,CAAS;GACpE;EACF;EACA,IAAI,SAAS,SAAS,WAAW;GAC/B,MAAM,SAAS;GACf;EACF;EACA,IAAI;GACF,MAAM,UAAU,MAAM,eAAe,SAAS,UAAU;GACxD,MAAM,SAAS;GACf,MAAM,KAAKU,gBAAgB,WAAW,MAAM,OAAO;EACrD,QAAQ;GACN,KAAKX,UAAU,OAAO,SAAS;EACjC;CACF;CAEA,MAAMY,aAAa,WAA0C;EAC3D,IAAI,QAAQ,KAAKZ,UAAU,IAAI,SAAS;EACxC,IAAI,UAAU,KAAA,KAAa,KAAKC,qBAAqB,KAAA,GAAW;GAC9D,IAAI,KAAKF,wBAAwB,IAAI,SAAS,GAC5C,MAAM,IAAI,MAAM,6CAA6C;GAE/D,KAAKA,wBAAwB,IAAI,SAAS;GAC1C,IAAI;IACF,MAAM,WAAW,kBAAkB,yBAAyB,CAAC,CAAC;IAC9D,MAAM,SAAS,MAAM,KAAKE,iBAAiB,KAAK,SAAS;IACzD,IAAI,WAAW,KAAA,GACb,IAAI;KAOF,QAAQ;MACN,UAAU,KAAA;MACV,SARc,KAAKA,iBAAiB,QAAQ;OAC5C,SAAS,OAAO;OAChB,mBAAmB;OACnB,wBAAwB,uBAAuB;OAC/C,UAAU;MACZ,CAGQ;MACN,iBAAiB,kBACf,SAAS,wBAAwB,aAAa;MAChD,QAAQ;KACV;KACA,KAAKD,UAAU,IAAI,WAAW,KAAK;IACrC,SAAS,OAAO;KACd,MAAM,IAAI,sBACR,uBACA,gDACA;MACE,MAAM;MACN,OAAO;KACT,CACF;IACF;GAEJ,UAAU;IACR,KAAKD,wBAAwB,OAAO,SAAS;GAC/C;EACF;EACA,IAAI,UAAU,KAAA,GACZ,MAAM,aACJ,uBACA,wCACA,yFACF;EAEF,IAAI,MAAM,WAAW,SACnB,MAAM,aACJ,uBACA,kDACA,uEACA,IACF;EAEF,MAAM,SAAS;EACf,OAAO;GAAE;GAAO;GAAW,UAAU,EAAE,MAAM,UAAU;EAAE;CAC3D;CAEA,MAAMY,gBACJ,WACA,SACe;EACf,IAAI,KAAKV,qBAAqB,KAAA,GAC5B;EAEF,MAAM,UAAU,KAAKA,iBAAiB,KACpC,SACA,uBAAuB,CACzB;EACA,MAAM,KAAKA,iBAAiB,KAAK,WAAW,OAAO;CACrD;CAEA,MAAM,cACJ,OAC0B;EAC1B,OAAO,KAAKO,oBAAoB,KAAKK,eAAe,KAAK,CAAC;CAC5D;CAEA,MAAM,aACJ,OAC0B;EAC1B,OAAO,KAAKL,oBACV,KAAKM,6BAA6B,KAAKC,cAAc,KAAK,CAAC,CAC7D;CACF;CAEA,MAAMD,uBACJ,WACiB;EACjB,MAAM,WAAW,KAAKE;EACtB,IAAI,gBAAsB,KAAA;EAC1B,KAAKA,oBAAoB,IAAI,SAAe,mBAAmB;GAC7D,UAAU;EACZ,CAAC;EACD,MAAM;EACN,IAAI;GACF,OAAO,MAAM,UAAU;EACzB,UAAU;GACR,QAAQ;EACV;CACF;CAEA,MAAMD,cACJ,OAC0B;EAC1B,MAAM,SAAS,MAAM,KAAKjB,OAAO,UAAU;GACzC,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC;EACD,MAAM,cAAc,MAAM,KAAKA,OAAO,OAAO,MAAM,YAAY,MAAM;EACrE,qBAAqB,OAAO,MAAM,YAAY,IAAI;EAClD,MAAM,WAAW,kBAAkB,yBACjC,MAAM,sBAAsB,KAAA,IACxB,CAAC,IACD,EAAE,UAAU,MAAM,kBAAkB,CAC1C;EACA,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,8BAA8B;IAC7C,UAAU,OAAO;IACjB,aAAa,MAAM;IACnB,KAAK,MAAM;IACX,WAAW,MAAM;IACjB,GAAG,KAAKK;GACV,CAAC;EACH,SAAS,OAAO;GACd,IAAI,0BAA0B,KAAK,GACjC,MAAM,IAAI,sBACR,sBACA,0CACA;IACE,MAAM;IACN,OAAO;GACT,CACF;GAEF,MAAM;EACR;EACA,MAAM,aAAa,mBAAmB;GACpC,UAAU,OAAO;GACjB;GACA,UAAU,SAAS;GACnB,OAAO,SAAS;GAChB,SAAS,MAAM;EACjB,CAAC;EACD,IACE,WAAW,YAAY,kCAAkC,QACzD,WAAW,YAAY,uBAAuB,OAE9C,MAAM,IAAI,MAAM,oDAAoD;EAEtE,MAAM,KAAKD,QAAQ,sBAAsB;EACzC,MAAM,YAAY,MAAM,WAAW,QAAQ;EAC3C,OAAO;GACL,WAAW;GACX,QAAQ;GACR,eAAe;GACf,WAAW,WAAW,YAAY;GAClC,aAAa,WAAW,YAAY;GACpC,mBAAmB,WAAW,YAAY;GAC1C,+BAA+B;GAC/B,oBAAoB;EACtB;CACF;CAEA,MAAMW,eACJ,OAC0B;EAC1B,MAAM,SAAS,MAAM,KAAKf,OAAO,UAAU;GACzC,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC;EACD,MAAM,cAAc,MAAM,KAAKA,OAAO,OAAO,MAAM,YAAY,MAAM;EACrE,qBAAqB,OAAO,MAAM,YAAY,IAAI;EAClD,MAAM,OAAO,WAAW,OAAO,KAAK;EACpC,MAAM,QAAQ,MAAM,KAAKW,SAAS,MAAM,WAAW,MAAM,QAAQ;EACjE,IAAI;GACF,MAAM,SAAS,MAAM,MAAM,QAAQ,YAAY,IAAI;GACnD,MAAM,KAAKE,gBAAgB,MAAM,WAAW,MAAM,MAAM,OAAO;GAC/D,MAAM,KAAKT,QAAQ,sBAAsB;GACzC,MAAM,YAAY,MAAM,OAAO,UAAU,YAAY;GACrD,KAAKe,eAAe,KAAK;GACzB,OAAO;IACL,WAAW;IACX,QAAQ;IACR,eAAe;IACf,WAAW,MAAM;IACjB,aAAa,OAAO,UAAU;GAChC;EACF,SAAS,OAAO;GACd,MAAM,KAAKP,iBAAiB,KAAK;GACjC,MAAM;EACR;CACF;CAEA,MAAM,YACJ,OAC0B;EAC1B,OAAO,KAAKF,oBAAoB,KAAKU,aAAa,KAAK,CAAC;CAC1D;CAEA,MAAM,oCACJ,OAC0B;EAC1B,IAAI;GACF,OAAO,MAAM,KAAKV,oBAChB,KAAKW,qCAAqC,KAAK,CACjD;EACF,SAAS,OAAO;GACd,MAAM,yBACJ,OACA,4BAA4B,eAC9B;EACF;CACF;CAEA,MAAMA,qCACJ,OAC0B;EAC1B,MAAM,EAAE,OAAO,aAAa,WAAW,MAAM,sBAC3C,4BAA4B,eAC5B,YAAY;GACV,MAAM,eAAe,MAAM,KAAKrB,OAAO,UAAU;IAC/C,MAAM,MAAM;IACZ,WAAW;IACX,cAAc;IACd,OAAO;GACT,CAAC;GACD,MAAM,cAAc,MAAM,KAAKA,OAAO,UAAU;IAC9C,MAAM,MAAM;IACZ,WAAW;IACX,cAAc;IACd,OAAO;GACT,CAAC;GACD,MAAM,oBAAoB,MAAM,KAAKA,OAAO,OAC1C,MAAM,YACN,MACF;GACA,qBAAqB,aAAa,MAAM,kBAAkB,IAAI;GAC9D,qBAAqB,YAAY,MAAM,kBAAkB,IAAI;GAC7D,qBAAqB,aAAa,MAAM,YAAY,IAAI;GACxD,OAAO;IACL,OAAO;IACP,aAAa;IACb,QAAQ;GACV;EACF,CACF;EACA,MAAM,OAAO,MAAM,sBACjB,4BAA4B,wBACtB,WAAW,OAAO,KAAK,CAC/B;EACA,MAAM,aAAa,MAAM,sBACvB,4BAA4B,qBAE1B,kBAAkB,iCAChB,OAAO,OACP,WAAW,MAAM,OAAO,4BAA4B,CACtD,CACJ;EACA,MAAM,QAAQ,MAAM,sBAClB,4BAA4B,uBACtB,KAAKW,SAAS,MAAM,WAAW,MAAM,QAAQ,CACrD;EACA,IAAI;GACF,MAAM,OAAO,MAAM,MAAM,QAAQ,kCAAkC,EACjE,QAAQ,CAAC;IAAE,UAAU;IAAM;GAAW,CAAC,EACzC,CAAC;GACD,MAAM,QAAQ,KAAK,OAAO,GAAG,CAAC;GAC9B,IAAI,KAAK,OAAO,WAAW,KAAK,UAAU,KAAA,GACxC,MAAM,IAAI,MACR,2DACF;GAEF,MAAM,eAAe,sBAAsB,MAAM,MAAM,YAAY;GACnE,KAAK,OAAO;GACZ,MAAM,KAAKE,gBAAgB,MAAM,WAAW,MAAM,MAAM,OAAO;GAC/D,MAAM,KAAKT,QAAQ,sBAAsB;GACzC,MAAM,YAAY,MAAM,YAAY;GACpC,KAAKe,eAAe,KAAK;GACzB,OAAO;IACL,WAAW;IACX,QAAQ;IACR,eAAe;IACf,WAAW,MAAM;IACjB,aAAa,MAAM;IACnB,8BAA8B;IAC9B,wBAAwB,WAAW;IACnC,gCAAgC,MAAM;GACxC;EACF,SAAS,OAAO;GACd,MAAM,KAAKP,iBAAiB,KAAK;GACjC,MAAM,yBACJ,OACA,4BAA4B,eAC9B;EACF;CACF;CAEA,MAAMQ,aACJ,OAC0B;EAC1B,MAAM,SAAS,MAAM,KAAKpB,OAAO,UAAU;GACzC,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC;EACD,MAAM,cAAc,MAAM,KAAKA,OAAO,OAAO,MAAM,YAAY,MAAM;EACrE,qBAAqB,OAAO,MAAM,YAAY,IAAI;EAClD,MAAM,OAAO,WAAW,OAAO,KAAK;EACpC,MAAM,QAAQ,MAAM,KAAKc,aAAa,MAAM,SAAS;EACrD,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,QAAQ,YAAY,IAAI;GACrD,MAAM,YAAY,MAAM,QAAQ;GAChC,KAAKK,eAAe,KAAK;GACzB,OAAO;IACL,WAAW;IACX,QAAQ;IACR,eAAe;IACf,WAAW,MAAM;GACnB;EACF,SAAS,OAAO;GACd,MAAM,KAAKP,iBAAiB,KAAK;GACjC,MAAM;EACR;CACF;CAEA,MAAM,cACJ,OAC0B;EAC1B,OAAO,KAAKF,oBAAoB,KAAKY,eAAe,KAAK,CAAC;CAC5D;CAEA,MAAMA,eACJ,OAC0B;EAC1B,MAAM,SAAS,MAAM,KAAKtB,OAAO,UAAU;GACzC,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC;EACD,MAAM,cAAc,MAAM,KAAKA,OAAO,OAAO,MAAM,YAAY,OAAO;EACtE,qBAAqB,OAAO,MAAM,YAAY,IAAI;EAClD,MAAM,QAAQ,MAAM,KAAKW,SAAS,MAAM,WAAW,MAAM,QAAQ;EACjE,IAAI;GACF,MAAM,SAAS,cAAc;IAC3B,UAAU,OAAO;IACjB,SAAS,MAAM,MAAM;IACrB,mBAAmB,MAAM;IACzB,QAAQ,EACN,UAAU,EACR,MAAM,MAAM,uBACR,oBAAoB,eACpB,oBAAoB,YAC1B,EACF;GACF,CAAC;GACD,MAAM,KAAKE,gBAAgB,MAAM,WAAW,MAAM,MAAM,OAAO;GAC/D,MAAM,KAAKT,QAAQ,sBAAsB;GACzC,MAAM,YAAY,MAAM,OAAO,QAAQ;GACvC,KAAKe,eAAe,KAAK;GACzB,OAAO;IACL,WAAW;IACX,QAAQ;IACR,eAAe;IACf,WAAW,MAAM;IACjB,aAAa,OAAO,QAAQ;IAC5B,YAAY,OAAO,QAAQ;IAC3B,qBAAqB,OAAO,QAAQ;IACpC,gBAAgB,OAAO,QAAQ,SAAS;GAC1C;EACF,SAAS,OAAO;GACd,MAAM,KAAKP,iBAAiB,KAAK;GACjC,MAAM,qBAAqB,KAAK;EAClC;CACF;CAEA,MAAM,YACJ,OAC0B;EAC1B,OAAO,KAAKF,oBAAoB,KAAKa,aAAa,KAAK,CAAC;CAC1D;CAEA,MAAMA,aACJ,OAC0B;EAC1B,MAAM,SAAS,MAAM,KAAKvB,OAAO,UAAU;GACzC,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC;EACD,MAAM,cAAc,MAAM,KAAKA,OAAO,OAAO,MAAM,YAAY,OAAO;EACtE,qBAAqB,OAAO,MAAM,YAAY,IAAI;EAClD,MAAM,QAAQ,MAAM,KAAKc,aAAa,MAAM,SAAS;EACrD,IAAI;GACF,MAAM,SAAS,gBAAgB;IAC7B,UAAU,OAAO;IACjB,SAAS,MAAM,MAAM;IACrB,mBAAmB,MAAM;GAC3B,CAAC;GACD,IAAI,OAAO,SAAS,WAAW,aAAa,CAAC,MAAM,sBACjD,MAAM,aACJ,oBACA,iFACA,uEACF;GAEF,MAAM,YAAY,MAAM,OAAO,QAAQ;GACvC,KAAKK,eAAe,KAAK;GACzB,OAAO;IACL,WAAW;IACX,QAAQ;IACR,eAAe;IACf,WAAW,MAAM;IACjB,qBAAqB,OAAO;IAC5B,0BAA0B,OAAO;IACjC,gBAAgB,OAAO,SAAS;GAClC;EACF,SAAS,OAAO;GACd,MAAM,KAAKP,iBAAiB,KAAK;GACjC,MAAM;EACR;CACF;CAEA,MAAM,YAAY,WAA6C;EAC7D,OAAO,KAAKF,oBAAoB,KAAKc,aAAa,SAAS,CAAC;CAC9D;CAEA,MAAMA,aAAa,WAA6C;EAO9D,MAAM,aAAa,iBAAgB,MANd,KAAKxB,OAAO,UAAU;GACzC,MAAM;GACN,WAAW;GACX,cAAc;GACd,OAAO;EACT,CAAC,EAAA,CACyC,KAAK;EAC/C,MAAM,cAAc,WAAW,SAAS,MAAM,MAC3C,SAAS,KAAK,WAAW,aAC5B;EACA,MAAM,gBACJ,WAAW,SAAS,4BAA4B,KAChD,WAAW,SAAS,2BAA2B,KAC/C,WAAW,SAAS,mCAAmC,KACvD,WAAW,SAAS,mCAAmC,KACvD,WAAW,SAAS,yBAAyB;EAC/C,OAAO;GACL,WAAW;GACX,QAAQ;GACR,eAAe;GACf,YAAY,WAAW,OAAO;GAC9B,gBAAgB,eAAe,gBAAgB,YAAY;EAC7D;CACF;AACF;AAEA,MAAM,UAAU,WAA4C;CAC1D,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;CAAE,CAAC;CAChE,mBAAmB,EAAE,GAAG,MAAM;AAChC;AAEA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAOA,MAAM,8BAGF;CACF,mCAAmC;EACjC,MAAM;EACN,MAAM;EACN,WAAW;CACb;CACA,sCAAsC;EACpC,MAAM;EACN,MAAM;EACN,WAAW;CACb;CACA,mCAAmC;EACjC,MAAM;EACN,MAAM;EACN,WAAW;CACb;CACA,qCAAqC;EACnC,MAAM;EACN,MAAM;EACN,WAAW;CACb;CACA,qCAAqC;EACnC,MAAM;EACN,MAAM;EACN,WAAW;CACb;AACF;AAEA,MAAM,cAAc,UAA2C;CAC7D,IAAI,iBAAiB,6BAA6B;EAChD,MAAM,SAAS,4BAA4B,MAAM;EACjD,OAAO,EACL,OAAO;GACL,MAAM,OAAO;GACb,SAAS,MAAM;GACf,MAAM,OAAO;GACb,WAAW,OAAO;EACpB,EACF;CACF;CACA,OAAO,mBAAmB,KAAK;AACjC;AAEA,MAAM,eAAe,UAAmC;CACtD,MAAM,WAAW,WAAW,KAAK;CACjC,OAAO;EACL,SAAS;EACT,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,QAAQ;EAAE,CAAC;EACnE,mBAAmB,EAAE,GAAG,SAAS;CACnC;AACF;;;;;;AAOA,MAAM,QAAQ,OACZ,YAC4B;CAC5B,IAAI;EACF,OAAO,MAAM,QAAQ;CACvB,SAAS,OAAO;EACd,OAAO,YAAY,KAAK;CAC1B;AACF;AAEA,MAAM,qBAAqB,OACzB,YAC4B;CAC5B,MAAM,QAAQ;EACZ,oBAAoB;EACpB,gBAAgB,kBAAkB,uBAAuB;EACzD,KAAK;GACH,wBAAwB;IACtB,WAAW;IACX,SAAS;GACX;GACA,SAAS;IAAC;IAAQ;IAAO;GAAM;GAC/B,aAAa,QAAQ;GACrB,OAAO;GACP,WAAW;EACb;CACF;CACA,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC;EAChE,mBAAmB;CACrB;AACF;AAEA,MAAM,gBAAgB,EAAE,OAAO;CAC7B,MAAM,EAAE,KAAK,cAAc;CAC3B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,wBAAwB;CACrD,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,uBAAuB;AACrD,CAAC;AAED,MAAM,kBACJ,UACmB;CACnB,MAAM,aAAa,wBAAwB,KAAK;CAChD,MAAM,QAAQ;EACZ,SAAS;EACT,YAAY,WAAW;EACvB,OAAO,WAAW;EAClB,eAAe,WAAW;EAC1B,UAAU,WAAW;EACrB,WAAW,WAAW;EACtB,MAAM;CACR;CACA,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC;EAChE,mBAAmB,EAAE,GAAG,MAAM;CAChC;AACF;AAOA,MAAa,mBAAmB;;;;;;;AAQhC,MAAa,4BACX,YACc;CAId,MAAM,SAAS,IAAI,UACjB;EACE,MAAM;EACGyB;CACX,GACA,EACE,cAAc,iBAChB,CACF;CACA,OAAO,aACL,gBACA;EACE,aACE;EACF,aAAa,EAAE,OAAO,CAAC,CAAC;EACxB,aAAa;GACX,cAAc;GACd,iBAAiB;GACjB,gBAAgB;EAClB;CACF,GACA,YAAY,YAAY,mBAAmB,OAAO,CAAC,CACrD;CACA,OAAO,aACL,uBACA;EACE,aAAa;EACb,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YAAY,OAAO,MAAM,QAAQ,cAAc,KAAK,CAAC,CAAC,CAChE;CACA,OAAO,aACL,qBACA;EACE,aAAa;EACb,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YAAY,OAAO,MAAM,QAAQ,YAAY,KAAK,CAAC,CAAC,CAC9D;CACA,OAAO,aACL,gDACA;EACE,aACE;EACF,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YACJ,OAAO,MAAM,QAAQ,oCAAoC,KAAK,CAAC,CACjE,CACJ;CACA,OAAO,aACL,uBACA;EACE,aACE;EACF,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YAAY,OAAO,MAAM,QAAQ,cAAc,KAAK,CAAC,CAAC,CAChE;CACA,OAAO,aACL,sBACA;EACE,aACE;EACF,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YAAY,OAAO,MAAM,QAAQ,aAAa,KAAK,CAAC,CAAC,CAC/D;CACA,OAAO,aACL,qBACA;EACE,aAAa;EACb,aAAa;EACb,aAAa;GAAE,iBAAiB;GAAO,gBAAgB;EAAM;CAC/D,GACA,OAAO,UACL,MAAM,YAAY,OAAO,MAAM,QAAQ,YAAY,KAAK,CAAC,CAAC,CAC9D;CACA,OAAO,aACL,qBACA;EACE,aACE;EACF,aAAa,EAAE,OAAO,EACpB,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,EACtD,CAAC;EACD,aAAa;GACX,cAAc;GACd,iBAAiB;GACjB,gBAAgB;EAClB;CACF,GACA,OAAO,EAAE,gBACP,MAAM,YAAY,OAAO,MAAM,QAAQ,YAAY,SAAS,CAAC,CAAC,CAClE;CACA,OAAO,aACL,iBACA;EACE,aACE;EACF,aAAa;EACb,aAAa;GACX,cAAc;GACd,iBAAiB;GACjB,gBAAgB;GAChB,eAAe;EACjB;CACF,GACA,OAAO,UAAU,YAAY,eAAe,KAAK,CAAC,CACpD;CACA,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/anonymize-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "Path-only local MCP server for stella PII anonymization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"LICENSE",
|
|
19
|
+
"NOTICE",
|
|
19
20
|
"README.md"
|
|
20
21
|
],
|
|
21
22
|
"publishConfig": {
|
|
@@ -34,20 +35,21 @@
|
|
|
34
35
|
"build": "tsdown",
|
|
35
36
|
"typecheck": "bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.json && bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.test.json",
|
|
36
37
|
"test": "bun test src/__test__/local.test.ts src/__test__/server.test.ts src/__test__/instructions.test.ts && bun run test:node-runtime",
|
|
38
|
+
"pretest:node-runtime": "test -f dist/server-entry.mjs || bun run build",
|
|
37
39
|
"test:node-runtime": "node --import tsx --test src/__test__/durable-sessions.test.ts && node scripts/test-packed-bin.mjs",
|
|
38
40
|
"format": "oxfmt ."
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
42
|
-
"@stll/anonymize": "2.
|
|
43
|
-
"@stll/anonymize-docx": "^2.
|
|
44
|
-
"@stll/anonymize-pdf": "2.
|
|
44
|
+
"@stll/anonymize": "2.8.1",
|
|
45
|
+
"@stll/anonymize-docx": "^2.8.1",
|
|
46
|
+
"@stll/anonymize-pdf": "2.8.1",
|
|
45
47
|
"fs-native-extensions": "^1.5.0",
|
|
46
48
|
"zod": "^4.4.3"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@types/node": "^26.2.0",
|
|
50
|
-
"bun-types": "
|
|
52
|
+
"bun-types": "1.4.0",
|
|
51
53
|
"expect": "^30.2.0",
|
|
52
54
|
"fflate": "^0.8.3",
|
|
53
55
|
"tsdown": "0.22.14",
|