@krischoichoi/channel-files 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +79 -0
  2. package/lib/attachment-resolver.d.ts +24 -0
  3. package/lib/attachment-resolver.d.ts.map +1 -0
  4. package/lib/attachment-resolver.js +38 -0
  5. package/lib/attachment-resolver.js.map +1 -0
  6. package/lib/attachments/extractors/docx.d.ts +6 -0
  7. package/lib/attachments/extractors/docx.d.ts.map +1 -0
  8. package/lib/attachments/extractors/docx.js +43 -0
  9. package/lib/attachments/extractors/docx.js.map +1 -0
  10. package/lib/attachments/extractors/index.d.ts +10 -0
  11. package/lib/attachments/extractors/index.d.ts.map +1 -0
  12. package/lib/attachments/extractors/index.js +10 -0
  13. package/lib/attachments/extractors/index.js.map +1 -0
  14. package/lib/attachments/extractors/pdf.d.ts +4 -0
  15. package/lib/attachments/extractors/pdf.d.ts.map +1 -0
  16. package/lib/attachments/extractors/pdf.js +44 -0
  17. package/lib/attachments/extractors/pdf.js.map +1 -0
  18. package/lib/attachments/extractors/registry.d.ts +43 -0
  19. package/lib/attachments/extractors/registry.d.ts.map +1 -0
  20. package/lib/attachments/extractors/registry.js +82 -0
  21. package/lib/attachments/extractors/registry.js.map +1 -0
  22. package/lib/attachments/extractors/text.d.ts +17 -0
  23. package/lib/attachments/extractors/text.d.ts.map +1 -0
  24. package/lib/attachments/extractors/text.js +0 -0
  25. package/lib/attachments/extractors/text.js.map +1 -0
  26. package/lib/attachments/extractors/types.d.ts +62 -0
  27. package/lib/attachments/extractors/types.d.ts.map +1 -0
  28. package/lib/attachments/extractors/types.js +13 -0
  29. package/lib/attachments/extractors/types.js.map +1 -0
  30. package/lib/attachments/extractors/xlsx.d.ts +6 -0
  31. package/lib/attachments/extractors/xlsx.d.ts.map +1 -0
  32. package/lib/attachments/extractors/xlsx.js +83 -0
  33. package/lib/attachments/extractors/xlsx.js.map +1 -0
  34. package/lib/attachments/filename.d.ts +23 -0
  35. package/lib/attachments/filename.d.ts.map +1 -0
  36. package/lib/attachments/filename.js +62 -0
  37. package/lib/attachments/filename.js.map +1 -0
  38. package/lib/attachments/hash.d.ts +5 -0
  39. package/lib/attachments/hash.d.ts.map +1 -0
  40. package/lib/attachments/hash.js +13 -0
  41. package/lib/attachments/hash.js.map +1 -0
  42. package/lib/attachments/index.d.ts +18 -0
  43. package/lib/attachments/index.d.ts.map +1 -0
  44. package/lib/attachments/index.js +18 -0
  45. package/lib/attachments/index.js.map +1 -0
  46. package/lib/attachments/mime.d.ts +27 -0
  47. package/lib/attachments/mime.d.ts.map +1 -0
  48. package/lib/attachments/mime.js +162 -0
  49. package/lib/attachments/mime.js.map +1 -0
  50. package/lib/attachments/pipeline-extractor.d.ts +8 -0
  51. package/lib/attachments/pipeline-extractor.d.ts.map +1 -0
  52. package/lib/attachments/pipeline-extractor.js +62 -0
  53. package/lib/attachments/pipeline-extractor.js.map +1 -0
  54. package/lib/attachments/pipeline.d.ts +29 -0
  55. package/lib/attachments/pipeline.d.ts.map +1 -0
  56. package/lib/attachments/pipeline.js +59 -0
  57. package/lib/attachments/pipeline.js.map +1 -0
  58. package/lib/attachments/policy.d.ts +25 -0
  59. package/lib/attachments/policy.d.ts.map +1 -0
  60. package/lib/attachments/policy.js +21 -0
  61. package/lib/attachments/policy.js.map +1 -0
  62. package/lib/attachments/render.d.ts +16 -0
  63. package/lib/attachments/render.d.ts.map +1 -0
  64. package/lib/attachments/render.js +28 -0
  65. package/lib/attachments/render.js.map +1 -0
  66. package/lib/attachments/schema.d.ts +58 -0
  67. package/lib/attachments/schema.d.ts.map +1 -0
  68. package/lib/attachments/schema.js +50 -0
  69. package/lib/attachments/schema.js.map +1 -0
  70. package/lib/attachments/store.d.ts +69 -0
  71. package/lib/attachments/store.d.ts.map +1 -0
  72. package/lib/attachments/store.js +254 -0
  73. package/lib/attachments/store.js.map +1 -0
  74. package/lib/attachments/tool-read.d.ts +34 -0
  75. package/lib/attachments/tool-read.d.ts.map +1 -0
  76. package/lib/attachments/tool-read.js +182 -0
  77. package/lib/attachments/tool-read.js.map +1 -0
  78. package/lib/attachments/types.d.ts +96 -0
  79. package/lib/attachments/types.d.ts.map +1 -0
  80. package/lib/attachments/types.js +18 -0
  81. package/lib/attachments/types.js.map +1 -0
  82. package/lib/backends/harness-native.d.ts +45 -0
  83. package/lib/backends/harness-native.d.ts.map +1 -0
  84. package/lib/backends/harness-native.js +56 -0
  85. package/lib/backends/harness-native.js.map +1 -0
  86. package/lib/catalog/legacy-backfill.d.ts +34 -0
  87. package/lib/catalog/legacy-backfill.d.ts.map +1 -0
  88. package/lib/catalog/legacy-backfill.js +49 -0
  89. package/lib/catalog/legacy-backfill.js.map +1 -0
  90. package/lib/catalog/store.d.ts +121 -0
  91. package/lib/catalog/store.d.ts.map +1 -0
  92. package/lib/catalog/store.js +227 -0
  93. package/lib/catalog/store.js.map +1 -0
  94. package/lib/catalog/types.d.ts +83 -0
  95. package/lib/catalog/types.d.ts.map +1 -0
  96. package/lib/catalog/types.js +29 -0
  97. package/lib/catalog/types.js.map +1 -0
  98. package/lib/index.d.ts +12 -0
  99. package/lib/index.d.ts.map +1 -0
  100. package/lib/index.js +12 -0
  101. package/lib/index.js.map +1 -0
  102. package/lib/migration/migrate-on-read.d.ts +61 -0
  103. package/lib/migration/migrate-on-read.d.ts.map +1 -0
  104. package/lib/migration/migrate-on-read.js +61 -0
  105. package/lib/migration/migrate-on-read.js.map +1 -0
  106. package/lib/migration/verify.d.ts +20 -0
  107. package/lib/migration/verify.d.ts.map +1 -0
  108. package/lib/migration/verify.js +35 -0
  109. package/lib/migration/verify.js.map +1 -0
  110. package/lib/paths.d.ts +13 -0
  111. package/lib/paths.d.ts.map +1 -0
  112. package/lib/paths.js +23 -0
  113. package/lib/paths.js.map +1 -0
  114. package/lib/plugin.d.ts +4 -0
  115. package/lib/plugin.d.ts.map +1 -0
  116. package/lib/plugin.js +6 -0
  117. package/lib/plugin.js.map +1 -0
  118. package/lib/service.d.ts +27 -0
  119. package/lib/service.d.ts.map +1 -0
  120. package/lib/service.js +67 -0
  121. package/lib/service.js.map +1 -0
  122. package/package.json +59 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * XLSX (and xls) text extractor.
3
+ *
4
+ * Uses the established, pure-JS xlsx@0.18.5 (SheetJS, exact-pinned) to read
5
+ * the workbook cells and render each sheet as a markdown table. Shared
6
+ * strings, inline strings, numbers and booleans are all surfaced as text.
7
+ *
8
+ * Capped parsing is enforced by the registry/pipeline; here we only map bytes
9
+ * -> text and wrap failures into a stable ExtractionError.
10
+ */
11
+ import * as XLSX from 'xlsx';
12
+ import { ExtractionError } from './types.js';
13
+ const SPREADSHEET_MIMES = [
14
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
15
+ 'application/vnd.ms-excel', // .xls
16
+ ];
17
+ /** Render one cell address range as a markdown table. */
18
+ function sheetToMarkdown(sheetName, sheet) {
19
+ const ref = sheet['!ref'];
20
+ if (!ref)
21
+ return '## ' + sheetName + '\n\n(empty sheet)\n';
22
+ const range = XLSX.utils.decode_range(ref);
23
+ const rows = [];
24
+ for (let r = range.s.r; r <= range.e.r; r++) {
25
+ const row = [];
26
+ for (let c = range.s.c; c <= range.e.c; c++) {
27
+ const addr = XLSX.utils.encode_cell({ r, c });
28
+ const cell = sheet[addr];
29
+ if (!cell || cell.v === undefined || cell.v === null) {
30
+ row.push('');
31
+ continue;
32
+ }
33
+ const val = String(cell.v).replace(/[\r\n]+/g, ' ').replace(/\|/g, '\\|');
34
+ row.push(val);
35
+ }
36
+ rows.push(row);
37
+ }
38
+ const header = rows[0] ?? [];
39
+ const sep = header.map(() => '---');
40
+ const all = [header, sep, ...rows.slice(1)].map((r) => '| ' + r.join(' | ') + ' |').join('\n');
41
+ return '## ' + sheetName + '\n\n' + all + '\n';
42
+ }
43
+ /** The singleton spreadsheet extractor. */
44
+ export const xlsxExtractor = {
45
+ mimes: SPREADSHEET_MIMES,
46
+ extensions: ['xlsx', 'xls'],
47
+ async extract(data, options) {
48
+ if (options?.signal?.aborted) {
49
+ throw new ExtractionError('parser-error', 'extraction aborted');
50
+ }
51
+ // XLSX / XLS are ZIP containers: reject bytes that are not even a zip so a
52
+ // corrupt blob is reported as parser-error rather than misparsed as cells.
53
+ if (data.length < 4 || data[0] !== 0x50 || data[1] !== 0x4b) {
54
+ throw new ExtractionError('parser-error', 'not a spreadsheet container');
55
+ }
56
+ try {
57
+ const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
58
+ const workbook = XLSX.read(buf, { type: 'buffer', cellText: false, cellNF: false });
59
+ if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
60
+ throw new ExtractionError('unsupported', 'workbook has no sheets');
61
+ }
62
+ const parts = workbook.SheetNames.map((name) => {
63
+ const sheet = workbook.Sheets[name];
64
+ return sheet ? sheetToMarkdown(name, sheet) : '';
65
+ }).filter((text) => text.length > 0);
66
+ const text = parts.join('\n').trim();
67
+ if (text.length === 0) {
68
+ throw new ExtractionError('unsupported', 'no extractable text in spreadsheet');
69
+ }
70
+ return { format: 'markdown', text };
71
+ }
72
+ catch (error) {
73
+ if (error instanceof ExtractionError)
74
+ throw error;
75
+ throw new ExtractionError('parser-error', 'spreadsheet text could not be extracted');
76
+ }
77
+ },
78
+ };
79
+ /** Whether a (verified) MIME / extension is served by the spreadsheet extractor. */
80
+ export function xlsxMimeServed(mime) {
81
+ return mime ? SPREADSHEET_MIMES.includes(mime.toLowerCase()) : false;
82
+ }
83
+ //# sourceMappingURL=xlsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx.js","sourceRoot":"","sources":["../../../src/attachments/extractors/xlsx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /** Strip control characters (including NUL) from a rendered string. */
2
+ export declare function stripControlChars(input: string): string;
3
+ /**
4
+ * Sanitize a channel-provided filename into a safe display basename.
5
+ *
6
+ * - removes any path separators (`/`, `\\`) and drive/colon prefixes;
7
+ * - strips control characters;
8
+ * - strips leading dots and trims;
9
+ * - removes Windows-reserved characters along with trailing dots/spaces;
10
+ * - limits the whole name to `maxLength` bytes-safe characters (the extension
11
+ * is preserved);
12
+ * - falls back to a default stem when nothing survives.
13
+ */
14
+ export declare function sanitizeFilename(input: string | undefined, options?: {
15
+ maxLength?: number;
16
+ fallback?: string;
17
+ }): string;
18
+ /**
19
+ * Normalize an adapter-provided MIME hint into a safe token. Returns
20
+ * `undefined` when the hint is missing or clearly invalid.
21
+ */
22
+ export declare function normalizeMimeHint(mimeType: string | undefined): string | undefined;
23
+ //# sourceMappingURL=filename.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/attachments/filename.ts"],"names":[],"mappings":"AAWA,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,CAsBR;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIlF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Filename sanitization for stored channel assets (plan \u00a747).
3
+ *
4
+ * A platform-supplied name is a hint from an untrusted boundary: it may carry
5
+ * path separators, control characters, leading dots or a bogus extension. We
6
+ * keep a safe, short basename plus its extension and NEVER let it influence
7
+ * the on-disk path (the store lays files out under generated attachment ids,
8
+ * so the sanitized name only appears in metadata).
9
+ */
10
+ import { extname } from 'node:path';
11
+ /** Strip control characters (including NUL) from a rendered string. */
12
+ export function stripControlChars(input) {
13
+ // eslint-disable-next-line no-control-regex
14
+ return input.replace(/[\u0000-\u001f\u007f]/g, '');
15
+ }
16
+ /**
17
+ * Sanitize a channel-provided filename into a safe display basename.
18
+ *
19
+ * - removes any path separators (`/`, `\\`) and drive/colon prefixes;
20
+ * - strips control characters;
21
+ * - strips leading dots and trims;
22
+ * - removes Windows-reserved characters along with trailing dots/spaces;
23
+ * - limits the whole name to `maxLength` bytes-safe characters (the extension
24
+ * is preserved);
25
+ * - falls back to a default stem when nothing survives.
26
+ */
27
+ export function sanitizeFilename(input, options = {}) {
28
+ const maxLength = options.maxLength ?? 80;
29
+ const fallback = options.fallback ?? 'attachment';
30
+ if (!input)
31
+ return fallback;
32
+ // 1. Drop any path separator so a name can never traverse or hide a subpath.
33
+ let name = stripControlChars(input).replace(/[\\/]/g, '');
34
+ // 2. Strip a windows-ish drive prefix ("C:" ...) and leading dots.
35
+ name = name.replace(/^[a-zA-Z]:/, '').replace(/^\.+/, '');
36
+ name = name.trim();
37
+ // 3. Remove Windows-reserved characters and trailing dots/spaces.
38
+ // eslint-disable-next-line no-control-regex
39
+ name = name.replace(/[<>:"|?*\u0000-\u001f\u007f]/g, '');
40
+ name = name.replace(/[. ]+$/g, '');
41
+ if (!name)
42
+ return fallback;
43
+ // Preserve the extension but bound the whole name.
44
+ const ext = extname(name); // ".pdf"
45
+ const stem = name.slice(0, name.length - ext.length);
46
+ if (stem.length === 0)
47
+ return (ext || fallback).slice(0, maxLength);
48
+ const budget = Math.max(1, maxLength - ext.length);
49
+ const kept = stem.slice(0, budget);
50
+ return kept + ext;
51
+ }
52
+ /**
53
+ * Normalize an adapter-provided MIME hint into a safe token. Returns
54
+ * `undefined` when the hint is missing or clearly invalid.
55
+ */
56
+ export function normalizeMimeHint(mimeType) {
57
+ if (!mimeType)
58
+ return undefined;
59
+ const m = /^[a-z0-9.+-]+\/[a-z0-9.+-]+$/i.exec(mimeType.trim());
60
+ return m ? m[0].toLowerCase() : undefined;
61
+ }
62
+ //# sourceMappingURL=filename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/attachments/filename.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,uEAAuE;AACvE,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,4CAA4C;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAyB,EACzB,UAAqD,EAAE;IAEvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,YAAY,CAAC;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,6EAA6E;IAC7E,IAAI,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1D,mEAAmE;IACnE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1D,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACnB,kEAAkE;IAClE,4CAA4C;IAC5C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;IACzD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC;IAE3B,mDAAmD;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,IAAI,GAAG,GAAG,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA4B;IAC5D,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,CAAC,GAAG,+BAA+B,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Hex-encoded SHA-256 of the given bytes. */
2
+ export declare function sha256Hex(bytes: Uint8Array | Buffer): string;
3
+ /** Hex-encoded SHA-256 of a UTF-8 string. */
4
+ export declare function sha256HexText(text: string): string;
5
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/attachments/hash.ts"],"names":[],"mappings":"AAKA,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAE5D;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SHA-256 helpers for the private channel asset store (plan \u00a747).
3
+ */
4
+ import { createHash } from 'node:crypto';
5
+ /** Hex-encoded SHA-256 of the given bytes. */
6
+ export function sha256Hex(bytes) {
7
+ return createHash('sha256').update(bytes).digest('hex');
8
+ }
9
+ /** Hex-encoded SHA-256 of a UTF-8 string. */
10
+ export function sha256HexText(text) {
11
+ return createHash('sha256').update(text, 'utf8').digest('hex');
12
+ }
13
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/attachments/hash.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,8CAA8C;AAC9C,MAAM,UAAU,SAAS,CAAC,KAA0B;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Private Channel Asset Store.
3
+ *
4
+ * Public surface of the channel-harness attachments module.
5
+ */
6
+ export * from './types.js';
7
+ export * from './policy.js';
8
+ export * from './filename.js';
9
+ export * from './mime.js';
10
+ export * from './hash.js';
11
+ export * from './schema.js';
12
+ export * from './store.js';
13
+ export * from './pipeline.js';
14
+ export * from './pipeline-extractor.js';
15
+ export * from './render.js';
16
+ export * from './extractors/index.js';
17
+ export * from './tool-read.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/attachments/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Private Channel Asset Store.
3
+ *
4
+ * Public surface of the channel-harness attachments module.
5
+ */
6
+ export * from './types.js';
7
+ export * from './policy.js';
8
+ export * from './filename.js';
9
+ export * from './mime.js';
10
+ export * from './hash.js';
11
+ export * from './schema.js';
12
+ export * from './store.js';
13
+ export * from './pipeline.js';
14
+ export * from './pipeline-extractor.js';
15
+ export * from './render.js';
16
+ export * from './extractors/index.js';
17
+ export * from './tool-read.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attachments/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MIME detection by magic-signature sniffing (plan \u00a747).
3
+ *
4
+ * The adapter's `mimeType` is only a HINT from an untrusted boundary. Before
5
+ * we trust what a stored asset claims to be, we re-verify it against the raw
6
+ * bytes' magic signatures. This module recognizes common binary containers
7
+ * (png/jpeg/gif/webp/pdf, OOXML zip members docx/xlsx, and zip) plus runs of
8
+ * printable ASCII/UTF-8 (plain text). Anything unrecognized yields
9
+ * `undefined` and the store keeps the (hint) value as-is.
10
+ */
11
+ /** Result of a sniff: a re-verified MIME (or undefined when unknown). */
12
+ export interface MagicSniffResult {
13
+ mimeType: string | undefined;
14
+ /** True when the bytes were recognized as printable text. */
15
+ isPlainText: boolean;
16
+ }
17
+ /**
18
+ * Sniff a re-verified MIME from raw bytes. `hint` (adapter mimeType) is never
19
+ * trusted on its own — it is only used as a tiebreaker/fallback when the magic
20
+ * is ambiguous (e.g. zip). Returns the detected type.
21
+ */
22
+ export declare function sniffMime(bytes: Uint8Array, hint?: string): string | undefined;
23
+ /** Whether the sniffed bytes were recognized as plain readable text. */
24
+ export declare function sniffIsPlainText(bytes: Uint8Array): boolean;
25
+ /** Re-verified MIME, preferring magic over the adapter hint (plan \u00a747). */
26
+ export declare function verifiedMime(bytes: Uint8Array, hint?: string): string | undefined;
27
+ //# sourceMappingURL=mime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/attachments/mime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAC;CACtB;AA0ED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM9E;AA4CD,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAE3D;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjF"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * MIME detection by magic-signature sniffing (plan \u00a747).
3
+ *
4
+ * The adapter's `mimeType` is only a HINT from an untrusted boundary. Before
5
+ * we trust what a stored asset claims to be, we re-verify it against the raw
6
+ * bytes' magic signatures. This module recognizes common binary containers
7
+ * (png/jpeg/gif/webp/pdf, OOXML zip members docx/xlsx, and zip) plus runs of
8
+ * printable ASCII/UTF-8 (plain text). Anything unrecognized yields
9
+ * `undefined` and the store keeps the (hint) value as-is.
10
+ */
11
+ const TEXT_SCAN_BYTES = 512;
12
+ function isUtf8OrAscii(bytes) {
13
+ // Reject on any NUL / most control chars; accept printable ASCII and valid
14
+ // UTF-8 multi-byte sequences. This is intentionally permissive but blocks
15
+ // null-terminated or binary gibberish.
16
+ const max = Math.min(bytes.length, TEXT_SCAN_BYTES);
17
+ for (let i = 0; i < max; i++) {
18
+ const b = bytes[i];
19
+ if (b === 0)
20
+ return false;
21
+ if (b < 0x09)
22
+ return false;
23
+ if (b >= 0x7f && (b === 0x7f || (b >= 0x80 && b <= 0xbf && i === 0))) {
24
+ // 0x7f is DEL (control); 0x80-0xbf as a lead byte is invalid.
25
+ return false;
26
+ }
27
+ if (b >= 0xc2 && b <= 0xdf && i + 1 < max && bytes[i + 1] >= 0x80 && bytes[i + 1] <= 0xbf) {
28
+ i++; // 2-byte sequence
29
+ }
30
+ else if (b >= 0xe0 && b <= 0xef && i + 2 < max) {
31
+ const b1 = bytes[i + 1];
32
+ const b2 = bytes[i + 2];
33
+ if (b1 >= 0x80 && b1 <= 0xbf && b2 >= 0x80 && b2 <= 0xbf) {
34
+ i += 2; // 3-byte sequence
35
+ }
36
+ else {
37
+ return false;
38
+ }
39
+ }
40
+ else if (b >= 0xf0 && b <= 0xf4 && i + 3 < max) {
41
+ const b1 = bytes[i + 1];
42
+ const b2 = bytes[i + 2];
43
+ const b3 = bytes[i + 3];
44
+ if (b1 >= 0x80 && b1 <= 0xbf && b2 >= 0x80 && b2 <= 0xbf && b3 >= 0x80 && b3 <= 0xbf) {
45
+ i += 3; // 4-byte sequence
46
+ }
47
+ else {
48
+ return false;
49
+ }
50
+ }
51
+ }
52
+ return true;
53
+ }
54
+ /** Inflate a ZIP central directory / local header fingerprint for OOXML. */
55
+ function sniffZip(bytes) {
56
+ // ZIP local file header: PK\u0003\u0004 (or empty/fast: PK\u0005\u0006, spanned PK\u0007\u0008).
57
+ if (bytes.length < 4)
58
+ return undefined;
59
+ if (bytes[0] !== 0x50 || bytes[1] !== 0x4b)
60
+ return undefined;
61
+ if (bytes[2] === 0x03 && bytes[3] === 0x04) {
62
+ // OOXML packages carry "[Content_Types].xml" with the first member often
63
+ // "_rels/.rels". Peek further down for the signature.
64
+ const marker = findAscii(bytes, '_rels/.rels') || findAscii(bytes, '[Content_Types].xml');
65
+ if (marker) {
66
+ if (findAscii(bytes, 'word/'))
67
+ return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
68
+ if (findAscii(bytes, 'xl/'))
69
+ return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
70
+ if (findAscii(bytes, 'ppt/'))
71
+ return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
72
+ return 'application/zip';
73
+ }
74
+ return 'application/zip';
75
+ }
76
+ if ((bytes[2] === 0x05 && bytes[3] === 0x06) || (bytes[2] === 0x07 && bytes[3] === 0x08)) {
77
+ return 'application/zip';
78
+ }
79
+ return undefined;
80
+ }
81
+ function findAscii(bytes, token) {
82
+ const needle = new TextEncoder().encode(token);
83
+ const scan = Math.min(bytes.length, 4096);
84
+ outer: for (let i = 0; i + needle.length <= scan; i++) {
85
+ for (let j = 0; j < needle.length; j++) {
86
+ if (bytes[i + j] !== needle[j])
87
+ continue outer;
88
+ }
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+ /**
94
+ * Sniff a re-verified MIME from raw bytes. `hint` (adapter mimeType) is never
95
+ * trusted on its own — it is only used as a tiebreaker/fallback when the magic
96
+ * is ambiguous (e.g. zip). Returns the detected type.
97
+ */
98
+ export function sniffMime(bytes, hint) {
99
+ if (!bytes || bytes.length === 0)
100
+ return undefined;
101
+ const magic = magicMime(bytes, hint);
102
+ if (magic)
103
+ return magic;
104
+ if (isUtf8OrAscii(bytes))
105
+ return hint?.startsWith('text/') ? hint : sniffPlainText(bytes);
106
+ return hint; // unknown binary — keep the adapter hint (best effort).
107
+ }
108
+ /** Magic-signature detection for the recognized binary containers. */
109
+ function magicMime(bytes, hint) {
110
+ // PNG: \u0089PNG\r\n\u001a\n
111
+ if (bytes.length >= 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e
112
+ && bytes[3] === 0x47 && bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a) {
113
+ return 'image/png';
114
+ }
115
+ // JPEG: FF D8 FF
116
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
117
+ return 'image/jpeg';
118
+ }
119
+ // GIF: GIF87a / GIF89a
120
+ if (bytes.length >= 6 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46
121
+ && (bytes[3] === 0x38) && (bytes[4] === 0x37 || bytes[4] === 0x39) && bytes[5] === 0x61) {
122
+ return 'image/gif';
123
+ }
124
+ // WEBP: RIFF....WEBP
125
+ if (bytes.length >= 12 && bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46
126
+ && bytes[3] === 0x46 && bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {
127
+ return 'image/webp';
128
+ }
129
+ // PDF: %PDF-
130
+ if (bytes.length >= 5 && bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44
131
+ && bytes[3] === 0x46 && bytes[4] === 0x2d) {
132
+ return 'application/pdf';
133
+ }
134
+ return sniffZip(bytes);
135
+ }
136
+ /** Plain-text detection: pick a textual MIME when bytes look like readable text. */
137
+ function sniffPlainText(bytes) {
138
+ // Cheap heuristic: JSON/XML/YAML-ish markers win for the common feed types,
139
+ // otherwise generic text/plain.
140
+ const sample = new TextDecoder('utf-8', { fatal: false })
141
+ .decode(bytes.slice(0, TEXT_SCAN_BYTES))
142
+ .trimStart();
143
+ if (sample.startsWith('{') && sample.includes('"'))
144
+ return 'application/json';
145
+ if (sample.startsWith('<?xml'))
146
+ return 'application/xml';
147
+ if (sample.startsWith('#') || sample.includes(': '))
148
+ return 'text/plain';
149
+ return 'text/plain';
150
+ }
151
+ /** Whether the sniffed bytes were recognized as plain readable text. */
152
+ export function sniffIsPlainText(bytes) {
153
+ return bytes.length > 0 && isUtf8OrAscii(bytes);
154
+ }
155
+ /** Re-verified MIME, preferring magic over the adapter hint (plan \u00a747). */
156
+ export function verifiedMime(bytes, hint) {
157
+ const magic = magicMime(bytes, hint);
158
+ if (magic)
159
+ return magic;
160
+ return sniffMime(bytes, hint);
161
+ }
162
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/attachments/mime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type AttachmentPolicy } from './policy.js';
2
+ import type { AttachmentExtractorHook } from './pipeline.js';
3
+ /**
4
+ * Create the registry-backed extraction hook for a given policy.
5
+ * Extraction can be disabled by not attaching the hook to the pipeline.
6
+ */
7
+ export declare function createAttachmentExtractor(policy: AttachmentPolicy): AttachmentExtractorHook;
8
+ //# sourceMappingURL=pipeline-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-extractor.d.ts","sourceRoot":"","sources":["../../src/attachments/pipeline-extractor.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAI7D;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,uBAAuB,CA6C3F"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * M4 pipeline extraction hook.
3
+ *
4
+ * The concrete AttachmentExtractorHook used by the bridge: drives the
5
+ * extractor registry (attemptExtraction) and persists the outcome through
6
+ * the store — putExtracted for ready, recordExtraction for unsupported /
7
+ * failed / too-large. Never throws (best-effort): every failure
8
+ * collapses to a typed extraction state.
9
+ */
10
+ import { extname } from 'node:path';
11
+ import { attemptExtraction } from './extractors/registry.js';
12
+ import {} from './policy.js';
13
+ /**
14
+ * Create the registry-backed extraction hook for a given policy.
15
+ * Extraction can be disabled by not attaching the hook to the pipeline.
16
+ */
17
+ export function createAttachmentExtractor(policy) {
18
+ return {
19
+ async extract(input) {
20
+ const { store, asset, data, signal } = input;
21
+ const extension = extname(asset.name).replace(/^\./, '') || undefined;
22
+ const attempt = await attemptExtraction({
23
+ mime: asset.mimeType,
24
+ extension,
25
+ data,
26
+ caps: policy.extract,
27
+ signal,
28
+ });
29
+ switch (attempt.status) {
30
+ case 'ready': {
31
+ const bytes = new TextEncoder().encode(attempt.result.text).byteLength;
32
+ const stored = await store.putExtracted(asset.attachmentId, {
33
+ text: attempt.result.text,
34
+ format: attempt.result.format,
35
+ bytes,
36
+ });
37
+ return (stored?.extraction ?? {
38
+ status: 'ready',
39
+ format: attempt.result.format,
40
+ bytes,
41
+ });
42
+ }
43
+ case 'unsupported':
44
+ await record(store, asset.attachmentId, { status: 'unsupported' });
45
+ return { status: 'unsupported' };
46
+ case 'too-large':
47
+ await record(store, asset.attachmentId, { status: 'too-large' });
48
+ return { status: 'too-large' };
49
+ case 'failed':
50
+ await record(store, asset.attachmentId, { status: 'failed', errorCode: attempt.errorCode });
51
+ return { status: 'failed', errorCode: attempt.errorCode };
52
+ }
53
+ },
54
+ };
55
+ }
56
+ /** Record a non-ready extraction status through the store (additive-safe). */
57
+ async function record(store, attachmentId, extraction) {
58
+ if (store.recordExtraction) {
59
+ await store.recordExtraction(attachmentId, extraction);
60
+ }
61
+ }
62
+ //# sourceMappingURL=pipeline-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-extractor.js","sourceRoot":"","sources":["../../src/attachments/pipeline-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAyB,MAAM,aAAa,CAAC;AAKpD;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAwB;IAChE,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,KAKb;YACC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;YAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;YACtE,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC;gBACtC,IAAI,EAAE,KAAK,CAAC,QAAQ;gBACpB,SAAS;gBACT,IAAI;gBACJ,IAAI,EAAE,MAAM,CAAC,OAAO;gBACpB,MAAM;aACP,CAAC,CAAC;YACH,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;gBACvB,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC;oBACvE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,EAAE;wBAC1D,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;wBACzB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;wBAC7B,KAAK;qBACN,CAAC,CAAC;oBACH,OAAO,CACL,MAAM,EAAE,UAAU,IAAI;wBACpB,MAAM,EAAE,OAAgB;wBACxB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;wBAC7B,KAAK;qBACN,CACF,CAAC;gBACJ,CAAC;gBACD,KAAK,aAAa;oBAChB,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;oBACnE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;gBACnC,KAAK,WAAW;oBACd,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;oBACjE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;gBACjC,KAAK,QAAQ;oBACX,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;oBAC5F,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;YAC9D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,MAAM,CACnB,KAA+B,EAC/B,YAAoB,EACpB,UAA2B;IAE3B,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC3B,MAAM,KAAK,CAAC,gBAAgB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACzD,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { MessagePart } from '@krischoichoi/channel-core';
2
+ import type { ChannelInboundAssetStore } from './store.js';
3
+ import type { StoredAssetDescriptor, AssetExtraction, StoredChannelAsset } from './types.js';
4
+ export interface AttachmentContext {
5
+ sessionId: string;
6
+ channelId: string;
7
+ accountId: string;
8
+ conversationId: string;
9
+ conversationType?: 'dm' | 'group';
10
+ threadId?: string;
11
+ messageId: string;
12
+ }
13
+ export interface AttachmentExtractorHook {
14
+ extract(input: {
15
+ store: ChannelInboundAssetStore;
16
+ asset: StoredChannelAsset;
17
+ data: Uint8Array;
18
+ signal?: AbortSignal;
19
+ }): Promise<AssetExtraction>;
20
+ }
21
+ export interface StoreBinaryPartOptions {
22
+ extractor?: AttachmentExtractorHook;
23
+ signal?: AbortSignal;
24
+ }
25
+ /** Store one channel-neutral file/audio/video part and optionally extract it. */
26
+ export declare function storeBinaryPart(store: ChannelInboundAssetStore, context: AttachmentContext, part: Extract<MessagePart, {
27
+ type: 'file' | 'audio' | 'video';
28
+ }>, options?: StoreBinaryPartOptions): Promise<StoredAssetDescriptor | undefined>;
29
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/attachments/pipeline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EACV,qBAAqB,EAErB,eAAe,EACf,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,KAAK,EAAE;QACb,KAAK,EAAE,wBAAwB,CAAC;QAChC,KAAK,EAAE,kBAAkB,CAAC;QAC1B,IAAI,EAAE,UAAU,CAAC;QACjB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,uBAAuB,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAMD,iFAAiF;AACjF,wBAAsB,eAAe,CACnC,KAAK,EAAE,wBAAwB,EAC/B,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;CAAE,CAAC,EAChE,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAkD5C"}