Compare commits

...

9 Commits

Author SHA1 Message Date
gemini-cli-robot c593a29647 chore(release): v0.26.0-preview.4 2026-01-25 17:57:54 +00:00
gemini-cli-robot cebe386d79 fix(patch): cherry-pick 12a5490 to release/v0.26.0-preview.3-pr-17395 [CONFLICTS] (#17424)
Co-authored-by: Adib234 <30782825+Adib234@users.noreply.github.com>
Co-authored-by: Sandy Tao <sandytao520@icloud.com>
Co-authored-by: A.K.M. Adib <adibakm@google.com>
2026-01-25 08:59:09 -05:00
gemini-cli-robot 1c207e2f82 chore(release): v0.26.0-preview.3 2026-01-22 22:27:22 +00:00
gemini-cli-robot ee87c98f43 fix(patch): cherry-pick addb57c to release/v0.26.0-preview.2-pr-17331 [CONFLICTS] (#17345)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-01-22 14:04:49 -08:00
gemini-cli-robot 75b5eeeb12 chore(release): v0.26.0-preview.2 2026-01-22 19:03:19 +00:00
gemini-cli-robot 0fa9a54088 fix(patch): cherry-pick 87a0db2 to release/v0.26.0-preview.1-pr-17308 [CONFLICTS] (#17317)
Co-authored-by: Shreya Keshive <shreyakeshive@google.com>
2026-01-22 18:34:24 +00:00
gemini-cli-robot 603e66b2ea chore(release): v0.26.0-preview.1 2026-01-22 01:02:56 +00:00
gemini-cli-robot dc8fc75ac0 fix(patch): cherry-pick 61040d0 to release/v0.26.0-preview.0-pr-17247 to patch version v0.26.0-preview.0 and create version 0.26.0-preview.1 (#17258)
Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
2026-01-22 00:38:07 +00:00
gemini-cli-robot 2b58605227 chore(release): v0.26.0-preview.0 2026-01-21 04:56:03 +00:00
28 changed files with 293 additions and 214 deletions
+1 -1
View File
@@ -909,7 +909,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`hooksConfig.enabled`** (boolean):
- **Description:** Canonical toggle for the hooks system. When disabled, no
hooks will be executed.
- **Default:** `false`
- **Default:** `true`
- **`hooksConfig.disabled`** (array):
- **Description:** List of hook names (commands) that should be disabled.
+22 -33
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"workspaces": [
"packages/*"
],
@@ -2474,7 +2474,6 @@
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
@@ -2655,7 +2654,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2689,7 +2687,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -3058,7 +3055,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -3092,7 +3088,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1"
@@ -3145,7 +3140,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
@@ -4358,7 +4352,6 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4636,7 +4629,6 @@
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.35.0",
"@typescript-eslint/types": "8.35.0",
@@ -5641,7 +5633,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6086,7 +6077,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/array-includes": {
"version": "3.1.9",
@@ -7370,6 +7362,7 @@
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -8689,7 +8682,6 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -9292,6 +9284,7 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@@ -9301,6 +9294,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -9310,6 +9304,7 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -9563,6 +9558,7 @@
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
@@ -9581,6 +9577,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -9589,13 +9586,15 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/finalhandler/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -10878,7 +10877,6 @@
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.7.tgz",
"integrity": "sha512-QHyxhNF5VonF5cRmdAJD/UPucB9nRx3FozWMjQrDGfBxfAL9lpyu72/MlFPgloS1TMTGsOt7YN6dTPPA6mh0Aw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
@@ -14063,7 +14061,8 @@
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/path-type": {
"version": "3.0.0",
@@ -14640,7 +14639,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -14651,7 +14649,6 @@
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
@@ -16911,7 +16908,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17135,8 +17131,7 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -17144,7 +17139,6 @@
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -17328,7 +17322,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -17491,6 +17484,7 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4.0"
}
@@ -17545,7 +17539,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -17659,7 +17652,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17672,7 +17664,6 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -18377,7 +18368,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -18393,7 +18383,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
"@google-cloud/storage": "^7.16.0",
@@ -18703,7 +18693,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -18807,7 +18797,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
@@ -18944,7 +18934,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -18967,7 +18956,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18984,7 +18973,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.26.0-nightly.20260115.6cb3ae4e0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.26.0-preview.4"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -26,7 +26,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.26.0-nightly.20260115.6cb3ae4e0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.26.0-preview.4"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -511,8 +511,5 @@ describe('migrate command', () => {
expect(debugLoggerLogSpy).toHaveBeenCalledWith(
'\nMigration complete! Please review the migrated hooks in .gemini/settings.json',
);
expect(debugLoggerLogSpy).toHaveBeenCalledWith(
'Note: Set hooksConfig.enabled to true in your settings to enable the hook system.',
);
});
});
@@ -244,9 +244,6 @@ export async function handleMigrateFromClaude() {
debugLogger.log(
'\nMigration complete! Please review the migrated hooks in .gemini/settings.json',
);
debugLogger.log(
'Note: Set hooksConfig.enabled to true in your settings to enable the hook system.',
);
} catch (error) {
debugLogger.error(`Error saving migrated hooks: ${getErrorMessage(error)}`);
}
+4 -4
View File
@@ -37,6 +37,7 @@ import {
type HookEventName,
type OutputFormat,
GEMINI_MODEL_ALIAS_AUTO,
coreEvents,
} from '@google/gemini-cli-core';
import {
type Settings,
@@ -47,7 +48,6 @@ import {
import { loadSandboxConfig } from './sandboxConfig.js';
import { resolvePath } from '../utils/resolvePath.js';
import { appEvents } from '../utils/events.js';
import { RESUME_LATEST } from '../utils/sessionUtils.js';
import { isWorkspaceTrusted } from './trustedFolders.js';
@@ -450,7 +450,7 @@ export async function loadCliConfig(
requestSetting: promptForSetting,
workspaceDir: cwd,
enabledExtensionOverrides: argv.extensions,
eventEmitter: appEvents as EventEmitter<ExtensionEvents>,
eventEmitter: coreEvents as EventEmitter<ExtensionEvents>,
clientVersion: await getVersion(),
});
await extensionManager.loadExtensions();
@@ -746,7 +746,7 @@ export async function loadCliConfig(
truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
truncateToolOutputLines: settings.tools?.truncateToolOutputLines,
enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation,
eventEmitter: appEvents,
eventEmitter: coreEvents,
useWriteTodos: argv.useWriteTodos ?? settings.useWriteTodos,
output: {
format: (argv.outputFormat ?? settings.output?.format) as OutputFormat,
@@ -763,7 +763,7 @@ export async function loadCliConfig(
// TODO: loading of hooks based on workspace trust
enableHooks:
(settings.tools?.enableHooks ?? true) &&
(settings.hooksConfig?.enabled ?? false),
(settings.hooksConfig?.enabled ?? true),
enableHooksUI: settings.tools?.enableHooks ?? true,
hooks: settings.hooks || {},
disabledHooks: settings.hooksConfig?.disabled || [],
@@ -872,6 +872,7 @@ describe('extension tests', () => {
);
const settings = loadSettings(tempWorkspaceDir).merged;
settings.hooksConfig.enabled = false;
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
+1 -1
View File
@@ -1646,7 +1646,7 @@ const SETTINGS_SCHEMA = {
label: 'Enable Hooks',
category: 'Advanced',
requiresRestart: false,
default: false,
default: true,
description:
'Canonical toggle for the hooks system. When disabled, no hooks will be executed.',
showInDialog: false,
+2
View File
@@ -1085,6 +1085,8 @@ describe('gemini.tsx main function exit codes', () => {
vi.mocked(loadSandboxConfig).mockResolvedValue({} as any);
vi.mocked(loadCliConfig).mockResolvedValue({
refreshAuth: vi.fn().mockRejectedValue(new Error('Auth failed')),
getRemoteAdminSettings: vi.fn().mockReturnValue(undefined),
isInteractive: vi.fn().mockReturnValue(true),
} as unknown as Config);
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
+6 -2
View File
@@ -373,6 +373,7 @@ export async function main() {
// Refresh auth to fetch remote admin settings from CCPA and before entering
// the sandbox because the sandbox will interfere with the Oauth2 web
// redirect.
let initialAuthFailed = false;
if (
settings.merged.security.auth.selectedType &&
!settings.merged.security.auth.useExternal
@@ -400,8 +401,7 @@ export async function main() {
}
} catch (err) {
debugLogger.error('Error authenticating:', err);
await runExitCleanup();
process.exit(ExitCodes.FATAL_AUTHENTICATION_ERROR);
initialAuthFailed = true;
}
}
@@ -427,6 +427,10 @@ export async function main() {
// another way to decouple refreshAuth from requiring a config.
if (sandboxConfig) {
if (initialAuthFailed) {
await runExitCleanup();
process.exit(ExitCodes.FATAL_AUTHENTICATION_ERROR);
}
let stdinData = '';
if (!process.stdin.isTTY) {
stdinData = await readStdin();
+28 -2
View File
@@ -103,6 +103,7 @@ import { RELAUNCH_EXIT_CODE } from '../utils/processUtils.js';
import type { SessionInfo } from '../utils/sessionUtils.js';
import { useMessageQueue } from './hooks/useMessageQueue.js';
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
import { useMcpStatus } from './hooks/useMcpStatus.js';
import { useSessionStats } from './contexts/SessionContext.js';
import { useGitBranchName } from './hooks/useGitBranchName.js';
import {
@@ -129,6 +130,7 @@ import {
} from './constants.js';
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
import { useInactivityTimer } from './hooks/useInactivityTimer.js';
import { isSlashCommand } from './utils/commandUtils.js';
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
return pendingHistoryItems.some((item) => {
@@ -861,6 +863,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
isActive: !embeddedShellFocused,
});
const { isMcpReady } = useMcpStatus(config);
const {
messageQueue,
addMessage,
@@ -871,6 +875,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
isConfigInitialized,
streamingState,
submitQuery,
isMcpReady,
});
cancelHandlerRef.current = useCallback(
@@ -909,10 +914,31 @@ Logging in with Google... Restarting Gemini CLI to continue.
const handleFinalSubmit = useCallback(
(submittedValue: string) => {
addMessage(submittedValue);
const isSlash = isSlashCommand(submittedValue.trim());
const isIdle = streamingState === StreamingState.Idle;
if (isSlash || (isIdle && isMcpReady)) {
void submitQuery(submittedValue);
} else {
// Check messageQueue.length === 0 to only notify on the first queued item
if (isIdle && !isMcpReady && messageQueue.length === 0) {
coreEvents.emitFeedback(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available and prompts will be queued.',
);
}
addMessage(submittedValue);
}
addInput(submittedValue); // Track input for up-arrow history
},
[addMessage, addInput],
[
addMessage,
addInput,
submitQuery,
isMcpReady,
streamingState,
messageQueue.length,
],
);
const handleClearScreen = useCallback(() => {
@@ -212,6 +212,9 @@ describe('KeypressContext', () => {
name: 'return',
sequence: '\r',
insertable: true,
shift: true,
meta: false,
ctrl: false,
}),
);
});
@@ -158,6 +158,9 @@ function bufferFastReturn(keypressHandler: KeypressHandler): KeypressHandler {
keypressHandler({
...key,
name: 'return',
shift: true, // to make it a newline, not a submission
ctrl: false,
meta: false,
sequence: '\r',
insertable: true,
});
@@ -1962,73 +1962,6 @@ describe('useGeminiStream', () => {
});
});
describe('MCP Discovery State', () => {
it('should block non-slash command queries when discovery is in progress and servers exist', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi
.fn()
.mockReturnValue(MCPDiscoveryState.IN_PROGRESS),
getMcpServerCount: vi.fn().mockReturnValue(1),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
});
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available.',
);
expect(mockSendMessageStream).not.toHaveBeenCalled();
});
it('should NOT block queries when discovery is NOT_STARTED but there are no servers', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi
.fn()
.mockReturnValue(MCPDiscoveryState.NOT_STARTED),
getMcpServerCount: vi.fn().mockReturnValue(0),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
});
expect(coreEvents.emitFeedback).not.toHaveBeenCalledWith(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available.',
);
expect(mockSendMessageStream).toHaveBeenCalled();
});
it('should NOT block slash commands even when discovery is in progress', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi
.fn()
.mockReturnValue(MCPDiscoveryState.IN_PROGRESS),
getMcpServerCount: vi.fn().mockReturnValue(1),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('/help');
});
expect(coreEvents.emitFeedback).not.toHaveBeenCalledWith(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available.',
);
});
});
describe('handleFinishedEvent', () => {
it('should add info message for MAX_TOKENS finish reason', async () => {
// Setup mock to return a stream with MAX_TOKENS finish reason
@@ -3182,68 +3115,4 @@ describe('useGeminiStream', () => {
});
});
});
describe('MCP Server Initialization', () => {
it('should allow slash commands to run while MCP servers are initializing', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi
.fn()
.mockReturnValue(MCPDiscoveryState.IN_PROGRESS),
getMcpServerCount: vi.fn().mockReturnValue(1),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('/help');
});
// Slash command should be handled, and no Gemini call should be made.
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/help');
expect(coreEvents.emitFeedback).not.toHaveBeenCalled();
});
it('should block normal prompts and provide feedback while MCP servers are initializing', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi
.fn()
.mockReturnValue(MCPDiscoveryState.IN_PROGRESS),
getMcpServerCount: vi.fn().mockReturnValue(1),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('a normal prompt');
});
// No slash command, no Gemini call, but feedback should be emitted.
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
expect(mockSendMessageStream).not.toHaveBeenCalled();
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available.',
);
});
it('should allow normal prompts to run when MCP servers are finished initializing', async () => {
const mockMcpClientManager = {
getDiscoveryState: vi.fn().mockReturnValue(MCPDiscoveryState.COMPLETED),
getMcpServerCount: vi.fn().mockReturnValue(1),
};
mockConfig.getMcpClientManager = () => mockMcpClientManager as any;
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('a normal prompt');
});
// Prompt should be sent to Gemini.
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
expect(mockSendMessageStream).toHaveBeenCalled();
expect(coreEvents.emitFeedback).not.toHaveBeenCalled();
});
});
});
@@ -31,7 +31,6 @@ import {
ValidationRequiredError,
coreEvents,
CoreEvent,
MCPDiscoveryState,
} from '@google/gemini-cli-core';
import type {
Config,
@@ -961,25 +960,6 @@ export const useGeminiStream = (
async ({ metadata: spanMetadata }) => {
spanMetadata.input = query;
const discoveryState = config
.getMcpClientManager()
?.getDiscoveryState();
const mcpServerCount =
config.getMcpClientManager()?.getMcpServerCount() ?? 0;
if (
!options?.isContinuation &&
typeof query === 'string' &&
!isSlashCommand(query.trim()) &&
mcpServerCount > 0 &&
discoveryState !== MCPDiscoveryState.COMPLETED
) {
coreEvents.emitFeedback(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available.',
);
return;
}
const queryId = `${Date.now()}-${Math.random()}`;
activeQueryIdRef.current = queryId;
if (
@@ -0,0 +1,97 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { act } from 'react';
import { render } from '../../test-utils/render.js';
import { useMcpStatus } from './useMcpStatus.js';
import {
MCPDiscoveryState,
type Config,
CoreEvent,
coreEvents,
} from '@google/gemini-cli-core';
describe('useMcpStatus', () => {
let mockConfig: Config;
let mockMcpClientManager: {
getDiscoveryState: Mock<() => MCPDiscoveryState>;
getMcpServerCount: Mock<() => number>;
};
beforeEach(() => {
mockMcpClientManager = {
getDiscoveryState: vi.fn().mockReturnValue(MCPDiscoveryState.NOT_STARTED),
getMcpServerCount: vi.fn().mockReturnValue(0),
};
mockConfig = {
getMcpClientManager: vi.fn().mockReturnValue(mockMcpClientManager),
} as unknown as Config;
});
const renderMcpStatusHook = (config: Config) => {
let hookResult: ReturnType<typeof useMcpStatus>;
function TestComponent({ config }: { config: Config }) {
hookResult = useMcpStatus(config);
return null;
}
render(<TestComponent config={config} />);
return {
result: {
get current() {
return hookResult;
},
},
};
};
it('should initialize with correct values (no servers)', () => {
const { result } = renderMcpStatusHook(mockConfig);
expect(result.current.discoveryState).toBe(MCPDiscoveryState.NOT_STARTED);
expect(result.current.mcpServerCount).toBe(0);
expect(result.current.isMcpReady).toBe(true);
});
it('should initialize with correct values (with servers, not started)', () => {
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
const { result } = renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
});
it('should not be ready while in progress', () => {
mockMcpClientManager.getDiscoveryState.mockReturnValue(
MCPDiscoveryState.IN_PROGRESS,
);
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
const { result } = renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
});
it('should update state when McpClientUpdate is emitted', () => {
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
mockMcpClientManager.getDiscoveryState.mockReturnValue(
MCPDiscoveryState.IN_PROGRESS,
);
const { result } = renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
mockMcpClientManager.getDiscoveryState.mockReturnValue(
MCPDiscoveryState.COMPLETED,
);
act(() => {
coreEvents.emit(CoreEvent.McpClientUpdate, new Map());
});
expect(result.current.discoveryState).toBe(MCPDiscoveryState.COMPLETED);
expect(result.current.isMcpReady).toBe(true);
});
});
+51
View File
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect, useState } from 'react';
import {
type Config,
coreEvents,
MCPDiscoveryState,
CoreEvent,
} from '@google/gemini-cli-core';
export function useMcpStatus(config: Config) {
const [discoveryState, setDiscoveryState] = useState<MCPDiscoveryState>(
() =>
config.getMcpClientManager()?.getDiscoveryState() ??
MCPDiscoveryState.NOT_STARTED,
);
const [mcpServerCount, setMcpServerCount] = useState<number>(
() => config.getMcpClientManager()?.getMcpServerCount() ?? 0,
);
useEffect(() => {
const onChange = () => {
const manager = config.getMcpClientManager();
if (manager) {
setDiscoveryState(manager.getDiscoveryState());
setMcpServerCount(manager.getMcpServerCount());
}
};
coreEvents.on(CoreEvent.McpClientUpdate, onChange);
return () => {
coreEvents.off(CoreEvent.McpClientUpdate, onChange);
};
}, [config]);
// We are ready if discovery has completed, OR if it hasn't even started and there are no servers.
const isMcpReady =
discoveryState === MCPDiscoveryState.COMPLETED ||
(discoveryState === MCPDiscoveryState.NOT_STARTED && mcpServerCount === 0);
return {
discoveryState,
mcpServerCount,
isMcpReady,
};
}
@@ -28,6 +28,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: boolean;
streamingState: StreamingState;
submitQuery: (query: string) => void;
isMcpReady: boolean;
}) => {
let hookResult: ReturnType<typeof useMessageQueue>;
function TestComponent(props: typeof initialProps) {
@@ -51,6 +52,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
expect(result.current.messageQueue).toEqual([]);
@@ -62,6 +64,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
act(() => {
@@ -80,6 +83,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
act(() => {
@@ -100,6 +104,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
act(() => {
@@ -120,6 +125,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
act(() => {
@@ -133,11 +139,12 @@ describe('useMessageQueue', () => {
);
});
it('should auto-submit queued messages when transitioning to Idle', async () => {
it('should auto-submit queued messages when transitioning to Idle and MCP is ready', async () => {
const { result, rerender } = renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
// Add some messages
@@ -157,11 +164,37 @@ describe('useMessageQueue', () => {
});
});
it('should wait for MCP readiness before auto-submitting', async () => {
const { result, rerender } = renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
isMcpReady: false,
});
// Add some messages while Idle but MCP not ready
act(() => {
result.current.addMessage('Delayed message');
});
expect(result.current.messageQueue).toEqual(['Delayed message']);
expect(mockSubmitQuery).not.toHaveBeenCalled();
// Transition MCP to ready
rerender({ isMcpReady: true });
await waitFor(() => {
expect(mockSubmitQuery).toHaveBeenCalledWith('Delayed message');
expect(result.current.messageQueue).toEqual([]);
});
});
it('should not auto-submit when queue is empty', () => {
const { rerender } = renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
// Transition to Idle with empty queue
@@ -175,6 +208,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
// Add messages
@@ -194,6 +228,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
// Start responding
@@ -235,6 +270,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
// Add multiple messages
@@ -265,6 +301,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: true,
});
let poppedMessages: string | undefined = 'not-undefined';
@@ -281,6 +318,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: false,
});
act(() => {
@@ -301,6 +339,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: false,
});
act(() => {
@@ -330,6 +369,7 @@ describe('useMessageQueue', () => {
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
isMcpReady: false,
});
// Add messages
+10 -1
View File
@@ -11,6 +11,7 @@ export interface UseMessageQueueOptions {
isConfigInitialized: boolean;
streamingState: StreamingState;
submitQuery: (query: string) => void;
isMcpReady: boolean;
}
export interface UseMessageQueueReturn {
@@ -30,6 +31,7 @@ export function useMessageQueue({
isConfigInitialized,
streamingState,
submitQuery,
isMcpReady,
}: UseMessageQueueOptions): UseMessageQueueReturn {
const [messageQueue, setMessageQueue] = useState<string[]>([]);
@@ -67,6 +69,7 @@ export function useMessageQueue({
if (
isConfigInitialized &&
streamingState === StreamingState.Idle &&
isMcpReady &&
messageQueue.length > 0
) {
// Combine all messages with double newlines for clarity
@@ -75,7 +78,13 @@ export function useMessageQueue({
setMessageQueue([]);
submitQuery(combinedMessage);
}
}, [isConfigInitialized, streamingState, messageQueue, submitQuery]);
}, [
isConfigInitialized,
streamingState,
isMcpReady,
messageQueue,
submitQuery,
]);
return {
messageQueue,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {
+1 -1
View File
@@ -682,7 +682,7 @@ export class Config {
? false
: (params.useWriteTodos ?? true);
this.enableHooksUI = params.enableHooksUI ?? true;
this.enableHooks = params.enableHooks ?? false;
this.enableHooks = params.enableHooks ?? true;
this.disabledHooks = params.disabledHooks ?? [];
this.codebaseInvestigatorSettings = {
@@ -245,6 +245,7 @@ export class McpClientManager {
if (currentPromise === this.discoveryPromise) {
this.discoveryPromise = undefined;
this.discoveryState = MCPDiscoveryState.COMPLETED;
this.eventEmitter?.emit('mcp-client-update', this.clients);
}
})
.catch(() => {}); // Prevents unhandled rejection from the .finally branch
@@ -273,6 +274,12 @@ export class McpClientManager {
this.cliConfig.getMcpServerCommand(),
);
if (Object.keys(servers).length === 0) {
this.discoveryState = MCPDiscoveryState.COMPLETED;
this.eventEmitter?.emit('mcp-client-update', this.clients);
return;
}
this.eventEmitter?.emit('mcp-client-update', this.clients);
await Promise.all(
Object.entries(servers).map(([name, config]) =>
+5 -1
View File
@@ -5,6 +5,8 @@
*/
import { EventEmitter } from 'node:events';
import type { McpClient } from '../tools/mcp-client.js';
import type { ExtensionEvents } from './extensionLoader.js';
/**
* Defines the severity level for user-facing feedback.
@@ -115,6 +117,7 @@ export enum CoreEvent {
Output = 'output',
MemoryChanged = 'memory-changed',
ExternalEditorClosed = 'external-editor-closed',
McpClientUpdate = 'mcp-client-update',
SettingsChanged = 'settings-changed',
HookStart = 'hook-start',
HookEnd = 'hook-end',
@@ -123,13 +126,14 @@ export enum CoreEvent {
RetryAttempt = 'retry-attempt',
}
export interface CoreEvents {
export interface CoreEvents extends ExtensionEvents {
[CoreEvent.UserFeedback]: [UserFeedbackPayload];
[CoreEvent.ModelChanged]: [ModelChangedPayload];
[CoreEvent.ConsoleLog]: [ConsoleLogPayload];
[CoreEvent.Output]: [OutputPayload];
[CoreEvent.MemoryChanged]: [MemoryChangedPayload];
[CoreEvent.ExternalEditorClosed]: never[];
[CoreEvent.McpClientUpdate]: Array<Map<string, McpClient> | never>;
[CoreEvent.SettingsChanged]: never[];
[CoreEvent.HookStart]: [HookStartPayload];
[CoreEvent.HookEnd]: [HookEndPayload];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
"version": "0.26.0-preview.4",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {
+2 -2
View File
@@ -1574,8 +1574,8 @@
"enabled": {
"title": "Enable Hooks",
"description": "Canonical toggle for the hooks system. When disabled, no hooks will be executed.",
"markdownDescription": "Canonical toggle for the hooks system. When disabled, no hooks will be executed.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `false`",
"default": false,
"markdownDescription": "Canonical toggle for the hooks system. When disabled, no hooks will be executed.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `true`",
"default": true,
"type": "boolean"
},
"disabled": {