fuli-context 0.4.0 → 0.7.2

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 (207) hide show
  1. package/README.en.md +5 -0
  2. package/README.md +411 -173
  3. package/README.zh-CN.md +448 -0
  4. package/compose.graphiti.yml +6 -6
  5. package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
  6. package/dist/web/assets/AboutPage-DfnZo7Lo.js +1 -0
  7. package/dist/web/assets/ConnectionsPage-BloaSKmt.js +1 -0
  8. package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
  9. package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
  10. package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +1 -0
  11. package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
  12. package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +1 -0
  13. package/dist/web/assets/KnowledgePage-BWL0jT01.js +1 -0
  14. package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +4 -0
  15. package/dist/web/assets/OverviewPage-5sJoO1Gh.js +1 -0
  16. package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
  17. package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +1 -0
  18. package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +1 -0
  19. package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +1 -0
  20. package/dist/web/assets/ReviewPage-B7EN817i.js +1 -0
  21. package/dist/web/assets/SearchableSelect-ClLN0rIb.js +1 -0
  22. package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
  23. package/dist/web/assets/SettingsPage-Chd_B3eH.js +1 -0
  24. package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  25. package/dist/web/assets/console-CbDsKrsO.js +1 -0
  26. package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
  27. package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
  28. package/dist/web/assets/i18n-BQcns1PH.js +5 -0
  29. package/dist/web/assets/identity-B8K4VYg6.js +1 -0
  30. package/dist/web/assets/index-CB8Xmsc2.js +2 -0
  31. package/dist/web/assets/index-CZeuPh-U.css +1 -0
  32. package/dist/web/assets/model-eF52GgPF.js +1 -0
  33. package/dist/web/assets/paths-hwz-Ac1g.js +1 -0
  34. package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
  35. package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +1 -0
  36. package/dist/web/assets/vue-router-BsEp_XwI.js +1 -0
  37. package/dist/web/index.html +9 -6
  38. package/docs/external-knowledge-architecture.md +279 -0
  39. package/examples/external-knowledge/markdown-folder.mjs +235 -0
  40. package/graph-provider/fuli_graph/app.py +57 -1
  41. package/graph-provider/fuli_graph/graph_models.py +3 -0
  42. package/graph-provider/fuli_graph/graph_query.py +62 -16
  43. package/graph-provider/fuli_graph/knowledge_review.py +612 -0
  44. package/graph-provider/fuli_graph/knowledge_review_models.py +127 -0
  45. package/graph-provider/fuli_graph/knowledge_search.py +51 -4
  46. package/graph-provider/fuli_graph/models.py +19 -0
  47. package/graph-provider/fuli_graph/runtime.py +15 -0
  48. package/graph-provider/fuli_graph/store_knowledge.py +2 -1
  49. package/graph-provider/pyproject.toml +1 -1
  50. package/npm-shrinkwrap.json +5 -407
  51. package/package.json +7 -4
  52. package/skills/capturing-session-knowledge/SKILL.md +24 -4
  53. package/skills/flreview/SKILL.md +73 -0
  54. package/skills/flreview/agents/openai.yaml +6 -0
  55. package/src/agent-tools/graph-definitions.js +79 -4
  56. package/src/agent-tools/graph-handlers.js +12 -0
  57. package/src/cli/command-registry.js +3 -3
  58. package/src/cli/local-runtime-command.js +60 -23
  59. package/src/cli/local-runtime-options.js +16 -6
  60. package/src/cli/setup-agent-selection.js +12 -12
  61. package/src/cli/setup-command.js +41 -18
  62. package/src/cli/shell-argument.js +10 -0
  63. package/src/cli/uninstall-command.js +17 -15
  64. package/src/cli/update-command.js +38 -30
  65. package/src/external-knowledge/catalog.js +61 -0
  66. package/src/external-knowledge/conflict-policy.js +58 -0
  67. package/src/external-knowledge/connected-search.js +166 -0
  68. package/src/external-knowledge/connectors/custom.js +133 -0
  69. package/src/external-knowledge/connectors/feishu.js +461 -0
  70. package/src/external-knowledge/connectors/mcp.js +438 -0
  71. package/src/external-knowledge/connectors/notion.js +307 -0
  72. package/src/external-knowledge/connectors/retrieval-api.js +222 -0
  73. package/src/external-knowledge/document-mapping.js +151 -0
  74. package/src/external-knowledge/graph-projection.js +17 -0
  75. package/src/external-knowledge/index.js +8 -0
  76. package/src/external-knowledge/registry.js +91 -0
  77. package/src/external-knowledge/runtime.js +68 -0
  78. package/src/external-knowledge/safe-diagnostic.js +14 -0
  79. package/src/external-knowledge/service.js +811 -0
  80. package/src/graphiti/agent-knowledge-workflows.js +7 -2
  81. package/src/graphiti/federated-application.js +66 -41
  82. package/src/graphiti/knowledge-item-role.js +21 -0
  83. package/src/graphiti/knowledge-review.js +43 -0
  84. package/src/graphiti/project-profile-mapping.js +32 -0
  85. package/src/graphiti/provider-client.js +22 -1
  86. package/src/graphiti/source-marker.js +54 -3
  87. package/src/graphiti/task-knowledge-recall.js +250 -0
  88. package/src/http/api-router.js +23 -108
  89. package/src/http/external-knowledge-api-router.js +104 -0
  90. package/src/http/graph-api-router.js +12 -1
  91. package/src/http/request-policy.js +38 -3
  92. package/src/local-runtime/lifecycle.js +37 -16
  93. package/src/mcp/create-mcp-server.js +1 -0
  94. package/src/mcp/instructions.js +7 -7
  95. package/src/mcp/tool-annotations.js +6 -0
  96. package/src/mcp/tool-result.js +1 -0
  97. package/src/server/application-lifecycle.js +2 -11
  98. package/src/server/blocked-ports.js +11 -0
  99. package/src/server/lan-access.js +52 -0
  100. package/src/server.js +103 -25
  101. package/src/setup/agent-installation-status.js +2 -1
  102. package/src/setup/agents.js +2 -1
  103. package/src/setup/claude-code-config.js +2 -1
  104. package/src/setup/codex-bootstrap.js +12 -7
  105. package/src/setup/container-runtime.js +25 -21
  106. package/src/setup/graph-runtime.js +219 -58
  107. package/src/setup/node-runtime.js +3 -3
  108. package/src/setup/options.js +2 -4
  109. package/src/setup/paths.js +18 -3
  110. package/src/setup/review-skill-adapters.js +49 -0
  111. package/src/setup/setup.js +26 -2
  112. package/src/setup/uninstall.js +4 -2
  113. package/src/system/resource-monitor.js +317 -0
  114. package/src/system/runtime-settings.js +131 -0
  115. package/src/system/system-service.js +119 -0
  116. package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
  117. package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
  118. package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
  119. package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +0 -1
  120. package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +0 -1
  121. package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
  122. package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
  123. package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
  124. package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
  125. package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
  126. package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
  127. package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
  128. package/dist/web/assets/console-B_Ena2DS.js +0 -5
  129. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  130. package/dist/web/assets/identity-CjBwYSMX.js +0 -1
  131. package/dist/web/assets/index-BQRkOwPx.js +0 -2
  132. package/dist/web/assets/index-CpLDx3of.css +0 -1
  133. package/dist/web/assets/model-C-5RVIJ2.js +0 -1
  134. package/dist/web/assets/paths-BkULqmGI.js +0 -1
  135. package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
  136. package/src/agent/candidate-read-service.js +0 -53
  137. package/src/agent/context-pack-service.js +0 -83
  138. package/src/agent/public-read-policy.js +0 -39
  139. package/src/agent/public-read-service.js +0 -69
  140. package/src/agent/read-projection.js +0 -107
  141. package/src/agent/search-service.js +0 -61
  142. package/src/agent/space-boundary.js +0 -33
  143. package/src/app/bootstrap.js +0 -8
  144. package/src/app/create-application.js +0 -143
  145. package/src/app/state-service.js +0 -38
  146. package/src/candidates.js +0 -78
  147. package/src/classifier.js +0 -63
  148. package/src/cli/command-arguments.js +0 -14
  149. package/src/context-pack.js +0 -206
  150. package/src/extractor.js +0 -77
  151. package/src/fact-history.js +0 -39
  152. package/src/facts/fact-service.js +0 -89
  153. package/src/http/bounded-json-array.js +0 -19
  154. package/src/http/lens-route.js +0 -50
  155. package/src/ingestion.js +0 -161
  156. package/src/lens/lens-correction-service.js +0 -81
  157. package/src/lens/lens-fact-writer.js +0 -85
  158. package/src/lens/lens-input.js +0 -62
  159. package/src/lens/lens-query-projection.js +0 -87
  160. package/src/lens/lens-query-ranking.js +0 -62
  161. package/src/lens/lens-query.js +0 -143
  162. package/src/lens/lens-resource-projection.js +0 -93
  163. package/src/lens/lens-resource-selection.js +0 -45
  164. package/src/lens/lens-resource-service.js +0 -103
  165. package/src/lens/lens-search-query.js +0 -122
  166. package/src/lens/lens-service.js +0 -183
  167. package/src/models.js +0 -44
  168. package/src/observer.js +0 -83
  169. package/src/project-rules.js +0 -33
  170. package/src/publication/outbox-service.js +0 -46
  171. package/src/publication/publication-envelope-schema.js +0 -75
  172. package/src/publication/publication-envelope.js +0 -37
  173. package/src/publication/publication-policy.js +0 -82
  174. package/src/publication/publication-service.js +0 -48
  175. package/src/router.js +0 -58
  176. package/src/runtime-options.js +0 -138
  177. package/src/security/episode-visibility.js +0 -14
  178. package/src/security/fact-visibility.js +0 -15
  179. package/src/security/source-metadata-policy.js +0 -8
  180. package/src/storage/episode-evidence-preview.js +0 -100
  181. package/src/storage/fact-search-order.js +0 -18
  182. package/src/storage/file/episode-evidence.js +0 -37
  183. package/src/storage/file/episode-index.js +0 -59
  184. package/src/storage/file/fact-search.js +0 -60
  185. package/src/storage/file-store.js +0 -437
  186. package/src/storage/migrate.js +0 -128
  187. package/src/storage/migrations/001-initial.sql +0 -83
  188. package/src/storage/migrations/002-storage-integrity.sql +0 -113
  189. package/src/storage/record-validation.js +0 -46
  190. package/src/storage/snapshot-defaults.js +0 -54
  191. package/src/storage/sqlite/candidates-repository.js +0 -84
  192. package/src/storage/sqlite/episode-evidence-repository.js +0 -154
  193. package/src/storage/sqlite/episode-privacy-sql.js +0 -33
  194. package/src/storage/sqlite/episodes-repository.js +0 -52
  195. package/src/storage/sqlite/fact-search-repository.js +0 -162
  196. package/src/storage/sqlite/facts-repository.js +0 -146
  197. package/src/storage/sqlite/imports-repository.js +0 -39
  198. package/src/storage/sqlite/mapper.js +0 -84
  199. package/src/storage/sqlite/open-database.js +0 -15
  200. package/src/storage/sqlite/outbox-repository.js +0 -103
  201. package/src/storage/sqlite/snapshot-repository.js +0 -69
  202. package/src/storage/sqlite/spaces-repository.js +0 -52
  203. package/src/storage/sqlite/subscriptions-repository.js +0 -54
  204. package/src/storage/sqlite-store.js +0 -159
  205. package/src/storage/stable-json.js +0 -24
  206. package/src/storage/store-port.js +0 -20
  207. package/src/store.js +0 -2
@@ -1,206 +0,0 @@
1
- import { isCurrentFact } from './models.js';
2
- import { listCandidates } from './candidates.js';
3
- import { getProjectRules } from './project-rules.js';
4
- import { getFactHistory } from './fact-history.js';
5
-
6
- export function buildContextPack(store, { personalSpaceId, spaceId, query = '' }) {
7
- const personalSpace = store.getSpace(personalSpaceId);
8
- const space = store.getSpace(spaceId);
9
- const scopedSpaceIds = [...new Set([personalSpaceId, spaceId])];
10
- const matches = store
11
- .searchFacts(scopedSpaceIds, query, { includeHistorical: false })
12
- .map((fact) => compactMatch(store, fact));
13
- const histories = buildHistories(store, matches);
14
- const rules = compactRules(getProjectRules(store, spaceId), store);
15
- const candidates = listCandidates(store, personalSpaceId)
16
- .filter((candidate) => !candidate.targetSpaceId || candidate.targetSpaceId === spaceId)
17
- .map(compactCandidate);
18
-
19
- return {
20
- query,
21
- personalSpace: compactSpace(personalSpace, personalSpaceId),
22
- space: compactSpace(space, spaceId),
23
- rules,
24
- matches,
25
- histories,
26
- candidateCount: candidates.length,
27
- candidates,
28
- answer: formatPackAnswer(matches)
29
- };
30
- }
31
-
32
- export function formatContextPack(pack) {
33
- const lines = [
34
- `Context ${pack.personalSpace.name} -> ${pack.space.name}`,
35
- `Query ${pack.query || '(empty)'}`,
36
- 'Rules',
37
- ...formatRules(pack.rules),
38
- 'Matches',
39
- ...formatMatches(pack.matches),
40
- ...formatHistories(pack.histories),
41
- `Candidates ${pack.candidateCount}`
42
- ];
43
-
44
- return lines.join('\n');
45
- }
46
-
47
- function compactRules(rules, store) {
48
- return {
49
- spaceId: rules.spaceId,
50
- spaceName: rules.spaceName,
51
- forbidden: rules.forbidden.map((fact) => compactFact(store, fact)),
52
- parameters: rules.parameters.map((fact) => compactFact(store, fact)),
53
- links: rules.links.map((fact) => compactFact(store, fact))
54
- };
55
- }
56
-
57
- function compactFact(store, fact) {
58
- return {
59
- id: fact.id,
60
- spaceId: fact.spaceId,
61
- spaceName: store.getSpace(fact.spaceId)?.name ?? fact.spaceId,
62
- subject: fact.subject,
63
- predicate: fact.predicate,
64
- object: fact.object,
65
- current: isCurrentFact(fact),
66
- validAt: fact.validAt,
67
- invalidAt: fact.invalidAt,
68
- source: compactSource(store, fact.sourceEpisodeId)
69
- };
70
- }
71
-
72
- function compactMatch(store, fact) {
73
- const compact = compactFact(store, fact);
74
- return {
75
- spaceId: compact.spaceId,
76
- spaceName: compact.spaceName,
77
- current: compact.current,
78
- fact: {
79
- id: compact.id,
80
- spaceId: compact.spaceId,
81
- subject: compact.subject,
82
- predicate: compact.predicate,
83
- object: compact.object,
84
- validAt: compact.validAt,
85
- invalidAt: compact.invalidAt
86
- },
87
- source: compact.source
88
- };
89
- }
90
-
91
- function buildHistories(store, matches) {
92
- const keys = new Set();
93
- const histories = [];
94
-
95
- for (const match of matches) {
96
- const key = `${match.fact.spaceId}:${match.fact.predicate}`;
97
- if (keys.has(key)) continue;
98
- keys.add(key);
99
-
100
- const history = getFactHistory(store, {
101
- spaceId: match.fact.spaceId,
102
- predicate: match.fact.predicate
103
- });
104
- if (history.facts.length <= 1) continue;
105
-
106
- histories.push({
107
- spaceId: history.spaceId,
108
- spaceName: history.spaceName,
109
- predicate: history.predicate,
110
- facts: history.facts.map((fact) => compactFact(store, fact))
111
- });
112
- }
113
-
114
- return histories;
115
- }
116
-
117
- function compactCandidate(candidate) {
118
- return {
119
- id: candidate.id,
120
- reason: candidate.reason,
121
- createdAt: candidate.createdAt,
122
- targetSpaceId: candidate.targetSpaceId,
123
- targetSpaceName: candidate.targetSpaceName,
124
- source: candidate.episode
125
- ? {
126
- id: candidate.episode.id,
127
- kind: candidate.episode.sourceKind,
128
- uri: candidate.episode.sourceUri,
129
- createdAt: candidate.episode.createdAt,
130
- preview: firstLine(candidate.episode.body)
131
- }
132
- : null
133
- };
134
- }
135
-
136
- function compactSource(store, episodeId) {
137
- const episode = store.getEpisode(episodeId);
138
- if (!episode) return null;
139
-
140
- return {
141
- id: episode.id,
142
- kind: episode.sourceKind,
143
- uri: episode.sourceUri,
144
- createdAt: episode.createdAt,
145
- preview: firstLine(episode.body)
146
- };
147
- }
148
-
149
- function compactSpace(space, fallbackId) {
150
- return {
151
- id: space?.id ?? fallbackId,
152
- name: space?.name ?? fallbackId,
153
- kind: space?.kind ?? null
154
- };
155
- }
156
-
157
- function firstLine(value) {
158
- return String(value).split(/\r?\n/)[0].slice(0, 160);
159
- }
160
-
161
- function formatPackAnswer(matches) {
162
- if (matches.length === 0) {
163
- return 'No matching current facts found.';
164
- }
165
-
166
- return matches
167
- .map((match) => `${match.fact.subject} ${match.fact.predicate} ${match.fact.object}`)
168
- .join('\n');
169
- }
170
-
171
- function formatRules(rules) {
172
- const lines = [
173
- ...rules.forbidden.map((fact) => `forbids ${fact.object}`),
174
- ...rules.parameters.map((fact) => `${humanPredicate(fact.predicate)} ${fact.object}`),
175
- ...rules.links.map((fact) => `url ${fact.object}`)
176
- ];
177
-
178
- return lines.length ? lines : ['(none)'];
179
- }
180
-
181
- function formatMatches(matches) {
182
- return matches.length
183
- ? matches.map(
184
- (match) =>
185
- `${match.spaceName} ${humanPredicate(match.fact.predicate)} ${match.fact.object}`
186
- )
187
- : ['(none)'];
188
- }
189
-
190
- function formatHistories(histories) {
191
- if (!histories?.length) return [];
192
-
193
- return [
194
- 'History',
195
- ...histories.flatMap((history) =>
196
- history.facts.map(
197
- (fact) =>
198
- `${fact.current ? 'current' : 'historical'} ${humanPredicate(history.predicate)} ${fact.object}`
199
- )
200
- )
201
- ];
202
- }
203
-
204
- function humanPredicate(predicate) {
205
- return predicate.startsWith('has_') ? predicate.slice(4) : predicate;
206
- }
package/src/extractor.js DELETED
@@ -1,77 +0,0 @@
1
- const URL_PATTERN = /https?:\/\/[^\s"',。))]+/g;
2
-
3
- export function extractFactSpecs(subject, body) {
4
- const lines = body
5
- .split(/\r?\n/)
6
- .map((line) => line.trim())
7
- .filter(Boolean);
8
- const specs = [];
9
-
10
- for (const line of lines) {
11
- const replacement = parseReplacement(subject, line);
12
- if (replacement) {
13
- specs.push(replacement);
14
- continue;
15
- }
16
-
17
- const forbidden = parseForbidden(subject, line);
18
- if (forbidden) {
19
- specs.push(forbidden);
20
- continue;
21
- }
22
-
23
- const keyValue = parseKeyValue(subject, line);
24
- if (keyValue) {
25
- specs.push(keyValue);
26
- continue;
27
- }
28
-
29
- for (const url of line.match(URL_PATTERN) ?? []) {
30
- specs.push({ kind: 'fact', subject, predicate: 'has_url', object: url });
31
- }
32
- }
33
-
34
- return specs;
35
- }
36
-
37
- function parseReplacement(subject, line) {
38
- const match = line.match(/^替代[::]\s*(.+?)\s*=>\s*(.+)$/);
39
- if (!match) return null;
40
-
41
- return {
42
- kind: 'replacement',
43
- subject,
44
- oldValue: match[1].trim(),
45
- newValue: match[2].trim()
46
- };
47
- }
48
-
49
- function parseForbidden(subject, line) {
50
- const match =
51
- line.match(/^禁止[::]\s*(.+)$/) ??
52
- line.match(/^(?:这个项目|本项目|项目)?\s*(?:禁止|不要|不允许)\s*(?:使用|采用|引入|用)?\s*(.+)$/);
53
- if (!match) return null;
54
-
55
- return {
56
- kind: 'fact',
57
- subject,
58
- predicate: 'forbids',
59
- object: cleanForbiddenObject(match[1])
60
- };
61
- }
62
-
63
- function cleanForbiddenObject(value) {
64
- return value.trim().replace(/[。.!!,,;;]+$/g, '').trim();
65
- }
66
-
67
- function parseKeyValue(subject, line) {
68
- const match = line.match(/^([\p{L}\p{N}_.-]+)\s*[::]\s*(.+)$/u);
69
- if (!match) return null;
70
-
71
- return {
72
- kind: 'fact',
73
- subject,
74
- predicate: `has_${match[1].trim()}`,
75
- object: match[2].trim()
76
- };
77
- }
@@ -1,39 +0,0 @@
1
- import { isCurrentFact } from './models.js';
2
-
3
- export function getFactHistory(store, { spaceId, predicate }) {
4
- const normalizedPredicate = normalizePredicate(predicate);
5
- const facts = store.listFacts({ includeHistorical: true })
6
- .filter((fact) => fact.spaceId === spaceId && fact.predicate === normalizedPredicate)
7
- .sort((left, right) => left.validAt.localeCompare(right.validAt))
8
- .map((fact) => enrichFact(store, fact));
9
-
10
- return {
11
- spaceId,
12
- spaceName: store.getSpace(spaceId)?.name ?? spaceId,
13
- predicate: normalizedPredicate,
14
- facts
15
- };
16
- }
17
-
18
- export function normalizePredicate(value) {
19
- if (value.startsWith('has_') || value === 'forbids') return value;
20
- return `has_${value}`;
21
- }
22
-
23
- function enrichFact(store, fact) {
24
- const episode = store.getEpisode(fact.sourceEpisodeId);
25
-
26
- return {
27
- ...fact,
28
- current: isCurrentFact(fact),
29
- source: episode
30
- ? {
31
- id: episode.id,
32
- kind: episode.sourceKind,
33
- uri: episode.sourceUri,
34
- body: episode.body,
35
- createdAt: episode.createdAt
36
- }
37
- : null
38
- };
39
- }
@@ -1,89 +0,0 @@
1
- import { FactScope, FactStatus } from '../models.js';
2
-
3
- export class FactService {
4
- constructor(store) {
5
- this.store = store;
6
- }
7
-
8
- writeSpecs({ spaceId, subject, episodeId, specs, scope = FactScope.PERSONAL }) {
9
- const written = [];
10
- for (const spec of specs) {
11
- if (spec.kind === 'replacement') {
12
- written.push(...this.#replaceFact({ spaceId, subject, episodeId, spec, scope }));
13
- continue;
14
- }
15
-
16
- if (this.#hasCurrentFact(spaceId, subject, spec.predicate, spec.object)) continue;
17
-
18
- const supersededFacts = this.#supersededCurrentFacts(
19
- spaceId,
20
- subject,
21
- spec.predicate,
22
- spec.object
23
- );
24
- const newFact = this.store.addFact({
25
- spaceId,
26
- subject,
27
- predicate: spec.predicate,
28
- object: spec.object,
29
- sourceEpisodeId: episodeId,
30
- status: FactStatus.CONFIRMED,
31
- scope
32
- });
33
- written.push(newFact);
34
-
35
- for (const oldFact of supersededFacts) {
36
- this.store.invalidateFact(oldFact.id, newFact.id);
37
- }
38
- }
39
- return written;
40
- }
41
-
42
- #hasCurrentFact(spaceId, subject, predicate, object) {
43
- return this.store.currentFacts(spaceId).some(
44
- (fact) =>
45
- fact.subject === subject &&
46
- fact.predicate === predicate &&
47
- fact.object === object
48
- );
49
- }
50
-
51
- #supersededCurrentFacts(spaceId, subject, predicate, object) {
52
- if (!isUniqueParameterPredicate(predicate)) return [];
53
-
54
- return this.store.currentFacts(spaceId).filter(
55
- (fact) =>
56
- fact.subject === subject &&
57
- fact.predicate === predicate &&
58
- fact.object !== object
59
- );
60
- }
61
-
62
- #replaceFact({ spaceId, subject, episodeId, spec, scope }) {
63
- const oldFacts = this.store.currentFacts(spaceId).filter(
64
- (fact) => fact.subject === subject && fact.object === spec.oldValue
65
- );
66
- const predicates = oldFacts.length
67
- ? [...new Set(oldFacts.map((fact) => fact.predicate))]
68
- : ['has_replacement'];
69
- const newFacts = predicates.map((predicate) => this.store.addFact({
70
- spaceId,
71
- subject,
72
- predicate,
73
- object: spec.newValue,
74
- sourceEpisodeId: episodeId,
75
- status: FactStatus.CONFIRMED,
76
- scope
77
- }));
78
-
79
- for (const oldFact of oldFacts) {
80
- const replacement = newFacts.find((fact) => fact.predicate === oldFact.predicate) ?? newFacts[0];
81
- this.store.invalidateFact(oldFact.id, replacement.id);
82
- }
83
- return newFacts;
84
- }
85
- }
86
-
87
- function isUniqueParameterPredicate(predicate) {
88
- return predicate.startsWith('has_') && predicate !== 'has_url';
89
- }
@@ -1,19 +0,0 @@
1
- export function selectBoundedJsonArray(items, budget) {
2
- const selected = [];
3
- let usedBytes = Buffer.byteLength('[]', 'utf8');
4
- let truncated = false;
5
-
6
- for (const item of items) {
7
- const serialized = JSON.stringify(item);
8
- const itemBytes = Buffer.byteLength(serialized, 'utf8');
9
- const nextBytes = usedBytes + itemBytes + (selected.length ? 1 : 0);
10
- if (nextBytes > budget) {
11
- truncated = true;
12
- continue;
13
- }
14
- selected.push(item);
15
- usedBytes = nextBytes;
16
- }
17
-
18
- return { items: selected, usedBytes, truncated };
19
- }
@@ -1,50 +0,0 @@
1
- import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
2
- import { selectBoundedJsonArray } from './bounded-json-array.js';
3
- import { sendJson } from './response.js';
4
-
5
- const DEFAULT_BUDGET = 1200;
6
- const MAX_BUDGET = 16384;
7
-
8
- export function handleLensRequest({ request, response, url, app }) {
9
- if (url.pathname !== '/api/lens' || request.method !== 'GET') return false;
10
-
11
- const personalSpaceId = app.requireSpaceId({
12
- id: url.searchParams.get('personalSpaceId'),
13
- name: url.searchParams.get('personalSpaceName'),
14
- label: 'Personal space'
15
- });
16
- const budget = parseBudget(url.searchParams.get('budget'));
17
- const view = app.lens.getUserLensView({
18
- personalSpaceId,
19
- task: '',
20
- budget,
21
- includeObserved: true,
22
- includeSuggested: false,
23
- includeRestricted: false
24
- });
25
- const bounded = selectBoundedJsonArray(view.entries, budget);
26
-
27
- sendJson(response, 200, {
28
- personalSpaceId,
29
- budget,
30
- usedBytes: bounded.usedBytes,
31
- truncated: view.truncated || bounded.truncated,
32
- facts: bounded.items
33
- });
34
- return true;
35
- }
36
-
37
- function parseBudget(value) {
38
- if (value === null) return DEFAULT_BUDGET;
39
- if (!/^[1-9]\d*$/.test(value)) throw invalidBudget();
40
- const budget = Number(value);
41
- if (!Number.isSafeInteger(budget) || budget < 2 || budget > MAX_BUDGET) throw invalidBudget();
42
- return budget;
43
- }
44
-
45
- function invalidBudget() {
46
- return new ApplicationError(
47
- ApplicationErrorCode.VALIDATION,
48
- `Budget must be an integer between 2 and ${MAX_BUDGET}`
49
- );
50
- }
package/src/ingestion.js DELETED
@@ -1,161 +0,0 @@
1
- import { classifyEpisode } from './classifier.js';
2
- import { extractFactSpecs } from './extractor.js';
3
- import { FactService } from './facts/fact-service.js';
4
- import { LensService } from './lens/lens-service.js';
5
- import { ApplicationError, ApplicationErrorCode } from './app/application-error.js';
6
- import { CandidateStatus, FactScope, PublishRoute, SpaceKind } from './models.js';
7
- import { detectSensitiveContent } from './security/sensitive-content.js';
8
- import { assertSafeSourceMetadata } from './security/source-metadata-policy.js';
9
- import { PublicationService } from './publication/publication-service.js';
10
-
11
- export class IngestionService {
12
- constructor(store, {
13
- facts = new FactService(store),
14
- lens = new LensService(store),
15
- publication = new PublicationService(store)
16
- } = {}) {
17
- this.store = store;
18
- this.facts = facts;
19
- this.lens = lens;
20
- this.publication = publication;
21
- }
22
-
23
- remember(input) {
24
- this.#assertSafeInput(input);
25
- return this.store.transaction(
26
- () => this.rememberInCurrentTransaction(input),
27
- { mode: 'immediate' }
28
- );
29
- }
30
-
31
- rememberInCurrentTransaction({
32
- personalSpaceId,
33
- sourceKind,
34
- body,
35
- targetSpaceId = null,
36
- sourceUri = null
37
- }) {
38
- this.#assertSafeInput({ body, sourceKind, sourceUri });
39
- const route = classifyEpisode({ sourceKind, body });
40
-
41
- if (route === PublishRoute.PERSONAL) {
42
- const result = this.lens.rememberUserFactsFromSourceInCurrentTransaction({
43
- personalSpaceId,
44
- subject: this.#spaceName(personalSpaceId),
45
- specs: extractFactSpecs(this.#spaceName(personalSpaceId), body),
46
- sourceText: body,
47
- sourceKind,
48
- sourceUri
49
- });
50
- return { route, ...result };
51
- }
52
-
53
- const personalEpisode = this.store.addEpisode(personalSpaceId, sourceKind, body, sourceUri);
54
-
55
- if (route === PublishRoute.PUBLIC && targetSpaceId) {
56
- const targetEpisode = this.store.addEpisode(targetSpaceId, sourceKind, body, sourceUri);
57
- const facts = this.facts.writeSpecs({
58
- spaceId: targetSpaceId,
59
- subject: this.#spaceName(targetSpaceId),
60
- episodeId: targetEpisode.id,
61
- specs: extractFactSpecs(this.#spaceName(targetSpaceId), body),
62
- scope: FactScope.PUBLIC
63
- });
64
- const publication = facts.length === 0
65
- ? null
66
- : this.publication.prepareInCurrentTransaction({
67
- spaceId: targetSpaceId,
68
- episode: targetEpisode,
69
- facts
70
- });
71
- return { route, episode: targetEpisode, publication };
72
- }
73
-
74
- const candidate = this.store.addCandidate({
75
- personalSpaceId,
76
- targetSpaceId,
77
- episodeId: personalEpisode.id,
78
- reason: 'not safe enough to publish automatically',
79
- status: CandidateStatus.PENDING
80
- });
81
- return { route: PublishRoute.CANDIDATE, episode: personalEpisode, candidate };
82
- }
83
-
84
- publishConfirmed(input) {
85
- this.#assertSafeInput(input);
86
- return this.store.transaction(
87
- () => this.publishConfirmedInCurrentTransaction(input),
88
- { mode: 'immediate' }
89
- );
90
- }
91
-
92
- publishConfirmedInCurrentTransaction({ targetSpaceId, sourceKind, body, sourceUri = null }) {
93
- this.#assertSafeInput({ body, sourceKind, sourceUri });
94
- const targetEpisode = this.store.addEpisode(targetSpaceId, sourceKind, body, sourceUri);
95
- const result = this.confirmEpisodeInCurrentTransaction({
96
- spaceId: targetSpaceId,
97
- episode: targetEpisode
98
- });
99
- return result;
100
- }
101
-
102
- confirmEpisode(input) {
103
- this.#assertSafeEpisode(input.episode);
104
- return this.store.transaction(
105
- () => this.confirmEpisodeInCurrentTransaction(input),
106
- { mode: 'immediate' }
107
- );
108
- }
109
-
110
- confirmEpisodeInCurrentTransaction({ spaceId, episode }) {
111
- this.#assertSafeEpisode(episode);
112
- const subject = this.#spaceName(spaceId);
113
- const specs = extractFactSpecs(subject, episode.body);
114
- if (this.store.getSpace(spaceId)?.kind === SpaceKind.PERSONAL) {
115
- return this.lens.writeUserFactsForEpisodeInCurrentTransaction({
116
- personalSpaceId: spaceId,
117
- subject,
118
- specs,
119
- episode
120
- });
121
- }
122
- const facts = this.facts.writeSpecs({
123
- spaceId,
124
- subject,
125
- episodeId: episode.id,
126
- specs,
127
- scope: FactScope.PUBLIC
128
- });
129
- const publication = facts.length === 0
130
- ? null
131
- : this.publication.prepareInCurrentTransaction({ spaceId, episode, facts });
132
- return { episode, publication };
133
- }
134
-
135
- #spaceName(spaceId) {
136
- return this.store.getSpace(spaceId)?.name ?? spaceId;
137
- }
138
-
139
- #assertSafeInput({ body, sourceKind, sourceUri }) {
140
- this.#assertSafeBody(body);
141
- assertSafeSourceMetadata(
142
- sourceKind,
143
- sourceUri,
144
- 'Sensitive source metadata is not allowed in ingestion'
145
- );
146
- }
147
-
148
- #assertSafeEpisode(episode) {
149
- this.#assertSafeInput(episode);
150
- }
151
-
152
- #assertSafeBody(body) {
153
- if (detectSensitiveContent(body).restricted) {
154
- throw new ApplicationError(
155
- ApplicationErrorCode.VALIDATION,
156
- 'Sensitive content is not allowed in ingestion'
157
- );
158
- }
159
- }
160
-
161
- }