fix(webui): disable dashboard auto update checks

feat(plugins): add plugin marketplace route
This commit is contained in:
LoveLosita
2026-05-12 11:20:28 +08:00
parent f75d98900f
commit 587aa0a531
6 changed files with 555 additions and 497 deletions

View File

@@ -1,12 +1,12 @@
{ {
"name": "maibot-dashboard", "name": "maibot-dashboard",
"version": "1.0.5", "version": "1.0.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "maibot-dashboard", "name": "maibot-dashboard",
"version": "1.0.5", "version": "1.0.10",
"dependencies": { "dependencies": {
"@codemirror/lang-css": "^6.3.1", "@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-javascript": "^6.2.4",
@@ -211,6 +211,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -646,6 +647,7 @@
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz",
"integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==", "integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.6.0", "@codemirror/state": "^6.6.0",
"crelt": "^1.0.6", "crelt": "^1.0.6",
@@ -741,6 +743,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=20.19.0"
}, },
@@ -789,6 +792,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=20.19.0"
} }
@@ -834,6 +838,7 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
@@ -1260,7 +1265,6 @@
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"cross-dirname": "^0.1.0", "cross-dirname": "^0.1.0",
"debug": "^4.3.4", "debug": "^4.3.4",
@@ -1282,7 +1286,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
@@ -1299,7 +1302,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@@ -1314,7 +1316,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
@@ -4852,6 +4853,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.168.10.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.168.10.tgz",
"integrity": "sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA==", "integrity": "sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@tanstack/history": "1.161.6", "@tanstack/history": "1.161.6",
"@tanstack/react-store": "^0.9.3", "@tanstack/react-store": "^0.9.3",
@@ -4937,6 +4939,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.168.9.tgz", "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.168.9.tgz",
"integrity": "sha512-18oeEwEDyXOIuO1VBP9ACaK7tYHZUjynGDCoUh/5c/BNhia9vCJCp9O0LfhZXOorDc/PmLSgvmweFhVmIxF10g==", "integrity": "sha512-18oeEwEDyXOIuO1VBP9ACaK7tYHZUjynGDCoUh/5c/BNhia9vCJCp9O0LfhZXOorDc/PmLSgvmweFhVmIxF10g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@tanstack/history": "1.161.6", "@tanstack/history": "1.161.6",
"cookie-es": "^2.0.0", "cookie-es": "^2.0.0",
@@ -5035,6 +5038,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
@@ -5587,6 +5591,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -5597,6 +5602,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@@ -5693,6 +5699,7 @@
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/scope-manager": "8.58.0",
"@typescript-eslint/types": "8.58.0", "@typescript-eslint/types": "8.58.0",
@@ -6011,6 +6018,7 @@
"resolved": "https://registry.npmjs.org/@uppy/core/-/core-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@uppy/core/-/core-5.2.0.tgz",
"integrity": "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw==", "integrity": "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@transloadit/prettier-bytes": "^0.3.4", "@transloadit/prettier-bytes": "^0.3.4",
"@uppy/store-default": "^5.0.0", "@uppy/store-default": "^5.0.0",
@@ -6027,6 +6035,7 @@
"resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-5.1.1.tgz",
"integrity": "sha512-6H/xVvhhdfwp1+FRMp2C+tudyaedqD5+LMDB8Iw20k9+QCL1eGzOh4wXm6MCqJtNfQ1tLaprGMG1jlo7yS/uyw==", "integrity": "sha512-6H/xVvhhdfwp1+FRMp2C+tudyaedqD5+LMDB8Iw20k9+QCL1eGzOh4wXm6MCqJtNfQ1tLaprGMG1jlo7yS/uyw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@transloadit/prettier-bytes": "^0.3.4", "@transloadit/prettier-bytes": "^0.3.4",
"@uppy/provider-views": "^5.2.2", "@uppy/provider-views": "^5.2.2",
@@ -6278,6 +6287,7 @@
"integrity": "sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==", "integrity": "sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vitest/utils": "4.1.2", "@vitest/utils": "4.1.2",
"fflate": "^0.8.2", "fflate": "^0.8.2",
@@ -6342,6 +6352,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -6375,6 +6386,7 @@
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -7024,6 +7036,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.10.12", "baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782", "caniuse-lite": "^1.0.30001782",
@@ -7805,8 +7818,7 @@
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
@@ -7897,7 +7909,8 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
"version": "3.2.4", "version": "3.2.4",
@@ -8002,6 +8015,7 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@@ -8462,6 +8476,7 @@
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "26.8.1", "app-builder-lib": "26.8.1",
"builder-util": "26.8.1", "builder-util": "26.8.1",
@@ -8839,7 +8854,6 @@
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@electron/asar": "^3.2.1", "@electron/asar": "^3.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
@@ -8860,7 +8874,6 @@
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0", "jsonfile": "^4.0.0",
@@ -9182,6 +9195,7 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -10641,6 +10655,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.29.2" "@babel/runtime": "^7.29.2"
}, },
@@ -10735,6 +10750,7 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/immer" "url": "https://opencollective.com/immer"
@@ -11425,6 +11441,7 @@
"integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@acemir/cssom": "^0.9.31", "@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.8.1", "@asamuzakjp/dom-selector": "^6.8.1",
@@ -13207,7 +13224,6 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.6" "minimist": "^1.2.6"
}, },
@@ -13877,6 +13893,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -13964,7 +13981,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"commander": "^9.4.0" "commander": "^9.4.0"
}, },
@@ -13982,7 +13998,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": "^12.20.0 || >=14" "node": "^12.20.0 || >=14"
} }
@@ -14013,6 +14028,7 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -14273,6 +14289,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -14304,6 +14321,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -14443,6 +14461,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/use-sync-external-store": "^0.0.6", "@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0" "use-sync-external-store": "^1.4.0"
@@ -14634,7 +14653,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/redux-thunk": { "node_modules/redux-thunk": {
"version": "3.1.0", "version": "3.1.0",
@@ -14894,7 +14914,6 @@
"deprecated": "Rimraf versions prior to v4 are no longer supported", "deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.1.3" "glob": "^7.1.3"
}, },
@@ -15154,6 +15173,7 @@
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz",
"integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==", "integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
@@ -15895,7 +15915,6 @@
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"rimraf": "~2.6.2" "rimraf": "~2.6.2"
@@ -16284,6 +16303,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -16709,6 +16729,7 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -17268,6 +17289,7 @@
"integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vitest/expect": "4.1.2", "@vitest/expect": "4.1.2",
"@vitest/mocker": "4.1.2", "@vitest/mocker": "4.1.2",
@@ -17719,6 +17741,7 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -1,39 +1,11 @@
import { useEffect, useState } from 'react'
import { getDashboardVersionStatus, type DashboardVersionStatus } from '@/lib/system-api'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { APP_VERSION, formatVersion } from '@/lib/version' import { formatVersion } from '@/lib/version'
interface LogoAreaProps { interface LogoAreaProps {
sidebarOpen: boolean sidebarOpen: boolean
} }
export function LogoArea({ sidebarOpen }: LogoAreaProps) { export function LogoArea({ sidebarOpen }: LogoAreaProps) {
const [versionStatus, setVersionStatus] = useState<DashboardVersionStatus | null>(null)
useEffect(() => {
let mounted = true
const loadVersionStatus = async () => {
try {
const status = await getDashboardVersionStatus(APP_VERSION)
if (mounted) {
setVersionStatus(status)
}
} catch (error) {
console.debug('检查 WebUI 版本更新失败:', error)
}
}
void loadVersionStatus()
return () => {
mounted = false
}
}, [])
const hasUpdate = versionStatus?.has_update === true && Boolean(versionStatus.latest_version)
return ( return (
<div className="flex h-20 items-center border-b px-4"> <div className="flex h-20 items-center border-b px-4">
<div <div
@@ -56,35 +28,7 @@ export function LogoArea({ sidebarOpen }: LogoAreaProps) {
<span className="shrink-0 whitespace-nowrap text-sm font-semibold text-primary/70"> <span className="shrink-0 whitespace-nowrap text-sm font-semibold text-primary/70">
{formatVersion()} {formatVersion()}
</span> </span>
{hasUpdate && (
<a
href={versionStatus?.pypi_url}
target="_blank"
rel="noopener noreferrer"
className={cn(
"inline-flex h-5 min-w-0 items-center rounded-md border border-amber-400/50 px-2",
"bg-amber-400/10 text-[11px] font-semibold text-amber-700",
"transition-colors hover:bg-amber-400/20 dark:text-amber-300"
)}
>
<span className="truncate"> v{versionStatus?.latest_version}</span>
</a>
)}
</div> </div>
{false && hasUpdate && (
<a
href={versionStatus?.pypi_url}
target="_blank"
rel="noopener noreferrer"
className={cn(
"inline-flex h-5 items-center rounded-md border border-amber-400/50 px-2",
"bg-amber-400/10 text-[11px] font-semibold text-amber-700",
"transition-colors hover:bg-amber-400/20 dark:text-amber-300"
)}
>
v{versionStatus?.latest_version}
</a>
)}
<div className="hidden"> <div className="hidden">
<span className="font-bold text-xl text-primary-gradient whitespace-nowrap">MaiBot WebUI</span> <span className="font-bold text-xl text-primary-gradient whitespace-nowrap">MaiBot WebUI</span>
<span className="text-base font-semibold text-primary/70 whitespace-nowrap"> <span className="text-base font-semibold text-primary/70 whitespace-nowrap">

View File

@@ -56,7 +56,7 @@ import { RestartOverlay } from '@/components/restart-overlay'
import { ExpressionReviewer } from '@/components/expression-reviewer' import { ExpressionReviewer } from '@/components/expression-reviewer'
import { getBotConfig, getModelConfig } from '@/lib/config-api' import { getBotConfig, getModelConfig } from '@/lib/config-api'
import { getReviewStats } from '@/lib/expression-api' import { getReviewStats } from '@/lib/expression-api'
import { getDashboardVersionStatus, type DashboardVersionStatus } from '@/lib/system-api' import type { DashboardVersionStatus } from '@/lib/system-api'
import { APP_VERSION } from '@/lib/version' import { APP_VERSION } from '@/lib/version'
import { ZoomableChart } from '@/components/ui/zoomable-chart' import { ZoomableChart } from '@/components/ui/zoomable-chart'
@@ -132,6 +132,9 @@ interface FeatureStatus {
visualEnabled: boolean visualEnabled: boolean
} }
const MAIBOT_RELEASES_URL = 'https://github.com/Mai-with-u/MaiBot/releases'
const MAIBOT_WEBUI_RELEASES_URL = 'https://pypi.org/project/maibot-dashboard/'
// 为饼图生成更丰富的颜色方案 (HSL色相均匀分布) // 为饼图生成更丰富的颜色方案 (HSL色相均匀分布)
const generatePieColors = (count: number): string[] => { const generatePieColors = (count: number): string[] => {
const colors: string[] = [] const colors: string[] = []
@@ -200,48 +203,20 @@ function IndexPageContent() {
let mounted = true let mounted = true
const loadLatestVersions = async () => { const loadLatestVersions = async () => {
try { if (!mounted) {
const response = await fetch('https://api.github.com/repos/Mai-with-u/MaiBot/releases?per_page=20', { return
headers: { Accept: 'application/vnd.github+json' },
})
if (!response.ok) {
throw new Error(`GitHub release status ${response.status}`)
}
const releases = await response.json() as Array<{
draft?: boolean
prerelease?: boolean
tag_name?: string
html_url?: string
}>
const visibleReleases = releases.filter((release) => !release.draft)
const stableRelease = visibleReleases.find((release) => !release.prerelease)
const testRelease = visibleReleases[0]
if (mounted) {
if (stableRelease?.tag_name) {
setMaibotStableRelease({
version: String(stableRelease.tag_name).replace(/^v/i, '').trim(),
url: stableRelease.html_url || 'https://github.com/Mai-with-u/MaiBot/releases',
})
}
if (testRelease?.tag_name) {
setMaibotTestRelease({
version: String(testRelease.tag_name).replace(/^v/i, '').trim(),
url: testRelease.html_url || 'https://github.com/Mai-with-u/MaiBot/releases',
})
}
}
} catch (error) {
console.debug('检查 MaiBot 最新版本失败:', error)
} }
try { // 阶段 0停止首页主动进行远程版本探测仅保留发布页跳转入口。
const status = await getDashboardVersionStatus(APP_VERSION) setMaibotStableRelease(null)
if (mounted) { setMaibotTestRelease(null)
setDashboardVersionStatus(status) setDashboardVersionStatus({
} current_version: APP_VERSION,
} catch (error) { latest_version: null,
console.debug('妫€鏌?WebUI 鐗堟湰鏇存柊澶辫触:', error) has_update: false,
} package_name: 'maibot-dashboard',
pypi_url: MAIBOT_WEBUI_RELEASES_URL,
})
} }
void loadLatestVersions() void loadLatestVersions()
@@ -631,7 +606,7 @@ function IndexPageContent() {
</div> </div>
<div className="hidden"> <div className="hidden">
<a <a
href={maibotTestRelease?.url || 'https://github.com/Mai-with-u/MaiBot/releases'} href={maibotTestRelease?.url || MAIBOT_RELEASES_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-1 transition-colors hover:text-muted-foreground" className="inline-flex items-center gap-1 transition-colors hover:text-muted-foreground"
@@ -642,38 +617,38 @@ function IndexPageContent() {
</div> </div>
<div className="space-y-1 border-t border-border/50 pt-2 text-xs text-muted-foreground/60"> <div className="space-y-1 border-t border-border/50 pt-2 text-xs text-muted-foreground/60">
<a <a
href={maibotStableRelease?.url || 'https://github.com/Mai-with-u/MaiBot/releases'} href={maibotStableRelease?.url || MAIBOT_RELEASES_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground" className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground"
> >
<span></span> <span></span>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{maibotStableRelease ? `v${maibotStableRelease.version}` : 'GitHub Releases'} GitHub Releases
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</span> </span>
</a> </a>
<a <a
href={maibotTestRelease?.url || 'https://github.com/Mai-with-u/MaiBot/releases'} href={maibotTestRelease?.url || MAIBOT_RELEASES_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground" className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground"
> >
<span></span> <span></span>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{maibotTestRelease ? `v${maibotTestRelease.version}` : 'GitHub Releases'} GitHub Releases
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</span> </span>
</a> </a>
<a <a
href={dashboardVersionStatus?.pypi_url || 'https://pypi.org/project/maibot-dashboard/'} href={dashboardVersionStatus?.pypi_url || MAIBOT_WEBUI_RELEASES_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground" className="flex items-center justify-between gap-2 transition-colors hover:text-muted-foreground"
> >
<span>WebUI </span> <span> WebUI </span>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
v{dashboardVersionStatus?.latest_version || APP_VERSION} PyPI
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</span> </span>
</a> </a>

View File

@@ -19,8 +19,6 @@ from src.manager.async_task_manager import async_task_manager
from src.plugin_runtime.integration import get_plugin_runtime_manager from src.plugin_runtime.integration import get_plugin_runtime_manager
from src.prompt.prompt_manager import prompt_manager from src.prompt.prompt_manager import prompt_manager
from src.services.memory_flow_service import memory_automation_service from src.services.memory_flow_service import memory_automation_service
from src.webui.dashboard_update import auto_update_dashboard_if_needed
# from src.api.main import start_api_server # from src.api.main import start_api_server
# 导入插件运行时 # 导入插件运行时
@@ -88,15 +86,7 @@ class MainSystem:
"""启动时自动检查并更新 WebUI dashboard。""" """启动时自动检查并更新 WebUI dashboard。"""
if not global_config.webui.enabled: if not global_config.webui.enabled:
return return
if not global_config.webui.auto_update_dashboard: logger.info("当前分支已禁用 WebUI dashboard 自动更新检查")
logger.info("WebUI dashboard 自动更新已关闭")
return
result = await auto_update_dashboard_if_needed()
if result.updated:
logger.info(result.message)
elif result.checked:
logger.info(result.message)
async def _init_components(self) -> None: async def _init_components(self) -> None:
"""初始化其他组件""" """初始化其他组件"""

View File

@@ -20,12 +20,7 @@ from src.common.database.database import engine, get_db_session
from src.common.database.database_model import Images, ImageType from src.common.database.database_model import Images, ImageType
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import MMC_VERSION from src.config.config import MMC_VERSION
from src.webui.dashboard_update import ( from src.webui.dashboard_update import DASHBOARD_PACKAGE_NAME, PYPI_PROJECT_URL
DASHBOARD_PACKAGE_NAME,
PYPI_PROJECT_URL,
detect_package_runner,
get_dashboard_version_info,
)
from src.webui.dependencies import require_auth from src.webui.dependencies import require_auth
router = APIRouter(prefix="/system", tags=["system"], dependencies=[Depends(require_auth)]) router = APIRouter(prefix="/system", tags=["system"], dependencies=[Depends(require_auth)])
@@ -129,31 +124,6 @@ class LocalCacheCleanupResponse(BaseModel):
removed_records: int = 0 removed_records: int = 0
def _parse_version_parts(version: str | None) -> Optional[list[int]]:
"""将版本号转换为可比较的整数列表。"""
if not version:
return None
parts: list[int] = []
for raw_part in version.split("."):
if not raw_part.isdigit():
return None
parts.append(int(raw_part))
return parts
def _is_newer_version(latest: str | None, current: str | None) -> bool:
"""判断 latest 是否新于 current。"""
latest_parts = _parse_version_parts(latest)
current_parts = _parse_version_parts(current)
if latest_parts is None or current_parts is None:
return False
max_len = max(len(latest_parts), len(current_parts))
latest_parts.extend([0] * (max_len - len(latest_parts)))
current_parts.extend([0] * (max_len - len(current_parts)))
return latest_parts > current_parts
def _iter_files(directory: Path) -> list[Path]: def _iter_files(directory: Path) -> list[Path]:
if not directory.exists() or not directory.is_dir(): if not directory.exists() or not directory.is_dir():
return [] return []
@@ -327,14 +297,12 @@ async def get_maibot_status():
@router.get("/dashboard-version", response_model=DashboardVersionResponse) @router.get("/dashboard-version", response_model=DashboardVersionResponse)
async def get_dashboard_version(current_version: Optional[str] = None): async def get_dashboard_version(current_version: Optional[str] = None):
"""获取 WebUI 当前版本和 PyPI 最新版本""" """获取 WebUI 当前版本信息,不执行远程更新探测"""
version_info = await get_dashboard_version_info(current_version)
return DashboardVersionResponse( return DashboardVersionResponse(
current_version=version_info.current_version, current_version=current_version or "unknown",
latest_version=version_info.latest_version, latest_version=None,
has_update=version_info.has_update, has_update=False,
runner=detect_package_runner(), runner="disabled",
) )

804
计划.md
View File

@@ -1,372 +1,530 @@
# 私聊定时跟进 V1 计划 # 私聊拟人性增强分阶段计划
## 当前结论 ## 总体目标
- 本方案只支持私聊,不支持群聊 - 在尽量保留当前 maibot 主干能力与最近更新的前提下,增强私聊中的连续感、活人感与主动性
- 旧草案中的“到点直接发送预先写死的 `message_text`”不再作为 V1 主方案 - 优先修复“私聊对象信息容易遗忘”“聊天断开后像失忆”“上下文窗口过于保守”这三类核心问题
- V1 改为“定时器负责唤醒当前私聊会话的正常思考流程”,由 LLM 基于到点时的当前上下文决定是否主动发言 - 再逐步增加“定时跟进”“主动私聊”“关系阶段推进”等能力
- 为了支持“主动发言但不强行回复某条旧消息”,需要新增一个与 `reply` 平行的主动私聊发送工具。 - 落地时坚持“平行新增、契约尽量不变”:
- 平台出口、消息存储、Maisaka 上下文写回、Napcat 适配等链路应尽量复用现有实现,不新造一条发送通道。 - 不破坏现有 `reply` 语义
- 新能力优先通过新工具、新配置、新任务表、新 metadata 增量扩展
- 尽量不改现有 WebUI 聊天消息结构与事件结构
## 目标 ## 借鉴来源
- 让 LLM 可以在当前私聊里创建一条未来触发的“定时跟进任务”。
- 到点后不直接发固定文本,而是重新进入该私聊的正常 planner 思考流程。
- 到点思考时直接复用现有上下文窗口、原有 planner prompt、原有工具机制。
- 如果 planner 判断现在适合主动发言,则调用新的主动私聊发送工具发出消息。
- 如果 planner 判断现在不适合打扰,则可以不发言并直接结束本轮。
## 明确不做 ### 1. `MoFox-Core`
- 不做群聊版定时跟进。 - 主要借鉴:
- 不做跨会话发送;当前私聊中创建的任务,只能唤醒当前私聊,也只能向当前私聊主动发言。 - 私聊对象关系信息常态注入
- 不做“任意指定目标用户/目标 session 的消息发送器”。 - 主动前护栏判断
- 不做复杂多意图分类,不引入冗余的 `intent_key` - `WAITING / IDLE` 状态机思路
- 不伪造一条新的用户入站消息来驱动到点逻辑 - 发完后等待回复、超时再判断是否继续发言的活人感逻辑
-把主动 follow-up 强行伪装成 `reply(msg_id=...)` -直接照搬:
- AFC / KFC 全套 runtime
- 强耦合插件系统
- 其整套数据库结构
## 核心方案 ### 2. `Muice-Chatbot`
- 主要借鉴:
- 轻量主动开场/问候思路
- 随机主动话题池思路
- 不直接照搬:
- 到点直接发送固定文本
- 过于依赖固定 prompt 的主动对话实现
### 1. 定时器从“文本发送器”改为“流程进入器” ## 当前问题
- 当前用户在私聊中表达未来某个时间点需要 bot 主动跟进 - 私聊对话者信息经常会被忘记,应该被常态化注入
- Planner 调用新的定时跟进工具,写入一条未来任务 - 当前上下文管理偏保守,没充分发挥大上下文模型的能力
- 到点后,后台调度器不直接发消息,而是唤醒对应私聊会话的 Maisaka planner - 私聊 runtime 重建后不会自动回灌最近历史,聊天断开一阵子后容易出现“失忆”
- planner 结合当前上下文重新判断: - 人物信息、关系信息、共同经历主要依赖临时检索,缺少稳定默认上下文。
- 是否应该主动发言 - planner 查到的信息不能稳定传递到 replyer导致回复阶段再次遗忘。
- 现在该说什么
- 是否应该放弃本次跟进
- 是否应该重新约下一个时间点
### 2. 新增主动私聊发送工具 ## 当前准备增加的能力
- 保留现有 `reply` - 定时跟进机制:允许 LLM 在当前私聊中创建未来跟进任务,到点后重新唤醒 planner 判断要不要主动开口。
- 语义仍然是“针对某条已有消息进行回复” - 主动私聊发送工具:新增不依赖 `msg_id``send_private_message`,作为 `replyer` 的新发送出口,让主动聊天不再伪装成回复旧消息。
- 仍然依赖 `msg_id` - 到点前护栏机制:加入静默时段、最小主动间隔、可选每日上限,避免技术上能发但体验上打扰。
- 新增 `send_private_message` - 阶段以及主线机制:允许你在私聊中像玩 galgame 一样,和小麦进行逐步推进的互动。
- 语义是“在当前私聊里主动发出一条可见消息” - 为后续私聊状态机预留扩展:后面再补“发完等回复、超时再判断”的活人感机制。
- 不依赖 `msg_id`
- 不默认引用旧消息
- 这样可以避免 QQ/Napcat 侧展示成“永远在回复某条旧消息”,更符合真实私聊中的自然主动聊天。
### 3. 发送出口继续复用现有通道 ## 兼容性原则
- `send_private_message` 不新造平台出口。
- 它和 `reply` 一样,最终复用现有: ### 前后端契约
- `send_service` - 现有 `reply` 工具保持原语义、原参数、原发送形态不变。
- `Platform IO` - 新增 `send_private_message``schedule_private_followup` 时,采用与现有 builtin tool 平行的注册、执行、记录方式。
- 现有 driver / adapter - 现有聊天消息 `WsMessage / ChatMessage / MessageSegment` 契约尽量不变:
- Napcat 出口 - 主动私聊发出的消息仍然是普通 bot 可见消息
- 这样发送成功后,仍可复用现有: - 不新增专用 `MessageSegment.type` 作为前提
- 消息写库 - 现有工具记录结构尽量不变:
- memory automation - 继续复用现有 tool record / tool result 链路
- 新增内容尽量放在 `tool_name``tool_data`、metadata 中
### 配置与 WebUI
- 配置面优先平行新增独立配置块,默认值完整,保证旧配置不改也能运行。
- WebUI V1 不应成为主阻塞项:
- 新功能优先复用现有聊天渲染与工具渲染
- 后续再考虑做任务管理页、状态展示页等增强 UI
---
## 阶段 0开发期稳定化
### 本阶段要解决的问题
- 当前准备进入连续多阶段改造,如果开发过程中误触自动更新,容易导致本体与 WebUI 版本漂移。
- 一旦前后端版本不一致,后续上下文、工具、调度器相关改造会很难排障。
- private-main 开发期间需要一个尽量稳定、可复现的运行基线。
### 本阶段要增加/修改的点
- `☐` 断开 WebUI 自动更新检测
- `☐` 断开本体自动更新检测
- `☐` 避免开发期自动拉取或自动提示更新
- `☐` 保留显式手动恢复/更新路径
### 如何做
- 找到当前 WebUI 与本体的更新检查入口:
- 启动时自动检查
- 定时轮询检查
- 页面内自动提示
- 开发期优先做“禁用自动检查”,而不是修改版本号或伪造远端结果。
- 尽量采用可逆方案:
- 配置开关
- 环境变量
- 显式开发模式判断
- 如果本体和 WebUI 的更新逻辑是分开的,也应分别断开,避免只关一边。
### 借鉴来源
- 不依赖 `MoFox-Core``Muice-Chatbot`
- 这是当前 private-main 连续改造前的工程防护阶段。
### 验收标准
- 启动本体时,不再主动发起自动更新检查。
- 启动 WebUI 时,不再自动弹更新提示或自动轮询更新状态。
- 本体日志和 WebUI 调试输出中,不再出现自动更新任务被触发的痕迹。
- 若后续需要更新,仍存在显式、可控、可手动触发的更新路径。
---
## 阶段 1修复私聊遗忘问题
### 本阶段要解决的问题
- 私聊对象信息经常被忘记。
- 聊天断开后再回来,像从空白状态重新开始。
- planner 查到的信息不稳定replyer 容易再次遗忘。
### 本阶段要增加/修改的点
- `☐` 私聊对象信息常态注入
- `☐` runtime 重建时自动回灌最近历史
- `☐` planner 到 replyer 的信息继承增强
- `☐` 上下文选择策略从“纯按条数裁切”调整为“更适合大上下文模型”的策略
### 本阶段目标预算
- 以“大上下文真正可用,但不盲目塞满”为原则,先把私聊 planner 的目标输入预算定在 **256K token 级别**
- 不建议把整整 256K 都让历史消息吃满,应预留系统提示、工具定义、注入块、输出空间。
- 第一版建议按下面的思路分配:
- 总目标预算:`256K`
- 预留给系统提示 / 工具定义 / injected reminders / 输出余量:`32K ~ 64K`
- 可用于“历史消息 + 人物信息块 + 共同事件块”的主预算:`160K ~ 220K`
- replyer 阶段不必与 planner 一样大,可以显著更小:
- 优先依赖 planner 已整理出的参考信息
- replyer 可先控制在 `32K ~ 64K` 级别
### 本阶段要先定下的几个原则
- **历史回灌** 不是滑动窗口本身,而是滑动窗口的前置步骤。
- **token 滑窗** 不应只做“超了就 pop”而应做“固定高优先级块保底 + 历史候选池按 token 预算裁切”。
- **人物信息块、当前约定、最近共同事件** 的优先级应高于普通旧聊天。
- **planner 与 replyer 的上下文策略不必完全一样**
- planner 负责大窗理解
- replyer 负责在较小上下文内稳定生成
- **必须保留降级路径**
- 即使 tokenizer 暂时不可用,也能退回近似估算模式
- 但默认目标是本地 token 预算裁切,而不是继续硬按条数
### 如何做
- 为当前私聊补一个轻量信息聚合入口:
- 优先从现有 `person_info`
- 再从 A_memorix 的人物画像/关系证据中补充
- 统一格式化为稳定上下文块,默认注入 planner
- 在 runtime 重建时,按 `session_id` 从消息库回灌最近一段真实聊天历史,而不是从空 `_chat_history` 开始。
- replyer 生成阶段不再过度依赖临时工具结果,而是显式承接 planner 已整理出的稳定参考信息。
- 调整上下文窗口策略:
- 不再只用固定条数近似控制
- 引入更大的私聊历史回放范围
- 优先保留“人物关系/最近共同事件/当前约定”这类高价值上下文
### 1.1 runtime 历史重建
- 触发时机:
- 私聊 runtime 首次创建时
- 私聊 runtime 被回收后再次恢复时
- 定时跟进任务唤醒私聊 runtime 时
- 基本流程:
1. 根据 `session_id` 查询最近一段真实消息
2. 将消息重新构造成 Maisaka 使用的 `LLMContextMessage`
3. 写回 `_chat_history`
4. 再叠加本轮新收到的消息与 injected reminders
- 第一版建议:
- 先取最近 `200 ~ 1000` 条私聊真实消息作为“候选历史池”
- 不是全部送给模型,而是交给后续 token 滑窗裁切
- 注意:
- 历史重建应优先复用现有消息入库格式和 `SessionBackedMessage` 构建逻辑
- 不要单独造一套“历史专用消息对象”
### 1.2 本地 tokenizer / token 预算器
- 当前项目里已有 token 使用统计,但那是模型返回 usage 后的结果,不足以做请求前选窗。
- 因此本阶段需要新增一个“请求前 token 估算器”抽象层。
- 目标不是一开始追求 100% 精确,而是先把“按 token 预算裁切”跑通。
- 建议方案:
- 新增统一的 `context_token_counter` / `prompt_token_estimator`
- 输入为即将送给模型的消息列表或其序列化结果
- 输出为估算 token 数
- tokenizer 选择建议:
- 优先选择与当前 OpenAI-compatible 请求栈兼容的本地 tokenizer 方案
- 若误差验证可接受,可直接作为私聊上下文预算器
- 若误差过大,再考虑换成更贴近目标模型的 tokenizer
- 降级方案:
- tokenizer 初始化失败时,可暂时退回“字符数 / 中文字数近似预算”
- 但只作为 fallback不作为主路径
### 1.3 token 滑动窗口
- 目标不是“把 256K 塞满”,而是“尽量稳定地用到 256K 级别能力”。
- 建议采用“两层预算”:
- 第一层:固定保底块
- 第二层:历史滑窗块
- 固定保底块建议包含:
- 系统提示
- 当前私聊对象信息块
- 当前约定 / 当前跟进原因
- 最近共同事件摘要
- 必要的工具定义
- 历史滑窗块建议逻辑:
1. 从最近消息开始向前累加
2. 每加入一条,重新累加 token
3. 超预算则停止
4. 保证最近几轮真实对话优先进入窗口
- 不建议简单地“全拼后从头一直 pop 到合法”作为唯一策略。
- 更推荐:
- 先固定高优先级块
- 再对普通历史做从近到远的预算填充
### 1.4 高优先级上下文保底
- 下面这些信息不应与普通旧聊天放在同一优先级:
- 当前私聊对象是谁
- 你和对方目前是什么关系
- 近期共同事件
- 最近承诺/约定
- 当前定时跟进原因
- 这些内容即使在历史极长的情况下,也应尽量保底进入窗口。
- 这部分内容应从:
- `person_info`
- A_memorix 的人物画像 / 关系证据 / episode
- 当前会话内最近承诺
统一汇总出来
### 1.5 planner → replyer 信息继承
- 目前 replyer 容易再次遗忘,核心原因之一是它并不稳定承接 planner 已经查到和整理过的信息。
- 第一版建议:
- planner 输出时显式生成“稳定参考信息块”
- replyer 直接消费这块,而不是只依赖再看一遍历史
- 这样即使 replyer 窗口比 planner 小很多,也能保持人物信息连续性。
### 1.6 配置建议
- 本阶段建议新增但保持默认兼容的配置项,例如:
- `chat.private_context_rebuild_enabled`
- `chat.private_context_rebuild_recent_limit`
- `chat.private_context_token_budget`
- `chat.private_context_reserved_tokens`
- `chat.private_context_inject_relation_info`
- `chat.private_context_recent_events_limit`
- `chat.replyer_context_token_budget`
- 默认值应尽量保守,避免对现有用户造成突然的延迟或成本上升。
### 借鉴来源
- `MoFox-Core`
- 借鉴其“关系信息 + 印象 + 偏好 +聊天流印象”统一拼装进私聊上下文的思路
- `Muice-Chatbot`
- 基本不借鉴实现,只参考“最近历史 + 旧记忆回注入”的轻量骨架
### 验收标准
- 同一个私聊会话在沉默一段时间后再次收到消息bot 不应明显忘记对方是谁。
- 重建后的 runtime 能带上最近一段真实聊天历史,而不是只看最后一条消息。
- 私聊中不需要用户重复提醒bot 也能较稳定记住:
- 对方称呼
- 近期正在聊的事
- 最近约定/承诺
- 在日志或调试信息里,能看到“重建历史条数”“对象信息注入块”的明确痕迹。
- 能在日志或调试信息里看到:
- 候选历史条数
- 最终入窗条数
- 估算 token 数
- 被预算裁掉的原因
- 在高上下文测试下planner 能稳定工作在 `256K` 级别预算附近,而不是仍然被 60 条历史限制住。
- replyer 即使使用更小窗口,也不应明显丢失 planner 已确认的人物信息。
---
## 阶段 2定时跟进 V1
### 本阶段要解决的问题
- 当前私聊没有“我之后再来找你”的能力。
- 即使用户和 bot 约好了未来某个时间点,系统也无法主动在那时重新思考。
- 需要主动发言时,只能强行伪装成 `reply(msg_id=...)`,不自然。
- 如果直接让 planner 裸写 `message_text` 并发送,会让主动私聊和普通回复走成两套文案体系。
### 本阶段要增加/修改的点
- `☐` 新 builtin tool`schedule_private_followup`
- `☐` 新 builtin tool`send_private_message`
- `☐` 私聊定时跟进任务表
- `☐` 后台调度器
- `☐` runtime 的“带 reminder 唤醒 planner”入口
### 如何做
- `schedule_private_followup`
- 只允许在当前私聊里创建未来任务
- 记录触发时间、跟进原因、当时承诺话术
- 后台调度器
- 轮询 `pending` 任务
- 到点后 claim 任务并唤醒当前私聊 runtime
- runtime 新入口
-`<system-reminder>` 形式注入“这不是用户新消息,而是一次到点跟进触发”
- planner 基于当前上下文重新判断要不要发
- `send_private_message`
- 不直接裸发 `message_text`
- 而是作为 `replyer` 的新发送语义出口
- 复用现有人设、表达习惯、replyer 文案生成链路
- 最终仍复用现有 `send_service` / Platform IO / 写库 / 历史同步
### 借鉴来源
- `MoFox-Core`
- 借鉴“主动思考不是直接发消息,而是先重进上下文再判断”的理念
- `Muice-Chatbot`
- 明确不采用其“到点直接发固定文本”的路径
- 当前 maibot 架构
- 延续“planner 负责想、replyer 负责写”的职责分离,不让主动私聊成为例外
### 验收标准
- 私聊中可以成功创建一条未来跟进任务。
- 到点后系统会唤醒 planner而不是直接发写死文本。
- planner 可以根据当前情况选择:
- 主动发一条消息
- 什么都不发,直接结束
- 再次约下一次跟进
- 主动发出的消息是普通 bot 消息,不带旧消息引用。
- 主动发出的文本风格应与普通 `reply()` 路径保持一致,不应明显像另一套文案系统。
- 发送成功后仍会进入:
- 现有消息存储
- Maisaka 历史同步 - Maisaka 历史同步
- memory automation
## 端到端流转链路 ### `send_private_message` 设计细化
### 1. 用户触发
- 用户在私聊中表达未来某个时间点需要 bot 主动跟进。
- 当前消息照常进入现有链路:
- 消息接收
- HeartFlow runtime
- Timing Gate
- Planner
### 2. Planner 决策
- Planner 分析当前局势后,可直接调用定时跟进工具。
- 工具应为 planner 直接可见的 action tool不走 deferred tools。
- 同一轮里planner 仍可继续:
- 调用 `reply`
- 正常回复用户
- 告知用户已经记下这次未来跟进
### 3. 工具执行
- 暂定工具名:`schedule_private_followup`
- 工具接收参数后,直接写入任务存储。
- 工具只允许操作当前私聊会话,不允许 LLM 指定别的 session。
- tool result 与其他工具一样,复用现有 tool call 记录与上下文链路。
### 4. 后台调度器轮询
- 独立后台调度器定期扫描到期任务。
- 扫描条件:
- `status = pending`
- `send_at_ts <= now`
- 任务取出后,不直接发送,而是进入“会话唤醒”流程。
### 5. 会话唤醒
- 调度器根据 `session_id` 定位或恢复对应私聊会话。
- 如果对应 runtime 当前不在内存中,应先恢复/创建该会话的 Maisaka runtime。
- 调度器调用一个新的运行时入口,例如:
- `trigger_scheduled_followup(task)`
- 或同等语义的方法
- 这个入口负责以“额外注入消息”的形式进入 planner而不是伪造用户消息。
### 6. 到点后的 planner 行为
- 唤醒后的 planner 继续使用项目原有的主 prompt。
- 同时在请求尾部追加一条 `injected_user_messages`,内容使用项目已有的 `<system-reminder>` 风格。
- planner 基于当前上下文重新分析后,可以:
- 调用 `send_private_message`
- 调用 `reply`(仅当它明确需要围绕某条旧消息回复时)
- 调用 `finish`
- 再次调用 `schedule_private_followup`
### 7. 主动发送
- 当 planner 判断应主动发言时,调用 `send_private_message`
- `send_private_message` 直接复用现有 `send_service.text_to_stream_with_message(...)`
- 发送参数应满足:
- `stream_id = 当前 ToolExecutionContext.session_id`
- `storage_message = True`
- `sync_to_maisaka_history = True`
- `maisaka_source_kind` 建议使用新的主动发送来源标记,例如:
- `proactive_send`
- 若后续需要区分定时唤醒来源,可再在 metadata 中补充 `trigger_source=scheduled_followup`
### 8. 任务完成
- 本次唤醒流程成功结束后,原任务应被标记为完成。
- “完成”不等于“一定发了消息”。
- 只要本次唤醒和 planner 处理已成功结束,即可完成任务,并记录最终动作结果。
## 工具设计
### 工具 1`schedule_private_followup`
#### 工具职责 #### 工具职责
- 当前私聊创建一条未来触发的定时跟进任务 - 当前私聊会话中主动发出一条可见消息
- 任务到点后唤醒当前私聊的 planner而不是直接发送固定文本。
#### 工具参数
- `send_at`
- 类型:`string`
- 含义:触发时间
- 建议:由 LLM 提供标准化后的绝对时间字符串
- `followup_reason`
- 类型:`string`
- 含义:这次未来跟进的原因/事项摘要
- 作用:到点时注入 reminder帮助 planner 理解为什么会被唤醒
- `assistant_commitment_text`
- 类型:`string`
- 含义当前这轮里bot 对用户作出的那句“未来会来找你/提醒你/跟进你”的承诺话术
- 作用:到点时也注入给 planner帮助它和之前的承诺保持一致
- `replace_existing`
- 类型:`boolean`
- 含义:是否覆盖当前私聊中尚未执行的旧跟进任务
- 默认建议:`false`
#### 工具隐含上下文
- 不要求 LLM 传 `session_id`
- 不要求 LLM 传 `user_id`
- 后端直接从当前 `ToolExecutionContext.session_id` 取目标私聊
- 如果当前不是私聊,工具直接失败
#### 工具返回
- 成功时至少返回:
- `task_id`
- `session_id`
- `send_at`
- `followup_reason`
- `assistant_commitment_text`
- `replace_existing`
- 若发生覆盖,返回被取消的旧任务 ID 列表
- 失败时返回明确原因:
- 非私聊
- 时间非法
- 跟进原因为空
- 承诺话术为空
- 存储失败
### 工具 2`send_private_message`
#### 工具职责
- 在当前私聊会话中主动发送一条可见消息。
- 不依赖 `msg_id` - 不依赖 `msg_id`
- 不默认带引用回复。 - 不默认带引用回复。
- 语义上是“主动开口”,但文案生成仍应走 `replyer`,而不是绕开 replyer 直接裸发。
#### 工具参数 #### 参数设计建议
- `message_text` - 不建议把主参数设计成裸 `message_text`
- 类型:`string` - 更推荐让 planner 提供“为什么说”,让 replyer 负责“具体怎么说”。
- 含义:要主动发送给当前私聊对象的文本内容 - 第一版建议参数:
- `proactive_reason`
- 类型:`string`
- 含义:这次主动发言的直接理由,作为 replyer 的“最新推理”
- `reference_info`
- 类型:`string`
- 含义:本轮主动发言依赖的事实性参考信息
- `trigger_source`
- 类型:`string`
- 含义:触发来源,例如 `scheduled_followup`
- `assistant_commitment_text`
- 类型:`string`
- 含义:若这次主动发言是在延续先前承诺,可作为风格一致性参考
#### 工具隐含上下文 #### 实现建议
- 不要求 LLM 传 `session_id` - `send_private_message` 应复用当前 `reply()` 已有的 replyer 生成链路。
- 不允许 LLM 指定别的目标会话 - 推荐执行路径:
- 后端直接使用当前 `ToolExecutionContext.session_id` 1. planner 调用 `send_private_message`
2. tool 内部获取当前会话 replyer
3. 以“主动发言场景”调用 `generate_reply_with_context(...)`
4. 此时 `reply_message=None`
5. `reply_reason=proactive_reason`
6. `reference_info` 中整理注入 `reference_info + assistant_commitment_text + trigger_source`
7. replyer 生成文本后,再复用现有发送链路发出
#### 工具返回 #### 返回建议
- 成功时至少返回: - 成功时至少返回:
- `session_id` - `session_id`
- `message_text` - `generated_message_text`
- `sent_message_id` - `sent_message_id`
- `maisaka_source_kind`
- 失败时返回明确原因: - 失败时返回明确原因:
- 非私聊 - 非私聊
- 文本为空 - replyer 生成失败
- 发送失败 - 发送失败
### 工具关系 ---
- `reply`:回应某条已有消息
- `send_private_message`:主动发言,不依赖某条已有消息
- `schedule_private_followup`:创建未来跟进任务,负责到点后唤醒 planner
## 任务数据模型 ## 阶段 3主动私聊护栏与轻量主动能力
### 建议字段 ### 本阶段要解决的问题
- `id` - 即使有定时跟进,系统也缺少“主动前的工程护栏”。
- `session_id` - 后续如果要做更常态化的主动私聊,没有最小打扰控制会很容易打扰用户。
- `send_at_ts`
- `status`
- `followup_reason`
- `assistant_commitment_text`
- `created_at_ts`
- `updated_at_ts`
- `created_by_tool_call_id`
- `cancelled_by_tool_call_id`
- `triggered_at_ts`
- `completed_at_ts`
- `completion_action`
- `sent_message_id`
- `last_error`
- `replace_existing`
### 状态 ### 本阶段要增加/修改的点
- `pending` - `☐` 静默时段
- `running` - `☐` 最小主动间隔
- `completed` - `☐` 可选每日上限
- `cancelled` - `☐` skip reason 与 guardrail snapshot
- `failed` - `☐` 轻量主动开场/问候 fallback
### 状态语义 ### 如何做
- `pending`:已创建,等待到点唤醒 - 在“任务到期”与“真正唤醒 planner”之间插入规则层
- `running`:调度器已取出,正在执行本次唤醒 - 私聊校验
- `completed`:本次唤醒流程已成功结束,无论是否真的发出消息 - 状态校验
- `cancelled`:被显式取消或被新任务覆盖 - 静默时段判断
- `failed`:本次唤醒流程执行失败 - 最小主动间隔判断
- 可选每日上限判断
- 对被跳过的任务记录:
- `skip_reason`
- `guardrail_snapshot_json`
- 为未来“非约定型主动私聊”预留轻量主动开场能力:
- 轻问候
- 轻话题冒泡
- 但此时仍不做完整主动聊天系统
### 完成动作建议 ### 借鉴来源
- `sent_private_message` - `MoFox-Core`
- `skipped` - 重点借鉴其主动思考调度器中的工程护栏思路
- `rescheduled` - `Muice-Chatbot`
- `unknown` - 借鉴其轻量问候/轻话题池思路,仅作为 fallback
## 覆盖规则 ### 验收标准
- 明显不适合打扰的时段,任务会被安全跳过或延后,而不是强行唤醒 planner。
- 日志中能明确看到“为什么没发”的 skip reason。
- 即使以后加入更强主动私聊,这一层规则也能直接复用。
### V1 定义 ---
- `replace_existing = true` 时:
- 将当前 `session_id` 下所有 `pending` 任务置为 `cancelled`
- 再创建当前新任务
- `replace_existing = false` 时:
- 不取消旧任务
- 直接新增当前任务
### 这样做的原因 ## 阶段 4私聊状态机活人感增强
- 不引入模糊分类
- 不猜“哪个旧任务和哪个新任务算同类”
- 规则简单、确定、易解释
## 到点注入给 planner 的提示方式 ### 本阶段要解决的问题
- 当前主动私聊即使能发,也缺少“发完之后等你回复”的真实感。
- 系统无法区分“及时回”“晚回”“一直没回”。
### 复用原则 ### 本阶段要增加/修改的点
- 继续复用原有 [prompts/zh-CN/maisaka_chat.prompt](/D:/mai-bot-align-pre17/prompts/zh-CN/maisaka_chat.prompt) - `☐` `WAITING / IDLE` 私聊状态机
- 不复制一整份新 prompt - `☐` 主动发送后的等待态
- 不把 reminder 伪装成真实聊天历史消息 - `☐` 超时后再判断是否继续等、追问或闭嘴
- 复用现有 `injected_user_messages` 机制 - `☐` 连续超时计数与打扰保护
### 风格要求 ### 如何做
- 注入内容应沿用项目现有 `<system-reminder>...</system-reminder>` 风格 - 在阶段 2 的基础上,扩展主动发送后的状态记录:
- 它在请求里以 `user` 身份追加,但语义上明确说明“不是用户刚刚发来的新消息” - 最近一次主动发送时间
- 应提醒 planner - 是否正在等待回复
- 这是一次定时跟进触发 - 等待配置
- 当前为什么会被唤醒 - 连续超时计数
- 你之前对用户说过什么 - 收到用户消息时区分:
- 可以主动发言,也可以不发言 - `reply_in_time`
- 除非明确需要回应某条旧消息,否则不要默认调用 `reply` - `reply_late`
- `new_message`
- 等待超时后,再重新进行一轮轻量判断:
- 继续等
- 轻追问
- 结束等待,不再打扰
### 提示文案打样 ### 借鉴来源
```text - `MoFox-Core`
<system-reminder> - 这是最核心的借鉴来源,重点是其 `WAITING / IDLE` 与超时后再思考的设计
以下内容不是用户刚刚发来的新消息,而是当前私聊的一次定时跟进触发。 - `Muice-Chatbot`
- 基本不借鉴
触发时间:{trigger_at} ### 验收标准
- 主动发言后,系统能进入等待态,而不是把这件事完全忘掉。
- 用户及时回复和很久后才回复时,系统的反应能出现可区分差异。
- 多次未回复后,系统会自动降低继续打扰的倾向。
你之前在这个私聊中设置过一次定时跟进。 ---
当时记录的跟进原因:
{followup_reason}
当时你已经对用户说过: ## 阶段 5关系阶段与主线推进
{assistant_commitment_text}
请结合当前上下文重新分析现在是否适合主动发言: ### 本阶段要解决的问题
- 如果仍然适合,就继续正常分析,并在需要时调用 send_private_message - 当前即使记住了人、能主动聊,也还没有真正的关系推进结构
- 如果当前话题已经变化、用户已经提到过同类内容、约定已经失效,或者现在不适合打扰,就不要强行发送,直接 finish - 缺少“朋友 → 更亲密阶段”的可持续演进逻辑
- 不要把这段提醒当成用户的新发言。
- 除非你明确是在回应历史中的某条具体消息,否则不要默认调用 reply。
</system-reminder>
```
## 与现有链路的复用 ### 本阶段要增加/修改的
- `☐` 关系阶段状态
- `☐` 阶段推进条件
- `☐` 近期主线事件/共同事件摘要
- `☐` 更适合私聊恋爱化推进的上下文注入
### Prompt 与上下文 ### 如何做
- 复用 Maisaka planner 主 prompt - 在现有 `person_info` / A_memorix 证据层之上,增加一层更明确的关系状态表达。
- 复用现有上下文窗口选择逻辑 - 阶段推进仍然尽量保持轻规则,不急着做全硬编码 romance engine。
- 复用 `injected_user_messages` 追加尾部提醒 - 优先做:
- 阶段状态
- 最近共同事件摘要
- 对阶段敏感的 prompt 注入
- 后续再看是否要加入更强规则。
### 发送 ### 借鉴来源
- `send_private_message` 复用 `send_service.text_to_stream_with_message(...)` - `MoFox-Core`
- 继续复用现有 `Platform IO` 和 Napcat 出口 - 借鉴其“关系分数 + 关系阶段 + 印象文案”的分层思路
- 不直接采用其当前阶段命名作为最终产品逻辑
- `Muice-Chatbot`
- 不足以支撑这一阶段
### 存储 ### 验收标准
- 复用现有消息发送成功后的消息存储链路 - 私聊中的称呼、关心方式、主动发言方式,会随关系阶段出现可感知变化。
- 最近共同事件能够被作为关系推进依据,而不是每次都从零开始。
- 系统在长期私聊中呈现出更稳定的关系连续性,而不是只有短期记忆连续性。
### 上下文写回 ---
- 复用 `sync_to_maisaka_history=True`
- 复用 runtime 的 `append_sent_message_to_chat_history(...)`
### Tool call 记录 ## 配置与 WebUI 影响评估
- 创建任务时,复用现有 Planner tool 执行记录链路
- 到点唤醒不伪造一条新的 LLM tool call
- 到点执行本质上属于调度器唤醒会话,而不是伪造用户发言
## 需要补的实现模块 ### 配置面
- 配置面预计是“小改”,不是大改。
- 建议新增独立配置块,例如:
- `private_followup.enabled`
- `private_followup.scheduler_interval_seconds`
- `private_followup.quiet_hours_start`
- `private_followup.quiet_hours_end`
- `private_followup.min_interval_between_proactive_seconds`
- `private_followup.max_daily_followups_per_session`
- `private_followup.inject_relation_info`
- `private_followup.recent_shared_events_limit`
- 原则:
- 默认值完整
- 老配置不动也能跑
- 优先改模板并递增版本号
### 1. 新内置工具 ### WebUI 面
- 新增 `schedule_private_followup` builtin tool - V1 到 V3 原则上不需要大改聊天协议。
- 新增 `send_private_message` builtin tool - 聊天渲染层:
- `send_private_message` 发送的仍然是普通 bot 消息
- 不要求新增 `MessageSegment.type`
- 工具渲染层:
- 新工具复用现有 tool record 展示链路
- 前端最多看到新的 `tool_name`
- 可选增强:
- 定时跟进任务列表
- 任务取消/重试管理页
- 主动护栏状态可视化
- 私聊状态机状态展示
### 2. 任务存储 ## 分阶段验收顺序建议
- 新增一张私聊定时跟进任务表 - 第 0 阶段先稳定开发环境,避免自动更新打断改造过程。
- 提供最小操作: - 第一阶段先解决遗忘与历史重建,不急着上主动聊天。
- create - 第二阶段打通定时跟进 V1。
- cancel pending by session - 第三阶段补主动护栏与轻量主动能力。
- list due pending - 第四阶段再做 `WAITING / IDLE` 状态机。
- mark running - 第五阶段最后做关系阶段与主线推进。
- mark completed
- mark failed
### 3. 后台调度器 ## 当前最推荐的开发顺序
- 独立异步循环 1. 断开 WebUI / 本体自动更新检测
- 固定间隔扫描 due tasks 2. 私聊对象信息常态注入
- 负责 claim 任务、唤醒会话、更新状态 3. runtime 历史重建
4. planner → replyer 信息继承增强
### 4. Maisaka 运行时入口 5. `schedule_private_followup`
- 为运行时新增一个“带 reminder 进入 planner 一轮”的入口 6. `send_private_message`
- 该入口负责把 `<system-reminder>` 注入到 planner 请求尾部 7. `send_private_message` 的 replyer 驱动实现
8. 后台调度器与 runtime 唤醒入口
### 5. 主程序启动 9. 到点前护栏
- 在现有后台任务体系中注册该调度器 10. `WAITING / IDLE` 扩展
11. 阶段/主线推进
## 最小验证路径
### 验证目标
- 确认创建任务、到点唤醒、重新思考、主动发送、写库、上下文同步都能走通
### 建议路径
1. 在私聊中让 bot 创建一条几分钟后的 `schedule_private_followup`
2. 当轮正常 `reply` 给用户,说明之后会再来跟进
3. 到点后由调度器唤醒当前私聊的 planner
4. planner 收到 `<system-reminder>` 后重新分析
5. 若判断合适,调用 `send_private_message`
6. 发送应复用现有 Napcat / Platform IO 出口成功发出
7. 发出的消息应进入:
- 现有消息存储
- Maisaka 上下文历史
- 现有 memory automation
## 当前建议的落地顺序
- 第一步:先把工具边界定下来:`schedule_private_followup``send_private_message`
- 第二步:把任务表与任务存储定下来
- 第三步:补运行时“带 reminder 唤醒 planner”入口
- 第四步:补后台调度器并接入主程序
- 第五步:打通主动发送、写库、上下文同步
- 第六步:再考虑取消/查看任务工具