fuli-context 0.7.4-beta.3 → 0.7.5
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/README.md +77 -6
- package/README.zh-CN.md +69 -6
- package/compose.graphiti.yml +6 -6
- package/dist/web/assets/AboutPage-ByCUor-j.js +1 -0
- package/dist/web/assets/ConnectionsPage-DkdkZ40Q.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-DSe3Gwep.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CYAjAb9J.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CV4F54R_.js +1 -0
- package/dist/web/assets/KnowledgePage-vYYXipOi.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-z4u5mXOG.js +4 -0
- package/dist/web/assets/OverviewPage-BWOfeS6k.js +1 -0
- package/dist/web/assets/PersonalProfilePage-c3w4-h7a.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-B7AlLEwf.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-CgftpIPB.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-DTHcL_ox.js +11 -0
- package/dist/web/assets/PublicProjectsPage-DdH0oGB3.js +1 -0
- package/dist/web/assets/ReviewPage-BYIpcIHG.js +1 -0
- package/dist/web/assets/SearchableSelect-Dz13ClQb.js +1 -0
- package/dist/web/assets/SettingsPage-2VkbXgZi.js +1 -0
- package/dist/web/assets/SettingsPage-CwZp2ka4.css +1 -0
- package/dist/web/assets/WritingTastePage-BOPQZuva.js +1 -0
- package/dist/web/assets/{console-D_2zC4lm.js → console-BlAhujsH.js} +1 -1
- package/dist/web/assets/{conversation-launcher-settings-DQmMH2j7.js → conversation-launcher-settings-DTYxiLJt.js} +1 -1
- package/dist/web/assets/i18n-Ci3heuiL.js +9 -0
- package/dist/web/assets/{identity-DmGcY3Y6.js → identity-4_-v6cB7.js} +1 -1
- package/dist/web/assets/index-BnH5mcZx.css +1 -0
- package/dist/web/assets/index-C67Q0JY2.js +2 -0
- package/dist/web/assets/{model-CVoOdRsL.js → model-yG0YqUyZ.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-CBNg4CdH.js → useApi-CROJJdhE-twhdZArA.js} +1 -1
- package/dist/web/assets/vue-router-C-wot_Di.js +1 -0
- package/dist/web/index.html +6 -6
- package/graph-provider/fuli_graph/app.py +8 -0
- package/graph-provider/fuli_graph/collaboration_context.py +69 -6
- package/graph-provider/fuli_graph/knowledge_search.py +84 -5
- package/graph-provider/fuli_graph/model_validation.py +130 -0
- package/graph-provider/fuli_graph/models.py +24 -5
- package/graph-provider/fuli_graph/project_agent_access.py +54 -0
- package/graph-provider/fuli_graph/project_agent_executor_models.py +635 -0
- package/graph-provider/fuli_graph/project_agent_models.py +278 -0
- package/graph-provider/fuli_graph/project_agent_routes.py +597 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +542 -0
- package/graph-provider/fuli_graph/runtime.py +259 -0
- package/graph-provider/fuli_graph/search_projection.py +3 -0
- package/graph-provider/fuli_graph/store.py +6 -0
- package/graph-provider/fuli_graph/store_knowledge.py +62 -9
- package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +756 -0
- package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +601 -0
- package/graph-provider/fuli_graph/store_project_agent_executors.py +1087 -0
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +458 -0
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +699 -0
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +409 -0
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +1144 -0
- package/graph-provider/fuli_graph/store_project_agents.py +965 -0
- package/graph-provider/pyproject.toml +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/capturing-session-knowledge/SKILL.md +39 -0
- package/src/adaptive-runtime/executor-pool.js +127 -0
- package/src/adaptive-runtime/graph-services.js +166 -0
- package/src/adaptive-runtime/lease-client.js +335 -0
- package/src/adaptive-runtime/runtime-broker.js +318 -0
- package/src/adaptive-runtime/settings.js +111 -0
- package/src/adaptive-runtime/state.js +74 -0
- package/src/agent-tools/graph-definitions.js +396 -0
- package/src/agent-tools/graph-handlers.js +61 -0
- package/src/agent-tools/project-agent-definitions.js +307 -0
- package/src/backup/graph-backup.js +289 -0
- package/src/backup/identity-reconciliation.js +208 -0
- package/src/backup/runtime-adapters.js +269 -0
- package/src/cli/command-registry.js +6 -2
- package/src/cli/graph-data-command.js +112 -0
- package/src/cli/local-runtime-command.js +16 -2
- package/src/cli/setup-command.js +19 -3
- package/src/cli/update-command.js +4 -0
- package/src/cli.js +6 -0
- package/src/graphiti/collaboration-preference-projection.js +29 -1
- package/src/graphiti/collaboration-preference-workflow.js +121 -0
- package/src/graphiti/federated-application.js +53 -90
- package/src/graphiti/project-agent-control-plane.js +263 -0
- package/src/graphiti/project-agent-mapping.js +1003 -0
- package/src/graphiti/project-agent-workflows.js +557 -0
- package/src/graphiti/provider-client.js +270 -1
- package/src/http/api-router.js +63 -9
- package/src/http/graph-api-router.js +278 -0
- package/src/local-runtime/lifecycle.js +46 -10
- package/src/mcp/context-resources.js +15 -6
- package/src/mcp/create-mcp-server.js +51 -16
- package/src/mcp/instructions.js +7 -5
- package/src/mcp/runtime.js +17 -5
- package/src/mcp/session-id.js +38 -1
- package/src/mcp/tool-annotations.js +69 -0
- package/src/mcp-server.js +23 -6
- package/src/native-runtime/runtime.js +717 -0
- package/src/server.js +6 -2
- package/src/setup/agent-installation-status.js +7 -1
- package/src/setup/agents.js +3 -1
- package/src/setup/claude-code-config.js +3 -1
- package/src/setup/codex-bootstrap.js +11 -10
- package/src/setup/codex-config.js +7 -1
- package/src/setup/cursor-config.js +3 -1
- package/src/setup/graph-runtime.js +158 -13
- package/src/setup/neo4j-memory-profile.js +43 -0
- package/src/setup/options.js +38 -2
- package/src/setup/paths.js +14 -0
- package/src/setup/setup.js +37 -1
- package/src/system/resource-monitor.js +181 -16
- package/src/system/runtime-settings.js +10 -1
- package/src/system/system-service.js +27 -2
- package/dist/web/assets/AboutPage-Dpf5fRWv.js +0 -1
- package/dist/web/assets/ConnectionsPage-D21aUDCP.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-u7lpfN3j.js +0 -1
- package/dist/web/assets/KnowledgeInspector-k4xnHtXd.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-Bdj7Rn84.js +0 -1
- package/dist/web/assets/KnowledgePage-UHVAbC1W.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-Lq3d-MeC.js +0 -4
- package/dist/web/assets/OverviewPage-CycAgA1V.js +0 -1
- package/dist/web/assets/PersonalProfilePage-U9oIV5FT.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-C5RpXKcA.js +0 -1
- package/dist/web/assets/PublicProjectsPage-DiWxm35E.js +0 -1
- package/dist/web/assets/ReviewPage-jxRfGSJK.js +0 -1
- package/dist/web/assets/SearchableSelect-B3-5IPup.js +0 -1
- package/dist/web/assets/SettingsPage-BqfGDOho.js +0 -1
- package/dist/web/assets/SettingsPage-CAfz4qiV.css +0 -1
- package/dist/web/assets/WritingTastePage-BJxhMqJR.js +0 -1
- package/dist/web/assets/i18n-CwqXDI1C.js +0 -5
- package/dist/web/assets/index-IUGLt4lG.js +0 -2
- package/dist/web/assets/index-X2j29j8i.css +0 -1
- package/dist/web/assets/vue-router-CWSsUUio.js +0 -1
|
@@ -11,6 +11,7 @@ from .models import (
|
|
|
11
11
|
_validate_confirmation_state,
|
|
12
12
|
)
|
|
13
13
|
from .personal_project_access import authorize_personal_project
|
|
14
|
+
from .project_agent_access import authorize_project_agent
|
|
14
15
|
from .provider_values import json_object, native_datetime as _native_datetime
|
|
15
16
|
|
|
16
17
|
|
|
@@ -20,6 +21,7 @@ async def read_collaboration_context(
|
|
|
20
21
|
space_id: str,
|
|
21
22
|
personal_project_id: str | None = None,
|
|
22
23
|
limit: int = 100,
|
|
24
|
+
project_agent_id: str | None = None,
|
|
23
25
|
) -> CollaborationContextResult:
|
|
24
26
|
space = await store.authorize(actor, space_id, 'reader')
|
|
25
27
|
if space['kind'] != 'personal':
|
|
@@ -34,6 +36,20 @@ async def read_collaboration_context(
|
|
|
34
36
|
space,
|
|
35
37
|
personal_project_id,
|
|
36
38
|
)
|
|
39
|
+
if project_agent_id:
|
|
40
|
+
if not personal_project_id:
|
|
41
|
+
raise HTTPException(
|
|
42
|
+
status_code=422,
|
|
43
|
+
detail='project Agent preferences require a personal project',
|
|
44
|
+
)
|
|
45
|
+
await authorize_project_agent(
|
|
46
|
+
store,
|
|
47
|
+
actor,
|
|
48
|
+
space,
|
|
49
|
+
personal_project_id,
|
|
50
|
+
project_agent_id,
|
|
51
|
+
require_memory=True,
|
|
52
|
+
)
|
|
37
53
|
project_scopes = await _project_scopes(
|
|
38
54
|
store,
|
|
39
55
|
space,
|
|
@@ -50,6 +66,7 @@ async def read_collaboration_context(
|
|
|
50
66
|
_node_query(),
|
|
51
67
|
group_id=space['group_id'],
|
|
52
68
|
project_id=personal_project_id,
|
|
69
|
+
project_agent_id=project_agent_id,
|
|
53
70
|
inherited_project_ids=inherited_project_ids,
|
|
54
71
|
limit=candidate_limit + 1,
|
|
55
72
|
routing_='r',
|
|
@@ -58,6 +75,7 @@ async def read_collaboration_context(
|
|
|
58
75
|
_edge_query(),
|
|
59
76
|
group_id=space['group_id'],
|
|
60
77
|
project_id=personal_project_id,
|
|
78
|
+
project_agent_id=project_agent_id,
|
|
61
79
|
inherited_project_ids=inherited_project_ids,
|
|
62
80
|
limit=candidate_limit + 1,
|
|
63
81
|
routing_='r',
|
|
@@ -97,17 +115,26 @@ async def read_collaboration_context(
|
|
|
97
115
|
project_preferences = [
|
|
98
116
|
item for item in items if item.preference_scope == 'project'
|
|
99
117
|
]
|
|
118
|
+
agent_preferences = [
|
|
119
|
+
item for item in items if item.preference_scope == 'agent'
|
|
120
|
+
]
|
|
100
121
|
(
|
|
101
122
|
authoritative_global,
|
|
102
123
|
authoritative_project,
|
|
124
|
+
authoritative_agent,
|
|
103
125
|
cross_scope_lower_authority_ids,
|
|
104
|
-
) = _highest_authority_by_key(
|
|
126
|
+
) = _highest_authority_by_key(
|
|
127
|
+
global_preferences,
|
|
128
|
+
project_preferences,
|
|
129
|
+
agent_preferences,
|
|
130
|
+
)
|
|
105
131
|
(
|
|
106
132
|
project_effective,
|
|
107
133
|
project_conflicts,
|
|
108
134
|
overridden_inherited_ids,
|
|
109
135
|
overridden_lower_authority_ids,
|
|
110
136
|
) = _scope_conflict_free(authoritative_project)
|
|
137
|
+
agent_effective, agent_conflicts = _conflict_free(authoritative_agent)
|
|
111
138
|
overridden_lower_authority_ids = sorted({
|
|
112
139
|
*cross_scope_lower_authority_ids,
|
|
113
140
|
*overridden_lower_authority_ids,
|
|
@@ -115,27 +142,45 @@ async def read_collaboration_context(
|
|
|
115
142
|
# Any authoritative project-layer claim blocks global fallback for that
|
|
116
143
|
# semantic key. A same-layer project conflict must remain deferred instead
|
|
117
144
|
# of silently reviving a global value as effective.
|
|
145
|
+
agent_keys = {item.preference_key for item in authoritative_agent}
|
|
118
146
|
project_keys = {item.preference_key for item in authoritative_project}
|
|
147
|
+
overridden_project_ids = sorted([
|
|
148
|
+
item.id
|
|
149
|
+
for item in authoritative_project
|
|
150
|
+
if item.preference_key in agent_keys
|
|
151
|
+
])
|
|
119
152
|
overridden_global_ids = sorted([
|
|
120
153
|
item.id
|
|
121
154
|
for item in authoritative_global
|
|
122
|
-
if item.preference_key in project_keys
|
|
155
|
+
if item.preference_key in project_keys or item.preference_key in agent_keys
|
|
123
156
|
])
|
|
124
157
|
global_effective, global_conflicts = _conflict_free([
|
|
125
158
|
item for item in authoritative_global
|
|
126
159
|
if item.preference_key not in project_keys
|
|
160
|
+
and item.preference_key not in agent_keys
|
|
127
161
|
])
|
|
128
|
-
|
|
162
|
+
project_effective = [
|
|
163
|
+
item for item in project_effective
|
|
164
|
+
if item.preference_key not in agent_keys
|
|
165
|
+
]
|
|
166
|
+
effective_preferences = [
|
|
167
|
+
*global_effective,
|
|
168
|
+
*project_effective,
|
|
169
|
+
*agent_effective,
|
|
170
|
+
]
|
|
129
171
|
|
|
130
172
|
return CollaborationContextResult(
|
|
131
173
|
personal_space_id=space_id,
|
|
132
174
|
personal_project_id=personal_project_id,
|
|
175
|
+
project_agent_id=project_agent_id,
|
|
133
176
|
global_preferences=global_preferences,
|
|
134
177
|
project_preferences=project_preferences,
|
|
178
|
+
agent_preferences=agent_preferences,
|
|
135
179
|
effective_preferences=effective_preferences,
|
|
136
|
-
conflicts=[*global_conflicts, *project_conflicts],
|
|
180
|
+
conflicts=[*global_conflicts, *project_conflicts, *agent_conflicts],
|
|
137
181
|
overridden_global_ids=overridden_global_ids,
|
|
138
182
|
overridden_inherited_ids=overridden_inherited_ids,
|
|
183
|
+
overridden_project_ids=overridden_project_ids,
|
|
139
184
|
overridden_lower_authority_ids=overridden_lower_authority_ids,
|
|
140
185
|
truncated=truncated,
|
|
141
186
|
)
|
|
@@ -220,6 +265,11 @@ def _node_query() -> str:
|
|
|
220
265
|
)
|
|
221
266
|
)
|
|
222
267
|
)
|
|
268
|
+
OR (
|
|
269
|
+
node.fuli_preference_scope = 'agent'
|
|
270
|
+
AND node.fuli_preference_project_id = $project_id
|
|
271
|
+
AND node.fuli_preference_agent_id = $project_agent_id
|
|
272
|
+
)
|
|
223
273
|
)
|
|
224
274
|
RETURN node.uuid AS id,
|
|
225
275
|
coalesce(node.fuli_key, node.uuid) AS key,
|
|
@@ -228,6 +278,7 @@ def _node_query() -> str:
|
|
|
228
278
|
node.fuli_profile_aspect AS profile_aspect,
|
|
229
279
|
coalesce(node.fuli_preference_scope, 'global') AS preference_scope,
|
|
230
280
|
node.fuli_preference_project_id AS preference_project_id,
|
|
281
|
+
node.fuli_preference_agent_id AS preference_agent_id,
|
|
231
282
|
node.fuli_attributes_json AS attributes_json,
|
|
232
283
|
node.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
233
284
|
node.fuli_reasoning_summary AS reasoning_summary,
|
|
@@ -265,6 +316,11 @@ def _edge_query() -> str:
|
|
|
265
316
|
)
|
|
266
317
|
)
|
|
267
318
|
)
|
|
319
|
+
OR (
|
|
320
|
+
edge.fuli_preference_scope = 'agent'
|
|
321
|
+
AND edge.fuli_preference_project_id = $project_id
|
|
322
|
+
AND edge.fuli_preference_agent_id = $project_agent_id
|
|
323
|
+
)
|
|
268
324
|
)
|
|
269
325
|
RETURN edge.uuid AS id,
|
|
270
326
|
coalesce(edge.fuli_key, edge.uuid) AS key,
|
|
@@ -276,6 +332,7 @@ def _edge_query() -> str:
|
|
|
276
332
|
edge.fuli_profile_aspect AS profile_aspect,
|
|
277
333
|
coalesce(edge.fuli_preference_scope, 'global') AS preference_scope,
|
|
278
334
|
edge.fuli_preference_project_id AS preference_project_id,
|
|
335
|
+
edge.fuli_preference_agent_id AS preference_agent_id,
|
|
279
336
|
edge.fuli_attributes_json AS attributes_json,
|
|
280
337
|
edge.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
281
338
|
edge.fuli_reasoning_summary AS reasoning_summary,
|
|
@@ -337,6 +394,7 @@ def _preference_item(
|
|
|
337
394
|
or key
|
|
338
395
|
)
|
|
339
396
|
project_id = record.get('preference_project_id')
|
|
397
|
+
agent_id = record.get('preference_agent_id')
|
|
340
398
|
scope = project_scopes.get(project_id, {
|
|
341
399
|
'scope_distance': 0,
|
|
342
400
|
'scope_path': [],
|
|
@@ -375,6 +433,7 @@ def _preference_item(
|
|
|
375
433
|
profile_aspect=record['profile_aspect'],
|
|
376
434
|
preference_scope=record['preference_scope'],
|
|
377
435
|
preference_project_id=project_id,
|
|
436
|
+
preference_agent_id=agent_id,
|
|
378
437
|
attributes=attributes,
|
|
379
438
|
weight=weight,
|
|
380
439
|
reason=reason,
|
|
@@ -425,6 +484,7 @@ def _conflict_free(
|
|
|
425
484
|
preference_key=preference_key,
|
|
426
485
|
preference_scope=first.preference_scope,
|
|
427
486
|
preference_project_id=first.preference_project_id,
|
|
487
|
+
preference_agent_id=first.preference_agent_id,
|
|
428
488
|
item_ids=[item.id for item in candidates],
|
|
429
489
|
))
|
|
430
490
|
effective.sort(key=_sort_key)
|
|
@@ -481,13 +541,15 @@ def _scope_conflict_free(
|
|
|
481
541
|
def _highest_authority_by_key(
|
|
482
542
|
global_items: list[CollaborationPreferenceItem],
|
|
483
543
|
project_items: list[CollaborationPreferenceItem],
|
|
544
|
+
agent_items: list[CollaborationPreferenceItem],
|
|
484
545
|
) -> tuple[
|
|
546
|
+
list[CollaborationPreferenceItem],
|
|
485
547
|
list[CollaborationPreferenceItem],
|
|
486
548
|
list[CollaborationPreferenceItem],
|
|
487
549
|
list[str],
|
|
488
550
|
]:
|
|
489
551
|
grouped: dict[str, list[CollaborationPreferenceItem]] = defaultdict(list)
|
|
490
|
-
for item in [*global_items, *project_items]:
|
|
552
|
+
for item in [*global_items, *project_items, *agent_items]:
|
|
491
553
|
grouped[item.preference_key].append(item)
|
|
492
554
|
selected_ids = set()
|
|
493
555
|
lower_authority_ids = []
|
|
@@ -505,6 +567,7 @@ def _highest_authority_by_key(
|
|
|
505
567
|
return (
|
|
506
568
|
[item for item in global_items if item.id in selected_ids],
|
|
507
569
|
[item for item in project_items if item.id in selected_ids],
|
|
570
|
+
[item for item in agent_items if item.id in selected_ids],
|
|
508
571
|
sorted(lower_authority_ids),
|
|
509
572
|
)
|
|
510
573
|
|
|
@@ -512,7 +575,7 @@ def _highest_authority_by_key(
|
|
|
512
575
|
def _sort_key(item: CollaborationPreferenceItem) -> tuple:
|
|
513
576
|
created = -item.created_at.timestamp() if item.created_at else 0
|
|
514
577
|
return (
|
|
515
|
-
0
|
|
578
|
+
{'global': 0, 'project': 1, 'agent': 2}[item.preference_scope],
|
|
516
579
|
0 if item.confirmation_status == 'confirmed' else 1,
|
|
517
580
|
item.profile_aspect,
|
|
518
581
|
item.preference_key,
|
|
@@ -6,6 +6,7 @@ from graphiti_core.edges import EntityEdge
|
|
|
6
6
|
|
|
7
7
|
from .models import ConfirmationBasis, EntitySearchResult, SearchRequest, SearchResult
|
|
8
8
|
from .personal_project_access import authorize_personal_project
|
|
9
|
+
from .project_agent_access import authorize_project_agent
|
|
9
10
|
from .provider_values import (
|
|
10
11
|
json_object,
|
|
11
12
|
native_datetime as _native_datetime,
|
|
@@ -103,6 +104,7 @@ async def search_knowledge(store, actor: dict, request: SearchRequest) -> Search
|
|
|
103
104
|
ranked_edges,
|
|
104
105
|
epistemic,
|
|
105
106
|
request.active_personal_project_id,
|
|
107
|
+
request.project_agent_id,
|
|
106
108
|
)
|
|
107
109
|
ranked_edges = ranked_edges[: request.limit]
|
|
108
110
|
entities = (
|
|
@@ -193,10 +195,14 @@ async def personal_edge_scopes(
|
|
|
193
195
|
coalesce(edge.fuli_preference_scope, 'global')
|
|
194
196
|
AS preference_scope,
|
|
195
197
|
edge.fuli_preference_project_id AS preference_project_id,
|
|
198
|
+
edge.fuli_preference_agent_id AS preference_agent_id,
|
|
196
199
|
assignment.project_id AS assignment_project_id,
|
|
197
200
|
[episode IN episodes
|
|
198
201
|
WHERE episode.fuli_personal_project_id IS NOT NULL |
|
|
199
202
|
episode.fuli_personal_project_id] AS episode_project_ids,
|
|
203
|
+
[episode IN episodes
|
|
204
|
+
WHERE episode.fuli_project_agent_id IS NOT NULL |
|
|
205
|
+
episode.fuli_project_agent_id] AS episode_project_agent_ids,
|
|
200
206
|
any(episode IN episodes
|
|
201
207
|
WHERE episode.fuli_personal_project_id IS NULL)
|
|
202
208
|
AS has_global_episode,
|
|
@@ -310,6 +316,16 @@ async def _personal_search_space(store, actor, spaces, request):
|
|
|
310
316
|
space = personal_spaces[0]
|
|
311
317
|
for project_id in request.personal_project_ids:
|
|
312
318
|
await authorize_personal_project(store, actor, space, project_id)
|
|
319
|
+
if request.project_agent_id:
|
|
320
|
+
await authorize_project_agent(
|
|
321
|
+
store,
|
|
322
|
+
actor,
|
|
323
|
+
space,
|
|
324
|
+
request.active_personal_project_id,
|
|
325
|
+
request.project_agent_id,
|
|
326
|
+
require_active=True,
|
|
327
|
+
require_memory=True,
|
|
328
|
+
)
|
|
313
329
|
return space
|
|
314
330
|
|
|
315
331
|
|
|
@@ -367,11 +383,15 @@ async def _personal_entities(
|
|
|
367
383
|
ELSE coalesce(node.fuli_preference_scope, 'global') END
|
|
368
384
|
AS preference_scope,
|
|
369
385
|
node.fuli_preference_project_id AS preference_project_id,
|
|
386
|
+
node.fuli_preference_agent_id AS preference_agent_id,
|
|
370
387
|
coalesce(node.fuli_key, node.uuid) AS key,
|
|
371
388
|
assignment.project_id AS assignment_project_id,
|
|
372
389
|
[episode IN episodes
|
|
373
390
|
WHERE episode.fuli_personal_project_id IS NOT NULL |
|
|
374
391
|
episode.fuli_personal_project_id] AS episode_project_ids,
|
|
392
|
+
[episode IN episodes
|
|
393
|
+
WHERE episode.fuli_project_agent_id IS NOT NULL |
|
|
394
|
+
episode.fuli_project_agent_id] AS episode_project_agent_ids,
|
|
375
395
|
any(episode IN episodes
|
|
376
396
|
WHERE episode.fuli_personal_project_id IS NULL)
|
|
377
397
|
AS has_global_episode,
|
|
@@ -461,6 +481,7 @@ async def _personal_entities(
|
|
|
461
481
|
ranked = _dedupe_ranked_entities(
|
|
462
482
|
ranked,
|
|
463
483
|
request.active_personal_project_id,
|
|
484
|
+
request.project_agent_id,
|
|
464
485
|
)[: request.limit]
|
|
465
486
|
return [
|
|
466
487
|
_entity_search_result(record, score, space['id'])
|
|
@@ -478,8 +499,10 @@ def _item_scope_metadata(
|
|
|
478
499
|
field in record
|
|
479
500
|
for field in (
|
|
480
501
|
'profile_aspect',
|
|
502
|
+
'preference_agent_id',
|
|
481
503
|
'assignment_project_id',
|
|
482
504
|
'episode_project_ids',
|
|
505
|
+
'episode_project_agent_ids',
|
|
483
506
|
'reference_project_ids',
|
|
484
507
|
'has_global_episode',
|
|
485
508
|
)
|
|
@@ -497,10 +520,38 @@ def _item_scope_metadata(
|
|
|
497
520
|
project_id = record.get('preference_project_id')
|
|
498
521
|
if project_id not in request.personal_project_ids:
|
|
499
522
|
return None
|
|
523
|
+
if preference_scope == 'agent':
|
|
524
|
+
agent_id = record.get('preference_agent_id')
|
|
525
|
+
if (
|
|
526
|
+
not request.project_agent_id
|
|
527
|
+
or agent_id != request.project_agent_id
|
|
528
|
+
or project_id != request.active_personal_project_id
|
|
529
|
+
):
|
|
530
|
+
return None
|
|
531
|
+
return _agent_scope_metadata(
|
|
532
|
+
project_id,
|
|
533
|
+
project_scopes[project_id],
|
|
534
|
+
agent_id,
|
|
535
|
+
)
|
|
500
536
|
return _project_scope_metadata(project_id, project_scopes[project_id])
|
|
501
537
|
|
|
502
538
|
assignment_project_id = record.get('assignment_project_id')
|
|
503
539
|
episode_project_ids = list(record.get('episode_project_ids') or [])
|
|
540
|
+
episode_project_agent_ids = list(
|
|
541
|
+
record.get('episode_project_agent_ids') or []
|
|
542
|
+
)
|
|
543
|
+
if episode_project_agent_ids:
|
|
544
|
+
if (
|
|
545
|
+
not request.project_agent_id
|
|
546
|
+
or request.project_agent_id not in episode_project_agent_ids
|
|
547
|
+
or request.active_personal_project_id not in episode_project_ids
|
|
548
|
+
):
|
|
549
|
+
return None
|
|
550
|
+
return _agent_scope_metadata(
|
|
551
|
+
request.active_personal_project_id,
|
|
552
|
+
project_scopes[request.active_personal_project_id],
|
|
553
|
+
request.project_agent_id,
|
|
554
|
+
)
|
|
504
555
|
reference_project_ids = list(record.get('reference_project_ids') or [])
|
|
505
556
|
project_ids = (
|
|
506
557
|
[assignment_project_id]
|
|
@@ -566,6 +617,17 @@ def _project_scope_metadata(project_id: str, scope: dict) -> dict:
|
|
|
566
617
|
}
|
|
567
618
|
|
|
568
619
|
|
|
620
|
+
def _agent_scope_metadata(project_id: str, scope: dict, agent_id: str) -> dict:
|
|
621
|
+
return {
|
|
622
|
+
**_project_scope_metadata(project_id, scope),
|
|
623
|
+
'project_agent_id': agent_id,
|
|
624
|
+
'inherited_from_project_id': None,
|
|
625
|
+
'scope_distance': 0,
|
|
626
|
+
'scope_path': [project_id],
|
|
627
|
+
'inherited': False,
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
|
|
569
631
|
def _ranked_relevance(base_score: float, metadata) -> float:
|
|
570
632
|
if base_score <= 0:
|
|
571
633
|
return 0.0
|
|
@@ -595,21 +657,34 @@ def _ranked_relevance(base_score: float, metadata) -> float:
|
|
|
595
657
|
)
|
|
596
658
|
|
|
597
659
|
|
|
598
|
-
def _scope_precedence(
|
|
660
|
+
def _scope_precedence(
|
|
661
|
+
metadata,
|
|
662
|
+
active_project_id: str | None,
|
|
663
|
+
project_agent_id: str | None = None,
|
|
664
|
+
) -> tuple:
|
|
599
665
|
project_id = metadata.get('defined_project_id')
|
|
600
666
|
return (
|
|
667
|
+
0 if project_agent_id and metadata.get('project_agent_id') == project_agent_id else 1,
|
|
601
668
|
0 if active_project_id and project_id == active_project_id else 1,
|
|
602
669
|
1 if metadata.get('inherited_from_project_id') else 0,
|
|
603
670
|
int(metadata.get('scope_distance') or 0),
|
|
604
671
|
)
|
|
605
672
|
|
|
606
673
|
|
|
607
|
-
def _dedupe_ranked_edges(
|
|
674
|
+
def _dedupe_ranked_edges(
|
|
675
|
+
ranked,
|
|
676
|
+
metadata_by_id,
|
|
677
|
+
active_project_id,
|
|
678
|
+
project_agent_id=None,
|
|
679
|
+
):
|
|
608
680
|
selected = {}
|
|
609
681
|
for score, edge in ranked:
|
|
610
682
|
metadata = metadata_by_id.get(edge.uuid, {})
|
|
611
683
|
key = metadata.get('key') or edge.uuid
|
|
612
|
-
quality = (
|
|
684
|
+
quality = (
|
|
685
|
+
_scope_precedence(metadata, active_project_id, project_agent_id),
|
|
686
|
+
-score,
|
|
687
|
+
)
|
|
613
688
|
current = selected.get(key)
|
|
614
689
|
if current is None or quality < current[0]:
|
|
615
690
|
selected[key] = (quality, score, edge)
|
|
@@ -620,11 +695,14 @@ def _dedupe_ranked_edges(ranked, metadata_by_id, active_project_id):
|
|
|
620
695
|
)
|
|
621
696
|
|
|
622
697
|
|
|
623
|
-
def _dedupe_ranked_entities(ranked, active_project_id):
|
|
698
|
+
def _dedupe_ranked_entities(ranked, active_project_id, project_agent_id=None):
|
|
624
699
|
selected = {}
|
|
625
700
|
for record, score in ranked:
|
|
626
701
|
key = record.get('key') or record.get('id')
|
|
627
|
-
quality = (
|
|
702
|
+
quality = (
|
|
703
|
+
_scope_precedence(record, active_project_id, project_agent_id),
|
|
704
|
+
-score,
|
|
705
|
+
)
|
|
628
706
|
current = selected.get(key)
|
|
629
707
|
if current is None or quality < current[0]:
|
|
630
708
|
selected[key] = (quality, record, score)
|
|
@@ -665,6 +743,7 @@ def _entity_search_result(record, score: float, space_id: str) -> EntitySearchRe
|
|
|
665
743
|
'attributes_json',
|
|
666
744
|
'assignment_project_id',
|
|
667
745
|
'episode_project_ids',
|
|
746
|
+
'episode_project_agent_ids',
|
|
668
747
|
'has_global_episode',
|
|
669
748
|
'reference_project_ids',
|
|
670
749
|
'inherited',
|
|
@@ -19,6 +19,136 @@ _CREDENTIAL_PATTERNS = (
|
|
|
19
19
|
),
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
# Keep this deliberately small and provider-local instead of depending on a
|
|
23
|
+
# third-party emoji database. The supplementary-plane range covers current
|
|
24
|
+
# pictographs and the explicit BMP ranges cover the common text-presentation
|
|
25
|
+
# symbols that become emoji with a variation selector (for example, ``❤️``).
|
|
26
|
+
_EMOJI_BASE_RANGES = (
|
|
27
|
+
(0x1F000, 0x1FAFF),
|
|
28
|
+
(0x2600, 0x27BF),
|
|
29
|
+
)
|
|
30
|
+
_EMOJI_BASE_CODEPOINTS = frozenset({
|
|
31
|
+
0x00A9, 0x00AE,
|
|
32
|
+
0x203C, 0x2049,
|
|
33
|
+
0x2122, 0x2139,
|
|
34
|
+
0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199,
|
|
35
|
+
0x21A9, 0x21AA,
|
|
36
|
+
0x231A, 0x231B, 0x2328, 0x23CF,
|
|
37
|
+
0x23E9, 0x23EA, 0x23EB, 0x23EC, 0x23ED, 0x23EE,
|
|
38
|
+
0x23EF, 0x23F0, 0x23F1, 0x23F2, 0x23F3,
|
|
39
|
+
0x23F8, 0x23F9, 0x23FA,
|
|
40
|
+
0x24C2,
|
|
41
|
+
0x25AA, 0x25AB, 0x25B6, 0x25C0,
|
|
42
|
+
0x25FB, 0x25FC, 0x25FD, 0x25FE,
|
|
43
|
+
0x2934, 0x2935,
|
|
44
|
+
0x2B05, 0x2B06, 0x2B07, 0x2B1B, 0x2B1C, 0x2B50, 0x2B55,
|
|
45
|
+
0x3030, 0x303D, 0x3297, 0x3299,
|
|
46
|
+
})
|
|
47
|
+
_EMOJI_VARIATION_SELECTORS = frozenset({0xFE0E, 0xFE0F})
|
|
48
|
+
_EMOJI_SKIN_TONES = range(0x1F3FB, 0x1F400)
|
|
49
|
+
_EMOJI_TAG_RANGE = range(0xE0020, 0xE0080)
|
|
50
|
+
_EMOJI_KEYCAP_MARK = 0x20E3
|
|
51
|
+
_EMOJI_ZWJ = 0x200D
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_emoji_base(character: str) -> bool:
|
|
55
|
+
codepoint = ord(character)
|
|
56
|
+
return (
|
|
57
|
+
codepoint not in _EMOJI_SKIN_TONES
|
|
58
|
+
and any(
|
|
59
|
+
start <= codepoint <= end
|
|
60
|
+
for start, end in _EMOJI_BASE_RANGES
|
|
61
|
+
)
|
|
62
|
+
) or codepoint in _EMOJI_BASE_CODEPOINTS
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_regional_indicator(character: str) -> bool:
|
|
66
|
+
return 0x1F1E6 <= ord(character) <= 0x1F1FF
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_keycap_base(character: str) -> bool:
|
|
70
|
+
return character in '#*' or '0' <= character <= '9'
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _valid_emoji_sequence(value: str) -> bool:
|
|
74
|
+
"""Accept one emoji grapheme, including common composed sequences."""
|
|
75
|
+
|
|
76
|
+
index = 0
|
|
77
|
+
saw_base = False
|
|
78
|
+
expect_base = True
|
|
79
|
+
while index < len(value):
|
|
80
|
+
character = value[index]
|
|
81
|
+
codepoint = ord(character)
|
|
82
|
+
if expect_base:
|
|
83
|
+
if _is_keycap_base(character):
|
|
84
|
+
index += 1
|
|
85
|
+
if (
|
|
86
|
+
index < len(value)
|
|
87
|
+
and ord(value[index]) in _EMOJI_VARIATION_SELECTORS
|
|
88
|
+
):
|
|
89
|
+
index += 1
|
|
90
|
+
if index >= len(value) or ord(value[index]) != _EMOJI_KEYCAP_MARK:
|
|
91
|
+
return False
|
|
92
|
+
index += 1
|
|
93
|
+
saw_base = True
|
|
94
|
+
expect_base = False
|
|
95
|
+
continue
|
|
96
|
+
if not _is_emoji_base(character):
|
|
97
|
+
return False
|
|
98
|
+
index += 1
|
|
99
|
+
saw_base = True
|
|
100
|
+
# A pair of regional indicators is one flag grapheme.
|
|
101
|
+
if (
|
|
102
|
+
_is_regional_indicator(character)
|
|
103
|
+
and index < len(value)
|
|
104
|
+
and _is_regional_indicator(value[index])
|
|
105
|
+
):
|
|
106
|
+
index += 1
|
|
107
|
+
expect_base = False
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if codepoint in _EMOJI_SKIN_TONES:
|
|
111
|
+
index += 1
|
|
112
|
+
continue
|
|
113
|
+
if codepoint in _EMOJI_VARIATION_SELECTORS:
|
|
114
|
+
index += 1
|
|
115
|
+
continue
|
|
116
|
+
if codepoint in _EMOJI_TAG_RANGE:
|
|
117
|
+
index += 1
|
|
118
|
+
continue
|
|
119
|
+
if codepoint == _EMOJI_ZWJ:
|
|
120
|
+
index += 1
|
|
121
|
+
if index >= len(value):
|
|
122
|
+
return False
|
|
123
|
+
expect_base = True
|
|
124
|
+
continue
|
|
125
|
+
# A second standalone base would be two graphemes. Occupation
|
|
126
|
+
# markers intentionally accept one grapheme only.
|
|
127
|
+
return False
|
|
128
|
+
return saw_base and not expect_base
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def validate_emoji_sequence(
|
|
132
|
+
value: str | None,
|
|
133
|
+
label: str = 'emoji',
|
|
134
|
+
) -> str | None:
|
|
135
|
+
"""Normalize and validate a conservative emoji-only field value."""
|
|
136
|
+
|
|
137
|
+
if value is None:
|
|
138
|
+
return None
|
|
139
|
+
if not isinstance(value, str):
|
|
140
|
+
raise ValueError(f'{label} must be an emoji')
|
|
141
|
+
normalized = value.strip()
|
|
142
|
+
if not normalized:
|
|
143
|
+
raise ValueError(f'{label} must be an emoji')
|
|
144
|
+
if len(normalized) > 32:
|
|
145
|
+
raise ValueError(f'{label} must contain at most 32 characters')
|
|
146
|
+
if any(character.isspace() for character in normalized):
|
|
147
|
+
raise ValueError(f'{label} must not contain whitespace')
|
|
148
|
+
if not _valid_emoji_sequence(normalized):
|
|
149
|
+
raise ValueError(f'{label} must contain one valid emoji sequence')
|
|
150
|
+
return normalized
|
|
151
|
+
|
|
22
152
|
|
|
23
153
|
def reject_credentials(model: Any, label: str) -> None:
|
|
24
154
|
searchable = json.dumps(model.model_dump(mode='json'), ensure_ascii=False)
|
|
@@ -104,6 +104,7 @@ ConfirmationStatus = Literal['confirmed', 'agent_confirmed', 'pending']
|
|
|
104
104
|
ConfirmationActorKind = Literal['user', 'agent', 'authoritative_source', 'import']
|
|
105
105
|
PersonalProfileAspect = Literal['taste', 'personality', 'judgment_preference']
|
|
106
106
|
PreferenceScope = Literal['global', 'project']
|
|
107
|
+
ScopedPreferenceScope = Literal['global', 'project', 'agent']
|
|
107
108
|
PreferenceConflictQueueStatus = Literal['ai_pending', 'resolved']
|
|
108
109
|
PreferenceConflictResolutionAction = Literal[
|
|
109
110
|
'merge',
|
|
@@ -671,12 +672,18 @@ class PublicationDraftDecision(StrictModel):
|
|
|
671
672
|
if self.decision != 'submit_public' and self.shared_proposal_id:
|
|
672
673
|
raise ValueError('shared_proposal_id is only valid for public submission')
|
|
673
674
|
return self
|
|
674
|
-
|
|
675
675
|
class KnowledgeCommit(StrictModel):
|
|
676
676
|
space_id: str = Field(min_length=1, max_length=128)
|
|
677
677
|
personal_project_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
678
|
+
project_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
678
679
|
episode: StructuredEpisode
|
|
679
680
|
|
|
681
|
+
@model_validator(mode='after')
|
|
682
|
+
def validate_project_agent_scope(self):
|
|
683
|
+
if self.project_agent_id and not self.personal_project_id:
|
|
684
|
+
raise ValueError('project Agent knowledge requires personal_project_id')
|
|
685
|
+
return self
|
|
686
|
+
|
|
680
687
|
|
|
681
688
|
class CommitResult(StrictModel):
|
|
682
689
|
status: Literal['committed', 'duplicate']
|
|
@@ -727,6 +734,7 @@ class SearchRequest(StrictModel):
|
|
|
727
734
|
min_length=1,
|
|
728
735
|
max_length=128,
|
|
729
736
|
)
|
|
737
|
+
project_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
730
738
|
inherit_project_knowledge: bool = True
|
|
731
739
|
include_personal_global: bool = False
|
|
732
740
|
|
|
@@ -749,6 +757,8 @@ class SearchRequest(StrictModel):
|
|
|
749
757
|
raise ValueError(
|
|
750
758
|
'active personal project id must be present in personal project ids'
|
|
751
759
|
)
|
|
760
|
+
if self.project_agent_id and not self.active_personal_project_id:
|
|
761
|
+
raise ValueError('project Agent search requires active_personal_project_id')
|
|
752
762
|
return self
|
|
753
763
|
|
|
754
764
|
|
|
@@ -767,12 +777,14 @@ class EntitySearchResult(StrictModel):
|
|
|
767
777
|
confirmation_basis: ConfirmationBasis | None = None
|
|
768
778
|
reasoning_summary: str | None = None
|
|
769
779
|
profile_aspect: PersonalProfileAspect | None = None
|
|
770
|
-
preference_scope:
|
|
780
|
+
preference_scope: ScopedPreferenceScope | None = None
|
|
771
781
|
preference_project_id: str | None = None
|
|
782
|
+
preference_agent_id: str | None = None
|
|
772
783
|
key: str | None = None
|
|
773
784
|
preference_key: str | None = None
|
|
774
785
|
preference_qualifiers: dict[str, Any] = Field(default_factory=dict)
|
|
775
786
|
defined_project_id: str | None = None
|
|
787
|
+
project_agent_id: str | None = None
|
|
776
788
|
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
777
789
|
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
778
790
|
human_edited: bool = False
|
|
@@ -816,12 +828,14 @@ class FactResult(StrictModel):
|
|
|
816
828
|
confirmation_basis: ConfirmationBasis | None = None
|
|
817
829
|
reasoning_summary: str | None = None
|
|
818
830
|
profile_aspect: PersonalProfileAspect | None = None
|
|
819
|
-
preference_scope:
|
|
831
|
+
preference_scope: ScopedPreferenceScope | None = None
|
|
820
832
|
preference_project_id: str | None = None
|
|
833
|
+
preference_agent_id: str | None = None
|
|
821
834
|
key: str | None = None
|
|
822
835
|
preference_key: str | None = None
|
|
823
836
|
preference_qualifiers: dict[str, Any] = Field(default_factory=dict)
|
|
824
837
|
defined_project_id: str | None = None
|
|
838
|
+
project_agent_id: str | None = None
|
|
825
839
|
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
826
840
|
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
827
841
|
human_edited: bool = False
|
|
@@ -859,8 +873,9 @@ class CollaborationPreferenceItem(StrictModel):
|
|
|
859
873
|
title: str
|
|
860
874
|
instruction: str
|
|
861
875
|
profile_aspect: PersonalProfileAspect
|
|
862
|
-
preference_scope:
|
|
876
|
+
preference_scope: ScopedPreferenceScope
|
|
863
877
|
preference_project_id: str | None = None
|
|
878
|
+
preference_agent_id: str | None = None
|
|
864
879
|
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
865
880
|
weight: float | None = Field(default=None, ge=0, le=1, allow_inf_nan=False)
|
|
866
881
|
reason: str | None = None
|
|
@@ -878,20 +893,24 @@ class CollaborationPreferenceItem(StrictModel):
|
|
|
878
893
|
|
|
879
894
|
class CollaborationPreferenceConflict(StrictModel):
|
|
880
895
|
preference_key: str
|
|
881
|
-
preference_scope:
|
|
896
|
+
preference_scope: ScopedPreferenceScope
|
|
882
897
|
preference_project_id: str | None = None
|
|
898
|
+
preference_agent_id: str | None = None
|
|
883
899
|
item_ids: list[str] = Field(min_length=2)
|
|
884
900
|
|
|
885
901
|
|
|
886
902
|
class CollaborationContextResult(StrictModel):
|
|
887
903
|
personal_space_id: str
|
|
888
904
|
personal_project_id: str | None = None
|
|
905
|
+
project_agent_id: str | None = None
|
|
889
906
|
global_preferences: list[CollaborationPreferenceItem] = Field(default_factory=list)
|
|
890
907
|
project_preferences: list[CollaborationPreferenceItem] = Field(default_factory=list)
|
|
908
|
+
agent_preferences: list[CollaborationPreferenceItem] = Field(default_factory=list)
|
|
891
909
|
effective_preferences: list[CollaborationPreferenceItem] = Field(default_factory=list)
|
|
892
910
|
conflicts: list[CollaborationPreferenceConflict] = Field(default_factory=list)
|
|
893
911
|
overridden_global_ids: list[str] = Field(default_factory=list)
|
|
894
912
|
overridden_inherited_ids: list[str] = Field(default_factory=list)
|
|
913
|
+
overridden_project_ids: list[str] = Field(default_factory=list)
|
|
895
914
|
overridden_lower_authority_ids: list[str] = Field(default_factory=list)
|
|
896
915
|
truncated: bool = False
|
|
897
916
|
|