@fortemi/core 2026.7.1 → 2026.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/aiwg-index.d.ts +48 -7
- package/dist/aiwg-index.js +184 -10
- package/dist/aiwg-index.js.map +1 -1
- package/dist/index.d.ts +69 -32
- package/dist/index.js +346 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -596,6 +596,19 @@ var migration0009 = {
|
|
|
596
596
|
`
|
|
597
597
|
};
|
|
598
598
|
|
|
599
|
+
// src/migrations/0010_attachment_text_metadata.ts
|
|
600
|
+
var migration0010 = {
|
|
601
|
+
version: 10,
|
|
602
|
+
name: "0010_attachment_text_metadata",
|
|
603
|
+
sql: `
|
|
604
|
+
ALTER TABLE attachment
|
|
605
|
+
ADD COLUMN IF NOT EXISTS mime_type TEXT;
|
|
606
|
+
|
|
607
|
+
ALTER TABLE attachment
|
|
608
|
+
ADD COLUMN IF NOT EXISTS extracted_text TEXT;
|
|
609
|
+
`
|
|
610
|
+
};
|
|
611
|
+
|
|
599
612
|
// src/migrations/index.ts
|
|
600
613
|
var allMigrations = [
|
|
601
614
|
migration0001,
|
|
@@ -606,7 +619,8 @@ var allMigrations = [
|
|
|
606
619
|
migration0006,
|
|
607
620
|
migration0007,
|
|
608
621
|
migration0008,
|
|
609
|
-
migration0009
|
|
622
|
+
migration0009,
|
|
623
|
+
migration0010
|
|
610
624
|
];
|
|
611
625
|
|
|
612
626
|
// src/data-archive.ts
|
|
@@ -1119,6 +1133,16 @@ function buildNoteConditions(options, startIdx, includeDeleted = false) {
|
|
|
1119
1133
|
}
|
|
1120
1134
|
|
|
1121
1135
|
// src/repositories/embedding-sets-repository.ts
|
|
1136
|
+
var ATTACHMENT_TEXT_JOIN = `
|
|
1137
|
+
LEFT JOIN (
|
|
1138
|
+
SELECT note_id,
|
|
1139
|
+
string_agg(extracted_text, ' ' ORDER BY position, created_at)
|
|
1140
|
+
FILTER (WHERE extracted_text IS NOT NULL AND extracted_text <> '') as extracted_text
|
|
1141
|
+
FROM attachment
|
|
1142
|
+
WHERE deleted_at IS NULL
|
|
1143
|
+
GROUP BY note_id
|
|
1144
|
+
) ax ON ax.note_id = n.id`;
|
|
1145
|
+
var COMBINED_TEXT_VECTOR_SQL = `to_tsvector('english', (coalesce(c.content, '') || ' ' || coalesce(ax.extracted_text, '')))`;
|
|
1122
1146
|
var DEFAULT_COMPATIBILITY = {
|
|
1123
1147
|
model: "require-same",
|
|
1124
1148
|
dimension: "require-same",
|
|
@@ -1498,7 +1522,7 @@ var EmbeddingSetsRepository = class {
|
|
|
1498
1522
|
params.push(criteria.updatedBefore);
|
|
1499
1523
|
}
|
|
1500
1524
|
if (criteria.query?.trim()) {
|
|
1501
|
-
conditions.push(`(n.tsv @@ plainto_tsquery('english', $${idx}) OR
|
|
1525
|
+
conditions.push(`(n.tsv @@ plainto_tsquery('english', $${idx}) OR ${COMBINED_TEXT_VECTOR_SQL} @@ plainto_tsquery('english', $${idx}))`);
|
|
1502
1526
|
params.push(criteria.query);
|
|
1503
1527
|
}
|
|
1504
1528
|
const result = await this.db.query(
|
|
@@ -1506,6 +1530,7 @@ var EmbeddingSetsRepository = class {
|
|
|
1506
1530
|
FROM embedding e
|
|
1507
1531
|
JOIN note n ON n.id = e.note_id
|
|
1508
1532
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1533
|
+
${ATTACHMENT_TEXT_JOIN}
|
|
1509
1534
|
WHERE ${conditions.join(" AND ")}
|
|
1510
1535
|
ORDER BY e.note_id, e.created_at DESC`,
|
|
1511
1536
|
params
|
|
@@ -1623,6 +1648,17 @@ var EmbeddingSetsRepository = class {
|
|
|
1623
1648
|
};
|
|
1624
1649
|
|
|
1625
1650
|
// src/repositories/search-repository.ts
|
|
1651
|
+
var ATTACHMENT_TEXT_JOIN2 = `
|
|
1652
|
+
LEFT JOIN (
|
|
1653
|
+
SELECT note_id,
|
|
1654
|
+
string_agg(extracted_text, ' ' ORDER BY position, created_at)
|
|
1655
|
+
FILTER (WHERE extracted_text IS NOT NULL AND extracted_text <> '') as extracted_text
|
|
1656
|
+
FROM attachment
|
|
1657
|
+
WHERE deleted_at IS NULL
|
|
1658
|
+
GROUP BY note_id
|
|
1659
|
+
) ax ON ax.note_id = n.id`;
|
|
1660
|
+
var COMBINED_TEXT_SQL = `(coalesce(c.content, '') || ' ' || coalesce(ax.extracted_text, ''))`;
|
|
1661
|
+
var COMBINED_TEXT_VECTOR_SQL2 = `to_tsvector('english', ${COMBINED_TEXT_SQL})`;
|
|
1626
1662
|
var SearchRepository = class {
|
|
1627
1663
|
constructor(db, semanticAvailable = false) {
|
|
1628
1664
|
this.db = db;
|
|
@@ -1707,7 +1743,7 @@ var SearchRepository = class {
|
|
|
1707
1743
|
const { conditions, params, nextIdx } = buildNoteConditions(options, 2);
|
|
1708
1744
|
conditions.unshift(
|
|
1709
1745
|
`(n.tsv @@ ${tsqFn}('english', $1) OR
|
|
1710
|
-
|
|
1746
|
+
${COMBINED_TEXT_VECTOR_SQL2} @@ ${tsqFn}('english', $1))`
|
|
1711
1747
|
);
|
|
1712
1748
|
let paramIdx = this.scopeToResolvedEmbeddingSet(conditions, params, nextIdx, resolvedEmbeddingSet);
|
|
1713
1749
|
const allParams = [query, ...params];
|
|
@@ -1716,6 +1752,7 @@ var SearchRepository = class {
|
|
|
1716
1752
|
`SELECT COUNT(*) as count
|
|
1717
1753
|
FROM note n
|
|
1718
1754
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1755
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1719
1756
|
WHERE ${where}`,
|
|
1720
1757
|
allParams
|
|
1721
1758
|
);
|
|
@@ -1724,17 +1761,18 @@ var SearchRepository = class {
|
|
|
1724
1761
|
const result = await this.db.query(
|
|
1725
1762
|
`SELECT n.id, n.title, n.created_at, n.updated_at,
|
|
1726
1763
|
ts_rank(
|
|
1727
|
-
setweight(n.tsv, 'A') || setweight(
|
|
1764
|
+
setweight(n.tsv, 'A') || setweight(${COMBINED_TEXT_VECTOR_SQL2}, 'B'),
|
|
1728
1765
|
${tsqFn}('english', $1)
|
|
1729
1766
|
) as rank,
|
|
1730
1767
|
ts_headline(
|
|
1731
1768
|
'english',
|
|
1732
|
-
|
|
1769
|
+
${COMBINED_TEXT_SQL},
|
|
1733
1770
|
${tsqFn}('english', $1),
|
|
1734
1771
|
'StartSel=<mark>, StopSel=</mark>, MaxWords=35, MinWords=15'
|
|
1735
1772
|
) as snippet
|
|
1736
1773
|
FROM note n
|
|
1737
1774
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1775
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1738
1776
|
WHERE ${where}
|
|
1739
1777
|
ORDER BY rank DESC, n.created_at DESC
|
|
1740
1778
|
LIMIT $${paramIdx++} OFFSET $${paramIdx++}`,
|
|
@@ -1750,6 +1788,7 @@ var SearchRepository = class {
|
|
|
1750
1788
|
const idsResult = await this.db.query(
|
|
1751
1789
|
`SELECT n.id FROM note n
|
|
1752
1790
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1791
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1753
1792
|
WHERE ${where}`,
|
|
1754
1793
|
allParams
|
|
1755
1794
|
);
|
|
@@ -1796,10 +1835,11 @@ var SearchRepository = class {
|
|
|
1796
1835
|
const result = await this.db.query(
|
|
1797
1836
|
`SELECT n.id, n.title, n.created_at, n.updated_at,
|
|
1798
1837
|
(e.vector <=> $${vecIdx}::vector) as distance,
|
|
1799
|
-
LEFT(
|
|
1838
|
+
LEFT(${COMBINED_TEXT_SQL}, 200) as snippet
|
|
1800
1839
|
FROM embedding e
|
|
1801
1840
|
JOIN note n ON n.id = e.note_id
|
|
1802
1841
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1842
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1803
1843
|
WHERE ${where}
|
|
1804
1844
|
ORDER BY e.vector <=> $${vecIdx}::vector ASC
|
|
1805
1845
|
LIMIT $${limIdx} OFFSET $${offIdx}`,
|
|
@@ -1840,7 +1880,7 @@ var SearchRepository = class {
|
|
|
1840
1880
|
const textConditions = [
|
|
1841
1881
|
...textCond.conditions,
|
|
1842
1882
|
`(n.tsv @@ ${tsqFn}('english', $1) OR
|
|
1843
|
-
|
|
1883
|
+
${COMBINED_TEXT_VECTOR_SQL2} @@ ${tsqFn}('english', $1))`
|
|
1844
1884
|
];
|
|
1845
1885
|
this.scopeToResolvedEmbeddingSet(textConditions, textCond.params, textCond.nextIdx, resolvedEmbeddingSet);
|
|
1846
1886
|
const textWhere = textConditions.join(" AND ");
|
|
@@ -1848,11 +1888,12 @@ var SearchRepository = class {
|
|
|
1848
1888
|
const textResult = await this.db.query(
|
|
1849
1889
|
`SELECT n.id,
|
|
1850
1890
|
ts_rank(
|
|
1851
|
-
setweight(n.tsv, 'A') || setweight(
|
|
1891
|
+
setweight(n.tsv, 'A') || setweight(${COMBINED_TEXT_VECTOR_SQL2}, 'B'),
|
|
1852
1892
|
${tsqFn}('english', $1)
|
|
1853
1893
|
) as rank
|
|
1854
1894
|
FROM note n
|
|
1855
1895
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1896
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1856
1897
|
WHERE ${textWhere}
|
|
1857
1898
|
ORDER BY rank DESC
|
|
1858
1899
|
LIMIT 100`,
|
|
@@ -1886,9 +1927,10 @@ var SearchRepository = class {
|
|
|
1886
1927
|
}
|
|
1887
1928
|
const noteResult = await this.db.query(
|
|
1888
1929
|
`SELECT n.id, n.title, n.created_at, n.updated_at,
|
|
1889
|
-
LEFT(
|
|
1930
|
+
LEFT(${COMBINED_TEXT_SQL}, 200) as snippet
|
|
1890
1931
|
FROM note n
|
|
1891
1932
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1933
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1892
1934
|
WHERE n.id = ANY($1)`,
|
|
1893
1935
|
[pageIds]
|
|
1894
1936
|
);
|
|
@@ -1936,9 +1978,10 @@ var SearchRepository = class {
|
|
|
1936
1978
|
const listParams = [...params, limit, offset];
|
|
1937
1979
|
const result = await this.db.query(
|
|
1938
1980
|
`SELECT n.id, n.title, n.created_at, n.updated_at,
|
|
1939
|
-
LEFT(
|
|
1981
|
+
LEFT(${COMBINED_TEXT_SQL}, 200) as snippet
|
|
1940
1982
|
FROM note n
|
|
1941
1983
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
1984
|
+
${ATTACHMENT_TEXT_JOIN2}
|
|
1942
1985
|
WHERE ${where}
|
|
1943
1986
|
ORDER BY n.created_at DESC
|
|
1944
1987
|
LIMIT $${paramIdx++} OFFSET $${paramIdx++}`,
|
|
@@ -3811,6 +3854,29 @@ function getLlmFunction() {
|
|
|
3811
3854
|
return llmFn;
|
|
3812
3855
|
}
|
|
3813
3856
|
|
|
3857
|
+
// src/repositories/note-text.ts
|
|
3858
|
+
async function getNoteTextWithExtractedAttachments(db, noteId) {
|
|
3859
|
+
const result = await db.query(
|
|
3860
|
+
`SELECT c.content,
|
|
3861
|
+
COALESCE(string_agg(a.extracted_text, E'
|
|
3862
|
+
' ORDER BY a.position, a.created_at)
|
|
3863
|
+
FILTER (WHERE a.extracted_text IS NOT NULL AND a.extracted_text <> ''), '') as extracted_text
|
|
3864
|
+
FROM note_revised_current c
|
|
3865
|
+
LEFT JOIN attachment a ON a.note_id = c.note_id AND a.deleted_at IS NULL
|
|
3866
|
+
WHERE c.note_id = $1
|
|
3867
|
+
GROUP BY c.content`,
|
|
3868
|
+
[noteId]
|
|
3869
|
+
);
|
|
3870
|
+
if (result.rows.length === 0) return null;
|
|
3871
|
+
const content = result.rows[0].content;
|
|
3872
|
+
const extractedText = result.rows[0].extracted_text ?? "";
|
|
3873
|
+
return {
|
|
3874
|
+
content,
|
|
3875
|
+
extracted_text: extractedText,
|
|
3876
|
+
combined: [content, extractedText].filter(Boolean).join("\n")
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3814
3880
|
// src/job-queue-worker.ts
|
|
3815
3881
|
var JOB_PRIORITIES = {
|
|
3816
3882
|
ai_revision: 1,
|
|
@@ -4092,12 +4158,9 @@ function conceptTaggingHandler(job, db) {
|
|
|
4092
4158
|
return (async () => {
|
|
4093
4159
|
const llmFn2 = getLlmFunction();
|
|
4094
4160
|
if (!llmFn2) return { skipped: true, reason: "no LLM function registered" };
|
|
4095
|
-
const
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
);
|
|
4099
|
-
if (result.rows.length === 0) throw new Error(`No content found for note ${job.note_id}`);
|
|
4100
|
-
const content = result.rows[0].content;
|
|
4161
|
+
const noteText = await getNoteTextWithExtractedAttachments(db, job.note_id);
|
|
4162
|
+
if (!noteText) throw new Error(`No content found for note ${job.note_id}`);
|
|
4163
|
+
const content = noteText.combined;
|
|
4101
4164
|
const prompt = `Task: Extract 3-5 topic tags from the text below.
|
|
4102
4165
|
Rules:
|
|
4103
4166
|
- Each tag is 1-3 words, lowercase
|
|
@@ -4990,9 +5053,17 @@ var AttachmentsRepository = class {
|
|
|
4990
5053
|
}
|
|
4991
5054
|
const attachmentId = generateId();
|
|
4992
5055
|
await this.db.query(
|
|
4993
|
-
`INSERT INTO attachment (id, note_id, blob_id, filename, display_name)
|
|
4994
|
-
VALUES ($1, $2, $3, $4, $5)`,
|
|
4995
|
-
[
|
|
5056
|
+
`INSERT INTO attachment (id, note_id, blob_id, filename, display_name, mime_type, extracted_text)
|
|
5057
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
5058
|
+
[
|
|
5059
|
+
attachmentId,
|
|
5060
|
+
input.noteId,
|
|
5061
|
+
blobId,
|
|
5062
|
+
input.filename,
|
|
5063
|
+
input.displayName ?? null,
|
|
5064
|
+
input.mimeType ?? null,
|
|
5065
|
+
input.extractedText ?? null
|
|
5066
|
+
]
|
|
4996
5067
|
);
|
|
4997
5068
|
return this.get(attachmentId);
|
|
4998
5069
|
}
|
|
@@ -5055,6 +5126,7 @@ var ManageAttachmentsInputSchema = z.object({
|
|
|
5055
5126
|
data_base64: z.string().optional(),
|
|
5056
5127
|
filename: z.string().optional(),
|
|
5057
5128
|
mime_type: z.string().optional(),
|
|
5129
|
+
extracted_text: z.string().optional(),
|
|
5058
5130
|
display_name: z.string().optional()
|
|
5059
5131
|
});
|
|
5060
5132
|
async function manageAttachments(db, blobStore, rawInput) {
|
|
@@ -5073,6 +5145,7 @@ async function manageAttachments(db, blobStore, rawInput) {
|
|
|
5073
5145
|
data,
|
|
5074
5146
|
filename: input.filename,
|
|
5075
5147
|
mimeType: input.mime_type,
|
|
5148
|
+
extractedText: input.extracted_text,
|
|
5076
5149
|
displayName: input.display_name
|
|
5077
5150
|
});
|
|
5078
5151
|
return { action: "attach", attachment, size_bytes: data.length };
|
|
@@ -5324,12 +5397,9 @@ function averageEmbeddings(embeddings) {
|
|
|
5324
5397
|
async function embeddingGenerationHandler(job, db) {
|
|
5325
5398
|
const fn = embedFn;
|
|
5326
5399
|
if (!fn) return { skipped: true, reason: "no embed function registered" };
|
|
5327
|
-
const
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
);
|
|
5331
|
-
if (noteResult.rows.length === 0) throw new Error(`No content for note ${job.note_id}`);
|
|
5332
|
-
const content = noteResult.rows[0].content;
|
|
5400
|
+
const noteText = await getNoteTextWithExtractedAttachments(db, job.note_id);
|
|
5401
|
+
if (!noteText) throw new Error(`No content for note ${job.note_id}`);
|
|
5402
|
+
const content = noteText.combined;
|
|
5333
5403
|
const chunks = chunkText(content);
|
|
5334
5404
|
const embeddings = await fn(chunks);
|
|
5335
5405
|
const vector2 = averageEmbeddings(embeddings);
|
|
@@ -6476,8 +6546,25 @@ function packTarGz(files) {
|
|
|
6476
6546
|
const tarData = encodeTar(files);
|
|
6477
6547
|
return gzipSync(tarData);
|
|
6478
6548
|
}
|
|
6479
|
-
|
|
6549
|
+
var DEFAULT_MAX_DECOMPRESSED_BYTES = 256 * 1024 * 1024;
|
|
6550
|
+
function unpackTarGz(data, opts) {
|
|
6551
|
+
const cap = opts?.maxDecompressedBytes ?? DEFAULT_MAX_DECOMPRESSED_BYTES;
|
|
6552
|
+
if (data.byteLength < 18) {
|
|
6553
|
+
throw new Error("Invalid gzip archive: too short");
|
|
6554
|
+
}
|
|
6555
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
6556
|
+
const declaredSize = view.getUint32(data.byteLength - 4, true);
|
|
6557
|
+
if (declaredSize > cap) {
|
|
6558
|
+
throw new Error(
|
|
6559
|
+
"Refusing to decompress archive: declared size " + declaredSize + " exceeds cap " + cap + " bytes"
|
|
6560
|
+
);
|
|
6561
|
+
}
|
|
6480
6562
|
const tarData = gunzipSync(data);
|
|
6563
|
+
if (tarData.byteLength > cap) {
|
|
6564
|
+
throw new Error(
|
|
6565
|
+
"Refusing to decompress archive: decompressed size " + tarData.byteLength + " exceeds cap " + cap + " bytes"
|
|
6566
|
+
);
|
|
6567
|
+
}
|
|
6481
6568
|
return decodeTar(tarData);
|
|
6482
6569
|
}
|
|
6483
6570
|
|
|
@@ -6512,6 +6599,7 @@ function noteToShard(note) {
|
|
|
6512
6599
|
title: note.title,
|
|
6513
6600
|
original_content: note.original_content,
|
|
6514
6601
|
revised_content: note.revised_content,
|
|
6602
|
+
...note.binary_sources?.length ? { binary_sources: note.binary_sources } : {},
|
|
6515
6603
|
format: note.format,
|
|
6516
6604
|
source: note.source,
|
|
6517
6605
|
starred: note.is_starred,
|
|
@@ -6532,6 +6620,7 @@ function noteFromShard(shard) {
|
|
|
6532
6620
|
is_archived: shard.archived,
|
|
6533
6621
|
original_content: shard.original_content,
|
|
6534
6622
|
revised_content: shard.revised_content,
|
|
6623
|
+
binary_sources: shard.binary_sources,
|
|
6535
6624
|
tags: shard.tags,
|
|
6536
6625
|
created_at: shard.created_at,
|
|
6537
6626
|
updated_at: shard.updated_at,
|
|
@@ -6789,9 +6878,40 @@ async function exportShard(db, options) {
|
|
|
6789
6878
|
tags.push(row.tag);
|
|
6790
6879
|
tagsByNote.set(row.note_id, tags);
|
|
6791
6880
|
}
|
|
6881
|
+
const attachmentRows = await db.query(
|
|
6882
|
+
`SELECT a.note_id,
|
|
6883
|
+
a.id,
|
|
6884
|
+
a.filename,
|
|
6885
|
+
a.mime_type,
|
|
6886
|
+
a.extracted_text,
|
|
6887
|
+
b.content_hash,
|
|
6888
|
+
b.size_bytes,
|
|
6889
|
+
b.storage_path
|
|
6890
|
+
FROM attachment a
|
|
6891
|
+
JOIN attachment_blob b ON b.id = a.blob_id
|
|
6892
|
+
WHERE a.deleted_at IS NULL
|
|
6893
|
+
ORDER BY a.note_id, a.position, a.created_at`
|
|
6894
|
+
);
|
|
6895
|
+
const binarySourcesByNote = /* @__PURE__ */ new Map();
|
|
6896
|
+
for (const row of attachmentRows.rows) {
|
|
6897
|
+
const source = {
|
|
6898
|
+
extracted_text: row.extracted_text ?? "",
|
|
6899
|
+
attachment: {
|
|
6900
|
+
id: row.id,
|
|
6901
|
+
path: row.storage_path ?? row.filename,
|
|
6902
|
+
mime: row.mime_type,
|
|
6903
|
+
checksum: row.content_hash,
|
|
6904
|
+
bytes: Number(row.size_bytes)
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
const sources = binarySourcesByNote.get(row.note_id) ?? [];
|
|
6908
|
+
sources.push(source);
|
|
6909
|
+
binarySourcesByNote.set(row.note_id, sources);
|
|
6910
|
+
}
|
|
6792
6911
|
const notes = noteRows.rows.map((row) => ({
|
|
6793
6912
|
...row,
|
|
6794
|
-
tags: tagsByNote.get(row.id) ?? []
|
|
6913
|
+
tags: tagsByNote.get(row.id) ?? [],
|
|
6914
|
+
binary_sources: binarySourcesByNote.get(row.id)
|
|
6795
6915
|
}));
|
|
6796
6916
|
const exportedNoteIds = new Set(notes.map((n) => n.id));
|
|
6797
6917
|
const shardNotes = notes.map((n) => noteToShard(n));
|
|
@@ -7071,6 +7191,8 @@ async function importShard(db, data, options) {
|
|
|
7071
7191
|
const report = options?.onProgress;
|
|
7072
7192
|
const warnings = [];
|
|
7073
7193
|
const errors = [];
|
|
7194
|
+
let droppedAttachmentCount = 0;
|
|
7195
|
+
let notesWithDroppedAttachments = 0;
|
|
7074
7196
|
const counts = {
|
|
7075
7197
|
notes: 0,
|
|
7076
7198
|
collections: 0,
|
|
@@ -7305,10 +7427,19 @@ async function importShard(db, data, options) {
|
|
|
7305
7427
|
[generateId(), note.id, tag]
|
|
7306
7428
|
);
|
|
7307
7429
|
}
|
|
7430
|
+
if (note.binary_sources?.length) {
|
|
7431
|
+
droppedAttachmentCount += note.binary_sources.length;
|
|
7432
|
+
notesWithDroppedAttachments++;
|
|
7433
|
+
}
|
|
7308
7434
|
counts.notes++;
|
|
7309
7435
|
report?.({ phase: "notes", done: index + 1, total: parsedNotes.length });
|
|
7310
7436
|
await maybeYield2(index + 1, batchSize);
|
|
7311
7437
|
}
|
|
7438
|
+
if (droppedAttachmentCount > 0) {
|
|
7439
|
+
warnings.push(
|
|
7440
|
+
`${droppedAttachmentCount} attachment(s) across ${notesWithDroppedAttachments} note(s) were not imported: shards currently carry attachment references (metadata + checksum) but not the binary content, so attachment bytes cannot be restored. Tracking: #237 (attachment round-trip) / server #1013 (binary contract).`
|
|
7441
|
+
);
|
|
7442
|
+
}
|
|
7312
7443
|
const totalSkos = parsedSkosSchemes.length + parsedSkosConcepts.length + parsedSkosRelations.length + parsedNoteSkosTags.length;
|
|
7313
7444
|
let doneSkos = 0;
|
|
7314
7445
|
report?.({ phase: "skos", done: doneSkos, total: totalSkos });
|
|
@@ -7585,6 +7716,11 @@ function parseJsonArray(data) {
|
|
|
7585
7716
|
|
|
7586
7717
|
// src/shard/shard-reader.ts
|
|
7587
7718
|
var decoder2 = new TextDecoder();
|
|
7719
|
+
function assertSafeComponentName(filename) {
|
|
7720
|
+
if (filename.length === 0 || filename.startsWith("/") || filename.includes("\\") || filename.includes("\0") || filename.includes(":") || filename.split("/").some((segment) => segment === "..")) {
|
|
7721
|
+
throw new Error(`Refusing to read unsafe shard component path: ${JSON.stringify(filename)}`);
|
|
7722
|
+
}
|
|
7723
|
+
}
|
|
7588
7724
|
function parseJsonlBytes(data) {
|
|
7589
7725
|
if (!data || data.byteLength === 0) return [];
|
|
7590
7726
|
return decoder2.decode(data).split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
@@ -7620,6 +7756,7 @@ var UrlComponentStore = class {
|
|
|
7620
7756
|
this.manifest = manifest;
|
|
7621
7757
|
}
|
|
7622
7758
|
async read(filename) {
|
|
7759
|
+
assertSafeComponentName(filename);
|
|
7623
7760
|
if (this.cache.has(filename)) return this.cache.get(filename);
|
|
7624
7761
|
const response = await this.fetchImpl(`${this.baseUrl}/${filename}`);
|
|
7625
7762
|
if (!response.ok) {
|
|
@@ -7653,7 +7790,8 @@ function tokenize(query) {
|
|
|
7653
7790
|
return query.toLowerCase().split(/[^a-z0-9]+/i).filter((token) => token.length > 0);
|
|
7654
7791
|
}
|
|
7655
7792
|
function noteSearchText(note) {
|
|
7656
|
-
|
|
7793
|
+
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7794
|
+
return `${note.title ?? ""} ${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.toLowerCase();
|
|
7657
7795
|
}
|
|
7658
7796
|
function countOccurrences(haystack, needle) {
|
|
7659
7797
|
if (!needle) return 0;
|
|
@@ -7674,7 +7812,8 @@ function noteMatchesTokens(note, tokens) {
|
|
|
7674
7812
|
function rankNote(note, tokens, weights) {
|
|
7675
7813
|
if (tokens.length === 0) return 0;
|
|
7676
7814
|
const title = (note.title ?? "").toLowerCase();
|
|
7677
|
-
const
|
|
7815
|
+
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7816
|
+
const content = `${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.toLowerCase();
|
|
7678
7817
|
const tagText = note.tags.join(" ").toLowerCase();
|
|
7679
7818
|
let score = 0;
|
|
7680
7819
|
for (const token of tokens) {
|
|
@@ -7685,7 +7824,8 @@ function rankNote(note, tokens, weights) {
|
|
|
7685
7824
|
return score;
|
|
7686
7825
|
}
|
|
7687
7826
|
function makeSnippet(note, tokens, length) {
|
|
7688
|
-
const
|
|
7827
|
+
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7828
|
+
const content = `${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.trim();
|
|
7689
7829
|
if (tokens.length === 0) return content.slice(0, length);
|
|
7690
7830
|
const lower = content.toLowerCase();
|
|
7691
7831
|
const firstAt = tokens.map((token) => lower.indexOf(token)).filter((index) => index !== -1).sort((a, b) => a - b)[0] ?? 0;
|
|
@@ -8088,6 +8228,16 @@ var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
|
8088
8228
|
"concepts",
|
|
8089
8229
|
"privacy"
|
|
8090
8230
|
];
|
|
8231
|
+
function isPrivacyExcluded(record, options) {
|
|
8232
|
+
const privacy = record.privacy;
|
|
8233
|
+
if (!privacy) return false;
|
|
8234
|
+
if (privacy.classification === "private" && !options?.includePrivate) return true;
|
|
8235
|
+
if (privacy.pii && !options?.includePii) return true;
|
|
8236
|
+
return false;
|
|
8237
|
+
}
|
|
8238
|
+
function filterAiwgRecordsByPrivacy(records, options) {
|
|
8239
|
+
return records.filter((record) => !isPrivacyExcluded(record, options));
|
|
8240
|
+
}
|
|
8091
8241
|
var REQUIRED_RECORD_FIELDS = [
|
|
8092
8242
|
"schema_version",
|
|
8093
8243
|
"id",
|
|
@@ -8114,8 +8264,12 @@ function hasString(value) {
|
|
|
8114
8264
|
return typeof value === "string" && value.length > 0;
|
|
8115
8265
|
}
|
|
8116
8266
|
function pushFacet(counts, name, value) {
|
|
8117
|
-
counts[name]
|
|
8118
|
-
|
|
8267
|
+
let bucket = counts[name];
|
|
8268
|
+
if (bucket === void 0) {
|
|
8269
|
+
bucket = /* @__PURE__ */ Object.create(null);
|
|
8270
|
+
counts[name] = bucket;
|
|
8271
|
+
}
|
|
8272
|
+
bucket[value] = (bucket[value] ?? 0) + 1;
|
|
8119
8273
|
}
|
|
8120
8274
|
function hasNonNegativeInteger(value) {
|
|
8121
8275
|
return Number.isInteger(value) && typeof value === "number" && value >= 0;
|
|
@@ -8237,6 +8391,57 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8237
8391
|
errors.push("items[" + index + "].compatibility must be an object");
|
|
8238
8392
|
}
|
|
8239
8393
|
}
|
|
8394
|
+
function isPrivacyClassification(value) {
|
|
8395
|
+
return value === "private" || value === "sanitized" || value === "public";
|
|
8396
|
+
}
|
|
8397
|
+
function isProvenanceConfidence(value) {
|
|
8398
|
+
return value === "source" || value === "candidate" || value === "reviewed" || value === "rejected";
|
|
8399
|
+
}
|
|
8400
|
+
function validateProvenanceItems(item, index, errors) {
|
|
8401
|
+
if (!Array.isArray(item.provenance)) return;
|
|
8402
|
+
for (const [provIndex, prov] of item.provenance.entries()) {
|
|
8403
|
+
const at = "items[" + index + "].provenance[" + provIndex + "]";
|
|
8404
|
+
if (!isPlainRecord(prov)) {
|
|
8405
|
+
errors.push(at + " must be an object");
|
|
8406
|
+
continue;
|
|
8407
|
+
}
|
|
8408
|
+
if (!hasString(prov.field)) errors.push(at + ".field is required");
|
|
8409
|
+
if (!hasString(prov.source)) errors.push(at + ".source is required");
|
|
8410
|
+
if (!hasString(prov.path)) errors.push(at + ".path is required");
|
|
8411
|
+
if (!isProvenanceConfidence(prov.confidence)) errors.push(at + ".confidence must be one of source, candidate, reviewed, rejected");
|
|
8412
|
+
if (!isPrivacyClassification(prov.privacy)) errors.push(at + ".privacy must be one of private, sanitized, public");
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
var V2_ONLY_RECORD_FIELDS = ["search", "chunks", "embeddings", "skos_concepts", "skos_relations", "compatibility"];
|
|
8416
|
+
var V2_ONLY_SOURCE_FIELDS = ["origin", "generated", "checksum", "updated_at"];
|
|
8417
|
+
var V2_ONLY_RELATIONSHIP_FIELDS = ["target_path", "direction", "metadata"];
|
|
8418
|
+
function forbidV2FieldsOnV1Record(item, index, errors) {
|
|
8419
|
+
if (item.schema_version !== "aiwg.fortemi.index.record.v1") return;
|
|
8420
|
+
const at = "items[" + index + "]";
|
|
8421
|
+
const bag = item;
|
|
8422
|
+
const v2msg = " is a v2-only field and must be absent on a record.v1 record";
|
|
8423
|
+
for (const field of V2_ONLY_RECORD_FIELDS) {
|
|
8424
|
+
if (bag[field] !== void 0) errors.push(at + "." + field + v2msg);
|
|
8425
|
+
}
|
|
8426
|
+
if (isPlainRecord(item.source)) {
|
|
8427
|
+
const src = item.source;
|
|
8428
|
+
for (const field of V2_ONLY_SOURCE_FIELDS) {
|
|
8429
|
+
if (src[field] !== void 0) errors.push(at + ".source." + field + v2msg);
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
if (isPlainRecord(item.privacy) && item.privacy.locality !== void 0) {
|
|
8433
|
+
errors.push(at + ".privacy.locality" + v2msg);
|
|
8434
|
+
}
|
|
8435
|
+
if (Array.isArray(item.relationships)) {
|
|
8436
|
+
for (const [relIndex, rel] of item.relationships.entries()) {
|
|
8437
|
+
if (!isPlainRecord(rel)) continue;
|
|
8438
|
+
const relBag = rel;
|
|
8439
|
+
for (const field of V2_ONLY_RELATIONSHIP_FIELDS) {
|
|
8440
|
+
if (relBag[field] !== void 0) errors.push(at + ".relationships[" + relIndex + "]." + field + v2msg);
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
}
|
|
8444
|
+
}
|
|
8240
8445
|
function validateAiwgFortemiIndexExport(value) {
|
|
8241
8446
|
const errors = [];
|
|
8242
8447
|
const counts = {};
|
|
@@ -8247,10 +8452,21 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
8247
8452
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
8248
8453
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
8249
8454
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
8455
|
+
else if (!isPrivacyClassification(data?.source?.privacy)) {
|
|
8456
|
+
errors.push("source.privacy must be one of private, sanitized, public");
|
|
8457
|
+
}
|
|
8250
8458
|
if (!Array.isArray(data?.items)) errors.push("items must be an array");
|
|
8251
8459
|
if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
|
|
8252
8460
|
errors.push("compatibility must be an object");
|
|
8253
8461
|
}
|
|
8462
|
+
if (data?.schema_version === "aiwg.fortemi.index.export.v1") {
|
|
8463
|
+
if (isPlainRecord(data.source) && data.source.graph !== void 0) {
|
|
8464
|
+
errors.push("source.graph is a v2-only field and must be absent on an export.v1 export");
|
|
8465
|
+
}
|
|
8466
|
+
if (data.compatibility !== void 0) {
|
|
8467
|
+
errors.push("compatibility is a v2-only field and must be absent on an export.v1 export");
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8254
8470
|
const ids = /* @__PURE__ */ new Set();
|
|
8255
8471
|
let previousId = "";
|
|
8256
8472
|
for (const [index, item] of (data.items ?? []).entries()) {
|
|
@@ -8285,8 +8501,12 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
8285
8501
|
errors.push("items[" + index + "].provenance must be a non-empty array");
|
|
8286
8502
|
}
|
|
8287
8503
|
validateOptionalRichMetadata(item, index, errors);
|
|
8504
|
+
validateProvenanceItems(item, index, errors);
|
|
8505
|
+
forbidV2FieldsOnV1Record(item, index, errors);
|
|
8288
8506
|
if (!item.privacy || typeof item.privacy.pii !== "boolean" || !hasString(item.privacy.classification)) {
|
|
8289
8507
|
errors.push("items[" + index + "].privacy requires classification and pii");
|
|
8508
|
+
} else if (!isPrivacyClassification(item.privacy.classification)) {
|
|
8509
|
+
errors.push("items[" + index + "].privacy.classification must be one of private, sanitized, public");
|
|
8290
8510
|
}
|
|
8291
8511
|
}
|
|
8292
8512
|
return { valid: errors.length === 0, errors, counts };
|
|
@@ -8307,6 +8527,9 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
8307
8527
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
8308
8528
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
8309
8529
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
8530
|
+
if (data?.source_export_schema_version !== void 0 && !isSupportedIndexSchemaVersion(data.source_export_schema_version)) {
|
|
8531
|
+
errors.push("source_export_schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2 when present");
|
|
8532
|
+
}
|
|
8310
8533
|
if (!hasNonNegativeInteger(data?.total)) errors.push("total must be a non-negative integer");
|
|
8311
8534
|
if (!hasPositiveInteger(data?.part_size)) errors.push("part_size must be a positive integer");
|
|
8312
8535
|
if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
|
|
@@ -8425,9 +8648,35 @@ function assertAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
8425
8648
|
}
|
|
8426
8649
|
return value;
|
|
8427
8650
|
}
|
|
8651
|
+
var ALLOWED_AIWG_FETCH_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:", "data:"]);
|
|
8652
|
+
function tryParseUrl(value) {
|
|
8653
|
+
try {
|
|
8654
|
+
return new URL(value);
|
|
8655
|
+
} catch {
|
|
8656
|
+
return null;
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
8659
|
+
function resolveAiwgFetchUrl(href, baseUrl) {
|
|
8660
|
+
if (baseUrl === void 0) {
|
|
8661
|
+
const absolute = tryParseUrl(href);
|
|
8662
|
+
if (absolute && !ALLOWED_AIWG_FETCH_SCHEMES.has(absolute.protocol)) {
|
|
8663
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + absolute.protocol);
|
|
8664
|
+
}
|
|
8665
|
+
return href;
|
|
8666
|
+
}
|
|
8667
|
+
const base = new URL(baseUrl);
|
|
8668
|
+
const resolved = new URL(href, base);
|
|
8669
|
+
if (!ALLOWED_AIWG_FETCH_SCHEMES.has(resolved.protocol)) {
|
|
8670
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + resolved.protocol);
|
|
8671
|
+
}
|
|
8672
|
+
if (resolved.origin !== base.origin) {
|
|
8673
|
+
throw new Error("Refusing cross-origin AIWG index fetch: " + resolved.origin + " != " + base.origin);
|
|
8674
|
+
}
|
|
8675
|
+
return resolved.toString();
|
|
8676
|
+
}
|
|
8428
8677
|
function createAiwgFetchChunkLoader(baseUrl) {
|
|
8429
8678
|
return async (part) => {
|
|
8430
|
-
const href =
|
|
8679
|
+
const href = resolveAiwgFetchUrl(part.href, baseUrl);
|
|
8431
8680
|
const response = await fetch(href);
|
|
8432
8681
|
if (!response.ok) throw new Error("Failed to fetch AIWG index chunk " + href + ": " + response.status);
|
|
8433
8682
|
return response.json();
|
|
@@ -8447,14 +8696,14 @@ function createAiwgFetchDetailLoader(baseUrl) {
|
|
|
8447
8696
|
return async (id, manifest) => {
|
|
8448
8697
|
if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
|
|
8449
8698
|
const relative = aiwgDetailHrefForId(manifest.detail, id);
|
|
8450
|
-
const href =
|
|
8699
|
+
const href = resolveAiwgFetchUrl(relative, baseUrl);
|
|
8451
8700
|
const response = await fetch(href);
|
|
8452
8701
|
if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
|
|
8453
8702
|
return response.json();
|
|
8454
8703
|
};
|
|
8455
8704
|
}
|
|
8456
8705
|
function getAiwgFortemiFacets(items) {
|
|
8457
|
-
const result =
|
|
8706
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
8458
8707
|
for (const item of items) {
|
|
8459
8708
|
pushFacet(result, "type", item.type);
|
|
8460
8709
|
pushFacet(result, "privacy", item.privacy.classification);
|
|
@@ -8470,7 +8719,55 @@ function recordTitle(item) {
|
|
|
8470
8719
|
return item.title ?? item.search?.title ?? item.search?.name ?? item.id;
|
|
8471
8720
|
}
|
|
8472
8721
|
function recordText(item) {
|
|
8473
|
-
|
|
8722
|
+
const base = item.text ?? item.search?.body ?? item.search?.summary ?? item.chunks?.map((chunk) => chunk.text ?? chunk.body ?? chunk.summary ?? "").filter(Boolean).join("\n") ?? "";
|
|
8723
|
+
const extractedText = "binary_sources" in item ? item.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "" : "";
|
|
8724
|
+
return [base, extractedText].filter(Boolean).join("\n");
|
|
8725
|
+
}
|
|
8726
|
+
function defaultEmbeddingInput(record, granularity) {
|
|
8727
|
+
const title = recordTitle(record);
|
|
8728
|
+
const text = recordText(record);
|
|
8729
|
+
if (granularity === "title-summary") {
|
|
8730
|
+
const extractedText = record.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "";
|
|
8731
|
+
return [title, record.search?.summary ?? "", extractedText].filter(Boolean).join("\n");
|
|
8732
|
+
}
|
|
8733
|
+
return [title, text].filter(Boolean).join("\n");
|
|
8734
|
+
}
|
|
8735
|
+
function generatedAtString(value) {
|
|
8736
|
+
if (value instanceof Date) return value.toISOString();
|
|
8737
|
+
return value ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
8738
|
+
}
|
|
8739
|
+
async function buildAiwgStaticEmbeddingSet(index, options) {
|
|
8740
|
+
assertAiwgFortemiIndexExport(index);
|
|
8741
|
+
const granularity = options.granularity ?? "body";
|
|
8742
|
+
const records = filterAiwgRecordsByPrivacy(options.records ?? index.items, options.privacy);
|
|
8743
|
+
const embeddings = [];
|
|
8744
|
+
for (const record of records) {
|
|
8745
|
+
const input = options.textForRecord?.(record) ?? defaultEmbeddingInput(record, granularity);
|
|
8746
|
+
const embedding = await options.backend.embed(input, record);
|
|
8747
|
+
if (embedding.length !== options.backend.dimensions) {
|
|
8748
|
+
throw new Error(`Embedding for ${record.id} has ${embedding.length} dimensions; expected ${options.backend.dimensions}`);
|
|
8749
|
+
}
|
|
8750
|
+
embeddings.push({
|
|
8751
|
+
record_id: record.id,
|
|
8752
|
+
embedding,
|
|
8753
|
+
granularity,
|
|
8754
|
+
input_hash: computeHash(new TextEncoder().encode(input)),
|
|
8755
|
+
source_path: record.source.path
|
|
8756
|
+
});
|
|
8757
|
+
}
|
|
8758
|
+
const embeddingSet = {
|
|
8759
|
+
schema_version: "aiwg.fortemi.embedding.set.v1",
|
|
8760
|
+
id: options.id,
|
|
8761
|
+
model: options.backend.model,
|
|
8762
|
+
dimensions: options.backend.dimensions,
|
|
8763
|
+
generated_at: generatedAtString(options.generatedAt),
|
|
8764
|
+
granularity,
|
|
8765
|
+
...options.metric ? { metric: options.metric } : {},
|
|
8766
|
+
input_hash_algorithm: "sha256",
|
|
8767
|
+
embeddings
|
|
8768
|
+
};
|
|
8769
|
+
assertAiwgStaticEmbeddingSet(embeddingSet);
|
|
8770
|
+
return embeddingSet;
|
|
8474
8771
|
}
|
|
8475
8772
|
function recordSearchValues(item) {
|
|
8476
8773
|
const search = item.search;
|
|
@@ -8503,7 +8800,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
8503
8800
|
const projection = options.projection;
|
|
8504
8801
|
const idEncoding = options.idEncoding ?? "base64url";
|
|
8505
8802
|
const detailHref = options.detailHref ?? "detail/{id}.json";
|
|
8506
|
-
const items = index.items;
|
|
8803
|
+
const items = filterAiwgRecordsByPrivacy(index.items, options.privacy);
|
|
8507
8804
|
const pad = (value) => String(value).padStart(4, "0");
|
|
8508
8805
|
const project = (record) => {
|
|
8509
8806
|
if (!projection) return record;
|
|
@@ -8531,6 +8828,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
8531
8828
|
schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
8532
8829
|
generated_at: options.generatedAt ?? index.generated_at,
|
|
8533
8830
|
source: index.source,
|
|
8831
|
+
source_export_schema_version: index.schema_version,
|
|
8534
8832
|
total: items.length,
|
|
8535
8833
|
part_size: partSize,
|
|
8536
8834
|
facets: getAiwgFortemiFacets(items),
|
|
@@ -8841,8 +9139,15 @@ function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, option
|
|
|
8841
9139
|
};
|
|
8842
9140
|
}).filter((result) => result !== null && result.score >= (options.minScore ?? -1)).sort((left, right) => right.score - left.score || left.item.id.localeCompare(right.item.id)).slice(offset, offset + limit);
|
|
8843
9141
|
}
|
|
8844
|
-
|
|
9142
|
+
var DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5e3;
|
|
9143
|
+
function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options) {
|
|
8845
9144
|
assertAiwgStaticEmbeddingSet(embeddingSet);
|
|
9145
|
+
const maxEmbeddings = options?.maxEmbeddings ?? DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS;
|
|
9146
|
+
if (embeddingSet.embeddings.length > maxEmbeddings) {
|
|
9147
|
+
throw new Error(
|
|
9148
|
+
"Embedding set too large for duplicate scan: " + embeddingSet.embeddings.length + " > " + maxEmbeddings + " (raise options.maxEmbeddings to override for trusted input)"
|
|
9149
|
+
);
|
|
9150
|
+
}
|
|
8846
9151
|
const byId = new Map(index.items.map((item) => [item.id, item]));
|
|
8847
9152
|
const pairs = [];
|
|
8848
9153
|
for (let leftIndex = 0; leftIndex < embeddingSet.embeddings.length; leftIndex += 1) {
|
|
@@ -9307,7 +9612,7 @@ function createAiwgIndexController(initialIndex) {
|
|
|
9307
9612
|
notify();
|
|
9308
9613
|
},
|
|
9309
9614
|
createReviewDecisionExport(generatedAt) {
|
|
9310
|
-
const source = index ?? (chunked ? { schema_version: "aiwg.fortemi.index.export.v1" } : null);
|
|
9615
|
+
const source = index ?? (chunked ? { schema_version: chunked.manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1" } : null);
|
|
9311
9616
|
if (!source) throw new Error("No AIWG index export or chunked manifest loaded");
|
|
9312
9617
|
return createAiwgReviewDecisionExport(source, reviewDecisions, generatedAt);
|
|
9313
9618
|
},
|
|
@@ -9364,8 +9669,8 @@ function communityIdsFor(item, options) {
|
|
|
9364
9669
|
}
|
|
9365
9670
|
|
|
9366
9671
|
// src/index.ts
|
|
9367
|
-
var VERSION = "2026.7.
|
|
9672
|
+
var VERSION = "2026.7.3";
|
|
9368
9673
|
|
|
9369
|
-
export { AIWG_SCAN_REQUIRED_FIELDS, ArchiveManager, AttachmentsRepository, CURRENT_MIGRATION_HEAD, CURRENT_SHARD_VERSION, CapabilityManager, CaptureKnowledgeInputSchema, CollectionsRepository, CommunitiesRepository, DB_SNAPSHOT_SCHEMA_VERSION, DbSnapshotVersionError, EMBED_REQUEST_KIND, EMBED_RESPONSE_KIND, EmbeddingSetsRepository, FallbackRouter, FortemiToolManifest, GetNoteInputSchema, GraphRepository, JOB_CAPABILITIES, JOB_PRIORITIES, JobQueueWorker, LOCAL_ENDPOINTS, LinksRepository, ListNotesInputSchema, ManageArchiveInputSchema, ManageAttachmentsInputSchema, ManageCapabilitiesInputSchema, ManageCollectionsInputSchema, ManageLinksInputSchema, ManageNoteInputSchema, ManageTagsInputSchema, MemoryBlobStore, MigrationRunner, NotesRepository, OpenAICompatibleProvider, PGliteStorageBackend, PGliteStorageBackendFactory, PGliteWorkerClient, PGliteWorkerStorageBackend, PGliteWorkerStorageBackendFactory, ProvenanceRepository, ProviderRegistry, SHARD_FORMAT, SUPPORTED_PGLITE_VERSION, SearchInputSchema, SearchRepository, SkosRepository, TagsRepository, TransactionProxy, TypedEventBus, VERSION, aiRevisionHandler, aiwgFortemiIndexToCommunityGraph, allMigrations, appendPluginScript, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildNoteConditions, buildPluginCsp, captureKnowledge, chunkText, classifyError, classifyModel, clearPrefetchedShard, collectionFromShard, collectionToShard, computeHash, computeSri, conceptTaggingHandler, cosineSimilarity, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, createBlobStore, createCosineSemanticProvider, createCspReportHandler, createFortemi, createLegacyProvider, createPGliteBackend, createPGliteInstance, createRemoteBackend, createRoutes, createShardBackend, createWorkerEmbedFunction, defaultStorageBackendFactory, detectCommunities, detectGpuCapabilities, detectInferenceCapabilities, discoverLocalProviders, dumpDbSnapshot, embeddingFromShard, embeddingGenerationHandler, embeddingSetFromShard, embeddingSetMemberToShard, embeddingSetToShard, embeddingToShard, enqueueFullWorkflow, enqueueJob, enqueueNoteCreationJobs, estimateModelFit, estimateVramMB, estimateVramTier, exportShard, fetchPluginScript, findAiwgStaticDuplicatePairs, fortemiManifest, fromPrefetched, generateId, getAiwgFortemiFacets, getEmbedFunction, getFortemiBridge, getFortemiSecretStore, getJobQueueStatus, getLlmFunction, getNote, getPrefetchedSha256, handleEmbedRequests, hasFortemiSecureSecrets, importShard, isPluginScriptAllowed, isShardPrefetched, linkFromShard, linkToShard, linkingHandler, listNotes, manageArchive, manageAttachments, manageCapabilities, manageCollections, manageLinks, manageNote, manageTags, matchRoute, noteFromShard, noteSkosTagToShard, noteToShard, openShard, packTarGz, parseCspReport, prefetchShard, provenanceEdgeToShard, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, registerLlmCapability, registerSemanticCapability, registerSemanticCapabilityWorker, registerServiceWorker, restoreDbSnapshot, searchTool, selectBackend, selectLlmModel, setEmbedFunction, setLlmFunction, sha256Hex, skosConceptToShard, skosRelationToShard, skosSchemeToShard, suggestTags, tagsFromShard, tagsToShard, titleGenerationHandler, unpackTarGz, unregisterLlmCapability, unregisterSemanticCapability, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet, validateChecksums, verifyDbSnapshotMeta, verifySri };
|
|
9674
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, ArchiveManager, AttachmentsRepository, CURRENT_MIGRATION_HEAD, CURRENT_SHARD_VERSION, CapabilityManager, CaptureKnowledgeInputSchema, CollectionsRepository, CommunitiesRepository, DB_SNAPSHOT_SCHEMA_VERSION, DbSnapshotVersionError, EMBED_REQUEST_KIND, EMBED_RESPONSE_KIND, EmbeddingSetsRepository, FallbackRouter, FortemiToolManifest, GetNoteInputSchema, GraphRepository, JOB_CAPABILITIES, JOB_PRIORITIES, JobQueueWorker, LOCAL_ENDPOINTS, LinksRepository, ListNotesInputSchema, ManageArchiveInputSchema, ManageAttachmentsInputSchema, ManageCapabilitiesInputSchema, ManageCollectionsInputSchema, ManageLinksInputSchema, ManageNoteInputSchema, ManageTagsInputSchema, MemoryBlobStore, MigrationRunner, NotesRepository, OpenAICompatibleProvider, PGliteStorageBackend, PGliteStorageBackendFactory, PGliteWorkerClient, PGliteWorkerStorageBackend, PGliteWorkerStorageBackendFactory, ProvenanceRepository, ProviderRegistry, SHARD_FORMAT, SUPPORTED_PGLITE_VERSION, SearchInputSchema, SearchRepository, SkosRepository, TagsRepository, TransactionProxy, TypedEventBus, VERSION, aiRevisionHandler, aiwgFortemiIndexToCommunityGraph, allMigrations, appendPluginScript, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, buildNoteConditions, buildPluginCsp, captureKnowledge, chunkText, classifyError, classifyModel, clearPrefetchedShard, collectionFromShard, collectionToShard, computeHash, computeSri, conceptTaggingHandler, cosineSimilarity, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, createBlobStore, createCosineSemanticProvider, createCspReportHandler, createFortemi, createLegacyProvider, createPGliteBackend, createPGliteInstance, createRemoteBackend, createRoutes, createShardBackend, createWorkerEmbedFunction, defaultStorageBackendFactory, detectCommunities, detectGpuCapabilities, detectInferenceCapabilities, discoverLocalProviders, dumpDbSnapshot, embeddingFromShard, embeddingGenerationHandler, embeddingSetFromShard, embeddingSetMemberToShard, embeddingSetToShard, embeddingToShard, enqueueFullWorkflow, enqueueJob, enqueueNoteCreationJobs, estimateModelFit, estimateVramMB, estimateVramTier, exportShard, fetchPluginScript, filterAiwgRecordsByPrivacy, findAiwgStaticDuplicatePairs, fortemiManifest, fromPrefetched, generateId, getAiwgFortemiFacets, getEmbedFunction, getFortemiBridge, getFortemiSecretStore, getJobQueueStatus, getLlmFunction, getNote, getPrefetchedSha256, handleEmbedRequests, hasFortemiSecureSecrets, importShard, isPluginScriptAllowed, isShardPrefetched, linkFromShard, linkToShard, linkingHandler, listNotes, manageArchive, manageAttachments, manageCapabilities, manageCollections, manageLinks, manageNote, manageTags, matchRoute, noteFromShard, noteSkosTagToShard, noteToShard, openShard, packTarGz, parseCspReport, prefetchShard, provenanceEdgeToShard, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, registerLlmCapability, registerSemanticCapability, registerSemanticCapabilityWorker, registerServiceWorker, restoreDbSnapshot, searchTool, selectBackend, selectLlmModel, setEmbedFunction, setLlmFunction, sha256Hex, skosConceptToShard, skosRelationToShard, skosSchemeToShard, suggestTags, tagsFromShard, tagsToShard, titleGenerationHandler, unpackTarGz, unregisterLlmCapability, unregisterSemanticCapability, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet, validateChecksums, verifyDbSnapshotMeta, verifySri };
|
|
9370
9675
|
//# sourceMappingURL=index.js.map
|
|
9371
9676
|
//# sourceMappingURL=index.js.map
|