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.
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- 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
|
+
}
|