From d7ba22ae8ee2b0509a61e33a01c321f0f8a42f1e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 4 Feb 2026 20:16:24 +0800 Subject: [PATCH 1/6] Enhance OAuth security configuration and cookie handling - Add SecureCookie field to TempSecurityConfig and SecurityConfig for better cookie security management. - Implement SetSecureCookieEnabled and IsSecureCookieEnabled functions to manage secure cookie settings globally. - Update response handling to utilize secure cookie settings, ensuring compliance with security best practices. Co-authored-by: Cursor --- data/bindata.go | 636 +++++++++++++++++------------------ openapi/config.go | 2 + openapi/oauth/oauth.go | 8 + openapi/oauth/types/types.go | 3 + openapi/openapi.go | 5 + openapi/response/response.go | 37 +- openapi/types.go | 1 + 7 files changed, 366 insertions(+), 326 deletions(-) diff --git a/data/bindata.go b/data/bindata.go index 4cb2fcd7..2f408c36 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -495,7 +495,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -515,7 +515,7 @@ func cuiV09IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -535,7 +535,7 @@ func cuiV10IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -555,7 +555,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -575,7 +575,7 @@ func cuiV10UmiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -595,7 +595,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 6599, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.env", size: 6599, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -615,7 +615,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -635,7 +635,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -655,7 +655,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -675,7 +675,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -695,7 +695,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -715,7 +715,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -735,7 +735,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -755,7 +755,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -775,7 +775,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -795,7 +795,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -815,7 +815,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -835,7 +835,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -855,7 +855,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -875,7 +875,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -895,7 +895,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -915,7 +915,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -935,7 +935,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -955,7 +955,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -975,7 +975,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -995,7 +995,7 @@ func initAgentAgentYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/agent.yml", size: 1583, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1015,7 +1015,7 @@ func initAgentLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/locales/en-us.yml", size: 151, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1035,7 +1035,7 @@ func initAgentLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/locales/zh-cn.yml", size: 135, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1055,7 +1055,7 @@ func initAgentPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/prompts.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1075,7 +1075,7 @@ func initAgentSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/search.yml", size: 330, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1095,7 +1095,7 @@ func initAgentTemplate__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1115,7 +1115,7 @@ func initAgentTemplate__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1135,7 +1135,7 @@ func initAgentTemplate__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1155,7 +1155,7 @@ func initAgentTemplate__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1175,7 +1175,7 @@ func initAgentTemplate__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1195,7 +1195,7 @@ func initAgentTemplate__assetsCssVarsCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/css/vars.css", size: 7296, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1215,7 +1215,7 @@ func initAgentTemplate__assetsImagesAssistantsExpensePng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/expense.png", size: 1434910, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1235,7 +1235,7 @@ func initAgentTemplate__assetsImagesAssistantsTasksSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/assistants/tasks.svg", size: 1686, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1255,7 +1255,7 @@ func initAgentTemplate__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/icons/app.png", size: 18302, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1275,7 +1275,7 @@ func initAgentTemplate__assetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/logo_color.svg", size: 2608, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1295,7 +1295,7 @@ func initAgentTemplate__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/images/logos/wordmark.svg", size: 8648, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1315,7 +1315,7 @@ func initAgentTemplate__assetsJsEcharts543MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/echarts-5.4.3.min.js", size: 1024740, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1335,7 +1335,7 @@ func initAgentTemplate__assetsJsHighlightMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/highlight.min.js", size: 65157, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1355,7 +1355,7 @@ func initAgentTemplate__assetsJsRemarkableMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/remarkable.min.js", size: 122397, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1375,7 +1375,7 @@ func initAgentTemplate__assetsJsYaoAgentDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.d.ts", size: 2082, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1395,7 +1395,7 @@ func initAgentTemplate__assetsJsYaoAgentJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__assets/js/yao-agent.js", size: 15828, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1415,7 +1415,7 @@ func initAgentTemplate__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__data.json", size: 538, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1435,7 +1435,7 @@ func initAgentTemplate__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/__document.html", size: 4391, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1455,7 +1455,7 @@ func initAgentTemplatePackageJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/package.json", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1475,7 +1475,7 @@ func initAgentTemplatePages401401Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.css", size: 700, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1495,7 +1495,7 @@ func initAgentTemplatePages401401Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.html", size: 531, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1515,7 +1515,7 @@ func initAgentTemplatePages401401Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.json", size: 54, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1535,7 +1535,7 @@ func initAgentTemplatePages401401Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/401.ts", size: 214, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1555,7 +1555,7 @@ func initAgentTemplatePages401__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/en-us.yml", size: 145, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1575,7 +1575,7 @@ func initAgentTemplatePages401__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/401/__locales/zh-cn.yml", size: 139, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1595,7 +1595,7 @@ func initAgentTemplatePages404404Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.css", size: 1877, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1615,7 +1615,7 @@ func initAgentTemplatePages404404Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.html", size: 896, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1635,7 +1635,7 @@ func initAgentTemplatePages404404Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.json", size: 32, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1655,7 +1655,7 @@ func initAgentTemplatePages404404Ts() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/404.ts", size: 450, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1675,7 +1675,7 @@ func initAgentTemplatePages404__localesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/en-us.yml", size: 243, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1695,7 +1695,7 @@ func initAgentTemplatePages404__localesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/agent/template/pages/404/__locales/zh-cn.yml", size: 232, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1715,7 +1715,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 1642, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 1642, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1735,7 +1735,7 @@ func initAssistantsLlmsLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/en-us.yml", size: 235, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1755,7 +1755,7 @@ func initAssistantsLlmsLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/llms/locales/zh-cn.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1775,7 +1775,7 @@ func initAssistantsLlmsPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/llms/package.yao", size: 546, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1795,7 +1795,7 @@ func initAssistantsLlmsPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/llms/prompts.yml", size: 127, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1815,7 +1815,7 @@ func initAssistantsMessagesLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/en-us.yml", size: 383, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1835,7 +1835,7 @@ func initAssistantsMessagesLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/locales/zh-cn.yml", size: 371, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1855,7 +1855,7 @@ func initAssistantsMessagesPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/package.yao", size: 562, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1875,7 +1875,7 @@ func initAssistantsMessagesSrcActionTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/action.ts", size: 4752, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1895,7 +1895,7 @@ func initAssistantsMessagesSrcBasicTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/basic.ts", size: 3310, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1915,7 +1915,7 @@ func initAssistantsMessagesSrcCodeTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/code.ts", size: 2463, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1935,7 +1935,7 @@ func initAssistantsMessagesSrcErrorTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/error.ts", size: 6281, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1955,7 +1955,7 @@ func initAssistantsMessagesSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/index.ts", size: 3326, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1975,7 +1975,7 @@ func initAssistantsMessagesSrcMarkdownTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/messages/src/markdown.ts", size: 7045, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1995,7 +1995,7 @@ func initAssistantsYaoLocalesEnUsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/en-us.yml", size: 353, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2015,7 +2015,7 @@ func initAssistantsYaoLocalesZhCnYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/yao/locales/zh-cn.yml", size: 363, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2035,7 +2035,7 @@ func initAssistantsYaoPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/yao/package.yao", size: 582, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2055,7 +2055,7 @@ func initAssistantsYaoPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/assistants/yao/prompts.yml", size: 648, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2075,7 +2075,7 @@ func initConnectorsAnthropicClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-opus-4_5.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2095,7 +2095,7 @@ func initConnectorsAnthropicClaudeSonnet4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/anthropic/claude-sonnet-4_5.conn.yao", size: 389, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2115,7 +2115,7 @@ func initConnectorsAzureGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/azure/gpt-5_2.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2135,7 +2135,7 @@ func initConnectorsDeepseekDeepseekChatConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-chat.conn.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2155,7 +2155,7 @@ func initConnectorsDeepseekDeepseekReasonerConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/deepseek/deepseek-reasoner.conn.yao", size: 385, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2175,7 +2175,7 @@ func initConnectorsFireworksLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/fireworks/llama-4-maverick.conn.yao", size: 449, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2195,7 +2195,7 @@ func initConnectorsGoogleGemini2_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-2_5-pro.conn.yao", size: 406, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2215,7 +2215,7 @@ func initConnectorsGoogleGemini3FlashConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/google/gemini-3-flash.conn.yao", size: 416, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2235,7 +2235,7 @@ func initConnectorsGroqLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/groq/llama-4-maverick.conn.yao", size: 420, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2255,7 +2255,7 @@ func initConnectorsMetaLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/meta/llama-4-maverick.conn.yao", size: 400, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2275,7 +2275,7 @@ func initConnectorsMistralMistralLarge3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/mistral/mistral-large-3.conn.yao", size: 381, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2295,7 +2295,7 @@ func initConnectorsOllamaDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/deepseek-r1.conn.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2315,7 +2315,7 @@ func initConnectorsOllamaGemma3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/gemma3.conn.yao", size: 350, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2335,7 +2335,7 @@ func initConnectorsOllamaLlama3_3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/llama3_3.conn.yao", size: 355, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2355,7 +2355,7 @@ func initConnectorsOllamaQwen2_5CoderConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 361, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen2_5-coder.conn.yao", size: 361, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2375,7 +2375,7 @@ func initConnectorsOllamaQwen3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/ollama/qwen3.conn.yao", size: 345, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2395,7 +2395,7 @@ func initConnectorsOpenaiGpt4oMiniConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o-mini.conn.yao", size: 371, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2415,7 +2415,7 @@ func initConnectorsOpenaiGpt4oConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-4o.conn.yao", size: 360, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2435,7 +2435,7 @@ func initConnectorsOpenaiGpt5_2ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openai/gpt-5_2.conn.yao", size: 362, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2455,7 +2455,7 @@ func initConnectorsOpenaiO3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openai/o3.conn.yao", size: 354, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2475,7 +2475,7 @@ func initConnectorsOpenaiTextEmbedding3LargeConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openai/text-embedding-3-large.conn.yao", size: 394, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2495,7 +2495,7 @@ func initConnectorsOpenrouterAutoConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/auto.conn.yao", size: 395, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2515,7 +2515,7 @@ func initConnectorsOpenrouterClaudeOpus4_5ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/claude-opus-4_5.conn.yao", size: 409, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2535,7 +2535,7 @@ func initConnectorsOpenrouterNovaPremierConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/openrouter/nova-premier.conn.yao", size: 410, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2555,7 +2555,7 @@ func initConnectorsSiliconflowDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/deepseek-v3.conn.yao", size: 405, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2575,7 +2575,7 @@ func initConnectorsSiliconflowQwen2_572bConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/siliconflow/qwen-2_5-72b.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2595,7 +2595,7 @@ func initConnectorsTogetherDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/together/deepseek-r1.conn.yao", size: 396, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2615,7 +2615,7 @@ func initConnectorsTogetherLlama4MaverickConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/together/llama-4-maverick.conn.yao", size: 429, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2635,7 +2635,7 @@ func initConnectorsVolcengineDeepseekR1ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-r1.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2655,7 +2655,7 @@ func initConnectorsVolcengineDeepseekV3ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/deepseek-v3.conn.yao", size: 408, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2675,7 +2675,7 @@ func initConnectorsVolcengineDoubao1_5ProConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/doubao-1_5-pro.conn.yao", size: 412, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2695,7 +2695,7 @@ func initConnectorsVolcengineGlm4PlusConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/glm-4-plus.conn.yao", size: 399, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2715,7 +2715,7 @@ func initConnectorsVolcengineQwenVlMaxConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/volcengine/qwen-vl-max.conn.yao", size: 403, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2735,7 +2735,7 @@ func initConnectorsXaiGrok4ConnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/connectors/xai/grok-4.conn.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2755,7 +2755,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2775,7 +2775,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2795,7 +2795,7 @@ func initDataTemplatesDefault__assetsBrandsAppleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/apple.svg", size: 650, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2815,7 +2815,7 @@ func initDataTemplatesDefault__assetsBrandsGithubSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/github.svg", size: 822, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2835,7 +2835,7 @@ func initDataTemplatesDefault__assetsBrandsGoogleSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/google.svg", size: 457, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2855,7 +2855,7 @@ func initDataTemplatesDefault__assetsBrandsMicrosoftSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/brands/microsoft.svg", size: 206, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2875,7 +2875,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2895,7 +2895,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2915,7 +2915,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2935,7 +2935,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2955,7 +2955,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2975,7 +2975,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2995,7 +2995,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3015,7 +3015,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3035,7 +3035,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3055,7 +3055,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3075,7 +3075,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3095,7 +3095,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3115,7 +3115,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3135,7 +3135,7 @@ func initMessengersChannelsYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/channels.yao", size: 474, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/channels.yao", size: 474, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3155,7 +3155,7 @@ func initMessengersProvidersPrimaryMailgunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/primary.mailgun.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/providers/primary.mailgun.yao", size: 356, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3175,7 +3175,7 @@ func initMessengersProvidersUnifiedTwilioYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/providers/unified.twilio.yao", size: 508, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3195,7 +3195,7 @@ func initMessengersTemplatesEnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.mail.html", size: 483, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3215,7 +3215,7 @@ func initMessengersTemplatesEnInvite_memberSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/invite_member.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3235,7 +3235,7 @@ func initMessengersTemplatesEnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_email.mail.html", size: 397, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3255,7 +3255,7 @@ func initMessengersTemplatesEnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/en/verify_mobile.sms.txt", size: 124, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3275,7 +3275,7 @@ func initMessengersTemplatesZhCnInvite_mailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_mail.html", size: 373, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3295,7 +3295,7 @@ func initMessengersTemplatesZhCnInvite_memberMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_member.mail.html", size: 441, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3315,7 +3315,7 @@ func initMessengersTemplatesZhCnInvite_smsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/invite_sms.txt", size: 122, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3335,7 +3335,7 @@ func initMessengersTemplatesZhCnVerify_emailMailHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_email.mail.html", size: 347, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3355,7 +3355,7 @@ func initMessengersTemplatesZhCnVerify_mobileSmsTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/messengers/templates/zh-cn/verify_mobile.sms.txt", size: 114, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3375,7 +3375,7 @@ func initModelsMenuModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/models/menu.mod.yao", size: 3246, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3395,7 +3395,7 @@ func initOpenapiCertsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/certs/README.md", size: 10174, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3415,7 +3415,7 @@ func initOpenapiCertsMtlsClientCaKeyTestingOnlyDoNotUseInProductionPem() (*asset return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca-key-TESTING-ONLY-DO-NOT-USE-IN-PRODUCTION.pem", size: 3268, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3435,7 +3435,7 @@ func initOpenapiCertsMtlsClientCaPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/certs/mtls-client-ca.pem", size: 2029, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3455,7 +3455,7 @@ func initOpenapiCertsSigningCertPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-cert.pem", size: 2090, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3475,7 +3475,7 @@ func initOpenapiCertsSigningKeyPem() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/certs/signing-key.pem", size: 3272, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3495,7 +3495,7 @@ func initOpenapiFeaturesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/features/alias.yml", size: 800, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3515,7 +3515,7 @@ func initOpenapiFeaturesFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/features/features.yml", size: 1202, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3535,7 +3535,7 @@ func initOpenapiFeaturesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/profile.yml", size: 96, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3555,12 +3555,12 @@ func initOpenapiFeaturesUserTeamYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/features/user/team.yml", size: 286, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _initOpenapiOpenapiYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x59\x5b\x6f\x1b\xbb\x11\x7e\xcf\xaf\x18\xe8\x29\x06\x24\xeb\x92\xd8\x27\x36\xd0\x07\x1d\x3b\xa7\x75\x73\x52\x0b\xb6\xd3\xe2\x20\x08\x16\x14\x77\xd6\x4b\x7b\x97\xdc\x43\x72\x25\xab\x41\xfe\x7b\x41\x72\x2f\xdc\x9b\xbc\x05\x9a\x3e\xc5\xe1\xce\x7c\x1f\x39\x33\x9c\x0b\xf5\xfd\x0d\xc0\x64\x4b\x14\xe6\x32\x99\x5c\xc2\x64\xbe\x5b\x4e\xa6\x30\x9f\xc3\xaf\x44\x21\x7c\xb9\xfb\x1d\x32\xa2\x63\x88\x84\x84\xdb\x0c\xf9\x7a\x73\x03\xc8\xc3\x4c\x30\xae\x95\x51\x55\x5a\x48\x34\x8a\x41\x70\x20\xe2\x54\x90\x5c\xc7\xa7\x6e\xd1\xc2\x5c\x63\x44\xf2\x44\x83\x5d\x72\x30\xeb\x5c\xc7\x10\x12\x4d\x8c\x3e\x25\x34\xee\xe8\xbb\xc5\x86\xbe\x5d\xea\xd1\xcf\xa4\xd8\xb1\x10\xa5\x9a\x5c\x82\x39\x0b\xc0\x24\x57\x28\x6b\x44\xfb\x3f\x0b\xf5\x45\xa1\x84\x52\xfe\x12\xd6\xfc\x00\x7f\x10\x01\xa9\x08\x31\x81\x3d\xd3\x31\x48\xfc\x33\x67\x12\x43\x88\x18\x26\xa1\x82\xb7\xa1\x63\xbf\x84\x1a\xeb\xc4\x91\xd0\x84\x21\xd7\x9d\x8d\xbb\x55\x4b\x77\x65\xff\x76\x07\xaf\xd9\xee\x5b\x76\x70\x1a\xf0\xe9\x9f\xf6\x44\x1d\x46\x1f\x16\x72\x85\x0a\x7e\x25\xe1\xa3\xdb\xc5\x8f\xa9\x31\x80\x95\xa8\x0f\xcf\x94\xca\x51\x06\x85\x37\x63\xad\x33\x75\x39\x9f\x27\x82\x92\x24\x16\x4a\x5f\x9e\x2d\x2e\x2e\xdc\xfe\xee\x8a\xc3\x5e\x16\x3b\xd1\xe2\x19\x39\x38\x7d\xeb\xf8\x7a\x93\x0a\xe5\x0e\xa5\x23\x50\xec\x91\x33\xfe\x58\x31\xd6\x4b\x01\x45\xa9\x03\x13\x2e\x86\xba\x58\x9c\x99\xc5\xd3\x0c\xd3\x36\xe9\x86\x58\x83\x27\x44\xb3\x1d\x82\x16\xf0\xfd\x8f\xf5\x6d\x70\x77\x7b\xfb\xf0\x63\x2e\x32\xe4\x24\x63\x73\xa3\xab\xe6\x10\x32\x89\x54\x0b\x79\x68\x13\x3e\xe3\xa1\xc3\xf7\x8c\x87\x9f\x4b\xa7\xd4\x5e\xc8\xd0\x50\x3a\x8e\xdb\x4c\x33\xc1\x49\x62\x38\xdc\x37\x6b\x39\xe4\x54\x1e\x32\x8d\x21\x64\x92\xed\x88\x46\x78\xc6\x83\xe7\x5f\x4c\x33\x7d\x38\x69\x53\x90\xe4\x51\x48\xa6\xe3\xd4\xe0\xdf\xdd\xaf\xce\xce\xdb\x24\x0f\xd6\x4d\x85\x3c\x54\xf2\x1e\xb2\x55\xab\x90\x77\x28\x59\xc4\x28\x31\xfa\xd6\x43\xe6\xa6\x7c\xfd\xd6\x42\x5d\x87\x21\x73\x7f\x83\x91\x71\x1a\xa8\xec\x49\x5c\x60\xf8\x38\x83\xc7\x48\x75\xa2\x02\x17\xad\x01\x25\xcd\x80\x30\xdf\x66\xee\xdb\x8c\x92\xda\x47\xf5\x26\xae\xd6\x3e\xb9\x4b\x3c\xa3\x5c\xd6\xa0\x47\x4e\xb6\x09\x1a\x07\x69\x99\x63\x8b\xe2\xa3\xfd\x08\x69\xae\x73\x92\xc0\xc3\xef\xf7\x60\xae\x0f\x72\xdd\x3d\x59\x44\x12\x85\xd5\xc9\xec\x51\xa4\xd0\xce\x8e\x35\x87\x95\xea\x27\x21\xb9\x16\x29\xd1\x8c\x36\x4e\x55\x62\x8c\x64\x62\x5c\xa3\xdc\x11\x7b\x9b\x57\xef\xe3\x8e\xc9\xfa\x90\x4b\x1d\x8f\x62\xf5\x3e\x76\x04\x36\x69\x00\x4c\xac\x53\xfd\x4b\x4c\x28\x45\xa5\x02\xbb\x1e\x24\x2c\x42\xcd\x52\x9b\x98\x97\x1d\xd2\xb5\x15\x2d\xe3\x82\x24\x2c\x64\xfa\x00\x19\x4a\x26\x42\x8f\x73\x19\x9f\xf4\x82\x47\x42\xa6\xc4\xa6\xce\xa7\xbd\x3e\x8a\xed\x24\x61\x06\x4f\x7b\x3d\x17\x19\xf9\x33\x47\x0f\xff\x69\xaf\xfb\x09\xbc\xcb\x34\x78\x8d\x1a\x3c\xe3\x6f\x93\xc4\x48\xa2\x8a\x7b\xcc\xd4\xe3\x9c\x3b\x27\xfc\xaa\xa1\x2a\xe7\x74\x08\x4a\x97\x1e\x0d\x66\xd9\xa0\xa9\xa2\xa0\x4e\xdf\xab\xd3\xe5\x60\xb4\x35\xf9\x6a\xd7\x38\x6b\x1f\x3f\x51\xe5\x1e\x27\x3c\x7f\xda\x6b\x8f\xc7\x2d\xd6\x1e\xca\x75\x2c\x24\xfb\x77\x91\x88\x44\x88\xcd\x28\x5b\x74\xd2\xc1\xda\xd7\x00\xa3\x71\x2c\xd8\x16\xe9\x51\x2a\xe4\x8f\x36\x0d\xbd\x5b\xbd\xce\xe2\x84\x81\x71\xd8\x1e\x4c\x12\xac\x59\xde\xad\x2a\x92\x10\x77\x8c\x62\xcf\x41\xce\x3a\x07\xb9\xb6\xa2\xaf\x9e\xe0\x2c\xed\x07\x2f\xb7\xfe\xe1\x08\xec\xf0\x96\x3f\x54\xa0\xa6\x7b\x79\x05\xd2\xb6\x48\x3e\xa0\x09\x22\xb7\x19\x88\x12\xb1\xef\xc5\xf5\x37\xeb\xa7\xab\x33\x75\xcc\x10\x99\x48\x12\x73\xe7\x7a\x92\xd5\x99\xaa\xb0\x5d\x58\x6e\x19\x0f\xcd\x85\x1e\x95\x76\x5d\x6c\x16\x2a\xa6\x6c\x14\xdd\x53\xa3\xae\x0d\x5d\x07\x95\x67\x99\x90\x1a\xc3\x8a\x53\x1f\x32\xb4\x25\x73\x12\x66\x22\x9b\x4c\x5d\x95\x99\xb4\x0b\xe8\x7d\xa9\xd8\xe6\x37\xea\x1e\xdd\x57\x83\x32\x05\x83\xf1\xcd\xb3\xa0\xfd\x1a\x90\x3c\x64\xc8\x29\xf6\x55\xe8\xb2\x11\x76\xf0\xa5\xe4\x60\x25\x2e\x05\x02\x1b\x70\xee\x26\x98\x4e\xd7\xb6\x4a\x5a\x32\xda\xcd\xbc\x25\x64\xad\x61\x7b\x63\x98\x81\x53\x98\x9b\x5a\xfc\x82\x7e\xcc\xba\x0f\xcd\xca\xa2\x90\xe6\x92\xe9\x83\x5f\x5c\xb2\x67\x8a\x41\xd9\x5d\xf7\x27\xb3\xa2\x55\x83\xcd\xa7\xab\x8f\xad\xdc\x45\x45\x9a\x25\x8c\x34\x8f\x6b\x20\x4e\x1a\xf8\x36\x00\x69\x4c\x12\x13\xbb\x18\xa4\xa8\x63\x11\x5a\xcf\xd9\x02\x30\xec\x31\x4b\x69\x83\xb2\xd2\x06\xa7\xdd\xf0\x9c\x41\xf9\xd6\x43\xe9\x3a\x23\x94\xf5\xb5\x5a\xae\xda\x17\xab\xa6\x28\xa5\xcb\x1b\xe6\x25\x80\x55\x7d\xa7\x94\x26\x1a\x83\x8c\x48\x92\xa2\x46\xe9\xdb\xae\x2f\xf4\x4b\xe3\x59\x35\xa8\xd4\xac\x1d\xaf\xee\xef\x7e\x33\x63\x8f\x46\x7a\xb4\xef\x68\x53\xbe\x92\x9d\xef\x5b\x54\xe3\x52\x73\x87\x64\x30\x2f\xb7\xf1\x47\x25\x65\x69\xd0\x13\x96\x32\x3d\x2e\x57\x18\x79\xb0\xf2\xe6\xae\x0e\xd6\xc8\x1a\xd5\xf8\x01\x95\xed\xa1\x97\x8b\x45\x0b\xf4\x1f\x79\xba\x45\x09\x22\x82\x52\xcc\x18\x03\xf6\x8c\x87\x8d\xd4\xb9\x5c\x2c\xfa\xb0\x9d\x9c\x35\x77\xc7\xda\x77\xd5\x46\xc1\xf8\xa4\x07\x33\xed\x83\xdc\x1e\xca\x6e\x9c\xbd\x62\x89\x0c\x65\xd1\x9c\x8f\x34\xca\x56\xe6\x1a\x4d\xc3\x40\x31\xa8\xa3\x6b\x9c\xd9\xad\x2e\x58\x5d\x20\x5a\x13\xfa\x3c\x26\x40\x53\xf2\x12\x44\x84\x25\x18\x06\x44\x6b\x93\xee\x8c\x1b\xce\x5a\x14\x9f\xc9\x0b\x4b\xf3\x14\x9c\x24\x24\xe2\x91\x71\x28\xe5\xfd\x22\x53\xe1\x26\x82\x3e\x8b\x5c\x07\x61\x2e\xcb\x66\xab\xaf\x8a\xaf\x29\x15\x39\xd7\x50\x88\x43\x29\x3e\x50\xc2\x8b\xf9\xcf\xd8\xe4\x19\x0f\x7d\xe3\xe2\x27\x3c\xf8\x93\xa2\x31\xb6\x42\xae\x98\x1d\x77\x5a\xaf\x01\xcd\xe4\xee\x61\x37\x06\xc6\xf5\xc7\xfb\xd9\xea\xec\x7c\xf6\xd7\xab\xcf\x6d\xb2\x8f\x95\x8a\xd7\xe5\x1a\xf6\x41\x4a\x0f\xac\x22\x66\x59\xb0\x8f\x99\xc6\x84\x29\xdd\x57\xa4\x6e\x36\x40\xc2\x50\xa2\x52\xa8\x80\x24\x89\xd8\xdb\x82\x08\xae\x3d\x1f\x3c\x0f\xcb\x82\x6d\x42\xe8\xf3\x28\xd8\xad\x71\x00\x86\x10\x49\x91\xbe\x06\x5c\xa4\xcd\xc0\xbe\x83\x1c\xaf\x3b\x7f\x7b\x78\xd8\xdc\x5b\x8b\x90\x24\xa9\xdf\xb8\x86\x2a\x4e\xc8\x94\x09\xe5\x20\xe7\xb6\xde\x61\x50\x69\x0c\x84\xfe\xb5\x53\x00\x2e\xf8\xcc\x71\xf5\x71\x78\x11\x5f\xd6\xd3\xea\xa5\xe9\x7b\xbb\x5d\x28\xae\xb6\x69\x31\x8c\xf7\xa9\xe0\x11\x0b\xcd\x2c\x4b\x92\x6e\xe7\x55\x3c\xa3\xb9\x2b\x6e\x54\x60\x06\xbe\xc6\x3c\xcb\xb7\x09\xa3\xde\x5e\xfc\xaf\x9d\x56\xc5\x35\x66\xe5\x11\x02\xd3\x72\xd7\x15\xb7\xd8\x73\xa0\x90\x4a\xd4\xc1\x96\x28\x46\x5f\xd9\x51\x6b\x0c\x77\x50\xfe\x66\xba\x88\x9d\x3d\x3d\x4a\x52\x98\xc3\x35\x6c\xdd\x39\xc0\xb4\x6f\x8d\x89\xa7\xd3\x15\x54\xef\x95\x55\x77\x60\x51\xbb\x7d\x5c\x17\x7c\x0a\x0d\xe8\x6e\x7b\x27\x51\x65\x82\x2b\xf4\xb6\x68\x37\xf5\xfa\x1e\x4a\xcd\xee\x36\x0c\x40\x97\x49\x51\x51\x32\x88\x0c\x39\x0b\xcd\xc1\x33\x29\x22\x96\x58\x1b\x60\x4a\x58\x32\xc8\x5b\x3c\xfa\x59\x0c\x9f\xcb\x41\x4d\xa1\x00\x9a\x82\x85\xa9\xd9\xab\x52\x73\xa4\xac\x17\xaf\xa2\x37\xd7\xe3\x0a\x7a\xd3\xed\x15\xec\xf9\xfb\x7e\x58\x27\x77\x04\xfa\xfc\xfd\x10\xb4\xd7\xed\x2c\x3a\x83\x4b\x0b\xbe\x90\x85\x19\x2c\xe0\x2f\xc0\x71\x87\x12\xf0\x25\x63\xb2\xc1\xb5\xa8\xc7\x98\xf0\xc0\x49\xca\x68\x20\xf1\x91\x29\x2d\xdb\x8f\x48\xc3\xb3\x7d\xa1\x58\xde\x12\x5f\x7f\x28\x33\x15\x69\x37\x90\xe8\x1e\x32\x83\x5c\xb2\x40\xd1\x18\xd3\x22\x22\x5c\x32\x9c\xba\xd7\xe1\x4e\x10\xac\x8b\xac\x5d\xaa\xc3\x97\xbb\x1b\x28\xd4\xfd\x60\xb0\x28\x53\x30\xff\x7c\x3b\xce\x1d\x0b\xd7\x30\x7d\x9d\x54\xcf\xd0\x86\x7d\xb9\xfa\xe5\x74\x71\xba\x38\x5d\x8e\xdb\x82\x45\xf1\x37\x50\x81\x4d\xa1\x82\xea\xc4\xa2\x37\xf4\x8d\xed\xa1\xff\x9b\x71\xb1\x87\xa5\x9e\x9e\x4c\x28\x71\xc1\x3b\x8f\x28\x57\x1d\x82\x9e\x91\xcb\x28\xce\x45\xa1\x33\xaf\x7e\x98\xa8\x37\x62\x04\x9a\x85\x22\x42\xa2\x73\x89\xca\x2f\x15\xf6\xd7\x81\xd5\x72\x54\xac\xd5\x03\x57\x89\x74\x74\xdc\x42\x6e\x1b\xb8\x41\x4c\xd7\xde\x55\xe3\x9c\xa9\xaa\xce\x5c\x83\xb0\xfd\x6f\x5f\xa3\xf6\x3e\xf0\x06\x36\x54\xbc\xdd\x8b\x45\x94\x88\xfd\xb8\x5b\xe8\xde\x2d\x1a\x29\xbf\xfd\x1e\xd2\x20\x28\xaa\xe3\x0b\x8d\x89\x19\x45\xc7\x70\xb8\x9d\x97\x2a\x70\xf7\xdb\x15\x7c\x38\xbf\x78\x37\x18\x7a\x59\xae\x62\xd3\x09\x37\xca\xd0\x18\x22\xa7\xd8\x3a\x4c\x35\xb0\x18\xde\x8b\xe5\xea\x7c\x90\xb7\x4c\x65\x45\xe8\xff\x4f\x33\x9a\x21\xff\xe5\xec\x62\x39\x64\xd6\x94\x66\x41\xfd\x18\x30\x8a\xee\xf3\xd5\x06\xde\x7e\xb6\x3f\xf1\x5d\x09\xae\xf1\x45\xc3\x46\x0a\x2d\xa8\x48\x4e\xfa\x1f\x16\xda\xef\xa3\x4a\xe4\x76\xc6\xa9\x66\xd6\x71\xf1\xe8\xd4\xbc\x01\xb6\xa8\xe6\x83\x4c\xff\xef\xa7\xae\x9f\xf8\x63\x49\x98\x89\x6c\xdc\x09\xae\x37\x62\x03\x6f\xaf\x31\x15\xbc\x8a\x02\x11\x19\x17\x89\x68\x26\xa2\xd9\x46\x98\x9e\x5f\x31\xc1\x4f\x06\xd9\x9e\xf6\x3a\x60\x5c\x4b\xa1\xb2\xce\x10\x3a\x7c\xaa\xbf\xff\xeb\xa1\xfc\xc9\xd3\xd7\x3d\x7e\xa3\x25\xee\x04\x1d\x1f\xeb\x45\x36\xaa\x94\x5c\x80\x2f\x16\x17\x43\x2c\xb9\x42\xc9\x78\x24\x02\x73\xa6\xb1\xa7\x28\x9e\xe1\x4d\x92\xfd\xa2\x50\xde\xf0\x48\x54\x0d\xe3\xb1\xd9\xe2\xc7\xf4\xcd\x8f\x37\xff\x09\x00\x00\xff\xff\xeb\x2c\x90\xc1\x00\x20\x00\x00") +var _initOpenapiOpenapiYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x59\xdb\x6e\x1b\x39\xd2\xbe\xcf\x53\x14\x74\x15\x03\x3a\x27\xf6\xc4\x06\xfe\x0b\x8d\x9d\xf9\xc7\x9b\xc9\x46\xb0\x9c\x5d\x0c\x06\x41\x83\xea\xae\xb6\x68\xb1\xc9\x1e\x92\x2d\x59\x1b\xe4\xdd\x17\x24\xfb\xc0\x3e\xc9\xbd\xc0\xce\x5e\xc5\x61\x57\x7d\x5f\xb1\x58\xac\x03\xf5\xfd\x0d\xc0\x68\x4b\x14\x66\x92\x8d\x6e\x60\x34\x3b\x2c\x46\x63\x98\xcd\xe0\x67\xa2\x10\xbe\x3e\xfc\x06\x29\xd1\x3b\x88\x85\x84\x2f\x29\xf2\xd5\xfa\x1e\x90\x47\xa9\xa0\x5c\x2b\xa3\xaa\xb4\x90\x68\x14\x83\xe0\x44\xc4\x54\x90\x4c\xef\xa6\x6e\xd1\xc2\xdc\x61\x4c\x32\xa6\xc1\x2e\x39\x98\x55\xa6\x77\x10\x11\x4d\x8c\x7e\x48\xc2\x5d\x4b\xdf\x2d\xd6\xf4\xed\x52\x87\x7e\x2a\xc5\x81\x46\x28\xd5\xe8\x06\xcc\x5e\x00\x46\x99\x42\x59\x21\xda\xff\x59\xa8\xaf\x0a\x25\x14\xf2\x37\xb0\xe2\x27\xf8\x9d\x08\x48\x44\x84\x0c\x8e\x54\xef\x40\xe2\x9f\x19\x95\x18\x41\x4c\x91\x45\x0a\xde\x46\x8e\xfd\x06\x2a\xac\x0b\x47\x12\x32\x8a\x5c\xb7\x0c\x77\xab\x96\xee\xd6\xfe\xed\x36\x5e\xb1\x6d\x1a\x7e\x70\x1a\xf0\xe9\x1f\x76\x47\x2d\x46\x1f\x16\x32\x85\x0a\x7e\x26\xd1\x93\xb3\xe2\xc7\xd8\x38\xc0\x4a\x54\x9b\xa7\x4a\x65\x28\x83\xfc\x34\x77\x5a\xa7\xea\x66\x36\x63\x22\x24\x6c\x27\x94\xbe\xb9\x9c\x5f\x5f\x3b\xfb\x1e\xf2\xcd\xde\xe4\x96\x68\xb1\x47\x0e\x4e\xdf\x1e\x7c\x65\xa4\x42\x79\x40\xe9\x08\x14\x7d\xe2\x94\x3f\x95\x8c\xd5\x52\x10\xa2\xd4\x81\x09\x17\x43\x9d\x2f\x4e\xcc\xe2\x34\xc5\xa4\x49\xba\x26\xd6\xe1\x8c\x68\x7a\x40\xd0\x02\xbe\xff\xbe\xfa\x12\x3c\x7c\xf9\xf2\xf8\x63\x26\x52\xe4\x24\xa5\x33\xa3\xab\x66\x10\x51\x89\xa1\x16\xf2\xd4\x24\xdc\xe3\xa9\xc5\xb7\xc7\xd3\x5f\x4b\xa7\xd4\x51\xc8\xc8\x50\x3a\x8e\x2f\xa9\xa6\x82\x13\x66\x38\xdc\x37\xeb\x39\xe4\xa1\x3c\xa5\x1a\x23\x48\x25\x3d\x10\x8d\xb0\xc7\x93\x77\xbe\x98\xa4\xfa\x74\xd1\xa4\x20\xec\x49\x48\xaa\x77\x89\xc1\x7f\xd8\x2c\x2f\xaf\x9a\x24\x8f\xf6\x98\x72\x79\x28\xe5\x3d\x64\xab\x56\x22\x1f\x50\xd2\x98\x86\xc4\xe8\xdb\x13\x32\x37\xe5\x8f\x6f\x0d\xd4\x55\x14\x51\xf7\x37\x18\x19\xa7\x81\xca\xee\xc4\x05\x86\x8f\xd3\xbb\x8d\x44\x33\x15\xb8\x68\x0d\x42\x52\x0f\x08\xf3\x6d\xe2\xbe\x4d\x42\x52\x9d\x51\x65\xc4\xed\xca\x27\x77\x89\x67\xd0\x91\xd5\xe8\x91\x93\x2d\x43\x73\x40\x5a\x66\xd8\xa0\xf8\x68\x3f\x42\x92\xe9\x8c\x30\x78\xfc\x6d\x03\xe6\xfa\x20\xd7\xed\x9d\xc5\x84\x29\x2c\x77\x66\xb7\x22\x85\x76\x7e\xac\x38\xac\x54\x37\x09\xc9\xb4\x48\x88\xa6\x61\x6d\x57\x05\xc6\x40\x26\xca\x35\xca\x03\xb1\xb7\x79\xf9\x7e\xd7\x72\x59\x17\x72\xa1\xe3\x51\x2c\xdf\xef\x1c\x81\x4d\x1a\x00\x23\x7b\xa8\xfe\x25\x26\x61\x88\x4a\x05\x76\x3d\x60\x34\x46\x4d\x13\x9b\x98\x17\x2d\xd2\x95\x15\x2d\xe2\x82\x30\x1a\x51\x7d\x82\x14\x25\x15\x91\xc7\xb9\xd8\x5d\x74\x82\xc7\x42\x26\xc4\xa6\xce\xe7\xa3\x3e\x8b\xed\x24\x61\x02\xcf\x47\x3d\x13\x29\xf9\x33\x43\x0f\xff\xf9\xa8\xbb\x09\xbc\xcb\xd4\x7b\x8d\x6a\x3c\xc3\x6f\x93\xc4\x58\xa2\xda\x75\xb8\xa9\xe3\x70\x1e\x9c\xf0\xab\x8e\x2a\x0f\xa7\x45\x50\x1c\xe9\xd9\x60\x96\x35\x9a\x32\x0a\xaa\xf4\xbd\x9c\x2e\x7a\xa3\xad\xce\x57\x1d\x8d\xf3\xf6\xf9\x1d\x95\xc7\xe3\x84\x67\xcf\x47\xed\xf1\xb8\xc5\xea\x84\x32\xbd\x13\x92\xfe\x2b\x4f\x44\x22\xc2\x7a\x94\xcd\x5b\xe9\x60\xe5\x6b\x80\xd1\x38\x17\x6c\xf3\xe4\x2c\x15\xf2\x27\x9b\x86\xde\x2d\x5f\x67\x71\xc2\x40\x39\x6c\x4f\x26\x09\x56\x2c\xef\x96\x25\x49\x84\x07\x1a\x62\xc7\x46\x2e\x5b\x1b\xb9\xb3\xa2\xaf\xee\xe0\x32\xe9\x06\x2f\x4c\xff\x70\x06\xb6\xdf\xe4\x0f\x25\xa8\xe9\x5e\x5e\x81\xb4\x2d\x92\x0f\x68\x82\xc8\x19\x03\x31\x13\xc7\x4e\x5c\xdf\x58\x3f\x5d\x5d\xaa\x73\x8e\x48\x05\x63\xe6\xce\x75\x24\xab\x4b\x55\x62\xbb\xb0\xdc\x52\x1e\x99\x0b\x3d\x28\xed\xba\xd8\xcc\x55\x4c\xd9\xc8\xbb\xa7\x5a\x5d\xeb\xbb\x0e\x2a\x4b\x53\x21\x35\x46\x25\xa7\x3e\xa5\x68\x4b\xe6\x28\x4a\x45\x3a\x1a\xbb\x2a\x33\x6a\x16\xd0\x4d\xa1\xd8\xe4\x37\xea\x1e\xdd\x1f\x06\x65\x0c\x06\xe3\x9b\xe7\x41\xfb\x35\x20\x59\x44\x91\x87\xd8\x55\xa1\x8b\x46\xd8\xc1\x17\x92\xbd\x95\xb8\x10\x08\x6c\xc0\xb9\x9b\x60\x3a\x5d\xdb\x2a\x69\x49\xc3\x76\xe6\x2d\x20\x2b\x0d\xdb\x1b\xc3\x04\x9c\xc2\xcc\xd4\xe2\x17\xf4\x63\xd6\x7d\xa8\x57\x16\x85\x61\x26\xa9\x3e\xf9\xc5\x25\xdd\x87\x18\x14\xdd\x75\x77\x32\xcb\x5b\x35\x58\x7f\xba\xfd\xd8\xc8\x5d\xa1\x48\x52\x46\x49\x7d\xbb\x06\xe2\xa2\x86\x6f\x03\x30\xdc\x11\x66\x62\x17\x83\x04\xf5\x4e\x44\xf6\xe4\x6c\x01\xe8\x3f\x31\x4b\x69\x83\xb2\xd4\x06\xa7\x5d\x3b\x39\x83\xf2\xad\x83\xd2\x75\x46\x28\xab\x6b\xb5\x58\x36\x2f\x56\x45\x51\x48\x17\x37\xcc\x4b\x00\xcb\xea\x4e\x29\x4d\x34\x06\x29\x91\x24\x41\x8d\xd2\xf7\x5d\x57\xe8\x17\xce\xb3\x6a\x50\xaa\x59\x3f\xde\x6e\x1e\x7e\x31\x63\x8f\xc6\xf0\x6c\xdf\xd1\xa4\x7c\x25\x3b\x6f\x1a\x54\xc3\x52\x73\x8b\xa4\x37\x2f\x37\xf1\x07\x25\x65\x69\xd0\x19\x4d\xa8\x1e\x96\x2b\x8c\x3c\x58\x79\x73\x57\x7b\x6b\x64\x85\x6a\xce\x01\x95\xed\xa1\x17\xf3\x79\x03\xf4\xef\x59\xb2\x45\x09\x22\x86\x42\xcc\x38\x03\x8e\x94\x47\xb5\xd4\xb9\x98\xcf\xbb\xb0\x9d\x9c\x75\x77\xcb\xdb\x0f\xa5\xa1\x60\xce\xa4\x03\x33\xe9\x82\xdc\x9e\x8a\x6e\x9c\xbe\xe2\x89\x14\x65\xde\x9c\x0f\x74\xca\x56\x66\x1a\x4d\xc3\x10\x62\x50\x45\xd7\x30\xb7\x5b\x5d\xb0\xba\x40\xb4\x26\xe1\x7e\x48\x80\x26\xe4\x25\x88\x09\x65\x18\x05\x44\x6b\x93\xee\xcc\x31\x5c\x36\x28\x3e\x93\x17\x9a\x64\x09\x38\x49\x60\xe2\x89\x72\x28\xe4\xfd\x22\x53\xe2\x32\x11\xee\x45\xa6\x83\x28\x93\x45\xb3\xd5\x55\xc5\x57\x61\x28\x32\xae\x21\x17\x87\x42\xbc\xa7\x84\xe7\xf3\x9f\xf1\xc9\x1e\x4f\x5d\xe3\xe2\x27\x3c\xf9\x93\xa2\x71\xb6\x42\xae\xa8\x1d\x77\x1a\xaf\x01\xf5\xe4\xee\x61\xd7\x06\xc6\xd5\xc7\xcd\x64\x79\x79\x35\xf9\xff\xdb\xcf\x4d\xb2\x8f\xa5\x8a\xd7\xe5\x1a\xf6\x5e\x4a\x0f\xac\x24\xa6\x69\x70\xdc\x51\x8d\x8c\x2a\xdd\x55\xa4\xee\xd7\x40\xa2\x48\xa2\x52\xa8\x80\x30\x26\x8e\xb6\x20\x82\x6b\xcf\x7b\xf7\x43\xd3\x60\xcb\x48\xb8\x1f\x04\xbb\x35\x07\x80\x11\xc4\x52\x24\xaf\x01\xe7\x69\x33\xb0\xef\x20\xe7\xeb\xce\xaf\x8f\x8f\xeb\x8d\xf5\x08\x61\xac\x7a\xe3\xea\xab\x38\x11\x55\x26\x94\x83\x8c\xdb\x7a\x87\x41\xa9\xd1\x13\xfa\x77\x4e\x01\xb8\xe0\x13\xc7\xd5\xc5\xd1\x48\xc9\x0e\x3a\x14\x62\x4f\xb1\x07\xf7\xab\x42\x08\x82\x5f\x85\xd2\x13\x48\x25\xc6\xf4\x05\x08\x8f\x60\x63\x55\x21\x66\xe4\xc9\x6e\xca\x61\xb4\xb6\x33\x85\x0d\x9a\x9e\xc2\x41\x5b\x49\x63\x9c\xe9\xf9\x00\xf9\x81\x4a\xc1\x13\x34\x26\xda\xf7\x31\x63\x7b\xf9\x94\x04\xf7\x6b\x05\x8c\xee\x11\x16\xd7\xcb\xe9\xe2\xea\xc3\xf4\x65\xfa\x52\xeb\x04\xca\x37\xb2\xef\xcd\x46\x27\x4f\x4a\xa6\x39\x32\x71\x1b\x0a\x1e\xd3\xc8\x4c\xe1\x84\xb5\x7b\xc6\xfc\x01\xd0\x25\x27\xa3\x02\x13\xf0\x35\x66\x69\xb6\x65\x34\xf4\xb6\xe6\x7f\x6d\x35\x59\xae\xa5\x2c\x9c\x1f\x98\x61\xa1\xea\x15\x72\x9b\x03\x85\xa1\x44\x1d\x6c\x89\xa2\xe1\x2b\x16\x35\x1e\x10\x1c\x94\x6f\x4c\x1b\xb1\x65\xd3\x93\x24\xb9\x3b\x5c\xab\xd9\x9e\x60\x4c\xe3\x59\x9b\xd5\x5a\xfd\x4c\xf9\xd2\x5a\xf6\x35\x16\xb5\xdd\x81\xb6\xc1\xc7\x50\x83\x6e\x37\xa6\x12\x55\x2a\xb8\x42\xcf\x44\x6b\xd4\xeb\x36\x14\x9a\x6d\x33\x0c\x40\x9b\x49\x85\xa2\x60\x10\x29\x72\x1a\x99\x8d\xa7\x52\xc4\x94\x59\x1f\x60\x42\x28\xeb\xe5\xcd\x9f\x2b\x2d\x86\xcf\xe5\xa0\xc6\x90\x03\x8d\xc1\xc2\x54\xec\x65\x91\x3c\xd3\x90\xe4\xef\xb9\xf7\x77\xc3\x5a\x91\xfa\xb1\x97\xb0\x57\xef\xbb\x61\x9d\xdc\x19\xe8\xab\xf7\x7d\xd0\x5e\x9f\x36\x6f\x8d\x5c\x0d\xf8\x5c\x16\x26\x30\x87\xff\x03\x8e\x07\x94\x80\x2f\x29\x95\x35\xae\x79\x35\x80\x45\x27\x4e\x12\x1a\x06\x12\x9f\xa8\xd2\xb2\xf9\xfc\xd5\xff\x2a\x91\x2b\x16\xb7\xc4\xd7\xef\xcb\xa9\x79\xc1\x08\x24\xba\x27\xd8\x20\x93\x34\x50\xe1\x0e\x93\x3c\x22\x5c\x1a\x1f\xbb\x77\xed\x56\x10\xac\xf2\x7a\x53\xa8\xc3\xd7\x87\x7b\xc8\xd5\xfd\x60\xb0\x28\x63\x30\xff\x7c\x3b\xcf\x6d\xd2\x9c\x63\x2e\xb3\x9e\x61\x5f\x2c\x7f\x9a\xce\xa7\xf3\xe9\x62\x98\x09\x16\xc5\x37\xa0\x04\x1b\x43\x09\xd5\x8a\x45\x6f\x5c\x1d\xda\xfd\xff\x27\x83\x6e\x07\x4b\x35\xf7\x99\x50\xe2\x82\xb7\x9e\x7f\x6e\x5b\x04\x1d\xc3\xa2\x51\x9c\x89\x5c\x67\x56\xfe\xa4\x52\x19\x62\x04\xea\x23\x63\x8c\x44\x67\x12\x95\x5f\x2a\xec\xef\x1a\xcb\xc5\xa0\x58\xab\x46\xc5\x02\xe9\xec\xa0\x88\xdc\xb6\x9e\xbd\x98\xae\x31\x2d\x07\x51\xd3\x0f\x38\x77\xf5\xc2\x76\xbf\xda\x0d\xb2\xbd\xe7\xf5\xae\xaf\xed\x70\x6f\x2d\x31\x13\xc7\x61\xb7\xd0\xbd\xb8\xd4\x52\x7e\xf3\x25\xa7\x46\x90\x57\xc7\x97\x70\x47\xcc\x10\x3d\x84\xc3\x59\x5e\xa8\xc0\xc3\x2f\xb7\xf0\xe1\xea\xfa\x5d\x6f\xe8\xa5\x99\xda\x99\x1e\xbe\x56\x86\x86\x10\x39\xc5\xc6\x66\xca\x51\xcb\xf0\x5e\x2f\x96\x57\xbd\xbc\x45\x2a\xcb\x43\xff\xbf\x9a\xd1\x0c\xf9\x4f\x97\xd7\x8b\x3e\xb7\x26\x61\x1a\x54\xcf\x18\x83\xe8\x3e\xdf\xae\xe1\xed\x67\xfb\xe3\xe4\xad\xe0\x1a\x5f\x34\xac\xa5\xd0\x22\x14\xec\xa2\xfb\x49\xa4\xf9\xb2\xab\x44\x66\xa7\xb3\x72\xda\x1e\x16\x8f\x4e\xcd\x1b\xbd\xf3\x6a\xde\xcb\xf4\xbf\x7e\xa4\xfb\x0b\x7f\xe6\x89\x52\x91\x0e\xdb\xc1\xdd\x5a\xac\xe1\xed\x1d\x26\x82\x97\x51\x20\x62\x73\x44\x22\x9e\x88\x78\xb2\x16\x66\x5a\x51\x54\xf0\x8b\x5e\xb6\xe7\xa3\x0e\x28\xd7\x52\xa8\xb4\x35\x3e\xf7\xef\xea\x6f\xff\x7c\x2c\x7e\xac\xf5\x75\xcf\xdf\x68\x89\x07\x11\x0e\x8f\xf5\x3c\x1b\x95\x4a\x2e\xc0\xe7\xf3\xeb\x3e\x96\x4c\xa1\xa4\x3c\x16\x81\xd9\xd3\xd0\x5d\xe4\x3f\x20\x98\x24\xfb\x55\xa1\xbc\xe7\xb1\x28\x1b\xc6\x9e\xe3\xb7\x25\xe3\xc7\xf8\xcd\x8f\x37\xff\x0e\x00\x00\xff\xff\xbb\x95\xdd\xac\xba\x20\x00\x00") func initOpenapiOpenapiYaoBytes() ([]byte, error) { return bindataRead( @@ -3575,7 +3575,7 @@ func initOpenapiOpenapiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8192, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/openapi.yao", size: 8378, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3595,7 +3595,7 @@ func initOpenapiScopes__yaoYaoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/__yao/yao.yml", size: 747, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3615,7 +3615,7 @@ func initOpenapiScopesAgentAssistantsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/assistants.yml", size: 2054, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3635,7 +3635,7 @@ func initOpenapiScopesAgentRobotsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 4625, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/agent/robots.yml", size: 4625, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3655,7 +3655,7 @@ func initOpenapiScopesAliasYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 17481, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/alias.yml", size: 17481, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3675,7 +3675,7 @@ func initOpenapiScopesApiApiYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/api/api.yml", size: 727, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3695,7 +3695,7 @@ func initOpenapiScopesAppMenuYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/app/menu.yml", size: 476, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3715,7 +3715,7 @@ func initOpenapiScopesChatCompletionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/completions.yml", size: 2001, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3735,7 +3735,7 @@ func initOpenapiScopesChatModelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/models.yml", size: 589, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3755,7 +3755,7 @@ func initOpenapiScopesChatReferencesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/references.yml", size: 581, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3775,7 +3775,7 @@ func initOpenapiScopesChatSessionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/chat/sessions.yml", size: 1603, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3795,7 +3795,7 @@ func initOpenapiScopesDslDslsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/dsl/dsls.yml", size: 2911, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3815,7 +3815,7 @@ func initOpenapiScopesFileFilesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/file/files.yml", size: 1486, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3835,7 +3835,7 @@ func initOpenapiScopesJobCategoriesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/categories.yml", size: 249, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3855,7 +3855,7 @@ func initOpenapiScopesJobExecutionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/executions.yml", size: 1217, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3875,7 +3875,7 @@ func initOpenapiScopesJobJobsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/jobs.yml", size: 937, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3895,7 +3895,7 @@ func initOpenapiScopesJobLogsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/logs.yml", size: 564, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3915,7 +3915,7 @@ func initOpenapiScopesJobStatsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/job/stats.yml", size: 419, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3935,7 +3935,7 @@ func initOpenapiScopesKbBackupsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/backups.yml", size: 713, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3955,7 +3955,7 @@ func initOpenapiScopesKbCollectionsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/collections.yml", size: 1725, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3975,7 +3975,7 @@ func initOpenapiScopesKbDocumentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/documents.yml", size: 2235, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3995,7 +3995,7 @@ func initOpenapiScopesKbGraphsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/graphs.yml", size: 1847, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4015,7 +4015,7 @@ func initOpenapiScopesKbHitsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/hits.yml", size: 1766, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4035,7 +4035,7 @@ func initOpenapiScopesKbProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/providers.yml", size: 351, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4055,7 +4055,7 @@ func initOpenapiScopesKbSearchYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/search.yml", size: 535, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4075,7 +4075,7 @@ func initOpenapiScopesKbSegmentsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/segments.yml", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4095,7 +4095,7 @@ func initOpenapiScopesKbVotesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/kb/votes.yml", size: 1805, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4115,7 +4115,7 @@ func initOpenapiScopesLlmProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/llm/providers.yml", size: 268, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4135,7 +4135,7 @@ func initOpenapiScopesMcpServersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/mcp/servers.yml", size: 242, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4155,7 +4155,7 @@ func initOpenapiScopesMessengerChannelsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/channels.yml", size: 244, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4175,7 +4175,7 @@ func initOpenapiScopesMessengerProvidersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/providers.yml", size: 306, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4195,7 +4195,7 @@ func initOpenapiScopesMessengerWebhooksYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/messenger/webhooks.yml", size: 512, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4215,7 +4215,7 @@ func initOpenapiScopesScopesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 527, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/scopes.yml", size: 527, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4235,7 +4235,7 @@ func initOpenapiScopesTraceTracesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/trace/traces.yml", size: 1456, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4255,7 +4255,7 @@ func initOpenapiScopesUserEntryYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/entry.yml", size: 794, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4275,7 +4275,7 @@ func initOpenapiScopesUserFeaturesYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/features.yml", size: 231, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4295,7 +4295,7 @@ func initOpenapiScopesUserInvitationsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/invitations.yml", size: 1458, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4315,7 +4315,7 @@ func initOpenapiScopesUserMembersYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/members.yml", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4335,7 +4335,7 @@ func initOpenapiScopesUserProfileYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 333, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/profile.yml", size: 333, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4355,7 +4355,7 @@ func initOpenapiScopesUserTeamsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/scopes/user/teams.yml", size: 1724, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4375,7 +4375,7 @@ func initOpenapiUserClientYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/client.yao", size: 624, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4395,7 +4395,7 @@ func initOpenapiUserEntryEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2438, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/en.yao", size: 2438, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4415,7 +4415,7 @@ func initOpenapiUserEntryZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2322, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/entry/zh-cn.yao", size: 2322, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4435,7 +4435,7 @@ func initOpenapiUserProvidersAppleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/apple.yao", size: 946, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4455,7 +4455,7 @@ func initOpenapiUserProvidersGithubYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/github.yao", size: 428, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4475,7 +4475,7 @@ func initOpenapiUserProvidersGoogleYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/google.yao", size: 434, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4495,7 +4495,7 @@ func initOpenapiUserProvidersMicrosoftYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/providers/microsoft.yao", size: 487, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4515,7 +4515,7 @@ func initOpenapiUserTeamEnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/en.yao", size: 2662, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4535,7 +4535,7 @@ func initOpenapiUserTeamZhCnYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/openapi/user/team/zh-cn.yao", size: 2576, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4555,7 +4555,7 @@ func initScriptsMenuTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/scripts/menu.ts", size: 15362, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4575,7 +4575,7 @@ func initScriptsSetupTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/setup.ts", size: 11914, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/scripts/setup.ts", size: 11914, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4595,7 +4595,7 @@ func initSeedsInvitation_codesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/seeds/invitation_codes.csv", size: 643, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4615,7 +4615,7 @@ func initSeedsMenusCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/seeds/menus.csv", size: 976, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4635,7 +4635,7 @@ func initSeedsRolesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/seeds/roles.csv", size: 2799, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4655,7 +4655,7 @@ func initSeedsTypesCsv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/seeds/types.csv", size: 3357, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4675,7 +4675,7 @@ func initServicesReademeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/services/READEME.md", size: 18, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4695,7 +4695,7 @@ func initSuisWebSuiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4715,7 +4715,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4735,7 +4735,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13047, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13047, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4755,7 +4755,7 @@ func libsuiOpenapiTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4775,7 +4775,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4795,7 +4795,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4815,7 +4815,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4835,7 +4835,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4855,7 +4855,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4875,7 +4875,7 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4895,7 +4895,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4915,7 +4915,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4935,7 +4935,7 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4955,7 +4955,7 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4975,7 +4975,7 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -4995,7 +4995,7 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5015,7 +5015,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5035,7 +5035,7 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5055,7 +5055,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5075,7 +5075,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5095,7 +5095,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5115,7 +5115,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5135,7 +5135,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5155,7 +5155,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5175,7 +5175,7 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5195,7 +5195,7 @@ func yaoAssistantsRobot_promptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 204, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 204, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5215,7 +5215,7 @@ func yaoAssistantsRobot_promptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5235,7 +5235,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5255,7 +5255,7 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5275,7 +5275,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5295,7 +5295,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5315,7 +5315,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5335,7 +5335,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5355,7 +5355,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5375,7 +5375,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5395,7 +5395,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5415,7 +5415,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5435,7 +5435,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5455,7 +5455,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5475,7 +5475,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5495,7 +5495,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5515,7 +5515,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5535,7 +5535,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5555,7 +5555,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5575,7 +5575,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5595,7 +5595,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5615,7 +5615,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5635,7 +5635,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5655,7 +5655,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5675,7 +5675,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5695,7 +5695,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5715,7 +5715,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5735,7 +5735,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5755,7 +5755,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5775,7 +5775,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5795,7 +5795,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5815,7 +5815,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5835,7 +5835,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5855,7 +5855,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5875,7 +5875,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5895,7 +5895,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5915,7 +5915,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5935,7 +5935,7 @@ func yaoFieldsModelTransJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5955,7 +5955,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5975,7 +5975,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -5995,7 +5995,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6015,7 +6015,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6035,7 +6035,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6055,7 +6055,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6075,7 +6075,7 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6095,7 +6095,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6115,7 +6115,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6135,7 +6135,7 @@ func yaoModelsAgentExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 5579, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 5579, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6155,7 +6155,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6175,7 +6175,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6195,7 +6195,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6215,7 +6215,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6235,7 +6235,7 @@ func yaoModelsAuditModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6255,7 +6255,7 @@ func yaoModelsConfigModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6275,7 +6275,7 @@ func yaoModelsDslModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6295,7 +6295,7 @@ func yaoModelsInvitationModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6315,7 +6315,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6335,7 +6335,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6355,7 +6355,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6375,7 +6375,7 @@ func yaoModelsJobLogModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6395,7 +6395,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6415,7 +6415,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6435,7 +6435,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6455,7 +6455,7 @@ func yaoModelsRoleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6475,7 +6475,7 @@ func yaoModelsTeamModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6495,7 +6495,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6515,7 +6515,7 @@ func yaoModelsUserTypeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6535,7 +6535,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6555,7 +6555,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6575,7 +6575,7 @@ func yaoStoresAgentCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6595,7 +6595,7 @@ func yaoStoresAgentMemoryChatXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6615,7 +6615,7 @@ func yaoStoresAgentMemoryContextXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6635,7 +6635,7 @@ func yaoStoresAgentMemoryTeamXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6655,7 +6655,7 @@ func yaoStoresAgentMemoryUserXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6675,7 +6675,7 @@ func yaoStoresCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6695,7 +6695,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6715,7 +6715,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6735,7 +6735,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6755,7 +6755,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6775,7 +6775,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6795,7 +6795,7 @@ func yaoStoresStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -6815,7 +6815,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1770202828, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1770207364, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/openapi/config.go b/openapi/config.go index 9925ef37..b0dff37a 100644 --- a/openapi/config.go +++ b/openapi/config.go @@ -93,6 +93,7 @@ func (config *Config) MarshalJSON() ([]byte, error) { IPBlacklist: config.OAuth.Security.IPBlacklist, RequireHTTPS: config.OAuth.Security.RequireHTTPS, DisableUnsecureEndpoints: config.OAuth.Security.DisableUnsecureEndpoints, + SecureCookie: config.OAuth.Security.SecureCookie, }, Client: TempClientConfig{ DefaultClientType: config.OAuth.Client.DefaultClientType, @@ -215,6 +216,7 @@ func (config *Config) UnmarshalJSON(data []byte) error { IPBlacklist: tempConfig.OAuth.Security.IPBlacklist, RequireHTTPS: tempConfig.OAuth.Security.RequireHTTPS, DisableUnsecureEndpoints: tempConfig.OAuth.Security.DisableUnsecureEndpoints, + SecureCookie: tempConfig.OAuth.Security.SecureCookie, } if tempConfig.OAuth.Security.StateParameterLifetime != "" { if duration, err := parseDuration(tempConfig.OAuth.Security.StateParameterLifetime); err == nil { diff --git a/openapi/oauth/oauth.go b/openapi/oauth/oauth.go index e67156ea..cbc9626a 100644 --- a/openapi/oauth/oauth.go +++ b/openapi/oauth/oauth.go @@ -179,6 +179,14 @@ func (s *Service) GetStore() store.Store { return s.store } +// GetSecurityConfig returns the security configuration for the service +func (s *Service) GetSecurityConfig() types.SecurityConfig { + if s.config == nil { + return types.SecurityConfig{} + } + return s.config.Security +} + // setConfigDefaults sets default values for configuration func setConfigDefaults(config *Config) error { // Certificate defaults diff --git a/openapi/oauth/types/types.go b/openapi/oauth/types/types.go index 730cee42..d7477b1d 100644 --- a/openapi/oauth/types/types.go +++ b/openapi/oauth/types/types.go @@ -575,6 +575,9 @@ type SecurityConfig struct { IPBlacklist []string `json:"ip_blacklist,omitempty"` // Optional: IP addresses blocked from access (default: []) RequireHTTPS bool `json:"require_https"` // Optional: Require HTTPS for all endpoints (default: true) DisableUnsecureEndpoints bool `json:"disable_unsecure_endpoints"` // Optional: Disable non-HTTPS endpoints (default: false) + + // Cookie security settings + SecureCookie *bool `json:"secure_cookie,omitempty"` // Optional: Use __Host- prefix and Secure flag for cookies (default: true). Set to false for non-HTTPS dev environments with non-localhost IPs. } // TokenClaims represents decoded token claims for both JWT and opaque tokens diff --git a/openapi/openapi.go b/openapi/openapi.go index a7d6ff0c..6a6113b7 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -21,6 +21,7 @@ import ( "github.com/yaoapp/yao/openapi/oauth" "github.com/yaoapp/yao/openapi/oauth/acl" "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" "github.com/yaoapp/yao/openapi/team" openapiTrace "github.com/yaoapp/yao/openapi/trace" "github.com/yaoapp/yao/openapi/user" @@ -63,6 +64,10 @@ func Load(appConfig config.Config) (*OpenAPI, error) { return nil, err } + // Set the secure cookie configuration for the response package + // This determines whether to use __Host- prefix and Secure flag for cookies + response.SetSecureCookieEnabled(oauthConfig.Security.SecureCookie) + // Load user configurations err = user.Load(appConfig) if err != nil { diff --git a/openapi/response/response.go b/openapi/response/response.go index 23e97c0c..7969c2f4 100644 --- a/openapi/response/response.go +++ b/openapi/response/response.go @@ -8,6 +8,23 @@ import ( "github.com/yaoapp/yao/openapi/oauth/types" ) +// secureCookieEnabled is the global setting for secure cookie behavior +// Default is nil (meaning true/enabled). Set to false to disable __Host- prefix and Secure flag. +// This is set during OAuth initialization based on the secure_cookie config. +var secureCookieEnabled *bool + +// SetSecureCookieEnabled sets the global secure cookie setting +// This should be called during OAuth initialization +func SetSecureCookieEnabled(enabled *bool) { + secureCookieEnabled = enabled +} + +// IsSecureCookieEnabled returns whether secure cookie is enabled +// Returns true if secureCookieEnabled is nil or true +func IsSecureCookieEnabled() bool { + return secureCookieEnabled == nil || *secureCookieEnabled +} + // Type aliases for OAuth types to simplify usage type ( // Core response types @@ -202,13 +219,14 @@ type SecureCookieOptions struct { } // NewSecureCookieOptions creates a new SecureCookieOptions with secure defaults +// The UseHostPrefix is determined by the secure_cookie configuration in openapi.yao func NewSecureCookieOptions() *SecureCookieOptions { return &SecureCookieOptions{ - MaxAge: 0, // Session cookie by default - Path: "/", // Root path - Domain: "", // Current domain - SameSite: "Lax", // Default SameSite policy - UseHostPrefix: true, // Use most secure __Host- prefix + MaxAge: 0, // Session cookie by default + Path: "/", // Root path + Domain: "", // Current domain + SameSite: "Lax", // Default SameSite policy + UseHostPrefix: IsSecureCookieEnabled(), // Determined by secure_cookie config } } @@ -284,12 +302,15 @@ func SendSecureCookieWithOptions(c *gin.Context, key string, value string, optio cookiePath := options.Path cookieDomain := options.Domain - if options.UseHostPrefix { + // Use the global secure cookie setting + useSecureCookie := IsSecureCookieEnabled() + + if options.UseHostPrefix && useSecureCookie { // __Host- prefix: Requires Secure flag, no Domain attribute, Path=/ cookieName = "__Host-" + key cookiePath = "/" // Must be "/" for __Host- prefix cookieDomain = "" // Must be empty for __Host- prefix - } else if options.UseSecurePrefix { + } else if options.UseSecurePrefix && useSecureCookie { // __Secure- prefix: Requires Secure flag, allows Domain and Path cookieName = "__Secure-" + key } @@ -319,7 +340,7 @@ func SendSecureCookieWithOptions(c *gin.Context, key string, value string, optio effectiveMaxAge, // maxAge (calculated from Expires if needed) cookiePath, // path cookieDomain, // domain - true, // secure (HTTPS only) - required for security prefixes + useSecureCookie, // secure (HTTPS only) - based on secure_cookie config true, // httpOnly (prevent XSS access) ) diff --git a/openapi/types.go b/openapi/types.go index a30f1c63..44568eaf 100644 --- a/openapi/types.go +++ b/openapi/types.go @@ -92,6 +92,7 @@ type TempSecurityConfig struct { IPBlacklist []string `json:"ip_blacklist,omitempty"` RequireHTTPS bool `json:"require_https"` DisableUnsecureEndpoints bool `json:"disable_unsecure_endpoints"` + SecureCookie *bool `json:"secure_cookie,omitempty"` } // TempClientConfig represents client configuration with string duration fields From 015fc9ef92e256ddd0760e466deb9b74bbab4c0c Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 4 Feb 2026 20:55:53 +0800 Subject: [PATCH 2/6] Add secure_cookie field to EntryConfig API response - Add SecureCookie field to EntryConfig struct for frontend access - Add GetCookieName helper to response package for dynamic cookie names - Update guard.go to use GetCookieName instead of hardcoded __Host- prefix - Pass secure_cookie setting to /user/entry API response Co-authored-by: Cursor --- openapi/oauth/guard.go | 9 ++++++--- openapi/response/response.go | 9 +++++++++ openapi/user/entry.go | 3 +++ openapi/user/types.go | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/openapi/oauth/guard.go b/openapi/oauth/guard.go index c9695790..c29449a8 100644 --- a/openapi/oauth/guard.go +++ b/openapi/oauth/guard.go @@ -109,7 +109,8 @@ func (s *Service) tryAutoRefreshToken(c *gin.Context, _ *types.TokenClaims) { func (s *Service) getAccessToken(c *gin.Context) string { token := c.GetHeader("Authorization") if token == "" { - cookie, err := c.Cookie("__Host-access_token") + cookieName := response.GetCookieName("access_token") + cookie, err := c.Cookie(cookieName) if err != nil { return "" } @@ -176,7 +177,8 @@ func (s *Service) GetAccessToken(c *gin.Context) string { func (s *Service) getRefreshToken(c *gin.Context) string { token := c.GetHeader("Authorization") if token == "" { - cookie, err := c.Cookie("__Host-refresh_token") + cookieName := response.GetCookieName("refresh_token") + cookie, err := c.Cookie(cookieName) if err != nil { return "" } @@ -200,7 +202,8 @@ func (s *Service) getSessionID(c *gin.Context) string { } // 1. Try to get Session ID from cookies first - if sid, err := c.Cookie("__Host-session_id"); err == nil && sid != "" { + cookieName := response.GetCookieName("session_id") + if sid, err := c.Cookie(cookieName); err == nil && sid != "" { return sid } diff --git a/openapi/response/response.go b/openapi/response/response.go index 7969c2f4..3286716d 100644 --- a/openapi/response/response.go +++ b/openapi/response/response.go @@ -25,6 +25,15 @@ func IsSecureCookieEnabled() bool { return secureCookieEnabled == nil || *secureCookieEnabled } +// GetCookieName returns the correct cookie name based on secure cookie setting +// If secure cookie is enabled, it returns "__Host-" + name, otherwise just name +func GetCookieName(name string) string { + if IsSecureCookieEnabled() { + return "__Host-" + name + } + return name +} + // Type aliases for OAuth types to simplify usage type ( // Core response types diff --git a/openapi/user/entry.go b/openapi/user/entry.go index 2e49886c..4c289c6f 100644 --- a/openapi/user/entry.go +++ b/openapi/user/entry.go @@ -45,6 +45,9 @@ func getEntryConfig(c *gin.Context) { // Create public config without sensitive data (deep copy to avoid modifying global config) publicConfig := createPublicEntryConfig(config) + // Add secure_cookie setting from OAuth config + publicConfig.SecureCookie = response.IsSecureCookieEnabled() + // Return the entry configuration response.RespondWithSuccess(c, response.StatusOK, publicConfig) } diff --git a/openapi/user/types.go b/openapi/user/types.go index ae88e03f..30c6d57c 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -112,6 +112,7 @@ type EntryConfig struct { InviteRequired bool `json:"invite_required,omitempty"` // From register config Invite *InvitePageConfig `json:"invite,omitempty"` // Invite code page configuration ThirdParty *ThirdParty `json:"third_party,omitempty"` + SecureCookie bool `json:"secure_cookie"` // Whether secure cookie is enabled (for frontend JWT verification) } // MessengerConfig represents the messenger configuration for user registration From 69058787f9df51f10045a77d0e2e75fd4acd82ca Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 5 Feb 2026 14:45:54 +0800 Subject: [PATCH 3/6] Add VNC remote desktop support for sandbox containers - Add VNC-enabled Docker images (playwright, desktop) with Xvfb, x11vnc, noVNC - Implement VNC proxy service for WebSocket-based VNC access - Add API endpoints: /sandbox/{id}/vnc, /vnc/client, /vnc/ws - Support dynamic VNC port mapping for Docker Desktop (macOS/Windows) - Add YAO_SANDBOX_VNC_PORT_MAPPING config option for local development - Update build.sh to support building VNC images - Include design document and updated README Co-authored-by: Cursor --- .gitignore | 1 + openapi/openapi.go | 4 + openapi/sandbox/sandbox.go | 98 ++ sandbox/DESIGN-PLAYWRIGHT-VNC.md | 1498 ++++++++++++++++++++++++++ sandbox/README.md | 97 +- sandbox/config.go | 8 + sandbox/docker/build.sh | 36 +- sandbox/docker/desktop/Dockerfile | 91 ++ sandbox/docker/playwright/Dockerfile | 89 ++ sandbox/docker/vnc/entrypoint-vnc.sh | 40 + sandbox/docker/vnc/start-vnc.sh | 94 ++ sandbox/manager.go | 36 + sandbox/vncproxy/config.go | 81 ++ sandbox/vncproxy/proxy.go | 565 ++++++++++ sandbox/vncproxy/proxy_test.go | 90 ++ 15 files changed, 2811 insertions(+), 17 deletions(-) create mode 100644 openapi/sandbox/sandbox.go create mode 100644 sandbox/DESIGN-PLAYWRIGHT-VNC.md create mode 100644 sandbox/docker/desktop/Dockerfile create mode 100644 sandbox/docker/playwright/Dockerfile create mode 100644 sandbox/docker/vnc/entrypoint-vnc.sh create mode 100644 sandbox/docker/vnc/start-vnc.sh create mode 100644 sandbox/vncproxy/config.go create mode 100644 sandbox/vncproxy/proxy.go create mode 100644 sandbox/vncproxy/proxy_test.go diff --git a/.gitignore b/.gitignore index 8ddfece5..eaab7002 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ agent/test/MULTI_TURN_DESIGN.md agent/test/UPGRADE_PLAN.md introduction/* !sandbox/docker/build.sh +!sandbox/docker/vnc/*.sh sandbox/docker/yao-bridge-* sandbox/docker/claude-proxy-* sandbox/docker/claude/claude-proxy-* diff --git a/openapi/openapi.go b/openapi/openapi.go index 6a6113b7..3d3da032 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -22,6 +22,7 @@ import ( "github.com/yaoapp/yao/openapi/oauth/acl" "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/response" + "github.com/yaoapp/yao/openapi/sandbox" "github.com/yaoapp/yao/openapi/team" openapiTrace "github.com/yaoapp/yao/openapi/trace" "github.com/yaoapp/yao/openapi/user" @@ -159,6 +160,9 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) { // App handlers (menu, etc.) app.Attach(group.Group("/app"), openapi.OAuth) + // Sandbox handlers (VNC proxy for visual browser automation) + sandbox.Attach(group.Group("/sandbox"), openapi.OAuth) + // Custom handlers (Defined by developer) } diff --git a/openapi/sandbox/sandbox.go b/openapi/sandbox/sandbox.go new file mode 100644 index 00000000..eecaf437 --- /dev/null +++ b/openapi/sandbox/sandbox.go @@ -0,0 +1,98 @@ +package sandbox + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/sandbox/vncproxy" +) + +var vncProxy *vncproxy.Proxy + +// Attach attaches sandbox handlers to the router group +// Routes: +// - GET /sandbox/:id/vnc - Get VNC status +// - GET /sandbox/:id/vnc/client - Get noVNC client page +// - GET /sandbox/:id/vnc/ws - WebSocket proxy to container VNC +func Attach(group *gin.RouterGroup, oauth types.OAuth) { + // Initialize VNC proxy lazily on first request + // This avoids startup errors if Docker is not available + + // VNC status endpoint (requires auth) + group.GET("/:id/vnc", oauth.Guard, handleVNCStatus) + + // VNC client page (requires auth) + group.GET("/:id/vnc/client", oauth.Guard, handleVNCClient) + + // VNC WebSocket proxy (requires auth) + // Note: WebSocket upgrade happens after auth middleware + group.GET("/:id/vnc/ws", oauth.Guard, handleVNCWebSocket) +} + +// ensureProxy ensures the VNC proxy is initialized +func ensureProxy() error { + if vncProxy != nil { + return nil + } + + var err error + vncProxy, err = vncproxy.NewProxy(nil) + return err +} + +// handleVNCStatus returns VNC status for a sandbox container +func handleVNCStatus(c *gin.Context) { + if err := ensureProxy(); err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "VNC service not available", + }) + return + } + + // Rewrite path to match vncproxy expected format + sandboxID := c.Param("id") + c.Request.URL.Path = "/v1/sandbox/" + sandboxID + "/vnc" + + vncProxy.HandleVNCStatus(c.Writer, c.Request) +} + +// handleVNCClient serves the noVNC client page +func handleVNCClient(c *gin.Context) { + if err := ensureProxy(); err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "VNC service not available", + }) + return + } + + // Rewrite path to match vncproxy expected format + sandboxID := c.Param("id") + c.Request.URL.Path = "/v1/sandbox/" + sandboxID + "/vnc/client" + + vncProxy.HandleVNCClient(c.Writer, c.Request) +} + +// handleVNCWebSocket proxies WebSocket to container VNC +func handleVNCWebSocket(c *gin.Context) { + if err := ensureProxy(); err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "VNC service not available", + }) + return + } + + // Rewrite path to match vncproxy expected format + sandboxID := c.Param("id") + c.Request.URL.Path = "/v1/sandbox/" + sandboxID + "/vnc/ws" + + vncProxy.HandleVNCWebSocket(c.Writer, c.Request) +} + +// Close closes the VNC proxy and releases resources +func Close() error { + if vncProxy != nil { + return vncProxy.Close() + } + return nil +} diff --git a/sandbox/DESIGN-PLAYWRIGHT-VNC.md b/sandbox/DESIGN-PLAYWRIGHT-VNC.md new file mode 100644 index 00000000..51a9e834 --- /dev/null +++ b/sandbox/DESIGN-PLAYWRIGHT-VNC.md @@ -0,0 +1,1498 @@ +# Sandbox VNC Integration Design Document + +## Overview + +This document describes the design for integrating VNC remote desktop access into the Yao Sandbox system. This enables users to **observe Claude's operations in real-time** through a web-based VNC client, providing full transparency and building trust. + +The design provides **multiple sandbox image variants** with VNC support. Users can choose the appropriate image type when configuring their assistants based on their needs. + +## Goals + +1. **Transparency**: Let users see exactly what Claude is doing in the sandbox in real-time +2. **Multiple Image Options**: Provide different sandbox images for different use cases +3. **User Choice**: Allow users to select sandbox image type when building assistants +4. **Web-Based Access**: Use noVNC for browser-based VNC access (no client installation required) +5. **Unified Entry Point**: Single proxy endpoint to access any container's VNC session +6. **Security**: Proper authentication and isolation between users +7. **Minimal Core Changes**: Leverage existing sandbox infrastructure with minimal modifications + +## Non-Goals + +1. Persistent VNC sessions across container restarts +2. Multi-user access to the same VNC session +3. Audio support + +## Architecture + +### High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ User Browser │ +│ │ +│ ┌──────────────────────────────────────────────────────────────────────┐ │ +│ │ Yao Web UI │ │ +│ │ │ │ +│ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │ +│ │ │ 💬 Chat Window │ │ 📺 VNC Preview (iframe) │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ User: Help me... │ │ Real-time view of Claude's │ │ │ +│ │ │ │ │ operations in sandbox │ │ │ +│ │ │ Claude: Working... │ │ │ │ │ +│ │ └─────────────────────┘ └─────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + │ WebSocket (VNC) + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Yao Server (Host) │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ VNC Proxy Service │ │ +│ │ (sandbox/vncproxy) │ │ +│ │ │ │ +│ │ Endpoints: │ │ +│ │ ├── GET /v1/sandbox/{id}/vnc → VNC status │ │ +│ │ ├── GET /v1/sandbox/{id}/vnc/client → noVNC client │ │ +│ │ └── GET /v1/sandbox/{id}/vnc/ws → WebSocket │ │ +│ │ │ │ +│ │ Internal Flow: │ │ +│ │ 1. Authenticate request (JWT/session) │ │ +│ │ 2. Resolve container name: yao-sandbox-{id} │ │ +│ │ 3. Get container IP from Docker API │ │ +│ │ 4. Proxy WebSocket to container_ip:6080 │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ Docker Bridge Network │ +│ │ │ +│ ┌──────────────────────────────────────┼──────────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────────┐ ┌──────────────────────────┐ ┌──────────────────┐ │ +│ │ sandbox-claude │ │ sandbox-claude-playwright│ │ sandbox-claude- │ │ +│ │ (No VNC) │ │ (Browser + VNC) │ │ desktop (Full) │ │ +│ │ │ │ │ │ │ │ +│ │ • Claude CLI │ │ • Claude CLI │ │ • Claude CLI │ │ +│ │ • Node.js │ │ • Node.js │ │ • Node.js │ │ +│ │ • Python │ │ • Python │ │ • Python │ │ +│ │ │ │ • Playwright + Browsers │ │ • XFCE Desktop │ │ +│ │ │ │ • Xvfb + VNC │ │ • File Manager │ │ +│ │ │ │ • Fluxbox (minimal WM) │ │ • Terminal │ │ +│ │ │ │ │ │ • Xvfb + VNC │ │ +│ └──────────────────┘ └──────────────────────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Image Variants + +| Image | VNC | Use Case | Size | Memory | +|-------|-----|----------|------|--------| +| `sandbox-claude` | ❌ | Code execution, scripts, CLI tasks | ~700MB | 2GB | +| `sandbox-claude-playwright` | ✅ | Browser automation, web scraping | ~1.8GB | 4GB | +| `sandbox-claude-desktop` | ✅ | Full visibility, any GUI app | ~2.5GB | 4GB | + +### User Selection Flow + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Assistant Configuration UI │ +│ │ +│ Assistant Name: [My Web Scraper ] │ +│ │ +│ Sandbox Environment: │ +│ ┌─────────────────────────────────────────────────────────────────┐│ +│ │ ○ Standard (sandbox-claude) ││ +│ │ Code execution, no GUI. Lightweight and fast. ││ +│ │ ││ +│ │ ○ Browser (sandbox-claude-playwright) ⭐ ││ +│ │ Playwright browser automation with VNC preview. ││ +│ │ See browser operations in real-time. ││ +│ │ ││ +│ │ ● Desktop (sandbox-claude-desktop) ││ +│ │ Full Ubuntu desktop with VNC preview. ││ +│ │ See ALL operations: terminal, files, browser, etc. ││ +│ └─────────────────────────────────────────────────────────────────┘│ +│ │ +│ [ Save Assistant ] │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## Components + +### 1. Docker Images + +**Location**: `sandbox/docker/` + +Three image variants sharing the same VNC infrastructure: + +``` +ubuntu:24.04 + └── sandbox-base:latest (~200MB) + └── sandbox-claude:latest (~700MB) # No VNC + ├── sandbox-claude-playwright:latest (~1.8GB) # VNC + Browser + └── sandbox-claude-desktop:latest (~2.5GB) # VNC + Full Desktop +``` + +#### 1.1 sandbox-claude-playwright (Browser + VNC) + +For browser automation tasks with real-time visibility. + +**Includes**: +- Everything from `sandbox-claude` +- Xvfb (virtual display) +- x11vnc + noVNC +- Fluxbox (minimal window manager) +- Playwright + Chromium/Firefox + +#### 1.2 sandbox-claude-desktop (Full Desktop + VNC) + +For maximum transparency - users can see everything Claude does. + +**Includes**: +- Everything from `sandbox-claude` +- Xvfb (virtual display) +- x11vnc + noVNC +- XFCE desktop environment +- Thunar file manager +- xfce4-terminal +- Playwright + browsers (optional) + +### 2. VNC Proxy Service + +**Location**: `sandbox/vncproxy/` + +A unified Go service that provides VNC access to all VNC-enabled containers. + +**Key Features**: +- Single entry point for all containers +- WebSocket proxy to container VNC +- Container IP resolution via Docker API +- Authentication and authorization +- Works with any VNC-enabled image + +**Key Interfaces**: + +```go +// VNCProxy handles VNC connections to sandbox containers +type VNCProxy struct { + docker *client.Client + manager *sandbox.Manager + config *Config +} + +// Config for VNC proxy +type Config struct { + // Container VNC port (fixed, internal) + ContainerVNCPort int // default: 5900 + + // Container noVNC/websockify port (fixed, internal) + ContainerNoVNCPort int // default: 6080 + + // Connection timeout + Timeout time.Duration +} + +// ServeHTTP handles HTTP requests +func (p *VNCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) + +// GetVNCURL returns the VNC URL for a container +func (p *VNCProxy) GetVNCURL(sandboxID string) (string, error) + +// GetContainerIP returns the internal IP of a container +func (p *VNCProxy) GetContainerIP(containerName string) (string, error) +``` + +### 3. Manager Extensions (Optional) + +**Location**: `sandbox/manager.go` + +**Note**: These extensions are optional. The core sandbox functionality works without changes because: +- Image is specified in assistant config, passed to existing `GetOrCreate()` +- VNC status is determined by checking container env vars at runtime + +Optional helper types for convenience: + +```go +// ImageType represents the sandbox image variant (optional, for reference) +type ImageType string + +const ( + ImageTypeClaude ImageType = "claude" // No VNC + ImageTypePlaywright ImageType = "playwright" // Browser + VNC + ImageTypeDesktop ImageType = "desktop" // Full desktop + VNC +) + +// ImageConfig holds configuration for each image type (optional, for reference) +var ImageConfigs = map[ImageType]struct { + Image string + VNCEnabled bool + Memory string + CPU float64 +}{ + ImageTypeClaude: {"yaoapp/sandbox-claude:latest", false, "2g", 1.0}, + ImageTypePlaywright: {"yaoapp/sandbox-claude-playwright:latest", true, "4g", 2.0}, + ImageTypeDesktop: {"yaoapp/sandbox-claude-desktop:latest", true, "4g", 2.0}, +} +``` + +VNC access is determined at runtime by VNC Proxy checking container env vars - no Manager changes needed. + +### 4. API Endpoints + +All endpoints under `/v1/sandbox/`. Each sandbox has its own unique ID (generated by the caller/business layer). + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/v1/sandbox/{id}` | POST | Create container (with image) | +| `/v1/sandbox/{id}` | GET | Get container status | +| `/v1/sandbox/{id}` | DELETE | Stop/remove container | +| `/v1/sandbox/{id}/vnc` | GET | Get VNC access info | +| `/v1/sandbox/{id}/vnc/client` | GET | Serve noVNC HTML client (supports `?viewonly=true`) | +| `/v1/sandbox/{id}/vnc/ws` | GET | WebSocket proxy to container VNC | + +**Sandbox ID**: +- Generated by the caller (business layer) +- Format: any unique string (e.g., UUID, `{userID}-{chatID}`, `{assistantID}-{sessionID}`) +- Container name: `yao-sandbox-{id}` + +**Create Container Request**: + +```json +// POST /v1/sandbox/abc123-def456 +{ + "image": "yaoapp/sandbox-claude-desktop:latest" // Optional, defaults based on config +} +``` + +**VNC Access Response**: + +```json +// GET /v1/sandbox/abc123-def456/vnc +// VNC ready: +{ + "available": true, + "status": "ready", + "sandbox_id": "abc123-def456", + "container": "yao-sandbox-abc123-def456", + "client_url": "/v1/sandbox/abc123-def456/vnc/client", + "websocket_url": "/v1/sandbox/abc123-def456/vnc/ws" +} + +// VNC starting (container running but VNC services not ready yet): +{ + "available": false, + "status": "starting", + "sandbox_id": "abc123-def456", + "container": "yao-sandbox-abc123-def456", + "message": "VNC services are starting..." +} + +// VNC not supported (sandbox-claude image): +{ + "available": false, + "status": "not_supported", + "sandbox_id": "abc123-def456", + "container": "yao-sandbox-abc123-def456", + "message": "VNC not available for this container type" +} + +// Container not found/running: +{ + "available": false, + "status": "unavailable", + "sandbox_id": "abc123-def456", + "message": "Container not available" +} +``` + +**Full API Structure**: + +``` +/v1/sandbox/ +├── {id} +│ ├── POST # Create container +│ ├── GET # Get container status +│ ├── DELETE # Stop/remove container +│ ├── /exec # Execute command +│ ├── /files # File operations +│ └── /vnc # VNC access (if available) +│ ├── GET # VNC status & URLs +│ ├── /client # noVNC HTML client +│ └── /ws # WebSocket proxy +``` + +**Business Layer Integration Example**: + +```go +// Agent executor generates sandbox ID +sandboxID := fmt.Sprintf("%s-%s", userID, chatID) + +// Or use UUID for more isolation +sandboxID := uuid.New().String() + +// Or per-assistant session +sandboxID := fmt.Sprintf("%s-%s", assistantID, sessionID) +``` + +### 5. Assistant Configuration (Developer Side) + +Developers configure sandbox image type in the assistant's `package.yao` file: + +```yaml +# assistants/my-assistant/package.yao +name: My Web Assistant +description: Web scraping assistant with browser preview + +sandbox: + command: claude + image: "yaoapp/sandbox-claude-desktop:latest" # Choose image variant + max_memory: "4g" + max_cpu: 2.0 +``` + +**Available Images**: +- `yaoapp/sandbox-claude:latest` - No VNC, lightweight +- `yaoapp/sandbox-claude-playwright:latest` - Browser + VNC +- `yaoapp/sandbox-claude-desktop:latest` - Full desktop + VNC + +**Note**: No changes required to `agent/sandbox/` code. The existing `Image` field in `SandboxConfig` already supports custom images. + +### 6. CUI Integration (User Side) + +Users interact with VNC preview through CUI's action system. The VNC preview opens as a **sidebar iframe** via the `navigate` action. + +#### 6.1 Roles and Responsibilities + +| Role | Action | Interface | +|------|--------|-----------| +| **Developer** | Configure `sandbox.image` in `package.yao` | YAML config file | +| **User** | View VNC preview during chat | CUI chat interface | + +#### 6.2 No CUI Page Needed + +The CUI `navigate` action already supports loading any URL via iframe in the sidebar. The `/v1/sandbox/{id}/vnc/client` API returns a complete HTML page with noVNC, so we can use it directly. + +**Navigate action route types** (from `cui/packages/cui/chatbox/messages/Action/actions/navigate.ts`): +- `$dashboard/xxx` → CUI Dashboard pages +- `/xxx` → Loaded via iframe in sidebar +- `http(s)://xxx` → External URLs via iframe + +Since `/v1/sandbox/{id}/vnc/client` starts with `/`, it will be loaded in an iframe automatically. + +#### 6.3 Opening VNC Preview via Action + +When the sandbox starts and VNC is available, Claude can return a `navigate` action to open the preview: + +```json +{ + "type": "action", + "actions": [{ + "name": "navigate", + "payload": { + "route": "/v1/sandbox/abc123-def456/vnc/client", + "title": "实时预览", + "icon": "material-desktop_windows" + } + }] +} +``` + +Or as a clickable button in the chat: + +```json +{ + "type": "action", + "actions": [{ + "name": "button", + "payload": { + "text": "📺 查看实时预览", + "action": { + "name": "navigate", + "payload": { + "route": "/v1/sandbox/abc123-def456/vnc/client", + "title": "实时预览", + "icon": "material-desktop_windows" + } + } + } + }] +} +``` + +#### 6.4 User Experience Flow + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Step 1: User starts chat with sandbox-enabled assistant │ +│ │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ 💬 Chat │ │ +│ │ │ │ +│ │ User: 帮我爬取这个网站的数据 │ │ +│ │ │ │ +│ │ Claude: 好的,我正在启动浏览器环境... │ │ +│ │ [📺 查看实时预览] ← Action button │ │ +│ │ │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + │ User clicks button + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Step 2: VNC preview opens in sidebar (iframe loads /vnc/client API) │ +│ │ +│ ┌──────────────────────────┐ ┌────────────────────────────────────┐ │ +│ │ 💬 Chat │ │ 📺 实时预览 [×] │ │ +│ │ │ │ ┌────────────────────────────────┐│ │ +│ │ User: 帮我爬取... │ │ │ ││ │ +│ │ │ │ │ noVNC (from API response) ││ │ +│ │ Claude: 正在打开 │ │ │ ││ │ +│ │ 浏览器,访问目标网站... │ │ │ User can see Claude ││ │ +│ │ │ │ │ operating the browser ││ │ +│ │ [📺 查看实时预览] │ │ │ ││ │ +│ │ │ │ └────────────────────────────────┘│ │ +│ └──────────────────────────┘ └────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +#### 6.5 How Claude Knows to Show VNC Button + +The VNC preview button is triggered by the **Agent executor**, not by Claude itself. When the sandbox starts with a VNC-enabled image, the executor can inject a system message or action. + +**Option A: Agent Executor Injects Action** (Recommended) + +In `agent/sandbox/claude/executor.go`, when sandbox starts with VNC: + +```go +func (e *Executor) Stream(...) { + // After sandbox container is ready + if e.isVNCEnabled() { + // Send VNC preview action to frontend + handler(message.StreamEvent{ + Type: "action", + Data: map[string]interface{}{ + "actions": []map[string]interface{}{{ + "name": "button", + "payload": map[string]interface{}{ + "text": "📺 查看实时预览", + "action": map[string]interface{}{ + "name": "navigate", + "payload": map[string]interface{}{ + "route": fmt.Sprintf("/v1/sandbox/%s/vnc/client", sandboxID), + "title": "实时预览", + }, + }, + }, + }}, + }, + }) + } + // ... continue with Claude execution +} +``` + +**Option B: System Prompt Hint** + +Add to system prompt when VNC is enabled: +``` +当你在沙盒中执行可视化任务时(如浏览器操作),可以告知用户点击"查看实时预览"按钮观看操作过程。 +``` + +#### 6.6 VNC Interaction Modes + +The VNC preview supports two modes controlled by the `viewonly` query parameter: + +| Mode | URL | Description | +|------|-----|-------------| +| **Interactive** (default) | `/vnc/client` | User can use keyboard and mouse | +| **View-only** | `/vnc/client?viewonly=true` | User can only watch | + +**Use Cases**: + +| Scenario | Mode | Example | +|----------|------|---------| +| Watch Claude browse web | View-only | `?viewonly=true` | +| User needs to login | Interactive | (default) | +| User needs to solve CAPTCHA | Interactive | (default) | +| Sensitive operation | View-only | `?viewonly=true` | + +**Action Examples**: + +```json +// View-only mode (just watching) +{ + "name": "navigate", + "payload": { + "route": "/v1/sandbox/abc123/vnc/client?viewonly=true", + "title": "实时预览" + } +} + +// Interactive mode (user needs to login) +{ + "name": "navigate", + "payload": { + "route": "/v1/sandbox/abc123/vnc/client", + "title": "请在此登录" + } +} +``` + +**How Claude Waits for User Input**: + +When user interaction is needed (e.g., login), Claude can: + +1. **Wait for user confirmation** (simple): + ``` + Claude: 请在 VNC 窗口中登录,完成后告诉我 + User: 登录好了 + Claude: 好的,继续执行... + ``` + +2. **Auto-detect via script** (advanced): + ```python + # Wait for login success indicator + page.wait_for_selector("#user-avatar", timeout=300000) # 5 min timeout + print("Login detected, continuing...") + ``` + +#### 6.7 CUI Changes + +**No CUI changes required.** The existing `navigate` action + `app/openSidebar` event already handles loading the VNC client API response in an iframe. + +## Implementation Details + +### Dockerfile.playwright + +```dockerfile +ARG REGISTRY=yaoapp +FROM ${REGISTRY}/sandbox-claude:latest + +USER root + +# Install X11, VNC, and minimal window manager +RUN apt-get update && apt-get install -y --no-install-recommends \ + xvfb \ + x11vnc \ + fluxbox \ + novnc \ + python3-websockify \ + fonts-liberation \ + fonts-noto-cjk \ + x11-utils \ + xdotool \ + && rm -rf /var/lib/apt/lists/* + +# Install Playwright system dependencies (requires root) +RUN npx playwright install-deps chromium firefox + +# Install Playwright and browsers as sandbox user +USER sandbox +RUN npm install -g playwright && \ + npx playwright install chromium firefox + +USER root + +# VNC startup script +COPY start-vnc.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/start-vnc.sh + +# Update entrypoint to start VNC (includes original claude entrypoint logic) +COPY entrypoint-vnc.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Environment +ENV DISPLAY=:99 +ENV VNC_PORT=5900 +ENV NOVNC_PORT=6080 +ENV RESOLUTION=1920x1080x24 +ENV SANDBOX_VNC_ENABLED=true + +EXPOSE 5900 6080 + +USER sandbox +WORKDIR /workspace + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["sleep", "infinity"] +``` + +### Dockerfile.desktop + +```dockerfile +ARG REGISTRY=yaoapp +FROM ${REGISTRY}/sandbox-claude:latest + +USER root + +# Install X11, VNC, and XFCE desktop +RUN apt-get update && apt-get install -y --no-install-recommends \ + xvfb \ + x11vnc \ + novnc \ + python3-websockify \ + # XFCE Desktop + xfce4 \ + xfce4-terminal \ + thunar \ + # Fonts + fonts-liberation \ + fonts-noto-cjk \ + # Utilities + x11-utils \ + xdotool \ + && apt-get remove -y xfce4-screensaver xscreensaver || true \ + && rm -rf /var/lib/apt/lists/* + +# Optional: Install Playwright system dependencies (requires root) +RUN npx playwright install-deps chromium || true + +# Optional: Install Playwright for browser automation +USER sandbox +RUN npm install -g playwright && \ + npx playwright install chromium || true + +USER root + +# VNC startup script +COPY start-vnc.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/start-vnc.sh + +# Update entrypoint (includes original claude entrypoint logic) +COPY entrypoint-vnc.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Environment +ENV DISPLAY=:99 +ENV VNC_PORT=5900 +ENV NOVNC_PORT=6080 +ENV RESOLUTION=1920x1080x24 +ENV SANDBOX_VNC_ENABLED=true +ENV SANDBOX_DESKTOP=xfce + +EXPOSE 5900 6080 + +USER sandbox +WORKDIR /workspace + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["sleep", "infinity"] +``` + +### start-vnc.sh (Shared) + +```bash +#!/bin/bash +set -e + +DISPLAY_NUM="${DISPLAY_NUM:-99}" +RESOLUTION="${RESOLUTION:-1920x1080x24}" +VNC_PORT="${VNC_PORT:-5900}" +NOVNC_PORT="${NOVNC_PORT:-6080}" +VNC_PASSWORD="${VNC_PASSWORD:-}" +DESKTOP="${SANDBOX_DESKTOP:-fluxbox}" + +export DISPLAY=:${DISPLAY_NUM} + +# Start Xvfb (virtual framebuffer) +echo "Starting Xvfb on display :${DISPLAY_NUM}..." +Xvfb :${DISPLAY_NUM} -screen 0 ${RESOLUTION} & +XVFB_PID=$! +sleep 1 + +if ! kill -0 $XVFB_PID 2>/dev/null; then + echo "ERROR: Xvfb failed to start" + exit 1 +fi + +# Start window manager / desktop +echo "Starting ${DESKTOP}..." +case "$DESKTOP" in + xfce|xfce4) + startxfce4 & + ;; + *) + fluxbox & + ;; +esac + +# Start VNC server +echo "Starting x11vnc on port ${VNC_PORT}..." +VNC_ARGS="-display :${DISPLAY_NUM} -forever -shared -rfbport ${VNC_PORT} -noxdamage" +if [ -n "$VNC_PASSWORD" ]; then + mkdir -p ~/.vnc + x11vnc -storepasswd "$VNC_PASSWORD" ~/.vnc/passwd + VNC_ARGS="$VNC_ARGS -rfbauth ~/.vnc/passwd" +else + VNC_ARGS="$VNC_ARGS -nopw" +fi +x11vnc $VNC_ARGS & + +# Start noVNC (websockify) +echo "Starting noVNC on port ${NOVNC_PORT}..." +websockify --web=/usr/share/novnc/ ${NOVNC_PORT} localhost:${VNC_PORT} & + +echo "VNC services started successfully" +echo " - Desktop: ${DESKTOP}" +echo " - VNC port: ${VNC_PORT}" +echo " - noVNC port: ${NOVNC_PORT}" + +# Note: Don't wait here - let the entrypoint continue +# Background processes will keep running +``` + +### entrypoint-vnc.sh + +```bash +#!/bin/bash +# Container entrypoint for VNC-enabled images +# This extends the original sandbox-claude entrypoint with VNC support + +# ============================================ +# VNC Services Startup +# ============================================ +if [ "$SANDBOX_VNC_ENABLED" = "true" ]; then + echo "Starting VNC services..." + /usr/local/bin/start-vnc.sh & + sleep 2 +fi + +# ============================================ +# Original sandbox-claude entrypoint logic +# (copied from sandbox-claude Dockerfile) +# ============================================ +WORKSPACE="${WORKSPACE:-/workspace}" +PORT="${CLAUDE_PROXY_PORT:-3456}" +ENV_FILE="/tmp/claude-proxy-env" + +# If proxy env vars are set AND proxy is not running, start it +# This supports docker run -e CLAUDE_PROXY_BACKEND=... usage +if [ -n "$CLAUDE_PROXY_BACKEND" ] && [ -n "$CLAUDE_PROXY_API_KEY" ] && [ -n "$CLAUDE_PROXY_MODEL" ]; then + if ! curl -s "http://127.0.0.1:${PORT}/health" > /dev/null 2>&1; then + /usr/local/bin/start-claude-proxy + fi + + # Write env vars to a file that can be sourced + if curl -s "http://127.0.0.1:${PORT}/health" > /dev/null 2>&1; then + echo "export ANTHROPIC_BASE_URL=http://127.0.0.1:${PORT}" > "$ENV_FILE" + echo "export ANTHROPIC_API_KEY=dummy" >> "$ENV_FILE" + chmod 644 "$ENV_FILE" + fi +fi + +# Execute the command passed to docker run +exec "$@" +``` + +### VNC Proxy Implementation + +```go +// sandbox/vncproxy/proxy.go +package vncproxy + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/docker/docker/client" + "github.com/gorilla/websocket" +) + +type Proxy struct { + docker *client.Client + config *Config + + // IP cache with TTL support + ipCache map[string]ipCacheEntry + ipCacheMu sync.RWMutex +} + +type Config struct { + ContainerVNCPort int // default: 5900 + ContainerNoVNCPort int // default: 6080 + Timeout time.Duration // default: 30s +} + +func New(docker *client.Client, config *Config) *Proxy { + if config.ContainerVNCPort == 0 { + config.ContainerVNCPort = 5900 + } + if config.ContainerNoVNCPort == 0 { + config.ContainerNoVNCPort = 6080 + } + if config.Timeout == 0 { + config.Timeout = 30 * time.Second + } + + return &Proxy{ + docker: docker, + config: config, + ipCache: make(map[string]ipCacheEntry), + } +} + +// HandleVNCStatus returns VNC status for a container +// GET /v1/sandbox/{id}/vnc +func (p *Proxy) HandleVNCStatus(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := fmt.Sprintf("yao-sandbox-%s", sandboxID) + + response := map[string]interface{}{ + "sandbox_id": sandboxID, + "container": containerName, + } + + // Check if container exists and is running + ip, err := p.getContainerIP(r.Context(), containerName) + if err != nil { + response["available"] = false + response["status"] = "unavailable" + response["message"] = "Container not available" + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // Check if VNC is enabled for this container + if !p.checkVNCEnabled(r.Context(), containerName) { + response["available"] = false + response["status"] = "not_supported" + response["message"] = "VNC not available for this container type" + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // Check if VNC services are ready (try to connect to websockify port) + if !p.checkVNCReady(r.Context(), ip) { + response["available"] = false + response["status"] = "starting" + response["message"] = "VNC services are starting..." + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // VNC is ready + response["available"] = true + response["status"] = "ready" + response["client_url"] = fmt.Sprintf("/v1/sandbox/%s/vnc/client", sandboxID) + response["websocket_url"] = fmt.Sprintf("/v1/sandbox/%s/vnc/ws", sandboxID) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// checkVNCReady tests if VNC services are ready by attempting TCP connection +func (p *Proxy) checkVNCReady(ctx context.Context, containerIP string) bool { + addr := fmt.Sprintf("%s:%d", containerIP, p.config.ContainerNoVNCPort) + conn, err := net.DialTimeout("tcp", addr, 2*time.Second) + if err != nil { + return false + } + conn.Close() + return true +} + +// HandleVNCClient serves the noVNC client page +// GET /v1/sandbox/{id}/vnc/client?viewonly=true|false +func (p *Proxy) HandleVNCClient(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := fmt.Sprintf("yao-sandbox-%s", sandboxID) + + // Verify container exists, is running, and has VNC + _, err := p.getContainerIP(r.Context(), containerName) + if err != nil { + http.Error(w, "Container not available", http.StatusNotFound) + return + } + + if !p.checkVNCEnabled(r.Context(), containerName) { + http.Error(w, "VNC not available for this container", http.StatusBadRequest) + return + } + + // Get viewonly parameter (default: false = interactive) + viewOnly := r.URL.Query().Get("viewonly") == "true" + + // Serve inline noVNC HTML page with status checking + // This embeds the noVNC client directly, with retry logic for VNC startup delay + wsURL := fmt.Sprintf("/v1/sandbox/%s/vnc/ws", sandboxID) + p.serveNoVNCPage(w, sandboxID, wsURL, viewOnly) +} + +// serveNoVNCPage serves an inline HTML page that loads noVNC +// Includes status checking and retry logic for VNC startup delay +// viewOnly: if true, user can only watch; if false, user can interact with keyboard/mouse +func (p *Proxy) serveNoVNCPage(w http.ResponseWriter, sandboxID string, wsPath string, viewOnly bool) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + + viewOnlyJS := "false" + if viewOnly { + viewOnlyJS = "true" + } + + html := fmt.Sprintf(` + + + Sandbox Preview + + + +
+
+
正在连接 VNC 服务...
+
+
+ + + + +`, sandboxID, wsPath, viewOnlyJS) + w.Write([]byte(html)) +} + +// HandleVNCWebSocket proxies WebSocket to container VNC +// GET /v1/sandbox/{id}/vnc/ws +func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := fmt.Sprintf("yao-sandbox-%s", sandboxID) + + ip, err := p.getContainerIP(r.Context(), containerName) + if err != nil { + http.Error(w, "Container not available", http.StatusNotFound) + return + } + + if !p.checkVNCEnabled(r.Context(), containerName) { + http.Error(w, "VNC not available for this container", http.StatusBadRequest) + return + } + + // Proxy WebSocket to container's websockify port + targetURL := fmt.Sprintf("ws://%s:%d", ip, p.config.ContainerNoVNCPort) + p.proxyWebSocket(w, r, targetURL) +} + +func (p *Proxy) checkVNCEnabled(ctx context.Context, containerName string) bool { + inspect, err := p.docker.ContainerInspect(ctx, containerName) + if err != nil { + return false + } + + // Check environment variable SANDBOX_VNC_ENABLED + for _, env := range inspect.Config.Env { + if env == "SANDBOX_VNC_ENABLED=true" { + return true + } + } + return false +} + +// ipCacheEntry holds cached IP with expiration +type ipCacheEntry struct { + IP string + ExpiresAt time.Time +} + +func (p *Proxy) getContainerIP(ctx context.Context, containerName string) (string, error) { + // Check cache first (with TTL) + p.ipCacheMu.RLock() + if entry, ok := p.ipCache[containerName]; ok { + if time.Now().Before(entry.ExpiresAt) { + p.ipCacheMu.RUnlock() + return entry.IP, nil + } + } + p.ipCacheMu.RUnlock() + + // Cache miss or expired, fetch from Docker + inspect, err := p.docker.ContainerInspect(ctx, containerName) + if err != nil { + // Remove stale cache entry + p.ipCacheMu.Lock() + delete(p.ipCache, containerName) + p.ipCacheMu.Unlock() + return "", fmt.Errorf("container not found: %w", err) + } + + if !inspect.State.Running { + // Remove stale cache entry + p.ipCacheMu.Lock() + delete(p.ipCache, containerName) + p.ipCacheMu.Unlock() + return "", fmt.Errorf("container not running") + } + + ip := inspect.NetworkSettings.IPAddress + if ip == "" { + if networks := inspect.NetworkSettings.Networks; networks != nil { + if bridge, ok := networks["bridge"]; ok { + ip = bridge.IPAddress + } + } + } + + if ip == "" { + return "", fmt.Errorf("container has no IP address") + } + + // Cache with 30 second TTL + p.ipCacheMu.Lock() + p.ipCache[containerName] = ipCacheEntry{ + IP: ip, + ExpiresAt: time.Now().Add(30 * time.Second), + } + p.ipCacheMu.Unlock() + + return ip, nil +} + +// InvalidateCache removes a container from the IP cache +// Call this when container state changes (stop/restart) +func (p *Proxy) InvalidateCache(containerName string) { + p.ipCacheMu.Lock() + delete(p.ipCache, containerName) + p.ipCacheMu.Unlock() +} + +func (p *Proxy) proxyWebSocket(w http.ResponseWriter, r *http.Request, targetURL string) { + upgrader := websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, + Subprotocols: []string{"binary"}, // Required for noVNC + } + + clientConn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + defer clientConn.Close() + + dialer := websocket.Dialer{ + HandshakeTimeout: p.config.Timeout, + } + + targetConn, _, err := dialer.Dial(targetURL, nil) + if err != nil { + return + } + defer targetConn.Close() + + errChan := make(chan error, 2) + + // Client -> Target + go func() { + for { + msgType, data, err := clientConn.ReadMessage() + if err != nil { + errChan <- err + return + } + if err := targetConn.WriteMessage(msgType, data); err != nil { + errChan <- err + return + } + } + }() + + // Target -> Client + go func() { + for { + msgType, data, err := targetConn.ReadMessage() + if err != nil { + errChan <- err + return + } + if err := clientConn.WriteMessage(msgType, data); err != nil { + errChan <- err + return + } + } + }() + + <-errChan +} + +func extractSandboxID(r *http.Request) string { + // Extract from path: /v1/sandbox/{id}/vnc/... + path := r.URL.Path + path = strings.TrimPrefix(path, "/v1/sandbox/") + parts := strings.Split(path, "/") + if len(parts) >= 1 { + return parts[0] + } + return "" +} +``` + +## Security Considerations + +### 1. Authentication + +All VNC endpoints verify user authentication: + +```go +func (p *Proxy) authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + + // Verify the requesting user owns this sandbox + // Implementation depends on how sandboxID maps to users: + // - If sandboxID = "{userID}-{chatID}", extract userID and compare with session + // - If sandboxID = UUID, lookup in database + // - Delegate to business layer authorization service + + // Example: extract userID from sandboxID pattern "{userID}-{chatID}" + // parts := strings.SplitN(sandboxID, "-", 2) + // if len(parts) >= 1 { + // ownerID := parts[0] + // sessionUserID := getSessionUserID(r) + // if sessionUserID != ownerID { + // http.Error(w, "Unauthorized", http.StatusUnauthorized) + // return + // } + // } + + // TODO: Implement authorization logic based on your sandboxID scheme + + next.ServeHTTP(w, r) + }) +} +``` + +### 2. Network Isolation + +- Containers use Docker bridge network (internal only) +- No VNC ports exposed to host +- All access through authenticated proxy +- Each user can only access their own containers + +### 3. Resource Limits by Image Type + +| Image Type | Memory | CPU | Disk | +|------------|--------|-----|------| +| claude | 2GB | 1.0 | - | +| playwright | 4GB | 2.0 | - | +| desktop | 4GB | 2.0 | - | + +## Configuration + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `YAO_SANDBOX_IMAGE` | `yaoapp/sandbox-claude:latest` | Default sandbox image | +| `YAO_SANDBOX_VNC_PORT_MAPPING` | `false` | Enable VNC port mapping to host (for Docker Desktop) | +| `YAO_VNC_PROXY_ENABLED` | `true` | Enable VNC proxy | +| `YAO_VNC_RESOLUTION` | `1920x1080x24` | VNC screen resolution | + +### Docker Desktop Support (macOS/Windows) + +Docker Desktop runs containers inside a LinuxKit VM, so container IPs (`172.17.0.x`) are not directly accessible from the host. To enable VNC access on Docker Desktop: + +```bash +# Enable VNC port mapping for local development +export YAO_SANDBOX_VNC_PORT_MAPPING=true +export YAO_SANDBOX_IMAGE="yaoapp/sandbox-claude-playwright:latest" +``` + +When `YAO_SANDBOX_VNC_PORT_MAPPING=true`: +- Container ports `6080/tcp` (noVNC) and `5900/tcp` (VNC) are mapped to random available host ports +- Ports are bound to `127.0.0.1` for security +- VNC Proxy automatically detects and uses the mapped host ports + +On Linux (native Docker), this option is not needed as container IPs are directly accessible. + +## Implementation Checklist + +### Yao Backend ✅ 完成 + +- [x] `sandbox/docker/playwright/Dockerfile` - Playwright + VNC image +- [x] `sandbox/docker/desktop/Dockerfile` - Full desktop + VNC image +- [x] `sandbox/docker/vnc/start-vnc.sh` - Shared VNC startup script +- [x] `sandbox/docker/vnc/entrypoint-vnc.sh` - VNC entrypoint +- [x] `sandbox/vncproxy/proxy.go` - VNC WebSocket proxy +- [x] `sandbox/vncproxy/config.go` - Proxy configuration +- [x] API router integration - VNC endpoints (`openapi/sandbox/sandbox.go`) +- [x] `sandbox/docker/build.sh` - Update build script +- [x] `sandbox/config.go` - VNC port mapping configuration +- [x] `sandbox/manager.go` - Dynamic VNC port mapping for Docker Desktop + +### No Changes Needed + +- `agent/sandbox/` - existing `Image` field already supports custom images +- `cui/` - existing `navigate` action handles iframe loading via `app/openSidebar` + +## File Structure + +### Yao (Backend) + +``` +yao/sandbox/ +├── vncproxy/ # VNC Proxy Service +│ ├── proxy.go # Main proxy implementation (with port mapping detection) +│ ├── proxy_test.go # Unit tests +│ └── config.go # Configuration +├── docker/ +│ ├── base/ +│ │ └── Dockerfile.base +│ ├── claude/ +│ │ ├── Dockerfile +│ │ └── Dockerfile.full +│ ├── playwright/ # Playwright + VNC image +│ │ └── Dockerfile +│ ├── desktop/ # XFCE Desktop + VNC image +│ │ └── Dockerfile +│ ├── vnc/ # Shared VNC scripts +│ │ ├── start-vnc.sh +│ │ └── entrypoint-vnc.sh +│ └── build.sh # Build script for all images +├── manager.go # Container management (with VNC port mapping) +├── config.go # Configuration (VNCPortMapping option) +├── DESIGN-PLAYWRIGHT-VNC.md # This document +├── TODO-VNC.md # Implementation checklist +└── README.md # Quick start guide +``` + +### CUI (Frontend) + +``` +cui/packages/cui/ +└── ... # No changes needed +``` + +The CUI `navigate` action already supports loading URLs via iframe in sidebar. The `/v1/sandbox/{id}/vnc/client` API returns a complete HTML page that will be loaded directly. + +### Agent (No Changes) + +``` +yao/agent/ +├── sandbox/ +│ ├── types.go # Already supports custom Image +│ └── ... # No changes needed +└── ... +``` + +## Command Execution + +### Overview + +Commands execute identically across all sandbox images. The `Manager.Exec()` and `Manager.Stream()` methods remain unchanged. + +### No Manager Changes Required + +```go +// Manager.Exec() and Manager.Stream() remain unchanged +// Commands run the same way on all images +// DISPLAY=:99 is set in container env, GUI apps (browsers) use it automatically +``` + +### Behavior by Image Type + +| Image | DISPLAY | VNC Visible | Agent Gets Output | +|-------|---------|-------------|-------------------| +| sandbox-claude | ❌ | N/A | ✅ | +| sandbox-claude-playwright | ✅ :99 | Browser window | ✅ | +| sandbox-claude-desktop | ✅ :99 | Browser + Desktop apps | ✅ | + +### What Users See in VNC + +| Operation | sandbox-claude-playwright | sandbox-claude-desktop | +|-----------|--------------------------|------------------------| +| Browser automation | ✅ Visible | ✅ Visible | +| File operations | ❌ | ✅ (open Thunar) | +| Terminal commands | ❌ | ❌ (output to Agent) | + +**Note**: Terminal command output goes to Agent, not to VNC terminal window. This is by design - `docker exec` runs commands directly in the container, not through a terminal emulator. Users can manually open a terminal in VNC if they want to run commands interactively. + +### Why This Design + +1. **100% backward compatible**: No changes to Manager.go +2. **Agent output intact**: stdout/stderr captured normally +3. **Browser visible**: Main use case (Playwright) works perfectly +4. **Low risk**: No code changes = no bugs +5. **Future improvement**: Terminal visibility can be added later if needed + +--- + +## Appendix + +### A. Image Comparison + +| Feature | sandbox-claude | sandbox-claude-playwright | sandbox-claude-desktop | +|---------|---------------|--------------------------|------------------------| +| Claude CLI | ✅ | ✅ | ✅ | +| Node.js | ✅ | ✅ | ✅ | +| Python | ✅ | ✅ | ✅ | +| VNC Access | ❌ | ✅ | ✅ | +| Playwright | ❌ | ✅ | ✅ (optional) | +| File Manager | ❌ | ❌ | ✅ | +| Terminal GUI | ❌ | ❌ | ✅ | +| Desktop | ❌ | Minimal (Fluxbox) | Full (XFCE) | +| Image Size | ~700MB | ~1.8GB | ~2.5GB | +| Memory | 2GB | 4GB | 4GB | +| **Best For** | Scripts, CLI | Browser automation | Full transparency | + +### B. User Visibility & Interaction + +What users can see and do in VNC: + +| Operation | sandbox-claude-playwright | sandbox-claude-desktop | +|-----------|--------------------------|------------------------| +| Browser navigation | ✅ See | ✅ See | +| Browser clicks/typing | ✅ See | ✅ See | +| File creation | ❌ (log only) | ✅ (file manager) | +| Command execution | ❌ (output to Agent) | ❌ (output to Agent) | +| Code editing | ❌ | ✅ (if editor installed) | +| **Trust Level** | Medium | High | + +**User Interaction Modes**: + +| Mode | URL Parameter | User Can | +|------|---------------|----------| +| View-only | `?viewonly=true` | Watch only | +| Interactive | (default) | Keyboard, mouse, typing | + +**Typical Interactive Scenarios**: +- User login (accounts, passwords) +- CAPTCHA solving +- Two-factor authentication +- Manual form filling + +**Note**: Command output goes to Agent (via docker exec), not to a visible terminal in VNC. Users can manually open a terminal in `sandbox-claude-desktop` if needed. + +### C. Implementation Summary + +| Component | Location | Changes | +|-----------|----------|---------| +| Docker Images | `sandbox/docker/playwright/`, `sandbox/docker/desktop/` | NEW | +| VNC Proxy | `sandbox/vncproxy/` | NEW | +| VNC API | Yao router | NEW endpoints | +| CUI | `cui/` | **No changes** (navigate action + iframe) | +| Sandbox Manager | `sandbox/manager.go` | **No changes** | +| Agent Sandbox | `agent/sandbox/` | **No changes** | + +### D. References + +- [Playwright Docker Documentation](https://playwright.dev/docs/docker) +- [noVNC GitHub](https://github.com/novnc/noVNC) +- [XFCE Documentation](https://docs.xfce.org/) +- [CUI Action System](../../cui/packages/cui/chatbox/messages/Action/actions/navigate.ts) +- [Yao Sandbox README](./README.md) +- [Yao Sandbox DESIGN](./DESIGN.md) diff --git a/sandbox/README.md b/sandbox/README.md index b2a7ca05..cd51ed35 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -10,6 +10,7 @@ The sandbox module enables Yao to safely run external AI coding agents (like Cla - IPC communication via Unix sockets - Resource limits (CPU, memory) - Security isolation +- **VNC remote desktop** for visual transparency (optional) ## Architecture @@ -26,11 +27,20 @@ The sandbox module enables Yao to safely run external AI coding agents (like Cla │ │ │ │ │ └────────────────────────┬────────────────────────────────┘ │ │ │ │ +│ ┌────────────────────────┴────────────────────────────────┐ │ +│ │ VNC Proxy Service │ │ +│ │ │ │ +│ │ - GET /v1/sandbox/{id}/vnc → VNC status │ │ +│ │ - GET /v1/sandbox/{id}/vnc/client → noVNC page │ │ +│ │ - GET /v1/sandbox/{id}/vnc/ws → WebSocket proxy │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ Container │ │ Container │ │ Container │ │ -│ │ (user1) │ │ (user2) │ │ (user3) │ │ +│ │ sandbox- │ │ sandbox- │ │ sandbox- │ │ +│ │ claude │ │ playwright │ │ desktop │ │ +│ │ (No VNC) │ │ (VNC) │ │ (VNC) │ │ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │ │ │ │ │ │ ──────┴───────────────┴───────────────┴──── │ @@ -45,7 +55,16 @@ The sandbox module enables Yao to safely run external AI coding agents (like Cla ```bash cd sandbox/docker + +# Build base image ./build.sh claude + +# Build VNC-enabled images +./build.sh playwright # Playwright + Fluxbox + VNC +./build.sh desktop # XFCE Desktop + VNC + +# Build all images +./build.sh all ``` ### Usage @@ -84,23 +103,37 @@ data, err := manager.ReadFile(ctx, container.Name, "/workspace/test.txt") ### Environment Variables -| Variable | Default | Description | -| -------------------------- | ----------------------------------- | ------------------------- | -| `YAO_SANDBOX_IMAGE` | `yao/sandbox-claude:latest` | Docker image | -| `YAO_SANDBOX_WORKSPACE` | `{YAO_DATA_ROOT}/sandbox/workspace` | Workspace directory | -| `YAO_SANDBOX_IPC` | `{YAO_DATA_ROOT}/sandbox/ipc` | IPC socket directory | -| `YAO_SANDBOX_MAX` | `100` | Max concurrent containers | -| `YAO_SANDBOX_IDLE_TIMEOUT` | `30m` | Idle timeout | -| `YAO_SANDBOX_MEMORY` | `2g` | Memory limit | -| `YAO_SANDBOX_CPU` | `1.0` | CPU limit | +| Variable | Default | Description | +| ------------------------------ | ----------------------------------- | ---------------------------------------------- | +| `YAO_SANDBOX_IMAGE` | `yao/sandbox-claude:latest` | Docker image | +| `YAO_SANDBOX_WORKSPACE` | `{YAO_DATA_ROOT}/sandbox/workspace` | Workspace directory | +| `YAO_SANDBOX_IPC` | `{YAO_DATA_ROOT}/sandbox/ipc` | IPC socket directory | +| `YAO_SANDBOX_MAX` | `100` | Max concurrent containers | +| `YAO_SANDBOX_IDLE_TIMEOUT` | `30m` | Idle timeout | +| `YAO_SANDBOX_MEMORY` | `2g` | Memory limit | +| `YAO_SANDBOX_CPU` | `1.0` | CPU limit | +| `YAO_SANDBOX_VNC_PORT_MAPPING` | `false` | Enable VNC port mapping (for Docker Desktop) | + +### Docker Desktop (macOS/Windows) + +Docker Desktop runs containers in a LinuxKit VM, so container IPs are not directly accessible from the host. Enable VNC port mapping for local development: + +```bash +export YAO_SANDBOX_VNC_PORT_MAPPING=true +export YAO_SANDBOX_IMAGE="yaoapp/sandbox-claude-playwright:latest" +``` + +When enabled, VNC ports (6080, 5900) are automatically mapped to random available host ports on `127.0.0.1`. ## Docker Images -| Image | Description | -| --------------------------- | ------------------------------------- | -| `yao/sandbox-base:latest` | Base image with git, curl, yao-bridge | -| `yao/sandbox-claude:latest` | + Claude CLI, Node.js 20, Python 3.11 | -| `yao/sandbox-claude:full` | + Go 1.23 | +| Image | VNC | Description | +| ------------------------------------------ | --- | ------------------------------------- | +| `yaoapp/sandbox-base:latest` | ❌ | Base image with git, curl, yao-bridge | +| `yaoapp/sandbox-claude:latest` | ❌ | + Claude CLI, Node.js 20, Python 3.11 | +| `yaoapp/sandbox-claude:full` | ❌ | + Go 1.23 | +| `yaoapp/sandbox-claude-playwright:latest` | ✅ | + Playwright, Fluxbox, VNC (~3.4GB) | +| `yaoapp/sandbox-claude-desktop:latest` | ✅ | + XFCE Desktop, VNC (~3.1GB) | ## IPC Communication @@ -112,6 +145,25 @@ Supported methods: - `tools/list` - List available tools - `tools/call` - Execute a tool +## VNC Remote Desktop + +VNC-enabled images (playwright, desktop) provide real-time visibility into Claude's operations. + +### API Endpoints + +| Endpoint | Description | +| ------------------------------- | ---------------------------------- | +| `GET /v1/sandbox/{id}/vnc` | VNC status (ready/starting/unavailable) | +| `GET /v1/sandbox/{id}/vnc/client` | noVNC HTML client page | +| `GET /v1/sandbox/{id}/vnc/ws` | WebSocket proxy to container VNC | + +### View Modes + +- **Interactive** (default): User can use keyboard and mouse +- **View-only** (`?viewonly=true`): User can only watch + +For detailed design, see [DESIGN-PLAYWRIGHT-VNC.md](./DESIGN-PLAYWRIGHT-VNC.md). + ## Directory Structure ``` @@ -120,11 +172,18 @@ sandbox/ ├── docker/ # Dockerfiles and build script │ ├── base/ │ ├── claude/ +│ ├── playwright/ # Playwright + VNC image +│ ├── desktop/ # XFCE Desktop + VNC image +│ ├── vnc/ # Shared VNC scripts │ └── build.sh ├── ipc/ # IPC system │ ├── manager.go │ ├── session.go │ └── types.go +├── vncproxy/ # VNC proxy service +│ ├── proxy.go +│ ├── config.go +│ └── proxy_test.go ├── config.go # Configuration ├── errors.go # Error types ├── helpers.go # Helper functions @@ -135,11 +194,17 @@ sandbox/ ## Testing ```bash +# Load environment variables first +source env.local.sh + # Unit tests (no Docker required) go test -v ./sandbox/... -run "^Test.*Validation|^Test.*Generation|^Test.*Parsing" # All tests (requires Docker) go test -v ./sandbox/... + +# VNC proxy tests only +go test -v ./sandbox/vncproxy/... ``` ## Security diff --git a/sandbox/config.go b/sandbox/config.go index f6335c5e..ccb653e9 100644 --- a/sandbox/config.go +++ b/sandbox/config.go @@ -21,6 +21,9 @@ type Config struct { ContainerWorkDir string `json:"container_workdir,omitempty"` // Container working directory, default: /workspace ContainerIPCSocket string `json:"container_ipc_socket,omitempty"` // Container IPC socket path, default: /tmp/yao.sock ContainerUser string `json:"container_user,omitempty"` // Container user, default: "" (use image default). Set to "0" for root. + + // VNC port mapping (for Docker Desktop on macOS/Windows where container IPs are not directly accessible) + VNCPortMapping bool `json:"vnc_port_mapping,omitempty"` // Enable VNC port mapping to host, default: false } // DefaultConfig returns a Config with default values @@ -116,4 +119,9 @@ func (c *Config) Init(dataRoot string) { if env := os.Getenv("YAO_SANDBOX_CONTAINER_USER"); env != "" { c.ContainerUser = env } + + // VNC port mapping (for Docker Desktop on macOS/Windows) + if env := os.Getenv("YAO_SANDBOX_VNC_PORT_MAPPING"); env != "" { + c.VNCPortMapping = env == "true" || env == "1" || env == "yes" + } } diff --git a/sandbox/docker/build.sh b/sandbox/docker/build.sh index af99e3bd..c1a126de 100755 --- a/sandbox/docker/build.sh +++ b/sandbox/docker/build.sh @@ -105,6 +105,22 @@ case $TOOL in build_multiarch "sandbox-claude" "claude/Dockerfile" "$PUSH" build_multiarch "sandbox-claude-full" "claude/Dockerfile.full" "$PUSH" ;; + claude-vnc) + echo "" + echo "=== Building Claude VNC images (Playwright + Desktop) ===" + build_multiarch "sandbox-claude-playwright" "playwright/Dockerfile" "$PUSH" + build_multiarch "sandbox-claude-desktop" "desktop/Dockerfile" "$PUSH" + ;; + playwright) + echo "" + echo "=== Building Claude Playwright image ===" + build_multiarch "sandbox-claude-playwright" "playwright/Dockerfile" "$PUSH" + ;; + desktop) + echo "" + echo "=== Building Claude Desktop image ===" + build_multiarch "sandbox-claude-desktop" "desktop/Dockerfile" "$PUSH" + ;; cursor) echo "" echo "=== Building Cursor images ===" @@ -116,14 +132,20 @@ case $TOOL in # Claude build_multiarch "sandbox-claude" "claude/Dockerfile" "$PUSH" build_multiarch "sandbox-claude-full" "claude/Dockerfile.full" "$PUSH" + # Claude VNC variants + build_multiarch "sandbox-claude-playwright" "playwright/Dockerfile" "$PUSH" + build_multiarch "sandbox-claude-desktop" "desktop/Dockerfile" "$PUSH" # Cursor (uncomment when ready) # build_multiarch "sandbox-cursor" "cursor/Dockerfile" "$PUSH" ;; *) echo "Unknown tool: $TOOL" - echo "Usage: $0 [claude|cursor|all] [true|false]" + echo "Usage: $0 [claude|claude-vnc|playwright|desktop|cursor|all] [true|false]" echo " $0 claude # Build Claude images locally" echo " $0 claude true # Build and push Claude images" + echo " $0 claude-vnc # Build Claude VNC images (Playwright + Desktop)" + echo " $0 playwright # Build Claude Playwright image only" + echo " $0 desktop # Build Claude Desktop image only" echo " $0 all true # Build and push all images" exit 1 ;; @@ -142,9 +164,21 @@ if [ "$PUSH" = "true" ]; then echo " - ${REGISTRY}/sandbox-claude:latest" echo " - ${REGISTRY}/sandbox-claude-full:latest" ;; + claude-vnc) + echo " - ${REGISTRY}/sandbox-claude-playwright:latest" + echo " - ${REGISTRY}/sandbox-claude-desktop:latest" + ;; + playwright) + echo " - ${REGISTRY}/sandbox-claude-playwright:latest" + ;; + desktop) + echo " - ${REGISTRY}/sandbox-claude-desktop:latest" + ;; all) echo " - ${REGISTRY}/sandbox-claude:latest" echo " - ${REGISTRY}/sandbox-claude-full:latest" + echo " - ${REGISTRY}/sandbox-claude-playwright:latest" + echo " - ${REGISTRY}/sandbox-claude-desktop:latest" ;; esac fi diff --git a/sandbox/docker/desktop/Dockerfile b/sandbox/docker/desktop/Dockerfile new file mode 100644 index 00000000..6fcd389c --- /dev/null +++ b/sandbox/docker/desktop/Dockerfile @@ -0,0 +1,91 @@ +# Claude sandbox with full XFCE desktop + VNC preview +# Image: sandbox-claude-desktop +# Base: sandbox-claude (Ubuntu 24.04 + Node.js + Python + Claude CLI) +# Adds: Xvfb + x11vnc + noVNC + XFCE desktop + File Manager + Terminal +# +# Supports both amd64 and arm64 architectures + +ARG REGISTRY=yaoapp +FROM ${REGISTRY}/sandbox-claude:latest + +USER root + +# Use MIT mirror (USA) for ARM64 +RUN sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || \ + sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list 2>/dev/null || true + +# Install X11, VNC, and XFCE desktop environment +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Virtual display + xvfb \ + # VNC server + x11vnc \ + # noVNC (HTML5 VNC client) and websockify + novnc \ + python3-websockify \ + # XFCE Desktop (full-featured but lightweight) + xfce4 \ + xfce4-terminal \ + thunar \ + # Fonts (required for proper rendering) + fonts-liberation \ + fonts-noto-cjk \ + fonts-noto-color-emoji \ + # X11 utilities + x11-utils \ + xdotool \ + # Audio + pulseaudio \ + # Remove screensaver (causes issues in container) + && apt-get remove -y xfce4-screensaver xscreensaver || true \ + && rm -rf /var/lib/apt/lists/* + +# Optional: Install Playwright system dependencies (requires root) +# Users can run browser automation in desktop mode too +RUN npx playwright install-deps chromium || true + +# Optional: Install Playwright for browser automation +USER sandbox +RUN npm install -g playwright && \ + pip install --user --break-system-packages playwright && \ + npx playwright install chromium || true + +USER root + +# Copy VNC startup scripts +# Note: Build context should be sandbox/docker/, so paths are relative to that +COPY vnc/start-vnc.sh /usr/local/bin/start-vnc.sh +COPY vnc/entrypoint-vnc.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/start-vnc.sh /usr/local/bin/entrypoint.sh + +# Environment variables for VNC +ENV DISPLAY=:99 +ENV VNC_PORT=5900 +ENV NOVNC_PORT=6080 +ENV RESOLUTION=1920x1080x24 +ENV SANDBOX_VNC_ENABLED=true +ENV SANDBOX_DESKTOP=xfce + +# Node.js environment - ensure global modules are accessible +ENV NODE_PATH=/home/sandbox/.npm-global/lib/node_modules + +# Expose VNC ports (internal use only, accessed via proxy) +EXPOSE 5900 6080 + +USER sandbox +WORKDIR /workspace + +# Verify installations +RUN echo "=== Verifying installations ===" && \ + node --version && \ + npm --version && \ + python3 --version && \ + which startxfce4 && \ + which thunar && \ + which xfce4-terminal && \ + which x11vnc && \ + which Xvfb && \ + echo "=== All installations verified ===" + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["sleep", "infinity"] diff --git a/sandbox/docker/playwright/Dockerfile b/sandbox/docker/playwright/Dockerfile new file mode 100644 index 00000000..be6faa46 --- /dev/null +++ b/sandbox/docker/playwright/Dockerfile @@ -0,0 +1,89 @@ +# Claude sandbox with Playwright browser automation + VNC preview +# Image: sandbox-claude-playwright +# Base: sandbox-claude (Ubuntu 24.04 + Node.js + Python + Claude CLI) +# Adds: Xvfb + x11vnc + noVNC + Fluxbox + Playwright browsers +# +# Supports both amd64 and arm64 architectures + +ARG REGISTRY=yaoapp +FROM ${REGISTRY}/sandbox-claude:latest + +USER root + +# Use MIT mirror (USA) for ARM64 +RUN sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || \ + sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list 2>/dev/null || true + +# Install X11, VNC, and minimal window manager +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Virtual display + xvfb \ + # VNC server + x11vnc \ + # noVNC (HTML5 VNC client) and websockify + novnc \ + python3-websockify \ + # Minimal window manager (lightweight, perfect for Playwright) + fluxbox \ + # Fonts (required for proper browser rendering) + fonts-liberation \ + fonts-noto-cjk \ + fonts-noto-color-emoji \ + # X11 utilities + x11-utils \ + xdotool \ + # Audio (for video playback in browsers, can be disabled) + pulseaudio \ + && rm -rf /var/lib/apt/lists/* + +# Install Playwright system dependencies (requires root) +# This installs system libraries needed by Chromium/Firefox +RUN npx playwright install-deps chromium firefox || true + +# Install Playwright and browsers as sandbox user +USER sandbox + +# Install Playwright for Node.js (global) and Python +RUN npm install -g playwright && \ + pip install --user --break-system-packages playwright && \ + npx playwright install chromium firefox + +USER root + +# Copy VNC startup scripts +# Note: Build context should be sandbox/docker/, so paths are relative to that +COPY vnc/start-vnc.sh /usr/local/bin/start-vnc.sh +COPY vnc/entrypoint-vnc.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/start-vnc.sh /usr/local/bin/entrypoint.sh + +# Environment variables for VNC +ENV DISPLAY=:99 +ENV VNC_PORT=5900 +ENV NOVNC_PORT=6080 +ENV RESOLUTION=1920x1080x24 +ENV SANDBOX_VNC_ENABLED=true +ENV SANDBOX_DESKTOP=fluxbox + +# Node.js environment - ensure global modules are accessible +ENV NODE_PATH=/home/sandbox/.npm-global/lib/node_modules + +# Expose VNC ports (internal use only, accessed via proxy) +EXPOSE 5900 6080 + +USER sandbox +WORKDIR /workspace + +# Verify installations +RUN echo "=== Verifying installations ===" && \ + node --version && \ + npm --version && \ + python3 --version && \ + npx playwright --version && \ + python3 -c "from playwright.sync_api import sync_playwright; print('Python Playwright: OK')" && \ + which fluxbox && \ + which x11vnc && \ + which Xvfb && \ + echo "=== All installations verified ===" + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["sleep", "infinity"] diff --git a/sandbox/docker/vnc/entrypoint-vnc.sh b/sandbox/docker/vnc/entrypoint-vnc.sh new file mode 100644 index 00000000..62e48f50 --- /dev/null +++ b/sandbox/docker/vnc/entrypoint-vnc.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Container entrypoint for VNC-enabled sandbox images +# This extends the original sandbox-claude entrypoint with VNC support + +# ============================================ +# VNC Services Startup +# ============================================ +if [ "$SANDBOX_VNC_ENABLED" = "true" ]; then + echo "[Entrypoint] Starting VNC services..." + /usr/local/bin/start-vnc.sh & + # Wait for VNC to initialize + sleep 3 + echo "[Entrypoint] VNC services started in background" +fi + +# ============================================ +# Original sandbox-claude entrypoint logic +# (from sandbox-claude Dockerfile) +# ============================================ +WORKSPACE="${WORKSPACE:-/workspace}" +PORT="${CLAUDE_PROXY_PORT:-3456}" +ENV_FILE="/tmp/claude-proxy-env" + +# If proxy env vars are set AND proxy is not running, start it +# This supports docker run -e CLAUDE_PROXY_BACKEND=... usage +if [ -n "$CLAUDE_PROXY_BACKEND" ] && [ -n "$CLAUDE_PROXY_API_KEY" ] && [ -n "$CLAUDE_PROXY_MODEL" ]; then + if ! curl -s "http://127.0.0.1:${PORT}/health" > /dev/null 2>&1; then + /usr/local/bin/start-claude-proxy + fi + + # Write env vars to a file that can be sourced + if curl -s "http://127.0.0.1:${PORT}/health" > /dev/null 2>&1; then + echo "export ANTHROPIC_BASE_URL=http://127.0.0.1:${PORT}" > "$ENV_FILE" + echo "export ANTHROPIC_API_KEY=dummy" >> "$ENV_FILE" + chmod 644 "$ENV_FILE" + fi +fi + +# Execute the command passed to docker run +exec "$@" diff --git a/sandbox/docker/vnc/start-vnc.sh b/sandbox/docker/vnc/start-vnc.sh new file mode 100644 index 00000000..d7d0677b --- /dev/null +++ b/sandbox/docker/vnc/start-vnc.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# VNC services startup script +# Shared by sandbox-claude-playwright and sandbox-claude-desktop +# Starts: Xvfb (virtual display) + Window Manager + x11vnc + websockify (noVNC) + +set -e + +DISPLAY_NUM="${DISPLAY_NUM:-99}" +RESOLUTION="${RESOLUTION:-1920x1080x24}" +VNC_PORT="${VNC_PORT:-5900}" +NOVNC_PORT="${NOVNC_PORT:-6080}" +VNC_PASSWORD="${VNC_PASSWORD:-}" +DESKTOP="${SANDBOX_DESKTOP:-fluxbox}" + +export DISPLAY=:${DISPLAY_NUM} + +echo "[VNC] Starting VNC services..." +echo "[VNC] Display: :${DISPLAY_NUM}" +echo "[VNC] Resolution: ${RESOLUTION}" +echo "[VNC] Desktop: ${DESKTOP}" + +# Start Xvfb (virtual framebuffer) +echo "[VNC] Starting Xvfb..." +Xvfb :${DISPLAY_NUM} -screen 0 ${RESOLUTION} & +XVFB_PID=$! +sleep 1 + +if ! kill -0 $XVFB_PID 2>/dev/null; then + echo "[VNC] ERROR: Xvfb failed to start" + exit 1 +fi +echo "[VNC] Xvfb started (PID: $XVFB_PID)" + +# Start window manager / desktop environment +echo "[VNC] Starting ${DESKTOP}..." +case "$DESKTOP" in + xfce|xfce4) + # XFCE desktop environment + startxfce4 & + ;; + fluxbox) + # Minimal window manager for Playwright + fluxbox & + ;; + *) + # Default to fluxbox + fluxbox & + ;; +esac +sleep 2 + +# Start x11vnc server +echo "[VNC] Starting x11vnc on port ${VNC_PORT}..." +VNC_ARGS="-display :${DISPLAY_NUM} -forever -shared -rfbport ${VNC_PORT} -noxdamage" + +if [ -n "$VNC_PASSWORD" ]; then + mkdir -p ~/.vnc + x11vnc -storepasswd "$VNC_PASSWORD" ~/.vnc/passwd + VNC_ARGS="$VNC_ARGS -rfbauth ~/.vnc/passwd" +else + VNC_ARGS="$VNC_ARGS -nopw" +fi + +x11vnc $VNC_ARGS & +X11VNC_PID=$! +sleep 1 + +if ! kill -0 $X11VNC_PID 2>/dev/null; then + echo "[VNC] ERROR: x11vnc failed to start" + exit 1 +fi +echo "[VNC] x11vnc started (PID: $X11VNC_PID)" + +# Start websockify (noVNC WebSocket proxy) +echo "[VNC] Starting websockify on port ${NOVNC_PORT}..." +websockify --web=/usr/share/novnc/ ${NOVNC_PORT} localhost:${VNC_PORT} & +WEBSOCKIFY_PID=$! +sleep 1 + +if ! kill -0 $WEBSOCKIFY_PID 2>/dev/null; then + echo "[VNC] ERROR: websockify failed to start" + exit 1 +fi +echo "[VNC] websockify started (PID: $WEBSOCKIFY_PID)" + +echo "[VNC] ==================================" +echo "[VNC] VNC services started successfully" +echo "[VNC] Desktop: ${DESKTOP}" +echo "[VNC] VNC port: ${VNC_PORT}" +echo "[VNC] noVNC port: ${NOVNC_PORT}" +echo "[VNC] ==================================" + +# Note: Don't wait here - let the entrypoint continue +# Background processes will keep running diff --git a/sandbox/manager.go b/sandbox/manager.go index 056b99bc..b0fb56d3 100644 --- a/sandbox/manager.go +++ b/sandbox/manager.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "net" "os" "path/filepath" "strings" @@ -17,6 +18,7 @@ import ( "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/go-connections/nat" "github.com/yaoapp/yao/sandbox/ipc" ) @@ -306,6 +308,24 @@ func (m *Manager) createContainer(ctx context.Context, userID, chatID string) (* CapDrop: []string{"ALL"}, } + // VNC port mapping for Docker Desktop (macOS/Windows) + // Only enable for VNC-capable images (playwright/desktop) when config is enabled + if m.config.VNCPortMapping && isVNCImage(m.config.Image) { + // Expose VNC ports in container config + containerConfig.ExposedPorts = nat.PortSet{ + "6080/tcp": struct{}{}, // noVNC websockify + "5900/tcp": struct{}{}, // VNC + } + // Enable SANDBOX_VNC_ENABLED environment variable + containerConfig.Env = append(containerConfig.Env, "SANDBOX_VNC_ENABLED=true") + + // Map to random available ports on 127.0.0.1 + hostConfig.PortBindings = nat.PortMap{ + "6080/tcp": []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: ""}}, // empty = random port + "5900/tcp": []nat.PortBinding{{HostIP: "127.0.0.1", HostPort: ""}}, + } + } + // Create container resp, err := m.dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, name) if err != nil { @@ -905,3 +925,19 @@ func (m *Manager) fixIPCSocketPermissions(ctx context.Context, containerID strin // Wait briefly for the chmod to complete time.Sleep(50 * time.Millisecond) } + +// isVNCImage checks if the image is VNC-capable (playwright or desktop variants) +func isVNCImage(imageName string) bool { + return strings.Contains(imageName, "playwright") || strings.Contains(imageName, "desktop") +} + +// findAvailablePort finds an available port on the host +// This is used as a fallback; Docker can auto-assign ports when HostPort is empty +func findAvailablePort() (int, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port, nil +} diff --git a/sandbox/vncproxy/config.go b/sandbox/vncproxy/config.go new file mode 100644 index 00000000..4f63492b --- /dev/null +++ b/sandbox/vncproxy/config.go @@ -0,0 +1,81 @@ +package vncproxy + +import ( + "os" + "strconv" + "time" +) + +// Config holds VNC proxy configuration +type Config struct { + // Network settings + DockerNetwork string `json:"docker_network,omitempty"` // Docker network name (default: bridge) + ContainerNoVNCPort int `json:"container_novnc_port,omitempty"` // noVNC port inside container (default: 6080) + ContainerVNCPort int `json:"container_vnc_port,omitempty"` // VNC port inside container (default: 5900) + ContainerNamePrefix string `json:"container_name_prefix,omitempty"` // Container name prefix (default: yao-sandbox-) + + // Cache settings + IPCacheTTL time.Duration `json:"ip_cache_ttl,omitempty"` // IP cache TTL (default: 30s) + + // VNC status check + VNCCheckTimeout time.Duration `json:"vnc_check_timeout,omitempty"` // Timeout for VNC ready check (default: 2s) +} + +// DefaultConfig returns default configuration +func DefaultConfig() *Config { + return &Config{ + DockerNetwork: "bridge", + ContainerNoVNCPort: 6080, + ContainerVNCPort: 5900, + ContainerNamePrefix: "yao-sandbox-", + IPCacheTTL: 30 * time.Second, + VNCCheckTimeout: 2 * time.Second, + } +} + +// Init initializes config from environment variables +func (c *Config) Init() { + if env := os.Getenv("YAO_VNC_DOCKER_NETWORK"); env != "" { + c.DockerNetwork = env + } else if c.DockerNetwork == "" { + c.DockerNetwork = "bridge" + } + + if env := os.Getenv("YAO_VNC_CONTAINER_NOVNC_PORT"); env != "" { + if v, err := strconv.Atoi(env); err == nil && v > 0 { + c.ContainerNoVNCPort = v + } + } else if c.ContainerNoVNCPort == 0 { + c.ContainerNoVNCPort = 6080 + } + + if env := os.Getenv("YAO_VNC_CONTAINER_VNC_PORT"); env != "" { + if v, err := strconv.Atoi(env); err == nil && v > 0 { + c.ContainerVNCPort = v + } + } else if c.ContainerVNCPort == 0 { + c.ContainerVNCPort = 5900 + } + + if env := os.Getenv("YAO_VNC_CONTAINER_NAME_PREFIX"); env != "" { + c.ContainerNamePrefix = env + } else if c.ContainerNamePrefix == "" { + c.ContainerNamePrefix = "yao-sandbox-" + } + + if env := os.Getenv("YAO_VNC_IP_CACHE_TTL"); env != "" { + if v, err := time.ParseDuration(env); err == nil && v > 0 { + c.IPCacheTTL = v + } + } else if c.IPCacheTTL == 0 { + c.IPCacheTTL = 30 * time.Second + } + + if env := os.Getenv("YAO_VNC_CHECK_TIMEOUT"); env != "" { + if v, err := time.ParseDuration(env); err == nil && v > 0 { + c.VNCCheckTimeout = v + } + } else if c.VNCCheckTimeout == 0 { + c.VNCCheckTimeout = 2 * time.Second + } +} diff --git a/sandbox/vncproxy/proxy.go b/sandbox/vncproxy/proxy.go new file mode 100644 index 00000000..6ec1e4c2 --- /dev/null +++ b/sandbox/vncproxy/proxy.go @@ -0,0 +1,565 @@ +package vncproxy + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/gorilla/websocket" +) + +// ipCacheEntry holds cached container IP with expiration +type ipCacheEntry struct { + IP string + ExpiresAt time.Time +} + +// Proxy handles VNC proxy requests +type Proxy struct { + config *Config + dockerClient *client.Client + ipCache sync.Map // containerName -> *ipCacheEntry + upgrader websocket.Upgrader +} + +// NewProxy creates a new VNC proxy +func NewProxy(config *Config) (*Proxy, error) { + if config == nil { + config = DefaultConfig() + } + config.Init() + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return nil, fmt.Errorf("failed to create Docker client: %w", err) + } + + // Verify Docker connection + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if _, err := cli.Ping(ctx); err != nil { + cli.Close() + return nil, fmt.Errorf("Docker not available: %w", err) + } + + return &Proxy{ + config: config, + dockerClient: cli, + upgrader: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true // Allow all origins for VNC + }, + Subprotocols: []string{"binary"}, // noVNC uses binary subprotocol + }, + }, nil +} + +// Close closes the proxy and releases resources +func (p *Proxy) Close() error { + return p.dockerClient.Close() +} + +// extractSandboxID extracts sandbox ID from request path +// Expected format: /v1/sandbox/{id}/vnc/... +func extractSandboxID(r *http.Request) string { + path := r.URL.Path + // Remove prefix /v1/sandbox/ + path = strings.TrimPrefix(path, "/v1/sandbox/") + // Get ID (first segment before next /) + if idx := strings.Index(path, "/"); idx > 0 { + return path[:idx] + } + return path +} + +// HandleVNCStatus returns VNC status for a container +// GET /v1/sandbox/{id}/vnc +func (p *Proxy) HandleVNCStatus(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := p.config.ContainerNamePrefix + sandboxID + + response := map[string]interface{}{ + "sandbox_id": sandboxID, + "container": containerName, + } + + // Check if container exists and is running + _, err := p.getContainerIP(r.Context(), containerName) + if err != nil { + response["available"] = false + response["status"] = "unavailable" + response["message"] = "Container not available" + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // Check if VNC is enabled for this container + if !p.checkVNCEnabled(r.Context(), containerName) { + response["available"] = false + response["status"] = "not_supported" + response["message"] = "VNC not available for this container type" + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // Check if VNC services are ready (try to connect to websockify port) + if !p.checkVNCReady(r.Context(), containerName) { + response["available"] = false + response["status"] = "starting" + response["message"] = "VNC services are starting..." + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // VNC is ready + response["available"] = true + response["status"] = "ready" + response["client_url"] = fmt.Sprintf("/v1/sandbox/%s/vnc/client", sandboxID) + response["websocket_url"] = fmt.Sprintf("/v1/sandbox/%s/vnc/ws", sandboxID) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// HandleVNCClient serves the noVNC client page +// GET /v1/sandbox/{id}/vnc/client?viewonly=true|false +func (p *Proxy) HandleVNCClient(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := p.config.ContainerNamePrefix + sandboxID + + // Verify container exists and is running + _, err := p.getContainerIP(r.Context(), containerName) + if err != nil { + http.Error(w, "Container not available", http.StatusNotFound) + return + } + + if !p.checkVNCEnabled(r.Context(), containerName) { + http.Error(w, "VNC not available for this container", http.StatusBadRequest) + return + } + + // Get viewonly parameter (default: false = interactive) + viewOnly := r.URL.Query().Get("viewonly") == "true" + + // Serve inline noVNC HTML page with status checking + wsPath := fmt.Sprintf("/v1/sandbox/%s/vnc/ws", sandboxID) + p.serveNoVNCPage(w, sandboxID, wsPath, viewOnly) +} + +// HandleVNCWebSocket proxies WebSocket connection to container VNC +// GET /v1/sandbox/{id}/vnc/ws +func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { + sandboxID := extractSandboxID(r) + containerName := p.config.ContainerNamePrefix + sandboxID + + // Get VNC endpoint (uses port mapping if available, otherwise container IP) + targetAddr, err := p.getVNCEndpoint(r.Context(), containerName) + if err != nil { + http.Error(w, "Container not available", http.StatusNotFound) + return + } + + // Upgrade HTTP to WebSocket + clientConn, err := p.upgrader.Upgrade(w, r, nil) + if err != nil { + return // Upgrader already sent error response + } + defer clientConn.Close() + + // Connect to container's websockify + targetConn, err := net.DialTimeout("tcp", targetAddr, 5*time.Second) + if err != nil { + clientConn.WriteMessage(websocket.CloseMessage, + websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "VNC connection failed")) + return + } + defer targetConn.Close() + + // Bidirectional proxy + done := make(chan struct{}) + + // Client -> Container + go func() { + defer func() { done <- struct{}{} }() + for { + messageType, data, err := clientConn.ReadMessage() + if err != nil { + return + } + if messageType == websocket.BinaryMessage { + if _, err := targetConn.Write(data); err != nil { + return + } + } + } + }() + + // Container -> Client + go func() { + defer func() { done <- struct{}{} }() + buf := make([]byte, 32*1024) + for { + n, err := targetConn.Read(buf) + if err != nil { + return + } + if err := clientConn.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil { + return + } + } + }() + + // Wait for either direction to close + <-done +} + +// getContainerIP gets the IP address of a container, using cache with TTL +func (p *Proxy) getContainerIP(ctx context.Context, containerName string) (string, error) { + // Check cache + if cached, ok := p.ipCache.Load(containerName); ok { + entry := cached.(*ipCacheEntry) + if time.Now().Before(entry.ExpiresAt) { + return entry.IP, nil + } + // Cache expired, delete it + p.ipCache.Delete(containerName) + } + + // Get from Docker + info, err := p.dockerClient.ContainerInspect(ctx, containerName) + if err != nil { + return "", fmt.Errorf("container not found: %w", err) + } + + if !info.State.Running { + return "", fmt.Errorf("container not running") + } + + // Get IP from the specified network or default bridge + var ip string + if info.NetworkSettings != nil && info.NetworkSettings.Networks != nil { + if net, ok := info.NetworkSettings.Networks[p.config.DockerNetwork]; ok { + ip = net.IPAddress + } else { + // Try to get IP from any network + for _, net := range info.NetworkSettings.Networks { + if net.IPAddress != "" { + ip = net.IPAddress + break + } + } + } + } + + if ip == "" { + return "", fmt.Errorf("container has no IP address") + } + + // Cache the result + p.ipCache.Store(containerName, &ipCacheEntry{ + IP: ip, + ExpiresAt: time.Now().Add(p.config.IPCacheTTL), + }) + + return ip, nil +} + +// getVNCEndpoint returns the host:port to connect to for VNC +// It first checks for port mapping (for Docker Desktop), then falls back to container IP +func (p *Proxy) getVNCEndpoint(ctx context.Context, containerName string) (string, error) { + info, err := p.dockerClient.ContainerInspect(ctx, containerName) + if err != nil { + return "", fmt.Errorf("container not found: %w", err) + } + + if !info.State.Running { + return "", fmt.Errorf("container not running") + } + + // Check for port mapping first (for Docker Desktop on macOS/Windows) + if info.NetworkSettings != nil && info.NetworkSettings.Ports != nil { + portKey := nat.Port(fmt.Sprintf("%d/tcp", p.config.ContainerNoVNCPort)) + if bindings, ok := info.NetworkSettings.Ports[portKey]; ok && len(bindings) > 0 { + binding := bindings[0] + if binding.HostPort != "" { + // Use mapped port on localhost + host := binding.HostIP + if host == "" || host == "0.0.0.0" { + host = "127.0.0.1" + } + return net.JoinHostPort(host, binding.HostPort), nil + } + } + } + + // Fall back to container IP (works on Linux with native Docker) + ip, err := p.getContainerIP(ctx, containerName) + if err != nil { + return "", err + } + return net.JoinHostPort(ip, fmt.Sprintf("%d", p.config.ContainerNoVNCPort)), nil +} + +// checkVNCEnabled checks if container has VNC enabled by checking env vars +func (p *Proxy) checkVNCEnabled(ctx context.Context, containerName string) bool { + info, err := p.dockerClient.ContainerInspect(ctx, containerName) + if err != nil { + return false + } + + // Check environment variables for VNC_ENABLED or SANDBOX_VNC_ENABLED + for _, env := range info.Config.Env { + if strings.HasPrefix(env, "SANDBOX_VNC_ENABLED=true") || + strings.HasPrefix(env, "VNC_ENABLED=true") { + return true + } + } + + // Also check if container image is a VNC-enabled variant + imageName := info.Config.Image + if strings.Contains(imageName, "playwright") || + strings.Contains(imageName, "desktop") { + return true + } + + return false +} + +// checkVNCReady tests if VNC services are ready +// Uses docker exec to test port connectivity (works across platforms including macOS Docker Desktop) +func (p *Proxy) checkVNCReady(ctx context.Context, containerName string) bool { + // Use docker exec to test port connectivity from inside the container + // This approach works regardless of host network configuration + execConfig := container.ExecOptions{ + Cmd: []string{"sh", "-c", fmt.Sprintf("nc -z localhost %d 2>/dev/null || (echo | timeout 1 cat < /dev/tcp/localhost/%d > /dev/null 2>&1)", p.config.ContainerNoVNCPort, p.config.ContainerNoVNCPort)}, + AttachStdout: false, + AttachStderr: false, + } + + execResp, err := p.dockerClient.ContainerExecCreate(ctx, containerName, execConfig) + if err != nil { + return false + } + + err = p.dockerClient.ContainerExecStart(ctx, execResp.ID, container.ExecStartOptions{}) + if err != nil { + return false + } + + // Wait for exec to complete and check exit code + for i := 0; i < 10; i++ { + inspect, err := p.dockerClient.ContainerExecInspect(ctx, execResp.ID) + if err != nil { + return false + } + if !inspect.Running { + return inspect.ExitCode == 0 + } + time.Sleep(100 * time.Millisecond) + } + + return false +} + +// serveNoVNCPage serves an inline HTML page with noVNC client +func (p *Proxy) serveNoVNCPage(w http.ResponseWriter, sandboxID, wsPath string, viewOnly bool) { + viewOnlyStr := "false" + modeIndicator := "可交互" + modeColor := "#4CAF50" + if viewOnly { + viewOnlyStr = "true" + modeIndicator = "只读模式" + modeColor = "#FF9800" + } + + html := fmt.Sprintf(` + + + + + VNC - %s + + + +
+
+
正在连接 VNC...
+
+
+
+
%s
+
+ + + +`, sandboxID, modeColor, modeIndicator, sandboxID, wsPath, viewOnlyStr) + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + io.WriteString(w, html) +} + +// RegisterRoutes registers VNC proxy routes to an HTTP mux +func (p *Proxy) RegisterRoutes(mux *http.ServeMux) { + mux.HandleFunc("/v1/sandbox/", func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + + // Match /v1/sandbox/{id}/vnc + if strings.HasSuffix(path, "/vnc") { + p.HandleVNCStatus(w, r) + return + } + + // Match /v1/sandbox/{id}/vnc/client + if strings.HasSuffix(path, "/vnc/client") { + p.HandleVNCClient(w, r) + return + } + + // Match /v1/sandbox/{id}/vnc/ws + if strings.HasSuffix(path, "/vnc/ws") { + p.HandleVNCWebSocket(w, r) + return + } + + http.NotFound(w, r) + }) +} + +// Helper function to check if request requires VNC container +func (p *Proxy) isVNCRequest(r *http.Request) bool { + path := r.URL.Path + return strings.Contains(path, "/vnc") +} diff --git a/sandbox/vncproxy/proxy_test.go b/sandbox/vncproxy/proxy_test.go new file mode 100644 index 00000000..cd13dad4 --- /dev/null +++ b/sandbox/vncproxy/proxy_test.go @@ -0,0 +1,90 @@ +package vncproxy + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestExtractSandboxID(t *testing.T) { + tests := []struct { + name string + path string + expected string + }{ + { + name: "VNC status path", + path: "/v1/sandbox/abc123/vnc", + expected: "abc123", + }, + { + name: "VNC client path", + path: "/v1/sandbox/user-chat-123/vnc/client", + expected: "user-chat-123", + }, + { + name: "VNC websocket path", + path: "/v1/sandbox/test-sandbox-id/vnc/ws", + expected: "test-sandbox-id", + }, + { + name: "Complex ID", + path: "/v1/sandbox/user_123-chat_456/vnc", + expected: "user_123-chat_456", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, tt.path, nil) + got := extractSandboxID(req) + if got != tt.expected { + t.Errorf("extractSandboxID() = %q, want %q", got, tt.expected) + } + }) + } +} + +func TestConfigDefaults(t *testing.T) { + config := DefaultConfig() + + if config.DockerNetwork != "bridge" { + t.Errorf("DockerNetwork = %q, want %q", config.DockerNetwork, "bridge") + } + if config.ContainerNoVNCPort != 6080 { + t.Errorf("ContainerNoVNCPort = %d, want %d", config.ContainerNoVNCPort, 6080) + } + if config.ContainerVNCPort != 5900 { + t.Errorf("ContainerVNCPort = %d, want %d", config.ContainerVNCPort, 5900) + } + if config.ContainerNamePrefix != "yao-sandbox-" { + t.Errorf("ContainerNamePrefix = %q, want %q", config.ContainerNamePrefix, "yao-sandbox-") + } +} + +func TestConfigInit(t *testing.T) { + config := &Config{} + config.Init() + + // Should have defaults after Init + if config.DockerNetwork != "bridge" { + t.Errorf("DockerNetwork = %q, want %q", config.DockerNetwork, "bridge") + } + if config.ContainerNoVNCPort != 6080 { + t.Errorf("ContainerNoVNCPort = %d, want %d", config.ContainerNoVNCPort, 6080) + } +} + +// Integration tests require Docker - skip if not available +func TestProxyCreation(t *testing.T) { + // This will fail if Docker is not available, which is expected in CI + proxy, err := NewProxy(nil) + if err != nil { + t.Skipf("Skipping test: Docker not available: %v", err) + } + defer proxy.Close() + + if proxy.config == nil { + t.Error("Proxy config should not be nil") + } +} From 55164c566575e054a994ff986583065afcc3367d Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 5 Feb 2026 18:23:15 +0800 Subject: [PATCH 4/6] Enhance VNC sandbox images with Yao branding and sudo support - Add passwordless sudo for sandbox user in playwright and desktop images - Configure Yao branding for Fluxbox (CUI dark theme, centered logo) - Configure Yao branding for XFCE (panel icons, wallpaper, workspace shortcut) - Add D-Bus support for XFCE desktop environment - Add dynamic wallpaper generation with configurable resolution and logo size - Hide Fluxbox toolbar for cleaner Playwright automation view - Create chromium wrapper script using Playwright's bundled browser - Add feh and imagemagick for wallpaper management in Fluxbox - Update .gitignore for config scripts and TODO file Co-authored-by: Cursor --- .gitignore | 2 + sandbox/docker/desktop/Dockerfile | 24 +++ .../config/panel-launcher-chromium.desktop | 12 ++ sandbox/docker/desktop/config/setup-xfce.sh | 160 ++++++++++++++++++ .../docker/desktop/config/workspace.desktop | 9 + .../docker/desktop/config/yao-logo-128.png | Bin 0 -> 7001 bytes .../docker/desktop/config/yao-logo-256.png | Bin 0 -> 14416 bytes sandbox/docker/desktop/config/yao-logo-48.png | Bin 0 -> 2897 bytes sandbox/docker/desktop/config/yao-logo.svg | 16 ++ sandbox/docker/playwright/Dockerfile | 18 +- sandbox/docker/playwright/config/yao-logo.png | Bin 0 -> 24810 bytes sandbox/docker/vnc/start-vnc.sh | 38 +++++ 12 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 sandbox/docker/desktop/config/panel-launcher-chromium.desktop create mode 100644 sandbox/docker/desktop/config/setup-xfce.sh create mode 100644 sandbox/docker/desktop/config/workspace.desktop create mode 100644 sandbox/docker/desktop/config/yao-logo-128.png create mode 100644 sandbox/docker/desktop/config/yao-logo-256.png create mode 100644 sandbox/docker/desktop/config/yao-logo-48.png create mode 100644 sandbox/docker/desktop/config/yao-logo.svg create mode 100644 sandbox/docker/playwright/config/yao-logo.png diff --git a/.gitignore b/.gitignore index eaab7002..fcd6eada 100644 --- a/.gitignore +++ b/.gitignore @@ -57,8 +57,10 @@ agent/test/UPGRADE_PLAN.md introduction/* !sandbox/docker/build.sh !sandbox/docker/vnc/*.sh +!sandbox/docker/desktop/config/*.sh sandbox/docker/yao-bridge-* sandbox/docker/claude-proxy-* sandbox/docker/claude/claude-proxy-* sandbox/proxy/claude-proxy-linux-* release/* +sandbox/TODO-VNC.md diff --git a/sandbox/docker/desktop/Dockerfile b/sandbox/docker/desktop/Dockerfile index 6fcd389c..aaa26c87 100644 --- a/sandbox/docker/desktop/Dockerfile +++ b/sandbox/docker/desktop/Dockerfile @@ -16,6 +16,8 @@ RUN sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu # Install X11, VNC, and XFCE desktop environment RUN apt-get update && apt-get install -y --no-install-recommends \ + # Sudo for sandbox user + sudo \ # Virtual display xvfb \ # VNC server @@ -23,6 +25,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # noVNC (HTML5 VNC client) and websockify novnc \ python3-websockify \ + # D-Bus (required for XFCE) + dbus-x11 \ # XFCE Desktop (full-featured but lightweight) xfce4 \ xfce4-terminal \ @@ -40,6 +44,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get remove -y xfce4-screensaver xscreensaver || true \ && rm -rf /var/lib/apt/lists/* +# Configure passwordless sudo for sandbox user +RUN echo "sandbox ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/sandbox && \ + chmod 0440 /etc/sudoers.d/sandbox + +# Create chromium wrapper script (uses Playwright's Chromium, starts maximized) +RUN echo '#!/bin/bash\nexec /home/sandbox/.cache/ms-playwright/chromium-1208/chrome-linux/chrome --no-sandbox --start-maximized "$@"' > /usr/local/bin/chromium && \ + chmod +x /usr/local/bin/chromium + # Optional: Install Playwright system dependencies (requires root) # Users can run browser automation in desktop mode too RUN npx playwright install-deps chromium || true @@ -58,6 +70,16 @@ COPY vnc/start-vnc.sh /usr/local/bin/start-vnc.sh COPY vnc/entrypoint-vnc.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/start-vnc.sh /usr/local/bin/entrypoint.sh +# Copy Yao branding assets +RUN mkdir -p /usr/share/yao +COPY desktop/config/yao-logo-48.png /usr/share/yao/yao-logo-48.png +COPY desktop/config/yao-logo-128.png /usr/share/yao/yao-logo-128.png +COPY desktop/config/yao-logo-256.png /usr/share/yao/yao-logo-256.png +COPY desktop/config/panel-launcher-chromium.desktop /usr/share/yao/panel-launcher-chromium.desktop +COPY desktop/config/workspace.desktop /usr/share/yao/workspace.desktop +COPY desktop/config/setup-xfce.sh /usr/local/bin/setup-xfce.sh +RUN chmod +x /usr/local/bin/setup-xfce.sh + # Environment variables for VNC ENV DISPLAY=:99 ENV VNC_PORT=5900 @@ -65,6 +87,8 @@ ENV NOVNC_PORT=6080 ENV RESOLUTION=1920x1080x24 ENV SANDBOX_VNC_ENABLED=true ENV SANDBOX_DESKTOP=xfce +# Set hostname for XFCE panel display +ENV HOSTNAME="Yao Sandbox" # Node.js environment - ensure global modules are accessible ENV NODE_PATH=/home/sandbox/.npm-global/lib/node_modules diff --git a/sandbox/docker/desktop/config/panel-launcher-chromium.desktop b/sandbox/docker/desktop/config/panel-launcher-chromium.desktop new file mode 100644 index 00000000..f392c51e --- /dev/null +++ b/sandbox/docker/desktop/config/panel-launcher-chromium.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Chromium +Comment=Access the Internet +GenericName=Web Browser +Exec=/usr/local/bin/chromium %U +Icon=org.xfce.webbrowser +Terminal=false +Categories=Network;WebBrowser; +MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/vnd.mozilla.xul+xml;application/rss+xml;application/rdf+xml;x-scheme-handler/http;x-scheme-handler/https; +StartupNotify=true diff --git a/sandbox/docker/desktop/config/setup-xfce.sh b/sandbox/docker/desktop/config/setup-xfce.sh new file mode 100644 index 00000000..75015a5a --- /dev/null +++ b/sandbox/docker/desktop/config/setup-xfce.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# XFCE desktop configuration script +# Runs on container startup to set up Yao branding and default applications + +set -e + +XFCE_CONFIG_DIR="$HOME/.config/xfce4" +XFDESKTOP_DIR="$HOME/.config/xfce4/xfconf/xfce-perchannel-xml" +ICONS_DIR="$HOME/.local/share/icons/hicolor" +APPS_DIR="$HOME/.local/share/applications" +DESKTOP_DIR="$HOME/Desktop" + +# Create necessary directories +mkdir -p "$XFCE_CONFIG_DIR/panel" +mkdir -p "$XFDESKTOP_DIR" +mkdir -p "$ICONS_DIR/48x48/apps" +mkdir -p "$ICONS_DIR/128x128/apps" +mkdir -p "$ICONS_DIR/256x256/apps" +mkdir -p "$APPS_DIR" +mkdir -p "$DESKTOP_DIR" + +# Copy Yao logo to user icons directory +if [ -f /usr/share/yao/yao-logo-48.png ]; then + cp /usr/share/yao/yao-logo-48.png "$ICONS_DIR/48x48/apps/yao.png" + cp /usr/share/yao/yao-logo-128.png "$ICONS_DIR/128x128/apps/yao.png" + cp /usr/share/yao/yao-logo-256.png "$ICONS_DIR/256x256/apps/yao.png" + # Also copy to system location for panel icon + sudo cp /usr/share/yao/yao-logo-48.png /usr/share/pixmaps/yao.png 2>/dev/null || true + gtk-update-icon-cache "$ICONS_DIR" 2>/dev/null || true +fi + +# Copy Chromium launcher to applications +if [ -f /usr/share/yao/panel-launcher-chromium.desktop ]; then + cp /usr/share/yao/panel-launcher-chromium.desktop "$APPS_DIR/chromium-browser.desktop" +fi + +# Configure xfdesktop - hide default icons (File System, Home, Trash), keep only custom shortcuts +cat > "$XFDESKTOP_DIR/xfce4-desktop.xml" << 'XMLEOF' + + + + + + + + + + + + +XMLEOF + +# Copy workspace shortcut to desktop (named "Workspace" with folder icon) +if [ -f /usr/share/yao/workspace.desktop ]; then + cp /usr/share/yao/workspace.desktop "$DESKTOP_DIR/workspace.desktop" + chmod +x "$DESKTOP_DIR/workspace.desktop" +fi + +# Set Chromium as default browser +xdg-settings set default-web-browser chromium-browser.desktop 2>/dev/null || true + +# Configure XFCE panel - set Applications menu icon to Yao logo +# This will be applied when xfce4-panel starts +cat > "$XFDESKTOP_DIR/xfce4-panel.xml" << 'XMLEOF' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +XMLEOF + +echo "[XFCE Setup] Configuration complete" diff --git a/sandbox/docker/desktop/config/workspace.desktop b/sandbox/docker/desktop/config/workspace.desktop new file mode 100644 index 00000000..dbb5ebdb --- /dev/null +++ b/sandbox/docker/desktop/config/workspace.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Workspace +Comment=Open Workspace folder +Icon=folder +Exec=thunar /workspace +Terminal=false +Categories=System;FileManager; diff --git a/sandbox/docker/desktop/config/yao-logo-128.png b/sandbox/docker/desktop/config/yao-logo-128.png new file mode 100644 index 0000000000000000000000000000000000000000..d86a79ac11818c1c276eb3bd62f1851731fef8c7 GIT binary patch literal 7001 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 CPUR*5 literal 0 HcmV?d00001 diff --git a/sandbox/docker/desktop/config/yao-logo-48.png b/sandbox/docker/desktop/config/yao-logo-48.png new file mode 100644 index 0000000000000000000000000000000000000000..52c779700b119aed32af89a43e04578d90141c00 GIT binary patch literal 2897 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 + + Yaobots + + + + + + + + + + + + + \ No newline at end of file diff --git a/sandbox/docker/playwright/Dockerfile b/sandbox/docker/playwright/Dockerfile index be6faa46..e131744f 100644 --- a/sandbox/docker/playwright/Dockerfile +++ b/sandbox/docker/playwright/Dockerfile @@ -16,6 +16,8 @@ RUN sed -i 's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.mit.edu/ubuntu # Install X11, VNC, and minimal window manager RUN apt-get update && apt-get install -y --no-install-recommends \ + # Sudo for sandbox user + sudo \ # Virtual display xvfb \ # VNC server @@ -25,6 +27,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-websockify \ # Minimal window manager (lightweight, perfect for Playwright) fluxbox \ + # Background/wallpaper utilities + feh \ + imagemagick \ # Fonts (required for proper browser rendering) fonts-liberation \ fonts-noto-cjk \ @@ -36,6 +41,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pulseaudio \ && rm -rf /var/lib/apt/lists/* +# Configure passwordless sudo for sandbox user +RUN echo "sandbox ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/sandbox && \ + chmod 0440 /etc/sudoers.d/sandbox + # Install Playwright system dependencies (requires root) # This installs system libraries needed by Chromium/Firefox RUN npx playwright install-deps chromium firefox || true @@ -50,11 +59,16 @@ RUN npm install -g playwright && \ USER root -# Copy VNC startup scripts +# Create directories for branding assets +RUN mkdir -p /usr/local/share/yao + +# Copy VNC startup scripts and branding assets # Note: Build context should be sandbox/docker/, so paths are relative to that COPY vnc/start-vnc.sh /usr/local/bin/start-vnc.sh COPY vnc/entrypoint-vnc.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/start-vnc.sh /usr/local/bin/entrypoint.sh +COPY playwright/config/setup-fluxbox.sh /usr/local/bin/setup-fluxbox.sh +COPY playwright/config/yao-logo.png /usr/local/share/yao/yao-logo.png +RUN chmod +x /usr/local/bin/start-vnc.sh /usr/local/bin/entrypoint.sh /usr/local/bin/setup-fluxbox.sh # Environment variables for VNC ENV DISPLAY=:99 diff --git a/sandbox/docker/playwright/config/yao-logo.png b/sandbox/docker/playwright/config/yao-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..de7e59fb2b8374048cf82680484c3d28ec23fded GIT binary patch literal 24810 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 YVc|z6QtRCJZ^tRgsmYeUd>i=x07`^SIRF3v literal 0 HcmV?d00001 diff --git a/sandbox/docker/vnc/start-vnc.sh b/sandbox/docker/vnc/start-vnc.sh index d7d0677b..57f0a03c 100644 --- a/sandbox/docker/vnc/start-vnc.sh +++ b/sandbox/docker/vnc/start-vnc.sh @@ -31,20 +31,58 @@ if ! kill -0 $XVFB_PID 2>/dev/null; then fi echo "[VNC] Xvfb started (PID: $XVFB_PID)" +# Start D-Bus session bus (required for XFCE) +if [ "$DESKTOP" = "xfce" ] || [ "$DESKTOP" = "xfce4" ]; then + echo "[VNC] Starting D-Bus session bus..." + if command -v dbus-launch &> /dev/null; then + eval $(dbus-launch --sh-syntax) + export DBUS_SESSION_BUS_ADDRESS + echo "[VNC] D-Bus started: $DBUS_SESSION_BUS_ADDRESS" + else + echo "[VNC] WARNING: dbus-launch not found, XFCE may have limited functionality" + fi +fi + # Start window manager / desktop environment echo "[VNC] Starting ${DESKTOP}..." case "$DESKTOP" in xfce|xfce4) + # Run XFCE setup script if exists (for Yao branding) + if [ -x /usr/local/bin/setup-xfce.sh ]; then + echo "[VNC] Running XFCE setup..." + /usr/local/bin/setup-xfce.sh || true + fi # XFCE desktop environment startxfce4 & ;; fluxbox) + # Run Fluxbox setup script if exists (Yao branding, disable toolbar) + if [ -x /usr/local/bin/setup-fluxbox.sh ]; then + echo "[VNC] Running Fluxbox setup..." + /usr/local/bin/setup-fluxbox.sh || true + fi # Minimal window manager for Playwright fluxbox & + sleep 1 + # Set wallpaper with feh if available (for Yao branding) + WALLPAPER="$HOME/.local/share/wallpapers/yao-wallpaper.png" + if [ -f "$WALLPAPER" ] && command -v feh &> /dev/null; then + echo "[VNC] Setting wallpaper..." + feh --bg-center "$WALLPAPER" || true + fi ;; *) # Default to fluxbox + if [ -x /usr/local/bin/setup-fluxbox.sh ]; then + /usr/local/bin/setup-fluxbox.sh || true + fi fluxbox & + sleep 1 + # Set wallpaper with feh if available + WALLPAPER="$HOME/.local/share/wallpapers/yao-wallpaper.png" + if [ -f "$WALLPAPER" ] && command -v feh &> /dev/null; then + feh --bg-center "$WALLPAPER" || true + fi ;; esac sleep 2 From 0a965c383d31e706a23b64c0a1f1a20af4455bb9 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 5 Feb 2026 21:46:07 +0800 Subject: [PATCH 5/6] Add sandbox ID and VNC URL methods to sandbox executor - Implement GetSandboxID method to return a mock sandbox ID for testing. - Add GetVNCUrl method to return an empty string for VNC access in tests. - Update SandboxExecutor interface to include new methods for sandbox identification and VNC URL retrieval. - Enhance context creation to set sandbox ID and VNC URL properties in the sandbox instance. Co-authored-by: Cursor --- agent/context/jsapi_sandbox.go | 67 +++++++++++++++++++++++++++++ agent/context/jsapi_sandbox_test.go | 11 +++++ agent/sandbox/claude/executor.go | 37 +++++++++++++++- agent/sandbox/types.go | 7 +++ openapi/openapi.go | 1 + openapi/sandbox/sandbox.go | 29 +++++++++++-- sandbox/manager.go | 28 +++++++++--- sandbox/types.go | 7 +++ sandbox/vncproxy/proxy.go | 44 ++++++++++--------- 9 files changed, 200 insertions(+), 31 deletions(-) diff --git a/agent/context/jsapi_sandbox.go b/agent/context/jsapi_sandbox.go index 8439d691..9ad1edc6 100644 --- a/agent/context/jsapi_sandbox.go +++ b/agent/context/jsapi_sandbox.go @@ -4,6 +4,7 @@ import ( "context" "github.com/yaoapp/gou/runtime/v8/bridge" + openapiSandbox "github.com/yaoapp/yao/openapi/sandbox" infraSandbox "github.com/yaoapp/yao/sandbox" "rogchap.com/v8go" ) @@ -22,6 +23,12 @@ type SandboxExecutor interface { // Workspace info GetWorkDir() string + + // Sandbox identification + GetSandboxID() string + + // VNC access (returns empty string if not available) + GetVNCUrl() string } // SetSandboxExecutor sets the sandbox executor for this context @@ -54,6 +61,8 @@ func (ctx *Context) newSandboxObject(iso *v8go.Isolate) *v8go.ObjectTemplate { sandboxObj.Set("WriteFile", ctx.sandboxWriteFileMethod(iso)) sandboxObj.Set("ListDir", ctx.sandboxListDirMethod(iso)) sandboxObj.Set("Exec", ctx.sandboxExecMethod(iso)) + sandboxObj.Set("GetVNCUrl", ctx.sandboxGetVNCUrlMethod(iso)) + sandboxObj.Set("GetSandboxID", ctx.sandboxGetSandboxIDMethod(iso)) return sandboxObj } @@ -72,6 +81,19 @@ func (ctx *Context) createSandboxInstance(v8ctx *v8go.Context) *v8go.Value { // Set workdir as a property sandboxTemplate.Set("workdir", ctx.sandboxExecutor.GetWorkDir()) + // Set sandbox_id as a property + sandboxID := ctx.sandboxExecutor.GetSandboxID() + sandboxTemplate.Set("sandbox_id", sandboxID) + + // Set vnc_url as a property (empty string if not available) + // GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise + vncSandboxID := ctx.sandboxExecutor.GetVNCUrl() + if vncSandboxID != "" { + sandboxTemplate.Set("vnc_url", openapiSandbox.GetVNCClientURL(vncSandboxID)) + } else { + sandboxTemplate.Set("vnc_url", "") + } + instance, err := sandboxTemplate.NewInstance(v8ctx) if err != nil { return nil @@ -233,3 +255,48 @@ func (ctx *Context) sandboxExecMethod(iso *v8go.Isolate) *v8go.FunctionTemplate return jsVal }) } + +// sandboxGetVNCUrlMethod implements ctx.sandbox.GetVNCUrl() +func (ctx *Context) sandboxGetVNCUrlMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { + return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + v8ctx := info.Context() + + if ctx.sandboxExecutor == nil { + return bridge.JsException(v8ctx, "sandbox executor not available") + } + + // GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise + vncSandboxID := ctx.sandboxExecutor.GetVNCUrl() + vncUrl := "" + if vncSandboxID != "" { + vncUrl = openapiSandbox.GetVNCClientURL(vncSandboxID) + } + + jsVal, err := v8go.NewValue(iso, vncUrl) + if err != nil { + return bridge.JsException(v8ctx, err.Error()) + } + + return jsVal + }) +} + +// sandboxGetSandboxIDMethod implements ctx.sandbox.GetSandboxID() +func (ctx *Context) sandboxGetSandboxIDMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { + return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + v8ctx := info.Context() + + if ctx.sandboxExecutor == nil { + return bridge.JsException(v8ctx, "sandbox executor not available") + } + + sandboxID := ctx.sandboxExecutor.GetSandboxID() + + jsVal, err := v8go.NewValue(iso, sandboxID) + if err != nil { + return bridge.JsException(v8ctx, err.Error()) + } + + return jsVal + }) +} diff --git a/agent/context/jsapi_sandbox_test.go b/agent/context/jsapi_sandbox_test.go index 414347f0..b040b413 100644 --- a/agent/context/jsapi_sandbox_test.go +++ b/agent/context/jsapi_sandbox_test.go @@ -88,6 +88,17 @@ func (e *realSandboxExecutor) GetWorkDir() string { return e.workDir } +func (e *realSandboxExecutor) GetSandboxID() string { + // Extract sandbox ID from container name (format: yao-sandbox-{userID}-{chatID}) + // For tests, just return a mock ID + return "test-user-test-chat" +} + +func (e *realSandboxExecutor) GetVNCUrl() string { + // Tests don't use VNC, return empty + return "" +} + // TestJsSandboxNotAvailable tests ctx.sandbox when not configured func TestJsSandboxNotAvailable(t *testing.T) { test.Prepare(t, config.Conf) diff --git a/agent/sandbox/claude/executor.go b/agent/sandbox/claude/executor.go index 158bbfd4..5cdf51ed 100644 --- a/agent/sandbox/claude/executor.go +++ b/agent/sandbox/claude/executor.go @@ -79,7 +79,12 @@ func NewExecutor(manager *infraSandbox.Manager, opts interface{}) (*Executor, er // Create or get container // Note: IPC session is created by manager.createContainer, socket is already bind mounted ctx := context.Background() - container, err := manager.GetOrCreate(ctx, execOpts.UserID, execOpts.ChatID) + createOpts := infraSandbox.CreateOptions{ + UserID: execOpts.UserID, + ChatID: execOpts.ChatID, + Image: execOpts.Image, + } + container, err := manager.GetOrCreate(ctx, execOpts.UserID, execOpts.ChatID, createOpts) if err != nil { return nil, fmt.Errorf("failed to create container: %w", err) } @@ -1073,6 +1078,36 @@ func (e *Executor) GetWorkDir() string { return e.workDir } +// GetSandboxID returns the sandbox ID (userID-chatID) +func (e *Executor) GetSandboxID() string { + if e.opts == nil { + return "" + } + return fmt.Sprintf("%s-%s", e.opts.UserID, e.opts.ChatID) +} + +// GetVNCUrl returns the VNC preview URL path +// Returns empty string if VNC is not enabled for this sandbox image +func (e *Executor) GetVNCUrl() string { + if e.opts == nil { + return "" + } + + // Check if the image supports VNC (playwright or desktop variants) + imageName := e.opts.Image + if imageName == "" { + return "" + } + + // VNC is only available for playwright and desktop images + if !strings.Contains(imageName, "playwright") && !strings.Contains(imageName, "desktop") { + return "" + } + + // Return only the sandbox ID, the full URL is constructed by openapi/sandbox.GetVNCClientURL() + return e.GetSandboxID() +} + // Close releases the executor resources and removes the container // Note: IPC session is managed by sandbox.Manager.Remove() func (e *Executor) Close() error { diff --git a/agent/sandbox/types.go b/agent/sandbox/types.go index ea17f710..209944a2 100644 --- a/agent/sandbox/types.go +++ b/agent/sandbox/types.go @@ -32,6 +32,13 @@ type Executor interface { // GetWorkDir returns the container workspace directory GetWorkDir() string + // GetSandboxID returns the sandbox ID (userID-chatID) + GetSandboxID() string + + // GetVNCUrl returns the VNC preview URL path (e.g., /api/__yao/vnc/{sandboxID}/) + // Returns empty string if VNC is not enabled for this sandbox image + GetVNCUrl() string + // Close releases container resources Close() error } diff --git a/openapi/openapi.go b/openapi/openapi.go index 3d3da032..32c0e64e 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -161,6 +161,7 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) { app.Attach(group.Group("/app"), openapi.OAuth) // Sandbox handlers (VNC proxy for visual browser automation) + sandbox.SetPathPrefix(baseURL) sandbox.Attach(group.Group("/sandbox"), openapi.OAuth) // Custom handlers (Defined by developer) diff --git a/openapi/sandbox/sandbox.go b/openapi/sandbox/sandbox.go index eecaf437..f0d6e731 100644 --- a/openapi/sandbox/sandbox.go +++ b/openapi/sandbox/sandbox.go @@ -1,7 +1,9 @@ package sandbox import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/yaoapp/yao/openapi/oauth/types" @@ -19,14 +21,13 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) { // Initialize VNC proxy lazily on first request // This avoids startup errors if Docker is not available - // VNC status endpoint (requires auth) + // VNC status endpoint group.GET("/:id/vnc", oauth.Guard, handleVNCStatus) - // VNC client page (requires auth) + // VNC client page group.GET("/:id/vnc/client", oauth.Guard, handleVNCClient) - // VNC WebSocket proxy (requires auth) - // Note: WebSocket upgrade happens after auth middleware + // VNC WebSocket proxy group.GET("/:id/vnc/ws", oauth.Guard, handleVNCWebSocket) } @@ -96,3 +97,23 @@ func Close() error { } return nil } + +// pathPrefix stores the router path prefix for sandbox endpoints +var pathPrefix string = "/v1/sandbox" + +// SetPathPrefix sets the path prefix for sandbox URLs +// Called during router setup with the actual OpenAPI base URL +func SetPathPrefix(prefix string) { + pathPrefix = strings.TrimSuffix(prefix, "/") + "/sandbox" +} + +// GetVNCClientURL returns the API VNC client page URL +// sandboxID is the sandbox identifier (userID-chatID) +// Returns the URL path like "/v1/sandbox/{id}/vnc/client" +// Note: For CUI navigation, use "$dashboard/sandbox/{id}" directly with sandbox_id +func GetVNCClientURL(sandboxID string) string { + if sandboxID == "" { + return "" + } + return fmt.Sprintf("%s/%s/vnc/client", pathPrefix, sandboxID) +} diff --git a/sandbox/manager.go b/sandbox/manager.go index b0fb56d3..80c8a545 100644 --- a/sandbox/manager.go +++ b/sandbox/manager.go @@ -185,9 +185,17 @@ func (m *Manager) Close() error { } // GetOrCreate returns existing container or creates new one -func (m *Manager) GetOrCreate(ctx context.Context, userID, chatID string) (*Container, error) { +func (m *Manager) GetOrCreate(ctx context.Context, userID, chatID string, opts ...CreateOptions) (*Container, error) { name := containerName(userID, chatID) + // Extract options if provided + var createOpts CreateOptions + if len(opts) > 0 { + createOpts = opts[0] + } + createOpts.UserID = userID + createOpts.ChatID = chatID + // Check if container already exists (fast path) if c, ok := m.containers.Load(name); ok { cont := c.(*Container) @@ -243,7 +251,7 @@ func (m *Manager) GetOrCreate(ctx context.Context, userID, chatID string) (*Cont } // Create new container - cont, err := m.createContainer(ctx, userID, chatID) + cont, err := m.createContainer(ctx, createOpts) if err != nil { return nil, err } @@ -256,11 +264,19 @@ func (m *Manager) GetOrCreate(ctx context.Context, userID, chatID string) (*Cont } // createContainer creates a new Docker container -func (m *Manager) createContainer(ctx context.Context, userID, chatID string) (*Container, error) { +func (m *Manager) createContainer(ctx context.Context, opts CreateOptions) (*Container, error) { + userID := opts.UserID + chatID := opts.ChatID name := containerName(userID, chatID) + // Use image from options or fall back to config default + image := opts.Image + if image == "" { + image = m.config.Image + } + // Ensure image exists, pull if not - if err := m.ensureImage(ctx, m.config.Image); err != nil { + if err := m.ensureImage(ctx, image); err != nil { return nil, err } @@ -283,7 +299,7 @@ func (m *Manager) createContainer(ctx context.Context, userID, chatID string) (* // Container configuration containerConfig := &container.Config{ - Image: m.config.Image, + Image: image, Cmd: []string{"sleep", "infinity"}, WorkingDir: m.config.ContainerWorkDir, User: m.config.ContainerUser, // Empty string uses image default @@ -310,7 +326,7 @@ func (m *Manager) createContainer(ctx context.Context, userID, chatID string) (* // VNC port mapping for Docker Desktop (macOS/Windows) // Only enable for VNC-capable images (playwright/desktop) when config is enabled - if m.config.VNCPortMapping && isVNCImage(m.config.Image) { + if m.config.VNCPortMapping && isVNCImage(image) { // Expose VNC ports in container config containerConfig.ExposedPorts = nat.PortSet{ "6080/tcp": struct{}{}, // noVNC websockify diff --git a/sandbox/types.go b/sandbox/types.go index 7db7e63c..96f09875 100644 --- a/sandbox/types.go +++ b/sandbox/types.go @@ -66,3 +66,10 @@ const ( StatusRunning = "running" StatusStopped = "stopped" ) + +// CreateOptions contains options for creating a container +type CreateOptions struct { + UserID string // User identifier (required) + ChatID string // Chat/session identifier (required) + Image string // Docker image to use (optional, falls back to config default) +} diff --git a/sandbox/vncproxy/proxy.go b/sandbox/vncproxy/proxy.go index 6ec1e4c2..4a671733 100644 --- a/sandbox/vncproxy/proxy.go +++ b/sandbox/vncproxy/proxy.go @@ -172,15 +172,21 @@ func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { return } - // Upgrade HTTP to WebSocket + // Upgrade HTTP to WebSocket (client side) clientConn, err := p.upgrader.Upgrade(w, r, nil) if err != nil { return // Upgrader already sent error response } defer clientConn.Close() - // Connect to container's websockify - targetConn, err := net.DialTimeout("tcp", targetAddr, 5*time.Second) + // Connect to container's websockify via WebSocket (not raw TCP) + // websockify expects WebSocket connections at /websockify path with binary subprotocol + wsURL := fmt.Sprintf("ws://%s/websockify", targetAddr) + dialer := websocket.Dialer{ + Subprotocols: []string{"binary"}, + HandshakeTimeout: 5 * time.Second, + } + targetConn, _, err := dialer.Dial(wsURL, nil) if err != nil { clientConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "VNC connection failed")) @@ -188,8 +194,8 @@ func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { } defer targetConn.Close() - // Bidirectional proxy - done := make(chan struct{}) + // Bidirectional WebSocket proxy + done := make(chan struct{}, 2) // Client -> Container go func() { @@ -199,10 +205,8 @@ func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { if err != nil { return } - if messageType == websocket.BinaryMessage { - if _, err := targetConn.Write(data); err != nil { - return - } + if err := targetConn.WriteMessage(messageType, data); err != nil { + return } } }() @@ -210,13 +214,12 @@ func (p *Proxy) HandleVNCWebSocket(w http.ResponseWriter, r *http.Request) { // Container -> Client go func() { defer func() { done <- struct{}{} }() - buf := make([]byte, 32*1024) for { - n, err := targetConn.Read(buf) + messageType, data, err := targetConn.ReadMessage() if err != nil { return } - if err := clientConn.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil { + if err := clientConn.WriteMessage(messageType, data); err != nil { return } } @@ -390,7 +393,7 @@ func (p *Proxy) serveNoVNCPage(w http.ResponseWriter, sandboxID, wsPath string, - VNC - %s + Sandbox - %s