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,29 @@
1
+ const PERSONAL_PROJECT_SEARCH_BATCH_SIZE = 16;
2
+
3
+ export function personalContextProjectIds(activeProjectId, contextProjectIds = []) {
4
+ const values = [activeProjectId, ...contextProjectIds]
5
+ .filter((value) => typeof value === 'string' && value.length > 0);
6
+ return [...new Set(values)];
7
+ }
8
+
9
+ export function personalProjectSearchBatches(projectIds) {
10
+ if (projectIds.length === 0) return [[]];
11
+ const batches = [];
12
+ for (let index = 0; index < projectIds.length; index += PERSONAL_PROJECT_SEARCH_BATCH_SIZE) {
13
+ batches.push(projectIds.slice(index, index + PERSONAL_PROJECT_SEARCH_BATCH_SIZE));
14
+ }
15
+ return batches;
16
+ }
17
+
18
+ export function scopedSearchItems(result, scope, providerUrl = null, defaultSpaceId = null) {
19
+ const project = (item) => ({
20
+ ...item,
21
+ scope,
22
+ providerUrl,
23
+ spaceId: item.space_id ?? defaultSpaceId
24
+ });
25
+ return {
26
+ facts: (result.facts ?? []).map(project),
27
+ entities: (result.entities ?? []).map(project)
28
+ };
29
+ }
@@ -0,0 +1,159 @@
1
+ import {
2
+ existsSync,
3
+ readFileSync,
4
+ realpathSync,
5
+ statSync
6
+ } from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ const CHILD_PROJECT_MARKERS = Object.freeze([
10
+ '.git',
11
+ 'package.json',
12
+ 'pyproject.toml',
13
+ 'Cargo.toml',
14
+ 'go.mod'
15
+ ]);
16
+
17
+ export function resolvePersonalProjectPath(projectPath, projects, {
18
+ fileExists = existsSync,
19
+ isDirectory = defaultIsDirectory,
20
+ realPath = defaultRealPath,
21
+ readText = defaultReadText,
22
+ pathApi = path
23
+ } = {}) {
24
+ if (projectPath === undefined || projectPath === null) {
25
+ return unresolved('not_provided');
26
+ }
27
+ if (
28
+ typeof projectPath !== 'string'
29
+ || !projectPath.trim()
30
+ || !pathApi.isAbsolute(projectPath)
31
+ ) {
32
+ throw new TypeError('projectPath must be an existing absolute directory');
33
+ }
34
+
35
+ const currentPath = realPath(pathApi.resolve(projectPath));
36
+ if (!fileExists(currentPath) || !isDirectory(currentPath)) {
37
+ throw new TypeError('projectPath must be an existing absolute directory');
38
+ }
39
+
40
+ const projectIds = new Set(
41
+ projects
42
+ .map(({ project_id: projectId }) => projectId)
43
+ .filter((projectId) => safeProjectId(projectId, pathApi))
44
+ );
45
+ const repositoryRoot = findRepositoryRoot(currentPath, {
46
+ fileExists,
47
+ pathApi
48
+ });
49
+ if (repositoryRoot) {
50
+ const repositoryId = pathApi.basename(repositoryRoot);
51
+ if (projectIds.has(repositoryId)) {
52
+ return matched(repositoryId, 'repository_root');
53
+ }
54
+ const worktreeId = worktreeOriginProjectId(
55
+ pathApi.join(repositoryRoot, '.git'),
56
+ { readText, pathApi }
57
+ );
58
+ if (worktreeId && projectIds.has(worktreeId)) {
59
+ return matched(worktreeId, 'git_worktree_origin');
60
+ }
61
+ }
62
+
63
+ const directoryId = pathApi.basename(currentPath);
64
+ if (projectIds.has(directoryId)) {
65
+ return matched(directoryId, 'directory_name');
66
+ }
67
+
68
+ const childMatches = [...projectIds].filter((projectId) => (
69
+ CHILD_PROJECT_MARKERS.some((marker) =>
70
+ fileExists(pathApi.join(currentPath, projectId, marker))
71
+ )
72
+ ));
73
+ if (childMatches.length === 1) {
74
+ return matched(childMatches[0], 'workspace_child');
75
+ }
76
+ if (childMatches.length > 1) {
77
+ return unresolved('ambiguous', { candidateCount: childMatches.length });
78
+ }
79
+ return unresolved('unmatched');
80
+ }
81
+
82
+ export function findRepositoryRoot(projectPath, {
83
+ fileExists = existsSync,
84
+ pathApi = path
85
+ } = {}) {
86
+ let current = pathApi.resolve(projectPath);
87
+ const filesystemRoot = pathApi.parse(current).root;
88
+ while (true) {
89
+ if (fileExists(pathApi.join(current, '.git'))) return current;
90
+ if (current === filesystemRoot) return null;
91
+ const parent = pathApi.dirname(current);
92
+ if (parent === current) return null;
93
+ current = parent;
94
+ }
95
+ }
96
+
97
+ function worktreeOriginProjectId(gitMarkerPath, {
98
+ readText,
99
+ pathApi
100
+ }) {
101
+ const source = readText(gitMarkerPath);
102
+ const gitDirectory = /^gitdir:\s*(.+)$/im.exec(source)?.[1]?.trim();
103
+ if (!gitDirectory) return null;
104
+ const resolved = pathApi.isAbsolute(gitDirectory)
105
+ ? pathApi.normalize(gitDirectory)
106
+ : pathApi.resolve(pathApi.dirname(gitMarkerPath), gitDirectory);
107
+ const marker = /[\\/]\.git[\\/]worktrees[\\/]/i.exec(resolved);
108
+ if (!marker) return null;
109
+ return pathApi.basename(resolved.slice(0, marker.index));
110
+ }
111
+
112
+ function safeProjectId(projectId, pathApi) {
113
+ return typeof projectId === 'string'
114
+ && projectId.length > 0
115
+ && projectId !== '.'
116
+ && projectId !== '..'
117
+ && pathApi.basename(projectId) === projectId;
118
+ }
119
+
120
+ function matched(personalProjectId, basis) {
121
+ return {
122
+ status: 'matched',
123
+ basis,
124
+ personalProjectId
125
+ };
126
+ }
127
+
128
+ function unresolved(status, extra = {}) {
129
+ return {
130
+ status,
131
+ basis: null,
132
+ personalProjectId: null,
133
+ ...extra
134
+ };
135
+ }
136
+
137
+ function defaultReadText(filePath) {
138
+ try {
139
+ return readFileSync(filePath, 'utf8');
140
+ } catch {
141
+ return '';
142
+ }
143
+ }
144
+
145
+ function defaultIsDirectory(filePath) {
146
+ try {
147
+ return statSync(filePath).isDirectory();
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ function defaultRealPath(filePath) {
154
+ try {
155
+ return realpathSync.native(filePath);
156
+ } catch {
157
+ return filePath;
158
+ }
159
+ }
@@ -0,0 +1,288 @@
1
+ import { ApplicationError } from '../app/application-error.js';
2
+
3
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
4
+ const MAX_REQUEST_TIMEOUT_MS = 2_147_483_647;
5
+
6
+ export class ProviderRequestError extends ApplicationError {
7
+ constructor(message, { status = 0, code = 'provider_error', details = null } = {}) {
8
+ super(code, message);
9
+ this.name = 'ProviderRequestError';
10
+ this.status = status;
11
+ this.code = code;
12
+ this.details = details;
13
+ }
14
+ }
15
+
16
+ export class GraphitiProviderClient {
17
+ constructor({
18
+ baseUrl,
19
+ accessToken,
20
+ fetchImpl = globalThis.fetch,
21
+ requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS
22
+ }) {
23
+ if (!baseUrl) throw new TypeError('Graphiti provider baseUrl is required');
24
+ if (!accessToken) throw new TypeError('Graphiti provider accessToken is required');
25
+ if (typeof fetchImpl !== 'function') throw new TypeError('fetch implementation is required');
26
+ if (!Number.isSafeInteger(requestTimeoutMs) ||
27
+ requestTimeoutMs < 1 ||
28
+ requestTimeoutMs > MAX_REQUEST_TIMEOUT_MS) {
29
+ throw new TypeError('Graphiti provider timeout must be a positive safe integer');
30
+ }
31
+ this.baseUrl = baseUrl.replace(/\/$/, '');
32
+ this.accessToken = accessToken;
33
+ this.fetch = fetchImpl;
34
+ this.requestTimeoutMs = requestTimeoutMs;
35
+ }
36
+
37
+ health() { return this.#request('/health', { authenticated: false }); }
38
+ listSpaces() { return this.#request('/v1/spaces'); }
39
+ createSpace(input) { return this.#request('/v1/spaces', { method: 'POST', body: input }); }
40
+ listProjectReleases(projectId) {
41
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}/releases`);
42
+ }
43
+ deleteProject(projectId) {
44
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}`, { method: 'DELETE' });
45
+ }
46
+ upsertPersonalProject(input) {
47
+ return this.#request('/v1/personal-projects', { method: 'PUT', body: input });
48
+ }
49
+ listPersonalProjects(personalSpaceId) {
50
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId });
51
+ return this.#request(`/v1/personal-projects?${query}`);
52
+ }
53
+ getPersonalProject(personalSpaceId, projectId) {
54
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId });
55
+ return this.#request(
56
+ `/v1/personal-projects/${encodeURIComponent(projectId)}?${query}`
57
+ );
58
+ }
59
+ createPublicationDraft(input) {
60
+ return this.#request('/v1/publication-drafts', { method: 'POST', body: input });
61
+ }
62
+ listPublicationDrafts(personalSpaceId, status = 'pending') {
63
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId, status });
64
+ return this.#request(`/v1/publication-drafts?${query}`);
65
+ }
66
+ getPublicationDraft(personalSpaceId, draftId) {
67
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId });
68
+ return this.#request(
69
+ `/v1/publication-drafts/${encodeURIComponent(draftId)}?${query}`
70
+ );
71
+ }
72
+ decidePublicationDraft(personalSpaceId, draftId, input) {
73
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId });
74
+ return this.#request(
75
+ `/v1/publication-drafts/${encodeURIComponent(draftId)}/decision?${query}`,
76
+ { method: 'POST', body: input }
77
+ );
78
+ }
79
+ createPrincipal(input) {
80
+ return this.#request('/v1/principals', { method: 'POST', body: input });
81
+ }
82
+ addMember(projectId, input) {
83
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}/members`, {
84
+ method: 'POST', body: input
85
+ });
86
+ }
87
+ createProjectRelation(projectId, input) {
88
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}/relations`, {
89
+ method: 'POST', body: input
90
+ });
91
+ }
92
+ listProjectRelations(projectId) {
93
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}/relations`);
94
+ }
95
+ decideProjectRelation(projectId, relationId, input) {
96
+ return this.#request(
97
+ `/v1/projects/${encodeURIComponent(projectId)}/relations/` +
98
+ `${encodeURIComponent(relationId)}/decision`,
99
+ { method: 'POST', body: input }
100
+ );
101
+ }
102
+ subscribe(input) {
103
+ return this.#request('/v1/subscriptions', { method: 'POST', body: input });
104
+ }
105
+ listSubscriptions(personalSpaceId) {
106
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId });
107
+ return this.#request(`/v1/subscriptions?${query}`);
108
+ }
109
+ unsubscribe(personalSpaceId, projectId, providerUrl) {
110
+ const query = new URLSearchParams({
111
+ personal_space_id: personalSpaceId,
112
+ provider_url: providerUrl
113
+ });
114
+ return this.#request(`/v1/subscriptions/${encodeURIComponent(projectId)}?${query}`, {
115
+ method: 'DELETE'
116
+ });
117
+ }
118
+ commit(input) {
119
+ return this.#request('/v1/knowledge/commits', { method: 'POST', body: input });
120
+ }
121
+ reviseKnowledgeItem(itemId, input) {
122
+ return this.#request(`/v1/knowledge/items/${encodeURIComponent(itemId)}`, {
123
+ method: 'PATCH', body: input
124
+ });
125
+ }
126
+ recordAgentViews(input) {
127
+ return this.#request('/v1/knowledge/agent-views', {
128
+ method: 'POST', body: input
129
+ });
130
+ }
131
+ recordKnowledgeUsage(input) {
132
+ return this.#request('/v1/knowledge/usage', {
133
+ method: 'POST', body: input
134
+ });
135
+ }
136
+ reviewHumanChange(itemId, input) {
137
+ return this.#request(
138
+ `/v1/knowledge/items/${encodeURIComponent(itemId)}/agent-review`,
139
+ { method: 'POST', body: input }
140
+ );
141
+ }
142
+ searchHumanChanges(input) {
143
+ return this.#request('/v1/knowledge/human-changes/search', {
144
+ method: 'POST', body: input
145
+ });
146
+ }
147
+ confirmKnowledgeBatch(input) {
148
+ return this.#request('/v1/knowledge/batch-confirmations', {
149
+ method: 'POST', body: input
150
+ });
151
+ }
152
+ reassignKnowledgeItem(itemId, input) {
153
+ return this.#request(
154
+ `/v1/knowledge/items/${encodeURIComponent(itemId)}/assignment`,
155
+ { method: 'POST', body: input }
156
+ );
157
+ }
158
+ setPreferenceScope(itemId, input) {
159
+ return this.#request(
160
+ `/v1/knowledge/items/${encodeURIComponent(itemId)}/preference-scope`,
161
+ { method: 'POST', body: input }
162
+ );
163
+ }
164
+ deferPreferenceConflict(input) {
165
+ return this.#request('/v1/preference-conflicts/defer', {
166
+ method: 'POST', body: input
167
+ });
168
+ }
169
+ listPreferenceConflicts(personalSpaceId, status = null, limit = 500) {
170
+ const query = new URLSearchParams({
171
+ personal_space_id: personalSpaceId,
172
+ limit: String(limit)
173
+ });
174
+ if (status) query.set('status', status);
175
+ return this.#request(`/v1/preference-conflicts?${query}`);
176
+ }
177
+ resolvePreferenceConflict(conflictId, input) {
178
+ return this.#request(
179
+ `/v1/preference-conflicts/${encodeURIComponent(conflictId)}/resolve`,
180
+ { method: 'POST', body: input }
181
+ );
182
+ }
183
+ completePreferenceConflict(conflictId, input) {
184
+ return this.#request(
185
+ `/v1/preference-conflicts/${encodeURIComponent(conflictId)}/complete`,
186
+ { method: 'POST', body: input }
187
+ );
188
+ }
189
+ previewKnowledgeProjectAction(itemId, input) {
190
+ return this.#request(
191
+ `/v1/knowledge/items/${encodeURIComponent(itemId)}/project-action/preview`,
192
+ { method: 'POST', body: input }
193
+ );
194
+ }
195
+ applyKnowledgeProjectAction(itemId, input) {
196
+ return this.#request(
197
+ `/v1/knowledge/items/${encodeURIComponent(itemId)}/project-action`,
198
+ { method: 'POST', body: input }
199
+ );
200
+ }
201
+ createProposal(projectId, input) {
202
+ return this.#request(`/v1/projects/${encodeURIComponent(projectId)}/proposals`, {
203
+ method: 'POST', body: input
204
+ });
205
+ }
206
+ listProposals(projectId, status = 'pending') {
207
+ const query = new URLSearchParams({ status });
208
+ return this.#request(
209
+ `/v1/projects/${encodeURIComponent(projectId)}/proposals?${query}`
210
+ );
211
+ }
212
+ decideProposal(projectId, proposalId, input) {
213
+ return this.#request(
214
+ `/v1/projects/${encodeURIComponent(projectId)}/proposals/` +
215
+ `${encodeURIComponent(proposalId)}/decision`,
216
+ { method: 'POST', body: input }
217
+ );
218
+ }
219
+ search(input) { return this.#request('/v1/search', { method: 'POST', body: input }); }
220
+ collaborationPreferences(personalSpaceId, personalProjectId = null, limit = 100) {
221
+ const query = new URLSearchParams({
222
+ personal_space_id: personalSpaceId,
223
+ limit: String(limit)
224
+ });
225
+ if (personalProjectId) query.set('personal_project_id', personalProjectId);
226
+ return this.#request(`/v1/collaboration-preferences?${query}`);
227
+ }
228
+ graph(spaceId, limit = 500, personalProjectId = null) {
229
+ const query = new URLSearchParams({ limit: String(limit) });
230
+ if (personalProjectId) query.set('personal_project_id', personalProjectId);
231
+ return this.#request(`/v1/spaces/${encodeURIComponent(spaceId)}/graph?${query}`);
232
+ }
233
+
234
+ async #request(path, {
235
+ method = 'GET',
236
+ body,
237
+ authenticated = true
238
+ } = {}) {
239
+ const headers = { accept: 'application/json' };
240
+ if (authenticated) headers.authorization = `Bearer ${this.accessToken}`;
241
+ if (body !== undefined) headers['content-type'] = 'application/json';
242
+ const controller = new AbortController();
243
+ let timedOut = false;
244
+ const timeout = setTimeout(() => {
245
+ timedOut = true;
246
+ controller.abort(new Error('Graphiti provider request timed out'));
247
+ }, this.requestTimeoutMs);
248
+ const { signal } = controller;
249
+ let response;
250
+ let payload;
251
+ try {
252
+ response = await this.fetch(`${this.baseUrl}${path}`, {
253
+ method,
254
+ headers,
255
+ body: body === undefined ? undefined : JSON.stringify(body),
256
+ signal
257
+ });
258
+ payload = await parseResponse(response);
259
+ } catch (error) {
260
+ throw new ProviderRequestError(
261
+ timedOut
262
+ ? 'Graphiti provider request timed out'
263
+ : 'Graphiti provider is unavailable',
264
+ {
265
+ status: timedOut ? 504 : 0,
266
+ code: timedOut ? 'provider_timeout' : 'provider_unavailable',
267
+ details: timedOut ? null : error instanceof Error ? error.message : null
268
+ }
269
+ );
270
+ } finally {
271
+ clearTimeout(timeout);
272
+ }
273
+ if (!response.ok) {
274
+ throw new ProviderRequestError(
275
+ typeof payload?.detail === 'string' ? payload.detail : 'Graphiti provider request failed',
276
+ { status: response.status, details: payload }
277
+ );
278
+ }
279
+ return payload;
280
+ }
281
+ }
282
+
283
+ async function parseResponse(response) {
284
+ const type = response.headers.get('content-type') ?? '';
285
+ if (type.includes('application/json')) return response.json();
286
+ const text = await response.text();
287
+ return text ? { detail: text } : null;
288
+ }
@@ -0,0 +1,85 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+
4
+ import { resolveSetupPaths } from '../setup/paths.js';
5
+
6
+ export class GraphRuntimeConfigurationError extends TypeError {}
7
+
8
+ export function resolveGraphRuntimeOptions(args = [], env = process.env, options = {}) {
9
+ const configuredPath = runtimeValue(args, '--runtime-config') ??
10
+ nonEmpty(env.FULI_GRAPH_RUNTIME_CONFIG);
11
+ const dataDir = resolveSetupPaths({ env, ...options.setupPaths }).dataDir;
12
+ return {
13
+ runtimeConfigPath: resolve(configuredPath ?? join(dataDir, 'graph-runtime.json'))
14
+ };
15
+ }
16
+
17
+ export function readGraphRuntimeConfig(path) {
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
21
+ } catch (error) {
22
+ throw new GraphRuntimeConfigurationError(
23
+ `Graphiti runtime is not configured. Run: node src/graphiti/setup.js`
24
+ );
25
+ }
26
+ validateConfig(parsed);
27
+ return parsed;
28
+ }
29
+
30
+ function validateConfig(config) {
31
+ if (config?.version !== 1) fail('unsupported graph runtime config version');
32
+ provider(config.personal, 'personal');
33
+ if (!config.personal.spaceId) fail('personal.spaceId is required');
34
+ if (!Array.isArray(config.workspaces)) fail('workspaces must be an array');
35
+ for (const [index, workspace] of config.workspaces.entries()) {
36
+ provider(workspace, `workspaces[${index}]`);
37
+ }
38
+ }
39
+
40
+ function provider(value, label) {
41
+ if (!value || typeof value !== 'object') fail(`${label} is required`);
42
+ for (const property of ['providerUrl', 'accessToken', 'principalId']) {
43
+ if (!nonEmpty(value[property])) fail(`${label}.${property} is required`);
44
+ }
45
+ value.providerUrl = canonicalProviderUrl(value.providerUrl);
46
+ }
47
+
48
+ export function canonicalProviderUrl(value) {
49
+ const normalized = String(value).replace(/\/$/, '');
50
+ let url;
51
+ try {
52
+ url = new URL(normalized);
53
+ } catch {
54
+ fail('provider URL is invalid');
55
+ }
56
+ const loopback = ['127.0.0.1', 'localhost', '::1'].includes(url.hostname);
57
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) {
58
+ fail('provider URL must use HTTPS or loopback HTTP');
59
+ }
60
+ if (url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
61
+ fail('provider URL must contain only scheme, host, and optional port');
62
+ }
63
+ return normalized;
64
+ }
65
+
66
+ function runtimeValue(args, flag) {
67
+ const index = args.indexOf(flag);
68
+ if (index === -1) return null;
69
+ const value = args[index + 1];
70
+ if (!nonEmpty(value) || value.startsWith('--')) {
71
+ throw new GraphRuntimeConfigurationError(`Missing value for ${flag}`);
72
+ }
73
+ if (args.indexOf(flag, index + 1) !== -1) {
74
+ throw new GraphRuntimeConfigurationError(`Duplicate ${flag}`);
75
+ }
76
+ return value;
77
+ }
78
+
79
+ function nonEmpty(value) {
80
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
81
+ }
82
+
83
+ function fail(message) {
84
+ throw new GraphRuntimeConfigurationError(message);
85
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ import { DEFAULT_FULI_PORT } from '../defaults.js';
5
+ import { ensureGraphRuntime } from '../setup/graph-runtime.js';
6
+ import { resolveSetupPaths } from '../setup/paths.js';
7
+
8
+ export async function main(args = process.argv.slice(2), env = process.env) {
9
+ const paths = resolveSetupPaths({ dataDir: option(args, '--data-dir'), env });
10
+ const result = await ensureGraphRuntime({
11
+ paths,
12
+ personalSpaceName: option(args, '--personal-space') ?? '我',
13
+ port: numberOption(args, '--port', DEFAULT_FULI_PORT),
14
+ personalOnly: args.includes('--personal-only'),
15
+ noStart: args.includes('--no-start')
16
+ });
17
+ process.stdout.write(`${JSON.stringify(result)}\n`);
18
+ }
19
+
20
+ function option(args, flag) {
21
+ const index = args.indexOf(flag);
22
+ if (index === -1) return null;
23
+ const value = args[index + 1];
24
+ if (!value || value.startsWith('--')) throw new TypeError(`Missing value for ${flag}`);
25
+ return value;
26
+ }
27
+
28
+ function numberOption(args, flag, fallback) {
29
+ const value = option(args, flag);
30
+ if (value === null) return fallback;
31
+ const parsed = Number(value);
32
+ if (!Number.isInteger(parsed) || parsed <= 0) {
33
+ throw new TypeError(`${flag} must be a positive integer`);
34
+ }
35
+ return parsed;
36
+ }
37
+
38
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
39
+ main().catch((error) => {
40
+ process.stderr.write(`${error.message}\n`);
41
+ process.exitCode = 1;
42
+ });
43
+ }