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 @@
1
+ import{A as e,E as t,F as n,G as r,J as i,Q as a,S as o,Y as s,p as c,q as l,w as u,x as d}from"./i18n-CWfd8Dtt.js";import{a as f,c as p,d as m,f as h,g,i as _,l as v,n as y,o as b,p as ee,r as x,s as S,t as C,u as w}from"./useApi-CROJJdhE-BLQiPVw0.js";var T=typeof document<`u`,E=/#/g,te=/&/g,ne=/\//g,re=/=/g,D=/\?/g,O=/\+/g,ie=/%5B/g,k=/%5D/g,ae=/%5E/g,oe=/%60/g,A=/%7B/g,se=/%7C/g,j=/%7D/g,M=/%20/g;function N(e){return e==null?``:encodeURI(``+e).replace(se,`|`).replace(ie,`[`).replace(k,`]`)}function ce(e){return N(e).replace(A,`{`).replace(j,`}`).replace(ae,`^`)}function P(e){return N(e).replace(O,`%2B`).replace(M,`+`).replace(E,`%23`).replace(te,`%26`).replace(oe,"`").replace(A,`{`).replace(j,`}`).replace(ae,`^`)}function F(e){return P(e).replace(re,`%3D`)}function le(e){return N(e).replace(E,`%23`).replace(D,`%3F`)}function ue(e){return le(e).replace(ne,`%2F`)}function I(e){if(e==null)return null;try{return decodeURIComponent(``+e)}catch{}return``+e}var L=/\/$/,R=e=>e.replace(L,``);function z(e,t,n=`/`){let r,i={},a=``,o=``,s=t.indexOf(`#`),c=t.indexOf(`?`);return c=s>=0&&c>s?-1:c,c>=0&&(r=t.slice(0,c),a=t.slice(c,s>0?s:t.length),i=e(a.slice(1))),s>=0&&(r||=t.slice(0,s),o=t.slice(s,t.length)),r=G(r??t,n),{fullPath:r+a+o,path:r,query:i,hash:I(o)}}function de(e,t){let n=t.query?e(t.query):``;return t.path+(n&&`?`)+n+(t.hash||``)}function B(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||`/`}function fe(e,t,n){let r=t.matched.length-1,i=n.matched.length-1;return r>-1&&r===i&&V(t.matched[r],n.matched[i])&&H(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function V(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function H(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var n in e)if(!U(e[n],t[n]))return!1;return!0}function U(e,t){return _(e)?W(e,t):_(t)?W(t,e):(e&&e.valueOf())===(t&&t.valueOf())}function W(e,t){return _(t)?e.length===t.length&&e.every((e,n)=>e===t[n]):e.length===1&&e[0]===t}function G(e,t){if(e.startsWith(`/`))return e;if(!e)return t;let n=t.split(`/`),r=e.split(`/`),i=r[r.length-1];(i===`..`||i===`.`)&&r.push(``);let a=n.length-1,o,s;for(o=0;o<r.length;o++)if(s=r[o],s!==`.`)if(s===`..`)a>1&&a--;else break;return n.slice(0,a).join(`/`)+`/`+r.slice(o).join(`/`)}var K={path:`/`,name:void 0,params:{},query:{},hash:``,fullPath:`/`,matched:[],meta:{},redirectedFrom:void 0};function q(e){if(!e)if(T){let t=document.querySelector(`base`);e=t&&t.getAttribute(`href`)||`/`,e=e.replace(/^\w+:\/\/[^/]+/,``)}else e=`/`;return e[0]!==`/`&&e[0]!==`#`&&(e=`/`+e),R(e)}var J=/^[^#]+#/;function Y(e,t){return e.replace(J,`#`)+t}function X(e,t){let n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}var Z=()=>({left:window.scrollX,top:window.scrollY});function pe(e){let t;if(`el`in e){let n=e.el,r=typeof n==`string`&&n.startsWith(`#`),i=typeof n==`string`?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!i)return;t=X(i,e)}else t=e;`scrollBehavior`in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left==null?window.scrollX:t.left,t.top==null?window.scrollY:t.top)}function me(e,t){return(history.state?history.state.position-t:-1)+e}var he=new Map;function ge(e,t){he.set(e,t)}function _e(e){let t=he.get(e);return he.delete(e),t}function ve(e){return typeof e==`string`||e&&typeof e==`object`}function ye(e){return typeof e==`string`||typeof e==`symbol`}function be(e){let t={};if(e===``||e===`?`)return t;let n=(e[0]===`?`?e.slice(1):e).split(`&`);for(let e=0;e<n.length;++e){let r=n[e].replace(O,` `),i=r.indexOf(`=`),a=I(i<0?r:r.slice(0,i)),o=i<0?null:I(r.slice(i+1));if(a in t){let e=t[a];_(e)||(e=t[a]=[e]),e.push(o)}else t[a]=o}return t}function xe(e){let t=``;for(let n in e){let r=e[n];if(n=F(n),r==null){r!==void 0&&(t+=(t.length?`&`:``)+n);continue}(_(r)?r.map(e=>e&&P(e)):[r&&P(r)]).forEach(e=>{e!==void 0&&(t+=(t.length?`&`:``)+n,e!=null&&(t+=`=`+e))})}return t}function Se(e){let t={};for(let n in e){let r=e[n];r!==void 0&&(t[n]=_(r)?r.map(e=>e==null?null:``+e):r==null?r:``+r)}return t}function Q(){let e=[];function t(t){return e.push(t),()=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function $(e,t,n,r,i,a=e=>e()){let o=r&&(r.enterCallbacks[i]=r.enterCallbacks[i]||[]);return()=>new Promise((s,c)=>{let l=e=>{e===!1?c(x(4,{from:n,to:t})):e instanceof Error?c(e):ve(e)?c(x(2,{from:t,to:e})):(o&&r.enterCallbacks[i]===o&&typeof e==`function`&&o.push(e),s())},u=a(()=>e.call(r&&r.instances[i],t,n,l)),d=Promise.resolve(u);e.length<3&&(d=d.then(l)),d.catch(e=>c(e))})}function Ce(e,t,n,r,i=e=>e()){let a=[];for(let o of e)for(let e in o.components){let s=o.components[e];if(!(t!==`beforeRouteEnter`&&!o.instances[e]))if(S(s)){let c=(s.__vccOpts||s)[t];c&&a.push($(c,n,r,o,e,i))}else{let c=s();a.push(()=>c.then(a=>{if(!a)throw Error(`Couldn't resolve component "${e}" at "${o.path}"`);let s=f(a)?a.default:a;o.mods[e]=a,o.components[e]=s;let c=(s.__vccOpts||s)[t];return c&&$(c,n,r,o,e,i)()}))}}return a}function we(e,t){let n=[],r=[],i=[],a=Math.max(t.matched.length,e.matched.length);for(let o=0;o<a;o++){let a=t.matched[o];a&&(e.matched.find(e=>V(e,a))?r.push(a):n.push(a));let s=e.matched[o];s&&(t.matched.find(e=>V(e,s))||i.push(s))}return[n,r,i]}var Te=()=>location.protocol+`//`+location.host;function Ee(e,t){let{pathname:n,search:r,hash:i}=t,a=e.indexOf(`#`);if(a>-1){let t=i.includes(e.slice(a))?e.slice(a).length:1,n=i.slice(t);return n[0]!==`/`&&(n=`/`+n),B(n,``)}return B(n,e)+r+i}function De(e,t,n,r){let i=[],a=[],o=null,s=({state:a})=>{let s=Ee(e,location),c=n.value,l=t.value,u=0;if(a){if(n.value=s,t.value=a,o&&o===c){o=null;return}u=l?a.position-l.position:0}else r(s);i.forEach(e=>{e(n.value,c,{delta:u,type:`pop`,direction:u?u>0?`forward`:`back`:``})})};function c(){o=n.value}function l(e){i.push(e);let t=()=>{let t=i.indexOf(e);t>-1&&i.splice(t,1)};return a.push(t),t}function u(){if(document.visibilityState===`hidden`){let{history:e}=window;if(!e.state)return;e.replaceState(y({},e.state,{scroll:Z()}),``)}}function d(){for(let e of a)e();a=[],window.removeEventListener(`popstate`,s),window.removeEventListener(`pagehide`,u),document.removeEventListener(`visibilitychange`,u)}return window.addEventListener(`popstate`,s),window.addEventListener(`pagehide`,u),document.addEventListener(`visibilitychange`,u),{pauseListeners:c,listen:l,destroy:d}}function Oe(e,t,n,r=!1,i=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:i?Z():null}}function ke(e){let{history:t,location:n}=window,r={value:Ee(e,n)},i={value:t.state};i.value||a(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function a(r,a,o){let s=e.indexOf(`#`),c=s>-1?(n.host&&document.querySelector(`base`)?e:e.slice(s))+r:Te()+e+r;try{t[o?`replaceState`:`pushState`](a,``,c),i.value=a}catch(e){console.error(e),n[o?`replace`:`assign`](c)}}function o(e,n){a(e,y({},t.state,Oe(i.value.back,e,i.value.forward,!0),n,{position:i.value.position}),!0),r.value=e}function s(e,n){let o=y({},i.value,t.state,{forward:e,scroll:Z()});a(o.current,o,!0),a(e,y({},Oe(r.value,e,null),{position:o.position+1},n),!1),r.value=e}return{location:r,state:i,push:s,replace:o}}function Ae(e){e=q(e);let t=ke(e),n=De(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}let i=y({location:``,base:e,go:r,createHref:Y.bind(null,e)},t,n);return Object.defineProperty(i,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(i,"state",{enumerable:!0,get:()=>t.state.value}),i}var je={type:0,value:``},Me=/[a-zA-Z0-9_]/;function Ne(e){if(!e)return[[]];if(e===`/`)return[[je]];if(!e.startsWith(`/`))throw Error(`Invalid path "${e}"`);function t(e){throw Error(`ERR (${n})/"${l}": ${e}`)}let n=0,r=n,i=[],a;function o(){a&&i.push(a),a=[]}let s=0,c,l=``,u=``;function d(){l&&=(n===0?a.push({type:0,value:l}):n===1||n===2||n===3?(a.length>1&&(c===`*`||c===`+`)&&t(`A repeatable param (${l}) must be alone in its segment. eg: '/:ids+.`),a.push({type:1,value:l,regexp:u,repeatable:c===`*`||c===`+`,optional:c===`*`||c===`?`})):t(`Invalid state to consume buffer`),``)}function f(){l+=c}for(;s<e.length;)switch(c=e[s++],n){case 0:c===`\\`?(r=n,n=4):c===`/`?(l&&d(),o()):c===`:`?(d(),n=1):f();break;case 4:f(),n=r;break;case 1:c===`(`?n=2:Me.test(c)?f():(d(),n=0,c!==`*`&&c!==`?`&&c!==`+`&&s--);break;case 2:c===`)`?u[u.length-1]==`\\`?u=u.slice(0,-1)+c:n=3:u+=c;break;case 3:d(),n=0,c!==`*`&&c!==`?`&&c!==`+`&&s--,u=``;break;default:t(`Unknown state`);break}return n===2&&t(`Unfinished custom RegExp for param "${l}"`),d(),o(),i}var Pe=`[^/]+?`,Fe={sensitive:!1,strict:!1,start:!0,end:!0},Ie=/[.+*?^${}()[\]/\\]/g;function Le(e,t){let n=y({},Fe,t),r=[],i=n.start?`^`:``,a=[];for(let t of e){let e=t.length?[]:[90];n.strict&&!t.length&&(i+=`/`);for(let r=0;r<t.length;r++){let o=t[r],s=40+(n.sensitive?.25:0);if(o.type===0)r||(i+=`/`),i+=o.value.replace(Ie,`\\$&`),s+=40;else if(o.type===1){let{value:e,repeatable:n,optional:c,regexp:l}=o;a.push({name:e,repeatable:n,optional:c});let u=l||Pe;if(u!==Pe){s+=10;try{RegExp(`(${u})`)}catch(t){throw Error(`Invalid custom RegExp for param "${e}" (${u}): `+t.message)}}let d=n?`((?:${u})(?:/(?:${u}))*)`:`(${u})`;r||(d=c&&t.length<2?`(?:/${d})`:`/`+d),c&&(d+=`?`),i+=d,s+=20,c&&(s+=-8),n&&(s+=-20),u===`.*`&&(s+=-50)}e.push(s)}r.push(e)}if(n.strict&&n.end){let e=r.length-1;r[e][r[e].length-1]+=.7000000000000001}n.strict||(i+=`/?`),n.end?i+=`$`:n.strict&&!i.endsWith(`/`)&&(i+=`(?:/|$)`);let o=new RegExp(i,n.sensitive?``:`i`);function s(e){let t=e.match(o),n={};if(!t)return null;for(let e=1;e<t.length;e++){let r=t[e]||``,i=a[e-1];n[i.name]=r&&i.repeatable?r.split(`/`):r}return n}function c(t){let n=``,r=!1;for(let i of e){(!r||!n.endsWith(`/`))&&(n+=`/`),r=!1;for(let e of i)if(e.type===0)n+=e.value;else if(e.type===1){let{value:a,repeatable:o,optional:s}=e,c=a in t?t[a]:``;if(_(c)&&!o)throw Error(`Provided param "${a}" is an array but it is not repeatable (* or + modifiers)`);let l=_(c)?c.join(`/`):c;if(!l)if(s)i.length<2&&(n.endsWith(`/`)?n=n.slice(0,-1):r=!0);else throw Error(`Missing required param "${a}"`);n+=l}}return n||`/`}return{re:o,score:r,keys:a,parse:s,stringify:c}}function Re(e,t){let n=0;for(;n<e.length&&n<t.length;){let r=t[n]-e[n];if(r)return r;n++}return e.length<t.length?e.length===1&&e[0]===80?-1:1:e.length>t.length?t.length===1&&t[0]===80?1:-1:0}function ze(e,t){let n=0,r=e.score,i=t.score;for(;n<r.length&&n<i.length;){let e=Re(r[n],i[n]);if(e)return e;n++}if(Math.abs(i.length-r.length)===1){if(Be(r))return 1;if(Be(i))return-1}return i.length-r.length}function Be(e){let t=e[e.length-1];return e.length>0&&t[t.length-1]<0}var Ve={strict:!1,end:!0,sensitive:!1};function He(e,t,n){let r=y(Le(Ne(e.path),n),{record:e,parent:t,children:[],alias:[]});return t&&!r.record.aliasOf==!t.record.aliasOf&&t.children.push(r),r}function Ue(e,t){let n=[],r=new Map;t=v(Ve,t);function i(e){return r.get(e)}function a(e,n,r){let i=!r,s=Ge(e);s.aliasOf=r&&r.record;let l=v(t,e),u=[s];if(`alias`in e){let t=typeof e.alias==`string`?[e.alias]:e.alias;for(let e of t)u.push(Ge(y({},s,{components:r?r.record.components:s.components,path:e,aliasOf:r?r.record:s})))}let d,f;for(let t of u){let{path:u}=t;if(n&&u[0]!==`/`){let e=n.record.path,r=e[e.length-1]===`/`?``:`/`;t.path=n.record.path+(u&&r+u)}if(d=He(t,n,l),r?r.alias.push(d):(f||=d,f!==d&&f.alias.push(d),i&&e.name&&!qe(d)&&o(e.name)),Ze(d)&&c(d),s.children){let e=s.children;for(let t=0;t<e.length;t++)a(e[t],d,r&&r.children[t])}r||=d}return f?()=>{o(f)}:w}function o(e){if(ye(e)){let t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(o),t.alias.forEach(o))}else{let t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(o),e.alias.forEach(o))}}function s(){return n}function c(e){let t=Ye(e,n);n.splice(t,0,e),e.record.name&&!qe(e)&&r.set(e.record.name,e)}function l(e,t){let i,a={},o,s;if(`name`in e&&e.name){if(i=r.get(e.name),!i)throw x(1,{location:e});s=i.record.name,a=y(We(t.params,i.keys.filter(e=>!e.optional).concat(i.parent?i.parent.keys.filter(e=>e.optional):[]).map(e=>e.name)),e.params&&We(e.params,i.keys.map(e=>e.name))),o=i.stringify(a)}else if(e.path!=null)o=e.path,i=n.find(e=>e.re.test(o)),i&&(a=i.parse(o),s=i.record.name,i.keys.forEach(e=>{e.optional&&!a[e.name]&&delete a[e.name]}));else{if(i=t.name?r.get(t.name):n.find(e=>e.re.test(t.path)),!i)throw x(1,{location:e,currentLocation:t});s=i.record.name,a=y({},t.params,e.params),o=i.stringify(a)}let c=[],l=i;for(;l;)c.unshift(l.record),l=l.parent;return{name:s,path:o,params:a,matched:c,meta:Je(c)}}e.forEach(e=>a(e));function u(){n.length=0,r.clear()}return{addRoute:a,resolve:l,removeRoute:o,clearRoutes:u,getRoutes:s,getRecordMatcher:i}}function We(e,t){let n={};for(let r of t)r in e&&(n[r]=e[r]);return n}function Ge(e){let t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:Ke(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:`components`in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function Ke(e){let t={},n=e.props||!1;if(`component`in e)t.default=n;else for(let r in e.components)t[r]=typeof n==`object`?n[r]:n;return t}function qe(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function Je(e){return e.reduce((e,t)=>y(e,t.meta),{})}function Ye(e,t){let n=0,r=t.length;for(;n!==r;){let i=n+r>>1;ze(e,t[i])<0?r=i:n=i+1}let i=Xe(e);return i&&(r=t.lastIndexOf(i,r-1)),r}function Xe(e){let t=e;for(;t=t.parent;)if(Ze(t)&&ze(e,t)===0)return t}function Ze({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function Qe(e){let t=u(h),n=u(m),r=c(()=>{let n=a(e.to);return t.resolve(n)}),i=c(()=>{let{matched:e}=r.value,{length:t}=e,i=e[t-1],a=n.matched;if(!i||!a.length)return-1;let o=a.findIndex(V.bind(null,i));if(o>-1)return o;let s=rt(e[t-2]);return t>1&&rt(i)===s&&a[a.length-1].path!==s?a.findIndex(V.bind(null,e[t-2])):o}),o=c(()=>i.value>-1&&nt(n.params,r.value.params)),s=c(()=>i.value>-1&&i.value===n.matched.length-1&&H(n.params,r.value.params));function l(n={}){if(tt(n)){let n=t[a(e.replace)?`replace`:`push`](a(e.to)).catch(w);return e.viewTransition&&typeof document<`u`&&`startViewTransition`in document&&document.startViewTransition(()=>n),n}return Promise.resolve()}return{route:r,href:c(()=>r.value.href),isActive:o,isExactActive:s,navigate:l}}function $e(e){return e.length===1?e[0]:e}var et=d({name:`RouterLink`,compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:`page`},viewTransition:Boolean},useLink:Qe,setup(e,{slots:t}){let n=r(Qe(e)),{options:i}=u(h),a=c(()=>({[it(e.activeClass,i.linkActiveClass,`router-link-active`)]:n.isActive,[it(e.exactActiveClass,i.linkExactActiveClass,`router-link-exact-active`)]:n.isExactActive}));return()=>{let r=t.default&&$e(t.default(n));return e.custom?r:o(`a`,{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:a.value},r)}}});function tt(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){let t=e.currentTarget.getAttribute(`target`);if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function nt(e,t){for(let n in t){let r=t[n],i=e[n];if(typeof r==`string`){if(r!==i)return!1}else if(!_(i)||i.length!==r.length||r.some((e,t)=>e.valueOf()!==i[t].valueOf()))return!1}return!0}function rt(e){return e?e.aliasOf?e.aliasOf.path:e.path:``}var it=(e,t,n)=>e??t??n,at=d({name:`RouterView`,inheritAttrs:!1,props:{name:{type:String,default:`default`},route:Object},compatConfig:{MODE:3},setup(t,{attrs:r,slots:i}){let s=u(ee),d=c(()=>t.route||s.value),f=u(g,0),m=c(()=>{let e=a(f),{matched:t}=d.value,n;for(;(n=t[e])&&!n.components;)e++;return e}),h=c(()=>d.value.matched[m.value]);e(g,c(()=>m.value+1)),e(p,h),e(ee,d);let _=l();return n(()=>[_.value,h.value,t.name],([e,t,n],[r,i,a])=>{t&&(t.instances[n]=e,i&&i!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=i.leaveGuards),t.updateGuards.size||(t.updateGuards=i.updateGuards))),e&&t&&(!i||!V(t,i)||!r)&&(t.enterCallbacks[n]||[]).forEach(t=>t(e))},{flush:`post`}),()=>{let e=d.value,n=t.name,a=h.value,s=a&&a.components[n];if(!s)return ot(i.default,{Component:s,route:e});let c=a.props[n],l=o(s,y({},c?c===!0?e.params:typeof c==`function`?c(e):c:null,r,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(a.instances[n]=null)},ref:_}));return ot(i.default,{Component:l,route:e})||l}}});function ot(e,t){if(!e)return null;let n=e(t);return n.length===1?n[0]:n}var st=at;function ct(e){let n=Ue(e.routes,e),r=e.parseQuery||be,o=e.stringifyQuery||xe,c=e.history,l=Q(),u=Q(),d=Q(),f=s(K),p=K;T&&e.scrollBehavior&&`scrollRestoration`in history&&(history.scrollRestoration=`manual`);let g=C.bind(null,e=>``+e),v=C.bind(null,ue),S=C.bind(null,I);function E(e,t){let r,i;return ye(e)?(r=n.getRecordMatcher(e),i=t):i=e,n.addRoute(i,r)}function te(e){let t=n.getRecordMatcher(e);t&&n.removeRoute(t)}function ne(){return n.getRoutes().map(e=>e.record)}function re(e){return!!n.getRecordMatcher(e)}function D(e,t){if(t=y({},t||f.value),typeof e==`string`){let i=z(r,e,t.path),a=n.resolve({path:i.path},t),o=c.createHref(i.fullPath);return y(i,a,{params:S(a.params),redirectedFrom:void 0,href:o})}let i;if(e.path!=null)i=y({},e,{path:z(r,e.path,t.path).path});else{let n=y({},e.params);for(let e in n)n[e]??delete n[e];i=y({},e,{params:v(n)}),t.params=v(t.params)}let a=n.resolve(i,t),s=e.hash||``;a.params=g(S(a.params));let l=de(o,y({},e,{hash:ce(s),path:a.path})),u=c.createHref(l);return y({fullPath:l,hash:s,query:o===xe?Se(e.query):e.query||{}},a,{redirectedFrom:void 0,href:u})}function O(e){return typeof e==`string`?z(r,e,f.value.path):y({},e)}function ie(e,t){if(p!==e)return x(8,{from:t,to:e})}function k(e){return A(e)}function ae(e){return k(y(O(e),{replace:!0}))}function oe(e,t){let n=e.matched[e.matched.length-1];if(n&&n.redirect){let{redirect:r}=n,i=typeof r==`function`?r(e,t):r;return typeof i==`string`&&(i=i.includes(`?`)||i.includes(`#`)?i=O(i):{path:i},i.params={}),y({query:e.query,hash:e.hash,params:i.path==null?e.params:{}},i)}}function A(e,t){let n=p=D(e),r=f.value,i=e.state,a=e.force,s=e.replace===!0,c=oe(n,r);if(c)return A(y(O(c),{state:typeof c==`object`?y({},i,c.state):i,force:a,replace:s}),t||n);let l=n;l.redirectedFrom=t;let u;return!a&&fe(o,r,n)&&(u=x(16,{to:l,from:r}),W(r,r,!0,!1)),(u?Promise.resolve(u):M(l,r)).catch(e=>b(e)?b(e,2)?e:U(e):V(e,l,r)).then(e=>{if(e){if(b(e,2))return A(y({replace:s},O(e.to),{state:typeof e.to==`object`?y({},i,e.to.state):i,force:a}),t||l)}else e=P(l,r,!0,s,i);return N(l,r,e),e})}function se(e,t){let n=ie(e,t);return n?Promise.reject(n):Promise.resolve()}function j(e){let t=J.values().next().value;return t&&typeof t.runWithContext==`function`?t.runWithContext(e):e()}function M(e,t){let n,[r,i,a]=we(e,t);n=Ce(r.reverse(),`beforeRouteLeave`,e,t);for(let i of r)i.leaveGuards.forEach(r=>{n.push($(r,e,t))});let o=se.bind(null,e,t);return n.push(o),X(n).then(()=>{n=[];for(let r of l.list())n.push($(r,e,t));return n.push(o),X(n)}).then(()=>{n=Ce(i,`beforeRouteUpdate`,e,t);for(let r of i)r.updateGuards.forEach(r=>{n.push($(r,e,t))});return n.push(o),X(n)}).then(()=>{n=[];for(let r of a)if(r.beforeEnter)if(_(r.beforeEnter))for(let i of r.beforeEnter)n.push($(i,e,t));else n.push($(r.beforeEnter,e,t));return n.push(o),X(n)}).then(()=>(e.matched.forEach(e=>e.enterCallbacks={}),n=Ce(a,`beforeRouteEnter`,e,t,j),n.push(o),X(n))).then(()=>{n=[];for(let r of u.list())n.push($(r,e,t));return n.push(o),X(n)}).catch(e=>b(e,8)?e:Promise.reject(e))}function N(e,t,n){d.list().forEach(r=>j(()=>r(e,t,n)))}function P(e,t,n,r,i){let a=ie(e,t);if(a)return a;let o=t===K,s=T?history.state:{};n&&(r||o?c.replace(e.fullPath,y({scroll:o&&s&&s.scroll},i)):c.push(e.fullPath,i)),f.value=e,W(e,t,n,o),U()}let F;function le(){F||=c.listen((e,t,n)=>{if(!Y.listening)return;let r=D(e),i=oe(r,Y.currentRoute.value);if(i){A(y(i,{replace:!0,force:!0}),r).catch(w);return}p=r;let a=f.value;T&&ge(me(a.fullPath,n.delta),Z()),M(r,a).catch(e=>b(e,12)?e:b(e,2)?(A(y(O(e.to),{force:!0}),r).then(e=>{b(e,20)&&!n.delta&&n.type===`pop`&&c.go(-1,!1)}).catch(w),Promise.reject()):(n.delta&&c.go(-n.delta,!1),V(e,r,a))).then(e=>{e||=P(r,a,!1),e&&(n.delta&&!b(e,8)?c.go(-n.delta,!1):n.type===`pop`&&b(e,20)&&c.go(-1,!1)),N(r,a,e)}).catch(w)})}let L=Q(),R=Q(),B;function V(e,t,n){U(e);let r=R.list();return r.length?r.forEach(r=>r(e,t,n)):console.error(e),Promise.reject(e)}function H(){return B&&f.value!==K?Promise.resolve():new Promise((e,t)=>{L.add([e,t])})}function U(e){return B||(B=!e,le(),L.list().forEach(([t,n])=>e?n(e):t()),L.reset()),e}function W(n,r,i,a){let{scrollBehavior:o}=e;if(!T||!o)return Promise.resolve();let s=!i&&_e(me(n.fullPath,0))||(a||!i)&&history.state&&history.state.scroll||null;return t().then(()=>o(n,r,s)).then(e=>n===f.value&&e&&pe(e)).catch(e=>n===f.value&&V(e,n,r))}let G=e=>c.go(e),q,J=new Set,Y={currentRoute:f,listening:!0,addRoute:E,removeRoute:te,clearRoutes:n.clearRoutes,hasRoute:re,getRoutes:ne,resolve:D,options:e,push:k,replace:ae,go:G,back:()=>G(-1),forward:()=>G(1),beforeEach:l.add,beforeResolve:u.add,afterEach:d.add,onError:R.add,isReady:H,install(e){e.component(`RouterLink`,et),e.component(`RouterView`,st),e.config.globalProperties.$router=Y,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>a(f)}),T&&!q&&f.value===K&&(q=!0,k(c.location).catch(e=>{}));let t={};for(let e in K)Object.defineProperty(t,e,{get:()=>f.value[e],enumerable:!0});e.provide(h,Y),e.provide(m,i(t)),e.provide(ee,f);let n=e.unmount;J.add(e),e.unmount=function(){J.delete(e),J.size<1&&(p=K,F&&F(),F=null,f.value=K,q=!1,B=!1),n()}}};function X(e){return e.reduce((e,t)=>e.then(()=>j(t)),Promise.resolve())}return Y}export{Ae as i,st as n,ct as r,et as t};
@@ -9,14 +9,15 @@
9
9
  />
10
10
  <link rel="icon" type="image/png" href="/assets/fuli-logo-ER1wQ8Kk.png" />
11
11
  <title>复利 · Context Graph</title>
12
- <script type="module" crossorigin src="/assets/index-BzhNfzU_.js"></script>
13
- <link rel="modulepreload" crossorigin href="/assets/i18n-CDxBs1sz.js">
14
- <link rel="modulepreload" crossorigin href="/assets/console-DE40hXGC.js">
15
- <link rel="modulepreload" crossorigin href="/assets/paths-DwHeiGMT.js">
16
- <link rel="modulepreload" crossorigin href="/assets/vue-router-Drfl1F8i.js">
12
+ <script type="module" crossorigin src="/assets/index-F5e2sQ7p.js"></script>
13
+ <link rel="modulepreload" crossorigin href="/assets/i18n-CWfd8Dtt.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/console-DMvHxKf9.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/useApi-CROJJdhE-BLQiPVw0.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/vue-router-DN8x6iY1.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/fuli-logo-Bu9WBuQe.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BDNMzG2s.js">
19
- <link rel="stylesheet" crossorigin href="/assets/index-CCOGfZzE.css">
19
+ <link rel="modulepreload" crossorigin href="/assets/paths-CDREouhi.js">
20
+ <link rel="stylesheet" crossorigin href="/assets/index-X2j29j8i.css">
20
21
  </head>
21
22
  <body>
22
23
  <div id="app"></div>
@@ -1,9 +1,13 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from typing import Annotated
3
3
 
4
- from fastapi import Depends, FastAPI, Header, Query
4
+ from fastapi import Depends, FastAPI, Header, HTTPException, Query
5
5
 
6
- from .auth import matches_bootstrap_token
6
+ from .auth import (
7
+ matches_bootstrap_token,
8
+ matches_human_review_token,
9
+ matches_workflow_observation_token,
10
+ )
7
11
  from .config import Settings, get_settings
8
12
  from .graph_models import GraphResult
9
13
  from .knowledge_usage_models import KnowledgeUsageCreate, KnowledgeUsageResult
@@ -30,7 +34,30 @@ from .project_action_models import (
30
34
  KnowledgeProjectActionResult,
31
35
  KnowledgeProjectPreviewRecord,
32
36
  KnowledgeProjectPreviewRequest,
37
+ PersonalProjectRelationReviewRecord,
38
+ PersonalProjectRelationReviewRequest,
39
+ )
40
+ from .personal_global_preference_models import (
41
+ PersonalGlobalPreferenceDecisionApply,
42
+ PersonalGlobalPreferenceDecisionInspection,
43
+ PersonalGlobalPreferenceDecisionIntent,
44
+ PersonalGlobalPreferenceDecisionPreview,
45
+ PersonalGlobalPreferenceDecisionRecord,
46
+ PersonalGlobalPreferenceDecisionStatusRequest,
47
+ PersonalGlobalPreferenceDecisionStatusResult,
48
+ PersonalGlobalPreferenceScopeOptions,
49
+ PersonalGlobalPreferenceScopeOptionsRequest,
50
+ )
51
+ from .workflow_candidate_models import (
52
+ WorkflowCandidate,
53
+ WorkflowCandidatePage,
54
+ WorkflowCandidateReview,
55
+ WorkflowCandidateReviewIntent,
56
+ WorkflowCandidateReviewPreview,
57
+ WorkflowCandidateSearch,
58
+ WorkflowRecommendationPage,
33
59
  )
60
+ from .workflow_observation_models import WorkflowTransitionObservation
34
61
  from .models import (
35
62
  BootstrapRequest,
36
63
  BootstrapResult,
@@ -105,6 +132,7 @@ from .knowledge_management import (
105
132
  from .project_knowledge import (
106
133
  apply_knowledge_project_action,
107
134
  preview_knowledge_project_action,
135
+ review_personal_project_relation,
108
136
  )
109
137
  from .preference_conflicts import (
110
138
  complete_preference_conflict,
@@ -112,6 +140,20 @@ from .preference_conflicts import (
112
140
  list_preference_conflicts,
113
141
  resolve_preference_conflict,
114
142
  )
143
+ from .personal_global_preferences import (
144
+ apply_personal_global_preference_decision,
145
+ inspect_personal_global_preference_decision,
146
+ personal_global_preference_decision_status,
147
+ personal_global_preference_scope_options,
148
+ preview_personal_global_preference_decision,
149
+ )
150
+ from .workflow_candidates import (
151
+ preview_workflow_candidate_review,
152
+ recommend_workflow_candidates,
153
+ review_workflow_candidate,
154
+ search_workflow_candidates,
155
+ )
156
+ from .workflow_observations import record_workflow_transition_observation
115
157
  from .runtime import GraphitiRuntime
116
158
  from .store import GraphStore
117
159
 
@@ -147,6 +189,51 @@ def create_app(settings: Settings | None = None) -> FastAPI:
147
189
 
148
190
  Actor = Annotated[dict, Depends(current_actor)]
149
191
 
192
+ async def current_human_reviewer(
193
+ actor: Actor,
194
+ x_fuli_human_review_token: Annotated[str | None, Header()] = None,
195
+ ) -> dict:
196
+ if not resolved_settings.human_review_token:
197
+ raise HTTPException(
198
+ status_code=503,
199
+ detail='human workflow review channel is not configured',
200
+ )
201
+ if not matches_human_review_token(
202
+ x_fuli_human_review_token,
203
+ resolved_settings.human_review_token,
204
+ ):
205
+ raise HTTPException(
206
+ status_code=403,
207
+ detail='independent human review proof required',
208
+ )
209
+ return {**actor, '_human_review_verified': True}
210
+
211
+ HumanReviewer = Annotated[dict, Depends(current_human_reviewer)]
212
+
213
+ async def current_workflow_observer(
214
+ actor: Actor,
215
+ x_fuli_workflow_observation_token: Annotated[
216
+ str | None,
217
+ Header(),
218
+ ] = None,
219
+ ) -> dict:
220
+ if not resolved_settings.workflow_observation_token:
221
+ raise HTTPException(
222
+ status_code=503,
223
+ detail='MCP host workflow observation channel is not configured',
224
+ )
225
+ if not matches_workflow_observation_token(
226
+ x_fuli_workflow_observation_token,
227
+ resolved_settings.workflow_observation_token,
228
+ ):
229
+ raise HTTPException(
230
+ status_code=403,
231
+ detail='independent MCP host observation proof required',
232
+ )
233
+ return actor
234
+
235
+ WorkflowObserver = Annotated[dict, Depends(current_workflow_observer)]
236
+
150
237
  @application.get('/health')
151
238
  async def health() -> dict:
152
239
  return await store.health()
@@ -337,6 +424,137 @@ def create_app(settings: Settings | None = None) -> FastAPI:
337
424
  async def commit_personal(request: KnowledgeCommit, actor: Actor) -> CommitResult:
338
425
  return await store.commit_personal(actor, request)
339
426
 
427
+ @application.post('/v1/workflow-observations', response_model=CommitResult)
428
+ async def record_workflow_observation(
429
+ request: WorkflowTransitionObservation,
430
+ actor: WorkflowObserver,
431
+ ) -> CommitResult:
432
+ return await record_workflow_transition_observation(store, actor, request)
433
+
434
+ @application.post(
435
+ '/v1/personal-global-preference-candidates/decision-status',
436
+ response_model=PersonalGlobalPreferenceDecisionStatusResult,
437
+ )
438
+ async def personal_global_preference_status(
439
+ request: PersonalGlobalPreferenceDecisionStatusRequest,
440
+ actor: Actor,
441
+ ) -> PersonalGlobalPreferenceDecisionStatusResult:
442
+ return await personal_global_preference_decision_status(
443
+ store,
444
+ actor,
445
+ request,
446
+ )
447
+
448
+ @application.post(
449
+ '/v1/personal-global-preference-candidates/{candidate_id}/scope-options',
450
+ response_model=PersonalGlobalPreferenceScopeOptions,
451
+ )
452
+ async def personal_global_preference_scopes(
453
+ candidate_id: str,
454
+ request: PersonalGlobalPreferenceScopeOptionsRequest,
455
+ actor: Actor,
456
+ ) -> PersonalGlobalPreferenceScopeOptions:
457
+ return await personal_global_preference_scope_options(
458
+ store,
459
+ actor,
460
+ candidate_id,
461
+ request,
462
+ )
463
+
464
+ @application.post(
465
+ '/v1/personal-global-preference-candidates/{candidate_id}/decision-inspection',
466
+ response_model=PersonalGlobalPreferenceDecisionInspection,
467
+ )
468
+ async def inspect_personal_global_preference(
469
+ candidate_id: str,
470
+ request: PersonalGlobalPreferenceDecisionIntent,
471
+ actor: Actor,
472
+ ) -> PersonalGlobalPreferenceDecisionInspection:
473
+ return await inspect_personal_global_preference_decision(
474
+ store,
475
+ actor,
476
+ candidate_id,
477
+ request,
478
+ )
479
+
480
+ @application.post(
481
+ '/v1/personal-global-preference-candidates/{candidate_id}/decision-preview',
482
+ response_model=PersonalGlobalPreferenceDecisionPreview,
483
+ )
484
+ async def preview_personal_global_preference(
485
+ candidate_id: str,
486
+ request: PersonalGlobalPreferenceDecisionIntent,
487
+ actor: HumanReviewer,
488
+ ) -> PersonalGlobalPreferenceDecisionPreview:
489
+ return await preview_personal_global_preference_decision(
490
+ store,
491
+ actor,
492
+ candidate_id,
493
+ request,
494
+ )
495
+
496
+ @application.post(
497
+ '/v1/personal-global-preference-candidates/{candidate_id}/decision',
498
+ response_model=PersonalGlobalPreferenceDecisionRecord,
499
+ )
500
+ async def apply_personal_global_preference(
501
+ candidate_id: str,
502
+ request: PersonalGlobalPreferenceDecisionApply,
503
+ actor: HumanReviewer,
504
+ ) -> PersonalGlobalPreferenceDecisionRecord:
505
+ return await apply_personal_global_preference_decision(
506
+ store,
507
+ actor,
508
+ candidate_id,
509
+ request,
510
+ )
511
+
512
+ @application.post(
513
+ '/v1/workflow-candidates/recommendations',
514
+ response_model=WorkflowRecommendationPage,
515
+ )
516
+ async def workflow_candidate_recommendations(
517
+ request: WorkflowCandidateSearch,
518
+ actor: Actor,
519
+ ) -> WorkflowRecommendationPage:
520
+ return await recommend_workflow_candidates(store, actor, request)
521
+
522
+ @application.post(
523
+ '/v1/workflow-candidates/search',
524
+ response_model=WorkflowCandidatePage,
525
+ )
526
+ async def workflow_candidate_search(
527
+ request: WorkflowCandidateSearch,
528
+ actor: Actor,
529
+ ) -> WorkflowCandidatePage:
530
+ return await search_workflow_candidates(store, actor, request)
531
+
532
+ @application.post(
533
+ '/v1/workflow-candidates/{candidate_id}/review-preview',
534
+ response_model=WorkflowCandidateReviewPreview,
535
+ )
536
+ async def preview_workflow_review(
537
+ candidate_id: str,
538
+ request: WorkflowCandidateReviewIntent,
539
+ actor: HumanReviewer,
540
+ ) -> WorkflowCandidateReviewPreview:
541
+ return await preview_workflow_candidate_review(
542
+ store, actor, candidate_id, request
543
+ )
544
+
545
+ @application.post(
546
+ '/v1/workflow-candidates/{candidate_id}/review',
547
+ response_model=WorkflowCandidate,
548
+ )
549
+ async def review_workflow(
550
+ candidate_id: str,
551
+ request: WorkflowCandidateReview,
552
+ actor: HumanReviewer,
553
+ ) -> WorkflowCandidate:
554
+ return await review_workflow_candidate(
555
+ store, actor, candidate_id, request
556
+ )
557
+
340
558
  @application.post(
341
559
  '/v1/knowledge/reviews/start',
342
560
  response_model=KnowledgeReviewRun,
@@ -577,6 +795,24 @@ def create_app(settings: Settings | None = None) -> FastAPI:
577
795
  ) -> KnowledgeProjectActionResult:
578
796
  return await apply_knowledge_project_action(store, actor, item_id, request)
579
797
 
798
+ @application.post(
799
+ '/v1/personal-spaces/{space_id}/project-relations/{relation_id}/review',
800
+ response_model=PersonalProjectRelationReviewRecord,
801
+ )
802
+ async def review_local_project_relation(
803
+ space_id: str,
804
+ relation_id: str,
805
+ request: PersonalProjectRelationReviewRequest,
806
+ actor: HumanReviewer,
807
+ ) -> PersonalProjectRelationReviewRecord:
808
+ return await review_personal_project_relation(
809
+ store,
810
+ actor,
811
+ space_id,
812
+ relation_id,
813
+ request,
814
+ )
815
+
580
816
  @application.post(
581
817
  '/v1/projects/{project_id}/proposals',
582
818
  response_model=ProposalRecord,
@@ -15,3 +15,21 @@ def matches_bootstrap_token(provided: str | None, expected: str) -> bool:
15
15
  if not provided:
16
16
  return False
17
17
  return hmac.compare_digest(provided, expected)
18
+
19
+
20
+ def matches_human_review_token(
21
+ provided: str | None,
22
+ expected: str | None,
23
+ ) -> bool:
24
+ if not provided or not expected:
25
+ return False
26
+ return hmac.compare_digest(provided, expected)
27
+
28
+
29
+ def matches_workflow_observation_token(
30
+ provided: str | None,
31
+ expected: str | None,
32
+ ) -> bool:
33
+ if not provided or not expected:
34
+ return False
35
+ return hmac.compare_digest(provided, expected)