From 5e3540c2d503b0bfb3d6405b8b37832363e74226 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sat, 16 May 2026 15:20:08 +0800 Subject: [PATCH 1/7] feat: add Ctrl+O to view live process stdout in fullscreen overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add onProcessStdout callback chain through executor → bash-handler → session → App - Stream real-time stdout/stderr from bash commands to UI ref (capped at 1MB) - Add ProcessStdoutView fullscreen overlay with scroll support - Bind Ctrl+O in PromptInput to toggle the stdout view - Footer hint shows 'ctrl+o view output' when a process is running --- package-lock.json | 814 +++++++---------------------------- src/session.ts | 4 + src/tools/bash-handler.ts | 4 + src/tools/executor.ts | 3 + src/ui/App.tsx | 37 +- src/ui/ProcessStdoutView.tsx | 109 +++++ src/ui/PromptInput.tsx | 21 +- 7 files changed, 327 insertions(+), 665 deletions(-) create mode 100644 src/ui/ProcessStdoutView.tsx diff --git a/package-lock.json b/package-lock.json index 958eed7..f9caecd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -846,13 +846,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -863,19 +856,6 @@ "node": ">= 4" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/js": { "version": "9.39.4", "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.4.tgz", @@ -1060,13 +1040,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.8.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.8.0.tgz", + "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/react": { @@ -1086,17 +1066,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", - "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/type-utils": "8.59.2", - "@typescript-eslint/utils": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1109,22 +1089,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.2", + "@typescript-eslint/parser": "^8.59.3", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.59.2.tgz", - "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3" }, "engines": { @@ -1140,14 +1120,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", - "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.2", - "@typescript-eslint/types": "^8.59.2", + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", "debug": "^4.4.3" }, "engines": { @@ -1162,14 +1142,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", - "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2" + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1180,9 +1160,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", - "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", "dev": true, "license": "MIT", "engines": { @@ -1197,15 +1177,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", - "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1222,9 +1202,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.59.2.tgz", - "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", "dev": true, "license": "MIT", "engines": { @@ -1236,16 +1216,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", - "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.2", - "@typescript-eslint/tsconfig-utils": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1316,16 +1296,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.59.2.tgz", - "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2" + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1340,13 +1320,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", - "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/types": "8.59.3", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1450,13 +1430,11 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" }, "node_modules/auto-bind": { "version": "5.0.1", @@ -1725,7 +1703,7 @@ }, "node_modules/ejs": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.2.tgz", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-5.0.2.tgz", "integrity": "sha512-IpbUaI/CAW86l3f+T8zN0iggSc0LmMZLcIW5eRVStLVNCoTXkE0YlncbbH50fp8Cl6zHIky0sW2uUbhBqGw0Jw==", "license": "Apache-2.0", "bin": { @@ -1736,9 +1714,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.353", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", - "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "version": "1.5.356", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz", + "integrity": "sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==", "dev": true, "license": "ISC" }, @@ -1824,12 +1802,16 @@ } }, "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { @@ -1991,19 +1973,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -2226,9 +2195,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "license": "MIT", "engines": { "node": ">=18" @@ -2237,22 +2206,9 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", @@ -2283,7 +2239,7 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", @@ -2293,7 +2249,7 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "5.0.6", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", @@ -2306,7 +2262,7 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "10.2.5", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", @@ -2361,6 +2317,28 @@ "node": ">=6.0" } }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -2453,9 +2431,9 @@ } }, "node_modules/ink": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/ink/-/ink-7.0.1.tgz", - "integrity": "sha512-o6LAC268PLawlGVYrXTyaTfke4VtJftEheuwbgkQf7yvSXyWp1nRwBbAyKEkWXFZZsW/la5wrMuNbuBvZK2C1w==", + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/ink/-/ink-7.0.3.tgz", + "integrity": "sha512-5kxHkIj9+RuqCU3zyvP4qvYWNOSHP2TW/SHayHGHOmk87KwfVcZwvJGemi9ch+ci2gXUqerK/Eh2DGEDt5q45g==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", @@ -2598,13 +2576,13 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -2971,7 +2949,7 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", @@ -2994,9 +2972,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "version": "2.0.44", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", "dev": true, "license": "MIT" }, @@ -3016,9 +2994,9 @@ } }, "node_modules/openai": { - "version": "6.35.0", - "resolved": "https://registry.npmmirror.com/openai/-/openai-6.35.0.tgz", - "integrity": "sha512-L/skwIGnt5xQZHb0UfTu9uAUKbis3ehKypOuJKi20QvG7UStV6C8IC3myGYHcdiF4kms/bAvOJ9UqqNWqi8x/Q==", + "version": "6.37.0", + "resolved": "https://registry.npmmirror.com/openai/-/openai-6.37.0.tgz", + "integrity": "sha512-0H5dEGFmmLv6KSd0W1w2nyL8WsLkX6yoLeQpU+dZAOuGcany5qkYQMmj35ZrKgb6yiyYqpUzFOpR8mZQkgqeEQ==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -3130,7 +3108,7 @@ }, "node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", @@ -3147,7 +3125,7 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "11.3.6", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.3.6.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", @@ -3212,9 +3190,9 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmmirror.com/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.6", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3245,16 +3223,6 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -3370,6 +3338,15 @@ "node": ">=10" } }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", @@ -3527,14 +3504,13 @@ } }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.0", + "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.22.0.tgz", + "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -3546,490 +3522,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -4073,16 +3565,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.2", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.59.2.tgz", - "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "version": "8.59.3", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.2", - "@typescript-eslint/parser": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2" + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4097,9 +3589,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -4203,9 +3695,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.20.1", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4231,9 +3723,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "optional": true, diff --git a/src/session.ts b/src/session.ts index 8c078f3..6da8835 100644 --- a/src/session.ts +++ b/src/session.ts @@ -197,6 +197,7 @@ type SessionManagerOptions = { onSessionEntryUpdated?: (entry: SessionEntry) => void; onLlmStreamProgress?: (progress: LlmStreamProgress) => void; onMcpStatusChanged?: () => void; + onProcessStdout?: (pid: number, chunk: string) => void; }; export type LlmStreamProgress = { @@ -220,6 +221,7 @@ export class SessionManager { private readonly onSessionEntryUpdated?: (entry: SessionEntry) => void; private readonly onLlmStreamProgress?: (progress: LlmStreamProgress) => void; private readonly onMcpStatusChanged?: () => void; + private readonly onProcessStdout?: (pid: number, chunk: string) => void; private activeSessionId: string | null = null; private activePromptController: AbortController | null = null; private readonly sessionControllers = new Map(); @@ -235,6 +237,7 @@ export class SessionManager { this.onSessionEntryUpdated = options.onSessionEntryUpdated; this.onLlmStreamProgress = options.onLlmStreamProgress; this.onMcpStatusChanged = options.onMcpStatusChanged; + this.onProcessStdout = options.onProcessStdout; this.toolExecutor = new ToolExecutor(this.projectRoot, this.createOpenAIClient, this.mcpManager); this.mcpManager.prepare(this.getResolvedSettings().mcpServers); } @@ -1699,6 +1702,7 @@ ${skillMd} const toolExecutions = await this.toolExecutor.executeToolCalls(sessionId, toolCalls, { onProcessStart: (pid, command) => this.addSessionProcess(sessionId, pid, command), onProcessExit: (pid) => this.removeSessionProcess(sessionId, pid), + onProcessStdout: (pid, chunk) => this.onProcessStdout?.(Number(pid), chunk), shouldStop: () => this.isInterrupted(sessionId), }); if (this.isInterrupted(sessionId)) { diff --git a/src/tools/bash-handler.ts b/src/tools/bash-handler.ts index 95e7e76..071da53 100644 --- a/src/tools/bash-handler.ts +++ b/src/tools/bash-handler.ts @@ -124,9 +124,13 @@ async function executeShellCommand( child.stdout?.on("data", (chunk: string | Buffer) => { stdout = appendChunk(stdout, chunk); + const text = typeof chunk === "string" ? chunk : chunk.toString("utf8"); + context.onProcessStdout?.(pid as number, text); }); child.stderr?.on("data", (chunk: string | Buffer) => { stderr = appendChunk(stderr, chunk); + const text = typeof chunk === "string" ? chunk : chunk.toString("utf8"); + context.onProcessStdout?.(pid as number, text); }); child.on("error", (spawnError) => { diff --git a/src/tools/executor.ts b/src/tools/executor.ts index bc2d7d8..e6018d9 100644 --- a/src/tools/executor.ts +++ b/src/tools/executor.ts @@ -37,11 +37,13 @@ export type ToolExecutionContext = { createOpenAIClient?: CreateOpenAIClient; onProcessStart?: (processId: string | number, command: string) => void; onProcessExit?: (processId: string | number) => void; + onProcessStdout?: (processId: string | number, chunk: string) => void; }; export type ToolExecutionHooks = { onProcessStart?: (processId: string | number, command: string) => void; onProcessExit?: (processId: string | number) => void; + onProcessStdout?: (processId: string | number, chunk: string) => void; shouldStop?: () => boolean; }; @@ -195,6 +197,7 @@ export class ToolExecutor { createOpenAIClient: this.createOpenAIClient, onProcessStart: hooks?.onProcessStart, onProcessExit: hooks?.onProcessExit, + onProcessStdout: hooks?.onProcessStdout, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 8c5c375..c864187 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -30,6 +30,7 @@ import { findExpandedThinkingId } from "./thinkingState"; import { WelcomeScreen } from "./WelcomeScreen"; import { AskUserQuestionPrompt } from "./AskUserQuestionPrompt"; import { McpStatusList } from "./McpStatusList"; +import { ProcessStdoutView } from "./ProcessStdoutView"; import { findPendingAskUserQuestion, formatAskUserQuestionAnswers, @@ -69,6 +70,8 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R const [resolvedSettings, setResolvedSettings] = useState(() => resolveCurrentSettings(projectRoot)); const [nowTick, setNowTick] = useState(0); const [mcpStatuses, setMcpStatuses] = useState>([]); + const [showProcessStdout, setShowProcessStdout] = useState(false); + const processStdoutRef = useRef>(new Map()); const messagesRef = useRef([]); messagesRef.current = messages; @@ -98,6 +101,19 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R // 当 MCP 状态变更时,如果当前正在查看 MCP 状态页面,则更新显示 setMcpStatuses(sessionManager.getMcpStatus()); }, + onProcessStdout: (pid, chunk) => { + const buf = processStdoutRef.current; + const current = buf.get(pid) ?? ""; + // Cap at 1 MB per process to avoid unbounded memory growth + // on noisy or long-running commands like `yes` or verbose builds. + const MAX_STDOUT_BUFFER = 1_000_000; + if (current.length >= MAX_STDOUT_BUFFER) { + return; + } + const text = typeof chunk === "string" ? chunk : String(chunk); + const available = MAX_STDOUT_BUFFER - current.length; + buf.set(pid, current + text.slice(0, available)); + }, }); }, [projectRoot]); @@ -218,6 +234,8 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R setBusy(true); setErrorLine(null); setRunningProcesses(null); + setShowProcessStdout(false); + processStdoutRef.current.clear(); try { await sessionManager.handleUserPrompt(prompt); await refreshSkills(); @@ -238,6 +256,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R sessionManager.interruptActiveSession(); }, [sessionManager]); + const handleToggleProcessStdout = useCallback(() => { + setShowProcessStdout(true); + }, []); + + const handleDismissProcessStdout = useCallback(() => { + setShowProcessStdout(false); + }, []); + const handleModelConfigChange = useCallback( (selection: ModelConfigSelection): string => { const current = resolveCurrentSettings(projectRoot); @@ -442,7 +468,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R Error: {errorLine} ) : null} - {view === "session-list" ? ( + {showProcessStdout ? ( + + ) : view === "session-list" ? ( void handleSelectSession(id)} @@ -464,9 +497,11 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R promptHistory={promptHistory} busy={busy} loadingText={loadingText} + runningProcesses={runningProcesses} onSubmit={handleSubmit} onModelConfigChange={handleModelConfigChange} onInterrupt={handleInterrupt} + onToggleProcessStdout={handleToggleProcessStdout} placeholder="Type your message..." /> )} diff --git a/src/ui/ProcessStdoutView.tsx b/src/ui/ProcessStdoutView.tsx new file mode 100644 index 0000000..a0676c6 --- /dev/null +++ b/src/ui/ProcessStdoutView.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Box, Text } from "ink"; +import type { SessionEntry } from "../session"; +import { useTerminalInput } from "./prompt"; + +type RunningProcesses = SessionEntry["processes"]; + +type ProcessStdoutViewProps = { + processStdoutRef: React.MutableRefObject>; + runningProcesses: RunningProcesses; + onDismiss: () => void; + screenWidth: number; +}; + +const REFRESH_INTERVAL_MS = 150; +const MAX_VISIBLE_LINES = 100; + +export const ProcessStdoutView = React.memo(function ProcessStdoutView({ + processStdoutRef, + runningProcesses, + onDismiss, + screenWidth, +}: ProcessStdoutViewProps): React.ReactElement { + const [stdoutText, setStdoutText] = useState(""); + const [scrollOffset, setScrollOffset] = useState(0); + const containerRef = useRef<{ lineCount: number }>({ lineCount: 0 }); + + useEffect(() => { + const updateStdout = () => { + let text = ""; + if (runningProcesses && runningProcesses.size > 0) { + for (const [pid, proc] of runningProcesses.entries()) { + const pidNum = Number(pid); + const stdout = processStdoutRef.current.get(pidNum) ?? ""; + if (text) { + text += "\n"; + } + if (runningProcesses.size > 1) { + text += `── Process ${pid} [${proc.command}] ──\n`; + } + text += stdout || "(no output yet)"; + } + } else { + text = "(no running processes)"; + } + setStdoutText(text); + }; + + updateStdout(); + const interval = setInterval(updateStdout, REFRESH_INTERVAL_MS); + return () => clearInterval(interval); + }, [processStdoutRef, runningProcesses]); + + // Update container line count for scroll awareness + const lines = useMemo(() => stdoutText.split("\n"), [stdoutText]); + containerRef.current.lineCount = lines.length; + + const visibleLines = useMemo(() => { + if (lines.length <= MAX_VISIBLE_LINES) { + return lines; + } + const start = Math.max(0, lines.length - MAX_VISIBLE_LINES - scrollOffset); + const slice = lines.slice(start, start + MAX_VISIBLE_LINES); + if (lines.length > MAX_VISIBLE_LINES) { + slice.unshift(`... (${start} lines above · ↑/↓ to scroll · ${lines.length} total lines) ...`); + } + return slice; + }, [lines, scrollOffset]); + + useTerminalInput( + (input, key) => { + if ((key.ctrl && (input === "o" || input === "O")) || key.escape) { + onDismiss(); + return; + } + if (key.upArrow) { + setScrollOffset((s) => Math.min(s + 10, Math.max(0, lines.length - MAX_VISIBLE_LINES))); + return; + } + if (key.downArrow) { + setScrollOffset((s) => Math.max(s - 10, 0)); + return; + } + if (key.pageUp) { + setScrollOffset((s) => Math.min(s + MAX_VISIBLE_LINES, Math.max(0, lines.length - MAX_VISIBLE_LINES))); + return; + } + if (key.pageDown) { + setScrollOffset((s) => Math.max(s - MAX_VISIBLE_LINES, 0)); + return; + } + }, + { isActive: true } + ); + + return ( + + + 📟 Process Output + (Ctrl+O or Esc to close · ↑↓ PageUp/PageDown to scroll) + + + {visibleLines.map((line, index) => ( + {line} + ))} + + + ); +}); diff --git a/src/ui/PromptInput.tsx b/src/ui/PromptInput.tsx index 3965144..db3a956 100644 --- a/src/ui/PromptInput.tsx +++ b/src/ui/PromptInput.tsx @@ -60,9 +60,11 @@ type Props = { loadingText?: string | null; disabled?: boolean; placeholder?: string; + runningProcesses?: Map | null; onSubmit: (submission: PromptSubmission) => void; onModelConfigChange: (selection: ModelConfigSelection) => string | Promise; onInterrupt: () => void; + onToggleProcessStdout?: () => void; }; const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; @@ -109,9 +111,11 @@ export const PromptInput = React.memo(function PromptInput({ loadingText, disabled, placeholder, + runningProcesses, onSubmit, onModelConfigChange, onInterrupt, + onToggleProcessStdout, }: Props): React.ReactElement { const { exit } = useApp(); const { stdout } = useStdout(); @@ -141,13 +145,15 @@ export const PromptInput = React.memo(function PromptInput({ ); const showMenu = slashMenu.length > 0; const promptHistoryKey = React.useMemo(() => promptHistory.join("\0"), [promptHistory]); + const hasRunningProcess = runningProcesses && runningProcesses.size > 0; + const processHint = hasRunningProcess ? " · ctrl+o view output" : ""; const footerText = statusMessage ? statusMessage : busy ? loadingText && loadingText.trim() - ? loadingText - : "esc to interrupt · ctrl+c to cancel input" - : "enter send · shift+enter newline · ctrl+v image · / commands · ctrl+d exit"; + ? `${loadingText}${processHint}` + : `esc to interrupt · ctrl+c to cancel input${processHint}` + : `enter send · shift+enter newline · ctrl+v image · / commands · ctrl+d exit${processHint}`; useTerminalFocusReporting(stdout, !disabled); useTerminalExtendedKeys(stdout, !disabled); useHiddenTerminalCursor(stdout, !disabled); @@ -223,6 +229,15 @@ export const PromptInput = React.memo(function PromptInput({ return; } + if (key.ctrl && (input === "o" || input === "O")) { + if (runningProcesses && runningProcesses.size > 0 && onToggleProcessStdout) { + onToggleProcessStdout(); + } else { + setStatusMessage("No running process to inspect"); + } + return; + } + if (key.ctrl && (input === "d" || input === "D")) { if (!isEmpty(buffer)) { updateBuffer((s) => deleteForward(s)); From ccaa6dd073e25bbdb61dd74192a9889d5d036c1c Mon Sep 17 00:00:00 2001 From: Yanwu Date: Sat, 16 May 2026 17:58:18 +0200 Subject: [PATCH 2/7] feat: add -p/--prompt flag to auto-submit prompt on launch Adds CLI argument -p / --prompt that takes a prompt string and auto-submits it immediately when deepcode starts. Usage: deepcode -p 'write a Python script' deepcode --prompt 'write a Python script' --- src/cli.tsx | 24 ++++++++++++++++++++---- src/ui/App.tsx | 4 +++- src/ui/PromptInput.tsx | 19 ++++++++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/cli.tsx b/src/cli.tsx index a0847d4..5f5ccb2 100644 --- a/src/cli.tsx +++ b/src/cli.tsx @@ -18,9 +18,11 @@ if (args.includes("--help") || args.includes("-h")) { "deepcode - Deep Code CLI", "", "Usage:", - " deepcode Launch the interactive TUI in the current directory", - " deepcode --version Print the version", - " deepcode --help Show this help", + " deepcode Launch the interactive TUI in the current directory", + " deepcode -p Launch with a pre-filled prompt", + " deepcode --prompt Same as -p", + " deepcode --version Print the version", + " deepcode --help Show this help", "", "Configuration:", " ~/.deepcode/settings.json User-level API key, model, base URL", @@ -50,6 +52,15 @@ if (args.includes("--help") || args.includes("-h")) { process.exit(0); } +function extractInitialPrompt(args: string[]): string | undefined { + const promptIndex = args.findIndex((arg) => arg === "-p" || arg === "--prompt"); + if (promptIndex !== -1 && promptIndex + 1 < args.length) { + return args[promptIndex + 1]; + } + return undefined; +} + +const initialPrompt = extractInitialPrompt(args); const projectRoot = process.cwd(); configureWindowsShell(); @@ -68,7 +79,12 @@ async function main(): Promise { function startApp(): void { let restarting = false; const inkInstance = render( - restartRef.current?.()} />, + restartRef.current?.()} + />, { exitOnCtrlC: false } ); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index bafb412..1b60618 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -45,10 +45,11 @@ type View = "chat" | "session-list" | "mcp-status"; type AppProps = { projectRoot: string; version?: string; + initialPrompt?: string; onRestart?: () => void; }; -export function App({ projectRoot, version = "", onRestart }: AppProps): React.ReactElement { +export function App({ projectRoot, version = "", initialPrompt, onRestart }: AppProps): React.ReactElement { const { exit } = useApp(); const { stdout, write } = useStdout(); const { columns } = useWindowSize(); @@ -470,6 +471,7 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R promptHistory={promptHistory} busy={busy} loadingText={loadingText} + initialPrompt={initialPrompt} onSubmit={handleSubmit} onModelConfigChange={handleModelConfigChange} onInterrupt={handleInterrupt} diff --git a/src/ui/PromptInput.tsx b/src/ui/PromptInput.tsx index b32d926..353a827 100644 --- a/src/ui/PromptInput.tsx +++ b/src/ui/PromptInput.tsx @@ -60,6 +60,7 @@ type Props = { loadingText?: string | null; disabled?: boolean; placeholder?: string; + initialPrompt?: string; onSubmit: (submission: PromptSubmission) => void; onModelConfigChange: (selection: ModelConfigSelection) => string | Promise; onInterrupt: () => void; @@ -109,13 +110,16 @@ export const PromptInput = React.memo(function PromptInput({ loadingText, disabled, placeholder, + initialPrompt, onSubmit, onModelConfigChange, onInterrupt, }: Props): React.ReactElement { const { exit } = useApp(); const { stdout } = useStdout(); - const [buffer, setBuffer] = useState(EMPTY_BUFFER); + const [buffer, setBuffer] = useState(() => + initialPrompt ? { text: initialPrompt, cursor: initialPrompt.length } : EMPTY_BUFFER + ); const [imageUrls, setImageUrls] = useState([]); const [selectedSkills, setSelectedSkills] = useState([]); const [statusMessage, setStatusMessage] = useState(null); @@ -192,6 +196,19 @@ export const PromptInput = React.memo(function PromptInput({ setDraftBeforeHistory(null); }, [promptHistoryKey]); + // Auto-submit initial prompt provided via -p/--prompt CLI flag + useEffect(() => { + if (!initialPrompt || !initialPrompt.trim()) return; + + onSubmit({ + text: initialPrompt, + imageUrls: [], + selectedSkills: undefined, + }); + setBuffer(EMPTY_BUFFER); + clearPromptUndoRedoState(undoRedoRef.current); + }, []); // Only on mount + useTerminalInput( (input, key) => { if (key.focusIn) { From e87f022dd42ab08f5929162a2bd742495e2e01a6 Mon Sep 17 00:00:00 2001 From: dengm Date: Sat, 16 May 2026 18:22:28 +0800 Subject: [PATCH 3/7] feat: add manual MCP server reconnect with secondary menu Replace automatic retry with user-initiated reconnect: - Failed servers show error details and a [Reconnect] option - Reconnect reads latest config from disk (no restart needed) - Single attempt per reconnect, no backoff/retry --- src/mcp/mcp-client.ts | 34 ++++- src/mcp/mcp-manager.ts | 266 +++++++++++++++++++++++--------------- src/session.ts | 4 + src/tests/session.test.ts | 55 ++++++++ src/ui/App.tsx | 9 +- src/ui/McpStatusList.tsx | 129 ++++++++++++------ 6 files changed, 346 insertions(+), 151 deletions(-) diff --git a/src/mcp/mcp-client.ts b/src/mcp/mcp-client.ts index 9636732..3651c88 100644 --- a/src/mcp/mcp-client.ts +++ b/src/mcp/mcp-client.ts @@ -106,19 +106,24 @@ export class McpClient { >(); private stderrBuffer = ""; private notificationHandler: McpNotificationHandler | null = null; + private disconnectHandler: ((reason: string) => void) | null = null; + private intentionallyDisconnected = false; constructor( private readonly serverName: string, private readonly command: string, private readonly args: string[] = [], private readonly env?: Record, - onNotification?: McpNotificationHandler + onNotification?: McpNotificationHandler, + onDisconnect?: (reason: string) => void ) { this.notificationHandler = onNotification ?? null; + this.disconnectHandler = onDisconnect ?? null; } async connect(timeoutMs: number): Promise { return new Promise((resolve, reject) => { + this.intentionallyDisconnected = false; const childEnv = { ...process.env, ...this.env, @@ -144,17 +149,35 @@ export class McpClient { }); } + let resolved = false; + const safeReject = (err: Error) => { + if (!resolved) { + resolved = true; + reject(err); + } + }; + this.process.on("error", (err) => { - reject(this.withStderr(`Failed to start MCP server "${this.serverName}" (${this.command}): ${err.message}`)); + safeReject( + this.withStderr(`Failed to start MCP server "${this.serverName}" (${this.command}): ${err.message}`) + ); }); this.process.on("close", (code) => { - const error = this.withStderr(`MCP server "${this.serverName}" exited with code ${code}`); + const reason = `MCP server "${this.serverName}" exited with code ${code}`; + const error = this.withStderr(reason); for (const [, pending] of this.pendingRequests) { clearTimeout(pending.timer); pending.reject(error); } this.pendingRequests.clear(); + this.reader?.close(); + this.reader = null; + this.process = null; + if (!this.intentionallyDisconnected && this.disconnectHandler) { + this.disconnectHandler(reason); + } + safeReject(error); }); if (this.process.stderr) { @@ -263,6 +286,7 @@ export class McpClient { } disconnect(): void { + this.intentionallyDisconnected = true; if (this.reader) { this.reader.close(); this.reader = null; @@ -273,6 +297,10 @@ export class McpClient { } } + isConnected(): boolean { + return this.process !== null && this.process.exitCode === null; + } + private sendRequest(method: string, params: Record, timeoutMs = 30_000): Promise { return new Promise((resolve, reject) => { const id = this.nextId++; diff --git a/src/mcp/mcp-manager.ts b/src/mcp/mcp-manager.ts index 5a9f553..217e3fc 100644 --- a/src/mcp/mcp-manager.ts +++ b/src/mcp/mcp-manager.ts @@ -1,7 +1,9 @@ import { McpClient, type McpToolDefinition, type McpPromptDefinition, type McpResourceDefinition } from "./mcp-client"; import type { McpServerConfig } from "../settings"; -const MCP_STARTUP_TIMEOUT_MS = 30_000; +const MCP_STARTUP_TIMEOUT_MS = process.env.DEEPCODE_MCP_TIMEOUT + ? parseInt(process.env.DEEPCODE_MCP_TIMEOUT, 10) + : 30_000; const MCP_CALL_TOOL_TIMEOUT_MS = 60_000; type McpToolEntry = { @@ -14,7 +16,7 @@ type McpToolEntry = { export type McpServerStatus = { name: string; - status: "starting" | "ready" | "failed"; + status: "starting" | "ready" | "failed" | "reconnecting"; connected: boolean; error?: string; toolCount: number; @@ -46,12 +48,10 @@ export class McpManager { private serverStatuses: McpServerStatus[] = []; private onToolsListChanged: (() => void) | null = null; private onStatusChanged: (() => void) | null = null; + private serverConfigs: Record = {}; prepare(servers?: Record): void { if (!servers || Object.keys(servers).length === 0) return; - // Clear the disposed flag — a re-prepare means we are live again. - // (disconnect() sets disposed=true to stop a stale initialize() loop, - // but prepare+initialize must be able to start a new one.) this.disposed = false; for (const name of Object.keys(servers)) { @@ -81,116 +81,175 @@ export class McpManager { if (!servers || Object.keys(servers).length === 0) return; - const entries = Object.entries(servers); + this.serverConfigs = servers; this.prepare(servers); - for (const [name, config] of entries) { + for (const [name, config] of Object.entries(servers)) { if (this.disposed) break; - let client: McpClient | null = null; - try { - client = new McpClient(name, config.command, config.args ?? [], config.env, (method) => { + await this.connectServer(name, config); + } + } + + async reconnect(name: string, config?: McpServerConfig): Promise { + if (this.disposed) return; + const effectiveConfig = config ?? this.serverConfigs[name]; + if (!effectiveConfig) return; + if (config) { + this.serverConfigs[name] = config; + } + + this.setStatus({ + name, + status: "reconnecting", + connected: false, + error: "Reconnecting...", + toolCount: 0, + tools: [], + promptCount: 0, + prompts: [], + resourceCount: 0, + resources: [], + }); + + await this.connectServer(name, effectiveConfig); + } + + private async connectServer(name: string, config: McpServerConfig): Promise { + if (this.disposed) return; + + // Clean up stale entries from previous connection attempts + this.clients = this.clients.filter((c) => c.isConnected()); + this.tools = this.tools.filter((t) => t.serverName !== name); + this.prompts = this.prompts.filter((p) => p.serverName !== name); + this.resources = this.resources.filter((r) => r.serverName !== name); + + let client: McpClient | null = null; + try { + client = new McpClient( + name, + config.command, + config.args ?? [], + config.env, + (method) => { if (method === "notifications/tools/list_changed") { - this.refreshServerTools(name, client!).catch(() => { - // swallow refresh errors - }); + this.refreshServerTools(name, client!).catch(() => {}); + } + }, + (reason) => { + if (!this.disposed && this.serverConfigs[name]) { + this.onServerCrash(name, reason); } - }); - await client.connect(MCP_STARTUP_TIMEOUT_MS); - if (this.disposed) { - client.disconnect(); - break; - } - this.clients.push(client); - - // Discover tools - const serverTools = await client.listTools(MCP_STARTUP_TIMEOUT_MS); - if (this.disposed) break; - const toolNamespacedNames: string[] = []; - for (const tool of serverTools) { - const namespacedName = `mcp__${name}__${tool.name}`; - this.tools.push({ - serverName: name, - originalName: tool.name, - namespacedName, - definition: tool, - client, - }); - toolNamespacedNames.push(namespacedName); - } - - // Discover prompts - let serverPrompts: McpPromptDefinition[] = []; - try { - serverPrompts = await client.listPrompts(MCP_STARTUP_TIMEOUT_MS); - } catch { - // Server may not support prompts — safe to ignore - } - if (this.disposed) break; - const promptNamespacedNames: string[] = []; - for (const prompt of serverPrompts) { - const namespacedName = `mcp__${name}__${prompt.name}`; - this.prompts.push({ - serverName: name, - namespacedName, - definition: prompt, - client, - }); - promptNamespacedNames.push(namespacedName); } + ); + await client.connect(MCP_STARTUP_TIMEOUT_MS); + if (this.disposed) { + client.disconnect(); + return; + } + this.clients.push(client); - // Discover resources - let serverResources: McpResourceDefinition[] = []; - try { - serverResources = await client.listResources(MCP_STARTUP_TIMEOUT_MS); - } catch { - // Server may not support resources — safe to ignore - } - if (this.disposed) break; - const resourceNamespacedNames: string[] = []; - for (const resource of serverResources) { - const namespacedName = `mcp__${name}__${resource.name}`; - this.resources.push({ - serverName: name, - namespacedName, - definition: resource, - client, - }); - resourceNamespacedNames.push(namespacedName); - } + const serverTools = await client.listTools(MCP_STARTUP_TIMEOUT_MS); + if (this.disposed) return; + const toolNamespacedNames: string[] = []; + for (const tool of serverTools) { + const namespacedName = `mcp__${name}__${tool.name}`; + this.tools.push({ + serverName: name, + originalName: tool.name, + namespacedName, + definition: tool, + client, + }); + toolNamespacedNames.push(namespacedName); + } - this.setStatus({ - name, - status: "ready", - connected: true, - toolCount: serverTools.length, - tools: toolNamespacedNames, - promptCount: serverPrompts.length, - prompts: promptNamespacedNames, - resourceCount: serverResources.length, - resources: resourceNamespacedNames, + let serverPrompts: McpPromptDefinition[] = []; + try { + serverPrompts = await client.listPrompts(MCP_STARTUP_TIMEOUT_MS); + } catch { + // server may not support prompts + } + if (this.disposed) return; + const promptNamespacedNames: string[] = []; + for (const prompt of serverPrompts) { + const namespacedName = `mcp__${name}__${prompt.name}`; + this.prompts.push({ + serverName: name, + namespacedName, + definition: prompt, + client, }); - } catch (err) { - if (this.disposed) break; - client?.disconnect(); - const message = err instanceof Error ? err.message : String(err); - // 不在控制台输出错误日志,避免暴露敏感信息 - // process.stderr.write(`[deepcode] MCP server "${name}" failed to initialize: ${message}\n`); - this.setStatus({ - name, - status: "failed", - connected: false, - error: message, - toolCount: 0, - tools: [], - promptCount: 0, - prompts: [], - resourceCount: 0, - resources: [], + promptNamespacedNames.push(namespacedName); + } + + let serverResources: McpResourceDefinition[] = []; + try { + serverResources = await client.listResources(MCP_STARTUP_TIMEOUT_MS); + } catch { + // server may not support resources + } + if (this.disposed) return; + const resourceNamespacedNames: string[] = []; + for (const resource of serverResources) { + const namespacedName = `mcp__${name}__${resource.name}`; + this.resources.push({ + serverName: name, + namespacedName, + definition: resource, + client, }); + resourceNamespacedNames.push(namespacedName); } + + this.setStatus({ + name, + status: "ready", + connected: true, + toolCount: serverTools.length, + tools: toolNamespacedNames, + promptCount: serverPrompts.length, + prompts: promptNamespacedNames, + resourceCount: serverResources.length, + resources: resourceNamespacedNames, + }); + } catch (err) { + client?.disconnect(); + const message = err instanceof Error ? err.message : String(err); + this.setStatus({ + name, + status: "failed", + connected: false, + error: message, + toolCount: 0, + tools: [], + promptCount: 0, + prompts: [], + resourceCount: 0, + resources: [], + }); } } + private onServerCrash(name: string, reason: string): void { + if (this.disposed) return; + this.clients = this.clients.filter((c) => c.isConnected()); + this.tools = this.tools.filter((t) => t.serverName !== name); + this.prompts = this.prompts.filter((p) => p.serverName !== name); + this.resources = this.resources.filter((r) => r.serverName !== name); + this.setStatus({ + name, + status: "failed", + connected: false, + error: reason, + toolCount: 0, + tools: [], + promptCount: 0, + prompts: [], + resourceCount: 0, + resources: [], + }); + } + getStatus(): McpServerStatus[] { const result = [...this.serverStatuses]; const knownNames = new Set(result.map((s) => s.name)); @@ -345,12 +404,12 @@ export class McpManager { this.resources = []; this.serverStatuses = []; this.configuredServerNames = []; + this.serverConfigs = {}; this.initialized = false; } private async refreshServerTools(serverName: string, client: McpClient): Promise { const serverTools = await client.listTools(MCP_STARTUP_TIMEOUT_MS); - // Remove old tool entries for this server this.tools = this.tools.filter((t) => t.serverName !== serverName); const toolNamespacedNames: string[] = []; for (const tool of serverTools) { @@ -364,13 +423,11 @@ export class McpManager { }); toolNamespacedNames.push(namespacedName); } - // Update status const existing = this.serverStatuses.find((s) => s.name === serverName); if (existing) { existing.toolCount = serverTools.length; existing.tools = toolNamespacedNames; } - // Notify listener this.onToolsListChanged?.(); } @@ -390,7 +447,6 @@ export class McpManager { } else { this.serverStatuses[index] = status; } - // 触发状态变更回调 this.onStatusChanged?.(); } } diff --git a/src/session.ts b/src/session.ts index 095cd3a..9e97f86 100644 --- a/src/session.ts +++ b/src/session.ts @@ -255,6 +255,10 @@ export class SessionManager { return this.mcpManager.getStatus(); } + async reconnectMcpServer(name: string, config?: McpServerConfig): Promise { + await this.mcpManager.reconnect(name, config); + } + dispose(): void { this.mcpManager.disconnect(); } diff --git a/src/tests/session.test.ts b/src/tests/session.test.ts index 50d016c..8ecb85e 100644 --- a/src/tests/session.test.ts +++ b/src/tests/session.test.ts @@ -1540,6 +1540,61 @@ test("SessionManager treats OpenAI APIUserAbortError as interrupted", async () = assert.equal(session?.failReason, "interrupted"); }); +test("SessionManager marks MCP server as failed on single failed attempt (no auto-retry)", async () => { + const workspace = createTempDir("deepcode-mcp-fail-noworkspace-"); + const serverPath = path.join(workspace, "mcp-server-fail.cjs"); + fs.writeFileSync(serverPath, "process.exit(7);", "utf8"); + + const manager = createSessionManager(workspace, "machine-id-mcp-fail-no"); + await manager.initMcpServers({ broken: { command: process.execPath, args: [serverPath] } }); + + const status = manager.getMcpStatus(); + assert.equal(status.length, 1); + assert.equal(status[0]?.status, "failed"); + assert.match(status[0]?.error ?? "", /exited with code 7/); + + manager.dispose(); +}); + +test("SessionManager reconnect succeeds on previously failed server", async () => { + const workspace = createTempDir("deepcode-mcp-reconn-ok-workspace-"); + const serverPath = path.join(workspace, "mcp-server-ok.cjs"); + fs.writeFileSync( + serverPath, + ` +const readline = require("readline"); +const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity }); +function send(message) { + process.stdout.write(JSON.stringify(message) + "\\n"); +} +rl.on("line", (line) => { + const request = JSON.parse(line); + if (!("id" in request)) return; + if (request.method === "initialize") { + send({ jsonrpc: "2.0", id: request.id, result: { protocolVersion: "2024-11-05", capabilities: {} } }); + return; + } + if (request.method === "tools/list") { + send({ jsonrpc: "2.0", id: request.id, result: { tools: [{ name: "ping", inputSchema: { type: "object", properties: {} } }] } }); + return; + } + send({ jsonrpc: "2.0", id: request.id, result: { content: [] } }); +}); +`, + "utf8" + ); + + const manager = createSessionManager(workspace, "machine-id-mcp-reconn-ok"); + await manager.initMcpServers({ fixable: { command: process.execPath, args: [serverPath] } }); + + const status = manager.getMcpStatus(); + assert.equal(status.length, 1); + assert.equal(status[0]?.status, "ready"); + assert.equal(status[0]?.toolCount, 1); + + manager.dispose(); +}); + function createSessionManager(projectRoot: string, machineId: string): SessionManager { return new SessionManager({ projectRoot, diff --git a/src/ui/App.tsx b/src/ui/App.tsx index bafb412..1f12198 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -455,7 +455,14 @@ export function App({ projectRoot, version = "", onRestart }: AppProps): React.R onCancel={() => setView("chat")} /> ) : view === "mcp-status" ? ( - setView("chat")} /> + setView("chat")} + onReconnect={(name) => { + const latest = resolveCurrentSettings(projectRoot); + void sessionManager.reconnectMcpServer(name, latest.mcpServers?.[name]); + }} + /> ) : shouldShowQuestionPrompt && pendingQuestion && !busy ? ( void; + onReconnect: (name: string) => void; }; -export function McpStatusList({ statuses, onCancel }: Props): React.ReactElement { +export function McpStatusList({ statuses, onCancel, onReconnect }: Props): React.ReactElement { const { columns, rows } = useWindowSize(); // 视图模式:server-list(服务器列表) 或 server-detail(服务器详情) @@ -20,10 +21,10 @@ export function McpStatusList({ statuses, onCancel }: Props): React.ReactElement setViewMode("server-list"); }, []); - // 进入服务器详情 + // 进入服务器详情(允许 ready、failed、reconnecting 状态) const enterDetail = useCallback(() => { const server = statuses[selectedServerIndex]; - if (server && server.status === "ready") { + if (server && (server.status === "ready" || server.status === "failed" || server.status === "reconnecting")) { setViewMode("server-detail"); } }, [statuses, selectedServerIndex]); @@ -59,6 +60,7 @@ export function McpStatusList({ statuses, onCancel }: Props): React.ReactElement server={statuses[selectedServerIndex]} onBack={goBack} onCancel={onCancel} + onReconnect={onReconnect} rows={rows} columns={columns} /> @@ -173,6 +175,7 @@ function ServerListView({ const readyCount = statuses.filter((s) => s.status === "ready").length; const startingCount = statuses.filter((s) => s.status === "starting").length; + const reconnectingCount = statuses.filter((s) => s.status === "reconnecting").length; const failedCount = statuses.filter((s) => s.status === "failed").length; return ( @@ -198,6 +201,11 @@ function ServerListView({ {startingCount} starting, + {reconnectingCount > 0 && ( + + {reconnectingCount} reconnecting, + + )} {failedCount} failed @@ -257,15 +265,23 @@ function ServerRow({ selected: boolean; labelColumnWidth: number; }): React.ReactElement { - const icon = status.status === "ready" ? "✓" : status.status === "failed" ? "✗" : "●"; - const color = status.status === "ready" ? "green" : status.status === "failed" ? "red" : "yellow"; + const icon = + status.status === "ready" ? "✓" : status.status === "failed" ? "✗" : status.status === "reconnecting" ? "↻" : "●"; + const color = + status.status === "ready" + ? "green" + : status.status === "failed" + ? "red" + : status.status === "reconnecting" + ? "#ff9900" + : "yellow"; // 加载动画:循环显示 (空) → . → .. → ... → (空) → ... const [dots, setDots] = React.useState(0); React.useEffect(() => { - if (status.status !== "starting") return; + if (status.status !== "starting" && status.status !== "reconnecting") return; const interval = setInterval(() => { - setDots((d) => (d + 1) % 4); // 0 → 1 → 2 → 3 → 0 ... + setDots((d) => (d + 1) % 4); }, 500); return () => clearInterval(interval); }, [status.status]); @@ -275,7 +291,9 @@ function ServerRow({ ? `Ready (${status.toolCount} tools, ${status.promptCount} prompts, ${status.resourceCount} resources)` : status.status === "failed" ? `Failed` - : "Starting" + (dots > 0 ? ".".repeat(dots) : " "); // 动态显示 (空) / . / .. / ... + : status.status === "reconnecting" + ? `Reconnecting${dots > 0 ? ".".repeat(dots) : " "}` + : "Starting" + (dots > 0 ? ".".repeat(dots) : " "); return ( @@ -293,8 +311,10 @@ function ServerRow({ - {/* Error message for failed servers */} - {status.status === "failed" && status.error ? : null} + {/* Error message for failed or reconnecting servers */} + {(status.status === "failed" || status.status === "reconnecting") && status.error ? ( + + ) : null} ); } @@ -304,59 +324,54 @@ function ServerDetailView({ server, onBack, onCancel, + onReconnect, rows, columns, }: { server: McpServerStatus; onBack: () => void; onCancel: () => void; + onReconnect: (name: string) => void; rows: number; columns: number; }): React.ReactElement { - const [activeIndex, setActiveIndex] = useState(0); + const [activeIndex, setActiveIndex] = React.useState(0); + const hasReconnect = server.status === "failed"; + const canScroll = server.status === "ready"; - // 合并所有 items(tools, prompts, resources) + // 合并所有 items(tools, prompts, resources)+ Reconnect 选项 const allItems = useMemo(() => { const items: { type: string; name: string }[] = []; + if (hasReconnect) { + items.push({ type: "action", name: "Reconnect" }); + } server.tools.forEach((tool) => items.push({ type: "tool", name: tool })); server.prompts.forEach((prompt) => items.push({ type: "prompt", name: prompt })); server.resources.forEach((resource) => items.push({ type: "resource", name: resource })); return items; - }, [server]); + }, [server, hasReconnect]); const totalItems = allItems.length; const maxVisible = useMemo(() => { - const reservedLines = 10; // header + title + stats + footer + borders + const reservedLines = 12; // header + title + stats + error + footer + borders const availableLines = Math.max(0, Math.min(rows, 30) - reservedLines); return Math.max(1, availableLines); }, [rows]); - // 使用 ref 跟踪 visibleStart,避免循环依赖 const visibleStartRef = React.useRef(0); - // 计算可见窗口起始位置:当 activeIndex 超出可见区域时才滚动(类似终端光标行为) const visibleStart = useMemo(() => { if (totalItems === 0) return 0; - const currentStart = visibleStartRef.current; let newStart = currentStart; - - // 如果 activeIndex 在当前可见窗口之前,滚动到 activeIndex if (activeIndex < currentStart) { newStart = activeIndex; - } - // 如果 activeIndex 在当前可见窗口之后,滚动到 activeIndex - else if (activeIndex >= currentStart + maxVisible) { + } else if (activeIndex >= currentStart + maxVisible) { newStart = activeIndex - maxVisible + 1; } - - // 限制在合法范围内 newStart = Math.max(0, Math.min(newStart, Math.max(0, totalItems - maxVisible))); - - // 更新 ref visibleStartRef.current = newStart; - return newStart; }, [activeIndex, maxVisible, totalItems]); @@ -371,11 +386,16 @@ function ServerDetailView({ onBack(); return; } - // Space 或 Enter 键返回一级菜单 - if (input === " " || key.return) { + if (key.return || input === " ") { + if (activeIndex === 0 && hasReconnect) { + onReconnect(server.name); + onBack(); + return; + } onBack(); return; } + if (!canScroll && !hasReconnect) return; if (key.upArrow) { setActiveIndex((prev) => Math.max(0, prev - 1)); return; @@ -384,25 +404,33 @@ function ServerDetailView({ setActiveIndex((prev) => Math.min(totalItems - 1, prev + 1)); return; } - if (key.pageUp) { + if (key.pageUp && canScroll) { setActiveIndex((prev) => Math.max(0, prev - maxVisible)); return; } - if (key.pageDown) { + if (key.pageDown && canScroll) { setActiveIndex((prev) => Math.min(totalItems - 1, prev + maxVisible)); return; } - if (key.home) { + if (key.home && canScroll) { setActiveIndex(0); return; } - if (key.end) { + if (key.end && canScroll) { setActiveIndex(totalItems - 1); } }); - const icon = "✓"; - const color = "green"; + const statusIcon = + server.status === "ready" ? "✓" : server.status === "failed" ? "✗" : server.status === "reconnecting" ? "↻" : "●"; + const statusColor = + server.status === "ready" + ? "green" + : server.status === "failed" + ? "red" + : server.status === "reconnecting" + ? "#ff9900" + : "yellow"; return ( {/* Header row */} - {icon} + {statusIcon} {server.name} - — Details + — {server.status === "ready" ? "Details" : "Status"} {/* Server info */} - {server.toolCount} tools, {server.promptCount} prompts, {server.resourceCount} resources + {server.status === "ready" + ? `${server.toolCount} tools, ${server.promptCount} prompts, ${server.resourceCount} resources` + : `Status: ${server.status}`} + {/* Error for failed/reconnecting */} + {server.error && (server.status === "failed" || server.status === "reconnecting") ? ( + + + + ) : null} {/* Items list */} {/* Footer */} - ↑/↓ scroll · Space/Enter back · Esc back · Ctrl+C close + + {hasReconnect + ? "Enter to reconnect · Esc back · Ctrl+C close" + : canScroll + ? "↑/↓ scroll · Space/Enter back · Esc back · Ctrl+C close" + : "Space/Enter back · Esc back · Ctrl+C close"} + @@ -481,13 +523,16 @@ function ServerDetailView({ } function ItemRow({ item, selected }: { item: { type: string; name: string }; selected: boolean }): React.ReactElement { - const icon = item.type === "tool" ? "🔧" : item.type === "prompt" ? "📝" : "📦"; + const isAction = item.type === "action"; + const icon = isAction ? "↻" : item.type === "tool" ? "🔧" : item.type === "prompt" ? "📝" : "📦"; + const color = isAction && selected ? "#ff9900" : selected ? "#229ac3" : undefined; return ( + {selected ? "> " : " "} {icon} - - {item.name} + + {isAction ? `[${item.name}]` : item.name} ); From 2b509715fec54287cbc872c078e09ebb8985c2ad Mon Sep 17 00:00:00 2001 From: Ji Zhang Date: Sun, 17 May 2026 10:31:18 +0800 Subject: [PATCH 4/7] fix: the -p/--prompt option causes duplicated LLM calls when using /new. --- src/cli.tsx | 6 ++++-- src/session.ts | 19 +++++++------------ src/tests/session.test.ts | 26 ++++++++++++++++++++++++++ src/ui/App.tsx | 15 ++++++++++++++- src/ui/PromptInput.tsx | 19 +------------------ 5 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/cli.tsx b/src/cli.tsx index 5f5ccb2..435499a 100644 --- a/src/cli.tsx +++ b/src/cli.tsx @@ -60,7 +60,7 @@ function extractInitialPrompt(args: string[]): string | undefined { return undefined; } -const initialPrompt = extractInitialPrompt(args); +let initialPrompt = extractInitialPrompt(args); const projectRoot = process.cwd(); configureWindowsShell(); @@ -78,11 +78,13 @@ async function main(): Promise { function startApp(): void { let restarting = false; + const appInitialPrompt = initialPrompt; + initialPrompt = undefined; const inkInstance = render( restartRef.current?.()} />, { exitOnCtrlC: false } diff --git a/src/session.ts b/src/session.ts index 095cd3a..5d6ee26 100644 --- a/src/session.ts +++ b/src/session.ts @@ -18,6 +18,7 @@ import { logOpenAIChatCompletionDebug, normalizeDebugError } from "./common/debu const MAX_SESSION_ENTRIES = 50; const DEFAULT_NEW_PROMPT_API_URL = "https://deepcode.vegamo.cn/api/plugin/new"; +const NEW_PROMPT_REPORT_TIMEOUT_MS = 3000; const DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD = 128 * 1024; const DEEPSEEK_V4_COMPACT_PROMPT_TOKEN_THRESHOLD = 512 * 1024; @@ -1305,6 +1306,9 @@ ${skillMd} return; } + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), NEW_PROMPT_REPORT_TIMEOUT_MS); + void fetch(DEFAULT_NEW_PROMPT_API_URL, { method: "POST", headers: { @@ -1312,19 +1316,10 @@ ${skillMd} Token: machineId, }, body: JSON.stringify({}), + signal: controller.signal, }) - .then(async (response) => { - if (response.ok) { - return; - } - - const body = await response.text().catch(() => ""); - throw new Error(`New prompt API request failed with status ${response.status}${body ? `: ${body}` : ""}`); - }) - .catch((error) => { - const message = error instanceof Error ? error.message : String(error); - console.warn(`Failed to report new prompt: ${message}`); - }); + .catch(() => {}) + .finally(() => clearTimeout(timeout)); } interruptActiveSession(): void { diff --git a/src/tests/session.test.ts b/src/tests/session.test.ts index 50d016c..e5ab740 100644 --- a/src/tests/session.test.ts +++ b/src/tests/session.test.ts @@ -6,6 +6,7 @@ import * as path from "path"; import { SessionManager, type SessionMessage } from "../session"; const originalFetch = globalThis.fetch; +const originalConsoleWarn = console.warn; const originalHome = process.env.HOME; const originalUserProfile = process.env.USERPROFILE; const tempDirs: string[] = []; @@ -20,6 +21,7 @@ function setHomeDir(dir: string): void { afterEach(() => { globalThis.fetch = originalFetch; + console.warn = originalConsoleWarn; if (originalHome === undefined) { delete process.env.HOME; } else { @@ -688,6 +690,7 @@ test("createSession reports a new prompt with the machineId token", async () => assert.equal(fetchCalls.length, 1); assert.equal(String(fetchCalls[0].input), "https://deepcode.vegamo.cn/api/plugin/new"); assert.equal(fetchCalls[0].init?.method, "POST"); + assert.ok(fetchCalls[0].init?.signal instanceof AbortSignal); assert.deepEqual(JSON.parse(String(fetchCalls[0].init?.body)), {}); assert.equal((fetchCalls[0].init?.headers as Record).Token, "machine-id-123"); }); @@ -719,10 +722,33 @@ test("replySession reports a new prompt with the machineId token", async () => { assert.equal(fetchCalls.length, 1); assert.equal(String(fetchCalls[0].input), "https://deepcode.vegamo.cn/api/plugin/new"); assert.equal(fetchCalls[0].init?.method, "POST"); + assert.ok(fetchCalls[0].init?.signal instanceof AbortSignal); assert.deepEqual(JSON.parse(String(fetchCalls[0].init?.body)), {}); assert.equal((fetchCalls[0].init?.headers as Record).Token, "machine-id-456"); }); +test("reporting a new prompt does not warn when the background request fails", async () => { + const workspace = createTempDir("deepcode-report-failure-workspace-"); + const home = createTempDir("deepcode-report-failure-home-"); + setHomeDir(home); + + const warnings: unknown[][] = []; + console.warn = (...args: unknown[]) => { + warnings.push(args); + }; + globalThis.fetch = (async () => { + throw new Error("fetch failed"); + }) as typeof fetch; + + const manager = createSessionManager(workspace, "machine-id-failure"); + (manager as any).activateSession = async () => {}; + + await manager.createSession({ text: "hello world" }); + await flushPromises(); + + assert.deepEqual(warnings, []); +}); + test("replySession continues without appending /continue as a user message", async () => { const workspace = createTempDir("deepcode-continue-workspace-"); const home = createTempDir("deepcode-continue-home-"); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 1b60618..b416647 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -53,6 +53,7 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App const { exit } = useApp(); const { stdout, write } = useStdout(); const { columns } = useWindowSize(); + const initialPromptSubmittedRef = useRef(false); const [view, setView] = useState("chat"); const [busy, setBusy] = useState(false); const [skills, setSkills] = useState([]); @@ -296,6 +297,19 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App [handlePrompt] ); + useEffect(() => { + if (initialPromptSubmittedRef.current || !initialPrompt || !initialPrompt.trim()) { + return; + } + + initialPromptSubmittedRef.current = true; + handleSubmit({ + text: initialPrompt, + imageUrls: [], + selectedSkills: undefined, + }); + }, [handleSubmit, initialPrompt]); + const handleSelectSession = useCallback( async (sessionId: string) => { const currentSessionId = sessionManager.getActiveSessionId(); @@ -471,7 +485,6 @@ export function App({ projectRoot, version = "", initialPrompt, onRestart }: App promptHistory={promptHistory} busy={busy} loadingText={loadingText} - initialPrompt={initialPrompt} onSubmit={handleSubmit} onModelConfigChange={handleModelConfigChange} onInterrupt={handleInterrupt} diff --git a/src/ui/PromptInput.tsx b/src/ui/PromptInput.tsx index 353a827..b32d926 100644 --- a/src/ui/PromptInput.tsx +++ b/src/ui/PromptInput.tsx @@ -60,7 +60,6 @@ type Props = { loadingText?: string | null; disabled?: boolean; placeholder?: string; - initialPrompt?: string; onSubmit: (submission: PromptSubmission) => void; onModelConfigChange: (selection: ModelConfigSelection) => string | Promise; onInterrupt: () => void; @@ -110,16 +109,13 @@ export const PromptInput = React.memo(function PromptInput({ loadingText, disabled, placeholder, - initialPrompt, onSubmit, onModelConfigChange, onInterrupt, }: Props): React.ReactElement { const { exit } = useApp(); const { stdout } = useStdout(); - const [buffer, setBuffer] = useState(() => - initialPrompt ? { text: initialPrompt, cursor: initialPrompt.length } : EMPTY_BUFFER - ); + const [buffer, setBuffer] = useState(EMPTY_BUFFER); const [imageUrls, setImageUrls] = useState([]); const [selectedSkills, setSelectedSkills] = useState([]); const [statusMessage, setStatusMessage] = useState(null); @@ -196,19 +192,6 @@ export const PromptInput = React.memo(function PromptInput({ setDraftBeforeHistory(null); }, [promptHistoryKey]); - // Auto-submit initial prompt provided via -p/--prompt CLI flag - useEffect(() => { - if (!initialPrompt || !initialPrompt.trim()) return; - - onSubmit({ - text: initialPrompt, - imageUrls: [], - selectedSkills: undefined, - }); - setBuffer(EMPTY_BUFFER); - clearPromptUndoRedoState(undoRedoRef.current); - }, []); // Only on mount - useTerminalInput( (input, key) => { if (key.focusIn) { From 65caf610b535b6a230ba9c58ac0ec5cc6b4ca7f0 Mon Sep 17 00:00:00 2001 From: Ji Zhang Date: Sun, 17 May 2026 12:57:24 +0800 Subject: [PATCH 5/7] feat: add test for Bash tool output streaming before command completion --- package-lock.json | 814 ++++++++++++++++++++++++++------ src/tests/tool-handlers.test.ts | 43 ++ 2 files changed, 704 insertions(+), 153 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9caecd..958eed7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -846,6 +846,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -856,6 +863,19 @@ "node": ">= 4" } }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@eslint/js": { "version": "9.39.4", "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.4.tgz", @@ -1040,13 +1060,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.8.0", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.8.0.tgz", - "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", + "version": "25.6.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~7.19.0" } }, "node_modules/@types/react": { @@ -1066,17 +1086,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", - "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/type-utils": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1089,22 +1109,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.3", + "@typescript-eslint/parser": "^8.59.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.59.3.tgz", - "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3" }, "engines": { @@ -1120,14 +1140,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", - "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.3", - "@typescript-eslint/types": "^8.59.3", + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", "debug": "^4.4.3" }, "engines": { @@ -1142,14 +1162,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", - "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3" + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1160,9 +1180,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", - "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", "dev": true, "license": "MIT", "engines": { @@ -1177,15 +1197,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", - "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1202,9 +1222,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.59.3.tgz", - "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", "dev": true, "license": "MIT", "engines": { @@ -1216,16 +1236,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", - "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.3", - "@typescript-eslint/tsconfig-utils": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1296,16 +1316,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.59.3.tgz", - "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3" + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1320,13 +1340,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", - "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1430,11 +1450,13 @@ } }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } }, "node_modules/auto-bind": { "version": "5.0.1", @@ -1703,7 +1725,7 @@ }, "node_modules/ejs": { "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/ejs/-/ejs-5.0.2.tgz", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.2.tgz", "integrity": "sha512-IpbUaI/CAW86l3f+T8zN0iggSc0LmMZLcIW5eRVStLVNCoTXkE0YlncbbH50fp8Cl6zHIky0sW2uUbhBqGw0Jw==", "license": "Apache-2.0", "bin": { @@ -1714,9 +1736,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.356", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz", - "integrity": "sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==", + "version": "1.5.353", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", "dev": true, "license": "ISC" }, @@ -1802,16 +1824,12 @@ } }, "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/eslint": { @@ -1973,6 +1991,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -2195,9 +2226,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -2206,9 +2237,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "resolved": "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", @@ -2239,7 +2283,7 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", @@ -2249,7 +2293,7 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", @@ -2262,7 +2306,7 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", @@ -2317,28 +2361,6 @@ "node": ">=6.0" } }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -2431,9 +2453,9 @@ } }, "node_modules/ink": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/ink/-/ink-7.0.3.tgz", - "integrity": "sha512-5kxHkIj9+RuqCU3zyvP4qvYWNOSHP2TW/SHayHGHOmk87KwfVcZwvJGemi9ch+ci2gXUqerK/Eh2DGEDt5q45g==", + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/ink/-/ink-7.0.1.tgz", + "integrity": "sha512-o6LAC268PLawlGVYrXTyaTfke4VtJftEheuwbgkQf7yvSXyWp1nRwBbAyKEkWXFZZsW/la5wrMuNbuBvZK2C1w==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", @@ -2576,13 +2598,13 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, + "version": "3.14.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -2949,7 +2971,7 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", @@ -2972,9 +2994,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.38", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, "license": "MIT" }, @@ -2994,9 +3016,9 @@ } }, "node_modules/openai": { - "version": "6.37.0", - "resolved": "https://registry.npmmirror.com/openai/-/openai-6.37.0.tgz", - "integrity": "sha512-0H5dEGFmmLv6KSd0W1w2nyL8WsLkX6yoLeQpU+dZAOuGcany5qkYQMmj35ZrKgb6yiyYqpUzFOpR8mZQkgqeEQ==", + "version": "6.35.0", + "resolved": "https://registry.npmmirror.com/openai/-/openai-6.35.0.tgz", + "integrity": "sha512-L/skwIGnt5xQZHb0UfTu9uAUKbis3ehKypOuJKi20QvG7UStV6C8IC3myGYHcdiF4kms/bAvOJ9UqqNWqi8x/Q==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -3108,7 +3130,7 @@ }, "node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", @@ -3125,7 +3147,7 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.3.6.tgz", "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", @@ -3190,9 +3212,9 @@ } }, "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmmirror.com/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "version": "19.2.5", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3223,6 +3245,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -3338,15 +3370,6 @@ "node": ">=10" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", @@ -3504,13 +3527,14 @@ } }, "node_modules/tsx": { - "version": "4.22.0", - "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.22.0.tgz", - "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==", + "version": "4.21.0", + "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.28.0" + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" @@ -3522,6 +3546,490 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -3565,16 +4073,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.3", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.59.3.tgz", - "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "version": "8.59.2", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.3", - "@typescript-eslint/parser": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3" + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3589,9 +4097,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "7.19.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, @@ -3695,9 +4203,9 @@ } }, "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -3723,9 +4231,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "version": "2.8.4", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", "dev": true, "license": "ISC", "optional": true, diff --git a/src/tests/tool-handlers.test.ts b/src/tests/tool-handlers.test.ts index 43af7ca..611d012 100644 --- a/src/tests/tool-handlers.test.ts +++ b/src/tests/tool-handlers.test.ts @@ -3,7 +3,9 @@ import assert from "node:assert/strict"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; +import { setTimeout as delay } from "node:timers/promises"; import type { ToolExecutionContext } from "../tools/executor"; +import { handleBashTool } from "../tools/bash-handler"; import { handleEditTool } from "../tools/edit-handler"; import { handleReadTool } from "../tools/read-handler"; import { handleWriteTool } from "../tools/write-handler"; @@ -19,6 +21,36 @@ afterEach(() => { } }); +test("Bash streams stdout and stderr before command completion", async () => { + const workspace = createTempWorkspace(); + const chunks: string[] = []; + let completed = false; + + const resultPromise = handleBashTool( + { + command: "printf 'first\\n'; sleep 1; printf 'second\\n'; printf 'err\\n' >&2", + }, + createContext("bash-live-output", workspace, { + onProcessStdout: (_pid, chunk) => { + chunks.push(chunk); + }, + }) + ).finally(() => { + completed = true; + }); + + await waitFor(() => chunks.join("").includes("first"), 1500); + + assert.equal(completed, false); + + const result = await resultPromise; + const streamedOutput = chunks.join(""); + assert.equal(result.ok, true); + assert.match(streamedOutput, /first/); + assert.match(streamedOutput, /second/); + assert.match(streamedOutput, /err/); +}); + test("Read returns snippet metadata and Edit can scope replacements by snippet_id", async () => { const workspace = createTempWorkspace(); const filePath = path.join(workspace, "sample.txt"); @@ -573,3 +605,14 @@ function createTempWorkspace(): string { tempDirs.push(dir); return dir; } + +async function waitFor(predicate: () => boolean, timeoutMs: number): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (predicate()) { + return; + } + await delay(25); + } + assert.equal(predicate(), true); +} From 52dafba25903dc70258d7e59dbe86e283a0f091f Mon Sep 17 00:00:00 2001 From: dengmik-commits <270912164+dengmik-commits@users.noreply.github.com> Date: Mon, 18 May 2026 09:50:38 +0800 Subject: [PATCH 6/7] fix: re-apply dynamic modifier parsing for Shift+Enter after upstream sync Upstream v0.1.21 reverted PR #70. Re-apply: - isShiftReturn() / isReturn() dynamic CSI modifier bit parsing - Kitty progressive enhancement (ESC[>1u) alongside xterm modifyOtherKeys - Clear input when key.return is true (safety net) --- src/tests/promptInputKeys.test.ts | 6 ++--- src/ui/prompt/cursor.ts | 4 +-- src/ui/prompt/useTerminalInput.ts | 43 ++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/tests/promptInputKeys.test.ts b/src/tests/promptInputKeys.test.ts index 69d2075..8952a3d 100644 --- a/src/tests/promptInputKeys.test.ts +++ b/src/tests/promptInputKeys.test.ts @@ -80,7 +80,7 @@ test("parseTerminalInput keeps BS payload for meta+backspace", () => { test("parseTerminalInput recognizes shifted return sequences", () => { const { input, key } = parseTerminalInput("\u001B\r"); - assert.equal(input, "\r"); + assert.equal(input, ""); assert.equal(key.return, true); assert.equal(key.shift, true); assert.equal(key.meta, false); @@ -108,8 +108,8 @@ test("parseTerminalInput recognizes alternate shifted return sequences", () => { }); test("terminal extended key helpers request and restore modifyOtherKeys mode", () => { - assert.equal(enableTerminalExtendedKeys(), "\u001B[>4;1m"); - assert.equal(disableTerminalExtendedKeys(), "\u001B[>4;0m"); + assert.equal(enableTerminalExtendedKeys(), "\u001B[>4;1m\u001B[>1u"); + assert.equal(disableTerminalExtendedKeys(), "\u001B[>4;0m\u001B[ { diff --git a/src/ui/prompt/cursor.ts b/src/ui/prompt/cursor.ts index 2668470..59b24f2 100644 --- a/src/ui/prompt/cursor.ts +++ b/src/ui/prompt/cursor.ts @@ -41,11 +41,11 @@ function disableTerminalFocusReporting(): string { } export function enableTerminalExtendedKeys(): string { - return "\u001B[>4;1m"; + return "\u001B[>4;1m\u001B[>1u"; } export function disableTerminalExtendedKeys(): string { - return "\u001B[>4;0m"; + return "\u001B[>4;0m\u001B[ Date: Mon, 18 May 2026 10:22:22 +0800 Subject: [PATCH 7/7] fix: refresh mcpToolDefinitions cache after MCP reconnect After reconnectMcpServer succeeds, SessionManager's cached mcpToolDefinitions was stale, causing "Unknown MCP tool" errors when the model tried to call reconnected tools. --- src/session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/session.ts b/src/session.ts index eddfe5c..0527ba8 100644 --- a/src/session.ts +++ b/src/session.ts @@ -261,6 +261,7 @@ export class SessionManager { async reconnectMcpServer(name: string, config?: McpServerConfig): Promise { await this.mcpManager.reconnect(name, config); + this.mcpToolDefinitions = this.mcpManager.getMcpToolDefinitions(); } dispose(): void {