generated from nhcarrigan/template
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7a1ab89ad8
|
|||
| c4af551375 | |||
| b88f25a61b | |||
| 5663b1c09a | |||
|
542d2eb315
|
|||
| 4134e11c88 |
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hikari-desktop",
|
"name": "hikari-desktop",
|
||||||
"version": "1.12.0",
|
"version": "1.14.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"@tauri-apps/cli": "2.10.0",
|
"@tauri-apps/cli": "2.10.0",
|
||||||
"@testing-library/jest-dom": "6.9.1",
|
"@testing-library/jest-dom": "6.9.1",
|
||||||
"@testing-library/svelte": "5.3.1",
|
"@testing-library/svelte": "5.3.1",
|
||||||
"@vitest/coverage-v8": "4.1.0",
|
"@vitest/coverage-v8": "4.0.18",
|
||||||
"eslint": "9.39.3",
|
"eslint": "9.39.3",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-svelte": "3.15.0",
|
"eslint-plugin-svelte": "3.15.0",
|
||||||
|
|||||||
Generated
+32
-58
@@ -151,8 +151,8 @@ importers:
|
|||||||
specifier: 5.3.1
|
specifier: 5.3.1
|
||||||
version: 5.3.1(svelte@5.53.5)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))
|
version: 5.3.1(svelte@5.53.5)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: 4.1.0
|
specifier: 4.0.18
|
||||||
version: 4.1.0(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))
|
version: 4.0.18(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: 9.39.3
|
specifier: 9.39.3
|
||||||
version: 9.39.3(jiti@2.6.1)
|
version: 9.39.3(jiti@2.6.1)
|
||||||
@@ -226,8 +226,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/parser@7.29.2':
|
'@babel/parser@7.28.6':
|
||||||
resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
|
resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -235,8 +235,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
|
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/types@7.29.0':
|
'@babel/types@7.28.6':
|
||||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@bcoe/v8-coverage@1.0.2':
|
'@bcoe/v8-coverage@1.0.2':
|
||||||
@@ -1118,11 +1118,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
|
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@vitest/coverage-v8@4.1.0':
|
'@vitest/coverage-v8@4.0.18':
|
||||||
resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==}
|
resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@vitest/browser': 4.1.0
|
'@vitest/browser': 4.0.18
|
||||||
vitest: 4.1.0
|
vitest: 4.0.18
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@vitest/browser':
|
'@vitest/browser':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1144,9 +1144,6 @@ packages:
|
|||||||
'@vitest/pretty-format@4.0.18':
|
'@vitest/pretty-format@4.0.18':
|
||||||
resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
|
resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
|
||||||
|
|
||||||
'@vitest/pretty-format@4.1.0':
|
|
||||||
resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==}
|
|
||||||
|
|
||||||
'@vitest/runner@4.0.18':
|
'@vitest/runner@4.0.18':
|
||||||
resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
|
resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
|
||||||
|
|
||||||
@@ -1159,9 +1156,6 @@ packages:
|
|||||||
'@vitest/utils@4.0.18':
|
'@vitest/utils@4.0.18':
|
||||||
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
|
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
|
||||||
|
|
||||||
'@vitest/utils@4.1.0':
|
|
||||||
resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==}
|
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1209,8 +1203,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ast-v8-to-istanbul@1.0.0:
|
ast-v8-to-istanbul@0.3.10:
|
||||||
resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==}
|
resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==}
|
||||||
|
|
||||||
axobject-query@4.1.0:
|
axobject-query@4.1.0:
|
||||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||||
@@ -1266,9 +1260,6 @@ packages:
|
|||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
|
||||||
|
|
||||||
cookie@0.6.0:
|
cookie@0.6.0:
|
||||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -1564,12 +1555,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
js-tokens@10.0.0:
|
|
||||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
js-tokens@9.0.1:
|
||||||
|
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1706,8 +1697,8 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
magicast@0.5.2:
|
magicast@0.5.1:
|
||||||
resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
|
resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==}
|
||||||
|
|
||||||
make-dir@4.0.0:
|
make-dir@4.0.0:
|
||||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||||
@@ -1911,9 +1902,6 @@ packages:
|
|||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
std-env@4.0.0:
|
|
||||||
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
|
|
||||||
|
|
||||||
strip-indent@3.0.0:
|
strip-indent@3.0.0:
|
||||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2193,13 +2181,13 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/helper-validator-identifier@7.28.5': {}
|
'@babel/helper-validator-identifier@7.28.5': {}
|
||||||
|
|
||||||
'@babel/parser@7.29.2':
|
'@babel/parser@7.28.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.28.6
|
||||||
|
|
||||||
'@babel/runtime@7.28.6': {}
|
'@babel/runtime@7.28.6': {}
|
||||||
|
|
||||||
'@babel/types@7.29.0':
|
'@babel/types@7.28.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-string-parser': 7.27.1
|
'@babel/helper-string-parser': 7.27.1
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
@@ -3112,17 +3100,17 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.56.1
|
'@typescript-eslint/types': 8.56.1
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
'@vitest/coverage-v8@4.1.0(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))':
|
'@vitest/coverage-v8@4.0.18(vitest@4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@bcoe/v8-coverage': 1.0.2
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
'@vitest/utils': 4.1.0
|
'@vitest/utils': 4.0.18
|
||||||
ast-v8-to-istanbul: 1.0.0
|
ast-v8-to-istanbul: 0.3.10
|
||||||
istanbul-lib-coverage: 3.2.2
|
istanbul-lib-coverage: 3.2.2
|
||||||
istanbul-lib-report: 3.0.1
|
istanbul-lib-report: 3.0.1
|
||||||
istanbul-reports: 3.2.0
|
istanbul-reports: 3.2.0
|
||||||
magicast: 0.5.2
|
magicast: 0.5.1
|
||||||
obug: 2.1.1
|
obug: 2.1.1
|
||||||
std-env: 4.0.0
|
std-env: 3.10.0
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
vitest: 4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)
|
vitest: 4.0.18(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)
|
||||||
|
|
||||||
@@ -3147,10 +3135,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@vitest/pretty-format@4.1.0':
|
|
||||||
dependencies:
|
|
||||||
tinyrainbow: 3.0.3
|
|
||||||
|
|
||||||
'@vitest/runner@4.0.18':
|
'@vitest/runner@4.0.18':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/utils': 4.0.18
|
'@vitest/utils': 4.0.18
|
||||||
@@ -3169,12 +3153,6 @@ snapshots:
|
|||||||
'@vitest/pretty-format': 4.0.18
|
'@vitest/pretty-format': 4.0.18
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@vitest/utils@4.1.0':
|
|
||||||
dependencies:
|
|
||||||
'@vitest/pretty-format': 4.1.0
|
|
||||||
convert-source-map: 2.0.0
|
|
||||||
tinyrainbow: 3.0.3
|
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
@@ -3210,11 +3188,11 @@ snapshots:
|
|||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
ast-v8-to-istanbul@1.0.0:
|
ast-v8-to-istanbul@0.3.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
js-tokens: 10.0.0
|
js-tokens: 9.0.1
|
||||||
|
|
||||||
axobject-query@4.1.0: {}
|
axobject-query@4.1.0: {}
|
||||||
|
|
||||||
@@ -3268,8 +3246,6 @@ snapshots:
|
|||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
|
||||||
|
|
||||||
cookie@0.6.0: {}
|
cookie@0.6.0: {}
|
||||||
|
|
||||||
crelt@1.0.6: {}
|
crelt@1.0.6: {}
|
||||||
@@ -3576,10 +3552,10 @@ snapshots:
|
|||||||
|
|
||||||
jiti@2.6.1: {}
|
jiti@2.6.1: {}
|
||||||
|
|
||||||
js-tokens@10.0.0: {}
|
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
|
js-tokens@9.0.1: {}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
@@ -3701,10 +3677,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
magicast@0.5.2:
|
magicast@0.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.29.2
|
'@babel/parser': 7.28.6
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.28.6
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
make-dir@4.0.0:
|
make-dir@4.0.0:
|
||||||
@@ -3891,8 +3867,6 @@ snapshots:
|
|||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
std-env@4.0.0: {}
|
|
||||||
|
|
||||||
strip-indent@3.0.0:
|
strip-indent@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
min-indent: 1.0.1
|
min-indent: 1.0.1
|
||||||
|
|||||||
Generated
+1
-1
@@ -1648,7 +1648,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hikari-desktop"
|
name = "hikari-desktop"
|
||||||
version = "1.12.0"
|
version = "1.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hikari-desktop"
|
name = "hikari-desktop"
|
||||||
version = "1.12.0"
|
version = "1.14.0"
|
||||||
description = "Hikari - Claude Code Visual Assistant"
|
description = "Hikari - Claude Code Visual Assistant"
|
||||||
authors = ["Naomi Carrigan"]
|
authors = ["Naomi Carrigan"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
+132
-3
@@ -1464,6 +1464,7 @@ pub async fn close_application(app_handle: AppHandle) -> Result<(), String> {
|
|||||||
pub struct MemoryFileInfo {
|
pub struct MemoryFileInfo {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub heading: Option<String>,
|
pub heading: Option<String>,
|
||||||
|
pub last_modified: Option<String>, // Unix timestamp in seconds as a string
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@@ -1535,7 +1536,11 @@ async fn list_memory_files_via_wsl() -> Result<MemoryFilesResponse, String> {
|
|||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let heading = read_wsl_file_first_heading(&path);
|
let heading = read_wsl_file_first_heading(&path);
|
||||||
files.push(MemoryFileInfo { path, heading });
|
files.push(MemoryFileInfo {
|
||||||
|
path,
|
||||||
|
heading,
|
||||||
|
last_modified: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MemoryFilesResponse { files })
|
Ok(MemoryFilesResponse { files })
|
||||||
@@ -1605,14 +1610,23 @@ async fn list_memory_files_native() -> Result<MemoryFilesResponse, String> {
|
|||||||
// Sort files alphabetically
|
// Sort files alphabetically
|
||||||
memory_paths.sort();
|
memory_paths.sort();
|
||||||
|
|
||||||
// Read first heading from each file
|
// Read first heading and modification time from each file
|
||||||
let files = memory_paths
|
let files = memory_paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
let heading = fs::read_to_string(&path)
|
let heading = fs::read_to_string(&path)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|content| extract_first_heading(&content));
|
.and_then(|content| extract_first_heading(&content));
|
||||||
MemoryFileInfo { path, heading }
|
let last_modified = fs::metadata(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|m| m.modified().ok())
|
||||||
|
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
|
||||||
|
.map(|d| d.as_secs().to_string());
|
||||||
|
MemoryFileInfo {
|
||||||
|
path,
|
||||||
|
heading,
|
||||||
|
last_modified,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -2604,6 +2618,103 @@ pub async fn open_binary_file(app: AppHandle, path: String) -> Result<(), String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read `~/.claude/CLAUDE.md` via WSL (for Windows).
|
||||||
|
/// Returns an empty string if the file does not exist.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
async fn get_global_claude_md_via_wsl() -> Result<String, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let output = Command::new("wsl")
|
||||||
|
.hide_window()
|
||||||
|
.args(["-e", "bash", "-l", "-c", "cat ~/.claude/CLAUDE.md 2>/dev/null || true"])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write content to `~/.claude/CLAUDE.md` via WSL (for Windows).
|
||||||
|
/// Creates the file (and `~/.claude/` directory) if they do not exist.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
async fn save_global_claude_md_via_wsl(content: String) -> Result<(), String> {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let mut child = Command::new("wsl")
|
||||||
|
.hide_window()
|
||||||
|
.args([
|
||||||
|
"-e",
|
||||||
|
"bash",
|
||||||
|
"-l",
|
||||||
|
"-c",
|
||||||
|
"mkdir -p ~/.claude && cat > ~/.claude/CLAUDE.md",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(stdin) = child.stdin.as_mut() {
|
||||||
|
stdin
|
||||||
|
.write_all(content.as_bytes())
|
||||||
|
.map_err(|e| format!("Failed to write content to WSL stdin: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| format!("Failed to wait for WSL command: {}", e))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err("Failed to save CLAUDE.md via WSL".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the contents of `~/.claude/CLAUDE.md`.
|
||||||
|
/// Returns an empty string if the file does not exist.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_global_claude_md() -> Result<String, String> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return get_global_claude_md_via_wsl().await;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
let path = dirs::home_dir()
|
||||||
|
.ok_or_else(|| "Could not determine home directory".to_string())?
|
||||||
|
.join(".claude")
|
||||||
|
.join("CLAUDE.md");
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::read_to_string(&path).map_err(|e| format!("Failed to read CLAUDE.md: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write content to `~/.claude/CLAUDE.md`.
|
||||||
|
/// Creates the file (and `~/.claude/` directory) if they do not exist.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_global_claude_md(content: String) -> Result<(), String> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return save_global_claude_md_via_wsl(content).await;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
let claude_dir = dirs::home_dir()
|
||||||
|
.ok_or_else(|| "Could not determine home directory".to_string())?
|
||||||
|
.join(".claude");
|
||||||
|
|
||||||
|
if !claude_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&claude_dir)
|
||||||
|
.map_err(|e| format!("Failed to create ~/.claude directory: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = claude_dir.join("CLAUDE.md");
|
||||||
|
std::fs::write(&path, content).map_err(|e| format!("Failed to write CLAUDE.md: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -3353,4 +3464,22 @@ gitea: gitea-mcp -t stdio (STDIO) - âś“ Connected"#;
|
|||||||
let (_, args) = build_wslpath_command(path);
|
let (_, args) = build_wslpath_command(path);
|
||||||
assert_eq!(args[2], path);
|
assert_eq!(args[2], path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_global_claude_md_path_construction() {
|
||||||
|
// Verify that home_dir() resolves successfully on the test platform
|
||||||
|
let home = dirs::home_dir();
|
||||||
|
assert!(home.is_some(), "home_dir() should be available in test environment");
|
||||||
|
let expected = home.unwrap().join(".claude").join("CLAUDE.md");
|
||||||
|
assert!(expected.to_string_lossy().contains(".claude"));
|
||||||
|
assert!(expected.to_string_lossy().ends_with("CLAUDE.md"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_save_global_claude_md_dir_path_construction() {
|
||||||
|
let home = dirs::home_dir();
|
||||||
|
assert!(home.is_some());
|
||||||
|
let dir = home.unwrap().join(".claude");
|
||||||
|
assert!(dir.to_string_lossy().contains(".claude"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,25 @@ pub struct ClaudeStartOptions {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub model_overrides: Option<HashMap<String, String>>,
|
pub model_overrides: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub session_name: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub disable_skill_shell_execution: bool,
|
||||||
|
|
||||||
|
/// Pass `--bare` flag to suppress UI chrome, useful for scripted headless `-p` calls (v2.1.81+).
|
||||||
|
#[serde(default)]
|
||||||
|
pub bare_mode: bool,
|
||||||
|
|
||||||
|
/// Controls `showClearContextOnPlanAccept` in `--settings` (v2.1.81+).
|
||||||
|
/// Defaults to true (matching CLI default). Set to false to suppress the dialog.
|
||||||
|
#[serde(default = "default_show_clear_context")]
|
||||||
|
pub show_clear_context_on_plan_accept: bool,
|
||||||
|
|
||||||
|
/// Sets `ANTHROPIC_CUSTOM_MODEL_OPTION` env var for custom model providers (v2.1.81+).
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom_model_option: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -200,6 +219,23 @@ pub struct HikariConfig {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub model_overrides: Option<HashMap<String, String>>,
|
pub model_overrides: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// Prevents skill scripts from executing shell commands (Claude Code v2.1.91+).
|
||||||
|
/// Passes `"disableSkillShellExecution": true` via the `--settings` flag.
|
||||||
|
#[serde(default)]
|
||||||
|
pub disable_skill_shell_execution: bool,
|
||||||
|
|
||||||
|
/// Pass `--bare` flag to suppress UI chrome, useful for scripted headless `-p` calls (v2.1.81+).
|
||||||
|
#[serde(default)]
|
||||||
|
pub bare_mode: bool,
|
||||||
|
|
||||||
|
/// Controls `showClearContextOnPlanAccept` in `--settings` (v2.1.81+).
|
||||||
|
#[serde(default = "default_show_clear_context")]
|
||||||
|
pub show_clear_context_on_plan_accept: bool,
|
||||||
|
|
||||||
|
/// Sets `ANTHROPIC_CUSTOM_MODEL_OPTION` env var for custom model providers (v2.1.81+).
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom_model_option: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HikariConfig {
|
impl Default for HikariConfig {
|
||||||
@@ -251,6 +287,10 @@ impl Default for HikariConfig {
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: None,
|
auto_memory_directory: None,
|
||||||
model_overrides: None,
|
model_overrides: None,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,6 +343,10 @@ fn default_enable_claudeai_mcp_servers() -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_show_clear_context() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum BudgetAction {
|
pub enum BudgetAction {
|
||||||
@@ -402,6 +446,10 @@ mod tests {
|
|||||||
assert!(config.enable_claudeai_mcp_servers);
|
assert!(config.enable_claudeai_mcp_servers);
|
||||||
assert!(config.auto_memory_directory.is_none());
|
assert!(config.auto_memory_directory.is_none());
|
||||||
assert!(config.model_overrides.is_none());
|
assert!(config.model_overrides.is_none());
|
||||||
|
assert!(!config.disable_skill_shell_execution);
|
||||||
|
assert!(!config.bare_mode);
|
||||||
|
assert!(config.show_clear_context_on_plan_accept);
|
||||||
|
assert!(config.custom_model_option.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -456,6 +504,10 @@ mod tests {
|
|||||||
"claude-opus-4-6".to_string(),
|
"claude-opus-4-6".to_string(),
|
||||||
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1".to_string(),
|
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-6-v1".to_string(),
|
||||||
)])),
|
)])),
|
||||||
|
disable_skill_shell_execution: true,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&config).unwrap();
|
let json = serde_json::to_string(&config).unwrap();
|
||||||
|
|||||||
@@ -224,6 +224,8 @@ pub fn run() {
|
|||||||
delete_all_drafts,
|
delete_all_drafts,
|
||||||
scan_project,
|
scan_project,
|
||||||
open_binary_file,
|
open_binary_file,
|
||||||
|
get_global_claude_md,
|
||||||
|
save_global_claude_md,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ pub enum ClaudeMessage {
|
|||||||
cwd: Option<String>,
|
cwd: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
tools: Option<Vec<String>>,
|
tools: Option<Vec<String>>,
|
||||||
|
/// Output style hint from Claude Code (v2.1.81+). Informational only.
|
||||||
|
#[serde(default)]
|
||||||
|
output_style: Option<String>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "assistant")]
|
#[serde(rename = "assistant")]
|
||||||
Assistant {
|
Assistant {
|
||||||
@@ -119,6 +122,15 @@ pub enum ClaudeMessage {
|
|||||||
permission_denials: Option<Vec<PermissionDenial>>,
|
permission_denials: Option<Vec<PermissionDenial>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
usage: Option<UsageInfo>,
|
usage: Option<UsageInfo>,
|
||||||
|
/// Fast mode state from Claude Code v2.1.81+. Values: "default" | "enabled" | "disabled".
|
||||||
|
#[serde(default)]
|
||||||
|
fast_mode_state: Option<String>,
|
||||||
|
/// Per-model usage breakdown from Claude Code v2.1.81+.
|
||||||
|
#[serde(default)]
|
||||||
|
model_usage: Option<serde_json::Value>,
|
||||||
|
/// Authoritative total cost in USD reported by Claude Code v2.1.81+.
|
||||||
|
#[serde(default)]
|
||||||
|
total_cost_usd: Option<f64>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "rate_limit_event")]
|
#[serde(rename = "rate_limit_event")]
|
||||||
RateLimitEvent {
|
RateLimitEvent {
|
||||||
@@ -280,6 +292,80 @@ pub struct UserQuestionEvent {
|
|||||||
pub conversation_id: Option<String>,
|
pub conversation_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ElicitationEvent {
|
||||||
|
pub message: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub request_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ElicitationResultEvent {
|
||||||
|
pub action: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub request_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StopFailureEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub stop_reason: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub error_type: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PostCompactEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub session_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CwdChangedEvent {
|
||||||
|
pub cwd: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct FileChangedEvent {
|
||||||
|
pub file: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TaskCreatedEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub task_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub parent_tool_use_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PermissionDeniedEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tool_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AgentStartEvent {
|
pub struct AgentStartEvent {
|
||||||
pub tool_use_id: String,
|
pub tool_use_id: String,
|
||||||
@@ -566,4 +652,337 @@ mod tests {
|
|||||||
panic!("Expected RateLimitEvent variant");
|
panic!("Expected RateLimitEvent variant");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elicitation_event_serialization() {
|
||||||
|
let event = ElicitationEvent {
|
||||||
|
message: "Please provide the API endpoint".to_string(),
|
||||||
|
server_name: Some("my-server".to_string()),
|
||||||
|
request_id: Some("req-123".to_string()),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"message\":\"Please provide the API endpoint\""));
|
||||||
|
assert!(serialized.contains("\"server_name\":\"my-server\""));
|
||||||
|
assert!(serialized.contains("\"request_id\":\"req-123\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elicitation_event_omits_none_fields() {
|
||||||
|
let event = ElicitationEvent {
|
||||||
|
message: "Enter your token".to_string(),
|
||||||
|
server_name: None,
|
||||||
|
request_id: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"message\":\"Enter your token\""));
|
||||||
|
assert!(!serialized.contains("server_name"));
|
||||||
|
assert!(!serialized.contains("request_id"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elicitation_result_event_serialization() {
|
||||||
|
let event = ElicitationResultEvent {
|
||||||
|
action: "accept".to_string(),
|
||||||
|
request_id: Some("req-123".to_string()),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"action\":\"accept\""));
|
||||||
|
assert!(serialized.contains("\"request_id\":\"req-123\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_elicitation_result_event_cancel_omits_none_fields() {
|
||||||
|
let event = ElicitationResultEvent {
|
||||||
|
action: "cancel".to_string(),
|
||||||
|
request_id: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"action\":\"cancel\""));
|
||||||
|
assert!(!serialized.contains("request_id"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stop_failure_event_serialization() {
|
||||||
|
let event = StopFailureEvent {
|
||||||
|
stop_reason: Some("api_error".to_string()),
|
||||||
|
error_type: Some("rate_limit".to_string()),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"stop_reason\":\"api_error\""));
|
||||||
|
assert!(serialized.contains("\"error_type\":\"rate_limit\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stop_failure_event_omits_none_fields() {
|
||||||
|
let event = StopFailureEvent {
|
||||||
|
stop_reason: None,
|
||||||
|
error_type: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(!serialized.contains("stop_reason"));
|
||||||
|
assert!(!serialized.contains("error_type"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stop_failure_event_partial_fields() {
|
||||||
|
let event = StopFailureEvent {
|
||||||
|
stop_reason: Some("api_error".to_string()),
|
||||||
|
error_type: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"stop_reason\":\"api_error\""));
|
||||||
|
assert!(!serialized.contains("error_type"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_compact_event_serialization() {
|
||||||
|
let event = PostCompactEvent {
|
||||||
|
session_id: Some("sess-abc".to_string()),
|
||||||
|
conversation_id: Some("conv-123".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"session_id\":\"sess-abc\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-123\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_compact_event_omits_none_fields() {
|
||||||
|
let event = PostCompactEvent {
|
||||||
|
session_id: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(!serialized.contains("session_id"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_compact_event_partial_fields() {
|
||||||
|
let event = PostCompactEvent {
|
||||||
|
session_id: Some("sess-xyz".to_string()),
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"session_id\":\"sess-xyz\""));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cwd_changed_event_serialization() {
|
||||||
|
let event = CwdChangedEvent {
|
||||||
|
cwd: "/home/naomi/code/my-project".to_string(),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"cwd\":\"/home/naomi/code/my-project\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cwd_changed_event_omits_none_fields() {
|
||||||
|
let event = CwdChangedEvent {
|
||||||
|
cwd: "/tmp/workspace".to_string(),
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"cwd\":\"/tmp/workspace\""));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_changed_event_serialization() {
|
||||||
|
let event = FileChangedEvent {
|
||||||
|
file: "/home/naomi/code/my-project/src/main.rs".to_string(),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"file\":\"/home/naomi/code/my-project/src/main.rs\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_changed_event_omits_none_fields() {
|
||||||
|
let event = FileChangedEvent {
|
||||||
|
file: "/tmp/test.txt".to_string(),
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"file\":\"/tmp/test.txt\""));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_task_created_event_serialization() {
|
||||||
|
let event = TaskCreatedEvent {
|
||||||
|
task_id: Some("task-abc123".to_string()),
|
||||||
|
description: Some("Explore the codebase".to_string()),
|
||||||
|
parent_tool_use_id: Some("toolu_xyz".to_string()),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"task_id\":\"task-abc123\""));
|
||||||
|
assert!(serialized.contains("\"description\":\"Explore the codebase\""));
|
||||||
|
assert!(serialized.contains("\"parent_tool_use_id\":\"toolu_xyz\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_task_created_event_omits_none_fields() {
|
||||||
|
let event = TaskCreatedEvent {
|
||||||
|
task_id: None,
|
||||||
|
description: None,
|
||||||
|
parent_tool_use_id: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert_eq!(serialized, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_task_created_event_partial_fields() {
|
||||||
|
let event = TaskCreatedEvent {
|
||||||
|
task_id: Some("task-001".to_string()),
|
||||||
|
description: None,
|
||||||
|
parent_tool_use_id: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"task_id\":\"task-001\""));
|
||||||
|
assert!(!serialized.contains("description"));
|
||||||
|
assert!(!serialized.contains("parent_tool_use_id"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permission_denied_event_serialization() {
|
||||||
|
let event = PermissionDeniedEvent {
|
||||||
|
tool_name: Some("Bash".to_string()),
|
||||||
|
reason: Some("Tool not in allow list".to_string()),
|
||||||
|
conversation_id: Some("conv-abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"tool_name\":\"Bash\""));
|
||||||
|
assert!(serialized.contains("\"reason\":\"Tool not in allow list\""));
|
||||||
|
assert!(serialized.contains("\"conversation_id\":\"conv-abc\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permission_denied_event_omits_none_fields() {
|
||||||
|
let event = PermissionDeniedEvent {
|
||||||
|
tool_name: None,
|
||||||
|
reason: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert_eq!(serialized, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permission_denied_event_partial_fields() {
|
||||||
|
let event = PermissionDeniedEvent {
|
||||||
|
tool_name: Some("Edit".to_string()),
|
||||||
|
reason: None,
|
||||||
|
conversation_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
assert!(serialized.contains("\"tool_name\":\"Edit\""));
|
||||||
|
assert!(!serialized.contains("reason"));
|
||||||
|
assert!(!serialized.contains("conversation_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_init_with_output_style() {
|
||||||
|
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","output_style":"auto"}"#;
|
||||||
|
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||||
|
if let ClaudeMessage::System { output_style, .. } = msg {
|
||||||
|
assert_eq!(output_style, Some("auto".to_string()));
|
||||||
|
} else {
|
||||||
|
panic!("Expected System variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_init_without_output_style() {
|
||||||
|
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1"}"#;
|
||||||
|
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||||
|
if let ClaudeMessage::System { output_style, .. } = msg {
|
||||||
|
assert!(output_style.is_none());
|
||||||
|
} else {
|
||||||
|
panic!("Expected System variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_result_message_with_fast_mode_state() {
|
||||||
|
let json = r#"{"type":"result","subtype":"success","fast_mode_state":"enabled"}"#;
|
||||||
|
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||||
|
if let ClaudeMessage::Result { fast_mode_state, .. } = msg {
|
||||||
|
assert_eq!(fast_mode_state, Some("enabled".to_string()));
|
||||||
|
} else {
|
||||||
|
panic!("Expected Result variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_result_message_with_total_cost_usd() {
|
||||||
|
let json = r#"{"type":"result","subtype":"success","total_cost_usd":0.05}"#;
|
||||||
|
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||||
|
if let ClaudeMessage::Result { total_cost_usd, .. } = msg {
|
||||||
|
assert!((total_cost_usd.unwrap() - 0.05).abs() < f64::EPSILON);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Result variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_result_message_without_new_fields() {
|
||||||
|
let json = r#"{"type":"result","subtype":"success"}"#;
|
||||||
|
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||||
|
if let ClaudeMessage::Result {
|
||||||
|
fast_mode_state,
|
||||||
|
model_usage,
|
||||||
|
total_cost_usd,
|
||||||
|
..
|
||||||
|
} = msg
|
||||||
|
{
|
||||||
|
assert!(fast_mode_state.is_none());
|
||||||
|
assert!(model_usage.is_none());
|
||||||
|
assert!(total_cost_usd.is_none());
|
||||||
|
} else {
|
||||||
|
panic!("Expected Result variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+884
-12
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "hikari-desktop",
|
"productName": "hikari-desktop",
|
||||||
"version": "1.12.0",
|
"version": "1.14.0",
|
||||||
"identifier": "com.naomi.hikari-desktop",
|
"identifier": "com.naomi.hikari-desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
|||||||
@@ -67,10 +67,16 @@ async function changeDirectory(path: string): Promise<void> {
|
|||||||
use_worktree: config.use_worktree ?? false,
|
use_worktree: config.use_worktree ?? false,
|
||||||
disable_1m_context: config.disable_1m_context ?? false,
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
max_output_tokens: config.max_output_tokens ?? null,
|
max_output_tokens: config.max_output_tokens ?? null,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: config.include_git_instructions ?? true,
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: config.auto_memory_directory || null,
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
model_overrides: config.model_overrides || null,
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,10 +154,16 @@ async function startNewConversation(): Promise<void> {
|
|||||||
use_worktree: config.use_worktree ?? false,
|
use_worktree: config.use_worktree ?? false,
|
||||||
disable_1m_context: config.disable_1m_context ?? false,
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
max_output_tokens: config.max_output_tokens ?? null,
|
max_output_tokens: config.max_output_tokens ?? null,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: config.include_git_instructions ?? true,
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: config.auto_memory_directory || null,
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
model_overrides: config.model_overrides || null,
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
const SUPPORTED_CLI_VERSION = "2.1.74";
|
const SUPPORTED_CLI_VERSION = "2.1.104";
|
||||||
|
|
||||||
let installedVersion = $state("Loading...");
|
let installedVersion = $state("Loading...");
|
||||||
let latestNpmVersion = $state<string | null>(null);
|
let latestNpmVersion = $state<string | null>(null);
|
||||||
|
|||||||
@@ -63,6 +63,10 @@
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
max_output_tokens: null,
|
max_output_tokens: null,
|
||||||
trusted_workspaces: [],
|
trusted_workspaces: [],
|
||||||
background_image_path: null,
|
background_image_path: null,
|
||||||
@@ -85,6 +89,9 @@
|
|||||||
let customUiFontStatus: string | null = $state(null);
|
let customUiFontStatus: string | null = $state(null);
|
||||||
let modelOverridesJson = $state("");
|
let modelOverridesJson = $state("");
|
||||||
let modelOverridesError: string | null = $state(null);
|
let modelOverridesError: string | null = $state(null);
|
||||||
|
let globalClaudeMd = $state("");
|
||||||
|
let globalClaudeMdSaving = $state(false);
|
||||||
|
let globalClaudeMdSaveStatus: string | null = $state(null);
|
||||||
|
|
||||||
interface AuthStatus {
|
interface AuthStatus {
|
||||||
is_logged_in: boolean;
|
is_logged_in: boolean;
|
||||||
@@ -122,6 +129,9 @@
|
|||||||
if (open && authStatus === null) {
|
if (open && authStatus === null) {
|
||||||
void refreshAuthStatus();
|
void refreshAuthStatus();
|
||||||
}
|
}
|
||||||
|
if (open) {
|
||||||
|
void loadGlobalClaudeMd();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
configStore.saveError.subscribe((error) => {
|
configStore.saveError.subscribe((error) => {
|
||||||
@@ -197,6 +207,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadGlobalClaudeMd() {
|
||||||
|
try {
|
||||||
|
globalClaudeMd = await invoke<string>("get_global_claude_md");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load global CLAUDE.md:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGlobalClaudeMd() {
|
||||||
|
globalClaudeMdSaving = true;
|
||||||
|
globalClaudeMdSaveStatus = null;
|
||||||
|
try {
|
||||||
|
await invoke("save_global_claude_md", { content: globalClaudeMd });
|
||||||
|
globalClaudeMdSaveStatus = "Saved!";
|
||||||
|
setTimeout(() => {
|
||||||
|
globalClaudeMdSaveStatus = null;
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
globalClaudeMdSaveStatus = `Error: ${error}`;
|
||||||
|
} finally {
|
||||||
|
globalClaudeMdSaving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
saveError = null;
|
saveError = null;
|
||||||
@@ -585,6 +619,22 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Disable Skill Shell Execution -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.disable_skill_shell_execution}
|
||||||
|
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]">Disable skill shell execution</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
Passes <code class="font-mono">disableSkillShellExecution: true</code> to prevent skill scripts
|
||||||
|
from executing shell commands (requires Claude Code v2.1.91+)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Include Git Instructions -->
|
<!-- Include Git Instructions -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="flex items-center gap-3 cursor-pointer">
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
@@ -610,13 +660,15 @@
|
|||||||
id="max-output-tokens"
|
id="max-output-tokens"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
placeholder="Default (32000)"
|
max="128000"
|
||||||
|
placeholder="Default (model-dependent)"
|
||||||
bind:value={config.max_output_tokens}
|
bind:value={config.max_output_tokens}
|
||||||
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)]"
|
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)]"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-[var(--text-tertiary)] mt-1">
|
<p class="text-xs text-[var(--text-tertiary)] mt-1">
|
||||||
Sets <code class="font-mono">CLAUDE_CODE_MAX_OUTPUT_TOKENS</code> — increase if responses are
|
Sets <code class="font-mono">CLAUDE_CODE_MAX_OUTPUT_TOKENS</code>. Maximum: 128k tokens
|
||||||
being cut off mid-reply
|
for Opus 4.6 and Sonnet 4.6 (64k default for Opus 4.6, 32k for other models). Increase if
|
||||||
|
responses are being cut off.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -660,6 +712,59 @@
|
|||||||
defaults.
|
defaults.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bare Mode -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.bare_mode}
|
||||||
|
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]">Bare mode</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
Passes <code class="font-mono">--bare</code> to suppress UI chrome — useful for scripted
|
||||||
|
headless <code class="font-mono">-p</code> calls (requires Claude Code v2.1.81+)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show Clear Context on Plan Accept -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.show_clear_context_on_plan_accept}
|
||||||
|
class="w-4 h-4 rounded border-[var(--border-color)] bg-[var(--bg-primary)] text-[var(--accent-primary)] focus:ring-[var(--accent-primary)]"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]"
|
||||||
|
>Show clear context prompt on plan accept</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
When enabled, prompts to clear context when accepting a plan. Passes
|
||||||
|
<code class="font-mono">showClearContextOnPlanAccept: false</code> in
|
||||||
|
<code class="font-mono">--settings</code> when disabled (requires Claude Code v2.1.81+)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Model Option -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="custom-model-option" class="block text-sm text-[var(--text-primary)] mb-1">
|
||||||
|
Custom model option <span class="text-[var(--text-tertiary)]">(optional)</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="custom-model-option"
|
||||||
|
type="text"
|
||||||
|
placeholder="Leave blank to use default"
|
||||||
|
bind:value={config.custom_model_option}
|
||||||
|
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)]"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1">
|
||||||
|
Sets <code class="font-mono">ANTHROPIC_CUSTOM_MODEL_OPTION</code> environment variable for custom
|
||||||
|
model providers (requires Claude Code v2.1.81+)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Greeting Section -->
|
<!-- Greeting Section -->
|
||||||
@@ -700,6 +805,47 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Global Instructions Section -->
|
||||||
|
<section class="mb-6">
|
||||||
|
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
||||||
|
Global Instructions
|
||||||
|
</h3>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="global-claude-md" class="block text-sm text-[var(--text-secondary)] mb-1">
|
||||||
|
~/.claude/CLAUDE.md
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="global-claude-md"
|
||||||
|
bind:value={globalClaudeMd}
|
||||||
|
rows="12"
|
||||||
|
placeholder="Add persistent instructions for all Claude Code sessions..."
|
||||||
|
class="w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] font-mono text-sm focus:outline-none focus:border-[var(--accent-primary)] resize-y"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onclick={saveGlobalClaudeMd}
|
||||||
|
disabled={globalClaudeMdSaving}
|
||||||
|
class="px-3 py-1.5 text-sm bg-[var(--accent-primary)] text-white rounded hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{globalClaudeMdSaving ? "Saving..." : "Save"}
|
||||||
|
</button>
|
||||||
|
{#if globalClaudeMdSaveStatus}
|
||||||
|
<span
|
||||||
|
class="text-xs {globalClaudeMdSaveStatus.startsWith('Error')
|
||||||
|
? 'text-red-500'
|
||||||
|
: 'text-green-500'}"
|
||||||
|
>
|
||||||
|
{globalClaudeMdSaveStatus}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-2">
|
||||||
|
Persistent instructions applied to all Claude Code sessions. Changes take effect on the next
|
||||||
|
session start.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- MCP Servers Section -->
|
<!-- MCP Servers Section -->
|
||||||
<section class="mb-6">
|
<section class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import { claudeStore, hasElicitationPending } from "$lib/stores/claude";
|
||||||
|
import { characterState } from "$lib/stores/character";
|
||||||
|
import type { ElicitationEvent } from "$lib/types/messages";
|
||||||
|
import { updateDiscordRpc, setSkipNextGreeting } from "$lib/tauri";
|
||||||
|
import { conversationsStore } from "$lib/stores/conversations";
|
||||||
|
import { configStore } from "$lib/stores/config";
|
||||||
|
|
||||||
|
let isVisible = $state(false);
|
||||||
|
let elicitation: ElicitationEvent | null = $state(null);
|
||||||
|
let response = $state("");
|
||||||
|
let grantedToolsList: string[] = $state([]);
|
||||||
|
let workingDirectory = $state("");
|
||||||
|
|
||||||
|
hasElicitationPending.subscribe((pending) => {
|
||||||
|
isVisible = pending;
|
||||||
|
if (!pending) {
|
||||||
|
response = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
claudeStore.pendingElicitation.subscribe((e) => {
|
||||||
|
elicitation = e;
|
||||||
|
if (e) {
|
||||||
|
characterState.setState("permission");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
claudeStore.grantedTools.subscribe((tools) => {
|
||||||
|
grantedToolsList = Array.from(tools);
|
||||||
|
});
|
||||||
|
|
||||||
|
claudeStore.currentWorkingDirectory.subscribe((dir) => {
|
||||||
|
workingDirectory = dir;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleSubmitAndReconnect() {
|
||||||
|
if (!elicitation || !response.trim()) return;
|
||||||
|
|
||||||
|
const conversationId = get(claudeStore.activeConversationId);
|
||||||
|
if (!conversationId) return;
|
||||||
|
|
||||||
|
const responseText = response.trim();
|
||||||
|
const elicitationMessage = elicitation.message;
|
||||||
|
const conversationHistory = claudeStore.getConversationHistory();
|
||||||
|
|
||||||
|
claudeStore.addLine("system", `MCP response submitted. Reconnecting with context...`);
|
||||||
|
claudeStore.clearElicitation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSkipNextGreeting(true);
|
||||||
|
|
||||||
|
await invoke("stop_claude", { conversationId });
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
const config = configStore.getConfig();
|
||||||
|
await invoke("start_claude", {
|
||||||
|
conversationId,
|
||||||
|
options: {
|
||||||
|
working_dir: workingDirectory || "/home/naomi",
|
||||||
|
model: config.model || null,
|
||||||
|
api_key: config.api_key || null,
|
||||||
|
custom_instructions: config.custom_instructions || null,
|
||||||
|
mcp_servers_json: config.mcp_servers_json || null,
|
||||||
|
allowed_tools: grantedToolsList,
|
||||||
|
use_worktree: config.use_worktree ?? false,
|
||||||
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeConversation = get(conversationsStore.activeConversation);
|
||||||
|
if (activeConversation) {
|
||||||
|
await updateDiscordRpc(
|
||||||
|
activeConversation.name,
|
||||||
|
config.model || "claude",
|
||||||
|
activeConversation.startedAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
if (conversationHistory) {
|
||||||
|
const contextMessage = `[CONTEXT RESTORATION]
|
||||||
|
I just responded to an MCP server elicitation request. Here's our conversation so far:
|
||||||
|
|
||||||
|
${conversationHistory}
|
||||||
|
|
||||||
|
The MCP server asked: "${elicitationMessage}"
|
||||||
|
My response: "${responseText}"
|
||||||
|
|
||||||
|
Please continue where we left off, taking my response into account.`;
|
||||||
|
|
||||||
|
await invoke("send_prompt", {
|
||||||
|
conversationId,
|
||||||
|
message: contextMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
characterState.setTemporaryState("success", 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to reconnect:", error);
|
||||||
|
claudeStore.addLine("error", `Reconnect failed: ${error}`);
|
||||||
|
characterState.setTemporaryState("error", 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDismiss() {
|
||||||
|
claudeStore.clearElicitation();
|
||||||
|
claudeStore.addLine("system", "MCP elicitation dismissed");
|
||||||
|
characterState.setTemporaryState("idle", 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (!isVisible || !elicitation) return;
|
||||||
|
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
handleDismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canSubmit(): boolean {
|
||||||
|
return response.trim().length > 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
|
{#if isVisible && elicitation}
|
||||||
|
<div
|
||||||
|
class="elicitation-overlay fixed inset-0 bg-black/70 flex items-center justify-center z-50 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="elicitation-modal bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-xl p-6 max-w-md w-full mx-4 shadow-2xl"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center">
|
||||||
|
<span class="text-xl">đź’¬</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-semibold text-[var(--text-primary)]">MCP Server Request</h2>
|
||||||
|
{#if elicitation.server_name}
|
||||||
|
<p class="text-sm text-[var(--text-secondary)]">from: {elicitation.server_name}</p>
|
||||||
|
{:else}
|
||||||
|
<p class="text-sm text-[var(--text-secondary)]">Input required from MCP server</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-[var(--text-primary)]">{elicitation.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<textarea
|
||||||
|
bind:value={response}
|
||||||
|
placeholder="Type your response here..."
|
||||||
|
class="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] placeholder-[var(--text-secondary)] resize-none focus:outline-none focus:border-[var(--accent-primary)]"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button
|
||||||
|
onclick={handleDismiss}
|
||||||
|
class="flex-1 px-4 py-2 bg-gray-500/20 hover:bg-gray-500/30 text-[var(--text-secondary)] rounded-lg transition-colors font-medium"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={handleSubmitAndReconnect}
|
||||||
|
disabled={!canSubmit()}
|
||||||
|
class="flex-1 px-4 py-2 bg-blue-500/20 hover:bg-blue-500/30 text-blue-400 rounded-lg transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Submit & Reconnect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -402,10 +402,16 @@ User: ${formattedMessage}`;
|
|||||||
allowed_tools: allAllowedTools,
|
allowed_tools: allAllowedTools,
|
||||||
use_worktree: config.use_worktree ?? false,
|
use_worktree: config.use_worktree ?? false,
|
||||||
disable_1m_context: config.disable_1m_context ?? false,
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: config.include_git_instructions ?? true,
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: config.auto_memory_directory || null,
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
model_overrides: config.model_overrides || null,
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
interface MemoryFileInfo {
|
interface MemoryFileInfo {
|
||||||
path: string;
|
path: string;
|
||||||
heading: string | null;
|
heading: string | null;
|
||||||
|
last_modified?: string; // Unix timestamp in seconds as a string, optional for backwards compat
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MemoryFilesResponse {
|
interface MemoryFilesResponse {
|
||||||
@@ -65,6 +66,16 @@
|
|||||||
return file.heading ?? getFileName(file.path);
|
return file.heading ?? getFileName(file.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatLastModified(ts: string | undefined): string {
|
||||||
|
if (!ts) return "";
|
||||||
|
const date = new Date(Number(ts) * 1000);
|
||||||
|
return date.toLocaleDateString(undefined, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function sendMemoryCommand() {
|
async function sendMemoryCommand() {
|
||||||
const conversationId = get(claudeStore.activeConversationId);
|
const conversationId = get(claudeStore.activeConversationId);
|
||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
@@ -205,7 +216,12 @@
|
|||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="file-name">{getDisplayName(file)}</span>
|
<div class="file-info">
|
||||||
|
<span class="file-name">{getDisplayName(file)}</span>
|
||||||
|
{#if file.last_modified}
|
||||||
|
<span class="file-date">{formatLastModified(file.last_modified)}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -416,7 +432,7 @@
|
|||||||
|
|
||||||
.file-item {
|
.file-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@@ -445,6 +461,13 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.125rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -453,6 +476,15 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-date {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.active .file-date {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
.file-viewer {
|
.file-viewer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -89,10 +89,16 @@
|
|||||||
allowed_tools: [...new Set([...newGrantedTools, ...config.auto_granted_tools])],
|
allowed_tools: [...new Set([...newGrantedTools, ...config.auto_granted_tools])],
|
||||||
use_worktree: config.use_worktree ?? false,
|
use_worktree: config.use_worktree ?? false,
|
||||||
disable_1m_context: config.disable_1m_context ?? false,
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: config.include_git_instructions ?? true,
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: config.auto_memory_directory || null,
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
model_overrides: config.model_overrides || null,
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,10 @@
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
let streamerModeActive = $state(false);
|
let streamerModeActive = $state(false);
|
||||||
@@ -160,6 +164,7 @@
|
|||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
throw new Error("No active conversation");
|
throw new Error("No active conversation");
|
||||||
}
|
}
|
||||||
|
const activeConversationForName = get(conversationsStore.activeConversation);
|
||||||
await invoke("start_claude", {
|
await invoke("start_claude", {
|
||||||
conversationId,
|
conversationId,
|
||||||
options: {
|
options: {
|
||||||
@@ -173,10 +178,17 @@
|
|||||||
use_worktree: currentConfig.use_worktree ?? false,
|
use_worktree: currentConfig.use_worktree ?? false,
|
||||||
disable_1m_context: currentConfig.disable_1m_context ?? false,
|
disable_1m_context: currentConfig.disable_1m_context ?? false,
|
||||||
max_output_tokens: currentConfig.max_output_tokens ?? null,
|
max_output_tokens: currentConfig.max_output_tokens ?? null,
|
||||||
|
disable_cron: currentConfig.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: currentConfig.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: currentConfig.include_git_instructions ?? true,
|
include_git_instructions: currentConfig.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: currentConfig.auto_memory_directory || null,
|
auto_memory_directory: currentConfig.auto_memory_directory || null,
|
||||||
model_overrides: currentConfig.model_overrides || null,
|
model_overrides: currentConfig.model_overrides || null,
|
||||||
|
session_name: activeConversationForName?.name || null,
|
||||||
|
bare_mode: currentConfig.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept:
|
||||||
|
currentConfig.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: currentConfig.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -334,10 +346,17 @@
|
|||||||
use_worktree: currentConfig.use_worktree ?? false,
|
use_worktree: currentConfig.use_worktree ?? false,
|
||||||
disable_1m_context: currentConfig.disable_1m_context ?? false,
|
disable_1m_context: currentConfig.disable_1m_context ?? false,
|
||||||
max_output_tokens: currentConfig.max_output_tokens ?? null,
|
max_output_tokens: currentConfig.max_output_tokens ?? null,
|
||||||
|
disable_cron: currentConfig.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: currentConfig.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: currentConfig.include_git_instructions ?? true,
|
include_git_instructions: currentConfig.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: currentConfig.auto_memory_directory || null,
|
auto_memory_directory: currentConfig.auto_memory_directory || null,
|
||||||
model_overrides: currentConfig.model_overrides || null,
|
model_overrides: currentConfig.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: currentConfig.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept:
|
||||||
|
currentConfig.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: currentConfig.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -218,10 +218,16 @@
|
|||||||
use_worktree: cfg.use_worktree ?? false,
|
use_worktree: cfg.use_worktree ?? false,
|
||||||
disable_1m_context: cfg.disable_1m_context ?? false,
|
disable_1m_context: cfg.disable_1m_context ?? false,
|
||||||
max_output_tokens: cfg.max_output_tokens ?? null,
|
max_output_tokens: cfg.max_output_tokens ?? null,
|
||||||
|
disable_cron: cfg.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: cfg.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: cfg.include_git_instructions ?? true,
|
include_git_instructions: cfg.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: cfg.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: cfg.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: cfg.auto_memory_directory || null,
|
auto_memory_directory: cfg.auto_memory_directory || null,
|
||||||
model_overrides: cfg.model_overrides || null,
|
model_overrides: cfg.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: cfg.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: cfg.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: cfg.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -108,10 +108,16 @@
|
|||||||
allowed_tools: grantedToolsList,
|
allowed_tools: grantedToolsList,
|
||||||
use_worktree: config.use_worktree ?? false,
|
use_worktree: config.use_worktree ?? false,
|
||||||
disable_1m_context: config.disable_1m_context ?? false,
|
disable_1m_context: config.disable_1m_context ?? false,
|
||||||
|
disable_cron: config.disable_cron ?? false,
|
||||||
|
disable_skill_shell_execution: config.disable_skill_shell_execution ?? false,
|
||||||
include_git_instructions: config.include_git_instructions ?? true,
|
include_git_instructions: config.include_git_instructions ?? true,
|
||||||
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
|
||||||
auto_memory_directory: config.auto_memory_directory || null,
|
auto_memory_directory: config.auto_memory_directory || null,
|
||||||
model_overrides: config.model_overrides || null,
|
model_overrides: config.model_overrides || null,
|
||||||
|
session_name: null,
|
||||||
|
bare_mode: config.bare_mode ?? false,
|
||||||
|
show_clear_context_on_plan_accept: config.show_clear_context_on_plan_accept ?? true,
|
||||||
|
custom_model_option: config.custom_model_option || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const claudeStore = {
|
|||||||
terminalLines: conversationsStore.terminalLines,
|
terminalLines: conversationsStore.terminalLines,
|
||||||
pendingPermission: conversationsStore.pendingPermission,
|
pendingPermission: conversationsStore.pendingPermission,
|
||||||
pendingQuestion: conversationsStore.pendingQuestion,
|
pendingQuestion: conversationsStore.pendingQuestion,
|
||||||
|
pendingElicitation: conversationsStore.pendingElicitation,
|
||||||
isProcessing: conversationsStore.isProcessing,
|
isProcessing: conversationsStore.isProcessing,
|
||||||
grantedTools: conversationsStore.grantedTools,
|
grantedTools: conversationsStore.grantedTools,
|
||||||
pendingRetryMessage: conversationsStore.pendingRetryMessage,
|
pendingRetryMessage: conversationsStore.pendingRetryMessage,
|
||||||
@@ -57,6 +58,10 @@ export const claudeStore = {
|
|||||||
clearQuestion: conversationsStore.clearQuestion,
|
clearQuestion: conversationsStore.clearQuestion,
|
||||||
requestQuestionForConversation: conversationsStore.requestQuestionForConversation,
|
requestQuestionForConversation: conversationsStore.requestQuestionForConversation,
|
||||||
clearQuestionForConversation: conversationsStore.clearQuestionForConversation,
|
clearQuestionForConversation: conversationsStore.clearQuestionForConversation,
|
||||||
|
requestElicitation: conversationsStore.requestElicitation,
|
||||||
|
clearElicitation: conversationsStore.clearElicitation,
|
||||||
|
requestElicitationForConversation: conversationsStore.requestElicitationForConversation,
|
||||||
|
clearElicitationForConversation: conversationsStore.clearElicitationForConversation,
|
||||||
grantTool: conversationsStore.grantTool,
|
grantTool: conversationsStore.grantTool,
|
||||||
revokeAllTools: conversationsStore.revokeAllTools,
|
revokeAllTools: conversationsStore.revokeAllTools,
|
||||||
isToolGranted: conversationsStore.isToolGranted,
|
isToolGranted: conversationsStore.isToolGranted,
|
||||||
@@ -126,6 +131,12 @@ export const hasQuestionPending = derived(
|
|||||||
($conversation) => $conversation?.pendingQuestion !== null
|
($conversation) => $conversation?.pendingQuestion !== null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const hasElicitationPending = derived(
|
||||||
|
claudeStore.activeConversation,
|
||||||
|
($conversation) =>
|
||||||
|
$conversation?.pendingElicitation !== null && $conversation?.pendingElicitation !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
// Derived store to check if Claude is currently processing (can be interrupted)
|
// Derived store to check if Claude is currently processing (can be interrupted)
|
||||||
export const isClaudeProcessing = derived(
|
export const isClaudeProcessing = derived(
|
||||||
[claudeStore.connectionStatus, characterState],
|
[claudeStore.connectionStatus, characterState],
|
||||||
|
|||||||
@@ -225,6 +225,10 @@ describe("config store", () => {
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(config.model).toBe("claude-sonnet-4");
|
expect(config.model).toBe("claude-sonnet-4");
|
||||||
@@ -289,6 +293,10 @@ describe("config store", () => {
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(config.model).toBeNull();
|
expect(config.model).toBeNull();
|
||||||
@@ -908,6 +916,10 @@ describe("config store", () => {
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockInvokeImpl = vi.mocked(invoke);
|
const mockInvokeImpl = vi.mocked(invoke);
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ export interface HikariConfig {
|
|||||||
auto_memory_directory: string | null;
|
auto_memory_directory: string | null;
|
||||||
// Model overrides for provider-specific model IDs (AWS Bedrock, Google Vertex, etc.)
|
// Model overrides for provider-specific model IDs (AWS Bedrock, Google Vertex, etc.)
|
||||||
model_overrides: Record<string, string> | null;
|
model_overrides: Record<string, string> | null;
|
||||||
|
// Prevents skill scripts from executing shell commands (Claude Code v2.1.91+)
|
||||||
|
disable_skill_shell_execution: boolean;
|
||||||
|
// Bare mode — suppress UI chrome for scripted headless -p calls (v2.1.81+)
|
||||||
|
bare_mode: boolean;
|
||||||
|
// Show clear context dialog when accepting a plan (v2.1.81+)
|
||||||
|
show_clear_context_on_plan_accept: boolean;
|
||||||
|
// Custom model option env var (v2.1.81+)
|
||||||
|
custom_model_option: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultConfig: HikariConfig = {
|
const defaultConfig: HikariConfig = {
|
||||||
@@ -149,6 +157,10 @@ const defaultConfig: HikariConfig = {
|
|||||||
enable_claudeai_mcp_servers: true,
|
enable_claudeai_mcp_servers: true,
|
||||||
auto_memory_directory: null,
|
auto_memory_directory: null,
|
||||||
model_overrides: null,
|
model_overrides: null,
|
||||||
|
disable_skill_shell_execution: false,
|
||||||
|
bare_mode: false,
|
||||||
|
show_clear_context_on_plan_accept: true,
|
||||||
|
custom_model_option: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createConfigStore() {
|
function createConfigStore() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store";
|
|||||||
import type {
|
import type {
|
||||||
TerminalLine,
|
TerminalLine,
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
|
ElicitationEvent,
|
||||||
PermissionRequest,
|
PermissionRequest,
|
||||||
UserQuestionEvent,
|
UserQuestionEvent,
|
||||||
Attachment,
|
Attachment,
|
||||||
@@ -32,6 +33,7 @@ export interface Conversation {
|
|||||||
grantedTools: Set<string>;
|
grantedTools: Set<string>;
|
||||||
pendingPermissions: PermissionRequest[];
|
pendingPermissions: PermissionRequest[];
|
||||||
pendingQuestion: UserQuestionEvent | null;
|
pendingQuestion: UserQuestionEvent | null;
|
||||||
|
pendingElicitation: ElicitationEvent | null;
|
||||||
scrollPosition: number;
|
scrollPosition: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
lastActivityAt: Date;
|
lastActivityAt: Date;
|
||||||
@@ -157,6 +159,7 @@ function createConversationsStore() {
|
|||||||
grantedTools: new Set(),
|
grantedTools: new Set(),
|
||||||
pendingPermissions: [],
|
pendingPermissions: [],
|
||||||
pendingQuestion: null,
|
pendingQuestion: null,
|
||||||
|
pendingElicitation: null,
|
||||||
scrollPosition: -1, // -1 means "scroll to bottom" (auto-scroll)
|
scrollPosition: -1, // -1 means "scroll to bottom" (auto-scroll)
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
lastActivityAt: new Date(),
|
lastActivityAt: new Date(),
|
||||||
@@ -221,6 +224,10 @@ function createConversationsStore() {
|
|||||||
($conv) => $conv?.pendingPermissions || []
|
($conv) => $conv?.pendingPermissions || []
|
||||||
);
|
);
|
||||||
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
|
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
|
||||||
|
const pendingElicitation = derived(
|
||||||
|
activeConversation,
|
||||||
|
($conv) => $conv?.pendingElicitation ?? null
|
||||||
|
);
|
||||||
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
|
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
|
||||||
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
|
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
|
||||||
const worktreeInfo = derived(activeConversation, ($conv) => $conv?.worktreeInfo ?? null);
|
const worktreeInfo = derived(activeConversation, ($conv) => $conv?.worktreeInfo ?? null);
|
||||||
@@ -234,6 +241,7 @@ function createConversationsStore() {
|
|||||||
pendingPermission: { subscribe: pendingPermission.subscribe },
|
pendingPermission: { subscribe: pendingPermission.subscribe },
|
||||||
pendingPermissions: { subscribe: pendingPermissions.subscribe },
|
pendingPermissions: { subscribe: pendingPermissions.subscribe },
|
||||||
pendingQuestion: { subscribe: pendingQuestion.subscribe },
|
pendingQuestion: { subscribe: pendingQuestion.subscribe },
|
||||||
|
pendingElicitation: { subscribe: pendingElicitation.subscribe },
|
||||||
isProcessing: { subscribe: isProcessing.subscribe },
|
isProcessing: { subscribe: isProcessing.subscribe },
|
||||||
grantedTools: { subscribe: grantedTools.subscribe },
|
grantedTools: { subscribe: grantedTools.subscribe },
|
||||||
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
|
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
|
||||||
@@ -399,6 +407,52 @@ function createConversationsStore() {
|
|||||||
return convs;
|
return convs;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
requestElicitation: (elicitation: ElicitationEvent) => {
|
||||||
|
const activeId = get(activeConversationId);
|
||||||
|
if (!activeId) return;
|
||||||
|
|
||||||
|
conversations.update((convs) => {
|
||||||
|
const conv = convs.get(activeId);
|
||||||
|
if (conv) {
|
||||||
|
conv.pendingElicitation = elicitation;
|
||||||
|
conv.lastActivityAt = new Date();
|
||||||
|
}
|
||||||
|
return convs;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearElicitation: () => {
|
||||||
|
const activeId = get(activeConversationId);
|
||||||
|
if (!activeId) return;
|
||||||
|
|
||||||
|
conversations.update((convs) => {
|
||||||
|
const conv = convs.get(activeId);
|
||||||
|
if (conv) {
|
||||||
|
conv.pendingElicitation = null;
|
||||||
|
conv.lastActivityAt = new Date();
|
||||||
|
}
|
||||||
|
return convs;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
requestElicitationForConversation: (conversationId: string, elicitation: ElicitationEvent) => {
|
||||||
|
conversations.update((convs) => {
|
||||||
|
const conv = convs.get(conversationId);
|
||||||
|
if (conv) {
|
||||||
|
conv.pendingElicitation = elicitation;
|
||||||
|
conv.lastActivityAt = new Date();
|
||||||
|
}
|
||||||
|
return convs;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearElicitationForConversation: (conversationId: string) => {
|
||||||
|
conversations.update((convs) => {
|
||||||
|
const conv = convs.get(conversationId);
|
||||||
|
if (conv) {
|
||||||
|
conv.pendingElicitation = null;
|
||||||
|
conv.lastActivityAt = new Date();
|
||||||
|
}
|
||||||
|
return convs;
|
||||||
|
});
|
||||||
|
},
|
||||||
setPendingRetryMessage: (message: string | null) => pendingRetryMessage.set(message),
|
setPendingRetryMessage: (message: string | null) => pendingRetryMessage.set(message),
|
||||||
|
|
||||||
// Conversation management
|
// Conversation management
|
||||||
|
|||||||
@@ -187,6 +187,33 @@ describe("toastStore", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("addError", () => {
|
||||||
|
it("adds an error toast with the warning icon", () => {
|
||||||
|
toastStore.addError("Something went wrong");
|
||||||
|
const toasts = get(toastStore);
|
||||||
|
expect(toasts).toHaveLength(1);
|
||||||
|
const toast = toasts[0];
|
||||||
|
expect(toast.kind).toBe("info");
|
||||||
|
if (toast.kind === "info") {
|
||||||
|
expect(toast.message).toBe("Something went wrong");
|
||||||
|
expect(toast.icon).toBe("⚠️");
|
||||||
|
expect(typeof toast.id).toBe("string");
|
||||||
|
expect(toast.id.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("auto-dismisses after 6000ms", () => {
|
||||||
|
toastStore.addError("Rate limit reached");
|
||||||
|
expect(get(toastStore)).toHaveLength(1);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(5999);
|
||||||
|
expect(get(toastStore)).toHaveLength(1);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1);
|
||||||
|
expect(get(toastStore)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("addUpdate", () => {
|
describe("addUpdate", () => {
|
||||||
it("adds a persistent update toast with the correct fields", () => {
|
it("adds a persistent update toast with the correct fields", () => {
|
||||||
toastStore.addUpdate("2.0.0", "1.9.0", "https://example.com/release");
|
toastStore.addUpdate("2.0.0", "1.9.0", "https://example.com/release");
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ function createToastStore() {
|
|||||||
setTimeout(() => remove(id), 4000);
|
setTimeout(() => remove(id), 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addError(message: string) {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
const toast: InfoToast = { id, kind: "info", message, icon: "⚠️" };
|
||||||
|
update((toasts) => [...toasts, toast]);
|
||||||
|
setTimeout(() => remove(id), 6000);
|
||||||
|
}
|
||||||
|
|
||||||
function addAchievement(achievement: AchievementUnlockedEvent["achievement"]) {
|
function addAchievement(achievement: AchievementUnlockedEvent["achievement"]) {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const toast: AchievementToast = { id, kind: "achievement", achievement };
|
const toast: AchievementToast = { id, kind: "achievement", achievement };
|
||||||
@@ -82,7 +89,7 @@ function createToastStore() {
|
|||||||
// Update toasts are persistent — no auto-dismiss
|
// Update toasts are persistent — no auto-dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
return { subscribe, addInfo, addAchievement, addUpdate, remove };
|
return { subscribe, addInfo, addError, addAchievement, addUpdate, remove };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toastStore = createToastStore();
|
export const toastStore = createToastStore();
|
||||||
|
|||||||
+57
-2
@@ -8,7 +8,10 @@ import { initStatsListener, resetSessionStats } from "$lib/stores/stats";
|
|||||||
import { initAchievementsListener } from "$lib/stores/achievements";
|
import { initAchievementsListener } from "$lib/stores/achievements";
|
||||||
import type {
|
import type {
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
|
ElicitationEvent,
|
||||||
PermissionPromptEvent,
|
PermissionPromptEvent,
|
||||||
|
PostCompactEvent,
|
||||||
|
StopFailureEvent,
|
||||||
UserQuestionEvent,
|
UserQuestionEvent,
|
||||||
} from "$lib/types/messages";
|
} from "$lib/types/messages";
|
||||||
import type { CharacterState } from "$lib/types/states";
|
import type { CharacterState } from "$lib/types/states";
|
||||||
@@ -406,7 +409,8 @@ export async function initializeTauriListeners() {
|
|||||||
| "rate-limit"
|
| "rate-limit"
|
||||||
| "compact-prompt"
|
| "compact-prompt"
|
||||||
| "worktree"
|
| "worktree"
|
||||||
| "config-change",
|
| "config-change"
|
||||||
|
| "elicitation",
|
||||||
content,
|
content,
|
||||||
tool_name || undefined,
|
tool_name || undefined,
|
||||||
costData,
|
costData,
|
||||||
@@ -425,7 +429,8 @@ export async function initializeTauriListeners() {
|
|||||||
| "rate-limit"
|
| "rate-limit"
|
||||||
| "compact-prompt"
|
| "compact-prompt"
|
||||||
| "worktree"
|
| "worktree"
|
||||||
| "config-change",
|
| "config-change"
|
||||||
|
| "elicitation",
|
||||||
content,
|
content,
|
||||||
tool_name || undefined,
|
tool_name || undefined,
|
||||||
costData,
|
costData,
|
||||||
@@ -611,6 +616,56 @@ export async function initializeTauriListeners() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
unlisteners.push(questionUnlisten);
|
unlisteners.push(questionUnlisten);
|
||||||
|
|
||||||
|
const elicitationUnlisten = await listen<ElicitationEvent>("claude:elicitation", (event) => {
|
||||||
|
const elicitationEvent = event.payload;
|
||||||
|
if (elicitationEvent.conversation_id) {
|
||||||
|
claudeStore.requestElicitationForConversation(
|
||||||
|
elicitationEvent.conversation_id,
|
||||||
|
elicitationEvent
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
claudeStore.requestElicitation(elicitationEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
unlisteners.push(elicitationUnlisten);
|
||||||
|
|
||||||
|
const elicitationResultUnlisten = await listen<{ conversation_id?: string }>(
|
||||||
|
"claude:elicitation-result",
|
||||||
|
(event) => {
|
||||||
|
const { conversation_id } = event.payload;
|
||||||
|
if (conversation_id) {
|
||||||
|
claudeStore.clearElicitationForConversation(conversation_id);
|
||||||
|
} else {
|
||||||
|
claudeStore.clearElicitation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
unlisteners.push(elicitationResultUnlisten);
|
||||||
|
|
||||||
|
const stopFailureUnlisten = await listen<StopFailureEvent>("claude:stop-failure", (event) => {
|
||||||
|
const { stop_reason, error_type } = event.payload;
|
||||||
|
|
||||||
|
characterState.setTemporaryState("error", 3000);
|
||||||
|
|
||||||
|
let message: string;
|
||||||
|
if (stop_reason === "rate_limit") {
|
||||||
|
message = "Rate limit reached";
|
||||||
|
} else if (stop_reason === "auth_failure" || stop_reason === "authentication") {
|
||||||
|
message = "Authentication failed";
|
||||||
|
} else {
|
||||||
|
message = `API error: ${stop_reason ?? error_type ?? "unknown"}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toastStore.addError(message);
|
||||||
|
});
|
||||||
|
unlisteners.push(stopFailureUnlisten);
|
||||||
|
|
||||||
|
const postCompactUnlisten = await listen<PostCompactEvent>("claude:post-compact", () => {
|
||||||
|
toastStore.addInfo("Context compacted", "🗜️");
|
||||||
|
characterState.setTemporaryState("success", 2000);
|
||||||
|
});
|
||||||
|
unlisteners.push(postCompactUnlisten);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cleanupTauriListeners() {
|
export function cleanupTauriListeners() {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export interface TerminalLine {
|
|||||||
| "rate-limit"
|
| "rate-limit"
|
||||||
| "compact-prompt"
|
| "compact-prompt"
|
||||||
| "worktree"
|
| "worktree"
|
||||||
| "config-change";
|
| "config-change"
|
||||||
|
| "elicitation";
|
||||||
content: string;
|
content: string;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
@@ -162,6 +163,30 @@ export interface UserQuestionEvent {
|
|||||||
conversation_id?: string;
|
conversation_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ElicitationEvent {
|
||||||
|
message: string;
|
||||||
|
server_name?: string;
|
||||||
|
request_id?: string;
|
||||||
|
conversation_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElicitationResultEvent {
|
||||||
|
action: string;
|
||||||
|
request_id?: string;
|
||||||
|
conversation_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StopFailureEvent {
|
||||||
|
stop_reason?: string;
|
||||||
|
error_type?: string;
|
||||||
|
conversation_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostCompactEvent {
|
||||||
|
session_id?: string;
|
||||||
|
conversation_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
|
export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
|
||||||
|
|
||||||
export interface Attachment {
|
export interface Attachment {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
import type { CharacterState } from "$lib/types/states";
|
import type { CharacterState } from "$lib/types/states";
|
||||||
import PermissionModal from "$lib/components/PermissionModal.svelte";
|
import PermissionModal from "$lib/components/PermissionModal.svelte";
|
||||||
import UserQuestionModal from "$lib/components/UserQuestionModal.svelte";
|
import UserQuestionModal from "$lib/components/UserQuestionModal.svelte";
|
||||||
|
import ElicitationModal from "$lib/components/ElicitationModal.svelte";
|
||||||
import ConfigSidebar from "$lib/components/ConfigSidebar.svelte";
|
import ConfigSidebar from "$lib/components/ConfigSidebar.svelte";
|
||||||
import AchievementsPanel from "$lib/components/AchievementsPanel.svelte";
|
import AchievementsPanel from "$lib/components/AchievementsPanel.svelte";
|
||||||
import ToastContainer from "$lib/components/ToastContainer.svelte";
|
import ToastContainer from "$lib/components/ToastContainer.svelte";
|
||||||
@@ -593,6 +594,7 @@
|
|||||||
|
|
||||||
<PermissionModal />
|
<PermissionModal />
|
||||||
<UserQuestionModal />
|
<UserQuestionModal />
|
||||||
|
<ElicitationModal />
|
||||||
<ConfigSidebar />
|
<ConfigSidebar />
|
||||||
<AchievementsPanel
|
<AchievementsPanel
|
||||||
bind:isOpen={achievementPanelOpen}
|
bind:isOpen={achievementPanelOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user