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,290 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from fastapi import HTTPException
|
|
4
|
+
|
|
5
|
+
from .models import (
|
|
6
|
+
CollaborationContextResult,
|
|
7
|
+
CollaborationPreferenceConflict,
|
|
8
|
+
CollaborationPreferenceItem,
|
|
9
|
+
ConfirmationBasis,
|
|
10
|
+
_validate_confirmation_state,
|
|
11
|
+
)
|
|
12
|
+
from .personal_project_access import authorize_personal_project
|
|
13
|
+
from .provider_values import json_object, native_datetime as _native_datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def read_collaboration_context(
|
|
17
|
+
store,
|
|
18
|
+
actor: dict,
|
|
19
|
+
space_id: str,
|
|
20
|
+
personal_project_id: str | None = None,
|
|
21
|
+
limit: int = 100,
|
|
22
|
+
) -> CollaborationContextResult:
|
|
23
|
+
space = await store.authorize(actor, space_id, 'reader')
|
|
24
|
+
if space['kind'] != 'personal':
|
|
25
|
+
raise HTTPException(
|
|
26
|
+
status_code=422,
|
|
27
|
+
detail='collaboration preferences require a personal space',
|
|
28
|
+
)
|
|
29
|
+
if personal_project_id:
|
|
30
|
+
await authorize_personal_project(
|
|
31
|
+
store,
|
|
32
|
+
actor,
|
|
33
|
+
space,
|
|
34
|
+
personal_project_id,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
candidate_limit = min(max(limit * 3, 100), store.settings.graph_limit)
|
|
38
|
+
node_records, _, _ = await store.runtime.driver.execute_query(
|
|
39
|
+
_node_query(),
|
|
40
|
+
group_id=space['group_id'],
|
|
41
|
+
project_id=personal_project_id,
|
|
42
|
+
limit=candidate_limit + 1,
|
|
43
|
+
routing_='r',
|
|
44
|
+
)
|
|
45
|
+
edge_records, _, _ = await store.runtime.driver.execute_query(
|
|
46
|
+
_edge_query(),
|
|
47
|
+
group_id=space['group_id'],
|
|
48
|
+
project_id=personal_project_id,
|
|
49
|
+
limit=candidate_limit + 1,
|
|
50
|
+
routing_='r',
|
|
51
|
+
)
|
|
52
|
+
truncated = (
|
|
53
|
+
len(node_records) > candidate_limit
|
|
54
|
+
or len(edge_records) > candidate_limit
|
|
55
|
+
)
|
|
56
|
+
nodes = [
|
|
57
|
+
item
|
|
58
|
+
for record in node_records[:candidate_limit]
|
|
59
|
+
if (item := _node_item(record)) is not None
|
|
60
|
+
]
|
|
61
|
+
edge_pairs = [
|
|
62
|
+
(record, item)
|
|
63
|
+
for record in edge_records[:candidate_limit]
|
|
64
|
+
if (item := _edge_item(record)) is not None
|
|
65
|
+
]
|
|
66
|
+
edges = [item for _, item in edge_pairs]
|
|
67
|
+
edge_endpoint_ids = {
|
|
68
|
+
endpoint_id
|
|
69
|
+
for record, _ in edge_pairs
|
|
70
|
+
for endpoint_id in (record.get('source_id'), record.get('target_id'))
|
|
71
|
+
}
|
|
72
|
+
items = [
|
|
73
|
+
*edges,
|
|
74
|
+
*(item for item in nodes if item.id not in edge_endpoint_ids),
|
|
75
|
+
]
|
|
76
|
+
items.sort(key=_sort_key)
|
|
77
|
+
if len(items) > limit:
|
|
78
|
+
truncated = True
|
|
79
|
+
items = items[:limit]
|
|
80
|
+
|
|
81
|
+
global_preferences = [
|
|
82
|
+
item for item in items if item.preference_scope == 'global'
|
|
83
|
+
]
|
|
84
|
+
project_preferences = [
|
|
85
|
+
item for item in items if item.preference_scope == 'project'
|
|
86
|
+
]
|
|
87
|
+
global_effective, global_conflicts = _conflict_free(global_preferences)
|
|
88
|
+
project_effective, project_conflicts = _conflict_free(project_preferences)
|
|
89
|
+
project_keys = {item.preference_key for item in project_preferences}
|
|
90
|
+
overridden_global_ids = [
|
|
91
|
+
item.id
|
|
92
|
+
for item in global_effective
|
|
93
|
+
if item.preference_key in project_keys
|
|
94
|
+
]
|
|
95
|
+
effective_preferences = [
|
|
96
|
+
item
|
|
97
|
+
for item in global_effective
|
|
98
|
+
if item.preference_key not in project_keys
|
|
99
|
+
] + project_effective
|
|
100
|
+
|
|
101
|
+
return CollaborationContextResult(
|
|
102
|
+
personal_space_id=space_id,
|
|
103
|
+
personal_project_id=personal_project_id,
|
|
104
|
+
global_preferences=global_preferences,
|
|
105
|
+
project_preferences=project_preferences,
|
|
106
|
+
effective_preferences=effective_preferences,
|
|
107
|
+
conflicts=[*global_conflicts, *project_conflicts],
|
|
108
|
+
overridden_global_ids=overridden_global_ids,
|
|
109
|
+
truncated=truncated,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _node_query() -> str:
|
|
114
|
+
return '''
|
|
115
|
+
MATCH (node:Entity {group_id: $group_id})
|
|
116
|
+
WHERE node.fuli_profile_aspect IS NOT NULL
|
|
117
|
+
AND node.fuli_invalid_at IS NULL
|
|
118
|
+
AND node.fuli_confirmation_status IN ['confirmed', 'agent_confirmed']
|
|
119
|
+
AND (
|
|
120
|
+
coalesce(node.fuli_preference_scope, 'global') = 'global'
|
|
121
|
+
OR (
|
|
122
|
+
node.fuli_preference_scope = 'project'
|
|
123
|
+
AND node.fuli_preference_project_id = $project_id
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
RETURN node.uuid AS id,
|
|
127
|
+
coalesce(node.fuli_key, node.uuid) AS key,
|
|
128
|
+
node.name AS title,
|
|
129
|
+
coalesce(node.summary, node.name) AS instruction,
|
|
130
|
+
node.fuli_profile_aspect AS profile_aspect,
|
|
131
|
+
coalesce(node.fuli_preference_scope, 'global') AS preference_scope,
|
|
132
|
+
node.fuli_preference_project_id AS preference_project_id,
|
|
133
|
+
node.fuli_attributes_json AS attributes_json,
|
|
134
|
+
node.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
135
|
+
node.fuli_confirmation_status AS confirmation_status,
|
|
136
|
+
node.created_at AS created_at
|
|
137
|
+
ORDER BY node.created_at DESC
|
|
138
|
+
LIMIT $limit
|
|
139
|
+
'''
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _edge_query() -> str:
|
|
143
|
+
return '''
|
|
144
|
+
MATCH (source:Entity)-[edge:RELATES_TO {group_id: $group_id}]->
|
|
145
|
+
(target:Entity)
|
|
146
|
+
WHERE edge.fuli_profile_aspect IS NOT NULL
|
|
147
|
+
AND edge.invalid_at IS NULL
|
|
148
|
+
AND edge.fuli_confirmation_status IN ['confirmed', 'agent_confirmed']
|
|
149
|
+
AND (
|
|
150
|
+
coalesce(edge.fuli_preference_scope, 'global') = 'global'
|
|
151
|
+
OR (
|
|
152
|
+
edge.fuli_preference_scope = 'project'
|
|
153
|
+
AND edge.fuli_preference_project_id = $project_id
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
RETURN edge.uuid AS id,
|
|
157
|
+
coalesce(edge.fuli_key, edge.uuid) AS key,
|
|
158
|
+
source.uuid AS source_id,
|
|
159
|
+
target.uuid AS target_id,
|
|
160
|
+
source.name AS source_name,
|
|
161
|
+
target.name AS target_name,
|
|
162
|
+
edge.fact AS instruction,
|
|
163
|
+
edge.fuli_profile_aspect AS profile_aspect,
|
|
164
|
+
coalesce(edge.fuli_preference_scope, 'global') AS preference_scope,
|
|
165
|
+
edge.fuli_preference_project_id AS preference_project_id,
|
|
166
|
+
edge.fuli_attributes_json AS attributes_json,
|
|
167
|
+
edge.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
168
|
+
edge.fuli_confirmation_status AS confirmation_status,
|
|
169
|
+
edge.created_at AS created_at
|
|
170
|
+
ORDER BY edge.created_at DESC
|
|
171
|
+
LIMIT $limit
|
|
172
|
+
'''
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _node_item(record) -> CollaborationPreferenceItem | None:
|
|
176
|
+
return _preference_item(
|
|
177
|
+
record,
|
|
178
|
+
item_kind='entity',
|
|
179
|
+
title=record.get('title') or record.get('key') or record.get('id'),
|
|
180
|
+
instruction=record.get('instruction') or record.get('title') or '',
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _edge_item(record) -> CollaborationPreferenceItem | None:
|
|
185
|
+
source = record.get('source_name') or '偏好'
|
|
186
|
+
target = record.get('target_name') or '适用场景'
|
|
187
|
+
return _preference_item(
|
|
188
|
+
record,
|
|
189
|
+
item_kind='relationship',
|
|
190
|
+
title=f'{source} → {target}',
|
|
191
|
+
instruction=record.get('instruction') or '',
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _preference_item(
|
|
196
|
+
record,
|
|
197
|
+
*,
|
|
198
|
+
item_kind: str,
|
|
199
|
+
title: str,
|
|
200
|
+
instruction: str,
|
|
201
|
+
) -> CollaborationPreferenceItem | None:
|
|
202
|
+
basis_data = json_object(record.get('confirmation_basis_json'))
|
|
203
|
+
if not basis_data:
|
|
204
|
+
return None
|
|
205
|
+
try:
|
|
206
|
+
basis = ConfirmationBasis.model_validate(basis_data)
|
|
207
|
+
confirmation_status = record.get('confirmation_status') or 'confirmed'
|
|
208
|
+
_validate_confirmation_state(confirmation_status, basis)
|
|
209
|
+
except ValueError:
|
|
210
|
+
return None
|
|
211
|
+
if not basis.confirmed_by or not basis.confirmed_at:
|
|
212
|
+
return None
|
|
213
|
+
attributes = json_object(record.get('attributes_json'))
|
|
214
|
+
key = str(record.get('key') or record.get('id'))
|
|
215
|
+
preference_key = str(
|
|
216
|
+
attributes.get('preferenceKey')
|
|
217
|
+
or attributes.get('preference_key')
|
|
218
|
+
or key
|
|
219
|
+
)
|
|
220
|
+
return CollaborationPreferenceItem(
|
|
221
|
+
id=str(record['id']),
|
|
222
|
+
item_kind=item_kind,
|
|
223
|
+
key=key,
|
|
224
|
+
preference_key=preference_key,
|
|
225
|
+
title=str(title),
|
|
226
|
+
instruction=str(instruction),
|
|
227
|
+
profile_aspect=record['profile_aspect'],
|
|
228
|
+
preference_scope=record['preference_scope'],
|
|
229
|
+
preference_project_id=record.get('preference_project_id'),
|
|
230
|
+
attributes=attributes,
|
|
231
|
+
confirmation_status=confirmation_status,
|
|
232
|
+
confirmed_at=basis.confirmed_at,
|
|
233
|
+
created_at=_native_datetime(record.get('created_at')),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _conflict_free(
|
|
238
|
+
items: list[CollaborationPreferenceItem],
|
|
239
|
+
) -> tuple[
|
|
240
|
+
list[CollaborationPreferenceItem],
|
|
241
|
+
list[CollaborationPreferenceConflict],
|
|
242
|
+
]:
|
|
243
|
+
grouped: dict[str, list[CollaborationPreferenceItem]] = defaultdict(list)
|
|
244
|
+
for item in items:
|
|
245
|
+
grouped[item.preference_key].append(item)
|
|
246
|
+
|
|
247
|
+
effective = []
|
|
248
|
+
conflicts = []
|
|
249
|
+
for preference_key, candidates in grouped.items():
|
|
250
|
+
strongest_status = (
|
|
251
|
+
'confirmed'
|
|
252
|
+
if any(item.confirmation_status == 'confirmed' for item in candidates)
|
|
253
|
+
else 'agent_confirmed'
|
|
254
|
+
)
|
|
255
|
+
candidates = [
|
|
256
|
+
item for item in candidates
|
|
257
|
+
if item.confirmation_status == strongest_status
|
|
258
|
+
]
|
|
259
|
+
instructions = {
|
|
260
|
+
_normalize(item.instruction)
|
|
261
|
+
for item in candidates
|
|
262
|
+
}
|
|
263
|
+
if len(instructions) <= 1:
|
|
264
|
+
effective.append(candidates[0])
|
|
265
|
+
continue
|
|
266
|
+
first = candidates[0]
|
|
267
|
+
conflicts.append(CollaborationPreferenceConflict(
|
|
268
|
+
preference_key=preference_key,
|
|
269
|
+
preference_scope=first.preference_scope,
|
|
270
|
+
preference_project_id=first.preference_project_id,
|
|
271
|
+
item_ids=[item.id for item in candidates],
|
|
272
|
+
))
|
|
273
|
+
effective.sort(key=_sort_key)
|
|
274
|
+
return effective, conflicts
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _sort_key(item: CollaborationPreferenceItem) -> tuple:
|
|
278
|
+
created = -item.created_at.timestamp() if item.created_at else 0
|
|
279
|
+
return (
|
|
280
|
+
0 if item.preference_scope == 'global' else 1,
|
|
281
|
+
0 if item.confirmation_status == 'confirmed' else 1,
|
|
282
|
+
item.profile_aspect,
|
|
283
|
+
item.preference_key,
|
|
284
|
+
created,
|
|
285
|
+
item.id,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _normalize(value: str) -> str:
|
|
290
|
+
return ' '.join(value.casefold().split())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, field_validator
|
|
5
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Settings(BaseSettings):
|
|
9
|
+
model_config = SettingsConfigDict(env_prefix='FULI_', extra='ignore')
|
|
10
|
+
|
|
11
|
+
provider_mode: Literal['personal', 'workspace'] = 'personal'
|
|
12
|
+
provider_id: str = 'local-personal'
|
|
13
|
+
provider_name: str = 'Fuli Personal Provider'
|
|
14
|
+
bootstrap_token: str = Field(min_length=24)
|
|
15
|
+
neo4j_uri: str = 'bolt://neo4j:7687'
|
|
16
|
+
neo4j_user: str = 'neo4j'
|
|
17
|
+
neo4j_password: str = Field(min_length=8)
|
|
18
|
+
neo4j_database: str = 'neo4j'
|
|
19
|
+
embedding_dim: int = 384
|
|
20
|
+
graph_limit: int = 500
|
|
21
|
+
search_limit: int = 20
|
|
22
|
+
|
|
23
|
+
@field_validator('provider_id')
|
|
24
|
+
@classmethod
|
|
25
|
+
def validate_provider_id(cls, value: str) -> str:
|
|
26
|
+
normalized = value.strip()
|
|
27
|
+
if not normalized or len(normalized) > 128:
|
|
28
|
+
raise ValueError('provider_id must contain 1 to 128 characters')
|
|
29
|
+
return normalized
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@lru_cache(maxsize=1)
|
|
33
|
+
def get_settings() -> Settings:
|
|
34
|
+
return Settings() # type: ignore[call-arg]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from .models import (
|
|
7
|
+
ConfirmationBasis,
|
|
8
|
+
ConfirmationStatus,
|
|
9
|
+
EpistemicQuadrant,
|
|
10
|
+
EpistemicStatus,
|
|
11
|
+
GraphEvidence,
|
|
12
|
+
HumanChangeStatus,
|
|
13
|
+
KnowledgeAssignmentRecord,
|
|
14
|
+
KnowledgeAuditRecord,
|
|
15
|
+
KnowledgeInheritanceMode,
|
|
16
|
+
KnowledgeItemKind,
|
|
17
|
+
KnowledgeRevisionRecord,
|
|
18
|
+
PersonalProfileAspect,
|
|
19
|
+
PreferenceScope,
|
|
20
|
+
StrictModel,
|
|
21
|
+
)
|
|
22
|
+
from .project_action_models import (
|
|
23
|
+
KnowledgeConflictRecord,
|
|
24
|
+
KnowledgeProjectReferenceRecord,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GraphNode(StrictModel):
|
|
29
|
+
id: str
|
|
30
|
+
name: str
|
|
31
|
+
type: str
|
|
32
|
+
group_id: str
|
|
33
|
+
summary: str
|
|
34
|
+
origin_quadrant: EpistemicQuadrant = 'known_known'
|
|
35
|
+
current_quadrant: EpistemicQuadrant = 'known_known'
|
|
36
|
+
epistemic_status: EpistemicStatus = 'confirmed'
|
|
37
|
+
epistemic_state_explicit: bool = True
|
|
38
|
+
confirmation_status: ConfirmationStatus = 'pending'
|
|
39
|
+
confirmation_state_explicit: bool = False
|
|
40
|
+
confirmation_basis: ConfirmationBasis | None = None
|
|
41
|
+
reasoning_summary: str | None = None
|
|
42
|
+
profile_aspect: PersonalProfileAspect | None = None
|
|
43
|
+
preference_scope: PreferenceScope | None = None
|
|
44
|
+
preference_project_id: str | None = None
|
|
45
|
+
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
46
|
+
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
47
|
+
human_edited: bool = False
|
|
48
|
+
human_change_status: HumanChangeStatus = 'none'
|
|
49
|
+
human_change_version: int = Field(default=0, ge=0)
|
|
50
|
+
last_human_changed_at: datetime | None = None
|
|
51
|
+
last_agent_viewed_at: datetime | None = None
|
|
52
|
+
last_agent_reviewed_at: datetime | None = None
|
|
53
|
+
utility_score: float = Field(default=0, ge=0, le=1)
|
|
54
|
+
confidence_score: float = Field(default=0.5, ge=0, le=1)
|
|
55
|
+
qualified_use_count: int = Field(default=0, ge=0)
|
|
56
|
+
distinct_task_count: int = Field(default=0, ge=0)
|
|
57
|
+
last_used_at: datetime | None = None
|
|
58
|
+
usage_generation: int = Field(default=1, ge=1)
|
|
59
|
+
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
60
|
+
evidence: list[GraphEvidence] = Field(default_factory=list)
|
|
61
|
+
created_at: datetime | None = None
|
|
62
|
+
invalid_at: datetime | None = None
|
|
63
|
+
replaced_by_item_id: str | None = None
|
|
64
|
+
replaced_by_item_kind: KnowledgeItemKind | None = None
|
|
65
|
+
revisions: list[KnowledgeRevisionRecord] = Field(default_factory=list)
|
|
66
|
+
assignments: list[KnowledgeAssignmentRecord] = Field(default_factory=list)
|
|
67
|
+
project_references: list[KnowledgeProjectReferenceRecord] = Field(default_factory=list)
|
|
68
|
+
conflicts: list[KnowledgeConflictRecord] = Field(default_factory=list)
|
|
69
|
+
audit_events: list[KnowledgeAuditRecord] = Field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class GraphEdge(StrictModel):
|
|
73
|
+
id: str
|
|
74
|
+
source: str
|
|
75
|
+
target: str
|
|
76
|
+
type: str
|
|
77
|
+
fact: str
|
|
78
|
+
origin_quadrant: EpistemicQuadrant = 'known_known'
|
|
79
|
+
current_quadrant: EpistemicQuadrant = 'known_known'
|
|
80
|
+
epistemic_status: EpistemicStatus = 'confirmed'
|
|
81
|
+
epistemic_state_explicit: bool = True
|
|
82
|
+
confirmation_status: ConfirmationStatus = 'pending'
|
|
83
|
+
confirmation_state_explicit: bool = False
|
|
84
|
+
confirmation_basis: ConfirmationBasis | None = None
|
|
85
|
+
reasoning_summary: str | None = None
|
|
86
|
+
profile_aspect: PersonalProfileAspect | None = None
|
|
87
|
+
preference_scope: PreferenceScope | None = None
|
|
88
|
+
preference_project_id: str | None = None
|
|
89
|
+
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
90
|
+
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
91
|
+
human_edited: bool = False
|
|
92
|
+
human_change_status: HumanChangeStatus = 'none'
|
|
93
|
+
human_change_version: int = Field(default=0, ge=0)
|
|
94
|
+
last_human_changed_at: datetime | None = None
|
|
95
|
+
last_agent_viewed_at: datetime | None = None
|
|
96
|
+
last_agent_reviewed_at: datetime | None = None
|
|
97
|
+
utility_score: float = Field(default=0, ge=0, le=1)
|
|
98
|
+
confidence_score: float = Field(default=0.5, ge=0, le=1)
|
|
99
|
+
qualified_use_count: int = Field(default=0, ge=0)
|
|
100
|
+
distinct_task_count: int = Field(default=0, ge=0)
|
|
101
|
+
last_used_at: datetime | None = None
|
|
102
|
+
usage_generation: int = Field(default=1, ge=1)
|
|
103
|
+
valid_at: datetime | None
|
|
104
|
+
invalid_at: datetime | None
|
|
105
|
+
replaced_by_item_id: str | None = None
|
|
106
|
+
replaced_by_item_kind: KnowledgeItemKind | None = None
|
|
107
|
+
created_at: datetime | None = None
|
|
108
|
+
confidence: float | None = None
|
|
109
|
+
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
110
|
+
episodes: list[str] = Field(default_factory=list)
|
|
111
|
+
evidence: list[GraphEvidence] = Field(default_factory=list)
|
|
112
|
+
revisions: list[KnowledgeRevisionRecord] = Field(default_factory=list)
|
|
113
|
+
assignments: list[KnowledgeAssignmentRecord] = Field(default_factory=list)
|
|
114
|
+
project_references: list[KnowledgeProjectReferenceRecord] = Field(default_factory=list)
|
|
115
|
+
conflicts: list[KnowledgeConflictRecord] = Field(default_factory=list)
|
|
116
|
+
audit_events: list[KnowledgeAuditRecord] = Field(default_factory=list)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class GraphResult(StrictModel):
|
|
120
|
+
space_id: str
|
|
121
|
+
nodes: list[GraphNode]
|
|
122
|
+
edges: list[GraphEdge]
|
|
123
|
+
truncated: bool = False
|