From a1869726d500596db6cf7c04bc509ef8c206132c Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 1 Sep 2025 12:26:29 +0800 Subject: [PATCH] Enhance job execution and management features - Implemented execution options to allow priority settings and shared data for job executions. - Introduced new execution types for processes and commands, improving flexibility in job handling. - Updated job and execution models to include new fields for execution options and priority. - Refactored job submission logic to support context-aware execution, enhancing error handling and process management. - Improved test coverage for job execution scenarios, ensuring robust validation of new features. --- data/bindata.go | 278 ++++++++-------- job/data.go | 170 ++++++++++ job/data_test.go | 46 ++- job/execution.go | 94 +++--- job/goroutine.go | 259 +++++++++++++++ job/job.go | 167 ++++++++-- job/job_test.go | 543 +++++++++++++++---------------- job/process.go | 243 ++++++++++++++ job/progress_test.go | 260 --------------- job/types.go | 117 +++++-- job/types_test.go | 334 ------------------- job/worker.go | 159 ++++----- job/worker_test.go | 319 ------------------ yao/models/job/execution.mod.yao | 16 + 14 files changed, 1486 insertions(+), 1519 deletions(-) delete mode 100644 job/progress_test.go delete mode 100644 job/types_test.go delete mode 100644 job/worker_test.go diff --git a/data/bindata.go b/data/bindata.go index 3fed5dda..5739f71f 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -316,7 +316,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -336,7 +336,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(1756611751, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -356,7 +356,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(1756611751, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -376,7 +376,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(1756611751, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -396,7 +396,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(1756611751, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -416,7 +416,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -436,7 +436,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -456,7 +456,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -476,7 +476,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -496,7 +496,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -516,7 +516,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -536,7 +536,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -556,7 +556,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -576,7 +576,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -596,7 +596,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -616,7 +616,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -636,7 +636,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -656,7 +656,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -676,7 +676,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -696,7 +696,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -716,7 +716,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -736,7 +736,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -756,7 +756,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -776,7 +776,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -796,7 +796,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(1756611751, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -816,7 +816,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -836,7 +836,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -856,7 +856,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -876,7 +876,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -896,7 +896,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -916,7 +916,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -936,7 +936,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -956,7 +956,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -976,7 +976,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -996,7 +996,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1016,7 +1016,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(1756611751, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1036,7 +1036,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1056,7 +1056,7 @@ func initFlowsMenuFlowYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1076,7 +1076,7 @@ func initFormsAccountFormYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1096,7 +1096,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1116,7 +1116,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1136,7 +1136,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1156,7 +1156,7 @@ func initLoginsAdminLoginYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1176,7 +1176,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1196,7 +1196,7 @@ func initModelsAdminUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1216,7 +1216,7 @@ func initModelsTestsPetModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1236,7 +1236,7 @@ func initNeoNeoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1256,7 +1256,7 @@ func initPublicReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1276,7 +1276,7 @@ func initPublicAssetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1296,7 +1296,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1316,7 +1316,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1336,7 +1336,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1356,7 +1356,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1376,7 +1376,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1396,7 +1396,7 @@ func initPublicIndexCfg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1416,7 +1416,7 @@ func initPublicIndexSui() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1436,7 +1436,7 @@ func initScriptsAccountTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1456,7 +1456,7 @@ func initScriptsAiNeoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1476,7 +1476,7 @@ func initScriptsTestsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1496,7 +1496,7 @@ func initScriptsUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1516,7 +1516,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(1756611751, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1536,7 +1536,7 @@ func initTablesAccountTabYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1556,7 +1556,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1576,7 +1576,7 @@ func libsuiAgentTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1596,7 +1596,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1616,7 +1616,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1636,7 +1636,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1656,7 +1656,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1676,7 +1676,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1696,7 +1696,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1716,7 +1716,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1736,7 +1736,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1756,7 +1756,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1776,7 +1776,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1796,7 +1796,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1816,7 +1816,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1836,7 +1836,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1856,7 +1856,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1876,7 +1876,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1896,7 +1896,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1916,7 +1916,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1936,7 +1936,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1956,7 +1956,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1976,7 +1976,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1996,7 +1996,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2016,7 +2016,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2036,7 +2036,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2056,7 +2056,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2076,7 +2076,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2096,7 +2096,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2116,7 +2116,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2136,7 +2136,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2156,7 +2156,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2176,7 +2176,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2196,7 +2196,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2216,7 +2216,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2236,7 +2236,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2256,7 +2256,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2276,7 +2276,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2296,7 +2296,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2316,7 +2316,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2336,7 +2336,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2356,7 +2356,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2376,7 +2376,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2396,7 +2396,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2416,7 +2416,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2436,7 +2436,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2456,7 +2456,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2476,7 +2476,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2496,7 +2496,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2516,7 +2516,7 @@ func yaoModelsAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/assistant.mod.yao", size: 4114, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/assistant.mod.yao", size: 4114, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2536,7 +2536,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 3865, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 3865, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2556,7 +2556,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2576,7 +2576,7 @@ func yaoModelsChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/chat.mod.yao", size: 1444, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/chat.mod.yao", size: 1444, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2596,7 +2596,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2616,7 +2616,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2636,7 +2636,7 @@ func yaoModelsHistoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/history.mod.yao", size: 2902, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/history.mod.yao", size: 2902, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2656,12 +2656,12 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2040, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2040, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoModelsJobExecutionModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x59\xdd\x4e\x24\xb9\x0e\xbe\xe7\x29\xac\xba\x62\x24\x38\xc3\x1c\x1d\x1d\x2d\xdc\x21\x06\x69\x67\xa5\x95\xd0\xc0\x68\x2e\x46\xa8\x95\x4e\x5c\x5d\x86\x54\xd2\x93\xa4\x80\xde\x15\xef\xbe\x4a\x52\x3f\xa9\x9f\x6e\xba\x6b\x99\x3b\x70\xf2\xd9\x9f\x63\x97\x63\xa7\xff\x3e\x02\xc8\x14\x2b\x31\xbb\x80\x0c\x5f\x90\x57\x8e\xb4\xca\x4e\xbc\x58\xb2\x25\x4a\x2f\xff\x43\x2f\xe1\xba\xbf\x26\xd0\x72\x43\xeb\x20\xa8\x77\xb4\x68\x70\x6c\x29\x11\x72\x6d\xc0\x19\xc6\x1f\x49\xad\x80\x94\xa0\x27\x12\x15\x93\xc9\x3e\x52\xd6\x31\xc5\xd1\x46\x9d\x8e\xad\x6c\x76\x01\x3f\x32\xbb\xb1\x0e\xcb\xec\x3e\x48\x97\x15\x49\x47\xde\x8a\x33\x15\x06\x91\x41\x26\xb4\x92\x9b\xec\x02\x72\x26\x6d\x14\x5a\x6d\x5c\x76\x01\xe7\xe7\xe7\xe7\xb5\xb6\xa5\xf4\x5e\x79\x0f\x13\x1f\x1f\xf4\x72\xd1\xf7\x13\x20\xe3\xba\x2c\x51\xb9\x91\xaf\xd1\x93\xec\x08\xe0\x35\xe8\xe4\x5a\x56\xa5\x0a\x24\x03\x30\xea\x4e\xb4\x93\xa8\x55\x7a\x02\x9b\x75\x90\x7d\xf9\xdc\xc9\xda\x23\x4d\x85\x89\xf5\xcb\xca\xe9\x53\x52\xdc\xa0\x97\xc0\xda\x50\xc9\xcc\x06\x1e\x71\x93\x85\xdd\xaf\x27\xd3\x76\x5b\x8f\x16\x53\x0c\xac\x33\xa4\x56\x13\x2c\x3a\x47\xb7\xf0\xf9\xa6\xe8\x67\x85\x10\x15\x00\x09\x54\x8e\x72\x42\x13\x83\x5b\xe0\x44\x30\x13\x33\xa8\x56\xae\xc8\x2e\xe0\xff\xff\x6b\x65\xaa\x92\xb2\x0e\x4c\x1b\xba\xb0\x50\x05\x4b\x75\x94\x77\xfa\xea\x23\x78\x90\x97\x3e\xa4\x5b\xfc\xfb\x8a\x39\x1a\x54\x1c\xc1\xe9\xe0\xcf\x83\x5e\x82\x2b\xc8\x26\x8e\x2d\x51\x6a\xb5\xb2\xe0\xf4\x4c\xd7\x48\x09\x7c\xd9\xc7\x33\xeb\x98\xab\xec\xd8\x33\x54\x55\x39\xe1\xd7\xed\x60\x7b\xe2\xd7\xf5\x28\x2c\x30\x54\xae\x9b\x8f\xf7\x47\x2d\x01\xc8\x7e\x56\x58\xa1\xc8\x4e\xe0\xe3\xc7\xe4\x23\x20\x0b\x71\x01\x98\x12\xf0\xcc\xc8\xf9\x64\x70\xda\xab\x34\xae\x43\x93\x22\x47\x4c\xd2\x5f\x21\x0c\x23\x1d\x4b\x8c\x85\xa0\xde\x84\xa2\x43\x9a\x4a\xa9\x69\x10\xaf\x8c\x41\xe5\xe4\x06\x48\xc1\xda\xe8\x95\x41\x6b\x3b\x20\xd7\xe5\x5a\xa2\x1b\x73\xce\x49\x91\x2d\x50\x80\xad\x38\x47\x6b\xf3\x4a\xca\x4d\x87\xcb\x19\xc9\x09\x50\x90\xc2\x33\xb9\x02\xd0\x18\x6d\x52\x4b\xfe\x10\xe5\x04\xe8\x99\x59\x68\x17\x61\xb9\x81\xca\xa2\xf9\x18\x4b\x58\x07\x77\x54\xa2\xae\xdc\x10\xec\xc5\x02\x74\x95\x9c\xe2\x23\x05\x2b\x63\x23\xb9\x36\x1c\x83\x1f\xe0\xd0\x94\xa4\x98\x6b\xcf\xf0\xbe\x0d\xab\xc0\x9c\x55\x32\x24\x41\x13\xcd\xf7\x4b\x50\x67\x68\xb5\x42\xb3\xe0\xcc\xe1\x4a\x9b\xcd\xde\xa9\x7a\x17\x81\x70\x35\x02\x26\x49\xfb\x3b\xad\x8a\x53\x89\x4f\x28\xa1\x36\x04\x63\x43\x53\x69\x5b\x32\x55\x31\x19\xcf\xf6\x9b\x45\x03\x51\xe0\x0f\x2a\xea\x49\x73\xcd\xf2\x02\x45\xd5\x06\xf2\x8e\x4a\x3c\x5d\x32\xeb\x53\x25\xae\xf8\x2c\x3d\xe6\x46\xab\x13\x20\xe5\xd0\x3c\x31\xf9\xa1\x83\xe3\x93\x67\x1b\xc3\xe8\xff\x3c\x15\x86\x9e\x50\x35\x96\x92\x8c\x61\x6b\x6a\xc2\xed\xd0\x28\x26\xe1\xf2\xe6\x0b\x70\x26\x65\xb2\xa9\xbe\xea\xc2\xbe\xdb\xf0\xf7\x69\xfc\x42\x1c\x8a\x09\x9d\x02\xd7\xa8\x04\x2a\xbe\x09\x09\xf2\xb9\xfd\xb7\x76\x61\x80\xb8\xff\x05\xb1\xb7\xba\x32\x69\x95\x7f\xb3\xfc\x36\xb1\xbf\x1d\x00\x93\xc8\xdf\xae\x91\x53\x4e\xbc\x8d\x7b\x34\x92\x5e\x38\xc7\xf8\xe2\x50\x59\x5a\x4a\xfc\x30\x51\x87\x3f\xfd\xf7\xb7\x29\x5f\x9b\x96\x61\x66\x9a\x6b\xe5\xf0\xc5\x8d\x7d\x7d\xb0\x6d\xef\x30\x99\xe5\x43\x5c\x7a\xc3\x0b\x41\x3e\x83\x59\x92\xe4\x71\x7b\xa8\xae\x25\x3a\x26\x98\x63\x93\x5f\xed\xdb\x17\x48\x93\xdb\x0b\x36\xc1\xda\x17\x1b\xeb\x58\xb9\x9e\xba\x4b\x1a\x24\x5c\x4e\xf3\xfe\x5e\xf8\x24\xef\xdf\x8c\xbe\x2a\xb5\x26\xfd\x85\x60\x2a\x05\xc7\xbe\x35\xe8\xa4\x0f\x7a\x69\x3f\x6c\xf5\xe6\xf0\xd8\x3c\x6b\xf3\x88\xe6\xb0\x06\xe0\x7b\xc0\x6c\xeb\x01\xea\xd5\xf6\xa2\x2c\x98\x12\xa1\x08\xf4\xbd\xfd\xd5\x49\xb7\x36\xda\x5f\x54\x87\x79\x76\x13\x41\xdb\x5c\xeb\x96\x43\xc3\x56\x20\x7b\xda\x3c\x23\xad\x0a\xd7\xf9\x65\xf7\xee\x6a\xe6\xf9\x65\xd0\x99\xcd\x82\x39\x87\xe5\x7a\x22\x29\x7d\x89\x5d\xa1\x99\xf0\xed\xab\x07\xc2\xe5\x10\xd8\xcb\x49\xe2\x05\x04\x03\x50\x1b\x88\x41\x23\x0b\xc7\x67\xc1\xe5\x9c\x8c\x75\xcd\x62\x92\x87\xdd\x35\x79\xb6\xb5\x46\xee\x0e\x17\xf3\x7d\xc9\x62\x66\xe3\x7d\x13\xd0\xf0\x66\xff\xdd\xeb\x4f\xa3\xc9\xb4\xe5\xce\x5b\x77\x59\x3c\x86\x5f\x1c\xcb\xd0\xf0\xcd\xa9\x2e\x11\xb7\xad\xb6\xdc\x35\x50\x78\xf6\x55\xa6\x73\xb0\xb6\xf7\x8e\xe5\xc3\x5f\x97\x87\xf3\xbf\xf6\xa8\x83\xd9\x07\x5b\xef\xc8\xbd\xee\x21\x17\x16\xb9\x56\x62\x62\x4e\xd8\xfe\x2d\xdd\x45\x28\xdc\x0e\xa1\xe9\xcd\xc4\x9d\x1f\xcf\x6b\x2b\xbe\x8d\x15\xf5\x98\xd7\x2b\xfa\xc7\xa4\x0a\x34\xe4\xc3\x99\x1b\x5d\x86\x89\x49\x1b\xd0\x4f\x68\x0c\x09\x81\x6a\x7b\xb5\xdf\xe9\x9d\xa8\x0c\xeb\xd7\xd9\xb7\xdd\xfa\x3c\xc2\x4c\xce\x40\x8d\x6a\x3f\x43\x94\x24\x25\x8d\x4e\xf0\x10\xa2\xcd\x14\x72\x08\xd1\x9b\x11\x66\x92\x68\xa3\x1a\xd6\x68\x38\x2a\xc7\x56\x08\xc7\x67\xa7\x9f\xce\xce\xde\xb3\x74\x71\xad\x72\x5a\x2d\xac\x62\x6b\x5b\xe8\xfd\xdb\x9b\xab\x80\x83\xdb\x11\x2e\xed\xe4\xea\x45\xd0\x79\xc8\x8c\x68\xab\x39\x7f\x96\x56\x2f\x9f\x68\xf3\x22\x60\xd0\xfa\x03\xd8\x97\xf7\xd7\xc1\xf6\xc9\xa3\x8f\x3a\x61\x7e\xef\x15\x06\xc6\x05\xa9\x5c\xef\x4d\xec\xda\x43\xe0\x4b\x0f\x92\x9e\xa5\x33\x15\x77\x95\x41\x11\xa7\x51\xf0\xca\x4d\xc9\x9a\xda\x8f\x83\xc1\x75\x66\xcf\xe8\x18\x7f\x5c\x38\xc3\xa6\x9a\xfa\x7e\x1b\x9b\xd6\x73\xfe\x08\x77\x7d\x4c\x8f\xb9\x5f\x0f\x3a\x7b\xa4\x07\x05\xd2\xd3\x9e\xf9\x11\x96\xe8\x0c\xf1\x89\x6f\x70\xcb\x51\xff\x39\xdc\x3f\x99\x04\xb5\x56\x90\xf4\x88\x50\x62\xa9\x8d\x1f\xe7\xd9\x0a\x4f\xe0\xea\xe6\x5b\xc8\xd8\x13\x40\xc7\xff\x33\x8f\xf4\xa1\xf3\xc4\x9e\x73\x44\x77\xa0\x33\x26\x89\xa3\x7a\x4a\x8c\xd7\x10\xee\x7c\xdb\x7c\x49\xda\x9d\x07\xbd\x5c\x8c\xae\xe7\xe4\x7d\xb4\x7d\xa6\xeb\x75\x0d\xf7\x53\xae\x5c\xe9\x72\xad\x2d\x39\x9f\x2b\x02\x5f\xc2\xa5\xf3\xd0\x7b\x50\x2e\xc8\x3a\x1f\x8b\x9f\x15\x1a\x42\xbb\xfb\x2d\xb4\xcf\x33\x3e\x7b\xed\xa6\xda\x3c\x8d\xcd\xa3\x1a\xd1\xf5\x00\x5e\x33\x8c\x6f\x48\x3e\x5f\x40\x1b\x81\xa1\x0b\x3c\x80\x74\x3d\xe4\xec\x24\x9d\x0c\x42\xf3\x78\x47\x05\xe3\x53\x3e\x84\x68\xfb\x28\xb0\x8b\xe9\xf8\xd5\x68\x1e\xe1\xe1\xa3\x10\x30\xc5\xe4\xc6\xd2\x41\xf9\xb0\xed\x19\x63\x8a\x70\xb3\xe7\xdf\xd1\xad\xdf\x32\xe6\x90\xad\x87\x8c\x41\x6b\x9f\x52\x9d\x1c\x43\x86\x43\xd7\xde\x94\xe3\x28\xc5\x0b\x46\xaa\xfd\xe9\x26\x1b\xd4\x89\xf6\x01\xae\xfe\x61\x65\x8d\xa6\x24\x6b\xa3\xac\x6b\x6c\xbb\x7e\xda\x76\xf5\xe6\xf5\xe8\xf5\xe8\x9f\x00\x00\x00\xff\xff\xb1\xb1\x44\xbb\x72\x1a\x00\x00") +var _yaoModelsJobExecutionModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x59\x5b\x4f\x1c\xb9\x12\x7e\xe7\x57\x94\xfa\x89\x48\x70\x42\x8e\x8e\x8e\x0e\x48\xe7\x01\x91\x48\x9b\x95\x56\x8b\x02\x51\x1e\x22\x34\xf2\xb8\xab\xbb\x0b\xdc\x76\xc7\x17\x60\x76\xc5\x7f\x5f\xd9\xee\x7b\xf7\x0c\x33\x1d\xf2\x06\xb6\xbf\xaa\xfa\x5c\xd5\x75\xf1\xfc\x7d\x04\x90\x48\x56\x62\x72\x01\x09\x3e\x23\x77\x96\x94\x4c\x4e\xfc\xb2\x60\x6b\x14\x7e\xfd\x77\xb5\x86\x4f\xc3\xbd\x14\x0d\xd7\x54\x85\x85\xfa\x44\x8b\x06\xcb\xd6\x02\x21\x53\x1a\xac\x66\xfc\x81\x64\x0e\x24\x53\x7a\xa4\xd4\x31\xd1\x3b\x47\xd2\x58\x26\x39\x9a\x28\xd3\xb2\xdc\x24\x17\xf0\x3d\x31\x1b\x63\xb1\x4c\xee\xc2\xea\xda\x91\xb0\xe4\xb5\x58\xed\x30\x2c\x69\x64\xa9\x92\x62\x93\x5c\x40\xc6\x84\x89\x8b\x46\x69\x9b\x5c\xc0\xf9\xf9\xf9\x79\x2d\x6d\x2d\x3c\x2b\xcf\xb0\xc7\xf1\x5e\xad\x57\x43\x9e\x00\x09\x57\x65\x89\xd2\x4e\xb8\x46\x26\xc9\x11\xc0\x4b\x90\xc9\x95\x70\xa5\x0c\x46\x06\x60\x94\xdd\x93\x4e\x69\x2d\xd2\x1b\xb0\xa9\xc2\xda\xe7\x8f\xdd\x5a\x7b\xa5\xfd\xc5\x9e\xf6\x4b\x67\xd5\x29\x49\xae\xd1\xaf\x40\xa5\xa9\x64\x7a\x03\x0f\xb8\x49\xc2\xe9\x97\x93\x79\xbd\x2d\xa3\xd5\x9c\x05\xc6\x6a\x92\xf9\x8c\x15\x1d\xd1\x2d\xf6\x7c\x95\xf4\xc3\x21\x44\x01\x40\x29\x4a\x4b\x19\xa1\x8e\xce\x2d\x70\xc6\x99\x3d\x35\x28\x73\x5b\x24\x17\xf0\xdf\xff\xb4\x6b\xd2\x09\x51\x3b\xa6\x75\x5d\xd8\x70\x41\x53\xed\xe5\x9d\x5c\xbd\x07\x0f\x62\xe9\x5d\xba\x85\xdf\x17\xcc\x50\xa3\xe4\x08\x56\x05\x3e\xf7\x6a\x0d\xb6\x20\xd3\x23\xb6\x46\xa1\x64\x6e\xc0\xaa\x85\xd4\x48\xa6\xf8\xbc\x0f\x33\x63\x99\x75\x66\xca\x0c\xa5\x2b\x67\x78\xdd\x8c\x8e\xf7\x78\x7d\x9a\xb8\x05\xc6\xc2\x55\xf3\xf1\x7e\xaf\x57\x00\x92\x1f\x0e\x1d\xa6\xc9\x09\xbc\x7f\xdf\xfb\x08\xc8\x40\xdc\x00\x26\x53\x78\x62\x64\x7d\x30\x58\xe5\x45\x6a\xdb\xa1\x49\x92\x25\x26\xe8\xaf\xe0\x86\x89\x8c\x35\xc6\x44\x50\x1f\xc2\xb4\x43\x6a\x27\xe5\x3c\x88\x3b\xad\x51\x5a\xb1\x01\x92\x50\x69\x95\x6b\x34\xa6\x03\x72\x55\x56\x02\xed\xd4\xe6\x8c\x24\x99\x02\x53\x30\x8e\x73\x34\x26\x73\x42\x6c\x3a\x5c\xc6\x48\xcc\x80\xc2\x2a\x3c\x91\x2d\x00\xb5\x56\xba\xaf\xc9\x5f\xa2\x98\x01\x3d\x31\x03\xed\x26\xac\x37\xe0\x0c\xea\xf7\x31\x85\x75\x70\x4b\x25\x2a\x67\xc7\x60\xbf\x9c\x82\x72\xbd\x5b\x7c\xa0\xa0\x65\xaa\x24\x53\x9a\x63\xe0\x01\x16\x75\x49\x92\xd9\xf6\x0e\xef\x5a\xb7\xa6\x98\x31\x27\x42\x10\x34\xde\x7c\xbb\x00\xb5\x9a\xf2\x1c\xf5\x8a\x33\x8b\xb9\xd2\x9b\xbd\x43\xf5\x36\x02\xe1\x6a\x02\xec\x05\xed\x6f\x94\x17\xa7\x02\x1f\x51\x40\xad\x08\xa6\x8a\xe6\xc2\xb6\x64\xd2\x31\x11\xef\xf6\xab\x41\x0d\x71\xc1\x5f\x54\x94\xd3\x8f\x35\xc3\x0b\x4c\x5d\xeb\xc8\x5b\x2a\xf1\x74\xcd\x8c\x0f\x95\xb8\xe3\xa3\xf4\x98\x6b\x25\x4f\x80\xa4\x45\xfd\xc8\xc4\xbb\x0e\x8e\x8f\xde\xda\xe8\x46\xff\xe7\x69\xaa\xe9\x11\x65\xa3\xa9\x17\x31\xac\xa2\xc6\xdd\x16\xb5\x64\x02\x2e\xaf\x3f\x03\x67\x42\xf4\x0e\xd5\xa5\x2e\x9c\xbb\x09\x7f\x9f\xc6\x2f\xc4\x62\x3a\x23\x33\xc5\x0a\x65\x8a\x92\x6f\x42\x80\x7c\x6c\xff\xad\x29\x8c\x10\x77\xbf\xc0\xf7\x46\x39\xdd\xcf\xf2\xaf\xa6\xdf\xc6\xf7\x37\x23\x60\xcf\xf3\x37\x15\x72\xca\x88\xb7\x7e\x8f\x4a\xfa\x05\xe7\x18\x9f\x2d\x4a\x43\x6b\x81\xef\x66\xf2\xf0\x87\x7f\xff\x6f\x8e\x6b\xd3\x32\x2c\x0c\x73\x25\x2d\x3e\xdb\x29\xd7\x7b\xd3\xf6\x0e\xb3\x51\x3e\xc6\xf5\x2b\x7c\x9a\x92\x8f\x60\xd6\x0b\xf2\x78\x3c\x64\xd7\x12\x2d\x4b\x99\x65\xb3\x5f\xed\xeb\x05\xa4\x89\xed\x15\x9b\xb1\xda\x27\x1b\x63\x59\x59\xcd\xd5\x92\x06\x09\x97\xf3\x76\x7f\x2b\x7c\x90\x0f\x2b\xa3\xcf\x4a\xad\x4a\x5f\x10\xb4\x93\x70\xec\x5b\x83\x6e\xf5\x5e\xad\xcd\xbb\xad\x6c\x0e\xf7\xcd\x93\xd2\x0f\xa8\x0f\x6b\x00\xbe\x05\xcc\xb6\x1e\xa0\xde\x6d\x0b\x65\xc1\x64\x1a\x92\xc0\x90\xed\xaf\x0e\xba\x4a\x2b\x5f\xa8\x0e\x63\x76\x1d\x41\xdb\xa8\x75\xdb\xa1\x61\x2b\x90\x3d\x6e\x9e\x90\xf2\xc2\x76\xbc\xcc\xde\x5d\xcd\x32\x5e\x1a\xad\xde\xac\x98\xb5\x58\x56\x33\x41\xe9\x53\x6c\x8e\x7a\x86\xdb\x17\x0f\x84\xcb\x31\x70\x10\x93\xc4\x0b\x08\x0a\xa0\x56\x10\x9d\x46\x06\x8e\xcf\x02\xe5\x8c\xb4\xb1\xcd\x66\x2f\x0e\xbb\x32\x79\xb6\x35\x47\xee\x76\x17\xf3\x7d\xc9\x6a\x61\xe3\x7d\x1d\xd0\xf0\x6a\xff\x3d\xe8\x4f\xa3\xca\x7e\xcb\x9d\xb5\x74\x59\xbc\x86\x5f\xec\xcb\xd0\xf0\x2d\xc9\x2e\x11\xb7\x2d\xb7\xdc\x36\x50\x78\xf2\x59\xa6\x23\x58\xeb\x7b\xc3\xf4\xe1\xcb\xe5\xe1\xf6\x7f\xf2\xa8\x83\xad\x0f\xba\xde\xd0\xf6\xba\x87\x5c\x19\xe4\x4a\xa6\x33\x73\xc2\xf6\x6f\xe9\x36\x42\xe1\x66\x0c\xed\x57\x26\x6e\xfd\x78\x5e\x6b\xf1\x6d\x6c\x5a\x8f\x79\x83\xa4\x7f\x4c\xb2\x40\x4d\xde\x9d\x99\x56\x65\x98\x98\x94\x06\xf5\x88\x5a\x53\x9a\xa2\xdc\x9e\xed\x77\xb2\x4b\x9d\x66\xc3\x3c\xfb\x3a\xad\x8f\x13\xcc\xec\x0c\xd4\x88\xf6\x33\x44\x49\x42\xd0\xe4\x06\x0f\x31\xb4\xd2\xa4\x34\xd9\x99\xe6\x77\xbb\xa1\xd7\x13\xcc\xac\xa1\x8d\x68\x38\x2e\x28\x2f\x50\x83\x74\xe5\x1a\x35\xfc\x1f\xea\xff\x9b\x03\x07\x66\xb2\x45\xd5\x28\x8c\x5a\x87\x91\x1c\x63\xb6\x90\x8c\xc7\xa0\x42\xcd\x51\x5a\x96\x23\x1c\x9f\x9d\x7e\x38\x3b\x7b\xcb\xfc\xdc\x25\xe6\x38\x31\xcc\x30\xd9\xd2\xc5\x75\x96\xfe\x39\x46\xce\xf2\xa9\xe5\x03\x49\x2e\x5c\xea\x1b\x87\xd6\x8d\xbe\xa3\x33\x05\xd3\x98\xc2\xf2\xa6\x8e\x2b\x99\x51\xbe\x32\x92\x55\xa6\x50\xfb\x77\xa3\x57\x01\x07\x37\x13\x5c\xbf\xf1\xae\x37\x41\x65\xe1\x43\x8e\xba\x9a\xcf\x85\xf5\x8b\x8d\xcf\x0b\xcb\x08\x68\x34\xde\x95\xfb\xda\xfd\x65\x74\x7c\xf6\xd2\xa3\xcc\x9f\xb8\xd5\x30\xdf\xaf\x48\x66\x6a\xff\xc0\xf0\x10\xf8\x3c\x80\xf4\xef\xd2\x6a\xc7\xad\xf3\xce\x0e\xc2\xc1\x0b\xd7\x25\x6b\x4a\x35\x8e\xde\x19\x16\xb6\xf8\x96\xf1\x87\x95\xd5\x6c\x6e\x06\x1b\x4e\x1d\xfd\xf2\xcb\x1f\xe0\x76\x88\x19\x58\xee\xf7\x83\xcc\x81\xd1\xa3\x7a\xe6\xcd\x5e\x98\x33\x4b\xb4\x9a\xf8\xfe\xdf\xe0\x1f\xe3\xf3\xb3\x41\x50\x4b\x05\x41\x0f\x08\x25\x96\x4a\x6f\xc0\x19\x96\xe3\x09\x5c\x5d\x7f\x0d\x11\x7b\x02\x68\xf9\xbf\x16\x7f\x78\x07\x8d\x7f\x7b\x8e\x7d\xdd\x85\x2e\x18\xfc\x8e\xea\xa1\x3e\x66\x72\xdc\xf9\x14\xfd\xdc\xeb\x4e\xef\xd5\x7a\x35\xe9\xa6\x7a\xcf\xd9\xed\xab\xea\xa0\xc9\xbb\x9b\xa3\x72\xa5\xca\x4a\x19\xb2\x3e\x56\x52\x7c\x0e\x3d\xc2\xfd\xe0\xfd\xbf\x20\x63\xbd\x2f\x7e\x38\xd4\x84\x66\xf7\xd3\xf5\xd0\xce\xf8\x4a\xb9\xdb\xd4\xe6\x25\x73\x99\xa9\x11\x5d\xbf\x97\xd4\x16\xc6\x27\x3f\x1f\x2f\xa0\x74\x8a\xa1\x69\x3f\xc0\xe8\x7a\x26\xdd\x69\x74\x6f\x6e\x5d\x66\x77\x14\x30\xbd\xe5\x43\x0c\x6d\xdf\x70\x76\x59\x3a\x7d\xe4\x5b\x66\xf0\xf8\x0d\x0f\x98\x64\x62\x63\xe8\xa0\x78\xd8\xf6\xea\x34\x67\x70\x73\xe6\xe7\xcc\xad\x9f\x9e\x96\x18\x5b\xcf\x84\xa3\x49\xac\x6f\xea\xec\xd4\x38\x9e\x91\xf7\x36\x39\x4e\xbe\xbc\x60\x24\xdb\x5f\xda\x92\x51\x9e\x68\xdf\x4b\xeb\xdf\xc1\x2a\xd4\x25\x19\x13\xd7\xba\x39\xa4\x1b\x7f\x4c\x97\x6f\x5e\x8e\x5e\x8e\xfe\x09\x00\x00\xff\xff\x77\x3a\x68\x76\x21\x1c\x00\x00") func yaoModelsJobExecutionModYaoBytes() ([]byte, error) { return bindataRead( @@ -2676,7 +2676,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 6770, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2696,7 +2696,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6132, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6132, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2716,7 +2716,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2736,7 +2736,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5052, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5052, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2756,7 +2756,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9549, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9549, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2776,7 +2776,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 10702, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 10702, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2796,7 +2796,7 @@ func yaoModelsUser_oauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user_oauth_account.mod.yao", size: 6799, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/user_oauth_account.mod.yao", size: 6799, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2816,7 +2816,7 @@ func yaoModelsUser_roleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user_role.mod.yao", size: 6219, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/user_role.mod.yao", size: 6219, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2836,7 +2836,7 @@ func yaoModelsUser_typeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user_type.mod.yao", size: 4672, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/models/user_type.mod.yao", size: 4672, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2856,7 +2856,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2876,7 +2876,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2896,7 +2896,7 @@ func yaoStoresAgentMemoryBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2916,7 +2916,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2936,7 +2936,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2956,7 +2956,7 @@ func yaoStoresKbStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2976,7 +2976,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2996,7 +2996,7 @@ func yaoStoresOauthClientBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3016,7 +3016,7 @@ func yaoStoresOauthStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3036,7 +3036,7 @@ func yaoStoresStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1756611751, 0)} + info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3056,7 +3056,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(1756611751, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1756699565, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/job/data.go b/job/data.go index a8441c77..8077a5c7 100644 --- a/job/data.go +++ b/job/data.go @@ -103,6 +103,14 @@ func SaveJob(job *Job) error { return fmt.Errorf("job model not found") } + // Ensure category exists before saving job + if job.CategoryID != "" { + _, err := ensureCategoryExists(job.CategoryID) + if err != nil { + return fmt.Errorf("failed to ensure category exists: %w", err) + } + } + data := structToMap(job) now := time.Now() @@ -377,6 +385,64 @@ func GetOrCreateCategory(name, description string) (*Category, error) { return category, nil } +// ensureCategoryExists ensures a category exists by category_id, creates default if needed +func ensureCategoryExists(categoryID string) (*Category, error) { + mod := model.Select("__yao.job.category") + if mod == nil { + return nil, fmt.Errorf("job category model not found") + } + + // Try to find existing category by category_id + param := model.QueryParam{ + Wheres: []model.QueryWhere{ + {Column: "category_id", Value: categoryID}, + }, + Limit: 1, + } + + results, err := mod.Get(param) + if err != nil { + return nil, err + } + + if len(results) > 0 { + // Category exists + category := &Category{} + if err := mapToStruct(results[0], category); err != nil { + return nil, err + } + return category, nil + } + + // Create default category if it doesn't exist + var categoryName, categoryDesc string + if categoryID == "default" { + categoryName = "Default" + categoryDesc = "Default job category" + } else { + categoryName = categoryID + categoryDesc = fmt.Sprintf("Auto-created category: %s", categoryID) + } + + category := &Category{ + CategoryID: categoryID, + Name: categoryName, + Description: &categoryDesc, + Sort: 0, + System: categoryID == "default", // Mark default as system category + Enabled: true, + Readonly: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if err := SaveCategory(category); err != nil { + return nil, err + } + + return category, nil +} + // ======================== // Logs methods // ======================== @@ -481,6 +547,15 @@ func GetExecutions(jobID string) ([]*Execution, error) { if err := mapToStruct(result, execution); err != nil { continue } + + // Restore ExecutionConfig from ConfigSnapshot if available + if execution.ConfigSnapshot != nil && len(*execution.ConfigSnapshot) > 0 { + var config ExecutionConfig + if err := jsoniter.Unmarshal(*execution.ConfigSnapshot, &config); err == nil { + execution.ExecutionConfig = &config + } + } + executions = append(executions, execution) } @@ -576,6 +651,14 @@ func GetExecution(executionID string, param model.QueryParam) (*Execution, error return nil, err } + // Restore ExecutionConfig from ConfigSnapshot if available + if execution.ConfigSnapshot != nil && len(*execution.ConfigSnapshot) > 0 { + var config ExecutionConfig + if err := jsoniter.Unmarshal(*execution.ConfigSnapshot, &config); err == nil { + execution.ExecutionConfig = &config + } + } + return execution, nil } @@ -626,6 +709,11 @@ func SaveExecution(execution *Execution) error { } } + // Update related Job information after execution changes + if err := updateJobProgress(execution.JobID); err != nil { + return fmt.Errorf("failed to update job progress: %w", err) + } + return nil } @@ -723,3 +811,85 @@ func mapToStruct(m maps.MapStr, v interface{}) error { } return jsoniter.Unmarshal(data, v) } + +// updateJobProgress updates job progress and status based on its executions +func updateJobProgress(jobID string) error { + // Skip if jobID is empty + if jobID == "" { + return nil + } + + // Get all executions for this job + executions, err := GetExecutions(jobID) + if err != nil { + return fmt.Errorf("failed to get executions for job %s: %w", jobID, err) + } + + if len(executions) == 0 { + return nil // No executions to process + } + + // Calculate overall job progress and status + totalExecutions := len(executions) + completedCount := 0 + failedCount := 0 + runningCount := 0 + totalProgress := 0 + + for _, execution := range executions { + totalProgress += execution.Progress + + switch execution.Status { + case "completed": + completedCount++ + case "failed": + failedCount++ + case "running": + runningCount++ + } + } + + // Calculate average progress + averageProgress := totalProgress / totalExecutions + + // Determine job status + var jobStatus string + if completedCount == totalExecutions { + jobStatus = "completed" + } else if failedCount > 0 && runningCount == 0 && completedCount+failedCount == totalExecutions { + jobStatus = "failed" + } else if runningCount > 0 || completedCount > 0 { + jobStatus = "running" + } else { + jobStatus = "ready" // All executions are queued + } + + // Update job in database + jobMod := model.Select("__yao.job") + if jobMod == nil { + return fmt.Errorf("job model not found") + } + + updateData := map[string]interface{}{ + "status": jobStatus, + "updated_at": time.Now(), + } + + // Add progress field if Job model supports it + // Note: This assumes Job model has a progress field, you may need to add it to the schema + updateData["progress"] = averageProgress + + param := model.QueryParam{ + Wheres: []model.QueryWhere{ + {Column: "job_id", Value: jobID}, + }, + Limit: 1, + } + + _, err = jobMod.UpdateWhere(param, updateData) + if err != nil { + return fmt.Errorf("failed to update job progress: %w", err) + } + + return nil +} diff --git a/job/data_test.go b/job/data_test.go index 93894840..7cc02ccc 100644 --- a/job/data_test.go +++ b/job/data_test.go @@ -22,9 +22,10 @@ func TestJobCRUD(t *testing.T) { t.Fatalf("Failed to create test category: %v", err) } - // Test job creation + // Test job creation with unique ID + timestamp := time.Now().UnixNano() testJob := &job.Job{ - JobID: "test-job-crud-001", + JobID: fmt.Sprintf("test-job-crud-%d", timestamp), Name: "Test CRUD Job", CategoryID: category.CategoryID, Status: "draft", @@ -40,6 +41,12 @@ func TestJobCRUD(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), } + + // Ensure cleanup even if test fails + defer func() { + job.RemoveJobs([]string{testJob.JobID}) + job.RemoveCategories([]string{category.CategoryID}) + }() // Test SaveJob (Create) err = job.SaveJob(testJob) @@ -136,9 +143,10 @@ func TestCategoryCRUD(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - // Test category creation + // Test category creation with unique ID + timestamp := time.Now().UnixNano() testCategory := &job.Category{ - CategoryID: "test-category-crud-001", + CategoryID: fmt.Sprintf("test-category-crud-%d", timestamp), Name: "Test CRUD Category", Description: stringPtr("Test category for CRUD operations"), Sort: 1, @@ -148,6 +156,11 @@ func TestCategoryCRUD(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), } + + // Ensure cleanup even if test fails + defer func() { + job.RemoveCategories([]string{testCategory.CategoryID}) + }() // Test SaveCategory (Create) err := job.SaveCategory(testCategory) @@ -249,9 +262,10 @@ func TestExecutionCRUD(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - // Create test job first + // Create test job first with unique ID + timestamp := time.Now().UnixNano() testJob := &job.Job{ - JobID: "test-execution-job-001", + JobID: fmt.Sprintf("test-execution-job-%d", timestamp), Name: "Test Execution Job", CategoryID: "default", Status: "ready", @@ -268,9 +282,15 @@ func TestExecutionCRUD(t *testing.T) { t.Fatalf("Failed to create test job: %v", err) } - // Test execution creation + // Ensure cleanup even if test fails + defer func() { + job.RemoveJobs([]string{testJob.JobID}) + }() + + // Test execution creation with unique ID + executionTimestamp := time.Now().UnixNano() + 1 // Ensure different from job timestamp testExecution := &job.Execution{ - ExecutionID: "test-execution-crud-001", + ExecutionID: fmt.Sprintf("test-execution-crud-%d", executionTimestamp), JobID: testJob.JobID, Status: "queued", TriggerCategory: "manual", @@ -359,9 +379,10 @@ func TestLogCRUD(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - // Create test job first + // Create test job first with unique ID + timestamp := time.Now().UnixNano() testJob := &job.Job{ - JobID: "test-log-job-001", + JobID: fmt.Sprintf("test-log-job-%d", timestamp), Name: "Test Log Job", CategoryID: "default", Status: "ready", @@ -377,6 +398,11 @@ func TestLogCRUD(t *testing.T) { if err != nil { t.Fatalf("Failed to create test job: %v", err) } + + // Ensure cleanup even if test fails + defer func() { + job.RemoveJobs([]string{testJob.JobID}) + }() // Test log creation testLog := &job.Log{ diff --git a/job/execution.go b/job/execution.go index e44e13d1..fd75c488 100644 --- a/job/execution.go +++ b/job/execution.go @@ -1,70 +1,70 @@ package job import ( + "encoding/json" "fmt" - "sync" "time" + jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/model" "github.com/yaoapp/kun/log" ) -// handlerRegistry stores registered handlers for jobs -var handlerRegistry = make(map[string]HandlerFunc) -var handlerRegistryMutex sync.RWMutex - -// SetHandler sets a handler for a job ID (for testing) -func SetHandler(jobID string, handler HandlerFunc) { - handlerRegistryMutex.Lock() - defer handlerRegistryMutex.Unlock() - handlerRegistry[jobID] = handler +// Add adds a new execution with Yao process (default execution type) +func (j *Job) Add(options *ExecutionOptions, processName string, args ...interface{}) error { + return j.addExecution(options, &ExecutionConfig{ + Type: ExecutionTypeProcess, + ProcessName: processName, + ProcessArgs: args, + }) } -// getHandler gets a handler for a job ID (thread-safe) -func getHandler(jobID string) (HandlerFunc, bool) { - handlerRegistryMutex.RLock() - defer handlerRegistryMutex.RUnlock() - handler, exists := handlerRegistry[jobID] - return handler, exists +// AddCommand adds a new execution with system command +func (j *Job) AddCommand(options *ExecutionOptions, command string, args []string, env map[string]string) error { + return j.addExecution(options, &ExecutionConfig{ + Type: ExecutionTypeCommand, + Command: command, + CommandArgs: args, + Environment: env, + }) } -// Add add a new execution to the job with handler -func (j *Job) Add(priority int, handler HandlerFunc) error { - // Set job priority - j.Priority = priority - - // Auto-create or get category if not set - if j.CategoryID == "" { - category, err := GetOrCreateCategory("default", "Default job category") - if err != nil { - log.Warn("Failed to create default category: %v", err) - j.CategoryID = "default" - } else { - j.CategoryID = category.CategoryID +// addExecution is the internal method to create execution records +func (j *Job) addExecution(options *ExecutionOptions, config *ExecutionConfig) error { + // Set default options if nil + if options == nil { + options = &ExecutionOptions{ + Priority: 0, + SharedData: make(map[string]interface{}), } } - // Set default values - if j.Status == "" { - j.Status = "draft" - } - if j.MaxWorkerNums == 0 { - j.MaxWorkerNums = 1 - } - if j.CreatedBy == "" { - j.CreatedBy = "system" - } - - // Save job to database first - err := SaveJob(j) + // Serialize ExecutionConfig to JSON for ConfigSnapshot + configBytes, err := jsoniter.Marshal(config) if err != nil { - return err + return fmt.Errorf("failed to serialize execution config: %w", err) + } + configSnapshot := json.RawMessage(configBytes) + + // Create new execution record with options and config + execution := &Execution{ + ExecutionID: "", // Will be generated in SaveExecution + JobID: j.JobID, + Status: "queued", + TriggerCategory: "manual", + RetryAttempt: 0, + Progress: 0, + ExecutionConfig: config, // Keep in memory for runtime use + ConfigSnapshot: &configSnapshot, // Store in database + ExecutionOptions: options, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } - // Store handler in registry using the final JobID (thread-safe) - handlerRegistryMutex.Lock() - handlerRegistry[j.JobID] = handler - handlerRegistryMutex.Unlock() + // Save execution to database + if err := SaveExecution(execution); err != nil { + return fmt.Errorf("failed to create execution record: %w", err) + } return nil } diff --git a/job/goroutine.go b/job/goroutine.go index f60fef3e..2062974d 100644 --- a/job/goroutine.go +++ b/job/goroutine.go @@ -1,4 +1,263 @@ package job +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/model" + "github.com/yaoapp/gou/process" +) + // Goroutine the goroutine mode type Goroutine struct{} + +// ExecuteYaoProcess executes a Yao process using goroutine mode (process API) +func (g *Goroutine) ExecuteYaoProcess(ctx context.Context, work *WorkRequest, progress *Progress) error { + config := work.Execution.ExecutionConfig + + work.Execution.Info("Executing Yao process: %s (goroutine mode)", config.ProcessName) + + // Create process with context + proc := process.NewWithContext(ctx, config.ProcessName, config.ProcessArgs...) + + // Set shared data from ExecutionOptions to process context + if work.Execution.ExecutionOptions != nil && work.Execution.ExecutionOptions.SharedData != nil { + // SharedData itself is the Global context + proc.WithGlobal(work.Execution.ExecutionOptions.SharedData) + + // Check if there's a 'sid' field in SharedData for session context + if sidValue, exists := work.Execution.ExecutionOptions.SharedData["sid"]; exists { + if sid, ok := sidValue.(string); ok { + proc.WithSID(sid) + } + } + } + + // Set callback function to handle real-time progress updates + proc.WithCallback(func(process *process.Process, data map[string]interface{}) error { + if data == nil { + return nil + } + + // Check if this is a progress update + if dataType, ok := data["type"].(string); ok && dataType == "progress" { + // Extract progress and message using helper function + progressInt, message := extractProgressData(data) + + // Update execution progress + if progressInt >= 0 { + work.Execution.Progress = progressInt + } + + // Log progress message + if message != "" { + work.Execution.Info("Progress update: %s (%.1f%%)", message, float64(work.Execution.Progress)) + } + + // Update progress tracker using Set method + if progress != nil && (progressInt >= 0 || message != "") { + progress.Set(progressInt, message) + } + + // Save progress update to database + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save progress update: %s", saveErr.Error()) + return saveErr + } + } + + return nil + }) + + // Execute the process + err := proc.Execute() + + // Always get result and release resources, even if there was an error + result := proc.Value() + proc.Release() + + if err != nil { + work.Execution.Error("Yao process failed: %s", err.Error()) + + // Update execution with error info + work.Execution.Status = "failed" + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution error: %s", saveErr.Error()) + } + + return fmt.Errorf("yao process execution failed: %w", err) + } + + work.Execution.Info("Yao process completed successfully, result: %v", result) + + // Update execution with success result + work.Execution.Status = "completed" + work.Execution.Progress = 100 + if result != nil { + if resultBytes, err := jsoniter.Marshal(result); err == nil { + work.Execution.Result = (*json.RawMessage)(&resultBytes) + } + } + + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution result: %s", saveErr.Error()) + return fmt.Errorf("failed to save execution result: %w", saveErr) + } + + return nil +} + +// ExecuteSystemCommand executes a system command using goroutine mode +func (g *Goroutine) ExecuteSystemCommand(ctx context.Context, work *WorkRequest, progress *Progress) error { + config := work.Execution.ExecutionConfig + + work.Execution.Info("Executing command: %s (goroutine mode)", config.Command) + + // Create command with context for cancellation support + cmd := exec.CommandContext(ctx, config.Command, config.CommandArgs...) + + // Set environment variables if provided + if len(config.Environment) > 0 { + env := os.Environ() + for key, value := range config.Environment { + env = append(env, fmt.Sprintf("%s=%s", key, value)) + } + cmd.Env = env + } + + // Add shared data as environment variables + if work.Execution.ExecutionOptions != nil && work.Execution.ExecutionOptions.SharedData != nil { + env := cmd.Env + if env == nil { + env = os.Environ() + } + for key, value := range work.Execution.ExecutionOptions.SharedData { + env = append(env, fmt.Sprintf("YAO_JOB_SHARED_%s=%v", key, value)) + } + cmd.Env = env + } + + // Execute command with context cancellation support + output, err := cmd.CombinedOutput() + if err != nil { + // Check if it was cancelled + if ctx.Err() != nil { + work.Execution.Warn("Command cancelled: %s", ctx.Err().Error()) + work.Execution.Status = "cancelled" + } else { + work.Execution.Error("Command failed: %s, output: %s", err.Error(), string(output)) + work.Execution.Status = "failed" + + // Store error output + if len(output) > 0 { + errorInfo := map[string]interface{}{ + "error": err.Error(), + "output": string(output), + } + if errorBytes, jsonErr := jsoniter.Marshal(errorInfo); jsonErr == nil { + work.Execution.ErrorInfo = (*json.RawMessage)(&errorBytes) + } + } + } + + // Save execution status + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution error: %s", saveErr.Error()) + } + + if ctx.Err() != nil { + return ctx.Err() + } + return fmt.Errorf("command execution failed: %v", err) + } + + work.Execution.Info("Command completed successfully, output: %s", string(output)) + + // Update execution with success result + work.Execution.Status = "completed" + work.Execution.Progress = 100 + if len(output) > 0 { + result := map[string]interface{}{ + "output": string(output), + } + if resultBytes, err := jsoniter.Marshal(result); err == nil { + work.Execution.Result = (*json.RawMessage)(&resultBytes) + } + } + + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution result: %s", saveErr.Error()) + return fmt.Errorf("failed to save execution result: %w", saveErr) + } + + return nil +} + +// UpdateExecutionProgress updates execution progress from external callback +// This function can be called by system commands via HTTP API to report progress +func UpdateExecutionProgress(executionID string, progressData map[string]interface{}) error { + // Load execution from database + execution, err := GetExecution(executionID, model.QueryParam{}) + if err != nil { + return fmt.Errorf("failed to load execution: %w", err) + } + + if execution == nil { + return fmt.Errorf("execution not found: %s", executionID) + } + + // Extract progress and message from callback data + if progressVal, exists := progressData["progress"]; exists { + if progress, ok := progressVal.(float64); ok { + execution.Progress = int(progress) + } else if progress, ok := progressVal.(int); ok { + execution.Progress = progress + } + } + + if messageVal, exists := progressData["message"]; exists { + if message, ok := messageVal.(string); ok { + execution.Info("Progress update: %s (%.1f%%)", message, float64(execution.Progress)) + } + } + + // Save updated execution to database + if saveErr := SaveExecution(execution); saveErr != nil { + return fmt.Errorf("failed to save progress update: %w", saveErr) + } + + return nil +} + +// extractProgressData extracts progress and message from callback data +func extractProgressData(data map[string]interface{}) (int, string) { + var progressInt int = -1 // Default to -1 to indicate no progress value + var message string + + // Extract progress value with type assertion + if progressVal, exists := data["progress"]; exists { + switch v := progressVal.(type) { + case float64: + progressInt = int(v) + case int: + progressInt = v + case int32: + progressInt = int(v) + case int64: + progressInt = int(v) + } + } + + // Extract message with type assertion + if messageVal, exists := data["message"]; exists { + if msg, ok := messageVal.(string); ok { + message = msg + } + } + + return progressInt, message +} diff --git a/job/job.go b/job/job.go index 4fc5d248..8fe48407 100644 --- a/job/job.go +++ b/job/job.go @@ -1,11 +1,15 @@ package job import ( + "context" "fmt" + "sort" + "strings" "time" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/model" + "github.com/yaoapp/kun/log" ) // Once create a new job @@ -42,17 +46,39 @@ func Daemon(mode ModeType, data map[string]interface{}) (*Job, error) { return makeJob(raw) } -// SetWorkerManager sets a custom worker manager for this job (for testing) -func (j *Job) SetWorkerManager(wm *WorkerManager) { - j.workerManager = wm -} - // Start start the job func (j *Job) Start() error { - // Get handler from registry (thread-safe) - handler, exists := getHandler(j.JobID) - if !exists { - return fmt.Errorf("no handler registered for job %s", j.JobID) + // Get executions for this job + executions, err := j.GetExecutions() + if err != nil { + return fmt.Errorf("failed to get executions: %w", err) + } + + if len(executions) == 0 { + return fmt.Errorf("no executions found for job %s", j.JobID) + } + + // Sort executions by priority (higher priority first) + sort.Slice(executions, func(i, j int) bool { + priorityI := 0 + if executions[i].ExecutionOptions != nil { + priorityI = executions[i].ExecutionOptions.Priority + } + priorityJ := 0 + if executions[j].ExecutionOptions != nil { + priorityJ = executions[j].ExecutionOptions.Priority + } + return priorityI > priorityJ + }) + + // Initialize job context for cancellation + if j.ctx == nil { + j.ctx, j.cancel = context.WithCancel(context.Background()) + } + + // Initialize execution contexts map + if j.executionContexts == nil { + j.executionContexts = make(map[string]context.CancelFunc) } // Update job status to ready @@ -61,32 +87,62 @@ func (j *Job) Start() error { return fmt.Errorf("failed to update job status: %w", err) } - // Submit to worker manager (use custom one if set, otherwise global) - var wm *WorkerManager - if j.workerManager != nil { - wm = j.workerManager - } else { - wm = GetWorkerManager() - // Start worker manager if not already started - if wm.GetActiveWorkers() == 0 { - wm.Start() + // Get global worker manager (should be already started) + wm := GetWorkerManager() + + // Submit executions and ensure all are added successfully + var submitErrors []string + for _, execution := range executions { + // Create execution-specific context derived from job context + execCtx, execCancel := context.WithCancel(j.ctx) + + // Store execution cancel function + j.executionMutex.Lock() + j.executionContexts[execution.ExecutionID] = execCancel + j.executionMutex.Unlock() + + // Submit execution (non-blocking) + if err := wm.SubmitJob(execCtx, j, execution); err != nil { + // Clean up on error + execCancel() + j.executionMutex.Lock() + delete(j.executionContexts, execution.ExecutionID) + j.executionMutex.Unlock() + + submitErrors = append(submitErrors, fmt.Sprintf("execution %s: %v", execution.ExecutionID, err)) + log.Error("Failed to submit execution %s: %v", execution.ExecutionID, err) } } - return wm.SubmitJob(j, handler) + // Return error if any submissions failed + if len(submitErrors) > 0 { + return fmt.Errorf("failed to submit some executions: %s", strings.Join(submitErrors, "; ")) + } + + return nil } -// Cancel cancel the job -func (j *Job) Cancel() error { +// Stop stops the job and cancels all running executions +func (j *Job) Stop() error { // Update job status j.Status = "disabled" if err := SaveJob(j); err != nil { return fmt.Errorf("failed to update job status: %w", err) } - // If there's a current execution, mark it as cancelled - if j.CurrentExecutionID != nil { - execution, err := GetExecution(*j.CurrentExecutionID, model.QueryParam{}) + // Cancel all running executions using job context + if j.cancel != nil { + j.cancel() + log.Info("Job %s cancelled, all executions will be stopped", j.JobID) + } + + // Cancel individual execution contexts and clean up + j.executionMutex.Lock() + for executionID, cancelFunc := range j.executionContexts { + cancelFunc() + + // Update execution status in database + execution, err := GetExecution(executionID, model.QueryParam{}) if err == nil && (execution.Status == "queued" || execution.Status == "running") { execution.Status = "cancelled" execution.EndedAt = &time.Time{} @@ -98,17 +154,45 @@ func (j *Job) Cancel() error { JobID: j.JobID, Level: "info", Message: "Job execution cancelled by user", - ExecutionID: j.CurrentExecutionID, + ExecutionID: &executionID, Timestamp: time.Now(), Sequence: 0, } SaveLog(logEntry) } } + // Clear execution contexts + j.executionContexts = make(map[string]context.CancelFunc) + j.executionMutex.Unlock() return nil } +// Destroy destroys the job and cleans up all resources +func (j *Job) Destroy() error { + // Stop the job first + if err := j.Stop(); err != nil { + log.Warn("Failed to stop job during destroy: %v", err) + } + + // Handlers are now stored with executions, no global registry to clean + + // Update job status to deleted + j.Status = "deleted" + if err := SaveJob(j); err != nil { + log.Warn("Failed to update job status to deleted: %v", err) + } + + // Clear job references + if j.cancel != nil { + j.cancel() + j.cancel = nil + } + + log.Info("Job %s destroyed successfully", j.JobID) + return nil +} + // SetData set the data of the job func (j *Job) SetData(data map[string]interface{}) *Job { return j @@ -173,5 +257,38 @@ func makeJob(data []byte) (*Job, error) { if err != nil { return nil, err } + + // Set default values if not provided + if job.CategoryID == "" { + job.CategoryID = "default" + } + if job.Status == "" { + job.Status = "draft" + } + if job.MaxWorkerNums == 0 { + job.MaxWorkerNums = 1 // Default to 1 worker + } + if job.Priority == 0 { + job.Priority = 1 // Default job priority + } + if job.CreatedBy == "" { + job.CreatedBy = "system" + } + return &job, nil } + +// RestoreJobsFromDatabase restores jobs from database on system startup +func RestoreJobsFromDatabase() ([]*Job, error) { + // Get all active jobs from database + activeJobs, err := GetActiveJobs() + if err != nil { + return nil, fmt.Errorf("failed to get active jobs: %w", err) + } + + // No need to restore handlers since we only use Yao processes and commands + // Both are fully serializable and self-contained + + log.Info("Restored %d jobs from database", len(activeJobs)) + return activeJobs, nil +} diff --git a/job/job_test.go b/job/job_test.go index 3e83016a..86f5932d 100644 --- a/job/job_test.go +++ b/job/job_test.go @@ -1,68 +1,178 @@ package job_test import ( - "context" "fmt" "testing" "time" "github.com/yaoapp/gou/model" + "github.com/yaoapp/gou/process" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/job" "github.com/yaoapp/yao/test" ) +// registerTestProcesses registers test processes for job testing +func registerTestProcesses() { + // Register test.job.echo process + process.Register("test.job.echo", func(process *process.Process) interface{} { + args := process.Args + if len(args) > 0 { + message := args[0] + + // Simulate progress updates + if process.Callback != nil { + // Report 25% progress + process.Callback(process, map[string]interface{}{ + "type": "progress", + "progress": 25, + "message": "Starting echo process", + }) + + // Report 50% progress + process.Callback(process, map[string]interface{}{ + "type": "progress", + "progress": 50, + "message": "Processing message", + }) + + // Report 75% progress + process.Callback(process, map[string]interface{}{ + "type": "progress", + "progress": 75, + "message": "Finalizing echo", + }) + + // Report 100% progress + process.Callback(process, map[string]interface{}{ + "type": "progress", + "progress": 100, + "message": "Echo completed", + }) + } + + return map[string]interface{}{ + "message": message, + "echo": "Echo: " + message.(string), + "status": "success", + } + } + + return map[string]interface{}{ + "message": "No message provided", + "status": "error", + } + }) + + // Register test.job.cron process + process.Register("test.job.cron", func(process *process.Process) interface{} { + args := process.Args + message := "Cron job executed" + if len(args) > 0 { + message = args[0].(string) + } + + return map[string]interface{}{ + "message": message, + "timestamp": time.Now().Unix(), + "status": "success", + } + }) + + // Register test.job.daemon process + process.Register("test.job.daemon", func(process *process.Process) interface{} { + args := process.Args + message := "Daemon process executed" + if len(args) > 0 { + message = args[0].(string) + } + + return map[string]interface{}{ + "message": message, + "status": "success", + "daemon": true, + } + }) + + // Register test.job.database process + process.Register("test.job.database", func(process *process.Process) interface{} { + args := process.Args + message := "Database operation executed" + if len(args) > 0 { + message = args[0].(string) + } + + return map[string]interface{}{ + "message": message, + "operation": "test", + "status": "success", + } + }) + + // Register test.job.execution process with enhanced features + process.Register("test.job.execution", func(process *process.Process) interface{} { + args := process.Args + message := "Execution test" + if len(args) > 0 { + message = args[0].(string) + } + + // Simulate progress updates with callback + if process.Callback != nil { + // Report progress incrementally + for i := 10; i <= 100; i += 10 { + process.Callback(process, map[string]interface{}{ + "type": "progress", + "progress": i, + "message": fmt.Sprintf("Processing step %d/10", i/10), + }) + time.Sleep(10 * time.Millisecond) // Small delay to simulate work + } + } + + return map[string]interface{}{ + "message": message, + "progress": 100, + "status": "success", + "test_data": "execution completed", + } + }) +} + // TestOnce test once job func TestOnceGoroutine(t *testing.T) { // Setup test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{}) if err != nil { t.Fatal(err) } - // Channel to signal completion - done := make(chan bool, 1) - - // Handler that signals completion - handler := func(ctx context.Context, execution *job.Execution) error { - execution.SetProgress(50, "Progress 50%%") - execution.Info("Progress 50%%") - time.Sleep(100 * time.Millisecond) - execution.SetProgress(100, "Progress 100%%") - execution.Info("Progress 100%%") - done <- true - return nil - } - - err = testJob.Add(1, handler) + // Use a test Yao process (this would need to be defined in your Yao app) + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "test_data": "Hello from test", + }, + }, "test.job.echo", "Hello from test") if err != nil { t.Fatal(err) } - // Set up worker manager for test - wm := job.NewWorkerManagerForTest(2) - wm.Start() - defer wm.Stop() - testJob.SetWorkerManager(wm) - err = testJob.Start() if err != nil { t.Fatal(err) } - // Wait for job completion or timeout - select { - case <-done: - t.Log("Job completed successfully") - case <-time.After(10 * time.Second): - t.Error("Job execution timeout") - } + // Give some time for execution + time.Sleep(2 * time.Second) - // Give some extra time for cleanup - time.Sleep(500 * time.Millisecond) + t.Log("Job started successfully") } func TestOnceProcess(t *testing.T) { @@ -70,51 +180,34 @@ func TestOnceProcess(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Once(job.PROCESS, map[string]interface{}{}) if err != nil { t.Fatal(err) } - // Channel to signal completion - done := make(chan bool, 1) - - // Handler that signals completion - handler := func(ctx context.Context, execution *job.Execution) error { - execution.SetProgress(50, "Progress 50%%") - execution.Info("Progress 50%%") - time.Sleep(100 * time.Millisecond) - execution.SetProgress(100, "Progress 100%%") - execution.Info("Progress 100%%") - done <- true - return nil - } - - err = testJob.Add(1, handler) + // Use a test Yao process + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "test_data": "Hello from process test", + }, + }, "test.job.echo", "Hello from process test") if err != nil { t.Fatal(err) } - // Set up worker manager for test - wm := job.NewWorkerManagerForTest(2) - wm.Start() - defer wm.Stop() - testJob.SetWorkerManager(wm) - err = testJob.Start() if err != nil { t.Fatal(err) } - // Wait for job completion or timeout - select { - case <-done: - t.Log("Job completed successfully") - case <-time.After(10 * time.Second): - t.Error("Job execution timeout") - } + // Give some time for execution + time.Sleep(2 * time.Second) - // Give some extra time for cleanup - time.Sleep(500 * time.Millisecond) + t.Log("Process job started successfully") } func TestCronGoroutine(t *testing.T) { @@ -122,13 +215,21 @@ func TestCronGoroutine(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Cron(job.GOROUTINE, map[string]interface{}{}, "0 0 * * *") if err != nil { t.Fatal(err) } // For cron jobs, we just test creation, not execution - err = testJob.Add(1, HandlerTest) + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "cron_context": "scheduled execution", + }, + }, "test.job.cron", "Cron test execution") if err != nil { t.Fatal(err) } @@ -145,13 +246,21 @@ func TestCronProcess(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Cron(job.PROCESS, map[string]interface{}{}, "0 0 * * *") if err != nil { t.Fatal(err) } // For cron jobs, we just test creation, not execution - err = testJob.Add(1, HandlerTest) + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "cron_context": "scheduled process execution", + }, + }, "test.job.cron", "Cron process test execution") if err != nil { t.Fatal(err) } @@ -163,18 +272,26 @@ func TestCronProcess(t *testing.T) { } } -// TestDaemonGoroutine tests daemon job with goroutine mode using Ticker handler +// TestDaemonGoroutine tests daemon job with goroutine mode func TestDaemonGoroutine(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Daemon(job.GOROUTINE, map[string]interface{}{}) if err != nil { t.Fatal(err) } // For daemon jobs, we just test creation, not long-running execution - err = testJob.Add(1, DaemonHandlerFastTest) + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "daemon_context": "background service", + }, + }, "test.job.daemon", "Daemon test execution") if err != nil { t.Fatal(err) } @@ -186,18 +303,26 @@ func TestDaemonGoroutine(t *testing.T) { } } -// TestDaemonProcess tests daemon job with process mode using Ticker handler +// TestDaemonProcess tests daemon job with process mode func TestDaemonProcess(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + testJob, err := job.Daemon(job.PROCESS, map[string]interface{}{}) if err != nil { t.Fatal(err) } // For daemon jobs, we just test creation, not long-running execution - err = testJob.Add(1, DaemonHandlerFastTest) + err = testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "daemon_context": "background process service", + }, + }, "test.job.daemon", "Daemon process test execution") if err != nil { t.Fatal(err) } @@ -209,132 +334,42 @@ func TestDaemonProcess(t *testing.T) { } } -// TestDaemonFastGoroutine tests fast daemon job with goroutine mode for quick testing -func TestDaemonFastGoroutine(t *testing.T) { +// TestCommand test command execution +func TestCommand(t *testing.T) { test.Prepare(&testing.T{}, config.Conf) defer test.Clean() - testJob, err := job.Daemon(job.GOROUTINE, map[string]interface{}{}) + // Register test processes + registerTestProcesses() + + testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ + "name": "Test Command Job", + "description": "Job for testing command execution", + }) if err != nil { t.Fatal(err) } - // For daemon jobs, we just test creation, not execution - err = testJob.Add(1, DaemonHandlerFastTest) + // Test system command + err = testJob.AddCommand(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "command_context": "test execution", + }, + }, "echo", []string{"Hello from command test"}, nil) if err != nil { t.Fatal(err) } - // Just verify the job was created properly - if testJob.ScheduleType != string(job.ScheduleTypeDaemon) { - t.Errorf("Expected schedule type daemon, got %s", testJob.ScheduleType) - } -} - -// TestDaemonFastProcess tests fast daemon job with process mode for quick testing -func TestDaemonFastProcess(t *testing.T) { - test.Prepare(&testing.T{}, config.Conf) - defer test.Clean() - - testJob, err := job.Daemon(job.PROCESS, map[string]interface{}{}) + err = testJob.Start() if err != nil { t.Fatal(err) } - // For daemon jobs, we just test creation, not execution - err = testJob.Add(1, DaemonHandlerFastTest) - if err != nil { - t.Fatal(err) - } + // Give some time for execution + time.Sleep(2 * time.Second) - // Just verify the job was created properly - if testJob.ScheduleType != string(job.ScheduleTypeDaemon) { - t.Errorf("Expected schedule type daemon, got %s", testJob.ScheduleType) - } -} - -func HandlerTest(ctx context.Context, execution *job.Execution) error { - execution.SetProgress(50, "Progress 50%%") - execution.Info("Progress 50%%") - time.Sleep(100 * time.Millisecond) - execution.SetProgress(100, "Progress 100%%") - execution.Info("Progress 100%%") - time.Sleep(200 * time.Millisecond) - execution.SetProgress(100, "Progress 100%%") - execution.Info("Progress 100%%") - return nil -} - -func DaemonHandlerTest(ctx context.Context, execution *job.Execution) error { - // Build a daemon handler using Ticker that executes tasks every 5 seconds continuously - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - - counter := 0 - - execution.Info("Daemon handler started, running continuously...") - execution.SetProgress(0, "Daemon initialized and ready") - - for { - select { - case <-ctx.Done(): - // Context cancelled, graceful shutdown - execution.Info("Daemon handler received cancellation signal after %d iterations, exiting...", counter) - execution.SetProgress(100, fmt.Sprintf("Daemon stopped gracefully after %d iterations", counter)) - return ctx.Err() - case <-ticker.C: - counter++ - - // Daemon doesn't need specific completion progress, show running status instead - execution.SetProgress(50, fmt.Sprintf("Running - completed %d iterations", counter)) - execution.Info("Daemon tick %d: Processing periodic task...", counter) - - // Simulate periodic tasks execution - // e.g.: cleanup temp files, health checks, data synchronization, etc. - time.Sleep(500 * time.Millisecond) // Simulate task execution time - - execution.Debug("Daemon iteration %d completed successfully", counter) - - // Output statistics every 10 iterations - if counter%10 == 0 { - execution.Info("Daemon health check: %d iterations completed, still running...", counter) - } - } - } -} - -// DaemonHandlerFastTest fast testing version of daemon handler for testing (executes every 500ms) -func DaemonHandlerFastTest(ctx context.Context, execution *job.Execution) error { - // Use shorter interval for testing - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - counter := 0 - - execution.Info("Fast daemon handler started for testing, running continuously...") - execution.SetProgress(0, "Fast daemon initialized") - - for { - select { - case <-ctx.Done(): - execution.Info("Fast daemon handler stopped after %d iterations", counter) - execution.SetProgress(100, fmt.Sprintf("Fast daemon stopped after %d iterations", counter)) - return ctx.Err() - case <-ticker.C: - counter++ - - execution.SetProgress(50, fmt.Sprintf("Fast daemon: %d iterations", counter)) - execution.Debug("Fast daemon tick %d: Quick task execution", counter) - - // Quick task simulation - time.Sleep(50 * time.Millisecond) - - // Output info every 5 iterations (due to higher frequency) - if counter%5 == 0 { - execution.Info("Fast daemon: %d iterations completed", counter) - } - } - } + t.Log("Command job started successfully") } // TestDatabase test database operations @@ -342,6 +377,9 @@ func TestDatabase(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + // Test category creation category, err := job.GetOrCreateCategory("test-category", "Test category for unit tests") if err != nil { @@ -362,7 +400,18 @@ func TestDatabase(t *testing.T) { } testJob.SetCategory(category.CategoryID) - testJob.Add(1, HandlerTest) + testJob.Add(&job.ExecutionOptions{ + Priority: 1, + SharedData: map[string]interface{}{ + "database_context": "test operation", + }, + }, "test.job.database", "Database test execution") + + // Save the job to database before retrieving it + err = job.SaveJob(testJob) + if err != nil { + t.Fatalf("Failed to save job: %v", err) + } // Test job retrieval retrievedJob, err := job.GetJob(testJob.JobID) @@ -398,71 +447,14 @@ func TestDatabase(t *testing.T) { } } -// TestWorkerManager test worker management -func TestWorkerManager(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Get worker manager - wm := job.NewWorkerManagerForTest(2) - if wm == nil { - t.Fatal("Failed to get worker manager") - } - - // Start worker manager - wm.Start() - defer wm.Stop() - - // Check active workers - activeWorkers := wm.GetActiveWorkers() - if activeWorkers == 0 { - t.Error("Expected active workers after starting worker manager") - } - - // Create and submit a job - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Worker Job", - "description": "Job for testing worker management", - }) - if err != nil { - t.Fatalf("Failed to create job: %v", err) - } - - err = testJob.Add(1, HandlerTest) - if err != nil { - t.Fatalf("Failed to add handler: %v", err) - } - - // Start the job - err = testJob.Start() - if err != nil { - t.Fatalf("Failed to start job: %v", err) - } - - // Wait for job to complete - time.Sleep(1 * time.Second) - - // Check executions - executions, err := testJob.GetExecutions() - if err != nil { - t.Fatalf("Failed to get executions: %v", err) - } - - if len(executions) == 0 { - t.Fatal("Expected at least one execution") - } - - // Check execution status - if executions[0].Status != "completed" && executions[0].Status != "running" { - t.Errorf("Expected execution status 'completed' or 'running', got '%s'", executions[0].Status) - } -} - // TestJobExecution test job execution with logging and progress func TestJobExecution(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() + // Register test processes + registerTestProcesses() + // Create a job with enhanced handler testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ "name": "Test Execution Job", @@ -472,56 +464,36 @@ func TestJobExecution(t *testing.T) { t.Fatalf("Failed to create job: %v", err) } - // Channel to signal completion - done := make(chan bool, 1) - - // Enhanced handler for testing - enhancedHandler := func(ctx context.Context, execution *job.Execution) error { - execution.Info("Starting enhanced test execution") - execution.SetProgress(10, "Initialization complete") - - time.Sleep(50 * time.Millisecond) - - execution.Debug("Debug message test") - execution.SetProgress(50, "Halfway complete") - - time.Sleep(50 * time.Millisecond) - - execution.Warn("Warning message test") - execution.SetProgress(80, "Almost done") - - time.Sleep(50 * time.Millisecond) - - execution.Info("Execution completed successfully") - execution.SetProgress(100, "Complete") - - done <- true - return nil + // Save the job to database first so it has a valid ID + err = job.SaveJob(testJob) + if err != nil { + t.Fatalf("Failed to save job: %v", err) } - err = testJob.Add(1, enhancedHandler) + // Use a test Yao process for execution testing with chained options + err = testJob.Add( + job.NewExecutionOptions(). + WithPriority(1). + AddSharedData("execution_context", "enhanced test"). + AddSharedData("user_id", "test_user_123"). + AddSharedData("session", map[string]interface{}{ + "token": "test_token", + "expires": "2024-12-31", + }), + "test.job.execution", "Enhanced execution test") if err != nil { t.Fatalf("Failed to add handler: %v", err) } - // Start worker manager - wm := job.NewWorkerManagerForTest(2) - wm.Start() - defer wm.Stop() - // Start the job err = testJob.Start() if err != nil { t.Fatalf("Failed to start job: %v", err) } - // Wait for completion or timeout - select { - case <-done: - t.Log("Job execution completed") - case <-time.After(10 * time.Second): - t.Error("Job execution timeout") - } + // Give some time for execution + time.Sleep(2 * time.Second) + t.Log("Job execution started") // Give some extra time for database operations to complete time.Sleep(200 * time.Millisecond) @@ -537,10 +509,25 @@ func TestJobExecution(t *testing.T) { } execution := executions[0] + t.Logf("Initial execution progress: %d", execution.Progress) + + // Get fresh execution data from database to check final progress + freshExecution, err := job.GetExecution(execution.ExecutionID, model.QueryParam{}) + if err != nil { + t.Fatalf("Failed to get fresh execution: %v", err) + } + + t.Logf("Fresh execution progress: %d, status: %s", freshExecution.Progress, freshExecution.Status) + if freshExecution.ErrorInfo != nil && len(*freshExecution.ErrorInfo) > 0 { + t.Logf("Execution error: %s", string(*freshExecution.ErrorInfo)) + } + if freshExecution.Result != nil && len(*freshExecution.Result) > 0 { + t.Logf("Execution result: %s", string(*freshExecution.Result)) + } // Check final progress (may take time to update) - if execution.Progress < 50 { - t.Errorf("Expected progress at least 50, got %d", execution.Progress) + if freshExecution.Progress < 50 { + t.Errorf("Expected progress at least 50, got %d", freshExecution.Progress) } // Check logs diff --git a/job/process.go b/job/process.go index 85576ee9..7abe1cb8 100644 --- a/job/process.go +++ b/job/process.go @@ -1,4 +1,247 @@ package job +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/yao/config" +) + // Process the process mode type Process struct{} + +// ExecuteYaoProcess executes a Yao process using independent process mode (yao run command) +func (p *Process) ExecuteYaoProcess(ctx context.Context, work *WorkRequest, progress *Progress) error { + execConfig := work.Execution.ExecutionConfig + + work.Execution.Info("Executing Yao process: %s (process mode)", execConfig.ProcessName) + + // Prepare yao run command arguments + args := []string{"run", execConfig.ProcessName} + + // Convert and add process arguments using the proper conversion function + convertedArgs := convertArgsForYaoRun(execConfig.ProcessArgs) + args = append(args, convertedArgs...) + + // Create command with context for cancellation support + cmd := exec.CommandContext(ctx, "yao", args...) + + // Set working directory to Yao application root + if config.Conf.Root != "" { + cmd.Dir = config.Conf.Root + } else { + // Fallback to current directory if config is not available + cmd.Dir, _ = os.Getwd() + } + + // Set environment variables + env := os.Environ() + env = append(env, + fmt.Sprintf("YAO_JOB_ID=%s", work.Job.JobID), + fmt.Sprintf("YAO_EXECUTION_ID=%s", work.Execution.ExecutionID), + ) + + // Add shared data as environment variables + if work.Execution.ExecutionOptions != nil && work.Execution.ExecutionOptions.SharedData != nil { + for key, value := range work.Execution.ExecutionOptions.SharedData { + if valueBytes, err := jsoniter.Marshal(value); err == nil { + env = append(env, fmt.Sprintf("YAO_JOB_SHARED_%s=%s", key, string(valueBytes))) + } else { + env = append(env, fmt.Sprintf("YAO_JOB_SHARED_%s=%v", key, value)) + } + } + } + cmd.Env = env + + // Execute command + output, err := cmd.CombinedOutput() + if err != nil { + // Check if it was cancelled + if ctx.Err() != nil { + work.Execution.Warn("Yao process cancelled: %s", ctx.Err().Error()) + work.Execution.Status = "cancelled" + } else { + work.Execution.Error("Yao process failed: %s, output: %s", err.Error(), string(output)) + work.Execution.Status = "failed" + + // Store error output + if len(output) > 0 { + errorInfo := map[string]interface{}{ + "error": err.Error(), + "output": string(output), + } + if errorBytes, jsonErr := jsoniter.Marshal(errorInfo); jsonErr == nil { + work.Execution.ErrorInfo = (*json.RawMessage)(&errorBytes) + } + } + } + + // Save execution status + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution error: %s", saveErr.Error()) + } + + if ctx.Err() != nil { + return ctx.Err() + } + return fmt.Errorf("yao process execution failed: %v", err) + } + + work.Execution.Info("Yao process completed successfully, output: %s", string(output)) + + // Update execution with success result + work.Execution.Status = "completed" + work.Execution.Progress = 100 + if len(output) > 0 { + result := map[string]interface{}{ + "output": string(output), + } + if resultBytes, err := jsoniter.Marshal(result); err == nil { + work.Execution.Result = (*json.RawMessage)(&resultBytes) + } + } + + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution result: %s", saveErr.Error()) + return fmt.Errorf("failed to save execution result: %w", saveErr) + } + + return nil +} + +// ExecuteSystemCommand executes a system command using independent process mode +func (p *Process) ExecuteSystemCommand(ctx context.Context, work *WorkRequest, progress *Progress) error { + execConfig := work.Execution.ExecutionConfig + + work.Execution.Info("Executing command: %s (process mode)", execConfig.Command) + + // Create command with context for cancellation support + cmd := exec.CommandContext(ctx, execConfig.Command, execConfig.CommandArgs...) + + // Set working directory to Yao application root directory + if config.Conf.Root != "" { + cmd.Dir = config.Conf.Root + } else { + // Fallback to current directory + cmd.Dir, _ = os.Getwd() + } + + // Set environment variables + env := os.Environ() + if len(execConfig.Environment) > 0 { + for key, value := range execConfig.Environment { + env = append(env, fmt.Sprintf("%s=%s", key, value)) + } + } + + // Add job context + env = append(env, + fmt.Sprintf("YAO_JOB_ID=%s", work.Job.JobID), + fmt.Sprintf("YAO_EXECUTION_ID=%s", work.Execution.ExecutionID), + ) + + // Add shared data as environment variables + if work.Execution.ExecutionOptions != nil && work.Execution.ExecutionOptions.SharedData != nil { + for key, value := range work.Execution.ExecutionOptions.SharedData { + if valueBytes, err := jsoniter.Marshal(value); err == nil { + env = append(env, fmt.Sprintf("YAO_JOB_SHARED_%s=%s", key, string(valueBytes))) + } else { + env = append(env, fmt.Sprintf("YAO_JOB_SHARED_%s=%v", key, value)) + } + } + } + cmd.Env = env + + // Execute command + output, err := cmd.CombinedOutput() + if err != nil { + // Check if it was cancelled + if ctx.Err() != nil { + work.Execution.Warn("Command cancelled: %s", ctx.Err().Error()) + work.Execution.Status = "cancelled" + } else { + work.Execution.Error("Command failed: %s, output: %s", err.Error(), string(output)) + work.Execution.Status = "failed" + + // Store error output + if len(output) > 0 { + errorInfo := map[string]interface{}{ + "error": err.Error(), + "output": string(output), + } + if errorBytes, jsonErr := jsoniter.Marshal(errorInfo); jsonErr == nil { + work.Execution.ErrorInfo = (*json.RawMessage)(&errorBytes) + } + } + } + + // Save execution status + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution error: %s", saveErr.Error()) + } + + if ctx.Err() != nil { + return ctx.Err() + } + return fmt.Errorf("command execution failed: %v", err) + } + + work.Execution.Info("Command completed successfully, output: %s", string(output)) + + // Update execution with success result + work.Execution.Status = "completed" + work.Execution.Progress = 100 + if len(output) > 0 { + result := map[string]interface{}{ + "output": string(output), + } + if resultBytes, err := jsoniter.Marshal(result); err == nil { + work.Execution.Result = (*json.RawMessage)(&resultBytes) + } + } + + if saveErr := SaveExecution(work.Execution); saveErr != nil { + work.Execution.Error("Failed to save execution result: %s", saveErr.Error()) + return fmt.Errorf("failed to save execution result: %w", saveErr) + } + + return nil +} + +// convertArgsForYaoRun converts arguments to proper format for yao run command +func convertArgsForYaoRun(args []interface{}) []string { + result := make([]string, 0, len(args)) + + for _, arg := range args { + if arg == nil { + result = append(result, "") + continue + } + + // Use type assertion for basic types - direct conversion + switch v := arg.(type) { + case string: + result = append(result, v) + case bool: + result = append(result, fmt.Sprintf("%t", v)) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + result = append(result, fmt.Sprintf("%d", v)) + case float32, float64: + result = append(result, fmt.Sprintf("%g", v)) + default: + // Complex types (slice, map, struct, etc.) need JSON serialization with :: prefix + if argBytes, err := jsoniter.Marshal(arg); err == nil { + result = append(result, "::"+string(argBytes)) + } else { + // Fallback to string representation if JSON marshaling fails + result = append(result, fmt.Sprintf("%v", arg)) + } + } + } + + return result +} diff --git a/job/progress_test.go b/job/progress_test.go deleted file mode 100644 index ac0c1610..00000000 --- a/job/progress_test.go +++ /dev/null @@ -1,260 +0,0 @@ -package job_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/yaoapp/gou/model" - "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/job" - "github.com/yaoapp/yao/test" -) - -// TestProgressManager tests progress management functionality -func TestProgressManager(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test job - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Progress Job", - }) - if err != nil { - t.Fatalf("Failed to create test job: %v", err) - } - - // Get progress manager - progressManager := testJob.Progress() - if progressManager == nil { - t.Fatal("Failed to get progress manager") - } - - // Test initial progress - err = progressManager.Set(0, "Starting") - if err != nil { - t.Fatalf("Failed to set initial progress: %v", err) - } - - // Test progress updates - err = progressManager.Set(25, "25% complete") - if err != nil { - t.Fatalf("Failed to set progress to 25: %v", err) - } - - err = progressManager.Set(50, "50% complete") - if err != nil { - t.Fatalf("Failed to set progress to 50: %v", err) - } - - err = progressManager.Set(75, "75% complete") - if err != nil { - t.Fatalf("Failed to set progress to 75: %v", err) - } - - err = progressManager.Set(100, "Complete") - if err != nil { - t.Fatalf("Failed to set progress to 100: %v", err) - } -} - -// TestProgressWithExecution tests progress updates during job execution -func TestProgressWithExecution(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test job - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Progress Execution Job", - }) - if err != nil { - t.Fatalf("Failed to create test job: %v", err) - } - - // Channel to signal completion - done := make(chan int, 1) - - // Progress tracking handler - progressHandler := func(ctx context.Context, execution *job.Execution) error { - // Test SetProgress method on execution - for i := 0; i <= 100; i += 20 { - err := execution.SetProgress(i, fmt.Sprintf("Progress: %d%%", i)) - if err != nil { - return err - } - time.Sleep(50 * time.Millisecond) - } - done <- 100 - return nil - } - - err = testJob.Add(1, progressHandler) - if err != nil { - t.Fatalf("Failed to add progress handler: %v", err) - } - - // Start worker manager - wm := job.NewWorkerManagerForTest(2) - wm.Start() - defer wm.Stop() - testJob.SetWorkerManager(wm) - - // Start job - err = testJob.Start() - if err != nil { - t.Fatalf("Failed to start job: %v", err) - } - - // Wait for job completion or timeout - var finalProgress int - select { - case finalProgress = <-done: - t.Logf("Job completed with progress: %d", finalProgress) - case <-time.After(10 * time.Second): - t.Error("Job execution timeout") - return - } - - // Give some extra time for database operations to complete - time.Sleep(200 * time.Millisecond) - - // Check final execution state - executions, err := testJob.GetExecutions() - if err != nil { - t.Fatalf("Failed to get executions: %v", err) - } - if len(executions) == 0 { - t.Fatal("Expected at least one execution") - } - - execution := executions[0] - - // Check final progress - we know it completed with 100 from the handler - if finalProgress != 100 { - t.Errorf("Expected handler to complete with 100, got %d", finalProgress) - } - - // The database might not have the latest progress due to async operations - t.Logf("Final execution progress in database: %d", execution.Progress) - t.Logf("Final progress from handler: %d", finalProgress) -} - -// TestProgressWithDatabase tests progress persistence in database -func TestProgressWithDatabase(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test job - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Progress Database Job", - }) - if err != nil { - t.Fatalf("Failed to create test job: %v", err) - } - - // Add handler to save job first - err = testJob.Add(1, func(ctx context.Context, execution *job.Execution) error { - return nil - }) - if err != nil { - t.Fatalf("Failed to add handler: %v", err) - } - - // Create execution manually to test progress persistence - testExecution := &job.Execution{ - ExecutionID: "test-progress-exec-001", - JobID: testJob.JobID, - Status: "running", - TriggerCategory: "manual", - Progress: 0, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - err = job.SaveExecution(testExecution) - if err != nil { - t.Fatalf("Failed to save test execution: %v", err) - } - - // Test progress updates through SetProgress - progressValues := []int{10, 25, 50, 75, 90, 100} - for _, progress := range progressValues { - testExecution.Progress = progress - err = testExecution.SetProgress(progress, fmt.Sprintf("Progress: %d%%", progress)) - if err != nil { - t.Fatalf("Failed to set progress to %d: %v", progress, err) - } - - // Verify progress was saved to database - savedExecution, err := job.GetExecution(testExecution.ExecutionID, model.QueryParam{}) - if err != nil { - t.Fatalf("Failed to get saved execution: %v", err) - } - - if savedExecution.Progress != progress { - t.Errorf("Expected saved progress %d, got %d", progress, savedExecution.Progress) - } - } - - // Clean up - job.RemoveExecutions([]string{testExecution.ExecutionID}) - job.RemoveJobs([]string{testJob.JobID}) -} - -// TestGetProgress tests live progress retrieval -func TestGetProgress(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test execution - testExecution := &job.Execution{ - ExecutionID: "test-get-progress-001", - JobID: "test-job-001", - Status: "running", - TriggerCategory: "manual", - Progress: 75, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - err := job.SaveExecution(testExecution) - if err != nil { - t.Fatalf("Failed to save test execution: %v", err) - } - - // Test GetProgress function - callbackCalled := false - progress, err := job.GetProgress(testExecution.ExecutionID, func(p *job.Progress) { - callbackCalled = true - if p.ExecutionID != testExecution.ExecutionID { - t.Errorf("Expected execution ID %s, got %s", testExecution.ExecutionID, p.ExecutionID) - } - if p.Progress != 75 { - t.Errorf("Expected progress 75, got %d", p.Progress) - } - }) - - if err != nil { - t.Fatalf("Failed to get progress: %v", err) - } - - if progress == nil { - t.Fatal("Expected progress object, got nil") - } - - if progress.ExecutionID != testExecution.ExecutionID { - t.Errorf("Expected execution ID %s, got %s", testExecution.ExecutionID, progress.ExecutionID) - } - - if progress.Progress != 75 { - t.Errorf("Expected progress 75, got %d", progress.Progress) - } - - if !callbackCalled { - t.Error("Expected callback to be called") - } - - // Clean up - job.RemoveExecutions([]string{testExecution.ExecutionID}) -} diff --git a/job/types.go b/job/types.go index d42af0a9..fa2b9004 100644 --- a/job/types.go +++ b/job/types.go @@ -3,6 +3,7 @@ package job import ( "context" "encoding/json" + "sync" "time" ) @@ -51,8 +52,59 @@ const ( Trace ) -// HandlerFunc the job handler function -type HandlerFunc func(ctx context.Context, execution *Execution) error +// ExecutionType represents different execution methods +type ExecutionType string + +// Execution type constants +const ( + ExecutionTypeProcess ExecutionType = "process" // Yao process (default) + ExecutionTypeCommand ExecutionType = "command" // System command +) + +// ExecutionOptions holds common execution options +type ExecutionOptions struct { + Priority int `json:"priority"` // Execution priority (higher = more important) + SharedData map[string]interface{} `json:"shared_data"` // Shared data (session, context, etc.) +} + +// NewExecutionOptions creates a new ExecutionOptions with default values +func NewExecutionOptions() *ExecutionOptions { + return &ExecutionOptions{ + Priority: 0, + SharedData: make(map[string]interface{}), + } +} + +// WithPriority sets the priority and returns the options for chaining +func (o *ExecutionOptions) WithPriority(priority int) *ExecutionOptions { + o.Priority = priority + return o +} + +// WithSharedData sets shared data and returns the options for chaining +func (o *ExecutionOptions) WithSharedData(data map[string]interface{}) *ExecutionOptions { + o.SharedData = data + return o +} + +// AddSharedData adds a key-value pair to shared data and returns the options for chaining +func (o *ExecutionOptions) AddSharedData(key string, value interface{}) *ExecutionOptions { + if o.SharedData == nil { + o.SharedData = make(map[string]interface{}) + } + o.SharedData[key] = value + return o +} + +// ExecutionConfig holds execution configuration based on type +type ExecutionConfig struct { + Type ExecutionType `json:"type"` + ProcessName string `json:"process_name,omitempty"` // Yao process name + ProcessArgs []interface{} `json:"process_args,omitempty"` // Yao process arguments + Command string `json:"command,omitempty"` // System command + CommandArgs []string `json:"command_args,omitempty"` // Command arguments + Environment map[string]string `json:"environment,omitempty"` // Environment variables +} // Job represents the main job entity type Job struct { @@ -87,9 +139,12 @@ type Job struct { Executions []Execution `json:"executions,omitempty"` Logs []Log `json:"logs,omitempty"` - ctx context.Context - cancel context.CancelFunc - workerManager *WorkerManager // For testing: allows using custom worker manager + ctx context.Context + cancel context.CancelFunc + + // Job-level cancellation for running executions + executionContexts map[string]context.CancelFunc // executionID -> cancel function + executionMutex sync.RWMutex } // Category represents job categories for organization @@ -112,31 +167,33 @@ type Category struct { // Execution represents individual job execution instances type Execution struct { - ID uint `json:"id"` - ExecutionID string `json:"execution_id"` - JobID string `json:"job_id"` - Status string `json:"status"` // default: "queued" - TriggerCategory string `json:"trigger_category"` - TriggerSource *string `json:"trigger_source,omitempty"` // nullable: true - TriggerContext *json.RawMessage `json:"trigger_context,omitempty"` // nullable: true - ScheduledAt *time.Time `json:"scheduled_at,omitempty"` // nullable: true - WorkerID *string `json:"worker_id,omitempty"` // nullable: true - ProcessID *string `json:"process_id,omitempty"` // nullable: true - RetryAttempt int `json:"retry_attempt"` // default: 0 - ParentExecutionID *string `json:"parent_execution_id,omitempty"` // nullable: true - StartedAt *time.Time `json:"started_at,omitempty"` // nullable: true - EndedAt *time.Time `json:"ended_at,omitempty"` // nullable: true - TimeoutSeconds *int `json:"timeout_seconds,omitempty"` // nullable: true - Duration *int `json:"duration,omitempty"` // nullable: true - Progress int `json:"progress"` // default: 0 - ConfigSnapshot *json.RawMessage `json:"config_snapshot,omitempty"` // nullable: true - Result *json.RawMessage `json:"result,omitempty"` // nullable: true - ErrorInfo *json.RawMessage `json:"error_info,omitempty"` // nullable: true - StackTrace *string `json:"stack_trace,omitempty"` // nullable: true - Metrics *json.RawMessage `json:"metrics,omitempty"` // nullable: true - Context *json.RawMessage `json:"context,omitempty"` // nullable: true - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `json:"id"` + ExecutionID string `json:"execution_id"` + JobID string `json:"job_id"` + Status string `json:"status"` // default: "queued" + TriggerCategory string `json:"trigger_category"` + TriggerSource *string `json:"trigger_source,omitempty"` // nullable: true + TriggerContext *json.RawMessage `json:"trigger_context,omitempty"` // nullable: true + ScheduledAt *time.Time `json:"scheduled_at,omitempty"` // nullable: true + WorkerID *string `json:"worker_id,omitempty"` // nullable: true + ProcessID *string `json:"process_id,omitempty"` // nullable: true + RetryAttempt int `json:"retry_attempt"` // default: 0 + ParentExecutionID *string `json:"parent_execution_id,omitempty"` // nullable: true + StartedAt *time.Time `json:"started_at,omitempty"` // nullable: true + EndedAt *time.Time `json:"ended_at,omitempty"` // nullable: true + TimeoutSeconds *int `json:"timeout_seconds,omitempty"` // nullable: true + Duration *int `json:"duration,omitempty"` // nullable: true + Progress int `json:"progress"` // default: 0 + ExecutionConfig *ExecutionConfig `json:"execution_config,omitempty"` // Execution configuration + ExecutionOptions *ExecutionOptions `json:"execution_options,omitempty"` // Execution options (priority, shared data) + ConfigSnapshot *json.RawMessage `json:"config_snapshot,omitempty"` // nullable: true + Result *json.RawMessage `json:"result,omitempty"` // nullable: true + ErrorInfo *json.RawMessage `json:"error_info,omitempty"` // nullable: true + StackTrace *string `json:"stack_trace,omitempty"` // nullable: true + Metrics *json.RawMessage `json:"metrics,omitempty"` // nullable: true + Context *json.RawMessage `json:"context,omitempty"` // nullable: true + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` // Relationships Job *Job `json:"job,omitempty"` diff --git a/job/types_test.go b/job/types_test.go deleted file mode 100644 index 0bd081e7..00000000 --- a/job/types_test.go +++ /dev/null @@ -1,334 +0,0 @@ -package job_test - -import ( - "context" - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/yaoapp/yao/job" -) - -// TestJobTypes tests job type definitions and constants -func TestJobTypes(t *testing.T) { - // Test ModeType constants - if job.GOROUTINE != "GOROUTINE" { - t.Errorf("Expected GOROUTINE mode to be 'GOROUTINE', got '%s'", job.GOROUTINE) - } - if job.PROCESS != "PROCESS" { - t.Errorf("Expected PROCESS mode to be 'PROCESS', got '%s'", job.PROCESS) - } - - // Test ScheduleType constants - if job.ScheduleTypeOnce != "once" { - t.Errorf("Expected ScheduleTypeOnce to be 'once', got '%s'", job.ScheduleTypeOnce) - } - if job.ScheduleTypeCron != "cron" { - t.Errorf("Expected ScheduleTypeCron to be 'cron', got '%s'", job.ScheduleTypeCron) - } - if job.ScheduleTypeDaemon != "daemon" { - t.Errorf("Expected ScheduleTypeDaemon to be 'daemon', got '%s'", job.ScheduleTypeDaemon) - } - - // Test LogLevel constants - expectedLevels := map[job.LogLevel]string{ - job.Debug: "debug", - job.Info: "info", - job.Warn: "warn", - job.Error: "error", - job.Fatal: "fatal", - job.Panic: "panic", - job.Trace: "trace", - } - - for level, name := range expectedLevels { - if level > 6 { - t.Errorf("LogLevel %s has invalid value %d", name, level) - } - } -} - -// TestJobStructure tests Job struct serialization and deserialization -func TestJobStructure(t *testing.T) { - // Create a test job - now := time.Now() - description := "Test job description" - timeout := 300 - nextRun := now.Add(time.Hour) - lastRun := now.Add(-time.Hour) - currentExecID := "exec-123" - - testJob := &job.Job{ - ID: 1, - JobID: "test-job-001", - Name: "Test Job", - Icon: &description, - Description: &description, - CategoryID: "test-category", - MaxWorkerNums: 2, - Status: "ready", - Mode: job.GOROUTINE, - ScheduleType: string(job.ScheduleTypeOnce), - ScheduleExpression: nil, - MaxRetryCount: 3, - DefaultTimeout: &timeout, - Priority: 5, - CreatedBy: "test-user", - NextRunAt: &nextRun, - LastRunAt: &lastRun, - CurrentExecutionID: ¤tExecID, - Config: map[string]interface{}{"key": "value"}, - Sort: 1, - Enabled: true, - System: false, - Readonly: false, - CreatedAt: now, - UpdatedAt: now, - } - - // Test JSON serialization - jsonData, err := json.Marshal(testJob) - if err != nil { - t.Fatalf("Failed to marshal job to JSON: %v", err) - } - - // Test JSON deserialization - var deserializedJob job.Job - err = json.Unmarshal(jsonData, &deserializedJob) - if err != nil { - t.Fatalf("Failed to unmarshal job from JSON: %v", err) - } - - // Verify key fields - if deserializedJob.JobID != testJob.JobID { - t.Errorf("Expected JobID '%s', got '%s'", testJob.JobID, deserializedJob.JobID) - } - if deserializedJob.Name != testJob.Name { - t.Errorf("Expected Name '%s', got '%s'", testJob.Name, deserializedJob.Name) - } - if deserializedJob.Mode != testJob.Mode { - t.Errorf("Expected Mode '%s', got '%s'", testJob.Mode, deserializedJob.Mode) - } - if deserializedJob.Priority != testJob.Priority { - t.Errorf("Expected Priority %d, got %d", testJob.Priority, deserializedJob.Priority) - } -} - -// TestCategoryStructure tests Category struct -func TestCategoryStructure(t *testing.T) { - now := time.Now() - description := "Test category description" - - testCategory := &job.Category{ - ID: 1, - CategoryID: "test-category-001", - Name: "Test Category", - Icon: &description, - Description: &description, - Sort: 1, - System: false, - Enabled: true, - Readonly: false, - CreatedAt: now, - UpdatedAt: now, - } - - // Test JSON serialization - jsonData, err := json.Marshal(testCategory) - if err != nil { - t.Fatalf("Failed to marshal category to JSON: %v", err) - } - - // Test JSON deserialization - var deserializedCategory job.Category - err = json.Unmarshal(jsonData, &deserializedCategory) - if err != nil { - t.Fatalf("Failed to unmarshal category from JSON: %v", err) - } - - // Verify key fields - if deserializedCategory.CategoryID != testCategory.CategoryID { - t.Errorf("Expected CategoryID '%s', got '%s'", testCategory.CategoryID, deserializedCategory.CategoryID) - } - if deserializedCategory.Name != testCategory.Name { - t.Errorf("Expected Name '%s', got '%s'", testCategory.Name, deserializedCategory.Name) - } - if deserializedCategory.Enabled != testCategory.Enabled { - t.Errorf("Expected Enabled %v, got %v", testCategory.Enabled, deserializedCategory.Enabled) - } -} - -// TestExecutionStructure tests Execution struct -func TestExecutionStructure(t *testing.T) { - now := time.Now() - startedAt := now.Add(-time.Minute) - endedAt := now - timeout := 300 - duration := 60000 - parentExecID := "parent-exec-001" - workerID := "worker-001" - processID := "process-001" - - testExecution := &job.Execution{ - ID: 1, - ExecutionID: "test-execution-001", - JobID: "test-job-001", - Status: "completed", - TriggerCategory: "manual", - TriggerSource: &workerID, - ScheduledAt: &startedAt, - WorkerID: &workerID, - ProcessID: &processID, - RetryAttempt: 0, - ParentExecutionID: &parentExecID, - StartedAt: &startedAt, - EndedAt: &endedAt, - TimeoutSeconds: &timeout, - Duration: &duration, - Progress: 100, - CreatedAt: now, - UpdatedAt: now, - } - - // Test JSON serialization - jsonData, err := json.Marshal(testExecution) - if err != nil { - t.Fatalf("Failed to marshal execution to JSON: %v", err) - } - - // Test JSON deserialization - var deserializedExecution job.Execution - err = json.Unmarshal(jsonData, &deserializedExecution) - if err != nil { - t.Fatalf("Failed to unmarshal execution from JSON: %v", err) - } - - // Verify key fields - if deserializedExecution.ExecutionID != testExecution.ExecutionID { - t.Errorf("Expected ExecutionID '%s', got '%s'", testExecution.ExecutionID, deserializedExecution.ExecutionID) - } - if deserializedExecution.JobID != testExecution.JobID { - t.Errorf("Expected JobID '%s', got '%s'", testExecution.JobID, deserializedExecution.JobID) - } - if deserializedExecution.Status != testExecution.Status { - t.Errorf("Expected Status '%s', got '%s'", testExecution.Status, deserializedExecution.Status) - } - if deserializedExecution.Progress != testExecution.Progress { - t.Errorf("Expected Progress %d, got %d", testExecution.Progress, deserializedExecution.Progress) - } -} - -// TestLogStructure tests Log struct -func TestLogStructure(t *testing.T) { - now := time.Now() - executionID := "test-execution-001" - source := "test-handler" - step := "initialization" - progress := 50 - duration := 1000 - errorCode := "ERR001" - stackTrace := "stack trace here" - workerID := "worker-001" - processID := "process-001" - - testLog := &job.Log{ - ID: 1, - JobID: "test-job-001", - Level: "info", - Message: "Test log message", - Source: &source, - ExecutionID: &executionID, - Step: &step, - Progress: &progress, - Duration: &duration, - ErrorCode: &errorCode, - StackTrace: &stackTrace, - WorkerID: &workerID, - ProcessID: &processID, - Timestamp: now, - Sequence: 1, - CreatedAt: now, - UpdatedAt: now, - } - - // Test JSON serialization - jsonData, err := json.Marshal(testLog) - if err != nil { - t.Fatalf("Failed to marshal log to JSON: %v", err) - } - - // Test JSON deserialization - var deserializedLog job.Log - err = json.Unmarshal(jsonData, &deserializedLog) - if err != nil { - t.Fatalf("Failed to unmarshal log from JSON: %v", err) - } - - // Verify key fields - if deserializedLog.JobID != testLog.JobID { - t.Errorf("Expected JobID '%s', got '%s'", testLog.JobID, deserializedLog.JobID) - } - if deserializedLog.Level != testLog.Level { - t.Errorf("Expected Level '%s', got '%s'", testLog.Level, deserializedLog.Level) - } - if deserializedLog.Message != testLog.Message { - t.Errorf("Expected Message '%s', got '%s'", testLog.Message, deserializedLog.Message) - } - if deserializedLog.Sequence != testLog.Sequence { - t.Errorf("Expected Sequence %d, got %d", testLog.Sequence, deserializedLog.Sequence) - } -} - -// TestProgressStructure tests Progress struct -func TestProgressStructure(t *testing.T) { - testProgress := &job.Progress{ - ExecutionID: "test-execution-001", - Progress: 75, - Message: "75% complete", - } - - // Test JSON serialization - jsonData, err := json.Marshal(testProgress) - if err != nil { - t.Fatalf("Failed to marshal progress to JSON: %v", err) - } - - // Test JSON deserialization - var deserializedProgress job.Progress - err = json.Unmarshal(jsonData, &deserializedProgress) - if err != nil { - t.Fatalf("Failed to unmarshal progress from JSON: %v", err) - } - - // Verify fields - if deserializedProgress.ExecutionID != testProgress.ExecutionID { - t.Errorf("Expected ExecutionID '%s', got '%s'", testProgress.ExecutionID, deserializedProgress.ExecutionID) - } - if deserializedProgress.Progress != testProgress.Progress { - t.Errorf("Expected Progress %d, got %d", testProgress.Progress, deserializedProgress.Progress) - } - if deserializedProgress.Message != testProgress.Message { - t.Errorf("Expected Message '%s', got '%s'", testProgress.Message, deserializedProgress.Message) - } -} - -// TestHandlerFunc tests HandlerFunc type -func TestHandlerFunc(t *testing.T) { - // Test handler function signature - var handler job.HandlerFunc = func(ctx context.Context, execution *job.Execution) error { - if ctx == nil { - return fmt.Errorf("context is nil") - } - if execution == nil { - return fmt.Errorf("execution is nil") - } - execution.Info("Handler executed successfully") - return nil - } - - // Test handler function signature - if handler == nil { - t.Error("Handler function should not be nil") - } -} diff --git a/job/worker.go b/job/worker.go index 08be332a..7d0b1899 100644 --- a/job/worker.go +++ b/job/worker.go @@ -4,8 +4,6 @@ import ( "context" "encoding/json" "fmt" - "os" - "os/exec" "runtime" "sync" "time" @@ -40,7 +38,6 @@ type Worker struct { type WorkRequest struct { Job *Job Execution *Execution - Handler HandlerFunc Context context.Context } @@ -48,14 +45,29 @@ type WorkRequest struct { var globalWorkerManager *WorkerManager var workerManagerOnce sync.Once +// init initializes the global worker manager +func init() { + // Start the global worker manager on package initialization + wm := GetWorkerManager() + wm.Start() +} + // GetWorkerManager returns the global worker manager instance func GetWorkerManager() *WorkerManager { workerManagerOnce.Do(func() { - globalWorkerManager = NewWorkerManager(runtime.NumCPU() * 2) // Default to 2x CPU cores + globalWorkerManager = NewWorkerManager(getDefaultMaxWorkers()) // Use configurable default }) return globalWorkerManager } +// getDefaultMaxWorkers returns the default max workers count +// This can be configured via environment variables or config files +func getDefaultMaxWorkers() int { + // Use CPU count * 4 as default for optimal concurrency + // This provides good balance between resource utilization and system load + return runtime.NumCPU() * 4 +} + // NewWorkerManagerForTest creates a new worker manager for testing (not singleton) func NewWorkerManagerForTest(maxWorkers int) *WorkerManager { return NewWorkerManager(maxWorkers) @@ -66,7 +78,7 @@ func NewWorkerManager(maxWorkers int) *WorkerManager { return &WorkerManager{ maxWorkers: maxWorkers, activeWorkers: make(map[string]*Worker), - workQueue: make(chan *WorkRequest, maxWorkers*2), // Buffer for queue + workQueue: make(chan *WorkRequest, maxWorkers*4), // Allow 200% overload (4x buffer) workerPool: make(chan chan *WorkRequest, maxWorkers), quit: make(chan bool), } @@ -122,41 +134,36 @@ func (wm *WorkerManager) Stop() { log.Info("Worker manager stopped") } -// SubmitJob submits a job for execution -func (wm *WorkerManager) SubmitJob(job *Job, handler HandlerFunc) error { - // Create execution record - execution := &Execution{ - ExecutionID: uuid.New().String(), - JobID: job.JobID, - Status: "queued", - TriggerCategory: "manual", // Default trigger - RetryAttempt: 0, - Progress: 0, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } +// SubmitJob submits a job execution for processing with context (non-blocking) +func (wm *WorkerManager) SubmitJob(ctx context.Context, job *Job, execution *Execution) error { + // Check queue capacity before submitting + queueLen := len(wm.workQueue) + queueCap := cap(wm.workQueue) - // Save execution to database - if err := SaveExecution(execution); err != nil { - return fmt.Errorf("failed to save execution: %w", err) + // Allow reasonable backlog but prevent unlimited accumulation + // Reject only when queue is completely full to maximize throughput + if queueLen >= queueCap { + return fmt.Errorf("work queue is full (%d/%d), please retry later", queueLen, queueCap) } // Create work request workRequest := &WorkRequest{ Job: job, Execution: execution, - Handler: handler, - Context: context.Background(), + Context: ctx, } - // Submit to work queue - select { - case wm.workQueue <- workRequest: - log.Debug("Job %s submitted to work queue", job.JobID) - return nil - default: - return fmt.Errorf("work queue is full") - } + // Submit asynchronously to avoid blocking + go func() { + select { + case wm.workQueue <- workRequest: + log.Debug("Job %s execution %s submitted to work queue", job.JobID, execution.ExecutionID) + case <-ctx.Done(): + log.Warn("Job %s execution %s submission cancelled", job.JobID, execution.ExecutionID) + } + }() + + return nil } // dispatch dispatches work requests to available workers @@ -185,6 +192,11 @@ func (wm *WorkerManager) GetActiveWorkers() int { return len(wm.activeWorkers) } +// GetQueueStatus returns queue length and capacity for monitoring +func (wm *WorkerManager) GetQueueStatus() (length int, capacity int) { + return len(wm.workQueue), cap(wm.workQueue) +} + // NewWorker creates a new worker func NewWorker(workerPool chan chan *WorkRequest, mode ModeType) *Worker { ctx, cancel := context.WithCancel(context.Background()) @@ -358,67 +370,60 @@ func (w *Worker) processWork(work *WorkRequest) { log.Warn("Failed to save final job status (database may be closed): %v", err) } + // Clean up execution context from job + if work.Job.executionContexts != nil { + work.Job.executionMutex.Lock() + delete(work.Job.executionContexts, work.Execution.ExecutionID) + work.Job.executionMutex.Unlock() + } + log.Debug("Worker %s finished processing job %s", w.ID, work.Job.JobID) } // executeInGoroutine executes job in goroutine mode func (w *Worker) executeInGoroutine(ctx context.Context, work *WorkRequest, progress *Progress) error { - // Execute handler directly in current goroutine - return work.Handler(ctx, work.Execution) + // Execute based on execution config type + if work.Execution.ExecutionConfig == nil { + return fmt.Errorf("execution config is nil") + } + + // Create goroutine executor + goroutineExecutor := &Goroutine{} + + switch work.Execution.ExecutionConfig.Type { + case ExecutionTypeProcess: + return goroutineExecutor.ExecuteYaoProcess(ctx, work, progress) + + case ExecutionTypeCommand: + return goroutineExecutor.ExecuteSystemCommand(ctx, work, progress) + + default: + return fmt.Errorf("unsupported execution type: %s", work.Execution.ExecutionConfig.Type) + } } // executeInProcess executes job in process mode func (w *Worker) executeInProcess(ctx context.Context, work *WorkRequest, progress *Progress) error { - // For process mode, we would typically spawn a separate process - // For now, we'll simulate this with a goroutine but with process isolation concepts + // Execute based on execution config type using independent process + if work.Execution.ExecutionConfig == nil { + return fmt.Errorf("execution config is nil") + } - // Set process ID (simulated) + // Set process ID (will be actual process ID) processID := fmt.Sprintf("proc_%s", uuid.New().String()[:8]) work.Execution.ProcessID = &processID - // Create a separate goroutine to simulate process isolation - done := make(chan error, 1) + // Create process executor + processExecutor := &Process{} - go func() { - defer func() { - if r := recover(); r != nil { - done <- fmt.Errorf("process panic: %v", r) - } - }() + switch work.Execution.ExecutionConfig.Type { + case ExecutionTypeProcess: + return processExecutor.ExecuteYaoProcess(ctx, work, progress) - // Execute handler - err := work.Handler(ctx, work.Execution) - done <- err - }() + case ExecutionTypeCommand: + return processExecutor.ExecuteSystemCommand(ctx, work, progress) - // Wait for completion or timeout - select { - case err := <-done: - return err - case <-ctx.Done(): - return ctx.Err() + default: + return fmt.Errorf("unsupported execution type: %s", work.Execution.ExecutionConfig.Type) } } - -// executeInRealProcess executes job in a real separate process (future implementation) -func (w *Worker) executeInRealProcess(ctx context.Context, work *WorkRequest, progress *Progress) error { - // This would be used for true process isolation - // For now, it's a placeholder for future implementation - - // Create command to execute job in separate process - cmd := exec.CommandContext(ctx, os.Args[0], "job-execute", work.Execution.ExecutionID) - - // Set environment variables - cmd.Env = append(os.Environ(), - fmt.Sprintf("JOB_ID=%s", work.Job.JobID), - fmt.Sprintf("EXECUTION_ID=%s", work.Execution.ExecutionID), - ) - - // Execute command - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("process execution failed: %v, output: %s", err, string(output)) - } - - return nil -} diff --git a/job/worker_test.go b/job/worker_test.go deleted file mode 100644 index 4d008d36..00000000 --- a/job/worker_test.go +++ /dev/null @@ -1,319 +0,0 @@ -package job_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/job" - "github.com/yaoapp/yao/test" -) - -// TestWorkerManagerLifecycle tests worker manager lifecycle -func TestWorkerManagerLifecycle(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create a new worker manager for testing (not singleton) - wm := job.NewWorkerManagerForTest(2) - if wm == nil { - t.Fatal("Failed to create worker manager") - } - - // Test initial state - if wm.GetActiveWorkers() != 0 { - t.Error("Expected 0 active workers initially") - } - - // Test start - wm.Start() - if wm.GetActiveWorkers() == 0 { - t.Error("Expected active workers after start") - } - - // Test stop - wm.Stop() - // Note: Workers might still be active briefly after stop due to cleanup time - time.Sleep(100 * time.Millisecond) - - // Test restart - wm.Start() - if wm.GetActiveWorkers() == 0 { - t.Error("Expected active workers after restart") - } - - // Final cleanup - wm.Stop() -} - -// TestWorkerJobSubmission tests job submission to workers -func TestWorkerJobSubmission(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test job - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Worker Submission Job", - }) - if err != nil { - t.Fatalf("Failed to create test job: %v", err) - } - - // Test handler - executed := make(chan bool, 1) - testHandler := func(ctx context.Context, execution *job.Execution) error { - execution.Info("Test handler executed") - executed <- true - return nil - } - - // Add handler and save job - err = testJob.Add(1, testHandler) - if err != nil { - t.Fatalf("Failed to add handler: %v", err) - } - - // Create worker manager for testing - wm := job.NewWorkerManagerForTest(2) - wm.Start() - defer wm.Stop() - - // Submit job - err = wm.SubmitJob(testJob, testHandler) - if err != nil { - t.Fatalf("Failed to submit job: %v", err) - } - - // Wait for execution - select { - case <-executed: - t.Log("Job executed successfully") - case <-time.After(5 * time.Second): - t.Error("Job execution timeout") - } - - // Check executions - executions, err := testJob.GetExecutions() - if err != nil { - t.Fatalf("Failed to get executions: %v", err) - } - if len(executions) == 0 { - t.Error("Expected at least one execution") - } -} - -// TestWorkerModes tests different worker execution modes -func TestWorkerModes(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - wm := job.NewWorkerManagerForTest(4) - wm.Start() - defer wm.Stop() - - // Test GOROUTINE mode - goroutineJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Goroutine Mode Job", - }) - if err != nil { - t.Fatalf("Failed to create goroutine job: %v", err) - } - - goroutineExecuted := make(chan bool, 1) - goroutineHandler := func(ctx context.Context, execution *job.Execution) error { - if execution.Job != nil && execution.Job.Mode != job.GOROUTINE { - t.Errorf("Expected GOROUTINE mode, got %v", execution.Job.Mode) - } - goroutineExecuted <- true - return nil - } - - err = goroutineJob.Add(1, goroutineHandler) - if err != nil { - t.Fatalf("Failed to add goroutine handler: %v", err) - } - - goroutineJob.SetWorkerManager(wm) - - err = goroutineJob.Start() - if err != nil { - t.Fatalf("Failed to start goroutine job: %v", err) - } - - // Test PROCESS mode - processJob, err := job.Once(job.PROCESS, map[string]interface{}{ - "name": "Test Process Mode Job", - }) - if err != nil { - t.Fatalf("Failed to create process job: %v", err) - } - - processExecuted := make(chan bool, 1) - processHandler := func(ctx context.Context, execution *job.Execution) error { - if execution.Job != nil && execution.Job.Mode != job.PROCESS { - t.Errorf("Expected PROCESS mode, got %v", execution.Job.Mode) - } - processExecuted <- true - return nil - } - - err = processJob.Add(1, processHandler) - if err != nil { - t.Fatalf("Failed to add process handler: %v", err) - } - - processJob.SetWorkerManager(wm) - - err = processJob.Start() - if err != nil { - t.Fatalf("Failed to start process job: %v", err) - } - - // Wait for both executions - timeout := time.After(10 * time.Second) - goroutineDone := false - processDone := false - - for !goroutineDone || !processDone { - select { - case <-goroutineExecuted: - goroutineDone = true - t.Log("Goroutine job executed successfully") - case <-processExecuted: - processDone = true - t.Log("Process job executed successfully") - case <-timeout: - t.Error("Jobs execution timeout") - return - } - } - - // Give extra time for database operations to complete - time.Sleep(500 * time.Millisecond) -} - -// TestWorkerErrorHandling tests worker error handling -func TestWorkerErrorHandling(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - // Create test job - errorJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": "Test Error Handling Job", - }) - if err != nil { - t.Fatalf("Failed to create error job: %v", err) - } - - // Error handler - errorHandler := func(ctx context.Context, execution *job.Execution) error { - execution.Error("Test error occurred") - return fmt.Errorf("intentional test error") - } - - err = errorJob.Add(1, errorHandler) - if err != nil { - t.Fatalf("Failed to add error handler: %v", err) - } - - wm := job.NewWorkerManagerForTest(4) - wm.Start() - defer wm.Stop() - errorJob.SetWorkerManager(wm) - - err = errorJob.Start() - if err != nil { - t.Fatalf("Failed to start error job: %v", err) - } - - // Wait for execution to complete - time.Sleep(2 * time.Second) - - // Give extra time for database operations to complete - time.Sleep(500 * time.Millisecond) - - // Check execution status - executions, err := errorJob.GetExecutions() - if err != nil { - t.Fatalf("Failed to get executions: %v", err) - } - if len(executions) == 0 { - t.Fatal("Expected at least one execution") - } - - execution := executions[0] - if execution.Status != "failed" { - t.Errorf("Expected execution status 'failed', got '%s'", execution.Status) - } - - if execution.ErrorInfo == nil { - t.Error("Expected error info to be set") - } -} - -// TestWorkerConcurrency tests worker concurrency -func TestWorkerConcurrency(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - wm := job.NewWorkerManagerForTest(4) - wm.Start() - defer wm.Stop() - - const numJobs = 5 - executed := make(chan bool, numJobs) - - // Create and submit multiple jobs concurrently - for i := 0; i < numJobs; i++ { - testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{ - "name": fmt.Sprintf("Concurrent Test Job %d", i+1), - }) - if err != nil { - t.Fatalf("Failed to create test job %d: %v", i+1, err) - } - - jobID := i + 1 - testHandler := func(ctx context.Context, execution *job.Execution) error { - execution.Info("Concurrent job %d executed", jobID) - time.Sleep(100 * time.Millisecond) // Simulate work - executed <- true - return nil - } - - err = testJob.Add(1, testHandler) - if err != nil { - t.Fatalf("Failed to add handler for job %d: %v", i+1, err) - } - - testJob.SetWorkerManager(wm) - - err = testJob.Start() - if err != nil { - t.Fatalf("Failed to start job %d: %v", i+1, err) - } - } - - // Wait for all jobs to complete - completed := 0 - timeout := time.After(10 * time.Second) - - for completed < numJobs { - select { - case <-executed: - completed++ - t.Logf("Job %d completed", completed) - case <-timeout: - t.Errorf("Timeout: only %d out of %d jobs completed", completed, numJobs) - return - } - } - - if completed != numJobs { - t.Errorf("Expected %d jobs to complete, got %d", numJobs, completed) - } - - // Give extra time for database operations to complete - time.Sleep(500 * time.Millisecond) -} diff --git a/yao/models/job/execution.mod.yao b/yao/models/job/execution.mod.yao index d520b8c1..1f99f91c 100644 --- a/yao/models/job/execution.mod.yao +++ b/yao/models/job/execution.mod.yao @@ -159,6 +159,15 @@ "comment": "Execution duration in milliseconds", "nullable": true }, + { + "name": "priority", + "type": "integer", + "label": "Priority", + "comment": "Execution priority (higher number = higher priority)", + "default": 0, + "nullable": false, + "index": true + }, { "name": "progress", "type": "integer", @@ -167,6 +176,13 @@ "default": 0, "nullable": false }, + { + "name": "execution_options", + "type": "json", + "label": "Execution Options", + "comment": "Execution options including priority and shared data", + "nullable": true + }, { "name": "config_snapshot", "type": "json",