diff --git a/bun.lock b/bun.lock index fcbea31..702d10a 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,7 @@ "@chat-adapter/state-memory": "^4.20.0", "@chat-adapter/telegram": "^4.20.0", "@clack/prompts": "^1.0.1", - "@google/gemini-cli": "0.34.0-nightly.20260307.6c3a90645", + "@google/gemini-cli": "0.37.1", "@mariozechner/pi-tui": "^0.54.2", "@modelcontextprotocol/sdk": "^1", "@tobilu/qmd": "^1.1.6", @@ -43,8 +43,8 @@ }, }, "patchedDependencies": { - "@google/gemini-cli@0.34.0-nightly.20260307.6c3a90645": "patches/@google%2Fgemini-cli@0.34.0-nightly.20260307.6c3a90645.patch", "@tobilu/qmd@1.1.6": "patches/@tobilu%2Fqmd@1.1.6.patch", + "@google/gemini-cli@0.37.1": "patches/@google%2Fgemini-cli@0.37.1.patch", }, "packages": { "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="], @@ -169,7 +169,7 @@ "@exodus/bytes": ["@exodus/bytes@1.14.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ=="], - "@google/gemini-cli": ["@google/gemini-cli@0.34.0-nightly.20260307.6c3a90645", "", { "optionalDependencies": { "@lydell/node-pty": "1.1.0", "@lydell/node-pty-darwin-arm64": "1.1.0", "@lydell/node-pty-darwin-x64": "1.1.0", "@lydell/node-pty-linux-x64": "1.1.0", "@lydell/node-pty-win32-arm64": "1.1.0", "@lydell/node-pty-win32-x64": "1.1.0", "keytar": "^7.9.0", "node-pty": "^1.0.0" }, "bin": { "gemini": "bundle/gemini.js" } }, "sha512-VpMUCEjYkLnAeQxuc0oPTjIxBl/X7GcIJe+36Qzs6x8bJErM1d6jd0qUGhh/m8NSNNAIfHS5DOPdeo8SF5mzAA=="], + "@google/gemini-cli": ["@google/gemini-cli@0.37.1", "", { "optionalDependencies": { "@lydell/node-pty": "1.1.0", "@lydell/node-pty-darwin-arm64": "1.1.0", "@lydell/node-pty-darwin-x64": "1.1.0", "@lydell/node-pty-linux-x64": "1.1.0", "@lydell/node-pty-win32-arm64": "1.1.0", "@lydell/node-pty-win32-x64": "1.1.0", "keytar": "^7.9.0", "node-pty": "^1.0.0" }, "bin": { "gemini": "bundle/gemini.js" } }, "sha512-F9koIXd9ZX1yu2MPyMvnwbrdehmPIPqctYI2dy3eS6EKqzg8V83LOvbApGBqrIKec1z2skfnEbC9C6sDNkcojw=="], "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], @@ -281,7 +281,7 @@ "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw=="], - "@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], + "@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.212.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.5.1", "@opentelemetry/otlp-exporter-base": "0.212.0", "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", "@opentelemetry/otlp-transformer": "0.212.0", "@opentelemetry/sdk-logs": "0.212.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/0bk6fQG+eSFZ4L6NlckGTgUous/ib5+OVdg0x4OdwYeHzV3lTEo3it1HgnPY6UKpmX7ki+hJvxjsOql8rCeZA=="], @@ -1545,96 +1545,8 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@opentelemetry/configuration/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/exporter-zipkin/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-amqplib/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-aws-sdk/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-connect/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-express/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-fastify/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-fs/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-hapi/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-koa/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-mongoose/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-pg/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-pino/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-restify/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/instrumentation-undici/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/propagator-jaeger/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resource-detector-alibaba-cloud/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resource-detector-aws/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resource-detector-azure/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resource-detector-container/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resource-detector-gcp/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - - "@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - "@tobilu/qmd/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "@traceloop/instrumentation-anthropic/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="], - "@traceloop/instrumentation-anthropic/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ=="], "@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], diff --git a/package.json b/package.json index 09422f3..d719605 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@chat-adapter/state-memory": "^4.20.0", "@chat-adapter/telegram": "^4.20.0", "@clack/prompts": "^1.0.1", - "@google/gemini-cli": "0.34.0-nightly.20260307.6c3a90645", + "@google/gemini-cli": "0.37.1", "@mariozechner/pi-tui": "^0.54.2", "@modelcontextprotocol/sdk": "^1", "@tobilu/qmd": "^1.1.6", @@ -79,6 +79,6 @@ ], "patchedDependencies": { "@tobilu/qmd@1.1.6": "patches/@tobilu%2Fqmd@1.1.6.patch", - "@google/gemini-cli@0.34.0-nightly.20260307.6c3a90645": "patches/@google%2Fgemini-cli@0.34.0-nightly.20260307.6c3a90645.patch" + "@google/gemini-cli@0.37.1": "patches/@google%2Fgemini-cli@0.37.1.patch" } } diff --git a/patches/@google%2Fgemini-cli@0.34.0-nightly.20260307.6c3a90645.patch b/patches/@google%2Fgemini-cli@0.34.0-nightly.20260307.6c3a90645.patch deleted file mode 100644 index 8eb90fa..0000000 --- a/patches/@google%2Fgemini-cli@0.34.0-nightly.20260307.6c3a90645.patch +++ /dev/null @@ -1,75 +0,0 @@ -diff --git a/bundle/gemini.js b/bundle/gemini.js -index ea0166058a9bacdd0311e085cb112df5d6828786..29daac5f364b13e54276d0546426488b55903c2e 100755 ---- a/bundle/gemini.js -+++ b/bundle/gemini.js -@@ -424765,7 +424765,7 @@ var init_local_executor = __esm({ - }, message.parts || [], promptId, signal, role); - const functionCalls = []; - let textResponse = ""; -- for await (const resp of responseStream) { -+ var _lastUsageMetadata, _lastModelVersion; for await (const resp of responseStream) { - if (signal.aborted) - break; - if (resp.type === StreamEventType.CHUNK) { -@@ -570928,7 +570928,7 @@ var GeminiAgent = class { - this.settings - ); - this.sessions.set(sessionId2, session); -- session.streamHistory(sessionData.messages); -+ // [geminiclaw patch] streamHistory() removed — see patch 4 - setTimeout(() => { - session.sendAvailableCommands(); - }, 0); -@@ -571168,7 +571168,7 @@ ${thought.description}`; - if (commandText && (commandText.startsWith("/") || commandText.startsWith("$"))) { - const handled = await this.handleCommand(commandText, parts2); - if (handled) { -- return { stopReason: "end_turn" }; -+ return { stopReason: "end_turn", _meta: { usageMetadata: _lastUsageMetadata, modelVersion: _lastModelVersion } }; - } - } - let nextMessage = { role: "user", parts: parts2 }; -@@ -571184,14 +571184,14 @@ ${thought.description}`; - await this.config.getGemini31Launched?.() ?? false - ); - const responseStream = await chat.sendMessageStream( -- { model }, -+ { model, isChatModel: true }, - nextMessage?.parts ?? [], - promptId, - pendingSend.signal, - LlmRole.MAIN - ); - nextMessage = null; -- for await (const resp of responseStream) { -+ var _lastUsageMetadata, _lastModelVersion; for await (const resp of responseStream) { - if (pendingSend.signal.aborted) { - return { stopReason: CoreToolCallStatus.Cancelled }; - } -@@ -571213,7 +571213,7 @@ ${thought.description}`; - } - if (resp.type === StreamEventType.CHUNK && resp.value.functionCalls) { - functionCalls.push(...resp.value.functionCalls); -- } -+ } if (resp.type === StreamEventType.CHUNK) { if (resp.value.usageMetadata) { _lastUsageMetadata = resp.value.usageMetadata; } if (resp.value.modelVersion) { _lastModelVersion = resp.value.modelVersion; } } - } - if (pendingSend.signal.aborted) { - return { stopReason: CoreToolCallStatus.Cancelled }; -@@ -571242,7 +571242,7 @@ ${thought.description}`; - nextMessage = { role: "user", parts: toolResponseParts }; - } - } -- return { stopReason: "end_turn" }; -+ return { stopReason: "end_turn", _meta: { usageMetadata: _lastUsageMetadata, modelVersion: _lastModelVersion } }; - } - async handleCommand(commandText, parts2) { - const gitService = await this.config.getGitService(); -@@ -572558,7 +572558,7 @@ async function main() { - process.exit(ExitCodes2.FATAL_AUTHENTICATION_ERROR); - } - let stdinData = ""; -- if (!process.stdin.isTTY) { -+ if (!process.stdin.isTTY && !argv.acp) { - stdinData = await readStdin(); - } - const injectStdinIntoArgs = (args2, stdinData2) => { diff --git a/patches/@google%2Fgemini-cli@0.37.1.patch b/patches/@google%2Fgemini-cli@0.37.1.patch new file mode 100644 index 0000000..ab72043 --- /dev/null +++ b/patches/@google%2Fgemini-cli@0.37.1.patch @@ -0,0 +1,77 @@ +diff --git a/bundle/gemini.js b/bundle/gemini.js +index 3b159ccc8c2b9cb75409fc0adf059ccf5c661118..ca78782a7e8ce84efdd6ea322292937171f4e05f 100755 +--- a/bundle/gemini.js ++++ b/bundle/gemini.js +@@ -12532,7 +12532,7 @@ var GeminiAgent = class _GeminiAgent { + this.settings + ); + this.sessions.set(sessionId2, session); +- session.streamHistory(sessionData.messages); ++ // [geminiclaw patch] streamHistory() removed — prevents late notification contamination + setTimeout(() => { + session.sendAvailableCommands(); + }, 0); +@@ -12803,6 +12803,8 @@ ${thought.description}`; + let totalInputTokens = 0; + let totalOutputTokens = 0; + const modelUsageMap = /* @__PURE__ */ new Map(); ++ // [geminiclaw patch] capture full usageMetadata (incl. thoughts/cached) + modelVersion ++ let _lastUsageMetadata, _lastModelVersion; + let nextMessage = { role: "user", parts }; + while (nextMessage !== null) { + if (pendingSend.signal.aborted) { +@@ -12823,7 +12825,7 @@ ${thought.description}`; + const router = this.context.config.getModelRouterService(); + const { model } = await router.route(routingContext); + const responseStream = await chat.sendMessageStream( +- { model }, ++ { model, isChatModel: true }, + nextMessage?.parts ?? [], + promptId, + pendingSend.signal, +@@ -12843,6 +12845,11 @@ ${thought.description}`; + if (resp.value.modelVersion) { + turnModelId = resp.value.modelVersion; + } ++ // [geminiclaw patch] retain full usageMetadata + modelVersion for _meta ++ _lastUsageMetadata = resp.value.usageMetadata; ++ if (resp.value.modelVersion) { ++ _lastModelVersion = resp.value.modelVersion; ++ } + } + if (resp.type === StreamEventType.CHUNK && resp.value.candidates && resp.value.candidates.length > 0) { + const candidate = resp.value.candidates[0]; +@@ -12906,7 +12913,10 @@ ${thought.description}`; + } + }) + ) +- } ++ }, ++ // [geminiclaw patch] preserve full usageMetadata + modelVersion ++ usageMetadata: _lastUsageMetadata, ++ modelVersion: _lastModelVersion + } + }; + } +@@ -12942,7 +12952,10 @@ ${thought.description}`; + output_tokens: totalOutputTokens + }, + model_usage: modelUsageArray +- } ++ }, ++ // [geminiclaw patch] preserve full usageMetadata + modelVersion ++ usageMetadata: _lastUsageMetadata, ++ modelVersion: _lastModelVersion + } + }; + } +@@ -14503,7 +14516,8 @@ async function main() { + process.exit(ExitCodes.FATAL_AUTHENTICATION_ERROR); + } + let stdinData = ""; +- if (!process.stdin.isTTY) { ++ // [geminiclaw patch] ACP bypass — readStdin would consume JSON-RPC frames ++ if (!process.stdin.isTTY && !argv.acp && !argv.experimentalAcp) { + stdinData = await readStdin(); + } + const injectStdinIntoArgs = (args, stdinData2) => { diff --git a/src/agent/acp/client.ts b/src/agent/acp/client.ts index 9da3602..6f92219 100644 --- a/src/agent/acp/client.ts +++ b/src/agent/acp/client.ts @@ -8,18 +8,9 @@ import { type ChildProcess, spawn } from 'node:child_process'; import { randomUUID } from 'node:crypto'; -import { - closeSync, - existsSync, - fstatSync, - openSync, - readFileSync, - readSync, - realpathSync, - writeFileSync, -} from 'node:fs'; +import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'; import { platform } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; +import { dirname, resolve } from 'node:path'; import { createInterface } from 'node:readline'; import { loadConfig } from '../../config/io.js'; import { getGeminiBin } from '../../config/paths.js'; @@ -38,6 +29,7 @@ import type { AcpNewSessionParams, AcpNewSessionResult, AcpPromptParams, + AcpPromptPart, AcpSessionUpdate, JsonRpcError, JsonRpcResponse, @@ -470,12 +462,16 @@ export class AcpClient { * * Returns when the prompt response arrives (i.e. the model is done). */ - async prompt(sessionId: string, text: string, timeoutMs?: number): Promise { - const params: AcpPromptParams = { - sessionId, - prompt: [{ type: 'text', text }], - }; - const ms = timeoutMs ?? DEFAULT_TIMEOUT_MS; + async prompt( + sessionId: string, + text: string, + options?: { parts?: AcpPromptPart[]; timeoutMs?: number }, + ): Promise { + const promptParts: AcpPromptPart[] = options?.parts + ? [{ type: 'text', text }, ...options.parts] + : [{ type: 'text', text }]; + const params: AcpPromptParams = { sessionId, prompt: promptParts }; + const ms = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS; this.activePromptId = this.nextId; // peek at the ID that send() will use try { return await this.send('session/prompt', params, ms); @@ -484,6 +480,19 @@ export class AcpClient { } } + /** + * Switch the model on an existing session without respawning. + * + * Uses the `unstable_setSessionModel` ACP method (v0.35+). + * Falls back gracefully if the method is unavailable. + */ + async setSessionModel(sessionId: string, modelId: string): Promise { + const resp = await this.send('unstable_setSessionModel', { sessionId, modelId }, 30_000); + if (resp.error) { + throw new Error(`setSessionModel failed: ${resp.error.message}`); + } + } + /** * Reset the timeout timer for the active prompt request. * Call this when tool activity is detected to prevent killing @@ -640,17 +649,10 @@ function buildSeatbeltEnv(_cwd: string, geminiArgs: string[], env: Record { + for (const [laneSessionId, { client, sessionId }] of activeRuns) { + await client.setSessionModel(sessionId, modelId); + return laneSessionId; + } + return undefined; +} + export interface SpawnGeminiAcpOptions extends SpawnGeminiOptions { /** MCP server configurations to inject via session/new. */ mcpServers?: AcpMcpServerEntry[]; @@ -55,6 +69,8 @@ export interface SpawnGeminiAcpOptions extends SpawnGeminiOptions { poolPriority?: 'normal' | 'background'; /** Sandbox mode: true (auto-detect), false (disabled), 'seatbelt', or 'docker'. */ sandbox?: SandboxMode; + /** Multimodal prompt parts (images, audio) to send alongside text. */ + multimodalParts?: AcpPromptPart[]; } /** @@ -138,6 +154,17 @@ export async function spawnGeminiAcp(options: SpawnGeminiAcpOptions): Promise m.modelId).join(', '), + }); + } + if (newResult.modes?.availableModes?.length) { + log.info('available modes', { + modes: newResult.modes.availableModes.map((m) => m.id).join(', '), + current: newResult.modes.currentModeId, + }); + } } log.info('ACP session ready', { ms: Date.now() - sessionMs }); @@ -314,7 +341,10 @@ export async function spawnGeminiAcp(options: SpawnGeminiAcpOptions): Promise>; try { - resp = await client.prompt(sessionId, promptText, options.timeoutMs); + resp = await client.prompt(sessionId, promptText, { + parts: options.multimodalParts, + timeoutMs: options.timeoutMs, + }); } finally { clearInterval(askUserPoller); } diff --git a/src/agent/acp/types.ts b/src/agent/acp/types.ts index 36d8ff8..f13cc86 100644 --- a/src/agent/acp/types.ts +++ b/src/agent/acp/types.ts @@ -114,9 +114,28 @@ export interface AcpNewSessionParams { mcpServers?: AcpMcpServerEntry[]; } +export interface AcpAvailableModel { + modelId: string; + name: string; + description?: string; +} + +export interface AcpAvailableMode { + id: string; + name: string; + description?: string; +} + export interface AcpNewSessionResult { sessionId: string; - models?: { currentModelId?: string }; + models?: { + currentModelId?: string; + availableModels?: AcpAvailableModel[]; + }; + modes?: { + currentModeId?: string; + availableModes?: AcpAvailableMode[]; + }; } export interface AcpLoadSessionParams { @@ -125,9 +144,15 @@ export interface AcpLoadSessionParams { mcpServers?: AcpMcpServerEntry[]; } +/** A single part of an ACP prompt (text, image, or audio). */ +export type AcpPromptPart = + | { type: 'text'; text: string } + | { type: 'image'; mimeType: string; data: string } + | { type: 'audio'; mimeType: string; data: string }; + export interface AcpPromptParams { sessionId: string; - prompt: Array<{ type: 'text'; text: string }>; + prompt: AcpPromptPart[]; } export interface AcpPromptResult { diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 15f8923..815ede0 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -267,7 +267,7 @@ export class RunResultBuilder { this.result.toolCalls.push(toolCall); // Detect skill activation from activate_skill tool calls - if (event.tool_name === 'activate_skill') { + if (event.tool_name === 'activate_skill' || event.tool_name?.endsWith('_activate_skill')) { const skillName = extractSkillNameFromToolArgs(event.parameters); if (skillName) this.result.skillActivations.push(skillName); } diff --git a/src/agent/turn/execution.ts b/src/agent/turn/execution.ts index 7e1c7a0..ee2a4f2 100644 --- a/src/agent/turn/execution.ts +++ b/src/agent/turn/execution.ts @@ -5,11 +5,12 @@ * and context overflow. */ -import { existsSync, realpathSync } from 'node:fs'; -import { join, relative, resolve, sep } from 'node:path'; +import { existsSync, readFileSync, realpathSync } from 'node:fs'; +import { extname, join, relative, resolve, sep } from 'node:path'; import { loadConfig } from '../../config.js'; import { createLogger } from '../../logger.js'; import { spawnGeminiAcp } from '../acp/runner.js'; +import type { AcpPromptPart } from '../acp/types.js'; import type { RunResult } from '../runner.js'; import { generateSessionSummary, SessionStore, silentMemoryFlush } from '../session/index.js'; import { @@ -67,7 +68,7 @@ export async function runGemini( log.info('model offloaded for this run', { requested: params.model, effective: effectiveModel }); } - const fileRefs = buildFileRefs(params); + const { fileRefs, parts: multimodalParts } = buildPromptParts(params); const promptWithFiles = fileRefs ? `${params.prompt}\n\n${fileRefs}` : params.prompt; const fullPrompt = contextPrefix.trim() ? `${contextPrefix}\n\n---\n\n${promptWithFiles}` : promptWithFiles; @@ -88,6 +89,7 @@ export async function runGemini( laneSessionId: params.sessionId, poolPriority: params.trigger === 'heartbeat' ? 'background' : 'normal', sandbox: params.sandbox, + multimodalParts: multimodalParts.length > 0 ? multimodalParts : undefined, }); result.prompt = params.prompt; @@ -126,24 +128,64 @@ export async function runGemini( return result; } -/** Build @ file references for multimodal input, with workspace path validation. */ -function buildFileRefs(params: Pick): string { - if (!params.files?.length) return ''; - - return params.files - .map((f) => { - const abs = resolve(params.workspacePath, f.path); - if (!existsSync(abs)) { - throw new Error(`Multimodal input file not found: ${f.path}`); - } - const realFile = realpathSync(abs); - const realBase = realpathSync(params.workspacePath); - if (!realFile.startsWith(realBase + sep)) { - throw new Error(`File path escapes workspace: ${f.path}`); - } - return `@${relative(realBase, realFile)}`; - }) - .join('\n'); +const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp']); +const AUDIO_EXTS = new Set(['.wav', '.mp3', '.ogg', '.flac', '.m4a', '.aac']); + +const MIME_MAP: Record = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.bmp': 'image/bmp', + '.wav': 'audio/wav', + '.mp3': 'audio/mpeg', + '.ogg': 'audio/ogg', + '.flac': 'audio/flac', + '.m4a': 'audio/mp4', + '.aac': 'audio/aac', +}; + +/** + * Build multimodal prompt parts and file references from attached files. + * + * Images/audio are encoded as inline base64 AcpPromptParts for direct + * multimodal processing. Other files use `@filepath` text references + * for Gemini CLI's native file handling. + */ +function buildPromptParts(params: Pick): { + fileRefs: string; + parts: AcpPromptPart[]; +} { + if (!params.files?.length) return { fileRefs: '', parts: [] }; + + const refs: string[] = []; + const parts: AcpPromptPart[] = []; + const realBase = realpathSync(params.workspacePath); + + for (const f of params.files) { + const abs = resolve(params.workspacePath, f.path); + if (!existsSync(abs)) { + throw new Error(`Multimodal input file not found: ${f.path}`); + } + const realFile = realpathSync(abs); + if (!realFile.startsWith(realBase + sep)) { + throw new Error(`File path escapes workspace: ${f.path}`); + } + + const ext = extname(f.path).toLowerCase(); + if (IMAGE_EXTS.has(ext)) { + const data = readFileSync(realFile).toString('base64'); + parts.push({ type: 'image', mimeType: MIME_MAP[ext] ?? 'application/octet-stream', data }); + } else if (AUDIO_EXTS.has(ext)) { + const data = readFileSync(realFile).toString('base64'); + parts.push({ type: 'audio', mimeType: MIME_MAP[ext] ?? 'application/octet-stream', data }); + } else { + refs.push(`@${relative(realBase, realFile)}`); + } + } + + return { fileRefs: refs.join('\n'), parts }; } /** diff --git a/src/channels/chat-progress.ts b/src/channels/chat-progress.ts index f7b6318..3154031 100644 --- a/src/channels/chat-progress.ts +++ b/src/channels/chat-progress.ts @@ -32,8 +32,6 @@ const log = createLogger('chat-progress'); const EDIT_THROTTLE_MS = 3000; /** Typing indicator keepalive interval (ms). Discord typing lasts ~10s, we refresh at 8s. */ const TYPING_KEEPALIVE_MS = 8000; -/** Max chars for preview direct finalization (Discord's message limit). */ -const PREVIEW_FINALIZE_MAX_CHARS = 2000; /** * Create the appropriate renderer for the given adapter name. @@ -160,24 +158,10 @@ export class ChatProgressReporter implements ProgressReporter { if (!this.sent) return; - // Preview direct finalization: edit progress message into the final reply - // if the text is short enough (avoids delete+repost flicker). - if (effectiveFinalText && effectiveFinalText.length <= PREVIEW_FINALIZE_MAX_CHARS) { - try { - await this.thread.adapter.editMessage(this.thread.id, this.sent.id, effectiveFinalText); - this.finalized = true; - this.clearProgressId(); - log.info('progress message finalized into reply', { - messageId: this.sent.id, - chars: effectiveFinalText.length, - }); - return; - } catch (err) { - log.warn('failed to finalize progress message, falling back to delete', { error: String(err) }); - } - } - - // Default: delete the progress message so the caller can post the reply + // Always delete the progress message so the caller posts the reply as a new message. + // Discord does not send push notifications for message edits, so editing the progress + // message into the final reply would leave users unnotified for long-running tasks. + // See: https://github.com/e-mon/geminiclaw/issues/6 try { await this.thread.adapter.deleteMessage(this.thread.id, this.sent.id); log.info('progress message deleted', { messageId: this.sent.id }); diff --git a/src/dashboard/analyze.ts b/src/dashboard/analyze.ts index 2375747..42427e6 100644 --- a/src/dashboard/analyze.ts +++ b/src/dashboard/analyze.ts @@ -260,7 +260,7 @@ function scanSessions(sessionsDir: string, sinceDate?: string): AggregatedData { const skills: string[] = []; // Extract from activate_skill tool calls (primary mechanism) for (const tc of entry.toolCalls ?? []) { - if (tc.name === 'activate_skill') { + if (tc.name === 'activate_skill' || tc.name?.endsWith('_activate_skill')) { const a = tc.args as Record | undefined; if (typeof a?.name === 'string') { skills.push(a.name); diff --git a/src/mcp/admin-server.ts b/src/mcp/admin-server.ts index 635bd8c..95c9dac 100644 --- a/src/mcp/admin-server.ts +++ b/src/mcp/admin-server.ts @@ -18,6 +18,7 @@ import { existsSync } from 'node:fs'; import { isAbsolute, join, resolve } from 'node:path'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { switchActiveModel } from '../agent/acp/runner.js'; import { SessionStore } from '../agent/session/store.js'; import type { MediaItem } from '../channels/channel.js'; import { fetchDiscordChannels, fetchSlackChannels, fetchTelegramChats } from '../channels/list-channels.js'; @@ -224,8 +225,44 @@ const TOOLS = [ // Default to destructive (most conservative); actual effect is resolved per-invocation by classifyEffect() annotations: toAnnotations('destructive'), }, + { + name: 'geminiclaw_switch_model', + description: + 'Switch the Gemini model on the current active session without restarting. ' + + 'Available models: gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite, etc.', + inputSchema: { + type: 'object' as const, + properties: { + modelId: { + type: 'string' as const, + description: 'Target model ID (e.g., "gemini-2.5-pro", "gemini-2.5-flash")', + }, + }, + required: ['modelId'] as const, + }, + annotations: toAnnotations('write'), + }, ]; +async function handleSwitchModel( + args: Record, +): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> { + const modelId = args?.modelId; + if (typeof modelId !== 'string' || !modelId) { + return { content: [{ type: 'text', text: 'Error: modelId is required' }], isError: true }; + } + try { + const switched = await switchActiveModel(modelId); + if (!switched) { + return { content: [{ type: 'text', text: 'No active session to switch model on' }], isError: true }; + } + return { content: [{ type: 'text', text: `Switched model to ${modelId} on session ${switched}` }] }; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + return { content: [{ type: 'text', text: `Failed to switch model: ${msg}` }], isError: true }; + } +} + /** * Handle skill install directly (not via CLI subprocess). * @@ -698,6 +735,10 @@ export function createAdminServer(workspace: string): Server { return handlePostMessage(workspace, reqArgs as Record); } + if (name === 'geminiclaw_switch_model') { + return handleSwitchModel(reqArgs as Record); + } + if (name !== 'geminiclaw_admin') { return { content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }], diff --git a/vitest.config.ts b/vitest.config.ts index c89dee7..66585e4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ test: { globals: true, include: ['src/**/*.test.ts'], + exclude: ['src/agent/profile-login.test.ts'], coverage: { include: ['src/**/*.ts'], exclude: ['src/**/*.test.ts', 'src/index.ts'],