fuli-context 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +178 -0
  3. package/compose.graphiti.yml +86 -0
  4. package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
  5. package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
  6. package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
  7. package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
  8. package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
  9. package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
  10. package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
  11. package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
  12. package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
  13. package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
  14. package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
  15. package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
  16. package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
  17. package/dist/web/assets/console-tbvTUi6p.js +1 -0
  18. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  19. package/dist/web/assets/identity-B3s5CXhv.js +1 -0
  20. package/dist/web/assets/index-Devf7glZ.css +1 -0
  21. package/dist/web/assets/index-DwUDhQKc.js +6 -0
  22. package/dist/web/assets/model-DVT8sjy9.js +1 -0
  23. package/dist/web/assets/paths-B790iniO.js +1 -0
  24. package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
  25. package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
  26. package/dist/web/index.html +22 -0
  27. package/graph-provider/Dockerfile +16 -0
  28. package/graph-provider/fuli_graph/__init__.py +5 -0
  29. package/graph-provider/fuli_graph/app.py +547 -0
  30. package/graph-provider/fuli_graph/auth.py +17 -0
  31. package/graph-provider/fuli_graph/collaboration_context.py +290 -0
  32. package/graph-provider/fuli_graph/config.py +34 -0
  33. package/graph-provider/fuli_graph/graph_models.py +123 -0
  34. package/graph-provider/fuli_graph/graph_projection.py +405 -0
  35. package/graph-provider/fuli_graph/graph_query.py +561 -0
  36. package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
  37. package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
  38. package/graph-provider/fuli_graph/knowledge_management.py +816 -0
  39. package/graph-provider/fuli_graph/knowledge_records.py +65 -0
  40. package/graph-provider/fuli_graph/knowledge_search.py +723 -0
  41. package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
  42. package/graph-provider/fuli_graph/model_validation.py +58 -0
  43. package/graph-provider/fuli_graph/models.py +1179 -0
  44. package/graph-provider/fuli_graph/personal_project_access.py +29 -0
  45. package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
  46. package/graph-provider/fuli_graph/project_action_models.py +142 -0
  47. package/graph-provider/fuli_graph/project_knowledge.py +707 -0
  48. package/graph-provider/fuli_graph/project_management.py +251 -0
  49. package/graph-provider/fuli_graph/provider_values.py +40 -0
  50. package/graph-provider/fuli_graph/runtime.py +350 -0
  51. package/graph-provider/fuli_graph/search_projection.py +156 -0
  52. package/graph-provider/fuli_graph/store.py +24 -0
  53. package/graph-provider/fuli_graph/store_identity.py +237 -0
  54. package/graph-provider/fuli_graph/store_knowledge.py +447 -0
  55. package/graph-provider/fuli_graph/store_projects.py +242 -0
  56. package/graph-provider/fuli_graph/store_publication.py +370 -0
  57. package/graph-provider/fuli_graph/store_records.py +134 -0
  58. package/graph-provider/pyproject.toml +35 -0
  59. package/npm-shrinkwrap.json +6045 -0
  60. package/package.json +99 -0
  61. package/skills/capturing-session-knowledge/SKILL.md +226 -0
  62. package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
  63. package/skills/grilling-project/SKILL.md +129 -0
  64. package/skills/grilling-project/agents/openai.yaml +4 -0
  65. package/skills/grilling-project/references/project-profile.md +105 -0
  66. package/src/agent/candidate-read-service.js +53 -0
  67. package/src/agent/context-pack-service.js +83 -0
  68. package/src/agent/public-read-policy.js +39 -0
  69. package/src/agent/public-read-service.js +69 -0
  70. package/src/agent/read-projection.js +107 -0
  71. package/src/agent/search-service.js +61 -0
  72. package/src/agent/space-boundary.js +33 -0
  73. package/src/agent-tools/graph-definitions.js +479 -0
  74. package/src/agent-tools/graph-handlers.js +96 -0
  75. package/src/agent-tools/schema.js +37 -0
  76. package/src/agent-tools.js +16 -0
  77. package/src/app/application-error.js +13 -0
  78. package/src/app/bootstrap.js +8 -0
  79. package/src/app/create-application.js +143 -0
  80. package/src/app/state-service.js +38 -0
  81. package/src/candidates.js +78 -0
  82. package/src/classifier.js +63 -0
  83. package/src/cli/command-arguments.js +14 -0
  84. package/src/cli/command-registry.js +78 -0
  85. package/src/cli/commands/candidates.js +17 -0
  86. package/src/cli/commands/ingestion.js +32 -0
  87. package/src/cli/commands/reads.js +50 -0
  88. package/src/cli/commands/spaces.js +20 -0
  89. package/src/cli/invocation.js +47 -0
  90. package/src/cli/local-runtime-command.js +94 -0
  91. package/src/cli/local-runtime-options.js +64 -0
  92. package/src/cli/migrate-command.js +71 -0
  93. package/src/cli/migration-destination.js +117 -0
  94. package/src/cli/setup-agent-selection.js +195 -0
  95. package/src/cli/setup-command.js +85 -0
  96. package/src/cli/uninstall-command.js +67 -0
  97. package/src/cli.js +90 -0
  98. package/src/context-pack.js +206 -0
  99. package/src/defaults.js +1 -0
  100. package/src/extractor.js +77 -0
  101. package/src/fact-history.js +39 -0
  102. package/src/facts/fact-service.js +89 -0
  103. package/src/graphiti/agent-access-policy.js +88 -0
  104. package/src/graphiti/capture-policy.js +84 -0
  105. package/src/graphiti/federated-application.js +1292 -0
  106. package/src/graphiti/personal-context.js +29 -0
  107. package/src/graphiti/project-path-context.js +159 -0
  108. package/src/graphiti/provider-client.js +288 -0
  109. package/src/graphiti/runtime-config.js +85 -0
  110. package/src/graphiti/setup.js +43 -0
  111. package/src/graphiti/source-marker.js +142 -0
  112. package/src/http/api-router.js +133 -0
  113. package/src/http/bounded-json-array.js +19 -0
  114. package/src/http/error-mapping.js +36 -0
  115. package/src/http/graph-api-router.js +257 -0
  116. package/src/http/lens-route.js +50 -0
  117. package/src/http/request-policy.js +41 -0
  118. package/src/http/response.js +29 -0
  119. package/src/http/static-handler.js +55 -0
  120. package/src/ingestion.js +161 -0
  121. package/src/lens/lens-correction-service.js +81 -0
  122. package/src/lens/lens-fact-writer.js +85 -0
  123. package/src/lens/lens-input.js +62 -0
  124. package/src/lens/lens-query-projection.js +87 -0
  125. package/src/lens/lens-query-ranking.js +62 -0
  126. package/src/lens/lens-query.js +143 -0
  127. package/src/lens/lens-resource-projection.js +93 -0
  128. package/src/lens/lens-resource-selection.js +45 -0
  129. package/src/lens/lens-resource-service.js +103 -0
  130. package/src/lens/lens-search-query.js +122 -0
  131. package/src/lens/lens-service.js +183 -0
  132. package/src/local-runtime/lifecycle.js +254 -0
  133. package/src/mcp/create-mcp-server.js +85 -0
  134. package/src/mcp/instructions.js +10 -0
  135. package/src/mcp/project-action-preview-tokens.js +72 -0
  136. package/src/mcp/runtime.js +71 -0
  137. package/src/mcp/tool-annotations.js +63 -0
  138. package/src/mcp/tool-result.js +167 -0
  139. package/src/mcp/tool-schema.js +60 -0
  140. package/src/mcp-server.js +76 -0
  141. package/src/models.js +44 -0
  142. package/src/observer.js +83 -0
  143. package/src/package-metadata.js +6 -0
  144. package/src/project-rules.js +33 -0
  145. package/src/publication/canonical-json.js +39 -0
  146. package/src/publication/outbox-service.js +46 -0
  147. package/src/publication/publication-envelope-schema.js +75 -0
  148. package/src/publication/publication-envelope.js +37 -0
  149. package/src/publication/publication-policy.js +82 -0
  150. package/src/publication/publication-service.js +48 -0
  151. package/src/router.js +58 -0
  152. package/src/runtime-options.js +139 -0
  153. package/src/security/episode-visibility.js +14 -0
  154. package/src/security/fact-visibility.js +15 -0
  155. package/src/security/sensitive-content.js +18 -0
  156. package/src/security/source-metadata-policy.js +8 -0
  157. package/src/server/application-lifecycle.js +34 -0
  158. package/src/server/listen.js +24 -0
  159. package/src/server.js +135 -0
  160. package/src/setup/agent-installation-status.js +97 -0
  161. package/src/setup/agents.js +123 -0
  162. package/src/setup/codex-bootstrap.js +118 -0
  163. package/src/setup/codex-config.js +122 -0
  164. package/src/setup/config-backup.js +22 -0
  165. package/src/setup/container-runtime.js +388 -0
  166. package/src/setup/cursor-config.js +61 -0
  167. package/src/setup/graph-runtime.js +360 -0
  168. package/src/setup/node-runtime.js +24 -0
  169. package/src/setup/options.js +91 -0
  170. package/src/setup/paths.js +47 -0
  171. package/src/setup/session-skill.js +91 -0
  172. package/src/setup/setup.js +85 -0
  173. package/src/setup/uninstall.js +90 -0
  174. package/src/storage/episode-evidence-preview.js +100 -0
  175. package/src/storage/fact-search-order.js +18 -0
  176. package/src/storage/file/episode-evidence.js +37 -0
  177. package/src/storage/file/episode-index.js +59 -0
  178. package/src/storage/file/fact-search.js +60 -0
  179. package/src/storage/file-store.js +437 -0
  180. package/src/storage/import-json.js +70 -0
  181. package/src/storage/json-file.js +41 -0
  182. package/src/storage/legacy-record-validation.js +152 -0
  183. package/src/storage/legacy-snapshot-validation.js +32 -0
  184. package/src/storage/migrate.js +128 -0
  185. package/src/storage/migrations/001-initial.sql +83 -0
  186. package/src/storage/migrations/002-storage-integrity.sql +113 -0
  187. package/src/storage/record-validation.js +46 -0
  188. package/src/storage/snapshot-defaults.js +54 -0
  189. package/src/storage/sqlite/candidates-repository.js +84 -0
  190. package/src/storage/sqlite/episode-evidence-repository.js +154 -0
  191. package/src/storage/sqlite/episode-privacy-sql.js +33 -0
  192. package/src/storage/sqlite/episodes-repository.js +52 -0
  193. package/src/storage/sqlite/fact-search-repository.js +162 -0
  194. package/src/storage/sqlite/facts-repository.js +146 -0
  195. package/src/storage/sqlite/imports-repository.js +39 -0
  196. package/src/storage/sqlite/mapper.js +84 -0
  197. package/src/storage/sqlite/open-database.js +15 -0
  198. package/src/storage/sqlite/outbox-repository.js +103 -0
  199. package/src/storage/sqlite/snapshot-repository.js +69 -0
  200. package/src/storage/sqlite/spaces-repository.js +52 -0
  201. package/src/storage/sqlite/subscriptions-repository.js +54 -0
  202. package/src/storage/sqlite-store.js +159 -0
  203. package/src/storage/stable-json.js +24 -0
  204. package/src/storage/store-port.js +20 -0
  205. package/src/store.js +2 -0
  206. package/src/workspace-protocol/array-validation.js +19 -0
  207. package/src/workspace-protocol/base64url-contract.js +27 -0
  208. package/src/workspace-protocol/constants.js +37 -0
  209. package/src/workspace-protocol/error-contract.js +24 -0
  210. package/src/workspace-protocol/fact-contract.js +110 -0
  211. package/src/workspace-protocol/governance-contract.js +47 -0
  212. package/src/workspace-protocol/index.js +53 -0
  213. package/src/workspace-protocol/json-budget.js +19 -0
  214. package/src/workspace-protocol/manifest-contract.js +186 -0
  215. package/src/workspace-protocol/publication-contract.js +212 -0
  216. package/src/workspace-protocol/query-contract.js +149 -0
  217. package/src/workspace-protocol/scalar-contract.js +192 -0
  218. package/src/workspace-protocol/sync-contract.js +184 -0
  219. package/src/workspace-protocol/text-contract.js +39 -0
  220. package/src/workspace-protocol/validation.js +66 -0
@@ -0,0 +1,83 @@
1
+ import { FactStatus } from '../models.js';
2
+ import { compactHistoryFact, compactMatch } from './read-projection.js';
3
+ import { requirePersonalSpace, requirePublicSpace } from './space-boundary.js';
4
+
5
+ export class AgentContextPackService {
6
+ constructor(store, publicReads, search, candidateReads) {
7
+ this.store = store;
8
+ this.publicReads = publicReads;
9
+ this.search = search;
10
+ this.candidateReads = candidateReads;
11
+ }
12
+
13
+ build({ personalSpaceId, spaceId, query = '' }) {
14
+ const personal = requirePersonalSpace(this.store, personalSpaceId);
15
+ const project = requirePublicSpace(this.store, spaceId);
16
+ const personalMatches = this.search
17
+ .personalMatches({ personalSpaceId, query, includeHistorical: false }, personal.name)
18
+ .filter(({ fact }) => [FactStatus.CONFIRMED, FactStatus.OBSERVED].includes(fact.status));
19
+ const publicMatches = this.search.publicMatches([spaceId], query);
20
+ const matches = [...personalMatches, ...publicMatches].map(compactMatch);
21
+ const candidates = this.candidateReads.listForSpace(personalSpaceId, spaceId);
22
+ return {
23
+ query,
24
+ personalSpace: compactSpace(personal),
25
+ space: compactSpace(project),
26
+ rules: this.#rules(spaceId),
27
+ matches,
28
+ histories: this.#histories(publicMatches),
29
+ candidateCount: candidates.length,
30
+ candidates,
31
+ answer: formatAnswer(matches)
32
+ };
33
+ }
34
+
35
+ #rules(spaceId) {
36
+ const rules = this.publicReads.projectRules(spaceId);
37
+ const compact = (fact) => compactHistoryFact(this.store, {
38
+ ...fact,
39
+ current: true
40
+ });
41
+ return {
42
+ spaceId: rules.spaceId,
43
+ spaceName: rules.spaceName,
44
+ forbidden: rules.forbidden.map(compact),
45
+ parameters: rules.parameters.map(compact),
46
+ links: rules.links.map(compact)
47
+ };
48
+ }
49
+
50
+ #histories(matches) {
51
+ const seen = new Set();
52
+ const histories = [];
53
+ for (const match of matches) {
54
+ const key = `${match.fact.spaceId}:${match.fact.predicate}`;
55
+ if (seen.has(key)) continue;
56
+ seen.add(key);
57
+ const history = this.publicReads.factHistory({
58
+ spaceId: match.fact.spaceId,
59
+ predicate: match.fact.predicate
60
+ });
61
+ if (history.facts.length <= 1) continue;
62
+ histories.push({
63
+ spaceId: history.spaceId,
64
+ spaceName: history.spaceName,
65
+ predicate: history.predicate,
66
+ facts: history.facts.map((fact) => compactHistoryFact(this.store, fact))
67
+ });
68
+ }
69
+ return histories;
70
+ }
71
+
72
+ }
73
+
74
+ function compactSpace(space) {
75
+ return { id: space.id, name: space.name, kind: space.kind };
76
+ }
77
+
78
+ function formatAnswer(matches) {
79
+ if (!matches.length) return 'No matching current facts found.';
80
+ return matches
81
+ .map(({ fact }) => `${fact.subject} ${fact.predicate} ${fact.object}`)
82
+ .join('\n');
83
+ }
@@ -0,0 +1,39 @@
1
+ import { FactScope, FactStatus, Sensitivity } from '../models.js';
2
+ import { detectSensitiveContent } from '../security/sensitive-content.js';
3
+
4
+ export function isAgentReadablePublicFact(store, fact) {
5
+ return fact?.scope === FactScope.PUBLIC &&
6
+ fact.status === FactStatus.CONFIRMED &&
7
+ fact.sensitivity === Sensitivity.NORMAL &&
8
+ [fact.subject, fact.predicate, fact.object].every(isSafeText) &&
9
+ getAgentReadablePublicSource(store, fact) !== null;
10
+ }
11
+
12
+ export function getAgentReadablePublicSource(store, fact) {
13
+ const episode = fact ? store.getEpisode(fact.sourceEpisodeId) : null;
14
+ if (!episode || episode.spaceId !== fact.spaceId) return null;
15
+ if (![
16
+ episode.sourceKind,
17
+ episode.sourceUri,
18
+ episode.body,
19
+ JSON.stringify(episode.metadata)
20
+ ].every(isSafeText)) return null;
21
+ return episode;
22
+ }
23
+
24
+ export function getAgentReadableReplacementId(store, fact) {
25
+ if (!fact.replacedByFactId) return null;
26
+ const replacement = store.getFact(fact.replacedByFactId);
27
+ return replacement?.spaceId === fact.spaceId && isAgentReadablePublicFact(store, replacement)
28
+ ? replacement.id
29
+ : null;
30
+ }
31
+
32
+ export function isSafeAgentText(value) {
33
+ return isSafeText(value);
34
+ }
35
+
36
+ function isSafeText(value) {
37
+ return value === null || value === undefined ||
38
+ !detectSensitiveContent(String(value)).restricted;
39
+ }
@@ -0,0 +1,69 @@
1
+ import { getFactHistory } from '../fact-history.js';
2
+ import { FactScope } from '../models.js';
3
+ import { getProjectRules } from '../project-rules.js';
4
+ import { projectPublicMatch } from './read-projection.js';
5
+ import { requirePublicSpace } from './space-boundary.js';
6
+
7
+ export class AgentPublicReadService {
8
+ constructor(store) {
9
+ this.store = store;
10
+ }
11
+
12
+ currentFacts(spaceId) {
13
+ requirePublicSpace(this.store, spaceId);
14
+ return this.#publicFacts(this.store.currentFacts(spaceId));
15
+ }
16
+
17
+ timeline(spaceId, subject) {
18
+ requirePublicSpace(this.store, spaceId);
19
+ return this.#publicFacts(this.store.timeline(spaceId, subject));
20
+ }
21
+
22
+ projectRules(spaceId) {
23
+ requirePublicSpace(this.store, spaceId);
24
+ const rules = getProjectRules(this.store, spaceId);
25
+ const facts = rules.facts
26
+ .filter((fact) => fact.scope === FactScope.PUBLIC)
27
+ .map((fact) => this.#enrich(fact))
28
+ .filter(Boolean);
29
+ return {
30
+ spaceId: rules.spaceId,
31
+ spaceName: rules.spaceName,
32
+ forbidden: facts.filter((fact) => fact.predicate === 'forbids'),
33
+ parameters: facts.filter(isProjectParameter),
34
+ links: facts.filter((fact) => fact.predicate === 'has_url'),
35
+ facts
36
+ };
37
+ }
38
+
39
+ factHistory(input) {
40
+ requirePublicSpace(this.store, input.spaceId);
41
+ const history = getFactHistory(this.store, input);
42
+ return {
43
+ ...history,
44
+ facts: history.facts
45
+ .filter((fact) => fact.scope === FactScope.PUBLIC)
46
+ .map((fact) => {
47
+ const enriched = this.#enrich(fact);
48
+ return enriched ? { ...enriched, current: fact.current } : null;
49
+ })
50
+ .filter(Boolean)
51
+ };
52
+ }
53
+
54
+ #publicFacts(facts) {
55
+ return facts
56
+ .filter((fact) => fact.scope === FactScope.PUBLIC)
57
+ .map((fact) => projectPublicMatch(this.store, fact)?.fact)
58
+ .filter(Boolean);
59
+ }
60
+
61
+ #enrich(fact) {
62
+ const match = projectPublicMatch(this.store, fact);
63
+ return match ? { ...match.fact, source: match.source } : null;
64
+ }
65
+ }
66
+
67
+ function isProjectParameter(fact) {
68
+ return fact.predicate.startsWith('has_') && fact.predicate !== 'has_url';
69
+ }
@@ -0,0 +1,107 @@
1
+ import { isCurrentFact } from '../models.js';
2
+ import {
3
+ getAgentReadablePublicSource,
4
+ getAgentReadableReplacementId,
5
+ isAgentReadablePublicFact
6
+ } from './public-read-policy.js';
7
+
8
+ export function projectPersonalMatch(item, spaceName) {
9
+ return {
10
+ spaceId: item.fact.spaceId,
11
+ spaceName,
12
+ current: item.current,
13
+ fact: item.fact,
14
+ source: projectEpisode(item.sourceEpisode)
15
+ };
16
+ }
17
+
18
+ export function projectPublicMatch(store, fact) {
19
+ if (!isAgentReadablePublicFact(store, fact)) return null;
20
+ const source = projectEpisode(getAgentReadablePublicSource(store, fact));
21
+ return {
22
+ spaceId: fact.spaceId,
23
+ spaceName: store.getSpace(fact.spaceId)?.name ?? fact.spaceId,
24
+ current: isCurrentFact(fact),
25
+ fact: projectPublicFact(store, fact, source),
26
+ source
27
+ };
28
+ }
29
+
30
+ export function compactMatch(match) {
31
+ return {
32
+ spaceId: match.spaceId,
33
+ spaceName: match.spaceName,
34
+ current: match.current,
35
+ fact: {
36
+ id: match.fact.id,
37
+ spaceId: match.fact.spaceId,
38
+ subject: match.fact.subject,
39
+ predicate: match.fact.predicate,
40
+ object: match.fact.object,
41
+ status: match.fact.status,
42
+ validAt: match.fact.validAt,
43
+ invalidAt: match.fact.invalidAt
44
+ },
45
+ source: compactSource(match.source)
46
+ };
47
+ }
48
+
49
+ export function compactHistoryFact(store, fact) {
50
+ return {
51
+ id: fact.id,
52
+ spaceId: fact.spaceId,
53
+ spaceName: store.getSpace(fact.spaceId)?.name ?? fact.spaceId,
54
+ subject: fact.subject,
55
+ predicate: fact.predicate,
56
+ object: fact.object,
57
+ status: fact.status,
58
+ current: fact.current,
59
+ validAt: fact.validAt,
60
+ invalidAt: fact.invalidAt,
61
+ source: compactSource(fact.source)
62
+ };
63
+ }
64
+
65
+ export function compactSource(source) {
66
+ if (!source) return null;
67
+ return {
68
+ id: source.id,
69
+ kind: source.kind ?? source.sourceKind,
70
+ uri: source.uri ?? source.sourceUri,
71
+ createdAt: source.createdAt,
72
+ preview: firstLine(source.body)
73
+ };
74
+ }
75
+
76
+ function projectPublicFact(store, fact, source) {
77
+ return {
78
+ id: fact.id,
79
+ spaceId: fact.spaceId,
80
+ subject: fact.subject,
81
+ predicate: fact.predicate,
82
+ object: fact.object,
83
+ sourceEpisodeId: source?.id ?? null,
84
+ validAt: fact.validAt,
85
+ invalidAt: fact.invalidAt,
86
+ replacedByFactId: getAgentReadableReplacementId(store, fact),
87
+ confidence: fact.confidence,
88
+ sensitivity: fact.sensitivity,
89
+ scope: fact.scope,
90
+ status: fact.status
91
+ };
92
+ }
93
+
94
+ function projectEpisode(episode) {
95
+ if (!episode) return null;
96
+ return {
97
+ id: episode.id,
98
+ kind: episode.sourceKind,
99
+ uri: episode.sourceUri,
100
+ body: episode.body,
101
+ createdAt: episode.createdAt
102
+ };
103
+ }
104
+
105
+ function firstLine(value) {
106
+ return value === undefined ? undefined : String(value).split(/\r?\n/)[0].slice(0, 160);
107
+ }
@@ -0,0 +1,61 @@
1
+ import { FactScope } from '../models.js';
2
+ import { projectPersonalMatch, projectPublicMatch } from './read-projection.js';
3
+ import { requirePersonalSpace } from './space-boundary.js';
4
+
5
+ export class AgentSearchService {
6
+ constructor(store, lensQuery) {
7
+ this.store = store;
8
+ this.lensQuery = lensQuery;
9
+ }
10
+
11
+ search({ personalSpaceId, query, includeHistorical = false }) {
12
+ const personal = requirePersonalSpace(this.store, personalSpaceId);
13
+ const publicSpaceIds = this.#subscribedPublicSpaceIds(personalSpaceId);
14
+ const matches = [
15
+ ...this.personalMatches({ personalSpaceId, query, includeHistorical }, personal.name),
16
+ ...this.publicMatches(publicSpaceIds, query, includeHistorical)
17
+ ];
18
+ const facts = matches.map((match) => match.fact);
19
+ return {
20
+ query,
21
+ spaceIds: [personalSpaceId, ...publicSpaceIds],
22
+ matches,
23
+ facts,
24
+ answer: formatAnswer(facts)
25
+ };
26
+ }
27
+
28
+ personalMatches(input, spaceName) {
29
+ const personal = spaceName ?? requirePersonalSpace(this.store, input.personalSpaceId).name;
30
+ return this.lensQuery.searchUserContext({
31
+ ...input,
32
+ includeRestricted: false
33
+ }).facts.map((item) => projectPersonalMatch(item, personal));
34
+ }
35
+
36
+ publicMatches(spaceIds, query, includeHistorical = false) {
37
+ if (!spaceIds.length) return [];
38
+ return this.store.searchFacts(spaceIds, query, { includeHistorical })
39
+ .filter((fact) => fact.scope === FactScope.PUBLIC)
40
+ .map((fact) => projectPublicMatch(this.store, fact))
41
+ .filter(Boolean);
42
+ }
43
+
44
+ #subscribedPublicSpaceIds(personalSpaceId) {
45
+ return this.store.subscriptionsFor(personalSpaceId)
46
+ .map((subscription) => this.store.getSpace(subscription.spaceId))
47
+ .filter((space) => space?.kind === 'public')
48
+ .map((space) => space.id);
49
+ }
50
+ }
51
+
52
+ function formatAnswer(facts) {
53
+ if (!facts.length) return '没有找到相关当前事实';
54
+ return facts
55
+ .map((fact) => `${fact.subject}:${humanPredicate(fact.predicate)} 是 ${fact.object}`)
56
+ .join('\n');
57
+ }
58
+
59
+ function humanPredicate(predicate) {
60
+ return predicate.startsWith('has_') ? predicate.slice(4) : predicate;
61
+ }
@@ -0,0 +1,33 @@
1
+ import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
2
+ import { SpaceKind } from '../models.js';
3
+
4
+ export function requirePublicSpace(store, spaceId) {
5
+ const space = store.getSpace(spaceId);
6
+ if (!space) {
7
+ throw new ApplicationError(ApplicationErrorCode.NOT_FOUND, `Space not found: ${spaceId}`);
8
+ }
9
+ if (space.kind !== SpaceKind.PUBLIC) {
10
+ throw new ApplicationError(
11
+ ApplicationErrorCode.VALIDATION,
12
+ `Agent project reads require a public space: ${spaceId}`
13
+ );
14
+ }
15
+ return space;
16
+ }
17
+
18
+ export function requirePersonalSpace(store, spaceId) {
19
+ const space = store.getSpace(spaceId);
20
+ if (!space) {
21
+ throw new ApplicationError(
22
+ ApplicationErrorCode.NOT_FOUND,
23
+ `Personal space not found: ${spaceId}`
24
+ );
25
+ }
26
+ if (space.kind !== SpaceKind.PERSONAL) {
27
+ throw new ApplicationError(
28
+ ApplicationErrorCode.VALIDATION,
29
+ `Space is not personal: ${spaceId}`
30
+ );
31
+ }
32
+ return space;
33
+ }