fuli-context 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +3 -3
  2. package/dist/web/assets/{AboutPage-DfnZo7Lo.js → AboutPage-qeO_vm0T.js} +1 -1
  3. package/dist/web/assets/{ConnectionsPage-BloaSKmt.js → ConnectionsPage-DO0GR9Xg.js} +1 -1
  4. package/dist/web/assets/{KnowledgeInspector-QvNpv0_k.js → KnowledgeInspector-DEW5lA1m.js} +1 -1
  5. package/dist/web/assets/{KnowledgeOrganizerPage-DKq1jVD5.js → KnowledgeOrganizerPage-BkM568Ld.js} +1 -1
  6. package/dist/web/assets/KnowledgePage-BAT4aZIm.js +1 -0
  7. package/dist/web/assets/{KnowledgeWorkspace-Czq6aZ7b.js → KnowledgeWorkspace-DyGtBz--.js} +1 -1
  8. package/dist/web/assets/OverviewPage-CmCcDCAs.js +1 -0
  9. package/dist/web/assets/{PersonalProfilePage-tVChM9ik.js → PersonalProfilePage-C42mwKN8.js} +1 -1
  10. package/dist/web/assets/PersonalProjectsPage-BwaXR62K.js +1 -0
  11. package/dist/web/assets/{PublicProjectsPage-BPJSeREN.js → PublicProjectsPage-egKBulOq.js} +1 -1
  12. package/dist/web/assets/{ReviewPage-B7EN817i.js → ReviewPage-B3vnZ6gn.js} +1 -1
  13. package/dist/web/assets/{SearchableSelect-ClLN0rIb.js → SearchableSelect-ih69qbcS.js} +1 -1
  14. package/dist/web/assets/{SettingsPage-Chd_B3eH.js → SettingsPage-oLE8mNs5.js} +1 -1
  15. package/dist/web/assets/{console-CbDsKrsO.js → console-DE40hXGC.js} +1 -1
  16. package/dist/web/assets/{i18n-BQcns1PH.js → i18n-CDxBs1sz.js} +1 -1
  17. package/dist/web/assets/{identity-B8K4VYg6.js → identity-BCxb1mcy.js} +1 -1
  18. package/dist/web/assets/{index-CB8Xmsc2.js → index-BzhNfzU_.js} +2 -2
  19. package/dist/web/assets/index-CCOGfZzE.css +1 -0
  20. package/dist/web/assets/{model-eF52GgPF.js → model-CCMdt10Q.js} +1 -1
  21. package/dist/web/assets/{paths-hwz-Ac1g.js → paths-DwHeiGMT.js} +1 -1
  22. package/dist/web/assets/{useMinimumLoadingDisplay-wCgGDmHg.js → useMinimumLoadingDisplay-HdI7XAmu.js} +1 -1
  23. package/dist/web/assets/{vue-router-BsEp_XwI.js → vue-router-Drfl1F8i.js} +1 -1
  24. package/dist/web/index.html +6 -6
  25. package/graph-provider/fuli_graph/knowledge_review.py +49 -4
  26. package/graph-provider/fuli_graph/knowledge_review_models.py +11 -5
  27. package/graph-provider/pyproject.toml +1 -1
  28. package/npm-shrinkwrap.json +2 -2
  29. package/package.json +1 -1
  30. package/skills/flreview/SKILL.md +67 -40
  31. package/skills/flreview/agents/openai.yaml +2 -2
  32. package/src/agent-tools/graph-definitions.js +4 -4
  33. package/src/graphiti/federated-application.js +2 -1
  34. package/src/mcp/create-mcp-server.js +3 -1
  35. package/README.en.md +0 -5
  36. package/dist/web/assets/KnowledgePage-BWL0jT01.js +0 -1
  37. package/dist/web/assets/OverviewPage-5sJoO1Gh.js +0 -1
  38. package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +0 -1
  39. package/dist/web/assets/index-CZeuPh-U.css +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
- RETURN knowledge_keys +
609
- collect(DISTINCT preference.left_item_kind + ':' + preference.left_item_id) +
610
- collect(DISTINCT preference.right_item_kind + ':' + preference.right_item_id)
611
- AS candidate_keys
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', 'skipped', 'deferred'
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):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.7.2"
3
+ version = "0.7.3"
4
4
  description = "Graphiti-backed personal and shared project provider for Fuli."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.7.2",
9
+ "version": "0.7.3",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@hono/node-server": "2.0.12",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Local-first context graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -5,69 +5,96 @@ description: Use when the user explicitly enters /flreview or explicitly asks to
5
5
 
6
6
  # Fuli Knowledge Review
7
7
 
8
- Review personal preferences and local personal-project knowledge with the user as the authority. Never silently rewrite, invalidate, promote, or globalize knowledge.
8
+ The invoking Agent owns each review session. FULI supplies durable knowledge, deterministic candidate ranking, and write tools; the Agent generates a fresh interactive review page for this invocation. There is no permanent **知识回顾** tab to open.
9
9
 
10
- ## Select Scope
10
+ ## Start The Session
11
11
 
12
- For every `/flreview`, first offer exactly these top-level choices:
12
+ For every exact `/flreview` invocation or explicit request to run the Fuli knowledge review:
13
13
 
14
- 1. **全部** — review personal-global preferences plus every local personal project. Each project includes its project knowledge and project-scoped personal preferences.
15
- 2. **个人偏好** — then choose **全局偏好** or the preferences for **某一个本地个人项目**.
16
- 3. **个人项目** — then choose **一个本地个人项目** or **全部本地个人项目**. Each selected project includes both project knowledge and project-scoped personal preferences.
14
+ 1. Use `list_knowledge_spaces` to identify the active personal space, then call `list_personal_projects` so every project choice uses a real local personal project.
15
+ 2. Use the `visualize` Skill to render an inline setup page in the current task. Collect scope and review depth in that page, not as serial chat questions. Ask patience first. If the user selects **完全没耐心**, hide mood, available-time, and token-budget fields and use a small highest-priority batch. Otherwise collect mood, available time, and token comfort together on the same setup page. These answers control only batch size and explanation depth, never Provider ranking. If the user's request already gives an exact scope and depth, skip setup.
16
+ 3. On the setup page's explicit start action, call `start_knowledge_review` with the selected scope. Then call `list_knowledge_review_candidates` with a bounded page size matching the selected depth.
17
+ 4. Generate all candidates from that batch on the same interactive page. Show all candidates at once; do not reveal them one at a time.
17
18
 
18
- Use `list_personal_projects` only when a branch needs a project choice. Never include subscribed or public projects. Do not infer the current directory as the selected project and do not invent another command variant.
19
+ The setup scope tree is:
19
20
 
20
- ## Calibrate Depth
21
+ - **全部** — global preferences plus every local personal project's knowledge and project-scoped preferences.
22
+ - **个人偏好** — global preferences or one selected local personal project's preferences.
23
+ - **个人项目** — one selected local personal project or all local personal projects. A project includes its knowledge and project-scoped preferences.
21
24
 
22
- After scope selection, first ask how much patience the user has for this review.
25
+ Never infer the current directory as the selected project. Never include subscribed or public projects.
23
26
 
24
- 交互硬规则:先询问用户有没有耐心;如果用户表示完全没耐心,不要再询问心情、时间或 token,只问少量最高优先级的关键问题。
27
+ A bounded page limit is a batch size, not a fixed total-question cap.
25
28
 
26
- - If the user says they are **完全没耐心**, immediately take the short path. Do not ask about mood, available time, or token budget. Ask only a small number of the highest-priority critical questions, one at a time, and make stopping easy.
27
- - Otherwise ask, concisely, about current mood, available time, and whether token budget is comfortable. Use the answers plus explicit requests such as “少问一点”, “多问一点”, or “直接开始” to choose page size and explanation depth.
28
- - Do not impose a fixed total-question cap. A tool page limit controls only one retrieval batch, not the length of the review.
29
+ ## Generated Review Page
29
30
 
30
- ## Start Or Resume
31
+ Follow the `visualize` Skill's inline-artifact rules. The artifact must not use `fetch`, XHR, WebSocket, or a direct FULI API. Every submitted action must call:
31
32
 
32
- Call `start_knowledge_review` with the exact selected scope and project ID when required. Reuse a returned active or paused run. Then call `list_knowledge_review_candidates` in bounded pages.
33
+ ```js
34
+ await window.openai.sendFollowUpMessage({ prompt, title })
35
+ ```
33
36
 
34
- If there is no previous completed review for that exact scope, review all historical in-scope knowledge. Otherwise present candidates in this order:
37
+ The setup action envelope contains only `action`, `personal_space_id`, scope, optional project, and depth inputs. It must not invent a `review_id`, `candidate_key`, `item_id`, or `item_kind`. Every candidate action envelope contains `personal_space_id`, `review_id`, `candidate_key`, `item_id`, and `item_kind`, plus only the fields needed for that action. Treat a valid envelope as a continuation of the active review, not as a new `/flreview` invocation.
35
38
 
36
- 没有上次完成记录时扫描全部历史。
39
+ Each candidate card shows its content, current scope and source projects, confirmation state, `current_quadrant`, selection reasons, relevant evidence counts, utility, and confidence. Provider order is authoritative; do not invent a second score. Render these actions:
37
40
 
38
- 1. Knowledge created or changed since the last completed review.
39
- 2. Current or older conflicts, negative evidence, or items marked `requires_attention`.
40
- 3. Low-weight knowledge, using the Provider's returned utility/confidence reasons.
41
- 4. The same durable pattern repeated across multiple sessions that may deserve a personal-global attribute.
41
+ - **确认保留**
42
+ - **修改**
43
+ - **调整范围**
44
+ - **失效**
45
+ - **稍后处理**
46
+ - **交给 AI 判断**
42
47
 
43
- Treat ranking thresholds as Provider policy, not user requirements. Do not calculate a second conflicting score in the Agent.
48
+ Do not present the review as one candidate at a time in chat. Chat may contain only a concise status and the generated interactive artifact.
44
49
 
45
- ## Review One Candidate
50
+ ## Action Semantics
46
51
 
47
- Show the content, scope, confirmation state, relevant evidence summary, weights, and why the item was selected. Ask for one outcome:
52
+ Do not mutate knowledge until the user submits an explicit visualization action. After any mutation, call `record_knowledge_review_progress` only after the write succeeds. If progress recording fails after a successful write, retry only progress; never repeat the mutation.
48
53
 
49
- - **确认保留** — keep it; if it is pending and the user explicitly confirms the content and classification, use the normal Fuli confirmation operation first.
50
- - **修改** — obtain the exact replacement text, apply `revise_personal_knowledge`, then record progress.
51
- - **失效** — reconfirm the exact target, apply `revise_personal_knowledge` with `invalidate`, then record progress.
52
- - **跳过** — do not ask again in this run; it may return in a later review.
53
- - **稍后处理** — do not ask again in this run; intentionally return it in the next review.
54
+ ### 确认保留
54
55
 
55
- After a successful mutation—or immediately for keep, skip, or later—call `record_knowledge_review_progress`. Never record a mutation outcome before its write succeeds.
56
+ If the candidate is already confirmed, record `confirmed`. If it is pending, first call `revise_personal_knowledge` with `action: confirm`, an auditable user confirmation basis, and no content or taxonomy changes; then record `confirmed`.
56
57
 
57
- For repeated cross-session patterns, ask whether the user wants a personal-global attribute, a project-scoped preference, or no promotion. Never auto-promote. Use `set_personal_preference_scope` only after explicit confirmation and only for an actual preference item.
58
+ ### 修改
58
59
 
59
- ## Pause Or Complete
60
+ Collect the exact replacement content and a reason in the card. Call `revise_personal_knowledge` with `action: update`, then record `updated`.
60
61
 
61
- If the user stops before the selected queue is exhausted, call `finish_knowledge_review` with `paused`. Resume that run next time. Only call it with `completed` after the user explicitly finishes the selected scope or no candidates remain.
62
+ ### 调整范围
62
63
 
63
- Only a completed run advances the last-completed review time. Paused runs do not. A “稍后处理” item returns in the next run; a “跳过” item stays out only for the current run.
64
+ - For a global preference with exactly one source project, offer a one-click move to that source project. With multiple source projects, require a project selection. Call `set_personal_preference_scope` with `scope: project` and the selected source project.
65
+ - For a project-scoped preference, offer promotion to global and call `set_personal_preference_scope` with `scope: global`.
66
+ - Ordinary project knowledge is not a personal preference. Never offer to turn ordinary project knowledge into a global preference.
64
67
 
65
- 只有完成回顾才推进上次回顾时间;暂停不能推进。
68
+ After a successful scope write, record `updated`. Never change scope automatically.
69
+
70
+ ### 失效
71
+
72
+ Offer these preset reasons and a custom reason field:
73
+
74
+ - 不应该沉淀为一条复利知识
75
+ - 不知所云
76
+ - 过期了
77
+ - 只在当时生效
78
+
79
+ Selecting a preset or entering a non-empty custom reason is sufficient authorization. There is no second confirmation and no confirmation checkbox. Call `revise_personal_knowledge` with `action: invalidate`, then record `invalidated` with the exact reason.
80
+
81
+ ### 稍后处理
82
+
83
+ Do not mutate the knowledge. Record `deferred` and remove it from the current page. FULI carries it into the next review as `deferred_from_previous` even if no other ranking threshold still matches.
84
+
85
+ ### 交给 AI 判断
86
+
87
+ This action means the user cannot responsibly classify the item now. Call `revise_personal_knowledge` with `action: update`, `currentQuadrant: unknown_unknown`, and a short `reasoningSummary` stating that the user delegated judgment during knowledge review. Never send `originQuadrant`: discovery classification is immutable evidence. The required order is `revise_personal_knowledge` before `record_knowledge_review_progress`. After that write succeeds, record `delegated_to_ai` and remove the item from the current page.
88
+
89
+ ## Continue, Pause, Or Complete
90
+
91
+ After each successful action, refresh the current review's remaining candidates and regenerate the interactive page. Load the next bounded batch when the current batch is exhausted.
92
+
93
+ Call `finish_knowledge_review` with `paused` when the user explicitly pauses. Call it with `completed` only when no candidates remain or the user explicitly finishes the selected scope. Only completion advances the last-completed watermark.
66
94
 
67
95
  ## Guardrails
68
96
 
69
- - Keep the user authoritative. Explain conflicts; do not resolve them silently.
70
- - Preserve original evidence and revision history.
71
- - Never include raw transcripts, credentials, secrets, temporary logs, or unsupported inference.
72
- - Do not turn repeated project facts into global personal attributes without explicit approval.
73
- - If a required Fuli review tool is unavailable, state that the installed integration needs updating; do not simulate persistence in prose.
97
+ - FULI-saved knowledge is the only candidate source. Never fabricate, mock, or supplement candidates from ordinary search.
98
+ - Keep original evidence and revision history intact.
99
+ - Never silently resolve conflicts, invalidate knowledge, or change scope.
100
+ - If a required FULI or visualization capability is unavailable, report the exact missing capability and stop safely.
@@ -1,6 +1,6 @@
1
1
  interface:
2
2
  display_name: "Fuli Knowledge Review"
3
- short_description: "Review personal preferences and local project knowledge"
4
- default_prompt: "Use $flreview to review my Fuli knowledge."
3
+ short_description: "Generate an interactive FULI knowledge review"
4
+ default_prompt: "Use $flreview to generate my interactive FULI knowledge review."
5
5
  policy:
6
6
  allow_implicit_invocation: true
@@ -514,7 +514,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
514
514
  {
515
515
  name: 'record_knowledge_review_progress',
516
516
  title: 'WRITE · Record one review outcome',
517
- description: 'Record one user-authorized candidate outcome after any required knowledge mutation succeeds. confirmed keeps the item, updated/invalidated follow successful writes, skipped suppresses it only in this run, and deferred intentionally returns it in the next run.',
517
+ description: 'Record one user-authorized candidate outcome after any required knowledge mutation succeeds. confirmed keeps the item, updated/invalidated follow successful writes, deferred carries it into the next review, and delegated_to_ai follows a successful current-quadrant change to unknown_unknown.',
518
518
  inputSchema: objectSchema({
519
519
  personalSpaceId: id,
520
520
  reviewId: id,
@@ -523,7 +523,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
523
523
  pattern: '^(entity|relationship):.+'
524
524
  },
525
525
  outcome: enumSchema([
526
- 'confirmed', 'updated', 'invalidated', 'skipped', 'deferred'
526
+ 'confirmed', 'updated', 'invalidated', 'deferred', 'delegated_to_ai'
527
527
  ]),
528
528
  note: nullableStringSchema()
529
529
  }, ['personalSpaceId', 'reviewId', 'candidateKey', 'outcome'])
@@ -540,13 +540,13 @@ export const GRAPH_TOOL_DEFINITIONS = [
540
540
  },
541
541
  {
542
542
  name: 'revise_personal_knowledge',
543
- description: 'Correct, invalidate, or restore one personal entity or relationship while preserving revision history and original evidence. This is the same personal-only operation used by the management UI.',
543
+ description: 'Confirm, correct, invalidate, or restore one personal entity or relationship while preserving revision history and original evidence. This is the same personal-only operation used by the management UI.',
544
544
  inputSchema: objectSchema({
545
545
  personalSpaceId: id,
546
546
  personalProjectId: nullableStringSchema(),
547
547
  itemKind: knowledgeItemKind,
548
548
  itemId: id,
549
- action: enumSchema(['update', 'invalidate', 'restore']),
549
+ action: enumSchema(['confirm', 'update', 'invalidate', 'restore']),
550
550
  reason: shortText,
551
551
  name: nullableStringSchema(),
552
552
  summary: nullableStringSchema(),
@@ -789,7 +789,7 @@ export class FederatedGraphApplication {
789
789
  }
790
790
 
791
791
  async listKnowledgeSpaces() {
792
- const [personalSpaces, personalProjects, subscriptions, ...workspaceSpaces] = await Promise.all([
792
+ const [availablePersonalSpaces, personalProjects, subscriptions, ...workspaceSpaces] = await Promise.all([
793
793
  this.personal.listSpaces(),
794
794
  this.personal.listPersonalProjects(this.config.personal.spaceId),
795
795
  this.personal.listSubscriptions(this.config.personal.spaceId),
@@ -799,6 +799,7 @@ export class FederatedGraphApplication {
799
799
  spaces: await safely(() => workspace.client.listSpaces(), [])
800
800
  }))
801
801
  ]);
802
+ const personalSpaces = availablePersonalSpaces.filter(({ id }) => id === this.config.personal.spaceId);
802
803
  return {
803
804
  activePersonalSpaceId: this.config.personal.spaceId,
804
805
  personalSpaces,
@@ -22,7 +22,9 @@ const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
22
22
  search_knowledge_graph: 32 * 1024,
23
23
  search_connected_knowledge: 64 * 1024,
24
24
  search_current_project_knowledge: 64 * 1024,
25
- discover_common_knowledge_candidates: 32 * 1024
25
+ discover_common_knowledge_candidates: 32 * 1024,
26
+ list_personal_projects: 32 * 1024,
27
+ list_knowledge_review_candidates: 32 * 1024
26
28
  });
27
29
 
28
30
  export function createMcpServer(app) {
package/README.en.md DELETED
@@ -1,5 +0,0 @@
1
- # Fuli
2
-
3
- The English README is now the default: [README.md](README.md).
4
-
5
- [简体中文](README.zh-CN.md)
@@ -1 +0,0 @@
1
- import{h as e,k as t,x as n}from"./i18n-BQcns1PH.js";import{t as r}from"./KnowledgeWorkspace-Czq6aZ7b.js";var i=n({__name:`KnowledgePage`,setup(n){return(n,i)=>(t(),e(r))}});export{i as default};
@@ -1 +0,0 @@
1
- import{$ as e,Q as t,_ as n,a as r,k as i,m as a,p as o,tt as s,x as c}from"./i18n-BQcns1PH.js";import{t as l}from"./console-CbDsKrsO.js";var u={class:`view`},d={class:`intro-row`},f=[`aria-label`],p={class:`two-column`},m={class:`section-block`},h={class:`section-title`},g={class:`flow-list`},_={class:`section-block`},v={class:`provider-list`},y={class:`provider-row`},b={class:`provider-state`},x={class:`provider-row`},S={class:`provider-state`},C=c({__name:`OverviewPage`,setup(c){let C=l(),w=o(()=>C.state),T=o(()=>w.value?.providers?.workspaces??[]),E=o(()=>T.value.filter(({status:e})=>e===`ready`).length);return(o,c)=>(i(),n(`section`,u,[a(`div`,d,[a(`div`,null,[a(`h3`,null,s(t(r)(`pages.overview.introTitle`)),1),a(`p`,null,s(t(r)(`pages.overview.introCopy`)),1)]),c[0]||=a(`span`,{class:`mode-chip`},`Agent structured`,-1)]),a(`div`,{class:`metrics`,"aria-label":t(r)(`pages.overview.metricsAria`)},[a(`article`,null,[a(`strong`,null,s(w.value?.personalSpaces.length??0),1),a(`span`,null,s(t(r)(`pages.overview.personalGraphs`)),1)]),a(`article`,null,[a(`strong`,null,s(w.value?.projects.length??0),1),a(`span`,null,s(t(r)(`pages.overview.accessibleProjects`)),1)]),a(`article`,null,[a(`strong`,null,s(w.value?.subscriptions.length??0),1),a(`span`,null,s(t(r)(`pages.overview.subscriptions`)),1)]),a(`article`,null,[a(`strong`,null,s(w.value?.personalProjects?.length??0),1),a(`span`,null,s(t(r)(`pages.overview.personalProjects`)),1)])],8,f),a(`div`,p,[a(`section`,m,[a(`div`,h,[a(`h3`,null,s(t(r)(`pages.overview.dataFlow`)),1)]),a(`ol`,g,[a(`li`,null,[c[1]||=a(`span`,null,`1`,-1),a(`div`,null,[a(`strong`,null,s(t(r)(`pages.overview.flow.identifyTitle`)),1),a(`p`,null,s(t(r)(`pages.overview.flow.identifyCopy`)),1)])]),a(`li`,null,[c[2]||=a(`span`,null,`2`,-1),a(`div`,null,[a(`strong`,null,s(t(r)(`pages.overview.flow.structureTitle`)),1),a(`p`,null,s(t(r)(`pages.overview.flow.structureCopy`)),1)])]),a(`li`,null,[c[3]||=a(`span`,null,`3`,-1),a(`div`,null,[a(`strong`,null,s(t(r)(`pages.overview.flow.routeTitle`)),1),a(`p`,null,s(t(r)(`pages.overview.flow.routeCopy`)),1)])])])]),a(`section`,_,[c[5]||=a(`div`,{class:`section-title`},[a(`h3`,null,`Provider`)],-1),a(`div`,v,[a(`div`,y,[a(`i`,{class:e({error:w.value?.providers?.personal?.status!==`ready`})},null,2),a(`div`,null,[a(`strong`,null,s(t(r)(`pages.overview.localKnowledge`)),1),c[4]||=a(`span`,null,`Graphiti / Neo4j`,-1)]),a(`span`,b,s(w.value?.providers?.personal?.status===`ready`?t(r)(`common.status.connected`):t(r)(`common.status.connectionError`)),1)]),a(`div`,x,[a(`i`,{class:e(t(C).publicRuntimeStatus)},null,2),a(`div`,null,[a(`strong`,null,s(t(r)(`pages.overview.publicService`)),1),a(`span`,null,s(t(C).publicRuntimeStatus===`ready`?t(r)(`pages.overview.sharedServicesReady`,{count:E.value||T.value.length}):t(C).publicRuntimeStatus===`error`?t(r)(`pages.overview.publicTemporarilyUnavailable`):t(r)(`pages.overview.publicOfflineLocalUnaffected`)),1)]),a(`span`,S,s(t(C).publicRuntimeStatus===`ready`?t(r)(`common.status.connected`):t(C).publicRuntimeStatus===`error`?t(r)(`common.status.connectionError`):t(r)(`common.status.notConnected`)),1)])])])])]))}});export{C as default};
@@ -1 +0,0 @@
1
- import{h as e,k as t,x as n}from"./i18n-BQcns1PH.js";import{t as r}from"./KnowledgeWorkspace-Czq6aZ7b.js";var i=n({__name:`PersonalProjectsPage`,setup(n){return(n,i)=>(t(),e(r,{"personal-projects-only":``}))}});export{i as default};