fuli-context 0.7.2 → 0.7.4-beta.1
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 +173 -21
- package/README.zh-CN.md +146 -12
- package/compose.graphiti.yml +2 -0
- package/dist/web/assets/{AboutPage-DfnZo7Lo.js → AboutPage-CftRexSY.js} +1 -1
- package/dist/web/assets/{ConnectionsPage-BloaSKmt.js → ConnectionsPage-D5J91x6d.js} +1 -1
- package/dist/web/assets/{useMinimumLoadingDisplay-BpCyBeko.css → KnowledgeEditDialog-DWUV_NPp.css} +1 -1
- package/dist/web/assets/KnowledgeEditDialog-wotVahvr.js +1 -0
- package/dist/web/assets/{KnowledgeInspector-Bjhdu2mf.css → KnowledgeInspector--1NujtXW.css} +1 -1
- package/dist/web/assets/KnowledgeInspector-BzkW0mVs.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BebUDiHt.js +1 -0
- package/dist/web/assets/KnowledgePage-OUT07aKH.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-1pHJ0MNZ.js +4 -0
- package/dist/web/assets/OverviewPage-CHE6XhdS.js +1 -0
- package/dist/web/assets/PersonalProfilePage-B7T09bow.css +1 -0
- package/dist/web/assets/PersonalProfilePage-CXXiabUy.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-d2wYGW8n.js +1 -0
- package/dist/web/assets/PublicProjectsPage-hxLShtnZ.js +1 -0
- package/dist/web/assets/{ReviewPage-B7EN817i.js → ReviewPage-DUeyu7E8.js} +1 -1
- package/dist/web/assets/{SearchableSelect-ClLN0rIb.js → SearchableSelect-C5RiB5F8.js} +1 -1
- package/dist/web/assets/SettingsPage-BMuASSzQ.js +1 -0
- package/dist/web/assets/SettingsPage-CAfz4qiV.css +1 -0
- package/dist/web/assets/WritingTastePage-B9BCYIwQ.js +1 -0
- package/dist/web/assets/WritingTastePage-DSgvTH6x.css +1 -0
- package/dist/web/assets/{console-CbDsKrsO.js → console-DMvHxKf9.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-Dnlcw7Rj.js +1 -0
- package/dist/web/assets/{i18n-BQcns1PH.js → i18n-CWfd8Dtt.js} +1 -1
- package/dist/web/assets/{identity-B8K4VYg6.js → identity-C9mLdR8L.js} +1 -1
- package/dist/web/assets/index-F5e2sQ7p.js +2 -0
- package/dist/web/assets/index-X2j29j8i.css +1 -0
- package/dist/web/assets/{model-eF52GgPF.js → model-CsYAR8O3.js} +1 -1
- package/dist/web/assets/paths-CDREouhi.js +1 -0
- package/dist/web/assets/useApi-CROJJdhE-BLQiPVw0.js +1 -0
- package/dist/web/assets/vue-router-DN8x6iY1.js +1 -0
- package/dist/web/index.html +7 -6
- package/graph-provider/fuli_graph/app.py +238 -2
- package/graph-provider/fuli_graph/auth.py +18 -0
- package/graph-provider/fuli_graph/collaboration_context.py +253 -17
- package/graph-provider/fuli_graph/common_knowledge.py +6 -2
- package/graph-provider/fuli_graph/config.py +5 -0
- package/graph-provider/fuli_graph/graph_models.py +6 -0
- package/graph-provider/fuli_graph/graph_projection.py +14 -0
- package/graph-provider/fuli_graph/graph_query.py +27 -3
- package/graph-provider/fuli_graph/knowledge_management.py +19 -1
- package/graph-provider/fuli_graph/knowledge_review.py +49 -4
- package/graph-provider/fuli_graph/knowledge_review_models.py +11 -5
- package/graph-provider/fuli_graph/knowledge_search.py +11 -0
- package/graph-provider/fuli_graph/models.py +15 -2
- package/graph-provider/fuli_graph/personal_global_preference_models.py +234 -0
- package/graph-provider/fuli_graph/personal_global_preferences.py +1247 -0
- package/graph-provider/fuli_graph/project_action_models.py +22 -0
- package/graph-provider/fuli_graph/project_knowledge.py +121 -2
- package/graph-provider/fuli_graph/provider_values.py +24 -0
- package/graph-provider/fuli_graph/runtime.py +78 -0
- package/graph-provider/fuli_graph/search_projection.py +14 -1
- package/graph-provider/fuli_graph/store_knowledge.py +116 -2
- package/graph-provider/fuli_graph/workflow_candidate_models.py +168 -0
- package/graph-provider/fuli_graph/workflow_candidates.py +996 -0
- package/graph-provider/fuli_graph/workflow_observation_models.py +30 -0
- package/graph-provider/fuli_graph/workflow_observations.py +84 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +4 -4
- package/skills/capturing-session-knowledge/SKILL.md +32 -5
- package/skills/flreview/SKILL.md +68 -40
- package/skills/flreview/agents/openai.yaml +2 -2
- package/src/agent-tools/graph-definitions.js +140 -24
- package/src/agent-tools/graph-handlers.js +10 -4
- package/src/cli/command-registry.js +1 -1
- package/src/cli/local-runtime-command.js +69 -2
- package/src/graphiti/agent-knowledge-workflows.js +497 -16
- package/src/graphiti/collaboration-preference-projection.js +162 -0
- package/src/graphiti/federated-application.js +157 -162
- package/src/graphiti/federated-search-support.js +102 -0
- package/src/graphiti/knowledge-provider-mapping.js +15 -0
- package/src/graphiti/provider-client.js +162 -10
- package/src/graphiti/related-project-suggestions.js +109 -0
- package/src/graphiti/runtime-config.js +14 -0
- package/src/graphiti/source-marker.js +2 -2
- package/src/graphiti/user-taste-skill.js +241 -0
- package/src/graphiti/workflow-candidate-mapping.js +87 -0
- package/src/graphiti/workflow-observation.js +82 -0
- package/src/graphiti/writing-taste-profile-workflow.js +25 -0
- package/src/graphiti/writing-taste-profile.js +490 -0
- package/src/http/graph-api-router.js +15 -2
- package/src/mcp/context-resources.js +158 -0
- package/src/mcp/create-mcp-server.js +41 -5
- package/src/mcp/instructions.js +5 -7
- package/src/mcp/session-id.js +60 -0
- package/src/mcp/task-context-registry.js +15 -3
- package/src/mcp/tool-annotations.js +8 -1
- package/src/mcp/tool-result.js +28 -4
- package/src/server.js +0 -1
- package/src/setup/claude-code-config.js +17 -6
- package/src/setup/graph-runtime.js +20 -2
- package/src/system/runtime-settings.js +121 -2
- package/src/system/system-service.js +5 -1
- package/README.en.md +0 -5
- package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +0 -1
- package/dist/web/assets/KnowledgePage-BWL0jT01.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +0 -4
- package/dist/web/assets/OverviewPage-5sJoO1Gh.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CdmEzyvZ.css +0 -1
- package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +0 -1
- package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +0 -1
- package/dist/web/assets/SettingsPage-C6ouU0sR.css +0 -1
- package/dist/web/assets/SettingsPage-Chd_B3eH.js +0 -1
- package/dist/web/assets/index-CB8Xmsc2.js +0 -2
- package/dist/web/assets/index-CZeuPh-U.css +0 -1
- package/dist/web/assets/paths-hwz-Ac1g.js +0 -1
- package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +0 -1
- package/dist/web/assets/vue-router-BsEp_XwI.js +0 -1
|
@@ -159,6 +159,12 @@ async def list_knowledge_review_candidates(
|
|
|
159
159
|
review_id=run.review_id,
|
|
160
160
|
routing_='r',
|
|
161
161
|
)
|
|
162
|
+
deferred_rows, _, _ = await store.runtime.driver.execute_query(
|
|
163
|
+
_DEFERRED_ITEM_QUERY,
|
|
164
|
+
space_id=space['id'],
|
|
165
|
+
review_id=run.review_id,
|
|
166
|
+
routing_='r',
|
|
167
|
+
)
|
|
162
168
|
candidates = build_review_candidates(
|
|
163
169
|
[dict(row) for row in [*entity_rows, *relationship_rows]],
|
|
164
170
|
scope=run.scope,
|
|
@@ -174,6 +180,12 @@ async def list_knowledge_review_candidates(
|
|
|
174
180
|
decided_candidate_keys={
|
|
175
181
|
row['candidate_key'] for row in decision_rows
|
|
176
182
|
},
|
|
183
|
+
deferred_candidate_keys={
|
|
184
|
+
key
|
|
185
|
+
for row in deferred_rows
|
|
186
|
+
for key in row.get('candidate_keys', [])
|
|
187
|
+
if key
|
|
188
|
+
},
|
|
177
189
|
)
|
|
178
190
|
selected = candidates[:request.limit]
|
|
179
191
|
return KnowledgeReviewCandidatePage(
|
|
@@ -272,7 +284,9 @@ def build_review_candidates(
|
|
|
272
284
|
review_cutoff_at: datetime | None = None,
|
|
273
285
|
conflict_item_keys: set[str],
|
|
274
286
|
decided_candidate_keys: set[str],
|
|
287
|
+
deferred_candidate_keys: set[str] | None = None,
|
|
275
288
|
) -> list[KnowledgeReviewCandidate]:
|
|
289
|
+
deferred_candidate_keys = deferred_candidate_keys or set()
|
|
276
290
|
prepared = []
|
|
277
291
|
for row in rows:
|
|
278
292
|
candidate_key = f"{row['item_kind']}:{row['item_id']}"
|
|
@@ -328,6 +342,7 @@ def build_review_candidates(
|
|
|
328
342
|
normalized,
|
|
329
343
|
previous_completed_at,
|
|
330
344
|
candidate_key in conflict_item_keys,
|
|
345
|
+
candidate_key in deferred_candidate_keys,
|
|
331
346
|
)
|
|
332
347
|
if not reasons:
|
|
333
348
|
continue
|
|
@@ -426,6 +441,7 @@ def _normalize_item_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
|
426
441
|
'preference_project_id': row.get('preference_project_id'),
|
|
427
442
|
'project_ids': project_ids,
|
|
428
443
|
'confirmation_status': row.get('confirmation_status') or 'pending',
|
|
444
|
+
'current_quadrant': row.get('current_quadrant') or 'known_known',
|
|
429
445
|
'utility_score': _float_or_default(row.get('utility_score'), 0),
|
|
430
446
|
'confidence_score': _float_or_default(row.get('confidence_score'), 0.5),
|
|
431
447
|
'qualified_use_count': int(row.get('qualified_use_count') or 0),
|
|
@@ -471,12 +487,15 @@ def _candidate_reasons(
|
|
|
471
487
|
item: dict,
|
|
472
488
|
previous_completed_at: datetime | None,
|
|
473
489
|
has_conflict: bool,
|
|
490
|
+
was_deferred: bool,
|
|
474
491
|
) -> list[str]:
|
|
475
492
|
reasons = []
|
|
476
493
|
if previous_completed_at is None or (
|
|
477
494
|
item['changed_at'] is not None and item['changed_at'] > previous_completed_at
|
|
478
495
|
):
|
|
479
496
|
reasons.append('changed_since_last')
|
|
497
|
+
if was_deferred:
|
|
498
|
+
reasons.append('deferred_from_previous')
|
|
480
499
|
if (
|
|
481
500
|
has_conflict
|
|
482
501
|
or item['requires_attention']
|
|
@@ -496,6 +515,7 @@ def _candidate_reasons(
|
|
|
496
515
|
def _reason_priority(reason: str) -> int:
|
|
497
516
|
return {
|
|
498
517
|
'changed_since_last': 1,
|
|
518
|
+
'deferred_from_previous': 1,
|
|
499
519
|
'conflict_or_attention': 2,
|
|
500
520
|
'low_weight': 3,
|
|
501
521
|
'repeated_cross_session': 4,
|
|
@@ -539,6 +559,11 @@ RETURN item.uuid AS item_id,
|
|
|
539
559
|
item.fuli_profile_aspect AS profile_aspect,
|
|
540
560
|
item.fuli_preference_scope AS preference_scope,
|
|
541
561
|
item.fuli_preference_project_id AS preference_project_id,
|
|
562
|
+
coalesce(
|
|
563
|
+
item.fuli_current_quadrant,
|
|
564
|
+
item.fuli_origin_quadrant,
|
|
565
|
+
'known_known'
|
|
566
|
+
) AS current_quadrant,
|
|
542
567
|
collect(DISTINCT assignment.project_id) AS assigned_project_ids,
|
|
543
568
|
collect(DISTINCT episode.fuli_personal_project_id) AS evidence_project_ids,
|
|
544
569
|
collect(DISTINCT episode.fuli_session_id) AS session_ids,
|
|
@@ -576,6 +601,11 @@ RETURN item.uuid AS item_id,
|
|
|
576
601
|
item.fuli_profile_aspect AS profile_aspect,
|
|
577
602
|
item.fuli_preference_scope AS preference_scope,
|
|
578
603
|
item.fuli_preference_project_id AS preference_project_id,
|
|
604
|
+
coalesce(
|
|
605
|
+
item.fuli_current_quadrant,
|
|
606
|
+
item.fuli_origin_quadrant,
|
|
607
|
+
'known_known'
|
|
608
|
+
) AS current_quadrant,
|
|
579
609
|
collect(DISTINCT assignment.project_id) AS assigned_project_ids,
|
|
580
610
|
collect(DISTINCT episode.fuli_personal_project_id) AS evidence_project_ids,
|
|
581
611
|
collect(DISTINCT episode.fuli_session_id) AS session_ids,
|
|
@@ -605,8 +635,23 @@ WITH space, collect(DISTINCT 'entity:' + knowledge.item_id) +
|
|
|
605
635
|
AS knowledge_keys
|
|
606
636
|
OPTIONAL MATCH (space)-[:HAS_PREFERENCE_CONFLICT]->
|
|
607
637
|
(preference:FuliPreferenceConflict {status: 'ai_pending'})
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
638
|
+
WITH knowledge_keys,
|
|
639
|
+
collect(DISTINCT preference.left_item_kind + ':' + preference.left_item_id) +
|
|
640
|
+
collect(DISTINCT preference.right_item_kind + ':' + preference.right_item_id)
|
|
641
|
+
AS preference_keys
|
|
642
|
+
RETURN knowledge_keys + preference_keys AS candidate_keys
|
|
643
|
+
'''
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
_DEFERRED_ITEM_QUERY = '''
|
|
647
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
648
|
+
[:HAS_KNOWLEDGE_REVIEW]->(past_run:FuliKnowledgeReviewRun)
|
|
649
|
+
WHERE past_run.id <> $review_id
|
|
650
|
+
MATCH (past_run)-[:HAS_REVIEW_DECISION]->
|
|
651
|
+
(decision:FuliKnowledgeReviewDecision)
|
|
652
|
+
WITH decision.candidate_key AS candidate_key, decision
|
|
653
|
+
ORDER BY decision.updated_at DESC, decision.id DESC
|
|
654
|
+
WITH candidate_key, collect(decision)[0] AS latest_decision
|
|
655
|
+
WHERE latest_decision.outcome = 'deferred'
|
|
656
|
+
RETURN collect(candidate_key) AS candidate_keys
|
|
612
657
|
'''
|
|
@@ -5,6 +5,7 @@ from pydantic import Field, model_validator
|
|
|
5
5
|
|
|
6
6
|
from .models import (
|
|
7
7
|
ConfirmationStatus,
|
|
8
|
+
EpistemicQuadrant,
|
|
8
9
|
KnowledgeFeedbackKind,
|
|
9
10
|
KnowledgeItemKind,
|
|
10
11
|
PersonalProfileAspect,
|
|
@@ -22,10 +23,11 @@ KnowledgeReviewScope = Literal[
|
|
|
22
23
|
KnowledgeReviewStatus = Literal['active', 'paused', 'completed']
|
|
23
24
|
KnowledgeReviewDisposition = Literal['paused', 'completed']
|
|
24
25
|
KnowledgeReviewOutcome = Literal[
|
|
25
|
-
'confirmed', 'updated', 'invalidated', '
|
|
26
|
+
'confirmed', 'updated', 'invalidated', 'deferred', 'delegated_to_ai'
|
|
26
27
|
]
|
|
27
28
|
KnowledgeReviewReason = Literal[
|
|
28
29
|
'changed_since_last',
|
|
30
|
+
'deferred_from_previous',
|
|
29
31
|
'conflict_or_attention',
|
|
30
32
|
'low_weight',
|
|
31
33
|
'repeated_cross_session',
|
|
@@ -68,11 +70,17 @@ class KnowledgeReviewCandidate(StrictModel):
|
|
|
68
70
|
item_kind: KnowledgeItemKind
|
|
69
71
|
title: str
|
|
70
72
|
content: str
|
|
73
|
+
# Keep the decision-critical ranking context near the front of the wire
|
|
74
|
+
# payload. Older MCP clients may project a bounded prefix of the Provider
|
|
75
|
+
# response, and the user must still see why a candidate was selected.
|
|
76
|
+
priority: int = Field(ge=1, le=4)
|
|
77
|
+
reasons: list[KnowledgeReviewReason]
|
|
78
|
+
confirmation_status: ConfirmationStatus
|
|
79
|
+
current_quadrant: EpistemicQuadrant
|
|
80
|
+
project_ids: list[str] = Field(default_factory=list)
|
|
71
81
|
profile_aspect: PersonalProfileAspect | None = None
|
|
72
82
|
preference_scope: PreferenceScope | None = None
|
|
73
83
|
preference_project_id: str | None = None
|
|
74
|
-
project_ids: list[str] = Field(default_factory=list)
|
|
75
|
-
confirmation_status: ConfirmationStatus
|
|
76
84
|
utility_score: float = Field(ge=0, le=1)
|
|
77
85
|
confidence_score: float = Field(ge=0, le=1)
|
|
78
86
|
qualified_use_count: int = Field(ge=0)
|
|
@@ -82,8 +90,6 @@ class KnowledgeReviewCandidate(StrictModel):
|
|
|
82
90
|
last_feedback_kind: KnowledgeFeedbackKind | None = None
|
|
83
91
|
distinct_session_count: int = Field(ge=0)
|
|
84
92
|
changed_at: datetime | None = None
|
|
85
|
-
priority: int = Field(ge=1, le=4)
|
|
86
|
-
reasons: list[KnowledgeReviewReason]
|
|
87
93
|
|
|
88
94
|
|
|
89
95
|
class KnowledgeReviewCandidateRequest(StrictModel):
|
|
@@ -10,6 +10,7 @@ from .provider_values import (
|
|
|
10
10
|
json_object,
|
|
11
11
|
native_datetime as _native_datetime,
|
|
12
12
|
normalized_text as _normalized,
|
|
13
|
+
preference_qualifiers,
|
|
13
14
|
)
|
|
14
15
|
from .search_projection import (
|
|
15
16
|
effective_confirmation_status,
|
|
@@ -255,6 +256,8 @@ async def personal_project_scopes(
|
|
|
255
256
|
WHERE all(
|
|
256
257
|
relation IN relationships(path)
|
|
257
258
|
WHERE relation.relation_type IN ['PART_OF', 'USES_KNOWLEDGE_FROM']
|
|
259
|
+
AND relation.status = 'active'
|
|
260
|
+
AND relation.confirmation_authority = 'human_review'
|
|
258
261
|
)
|
|
259
262
|
AND EXISTS {
|
|
260
263
|
MATCH (space)-[:CONTAINS_PROJECT]->(source)
|
|
@@ -634,6 +637,14 @@ def _dedupe_ranked_entities(ranked, active_project_id):
|
|
|
634
637
|
|
|
635
638
|
def _entity_search_result(record, score: float, space_id: str) -> EntitySearchResult:
|
|
636
639
|
value = dict(record)
|
|
640
|
+
attributes = json_object(value.get('attributes_json'))
|
|
641
|
+
value['preference_key'] = (
|
|
642
|
+
attributes.get('preferenceKey')
|
|
643
|
+
or attributes.get('preference_key')
|
|
644
|
+
or value.get('key')
|
|
645
|
+
or value.get('id')
|
|
646
|
+
)
|
|
647
|
+
value['preference_qualifiers'] = preference_qualifiers(attributes)
|
|
637
648
|
basis = json_object(value.pop('confirmation_basis_json', None))
|
|
638
649
|
source_references = value.pop('source_references', [])
|
|
639
650
|
created_at = _native_datetime(value.pop('created_at', None))
|
|
@@ -163,8 +163,6 @@ def _validate_inheritance(
|
|
|
163
163
|
raise ValueError(
|
|
164
164
|
'inherited project ids are only valid for selected_projects inheritance'
|
|
165
165
|
)
|
|
166
|
-
if profile_aspect and mode != 'local_only':
|
|
167
|
-
raise ValueError('personal preferences cannot inherit across projects')
|
|
168
166
|
|
|
169
167
|
|
|
170
168
|
class ConfirmationActor(StrictModel):
|
|
@@ -772,6 +770,8 @@ class EntitySearchResult(StrictModel):
|
|
|
772
770
|
preference_scope: PreferenceScope | None = None
|
|
773
771
|
preference_project_id: str | None = None
|
|
774
772
|
key: str | None = None
|
|
773
|
+
preference_key: str | None = None
|
|
774
|
+
preference_qualifiers: dict[str, Any] = Field(default_factory=dict)
|
|
775
775
|
defined_project_id: str | None = None
|
|
776
776
|
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
777
777
|
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
@@ -819,6 +819,8 @@ class FactResult(StrictModel):
|
|
|
819
819
|
preference_scope: PreferenceScope | None = None
|
|
820
820
|
preference_project_id: str | None = None
|
|
821
821
|
key: str | None = None
|
|
822
|
+
preference_key: str | None = None
|
|
823
|
+
preference_qualifiers: dict[str, Any] = Field(default_factory=dict)
|
|
822
824
|
defined_project_id: str | None = None
|
|
823
825
|
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
824
826
|
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
@@ -860,9 +862,18 @@ class CollaborationPreferenceItem(StrictModel):
|
|
|
860
862
|
preference_scope: PreferenceScope
|
|
861
863
|
preference_project_id: str | None = None
|
|
862
864
|
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
865
|
+
weight: float | None = Field(default=None, ge=0, le=1, allow_inf_nan=False)
|
|
866
|
+
reason: str | None = None
|
|
867
|
+
confirmation_basis: ConfirmationBasis
|
|
868
|
+
reasoning_summary: str | None = None
|
|
869
|
+
inheritance_mode: KnowledgeInheritanceMode = 'local_only'
|
|
870
|
+
inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
863
871
|
confirmation_status: Literal['confirmed', 'agent_confirmed']
|
|
864
872
|
confirmed_at: datetime
|
|
865
873
|
created_at: datetime | None = None
|
|
874
|
+
scope_distance: int = Field(default=0, ge=0, le=2)
|
|
875
|
+
inherited_from_project_id: str | None = None
|
|
876
|
+
scope_path: list[str] = Field(default_factory=list, max_length=3)
|
|
866
877
|
|
|
867
878
|
|
|
868
879
|
class CollaborationPreferenceConflict(StrictModel):
|
|
@@ -880,6 +891,8 @@ class CollaborationContextResult(StrictModel):
|
|
|
880
891
|
effective_preferences: list[CollaborationPreferenceItem] = Field(default_factory=list)
|
|
881
892
|
conflicts: list[CollaborationPreferenceConflict] = Field(default_factory=list)
|
|
882
893
|
overridden_global_ids: list[str] = Field(default_factory=list)
|
|
894
|
+
overridden_inherited_ids: list[str] = Field(default_factory=list)
|
|
895
|
+
overridden_lower_authority_ids: list[str] = Field(default_factory=list)
|
|
883
896
|
truncated: bool = False
|
|
884
897
|
|
|
885
898
|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, model_validator
|
|
5
|
+
|
|
6
|
+
from .models import (
|
|
7
|
+
ConfirmationBasis,
|
|
8
|
+
ConfirmationStatus,
|
|
9
|
+
KnowledgeItemKind,
|
|
10
|
+
PersonalProfileAspect,
|
|
11
|
+
StrictModel,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
PersonalGlobalPreferenceTargetScope = Literal[
|
|
15
|
+
'parent_project',
|
|
16
|
+
'personal_global',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PersonalGlobalPreferenceSourceRef(StrictModel):
|
|
21
|
+
item_id: str = Field(min_length=1, max_length=256)
|
|
22
|
+
item_kind: KnowledgeItemKind
|
|
23
|
+
project_id: str = Field(min_length=1, max_length=128)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PersonalGlobalPreferenceSourceSnapshot(
|
|
27
|
+
PersonalGlobalPreferenceSourceRef
|
|
28
|
+
):
|
|
29
|
+
key: str = Field(min_length=1, max_length=256)
|
|
30
|
+
preference_key: str = Field(min_length=1, max_length=512)
|
|
31
|
+
preference_qualifiers: dict[str, Any] = Field(default_factory=dict)
|
|
32
|
+
title: str = Field(min_length=1, max_length=512)
|
|
33
|
+
instruction: str = Field(max_length=8192)
|
|
34
|
+
profile_aspect: PersonalProfileAspect
|
|
35
|
+
confirmation_status: Literal['confirmed', 'agent_confirmed']
|
|
36
|
+
confirmation_basis: ConfirmationBasis
|
|
37
|
+
human_change_version: int = Field(default=0, ge=0)
|
|
38
|
+
usage_generation: int = Field(default=1, ge=1)
|
|
39
|
+
last_human_changed_at: datetime | None = None
|
|
40
|
+
negative_evidence_count: int = Field(default=0, ge=0)
|
|
41
|
+
requires_attention: bool = False
|
|
42
|
+
last_feedback_at: datetime | None = None
|
|
43
|
+
source_uris: list[str] = Field(default_factory=list, max_length=20)
|
|
44
|
+
stored_confirmation_basis_json: str = Field(
|
|
45
|
+
default='{}',
|
|
46
|
+
exclude=True,
|
|
47
|
+
repr=False,
|
|
48
|
+
)
|
|
49
|
+
stored_attributes_json: str = Field(default='{}', exclude=True, repr=False)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PersonalGlobalPreferenceEligibleTargetScope(StrictModel):
|
|
53
|
+
target_scope: PersonalGlobalPreferenceTargetScope
|
|
54
|
+
target_project_id: str | None = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
min_length=1,
|
|
57
|
+
max_length=128,
|
|
58
|
+
)
|
|
59
|
+
max_distance: int | None = Field(default=None, ge=1, le=2)
|
|
60
|
+
|
|
61
|
+
@model_validator(mode='after')
|
|
62
|
+
def validate_target(self):
|
|
63
|
+
if self.target_scope == 'parent_project' and not self.target_project_id:
|
|
64
|
+
raise ValueError('parent_project target requires target_project_id')
|
|
65
|
+
if self.target_scope == 'personal_global' and self.target_project_id:
|
|
66
|
+
raise ValueError('personal_global target cannot set target_project_id')
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PersonalGlobalPreferenceScopeOptionsRequest(StrictModel):
|
|
71
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
72
|
+
source_items: list[PersonalGlobalPreferenceSourceRef] = Field(
|
|
73
|
+
min_length=2,
|
|
74
|
+
max_length=32,
|
|
75
|
+
)
|
|
76
|
+
preference_key: str = Field(min_length=1, max_length=512)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PersonalGlobalPreferenceScopeOptions(StrictModel):
|
|
80
|
+
personal_space_id: str
|
|
81
|
+
candidate_id: str
|
|
82
|
+
candidate_version: str
|
|
83
|
+
preference_key: str
|
|
84
|
+
source_snapshots: list[PersonalGlobalPreferenceSourceSnapshot]
|
|
85
|
+
eligible_target_scopes: list[PersonalGlobalPreferenceEligibleTargetScope]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PersonalGlobalPreferenceDecisionIntent(StrictModel):
|
|
89
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
90
|
+
candidate_version: str = Field(pattern=r'^v1:[a-f0-9]{24}$')
|
|
91
|
+
decision_revision: int = Field(ge=0)
|
|
92
|
+
source_items: list[PersonalGlobalPreferenceSourceRef] = Field(
|
|
93
|
+
min_length=2,
|
|
94
|
+
max_length=32,
|
|
95
|
+
)
|
|
96
|
+
preference_key: str = Field(min_length=1, max_length=512)
|
|
97
|
+
target_scope: PersonalGlobalPreferenceTargetScope
|
|
98
|
+
target_project_id: str | None = Field(
|
|
99
|
+
min_length=1,
|
|
100
|
+
max_length=128,
|
|
101
|
+
)
|
|
102
|
+
decision: Literal['approve', 'reject']
|
|
103
|
+
global_title: str | None = Field(default=None, min_length=1, max_length=512)
|
|
104
|
+
global_instruction: str | None = Field(
|
|
105
|
+
default=None,
|
|
106
|
+
min_length=1,
|
|
107
|
+
max_length=8192,
|
|
108
|
+
)
|
|
109
|
+
profile_aspect: PersonalProfileAspect | None = None
|
|
110
|
+
human_confirmation_reason: str = Field(min_length=1, max_length=4096)
|
|
111
|
+
confirmed_at: datetime
|
|
112
|
+
session_id: str = Field(min_length=1, max_length=256)
|
|
113
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
114
|
+
|
|
115
|
+
@model_validator(mode='after')
|
|
116
|
+
def validate_decision(self):
|
|
117
|
+
item_keys = {
|
|
118
|
+
(item.item_kind, item.item_id)
|
|
119
|
+
for item in self.source_items
|
|
120
|
+
}
|
|
121
|
+
project_ids = {item.project_id for item in self.source_items}
|
|
122
|
+
if len(item_keys) != len(self.source_items):
|
|
123
|
+
raise ValueError('source preference items must be unique')
|
|
124
|
+
if len(project_ids) != len(self.source_items):
|
|
125
|
+
raise ValueError(
|
|
126
|
+
'source preferences must come from distinct projects'
|
|
127
|
+
)
|
|
128
|
+
if self.target_scope == 'parent_project' and not self.target_project_id:
|
|
129
|
+
raise ValueError('parent_project target requires target_project_id')
|
|
130
|
+
if self.target_scope == 'personal_global' and self.target_project_id:
|
|
131
|
+
raise ValueError('personal_global target cannot set target_project_id')
|
|
132
|
+
if self.decision == 'approve':
|
|
133
|
+
if not (
|
|
134
|
+
self.global_title
|
|
135
|
+
and self.global_instruction
|
|
136
|
+
and self.profile_aspect
|
|
137
|
+
):
|
|
138
|
+
raise ValueError(
|
|
139
|
+
'approve requires a complete global preference assertion'
|
|
140
|
+
)
|
|
141
|
+
elif any((
|
|
142
|
+
self.global_title,
|
|
143
|
+
self.global_instruction,
|
|
144
|
+
self.profile_aspect,
|
|
145
|
+
)):
|
|
146
|
+
raise ValueError(
|
|
147
|
+
'reject cannot include a global preference assertion'
|
|
148
|
+
)
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class PersonalGlobalPreferenceDecisionApply(
|
|
153
|
+
PersonalGlobalPreferenceDecisionIntent
|
|
154
|
+
):
|
|
155
|
+
approval_token: str = Field(min_length=32, max_length=512)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class PersonalGlobalPreferenceDecisionInspection(StrictModel):
|
|
159
|
+
status: Literal['human_review_required']
|
|
160
|
+
personal_space_id: str
|
|
161
|
+
candidate_id: str
|
|
162
|
+
candidate_version: str
|
|
163
|
+
decision_revision: int = Field(ge=0)
|
|
164
|
+
decision: Literal['approve', 'reject']
|
|
165
|
+
preference_key: str
|
|
166
|
+
target_scope: PersonalGlobalPreferenceTargetScope
|
|
167
|
+
target_project_id: str | None = None
|
|
168
|
+
eligible_target_scopes: list[PersonalGlobalPreferenceEligibleTargetScope]
|
|
169
|
+
payload_fingerprint: str
|
|
170
|
+
source_snapshots: list[PersonalGlobalPreferenceSourceSnapshot]
|
|
171
|
+
candidate_binding_verified: bool = True
|
|
172
|
+
original_sources_will_remain_unchanged: bool = True
|
|
173
|
+
scope_apply_performed: bool = False
|
|
174
|
+
approval_token_issued: bool = False
|
|
175
|
+
required_action: str
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class PersonalGlobalPreferenceDecisionPreview(StrictModel):
|
|
179
|
+
preview_id: str
|
|
180
|
+
candidate_id: str
|
|
181
|
+
candidate_version: str
|
|
182
|
+
decision_revision: int = Field(ge=0)
|
|
183
|
+
target_scope: PersonalGlobalPreferenceTargetScope
|
|
184
|
+
target_project_id: str | None = None
|
|
185
|
+
eligible_target_scopes: list[PersonalGlobalPreferenceEligibleTargetScope]
|
|
186
|
+
payload_fingerprint: str
|
|
187
|
+
approval_token: str
|
|
188
|
+
expires_at: datetime
|
|
189
|
+
source_snapshots: list[PersonalGlobalPreferenceSourceSnapshot]
|
|
190
|
+
candidate_binding_verified: bool = True
|
|
191
|
+
original_sources_will_remain_unchanged: bool = True
|
|
192
|
+
scope_apply_performed: bool = False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class PersonalGlobalPreferenceCandidateVersionRef(StrictModel):
|
|
196
|
+
candidate_id: str = Field(
|
|
197
|
+
pattern=r'^personal-global-[a-f0-9]{20}$',
|
|
198
|
+
)
|
|
199
|
+
candidate_version: str = Field(pattern=r'^v1:[a-f0-9]{24}$')
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class PersonalGlobalPreferenceDecisionStatusRequest(StrictModel):
|
|
203
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
204
|
+
candidates: list[PersonalGlobalPreferenceCandidateVersionRef] = Field(
|
|
205
|
+
min_length=1,
|
|
206
|
+
max_length=100,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class PersonalGlobalPreferenceDecisionRecord(StrictModel):
|
|
211
|
+
decision_event_id: str
|
|
212
|
+
candidate_id: str
|
|
213
|
+
candidate_version: str
|
|
214
|
+
decision_revision: int = Field(ge=1)
|
|
215
|
+
decision: Literal['approved', 'rejected']
|
|
216
|
+
target_scope: PersonalGlobalPreferenceTargetScope
|
|
217
|
+
target_project_id: str | None = None
|
|
218
|
+
global_assertion_id: str | None = None
|
|
219
|
+
global_assertion_active: bool
|
|
220
|
+
decision_sequence: int = Field(ge=1)
|
|
221
|
+
decided_at: datetime
|
|
222
|
+
human_confirmation_reason: str
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class PersonalGlobalPreferenceDecisionRevision(StrictModel):
|
|
226
|
+
candidate_id: str
|
|
227
|
+
decision_revision: int = Field(ge=0)
|
|
228
|
+
current_candidate_version: str | None = None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class PersonalGlobalPreferenceDecisionStatusResult(StrictModel):
|
|
232
|
+
personal_space_id: str
|
|
233
|
+
decisions: list[PersonalGlobalPreferenceDecisionRecord]
|
|
234
|
+
revisions: list[PersonalGlobalPreferenceDecisionRevision]
|