fuli-context 0.7.3 → 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.
Files changed (107) hide show
  1. package/README.md +170 -18
  2. package/README.zh-CN.md +146 -12
  3. package/compose.graphiti.yml +2 -0
  4. package/dist/web/assets/{AboutPage-qeO_vm0T.js → AboutPage-CftRexSY.js} +1 -1
  5. package/dist/web/assets/{ConnectionsPage-DO0GR9Xg.js → ConnectionsPage-D5J91x6d.js} +1 -1
  6. package/dist/web/assets/{useMinimumLoadingDisplay-BpCyBeko.css → KnowledgeEditDialog-DWUV_NPp.css} +1 -1
  7. package/dist/web/assets/KnowledgeEditDialog-wotVahvr.js +1 -0
  8. package/dist/web/assets/{KnowledgeInspector-Bjhdu2mf.css → KnowledgeInspector--1NujtXW.css} +1 -1
  9. package/dist/web/assets/KnowledgeInspector-BzkW0mVs.js +1 -0
  10. package/dist/web/assets/KnowledgeOrganizerPage-BebUDiHt.js +1 -0
  11. package/dist/web/assets/KnowledgePage-OUT07aKH.js +1 -0
  12. package/dist/web/assets/KnowledgeWorkspace-1pHJ0MNZ.js +4 -0
  13. package/dist/web/assets/{OverviewPage-CmCcDCAs.js → OverviewPage-CHE6XhdS.js} +1 -1
  14. package/dist/web/assets/PersonalProfilePage-B7T09bow.css +1 -0
  15. package/dist/web/assets/PersonalProfilePage-CXXiabUy.js +1 -0
  16. package/dist/web/assets/PersonalProjectsPage-d2wYGW8n.js +1 -0
  17. package/dist/web/assets/PublicProjectsPage-hxLShtnZ.js +1 -0
  18. package/dist/web/assets/{ReviewPage-B3vnZ6gn.js → ReviewPage-DUeyu7E8.js} +1 -1
  19. package/dist/web/assets/{SearchableSelect-ih69qbcS.js → SearchableSelect-C5RiB5F8.js} +1 -1
  20. package/dist/web/assets/SettingsPage-BMuASSzQ.js +1 -0
  21. package/dist/web/assets/SettingsPage-CAfz4qiV.css +1 -0
  22. package/dist/web/assets/WritingTastePage-B9BCYIwQ.js +1 -0
  23. package/dist/web/assets/WritingTastePage-DSgvTH6x.css +1 -0
  24. package/dist/web/assets/{console-DE40hXGC.js → console-DMvHxKf9.js} +1 -1
  25. package/dist/web/assets/conversation-launcher-settings-Dnlcw7Rj.js +1 -0
  26. package/dist/web/assets/{i18n-CDxBs1sz.js → i18n-CWfd8Dtt.js} +1 -1
  27. package/dist/web/assets/{identity-BCxb1mcy.js → identity-C9mLdR8L.js} +1 -1
  28. package/dist/web/assets/index-F5e2sQ7p.js +2 -0
  29. package/dist/web/assets/{index-CCOGfZzE.css → index-X2j29j8i.css} +1 -1
  30. package/dist/web/assets/{model-CCMdt10Q.js → model-CsYAR8O3.js} +1 -1
  31. package/dist/web/assets/paths-CDREouhi.js +1 -0
  32. package/dist/web/assets/useApi-CROJJdhE-BLQiPVw0.js +1 -0
  33. package/dist/web/assets/vue-router-DN8x6iY1.js +1 -0
  34. package/dist/web/index.html +7 -6
  35. package/graph-provider/fuli_graph/app.py +238 -2
  36. package/graph-provider/fuli_graph/auth.py +18 -0
  37. package/graph-provider/fuli_graph/collaboration_context.py +253 -17
  38. package/graph-provider/fuli_graph/common_knowledge.py +6 -2
  39. package/graph-provider/fuli_graph/config.py +5 -0
  40. package/graph-provider/fuli_graph/graph_models.py +6 -0
  41. package/graph-provider/fuli_graph/graph_projection.py +14 -0
  42. package/graph-provider/fuli_graph/graph_query.py +27 -3
  43. package/graph-provider/fuli_graph/knowledge_management.py +19 -1
  44. package/graph-provider/fuli_graph/knowledge_search.py +11 -0
  45. package/graph-provider/fuli_graph/models.py +15 -2
  46. package/graph-provider/fuli_graph/personal_global_preference_models.py +234 -0
  47. package/graph-provider/fuli_graph/personal_global_preferences.py +1247 -0
  48. package/graph-provider/fuli_graph/project_action_models.py +22 -0
  49. package/graph-provider/fuli_graph/project_knowledge.py +121 -2
  50. package/graph-provider/fuli_graph/provider_values.py +24 -0
  51. package/graph-provider/fuli_graph/runtime.py +78 -0
  52. package/graph-provider/fuli_graph/search_projection.py +14 -1
  53. package/graph-provider/fuli_graph/store_knowledge.py +116 -2
  54. package/graph-provider/fuli_graph/workflow_candidate_models.py +168 -0
  55. package/graph-provider/fuli_graph/workflow_candidates.py +996 -0
  56. package/graph-provider/fuli_graph/workflow_observation_models.py +30 -0
  57. package/graph-provider/fuli_graph/workflow_observations.py +84 -0
  58. package/graph-provider/pyproject.toml +1 -1
  59. package/npm-shrinkwrap.json +18 -18
  60. package/package.json +4 -4
  61. package/skills/capturing-session-knowledge/SKILL.md +32 -5
  62. package/skills/flreview/SKILL.md +4 -3
  63. package/src/agent-tools/graph-definitions.js +136 -20
  64. package/src/agent-tools/graph-handlers.js +10 -4
  65. package/src/cli/command-registry.js +1 -1
  66. package/src/cli/local-runtime-command.js +69 -2
  67. package/src/graphiti/agent-knowledge-workflows.js +497 -16
  68. package/src/graphiti/collaboration-preference-projection.js +162 -0
  69. package/src/graphiti/federated-application.js +155 -161
  70. package/src/graphiti/federated-search-support.js +102 -0
  71. package/src/graphiti/knowledge-provider-mapping.js +15 -0
  72. package/src/graphiti/provider-client.js +162 -10
  73. package/src/graphiti/related-project-suggestions.js +109 -0
  74. package/src/graphiti/runtime-config.js +14 -0
  75. package/src/graphiti/source-marker.js +2 -2
  76. package/src/graphiti/user-taste-skill.js +241 -0
  77. package/src/graphiti/workflow-candidate-mapping.js +87 -0
  78. package/src/graphiti/workflow-observation.js +82 -0
  79. package/src/graphiti/writing-taste-profile-workflow.js +25 -0
  80. package/src/graphiti/writing-taste-profile.js +490 -0
  81. package/src/http/graph-api-router.js +15 -2
  82. package/src/mcp/context-resources.js +158 -0
  83. package/src/mcp/create-mcp-server.js +39 -5
  84. package/src/mcp/instructions.js +5 -7
  85. package/src/mcp/session-id.js +60 -0
  86. package/src/mcp/task-context-registry.js +15 -3
  87. package/src/mcp/tool-annotations.js +8 -1
  88. package/src/mcp/tool-result.js +28 -4
  89. package/src/server.js +0 -1
  90. package/src/setup/claude-code-config.js +17 -6
  91. package/src/setup/graph-runtime.js +20 -2
  92. package/src/system/runtime-settings.js +121 -2
  93. package/src/system/system-service.js +5 -1
  94. package/dist/web/assets/KnowledgeInspector-DEW5lA1m.js +0 -1
  95. package/dist/web/assets/KnowledgeOrganizerPage-BkM568Ld.js +0 -1
  96. package/dist/web/assets/KnowledgePage-BAT4aZIm.js +0 -1
  97. package/dist/web/assets/KnowledgeWorkspace-DyGtBz--.js +0 -4
  98. package/dist/web/assets/PersonalProfilePage-C42mwKN8.js +0 -1
  99. package/dist/web/assets/PersonalProfilePage-CdmEzyvZ.css +0 -1
  100. package/dist/web/assets/PersonalProjectsPage-BwaXR62K.js +0 -1
  101. package/dist/web/assets/PublicProjectsPage-egKBulOq.js +0 -1
  102. package/dist/web/assets/SettingsPage-C6ouU0sR.css +0 -1
  103. package/dist/web/assets/SettingsPage-oLE8mNs5.js +0 -1
  104. package/dist/web/assets/index-BzhNfzU_.js +0 -2
  105. package/dist/web/assets/paths-DwHeiGMT.js +0 -1
  106. package/dist/web/assets/useMinimumLoadingDisplay-HdI7XAmu.js +0 -1
  107. package/dist/web/assets/vue-router-Drfl1F8i.js +0 -1
@@ -0,0 +1,30 @@
1
+ from datetime import datetime
2
+ from typing import Literal
3
+
4
+ from pydantic import Field
5
+
6
+ from .models import StrictModel
7
+
8
+
9
+ class WorkflowObservedStep(StrictModel):
10
+ action_id: str = Field(min_length=1, max_length=256)
11
+ name: str = Field(min_length=1, max_length=512)
12
+ summary: str | None = Field(default=None, min_length=1, max_length=4096)
13
+
14
+
15
+ class WorkflowTransitionObservation(StrictModel):
16
+ personal_space_id: str = Field(min_length=1, max_length=128)
17
+ personal_project_id: str | None = Field(default=None, min_length=1, max_length=128)
18
+ host_session_id: str = Field(min_length=1, max_length=256)
19
+ observation_id: str = Field(min_length=8, max_length=256)
20
+ from_step: WorkflowObservedStep
21
+ to_step: WorkflowObservedStep
22
+ workflow_key: str = Field(min_length=1, max_length=256)
23
+ condition: dict[str, object] = Field(default_factory=dict)
24
+ observed_at: datetime
25
+ evidence_summary: str = Field(min_length=1, max_length=2048)
26
+ source_application: Literal[
27
+ 'codex', 'claude_code', 'cursor', 'kiro', 'other'
28
+ ] | None = None
29
+ source_turn_id: str | None = Field(default=None, min_length=1, max_length=256)
30
+ sensitivity: Literal['normal', 'private', 'restricted'] = 'normal'
@@ -0,0 +1,84 @@
1
+ from .models import KnowledgeCommit
2
+ from .workflow_observation_models import WorkflowTransitionObservation
3
+
4
+
5
+ async def record_workflow_transition_observation(
6
+ store,
7
+ actor: dict,
8
+ request: WorkflowTransitionObservation,
9
+ ):
10
+ basis = {
11
+ 'existence_reason': (
12
+ 'An Agent reported one completed-action transition through the MCP seam.'
13
+ ),
14
+ 'quadrant_reason': (
15
+ 'Behavioral sequence evidence is not user consent or authorization.'
16
+ ),
17
+ 'proposed_by': {
18
+ 'kind': 'agent',
19
+ 'label': 'mcp-host-workflow-observer',
20
+ },
21
+ }
22
+ commit = KnowledgeCommit.model_validate({
23
+ 'space_id': request.personal_space_id,
24
+ 'personal_project_id': request.personal_project_id,
25
+ 'episode': {
26
+ 'idempotency_key': request.observation_id,
27
+ 'session_id': request.host_session_id,
28
+ 'name': (
29
+ f'Observed {request.from_step.name} then {request.to_step.name}'
30
+ )[:512],
31
+ 'source_kind': 'workflow_action_observation',
32
+ 'source_description': (
33
+ 'One Agent-reported action transition submitted through the Fuli MCP host.'
34
+ ),
35
+ 'source_application': request.source_application or 'other',
36
+ 'source_turn_id': request.source_turn_id,
37
+ 'reference_time': request.observed_at,
38
+ 'summary': request.evidence_summary,
39
+ 'sensitivity': request.sensitivity,
40
+ 'entities': [
41
+ _workflow_step(request.from_step, basis),
42
+ _workflow_step(request.to_step, basis),
43
+ ],
44
+ 'relationships': [{
45
+ 'key': request.workflow_key,
46
+ 'source': request.from_step.action_id,
47
+ 'target': request.to_step.action_id,
48
+ 'type': 'RECOMMENDS_NEXT',
49
+ 'fact': request.evidence_summary,
50
+ 'valid_at': request.observed_at,
51
+ 'origin_quadrant': 'unknown_known',
52
+ 'current_quadrant': 'unknown_known',
53
+ 'epistemic_status': 'observed',
54
+ 'confirmation_status': 'pending',
55
+ 'confirmation_basis': basis,
56
+ 'reasoning_summary': (
57
+ 'Agent-reported transition; the MCP host attests only '
58
+ 'session, time, and observation identity; no authority.'
59
+ ),
60
+ 'attributes': {
61
+ 'workflowCondition': request.condition,
62
+ 'observationKind': 'completed_action_transition',
63
+ },
64
+ }],
65
+ },
66
+ })
67
+ return await store.commit_workflow_observation(actor, commit)
68
+
69
+
70
+ def _workflow_step(step, basis: dict) -> dict:
71
+ return {
72
+ 'key': step.action_id,
73
+ 'name': step.name,
74
+ 'type': 'WorkflowStep',
75
+ 'summary': step.summary or f'Reported completed action: {step.name}',
76
+ 'origin_quadrant': 'unknown_known',
77
+ 'current_quadrant': 'unknown_known',
78
+ 'epistemic_status': 'observed',
79
+ 'confirmation_status': 'pending',
80
+ 'confirmation_basis': basis,
81
+ 'reasoning_summary': (
82
+ 'The Agent reported the action completion; reuse remains unconfirmed.'
83
+ ),
84
+ }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.7.3"
3
+ version = "0.7.4b1"
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.3",
3
+ "version": "0.7.4-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.7.3",
9
+ "version": "0.7.4-beta.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@hono/node-server": "2.0.12",
@@ -22,7 +22,7 @@
22
22
  "@types/node": "^24.13.3",
23
23
  "@vitejs/plugin-vue": "^6.0.8",
24
24
  "@vue/test-utils": "^2.4.11",
25
- "brace-expansion": "5.0.8",
25
+ "brace-expansion": "5.0.9",
26
26
  "d3": "^7.9.0",
27
27
  "gsap": "^3.15.0",
28
28
  "jsdom": "^29.1.1",
@@ -1868,9 +1868,9 @@
1868
1868
  "license": "ISC"
1869
1869
  },
1870
1870
  "node_modules/brace-expansion": {
1871
- "version": "5.0.8",
1872
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
1873
- "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
1871
+ "version": "5.0.9",
1872
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
1873
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
1874
1874
  "dev": true,
1875
1875
  "license": "MIT",
1876
1876
  "dependencies": {
@@ -2950,9 +2950,9 @@
2950
2950
  "license": "MIT"
2951
2951
  },
2952
2952
  "node_modules/fast-uri": {
2953
- "version": "3.1.4",
2954
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
2955
- "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
2953
+ "version": "3.1.5",
2954
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
2955
+ "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
2956
2956
  "funding": [
2957
2957
  {
2958
2958
  "type": "github",
@@ -3165,9 +3165,9 @@
3165
3165
  }
3166
3166
  },
3167
3167
  "node_modules/hono": {
3168
- "version": "4.12.32",
3169
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz",
3170
- "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==",
3168
+ "version": "4.13.0",
3169
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz",
3170
+ "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==",
3171
3171
  "license": "MIT",
3172
3172
  "engines": {
3173
3173
  "node": ">=16.9.0"
@@ -4338,9 +4338,9 @@
4338
4338
  }
4339
4339
  },
4340
4340
  "node_modules/postcss": {
4341
- "version": "8.5.22",
4342
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
4343
- "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
4341
+ "version": "8.5.25",
4342
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
4343
+ "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
4344
4344
  "dev": true,
4345
4345
  "funding": [
4346
4346
  {
@@ -5085,9 +5085,9 @@
5085
5085
  "license": "ISC"
5086
5086
  },
5087
5087
  "node_modules/undici": {
5088
- "version": "7.28.0",
5089
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
5090
- "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
5088
+ "version": "7.29.0",
5089
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
5090
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
5091
5091
  "dev": true,
5092
5092
  "license": "MIT",
5093
5093
  "engines": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.7.3",
4
- "description": "Local-first context graph for AI agents.",
3
+ "version": "0.7.4-beta.1",
4
+ "description": "Local-first collaboration relationship graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "README.zh-CN.md",
@@ -78,7 +78,7 @@
78
78
  "@types/node": "^24.13.3",
79
79
  "@vitejs/plugin-vue": "^6.0.8",
80
80
  "@vue/test-utils": "^2.4.11",
81
- "brace-expansion": "5.0.8",
81
+ "brace-expansion": "5.0.9",
82
82
  "d3": "^7.9.0",
83
83
  "gsap": "^3.15.0",
84
84
  "jsdom": "^29.1.1",
@@ -98,6 +98,6 @@
98
98
  "@hono/node-server": "2.0.12"
99
99
  },
100
100
  "brace-expansion": "$brace-expansion",
101
- "fast-uri": "3.1.4"
101
+ "fast-uri": "3.1.5"
102
102
  }
103
103
  }
@@ -103,13 +103,23 @@ IDs; never search every personal project, every subscription, or the whole graph
103
103
  Use the current repository, the user's wording, and `list_knowledge_spaces` to resolve scope.
104
104
  Ask if the active project remains ambiguous.
105
105
 
106
+ FULI exposes read-only MCP resources for this selection flow. When the Agent host offers a FULI
107
+ project or the global taste resource in its `@` mention picker and the user selects it, treat the
108
+ resource's exact identifiers as explicit scope for the current task. Use the selected
109
+ `personal_project_id` for project retrieval, or apply the global taste resource as a
110
+ personal-global profile. A selected resource never authorizes searching another project,
111
+ following `RELATED_TO`, or performing a write.
112
+
106
113
  The active child project may also retrieve only knowledge explicitly marked inheritable from
107
114
  projects reached through its outgoing `PART_OF` or `USES_KNOWLEDGE_FROM` relations. This means
108
115
  project-specific PRDs, configuration IDs, and debugging notes remain in the child, while a parent
109
116
  may own shared run, validation, and test runbooks. Search the child first, then authorized parents.
110
- Never inherit project-scoped personal preferences, never traverse `RELATED_TO`, stop after two
111
- hops, and retain the returned scope path. Exact active-project knowledge with the same stable key
112
- overrides an inherited item.
117
+ Project-scoped personal preferences may enter the child context only when the preference is
118
+ explicitly marked `descendants`, or `selected_projects` includes that exact active child, and the
119
+ parent is reached through `PART_OF` or `USES_KNOWLEDGE_FROM`. Never inherit `local_only`, never
120
+ traverse `RELATED_TO`, stop after two hops, and retain the returned scope path. Confirmation
121
+ authority is a hard gate before scope distance; within the same authority layer the nearest scope
122
+ wins, while equal-layer conflicting instructions remain deferred and weight cannot choose a winner.
113
123
 
114
124
  After a retrieved personal item materially affects the final answer or a completed action, call
115
125
  `record_knowledge_usage` once for that task and item with `cited` or `applied`. Do not record
@@ -118,6 +128,20 @@ promote only to `agent_confirmed`; it never substitutes for human confirmation o
118
128
  eligible for public publication. Use the current user task's caller-stable identifier as
119
129
  `taskId` and reuse it on retries; never generate another ID to recount the same task.
120
130
 
131
+ ## Use the Generated Taste Skill
132
+
133
+ When a UI, writing, product, architecture, code, or other judgment call would benefit from the
134
+ person's established taste, call `get_user_taste_skill` with the current working directory and
135
+ current task prompt. It returns a bounded, read-only `user-taste` Skill projection built from
136
+ effective personal preferences and task-matched recommendations. Apply the current request and
137
+ authoritative constraints first; treat `Confirmed` and `Observed` evidence differently; preserve
138
+ project scope; and explain the matching preference and material tradeoff when recommending.
139
+
140
+ The projection is regenerated from the durable graph as preferences are added or revised, so it
141
+ can grow over time without copying or overwriting a user-authored `user-taste/SKILL.md`. Never
142
+ promote a pending inference in the generated Skill and never treat the projection itself as new
143
+ knowledge to capture.
144
+
121
145
  The current `agent-usage-v1` policy requires at least five idempotent material-use events across
122
146
  at least three distinct tasks in the current content generation, with no open knowledge or
123
147
  preference conflict. It maintains utility and confidence as separate scores and caps
@@ -248,8 +272,11 @@ the personal-global graph without `personalProjectId`:
248
272
 
249
273
  When the user explicitly limits one preference to a project, keep `profileAspect` and capture
250
274
  it with that exact `personalProjectId`; this makes the preference project-scoped without
251
- turning it into public project knowledge. Use `set_personal_preference_scope` when the user
252
- later changes the scope. Never put any personal-profile item into a public proposal.
275
+ turning it into public project knowledge. The Agent has no direct scope-write tool. Safe
276
+ narrowing from global to one exact project belongs to a trusted local UI; any broader parent or
277
+ personal global scope must use preference convergence, with an explicit target and a Provider
278
+ `HumanReviewer` channel behind trusted local user-presence. Never put any personal-profile item
279
+ into a public proposal.
253
280
  For a preference that may have both global and project-specific forms, store a stable
254
281
  `attributes.preferenceKey` on each form so the project value can explicitly override the same
255
282
  global decision without hiding unrelated preferences.
@@ -61,11 +61,12 @@ Collect the exact replacement content and a reason in the card. Call `revise_per
61
61
 
62
62
  ### 调整范围
63
63
 
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`.
64
+ - The Agent must not perform a direct scope write. Scope mutation is not an Agent or MCP capability.
65
+ - For a global preference, the only direct operation is safe narrowing to one exact project. With one source project, offer that exact project; with multiple source projects, require an explicit selection. Hand the exact item and project to a trusted local FULI human-review UI with a real user-presence gate.
66
+ - For a project-scoped preference, never offer direct promotion. A broader parent-project or personal global scope must use the independently reviewed preference-convergence flow: discover the candidate, preserve every source and qualifier, require an explicit `target_scope` and `target_project_id`, and let only the Provider `HumanReviewer` channel apply the decision through the trusted local human-review UI.
66
67
  - Ordinary project knowledge is not a personal preference. Never offer to turn ordinary project knowledge into a global preference.
67
68
 
68
- After a successful scope write, record `updated`. Never change scope automatically.
69
+ If the trusted local human-review UI is unavailable, report that exact missing capability and leave the candidate unchanged. After that trusted flow reports a successful scope write, record `updated`. Never change scope automatically.
69
70
 
70
71
  ### 失效
71
72
 
@@ -82,7 +82,10 @@ const epistemicFields = {
82
82
  epistemicStatus,
83
83
  confirmationStatus,
84
84
  confirmationBasis,
85
- reasoningSummary: nullableStringSchema(),
85
+ reasoningSummary: {
86
+ ...nullableStringSchema(),
87
+ description: 'Required and nonempty whenever originQuadrant is not known_known. Explain how the item became known; omitting it rejects the whole batch.'
88
+ },
86
89
  profileAspect,
87
90
  inheritanceMode,
88
91
  inheritedProjectIds: arraySchema(id, { maxItems: 32 })
@@ -188,6 +191,40 @@ const commonKnowledgePromotionRequired = [
188
191
  'personalSpaceId', 'parentProjectId', 'itemKind', 'canonicalItemId',
189
192
  'duplicateItemIds', 'reason', 'humanConfirmationReason'
190
193
  ];
194
+ const personalGlobalPreferenceDecisionIntent = {
195
+ personalSpaceId: id,
196
+ candidateId: {
197
+ ...boundedString(64),
198
+ pattern: '^personal-global-[a-f0-9]{20}$'
199
+ },
200
+ candidateVersion: {
201
+ ...boundedString(32),
202
+ pattern: '^v1:[a-f0-9]{24}$'
203
+ },
204
+ decisionRevision: integerSchema({ minimum: 0 }),
205
+ decision: enumSchema(['approve', 'reject']),
206
+ sourceItems: arraySchema(objectSchema({
207
+ itemId: id,
208
+ itemKind: knowledgeItemKind,
209
+ projectId: id
210
+ }, ['itemId', 'itemKind', 'projectId']), { minItems: 2, maxItems: 32 }),
211
+ preferenceKey: boundedString(512),
212
+ targetScope: enumSchema(['parent_project', 'personal_global']),
213
+ targetProjectId: nullableStringSchema(),
214
+ profileAspect,
215
+ globalTitle: { type: ['string', 'null'], minLength: 1, maxLength: 512 },
216
+ globalInstruction: { type: ['string', 'null'], minLength: 1, maxLength: 8192 },
217
+ humanConfirmationReason: boundedString(4096),
218
+ confirmedAt: dateTime,
219
+ sessionId: id,
220
+ idempotencyKey: id
221
+ };
222
+ const personalGlobalPreferenceDecisionRequired = [
223
+ 'personalSpaceId', 'candidateId', 'candidateVersion', 'decisionRevision',
224
+ 'decision', 'sourceItems', 'preferenceKey', 'targetScope',
225
+ 'targetProjectId', 'humanConfirmationReason',
226
+ 'confirmedAt', 'sessionId', 'idempotencyKey'
227
+ ];
191
228
  const captureEpisodeFields = {
192
229
  idempotencyKey: id,
193
230
  name: label,
@@ -207,6 +244,11 @@ const captureEpisodeRequired = [
207
244
  'idempotencyKey', 'name', 'sourceKind', 'sourceDescription',
208
245
  'referenceTime', 'entities', 'relationships'
209
246
  ];
247
+ const workflowObservedStep = objectSchema({
248
+ actionId: id,
249
+ name: label,
250
+ summary: nullableStringSchema()
251
+ }, ['actionId', 'name']);
210
252
 
211
253
  export const GRAPH_TOOL_DEFINITIONS = [
212
254
  {
@@ -247,6 +289,17 @@ export const GRAPH_TOOL_DEFINITIONS = [
247
289
  limit: integerSchema({ minimum: 1, maximum: 200 })
248
290
  }, ['projectPath'])
249
291
  },
292
+ {
293
+ name: 'get_user_taste_skill',
294
+ title: 'READ · Generate the current user-taste Skill',
295
+ description: 'Generate a bounded, read-only user-taste Skill projection from the effective personal preferences for the exact active project. It uses prior confirmed and explicitly agent-confirmed preference data to provide task-specific recommendations, labels evidence status and scope, preserves the current-request and authoritative-constraint precedence, and never overwrites a user-authored taste Skill or promotes a pending inference. Call it when a UI, writing, product, architecture, code, or other judgment call would benefit from the user\'s established taste.',
296
+ inputSchema: objectSchema({
297
+ projectPath: boundedString(4096),
298
+ taskPrompt: boundedString(8192),
299
+ personalProjectId: nullableStringSchema(),
300
+ limit: integerSchema({ minimum: 1, maximum: 200 })
301
+ }, ['projectPath'])
302
+ },
250
303
  {
251
304
  name: 'resolve_deferred_preference_conflict',
252
305
  description: 'Resolve one AI-deferred collaboration-preference conflict returned by get_collaboration_preferences when, and only when, the current task needs that preference. Compare both confirmed sides against their evidence and the current context before choosing. The operation updates the effective knowledge, preserves revision history, and permanently marks the conflict as previously conflicted and resolved by AI. Use merge with a canonical item and merged instruction, keep_left or keep_right to retain one side, or split_scope to move one side to an exact personal project.',
@@ -265,18 +318,49 @@ export const GRAPH_TOOL_DEFINITIONS = [
265
318
  },
266
319
  {
267
320
  name: 'capture_session_knowledge',
268
- description: 'Silently batch durable session knowledge when the user-enabled capture policy allows it. When knowledge was read from an online source, sourceUri preserves the exact original HTTP(S) link so a capable Agent can re-read it later and refresh Fuli knowledge; it does not authorize source-document writeback. Every item records its discovery quadrant, structured confirmation basis, and pending or confirmed state. A disabled policy returns capture_disabled without writing. Personal profile knowledge stays personal; only project knowledge with an auditable confirmation can enter public review.',
321
+ description: 'Silently batch durable session knowledge when the user-enabled capture policy allows it. When knowledge was read from an online source, sourceUri preserves the exact original HTTP(S) link so a capable Agent can re-read it later and refresh Fuli knowledge; it does not authorize source-document writeback. Every item records its discovery quadrant, structured confirmation basis, and pending or confirmed state. reasoningSummary is required for every entity or relationship whose originQuadrant is not known_known. A disabled policy returns capture_disabled without writing. Personal profile knowledge stays personal; only project knowledge with an auditable confirmation can enter public review.',
269
322
  inputSchema: objectSchema({
270
- targetKind: enumSchema(['personal', 'project']),
271
- spaceId: id,
272
- personalProjectId: nullableStringSchema(),
273
- providerUrl: nullableStringSchema(),
323
+ targetKind: {
324
+ ...enumSchema(['personal', 'project']),
325
+ description: 'Use "personal" for the personal graph, including knowledge scoped to a local personal project via personalProjectId. Use "project" only for a team-shared project queued for public review.'
326
+ },
327
+ spaceId: {
328
+ ...id,
329
+ description: 'With targetKind "personal", the active personal space id. With targetKind "project", the team-shared project id.'
330
+ },
331
+ personalProjectId: {
332
+ ...nullableStringSchema(),
333
+ description: 'Local personal project scope. Only meaningful with targetKind "personal".'
334
+ },
335
+ providerUrl: {
336
+ ...nullableStringSchema(),
337
+ description: 'Required with targetKind "project" and ignored for "personal".'
338
+ },
274
339
  sessionId: id,
275
340
  ...captureEpisodeFields
276
341
  }, [
277
342
  'targetKind', 'spaceId', 'sessionId', ...captureEpisodeRequired
278
343
  ])
279
344
  },
345
+ {
346
+ name: 'record_workflow_transition_observation',
347
+ title: 'WRITE · Observe one completed workflow transition',
348
+ description: 'Record one Agent-reported completed X-to-Y action transition in the personal Provider. One call submits one observation; calls for the same project, X/Y, workflow key, condition, and MCP-host session deduplicate to one Episodic node. The Provider derives occurrence and distinct-session counts from persisted episodes. The MCP host attests session, time, and observation identity, not the truth of the reported actions. The tool does not accept aggregate counts, confirmation authority, approval, or durable-consent claims. Every observation remains pending behavioral evidence and is not user consent or execution authorization. Call it only after both actions completed successfully. This is an Agent/adapter observation seam, not automatic telemetry for arbitrary host tools.',
349
+ inputSchema: objectSchema({
350
+ personalSpaceId: id,
351
+ personalProjectId: nullableStringSchema(),
352
+ fromStep: workflowObservedStep,
353
+ toStep: workflowObservedStep,
354
+ workflowKey: id,
355
+ condition: attributes,
356
+ evidenceSummary: shortText,
357
+ sourceApplication,
358
+ sourceTurnId: { type: ['string', 'null'], minLength: 1, maxLength: 256 },
359
+ sensitivity: enumSchema(['normal', 'private', 'restricted'])
360
+ }, [
361
+ 'personalSpaceId', 'fromStep', 'toStep', 'workflowKey', 'evidenceSummary'
362
+ ])
363
+ },
280
364
  {
281
365
  name: 'record_decision_trace',
282
366
  title: 'WRITE · Store a decision with its rationale',
@@ -322,7 +406,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
322
406
  {
323
407
  name: 'search_knowledge_graph',
324
408
  title: 'READ · Search the scoped knowledge graph',
325
- description: 'Search durable context before saying you do not know when a task may depend on remembered URLs, routes, requirements, architecture, prior decisions, runbooks, rationale, or personal preferences. Supporting facts and entities expose bounded source_uris when their evidence came from online sources, allowing a capable Agent to re-read the original source before refreshing Fuli knowledge. Pending knowledge is searchable and explicitly marked; agent-confirmed knowledge ranks below human-confirmed knowledge. The bounded scope includes the personal-global profile, exact active local project, selectively inheritable knowledge reached through PART_OF or USES_KNOWLEDGE_FROM, explicitly selected additional personal projects, and selected subscribed team projects. Generic RELATED_TO links never expand scope. Use all_local_confirmed only after explicit user confirmation; it searches registered local personal projects for this query and never expands public projects. If that still has no support, use read-only local file search in the current repository or workspace files within a safe root. The response includes sourceMarker for supporting results, noMatchSourceMarker when returned items do not support the answer, and retrievalGuidance with the required next action.',
409
+ description: 'Search durable context before saying you do not know when a task may depend on remembered URLs, routes, requirements, architecture, prior decisions, runbooks, rationale, or personal preferences. Supporting facts and entities expose bounded source_uris when their evidence came from online sources, allowing a capable Agent to re-read the original source before refreshing Fuli knowledge. Pending knowledge is searchable and explicitly marked; agent-confirmed knowledge ranks below human-confirmed knowledge. The bounded scope includes the personal-global profile, exact active local project, selectively inheritable knowledge reached through PART_OF or USES_KNOWLEDGE_FROM, explicitly selected additional personal projects, and selected subscribed team projects. Generic RELATED_TO links never expand scope. A result may instead include one-time read-only relatedProjectSuggestions only for active human-authorized RELATED_TO edges; ask the user before running the supplied exact-project follow-up query, and never silently add that project to future scope. Use all_local_confirmed only after explicit user confirmation; it searches registered local personal projects for this query and never expands public projects. If that still has no support, use read-only local file search in the current repository or workspace files within a safe root. The response includes sourceMarker for supporting results, noMatchSourceMarker when returned items do not support the answer, and retrievalGuidance with the required next action.',
326
410
  inputSchema: objectSchema({
327
411
  personalSpaceId: id,
328
412
  query: shortText,
@@ -396,7 +480,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
396
480
  {
397
481
  name: 'search_current_project_knowledge',
398
482
  title: 'READ · Search current project and its knowledge sources',
399
- description: 'High-level project search for normal Agent work. Pass projectPath plus one or more focused queries; Fuli resolves the exact local project, searches its local knowledge first, then follows outgoing PART_OF or USES_KNOWLEDGE_FROM relations to authorized parent/source knowledge. An exact current-project item with the same stable key overrides an inherited item. The tool never guesses an ambiguous project and never traverses RELATED_TO.',
483
+ description: 'High-level project search for normal Agent work. Pass projectPath plus one or more focused queries; Fuli resolves the exact local project, searches its local knowledge first, then follows outgoing PART_OF or USES_KNOWLEDGE_FROM relations to authorized parent/source knowledge. An exact current-project item with the same stable key overrides an inherited item. The tool never guesses an ambiguous project and never traverses RELATED_TO. Instead it returns structured related_project_suggestions so the Agent can ask whether to add exactly one related project to this read-only search; explicit human confirmation is required before any expansion.',
400
484
  inputSchema: objectSchema({
401
485
  projectPath: boundedString(4096),
402
486
  queries: arraySchema(shortText, { minItems: 1, maxItems: 10 }),
@@ -418,6 +502,28 @@ export const GRAPH_TOOL_DEFINITIONS = [
418
502
  limitPerProject: integerSchema({ minimum: 1, maximum: 50 })
419
503
  }, ['personalSpaceId', 'parentProjectId', 'query'])
420
504
  },
505
+ {
506
+ name: 'discover_personal_global_preference_candidates',
507
+ title: 'READ · Find possible personal-global preferences',
508
+ description: 'Read-only scope-governance aid across two or more explicitly selected personal projects. It searches each exact project independently, excludes inherited and existing personal-global results, preserves every original preference text, qualifier, project source, and source URI, and exposes any shared lexical core only as a non-authoritative derived candidate. Similarity and weight never apply or promote a preference. Every candidate requires explicit human scope judgment through the candidate-bound review workflow.',
509
+ inputSchema: objectSchema({
510
+ personalSpaceId: id,
511
+ personalProjectIds: arraySchema(id, { minItems: 2, maxItems: 32 }),
512
+ query: shortText,
513
+ minProjects: integerSchema({ minimum: 2, maximum: 32 }),
514
+ similarityThreshold: numberSchema({ minimum: 0, maximum: 1 }),
515
+ limitPerProject: integerSchema({ minimum: 1, maximum: 50 })
516
+ }, ['personalSpaceId', 'personalProjectIds', 'query'])
517
+ },
518
+ {
519
+ name: 'preview_personal_global_preference_decision',
520
+ title: 'READ · Inspect a version-bound scope decision',
521
+ description: 'Read-only inspection for one exact personal-global preference candidate and decision revision. It revalidates every selected local source in the Provider and returns the preserved snapshots plus an exact payload fingerprint. It deliberately does not mint an approval token: an independent human-review client must confirm that fingerprint and mint the short-lived, one-time capability. The Agent cannot claim human authority through this tool.',
522
+ inputSchema: objectSchema(
523
+ personalGlobalPreferenceDecisionIntent,
524
+ personalGlobalPreferenceDecisionRequired
525
+ )
526
+ },
421
527
  {
422
528
  name: 'preview_common_knowledge_promotion',
423
529
  title: 'CONFIRM · Preview parent-project promotion',
@@ -538,6 +644,28 @@ export const GRAPH_TOOL_DEFINITIONS = [
538
644
  disposition: enumSchema(['paused', 'completed'])
539
645
  }, ['personalSpaceId', 'reviewId', 'disposition'])
540
646
  },
647
+ {
648
+ name: 'list_workflow_candidates',
649
+ title: 'READ · List learned workflow candidates',
650
+ description: 'Read persisted X-to-Y workflow candidates and their condition, explainable occurrence count, distinct-session count, recency, confirmation authority, negative evidence, declines, review status, and durable authorization. candidateVersion changes only when the rule definition changes; evidenceRevision tracks added or changed evidence; decisionRevision tracks review/authorization state transitions, including reset after a rule change. Rejected candidates and evidence remain in history. This read never executes a step or grants authority.',
651
+ inputSchema: objectSchema({
652
+ personalSpaceId: id,
653
+ personalProjectId: nullableStringSchema(),
654
+ afterStepKey: nullableStringSchema(),
655
+ limit: integerSchema({ minimum: 1, maximum: 100 })
656
+ }, ['personalSpaceId'])
657
+ },
658
+ {
659
+ name: 'recommend_next_workflow_steps',
660
+ title: 'READ · Recommend what to ask after a workflow step',
661
+ description: 'Read persisted candidates matching the completed source step. Provider weights only decide whether a candidate crosses the recommendation threshold; they never grant execution authority. A recommended but unapproved candidate returns ask_user and must not execute Y. Even a durable authorization never replaces per-call approval for high-risk send, delete, publish, payment, or external-write tools.',
662
+ inputSchema: objectSchema({
663
+ personalSpaceId: id,
664
+ personalProjectId: nullableStringSchema(),
665
+ afterStepKey: id,
666
+ limit: integerSchema({ minimum: 1, maximum: 100 })
667
+ }, ['personalSpaceId', 'afterStepKey'])
668
+ },
541
669
  {
542
670
  name: 'revise_personal_knowledge',
543
671
  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.',
@@ -573,18 +701,6 @@ export const GRAPH_TOOL_DEFINITIONS = [
573
701
  reason: shortText
574
702
  }, ['personalSpaceId', 'itemKind', 'itemId', 'targetProjectId', 'reason'])
575
703
  },
576
- {
577
- name: 'set_personal_preference_scope',
578
- description: 'Change one taste, personality, or judgment preference between the default personal-global scope and one exact personal project. The source evidence remains unchanged and the scope change is recorded in revision history.',
579
- inputSchema: objectSchema({
580
- personalSpaceId: id,
581
- itemKind: knowledgeItemKind,
582
- itemId: id,
583
- scope: enumSchema(['global', 'project']),
584
- projectId: nullableStringSchema(),
585
- reason: shortText
586
- }, ['personalSpaceId', 'itemKind', 'itemId', 'scope', 'reason'])
587
- },
588
704
  {
589
705
  name: 'preview_personal_project_action',
590
706
  title: 'PREVIEW · Authorize a personal project write',
@@ -10,12 +10,15 @@ const HANDLERS = Object.freeze({
10
10
  agentInvocation: true,
11
11
  agentToolName: 'get_collaboration_preferences'
12
12
  }),
13
+ get_user_taste_skill: (app, input) => app.getUserTasteSkill(input),
13
14
  resolve_deferred_preference_conflict: (app, input) =>
14
15
  app.resolveDeferredPreferenceConflict({
15
16
  ...input,
16
17
  operationActor: 'agent'
17
18
  }),
18
19
  capture_session_knowledge: (app, input) => app.captureSessionKnowledge(input),
20
+ record_workflow_transition_observation: (app, input) =>
21
+ app.recordWorkflowTransitionObservation(input),
19
22
  record_decision_trace: (app, input) => app.recordDecisionTrace(input),
20
23
  search_knowledge_graph: (app, input) => app.searchKnowledge({
21
24
  ...input,
@@ -34,6 +37,10 @@ const HANDLERS = Object.freeze({
34
37
  app.searchCurrentProjectKnowledge(input),
35
38
  discover_common_knowledge_candidates: (app, input) =>
36
39
  app.discoverCommonKnowledgeCandidates(input),
40
+ discover_personal_global_preference_candidates: (app, input) =>
41
+ app.discoverPersonalGlobalPreferenceCandidates(input),
42
+ preview_personal_global_preference_decision: (app, input) =>
43
+ app.previewPersonalGlobalPreferenceDecision(input),
37
44
  preview_common_knowledge_promotion: (app, input) =>
38
45
  app.previewCommonKnowledgePromotion(input),
39
46
  apply_common_knowledge_promotion: (app, input) => {
@@ -65,6 +72,9 @@ const HANDLERS = Object.freeze({
65
72
  record_knowledge_review_progress: (app, input) =>
66
73
  app.recordKnowledgeReviewProgress(input),
67
74
  finish_knowledge_review: (app, input) => app.finishKnowledgeReview(input),
75
+ list_workflow_candidates: (app, input) => app.listWorkflowCandidates(input),
76
+ recommend_next_workflow_steps: (app, input) =>
77
+ app.recommendNextWorkflowSteps(input),
68
78
  revise_personal_knowledge: (app, input) => app.reviseKnowledgeItem({
69
79
  ...input,
70
80
  operationActor: 'agent'
@@ -73,10 +83,6 @@ const HANDLERS = Object.freeze({
73
83
  ...input,
74
84
  operationActor: 'agent'
75
85
  }),
76
- set_personal_preference_scope: (app, input) => app.setPersonalPreferenceScope({
77
- ...input,
78
- operationActor: 'agent'
79
- }),
80
86
  preview_personal_project_action: (app, input) =>
81
87
  app.previewKnowledgeProjectAction(input),
82
88
  apply_personal_project_action: (app, input) => {
@@ -12,7 +12,7 @@ General:
12
12
  --version, -v
13
13
 
14
14
  Local service:
15
- start [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
15
+ start [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME] (checks Agent setup)
16
16
  stop [--data-dir DIR]
17
17
  restart [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
18
18
  status [--json] [--data-dir DIR] [--port PORT]