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,81 +0,0 @@
1
- import { nowIso, FactScope, FactStatus, isCurrentFact } from '../models.js';
2
- import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
3
- import { assertStorePort } from '../storage/store-port.js';
4
- import { writePersonalFact } from './lens-fact-writer.js';
5
- import { assertSafeLensTexts, requirePersonalSpace } from './lens-input.js';
6
-
7
- const ACTIONS = new Set(['replace', 'reject', 'deprecate']);
8
- const CORRECTABLE_STATUSES = new Set([
9
- FactStatus.CONFIRMED,
10
- FactStatus.OBSERVED,
11
- FactStatus.SUGGESTED
12
- ]);
13
- const IMMEDIATE = { mode: 'immediate' };
14
-
15
- export class LensCorrectionService {
16
- constructor(store) {
17
- assertStorePort(store);
18
- this.store = store;
19
- }
20
-
21
- correctUserFact({ personalSpaceId, factId, action, value, sourceText, sourceKind = 'correction' }) {
22
- if (!ACTIONS.has(action)) {
23
- throw new ApplicationError(ApplicationErrorCode.VALIDATION, `Unknown correction action: ${action}`);
24
- }
25
-
26
- requirePersonalSpace(this.store, personalSpaceId);
27
- assertSafeLensTexts([sourceText]);
28
- if (action === 'replace') assertSafeLensTexts([value]);
29
-
30
- const original = this.#requireCurrentFact(personalSpaceId, factId);
31
- if (action === 'replace') assertSafeLensTexts([original.subject, original.predicate]);
32
-
33
- return this.store.transaction(() => {
34
- requirePersonalSpace(this.store, personalSpaceId);
35
- const current = this.#requireCurrentFact(personalSpaceId, factId);
36
- assertSafeLensTexts([sourceText]);
37
- if (action === 'replace') assertSafeLensTexts([current.subject, current.predicate, value]);
38
-
39
- const episode = this.store.addEpisode(
40
- personalSpaceId,
41
- sourceKind,
42
- sourceText,
43
- null,
44
- { kind: 'lens_correction', factId: current.id, action }
45
- );
46
-
47
- if (action === 'replace') {
48
- const fact = writePersonalFact(this.store, {
49
- spaceId: personalSpaceId,
50
- subject: current.subject,
51
- predicate: current.predicate,
52
- object: value,
53
- sourceEpisodeId: episode.id,
54
- status: FactStatus.CONFIRMED,
55
- sensitivity: current.sensitivity,
56
- confidence: current.confidence,
57
- scope: FactScope.PERSONAL
58
- });
59
- this.store.invalidateFact(current.id, fact.id);
60
- return { episode, fact, replacedFact: this.store.getFact(current.id) };
61
- }
62
-
63
- const fact = this.store.updateFact(current.id, {
64
- status: action === 'reject' ? FactStatus.REJECTED : FactStatus.DEPRECATED,
65
- invalidAt: nowIso()
66
- });
67
- return { episode, fact };
68
- }, IMMEDIATE);
69
- }
70
-
71
- #requireCurrentFact(personalSpaceId, factId) {
72
- const fact = this.store.getFact(factId);
73
- if (!fact || fact.spaceId !== personalSpaceId || fact.scope !== FactScope.PERSONAL) {
74
- throw new ApplicationError(ApplicationErrorCode.NOT_FOUND, `Lens fact not found: ${factId}`);
75
- }
76
- if (!isCurrentFact(fact) || !CORRECTABLE_STATUSES.has(fact.status)) {
77
- throw new ApplicationError(ApplicationErrorCode.VALIDATION, `Lens fact is not current: ${factId}`);
78
- }
79
- return fact;
80
- }
81
- }
@@ -1,85 +0,0 @@
1
- import { FactScope, FactStatus } from '../models.js';
2
-
3
- const STATUS_RANK = Object.freeze({
4
- [FactStatus.SUGGESTED]: 1,
5
- [FactStatus.OBSERVED]: 2,
6
- [FactStatus.CONFIRMED]: 3
7
- });
8
-
9
- export function writePersonalFact(store, input) {
10
- const equivalent = equivalentCurrentFacts(store, input);
11
- const fact = store.addFact({
12
- spaceId: input.spaceId,
13
- subject: input.subject,
14
- predicate: input.predicate,
15
- object: input.object,
16
- sourceEpisodeId: input.sourceEpisodeId,
17
- status: input.status,
18
- confidence: input.confidence,
19
- sensitivity: input.sensitivity,
20
- scope: FactScope.PERSONAL
21
- });
22
- const candidates = [...equivalent, fact];
23
- const winner = candidates.reduce(strongerOrLater);
24
- for (const candidate of candidates) {
25
- if (candidate.id !== winner.id) store.invalidateFact(candidate.id, winner.id);
26
- }
27
- return store.getFact(fact.id);
28
- }
29
-
30
- export function writePersonalFactSpecs(store, input) {
31
- const facts = [];
32
- for (const spec of input.specs) {
33
- if (spec.kind === 'replacement') {
34
- facts.push(...writeReplacement(store, input, spec));
35
- continue;
36
- }
37
- facts.push(writePersonalFact(store, {
38
- ...input,
39
- subject: spec.subject ?? input.subject,
40
- predicate: spec.predicate,
41
- object: spec.object,
42
- status: FactStatus.CONFIRMED
43
- }));
44
- }
45
- return facts;
46
- }
47
-
48
- function writeReplacement(store, input, spec) {
49
- const subject = spec.subject ?? input.subject;
50
- const oldFacts = store.currentFacts(input.spaceId).filter(
51
- (fact) => fact.subject === subject && fact.object === spec.oldValue
52
- );
53
- const predicates = oldFacts.length
54
- ? [...new Set(oldFacts.map((fact) => fact.predicate))]
55
- : ['has_replacement'];
56
- return predicates.map((predicate) => {
57
- const fact = writePersonalFact(store, {
58
- ...input,
59
- subject,
60
- predicate,
61
- object: spec.newValue,
62
- status: FactStatus.CONFIRMED
63
- });
64
- for (const previous of oldFacts.filter((item) => item.predicate === predicate)) {
65
- store.invalidateFact(previous.id, fact.id);
66
- }
67
- return fact;
68
- });
69
- }
70
-
71
- function equivalentCurrentFacts(store, input) {
72
- return store.currentFacts(input.spaceId).filter((fact) =>
73
- fact.subject === input.subject &&
74
- fact.predicate === input.predicate &&
75
- fact.object === input.object
76
- );
77
- }
78
-
79
- function strongerOrLater(current, candidate) {
80
- return rank(candidate.status) >= rank(current.status) ? candidate : current;
81
- }
82
-
83
- function rank(status) {
84
- return STATUS_RANK[status] ?? 0;
85
- }
@@ -1,62 +0,0 @@
1
- import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
2
- import { Sensitivity, SpaceKind } from '../models.js';
3
- import { detectSensitiveContent } from '../security/sensitive-content.js';
4
-
5
- export function validateLensWriteInput(
6
- store,
7
- { personalSpaceId, texts, sensitivity, confidence }
8
- ) {
9
- requirePersonalSpace(store, personalSpaceId);
10
- assertSafeLensTexts(texts);
11
- if (!Object.values(Sensitivity).includes(sensitivity)) {
12
- throw new ApplicationError(
13
- ApplicationErrorCode.VALIDATION,
14
- `Invalid sensitivity: ${sensitivity}`
15
- );
16
- }
17
- if (
18
- typeof confidence !== 'number' ||
19
- !Number.isFinite(confidence) ||
20
- confidence < 0 ||
21
- confidence > 1
22
- ) {
23
- throw new ApplicationError(
24
- ApplicationErrorCode.VALIDATION,
25
- 'Confidence must be between 0 and 1'
26
- );
27
- }
28
- }
29
-
30
- export function requirePersonalSpace(store, spaceId) {
31
- const space = store.getSpace(spaceId);
32
- if (!space) {
33
- throw new ApplicationError(
34
- ApplicationErrorCode.NOT_FOUND,
35
- `Personal space not found: ${spaceId}`
36
- );
37
- }
38
- if (space.kind !== SpaceKind.PERSONAL) {
39
- throw new ApplicationError(
40
- ApplicationErrorCode.VALIDATION,
41
- `Space is not personal: ${spaceId}`
42
- );
43
- }
44
- return space;
45
- }
46
-
47
- export function assertSafeLensTexts(texts) {
48
- for (const text of texts) {
49
- if (typeof text !== 'string' || text.length === 0) {
50
- throw new ApplicationError(
51
- ApplicationErrorCode.VALIDATION,
52
- 'Lens text must be a nonempty string'
53
- );
54
- }
55
- if (detectSensitiveContent(text).restricted) {
56
- throw new ApplicationError(
57
- ApplicationErrorCode.VALIDATION,
58
- 'Sensitive content is not allowed in Personal Lens'
59
- );
60
- }
61
- }
62
- }
@@ -1,87 +0,0 @@
1
- import { FactScope, FactStatus, isCurrentFact } from '../models.js';
2
- import { isRestrictedEpisode } from '../security/episode-visibility.js';
3
- import { isRestrictedFact } from '../security/fact-visibility.js';
4
-
5
- export function isInjectableFact(
6
- fact,
7
- { includeObserved, includeSuggested, includeRestricted }
8
- ) {
9
- if (fact.invalidAt) return false;
10
- if (fact.status === FactStatus.CONFIRMED) return isVisibleFact(fact, includeRestricted);
11
- if (fact.status === FactStatus.OBSERVED && includeObserved) {
12
- return isVisibleFact(fact, includeRestricted);
13
- }
14
- if (fact.status === FactStatus.SUGGESTED && includeSuggested) {
15
- return isVisibleFact(fact, includeRestricted);
16
- }
17
- return false;
18
- }
19
-
20
- export function isSearchableFact(fact, { includeHistorical, includeRestricted }) {
21
- return (includeHistorical || isCurrentFact(fact)) && isVisibleFact(fact, includeRestricted);
22
- }
23
-
24
- export function buildCorrectionIndex(groups, personalSpaceId, includeRestricted) {
25
- const index = new Map();
26
- for (const group of groups) {
27
- if (typeof group.factId !== 'string') continue;
28
- const episodes = group.episodes.filter((episode) =>
29
- episode.spaceId === personalSpaceId &&
30
- episode.metadata?.kind === 'lens_correction' &&
31
- episode.metadata?.factId === group.factId &&
32
- (includeRestricted || !isRestrictedEpisode(episode))
33
- );
34
- index.set(group.factId, {
35
- episodes,
36
- truncated: group.truncated === true || episodes.length < group.episodes.length
37
- });
38
- }
39
- return index;
40
- }
41
-
42
- export function projectSearchFact(
43
- store,
44
- fact,
45
- evidenceEpisode,
46
- personalSpaceId,
47
- correctionIndex,
48
- includeRestricted
49
- ) {
50
- const sourceEpisode = safePersonalEpisode(
51
- evidenceEpisode,
52
- personalSpaceId,
53
- includeRestricted
54
- );
55
- const replacement = fact.replacedByFactId ? store.getFact(fact.replacedByFactId) : null;
56
- const replacementFact = replacement?.spaceId === personalSpaceId &&
57
- replacement.scope === FactScope.PERSONAL &&
58
- isVisibleFact(replacement, includeRestricted)
59
- ? replacement
60
- : null;
61
- return {
62
- fact: projectSearchFactDto(fact, sourceEpisode, replacementFact),
63
- current: isCurrentFact(fact),
64
- sourceEpisode,
65
- replacementFact,
66
- correctionEpisodes: correctionIndex.get(fact.id)?.episodes ?? [],
67
- correctionEpisodesTruncated: correctionIndex.get(fact.id)?.truncated ?? false
68
- };
69
- }
70
-
71
- function projectSearchFactDto(fact, sourceEpisode, replacementFact) {
72
- return {
73
- ...fact,
74
- sourceEpisodeId: sourceEpisode?.id ?? null,
75
- replacedByFactId: replacementFact?.id ?? null
76
- };
77
- }
78
-
79
- function isVisibleFact(fact, includeRestricted) {
80
- return includeRestricted === true || !isRestrictedFact(fact);
81
- }
82
-
83
- export function safePersonalEpisode(episode, personalSpaceId, includeRestricted) {
84
- if (!episode || episode.spaceId !== personalSpaceId) return null;
85
- if (!includeRestricted && isRestrictedEpisode(episode)) return null;
86
- return episode;
87
- }
@@ -1,62 +0,0 @@
1
- import { FactStatus } from '../models.js';
2
-
3
- export { compareSearchFacts } from '../storage/fact-search-order.js';
4
-
5
- export const BOUNDARY_PREDICATES = Object.freeze([
6
- 'has_boundary',
7
- 'has_access_boundary',
8
- 'does_not_want',
9
- 'avoids',
10
- 'forbids'
11
- ]);
12
- export const BOUNDARY_PREFIXES = Object.freeze([
13
- 'boundary_',
14
- 'does_not_',
15
- 'avoids_',
16
- 'forbids_',
17
- 'must_not_'
18
- ]);
19
-
20
- const STATUS_RANK = Object.freeze({
21
- [FactStatus.SUGGESTED]: 1,
22
- [FactStatus.OBSERVED]: 2,
23
- [FactStatus.CONFIRMED]: 3
24
- });
25
-
26
- export function taskTokens(task) {
27
- return new Set(normalizedTokens(task));
28
- }
29
-
30
- export function compareLensFacts(left, right, terms) {
31
- return compareNumber(isBoundary(right), isBoundary(left)) ||
32
- compareNumber(termMatches(right, terms), termMatches(left, terms)) ||
33
- compareNumber(STATUS_RANK[right.status] ?? 0, STATUS_RANK[left.status] ?? 0) ||
34
- compareText(right.validAt, left.validAt) ||
35
- compareText(left.id, right.id);
36
- }
37
-
38
- function termMatches(fact, terms) {
39
- const factTokens = new Set(normalizedTokens(`${fact.subject} ${fact.predicate} ${fact.object}`));
40
- let matches = 0;
41
- for (const term of terms) if (factTokens.has(term)) matches += 1;
42
- return matches;
43
- }
44
-
45
- function normalizedTokens(text) {
46
- return String(text).normalize('NFKC').toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
47
- }
48
-
49
- function isBoundary(fact) {
50
- return BOUNDARY_PREDICATES.includes(fact.predicate) ||
51
- BOUNDARY_PREFIXES.some((prefix) => fact.predicate.startsWith(prefix));
52
- }
53
-
54
- function compareNumber(left, right) {
55
- return left - right;
56
- }
57
-
58
- function compareText(left, right) {
59
- const first = String(left ?? '');
60
- const second = String(right ?? '');
61
- return first === second ? 0 : (first < second ? -1 : 1);
62
- }
@@ -1,143 +0,0 @@
1
- import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
2
- import { FactScope } from '../models.js';
3
- import { requirePersonalSpace } from './lens-input.js';
4
- import {
5
- BOUNDARY_PREDICATES,
6
- BOUNDARY_PREFIXES,
7
- compareLensFacts,
8
- taskTokens
9
- } from './lens-query-ranking.js';
10
- import {
11
- isInjectableFact,
12
- projectSearchFact,
13
- safePersonalEpisode
14
- } from './lens-query-projection.js';
15
- import { LensSearchQuery } from './lens-search-query.js';
16
-
17
- export { BOUNDARY_PREDICATES, BOUNDARY_PREFIXES };
18
- export {
19
- DEFAULT_SEARCH_USER_CONTEXT_LIMIT,
20
- MAX_SEARCH_USER_CONTEXT_LIMIT
21
- } from './lens-search-query.js';
22
-
23
- export class LensQuery extends LensSearchQuery {
24
- getUserLens(input) {
25
- return this.#getUserLens(input, true);
26
- }
27
-
28
- #getUserLens({
29
- personalSpaceId,
30
- task,
31
- budget,
32
- includeObserved = true,
33
- includeSuggested = false,
34
- includeRestricted = false
35
- }, redactRelations) {
36
- requirePersonalSpace(this.store, personalSpaceId);
37
- validateTask(task);
38
- validateBudget(budget);
39
-
40
- const terms = taskTokens(task);
41
- const allowRestricted = includeRestricted === true;
42
- const facts = this.store.currentFacts(personalSpaceId)
43
- .filter((fact) => fact.spaceId === personalSpaceId && fact.scope === FactScope.PERSONAL)
44
- .filter((fact) => isInjectableFact(fact, {
45
- includeObserved,
46
- includeSuggested,
47
- includeRestricted: allowRestricted
48
- }))
49
- .sort((left, right) => compareLensFacts(left, right, terms));
50
- const selected = [];
51
- const lines = [];
52
- let bytes = 0;
53
-
54
- for (const fact of facts) {
55
- const line = formatFactLine(fact);
56
- const nextBytes = bytes + (lines.length ? 1 : 0) + Buffer.byteLength(line, 'utf8');
57
- if (nextBytes > budget) continue;
58
- selected.push(fact);
59
- lines.push(line);
60
- bytes = nextBytes;
61
- }
62
-
63
- return {
64
- personalSpaceId,
65
- task,
66
- text: lines.join('\n'),
67
- facts: redactRelations
68
- ? selected.map((fact) => {
69
- const source = this.store.episodeEvidencePreview(
70
- personalSpaceId,
71
- fact.sourceEpisodeId,
72
- { includeRestricted: allowRestricted, maxBodyBytes: 0 }
73
- );
74
- return projectSearchFact(
75
- this.store,
76
- fact,
77
- source,
78
- personalSpaceId,
79
- new Map(),
80
- allowRestricted
81
- ).fact;
82
- })
83
- : selected,
84
- estimatedTokens: bytes,
85
- truncated: selected.length < facts.length
86
- };
87
- }
88
-
89
- getUserLensView(input) {
90
- return this.store.transaction(() => {
91
- const lens = this.#getUserLens(input, false);
92
- const entries = [];
93
- for (const fact of lens.facts) {
94
- const source = safePersonalEpisode(
95
- this.store.episodeEvidencePreview(
96
- lens.personalSpaceId,
97
- fact.sourceEpisodeId,
98
- { includeRestricted: input.includeRestricted === true, maxBodyBytes: 0 }
99
- ),
100
- lens.personalSpaceId,
101
- input.includeRestricted === true
102
- );
103
- if (source) entries.push(projectLensViewEntry(fact, source));
104
- }
105
- return {
106
- entries,
107
- truncated: lens.truncated || entries.length < lens.facts.length
108
- };
109
- });
110
- }
111
-
112
- }
113
-
114
- function validateTask(task) {
115
- if (typeof task !== 'string') {
116
- throw new ApplicationError(ApplicationErrorCode.VALIDATION, 'Task must be a string');
117
- }
118
- }
119
-
120
- function validateBudget(budget) {
121
- if (!Number.isInteger(budget) || budget <= 0) {
122
- throw new ApplicationError(ApplicationErrorCode.VALIDATION, 'Budget must be a positive integer');
123
- }
124
- }
125
-
126
- function formatFactLine(fact) {
127
- return `${fact.subject} ${fact.predicate} ${fact.object} (${fact.status})`;
128
- }
129
-
130
- function projectLensViewEntry(fact, source) {
131
- return {
132
- subject: fact.subject,
133
- predicate: fact.predicate,
134
- object: fact.object,
135
- status: fact.status,
136
- validAt: fact.validAt,
137
- source: {
138
- kind: source.sourceKind,
139
- uri: source.sourceUri,
140
- createdAt: source.createdAt
141
- }
142
- };
143
- }
@@ -1,93 +0,0 @@
1
- import { detectSensitiveContent } from '../security/sensitive-content.js';
2
-
3
- export function projectCurrentFact(fact) {
4
- return compactFact(fact);
5
- }
6
-
7
- export function isSafeResourceFact(fact) {
8
- return compactFact(fact) !== null;
9
- }
10
-
11
- export function projectHistoryItem(item) {
12
- const fact = compactFact(item.fact);
13
- if (!fact) return null;
14
- return {
15
- current: item.current,
16
- fact,
17
- source: compactEpisode(item.sourceEpisode),
18
- replacementFact: compactFact(item.replacementFact),
19
- correctionEpisodes: item.correctionEpisodes.map(compactCorrection).filter(Boolean)
20
- };
21
- }
22
-
23
- export function projectSubscription(subscription, space) {
24
- if (![
25
- space?.id,
26
- space?.name,
27
- space?.kind,
28
- subscription?.mode,
29
- subscription?.createdAt
30
- ].every(isSafeResourceText)) return null;
31
- const compactSpace = { id: space.id, name: space.name, kind: space.kind };
32
- if (isSafeResourceText(space.description)) {
33
- compactSpace.description = space.description;
34
- }
35
- return {
36
- space: compactSpace,
37
- mode: subscription.mode,
38
- createdAt: subscription.createdAt
39
- };
40
- }
41
-
42
- function compactFact(fact) {
43
- if (!fact || ![
44
- fact.id,
45
- fact.subject,
46
- fact.predicate,
47
- fact.object,
48
- fact.status,
49
- fact.validAt
50
- ].every(isSafeResourceText)) return null;
51
- if (!Number.isFinite(fact.confidence) || fact.confidence < 0 || fact.confidence > 1) {
52
- return null;
53
- }
54
- return {
55
- id: fact.id,
56
- subject: fact.subject,
57
- predicate: fact.predicate,
58
- object: fact.object,
59
- status: fact.status,
60
- confidence: fact.confidence,
61
- validAt: fact.validAt
62
- };
63
- }
64
-
65
- function compactEpisode(episode) {
66
- if (!episode) return null;
67
- if (![
68
- episode.id,
69
- episode.sourceKind,
70
- episode.createdAt
71
- ].every(isSafeResourceText)) return null;
72
- if (!isSafeResourceText(episode.sourceUri, { nullable: true })) return null;
73
- return {
74
- id: episode.id,
75
- kind: episode.sourceKind,
76
- uri: episode.sourceUri,
77
- createdAt: episode.createdAt
78
- };
79
- }
80
-
81
- function compactCorrection(episode) {
82
- const compact = compactEpisode(episode);
83
- if (!compact || !isSafeResourceText(episode.metadata?.action)) return null;
84
- return {
85
- ...compact,
86
- action: episode.metadata.action
87
- };
88
- }
89
-
90
- export function isSafeResourceText(value, { nullable = false } = {}) {
91
- if (value === null || value === undefined) return nullable;
92
- return typeof value === 'string' && !detectSensitiveContent(value).restricted;
93
- }
@@ -1,45 +0,0 @@
1
- const EMPTY_ARRAY_BYTES = 2;
2
- const HISTORY_ENVELOPE_RESERVE_BYTES = 1024;
3
-
4
- export function selectCurrentFacts(facts, budgetBytes) {
5
- const selected = [];
6
- const lines = [];
7
- let bytes = 0;
8
- for (const fact of facts) {
9
- const line = `${fact.subject} ${fact.predicate} ${fact.object} (${fact.status})`;
10
- const nextBytes = bytes + (lines.length ? 1 : 0) + Buffer.byteLength(line, 'utf8');
11
- if (nextBytes > budgetBytes) continue;
12
- selected.push(fact);
13
- lines.push(line);
14
- bytes = nextBytes;
15
- }
16
- return {
17
- facts: selected,
18
- text: lines.join('\n'),
19
- truncated: selected.length < facts.length
20
- };
21
- }
22
-
23
- export function selectHistoryItems(items, { limit, budgetBytes }) {
24
- const selected = [];
25
- const itemsBudget = budgetBytes - HISTORY_ENVELOPE_RESERVE_BYTES;
26
- let itemsBytes = EMPTY_ARRAY_BYTES;
27
- let truncated = false;
28
-
29
- for (const item of items) {
30
- if (selected.length >= limit) {
31
- truncated = true;
32
- break;
33
- }
34
- const itemBytes = Buffer.byteLength(JSON.stringify(item), 'utf8');
35
- const nextItemsBytes = itemsBytes + (selected.length ? 1 : 0) + itemBytes;
36
- if (nextItemsBytes > itemsBudget) {
37
- truncated = true;
38
- continue;
39
- }
40
- selected.push(item);
41
- itemsBytes = nextItemsBytes;
42
- }
43
-
44
- return { items: selected, itemsBytes, truncated };
45
- }