feat: add CODETYPER ASCII art to exit summary
Add prominent CODETYPER logo using ASCII box-drawing characters at the top of the exit summary for enhanced branding. This provides a polished, professional appearance when exiting the CLI. Key improvements: - Add ASCII logo to session summary output - Simplify exit flow to use global message storage in terminal.ts - Remove duplicate exit message handling from ExitProvider - Fix signal handlers to prevent duplicate exit messages - Clean up debug logging from app.tsx - Ensure exit message persists on terminal after process exit The exit summary now displays comprehensive session statistics with: - CODETYPER ASCII logo - Total usage and Premium requests - API time and total session time - Code changes breakdown (+additions/-deletions) - Per-model token usage - Resume command with session ID Works correctly on all exit paths (normal exit, SIGINT, SIGTERM, errors).
This commit is contained in:
162
package-lock.json
generated
162
package-lock.json
generated
@@ -1180,13 +1180,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/checkbox": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.4.tgz",
|
||||
"integrity": "sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.6.tgz",
|
||||
"integrity": "sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^2.0.3",
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/figures": "^2.0.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
@@ -1203,12 +1203,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/confirm": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.4.tgz",
|
||||
"integrity": "sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA==",
|
||||
"version": "6.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.6.tgz",
|
||||
"integrity": "sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1224,18 +1224,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/core": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.1.tgz",
|
||||
"integrity": "sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA==",
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.3.tgz",
|
||||
"integrity": "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^2.0.3",
|
||||
"@inquirer/figures": "^2.0.3",
|
||||
"@inquirer/type": "^4.0.3",
|
||||
"cli-width": "^4.1.0",
|
||||
"fast-wrap-ansi": "^0.2.0",
|
||||
"mute-stream": "^3.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"wrap-ansi": "^9.0.2"
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||
@@ -1250,12 +1250,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/editor": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.4.tgz",
|
||||
"integrity": "sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.6.tgz",
|
||||
"integrity": "sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/external-editor": "^2.0.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
@@ -1272,12 +1272,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/expand": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.4.tgz",
|
||||
"integrity": "sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.6.tgz",
|
||||
"integrity": "sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1323,12 +1323,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/input": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.4.tgz",
|
||||
"integrity": "sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.6.tgz",
|
||||
"integrity": "sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1344,12 +1344,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/number": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.4.tgz",
|
||||
"integrity": "sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ==",
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.6.tgz",
|
||||
"integrity": "sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1365,13 +1365,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/password": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.4.tgz",
|
||||
"integrity": "sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.6.tgz",
|
||||
"integrity": "sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^2.0.3",
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1387,21 +1387,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/prompts": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.2.0.tgz",
|
||||
"integrity": "sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA==",
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.2.1.tgz",
|
||||
"integrity": "sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/checkbox": "^5.0.4",
|
||||
"@inquirer/confirm": "^6.0.4",
|
||||
"@inquirer/editor": "^5.0.4",
|
||||
"@inquirer/expand": "^5.0.4",
|
||||
"@inquirer/input": "^5.0.4",
|
||||
"@inquirer/number": "^4.0.4",
|
||||
"@inquirer/password": "^5.0.4",
|
||||
"@inquirer/rawlist": "^5.2.0",
|
||||
"@inquirer/search": "^4.1.0",
|
||||
"@inquirer/select": "^5.0.4"
|
||||
"@inquirer/checkbox": "^5.0.5",
|
||||
"@inquirer/confirm": "^6.0.5",
|
||||
"@inquirer/editor": "^5.0.5",
|
||||
"@inquirer/expand": "^5.0.5",
|
||||
"@inquirer/input": "^5.0.5",
|
||||
"@inquirer/number": "^4.0.5",
|
||||
"@inquirer/password": "^5.0.5",
|
||||
"@inquirer/rawlist": "^5.2.1",
|
||||
"@inquirer/search": "^4.1.1",
|
||||
"@inquirer/select": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||
@@ -1416,12 +1416,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/rawlist": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.0.tgz",
|
||||
"integrity": "sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.2.tgz",
|
||||
"integrity": "sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1437,12 +1437,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/search": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.0.tgz",
|
||||
"integrity": "sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.2.tgz",
|
||||
"integrity": "sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/figures": "^2.0.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
@@ -1459,13 +1459,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/select": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.4.tgz",
|
||||
"integrity": "sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.6.tgz",
|
||||
"integrity": "sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^2.0.3",
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/figures": "^2.0.3",
|
||||
"@inquirer/type": "^4.0.3"
|
||||
},
|
||||
@@ -3849,9 +3849,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001769",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
|
||||
"integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
|
||||
"version": "1.0.30001770",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
|
||||
"integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -4976,9 +4976,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n": {
|
||||
"version": "17.23.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.2.tgz",
|
||||
"integrity": "sha512-RhWBeb7YVPmNa2eggvJooiuehdL76/bbfj/OJewyoGT80qn5PXdz8zMOTO6YHOsI7byPt7+Ighh/i/4a5/v7hw==",
|
||||
"version": "17.24.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
|
||||
"integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5321,6 +5321,30 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-string-truncated-width": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz",
|
||||
"integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-string-width": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz",
|
||||
"integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-string-truncated-width": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-wrap-ansi": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz",
|
||||
"integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-string-width": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
@@ -6031,14 +6055,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer": {
|
||||
"version": "13.2.2",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.2.2.tgz",
|
||||
"integrity": "sha512-+hlN8I88JE9T3zjWHGnMhryniRDbSgFNJHJTyD2iKO5YNpMRyfghQ6wVoe+gV4ygMM4r4GzlsBxNa1g/UUZixA==",
|
||||
"version": "13.2.4",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.2.4.tgz",
|
||||
"integrity": "sha512-7JJ8+lGhGtJOeVnrH4IqP7mQgOqvHkKS6DNLTkMHEI3iHKzZUaidOivU9q8wrlSRvT0ISCBMweMK7TWYzr5BhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^2.0.3",
|
||||
"@inquirer/core": "^11.1.1",
|
||||
"@inquirer/prompts": "^8.2.0",
|
||||
"@inquirer/core": "^11.1.3",
|
||||
"@inquirer/prompts": "^8.2.1",
|
||||
"@inquirer/type": "^4.0.3",
|
||||
"mute-stream": "^3.0.0",
|
||||
"run-async": "^4.0.6",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Agent and Skill Templates
|
||||
*
|
||||
* Templates for creating new agents and skills in .codetyper/ directories.
|
||||
* Based on patterns from claude-code and opencode.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -127,7 +126,16 @@ export const DEFAULT_TOOLS_BY_TYPE = {
|
||||
plan: ["glob", "grep", "read"],
|
||||
security: ["glob", "grep", "read"],
|
||||
documentation: ["glob", "grep", "read", "write"],
|
||||
general: ["glob", "grep", "read", "write", "edit", "bash", "web_search", "web_fetch"],
|
||||
general: [
|
||||
"glob",
|
||||
"grep",
|
||||
"read",
|
||||
"write",
|
||||
"edit",
|
||||
"bash",
|
||||
"web_search",
|
||||
"web_fetch",
|
||||
],
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Keybind Configuration
|
||||
*
|
||||
* Defines all configurable keybindings with defaults.
|
||||
* Modeled after OpenCode's keybind system with leader key support,
|
||||
* comma-separated alternatives, and `<leader>` prefix expansion.
|
||||
*
|
||||
* Format: "mod+key" or "mod+key,mod+key" for alternatives
|
||||
@@ -62,9 +61,6 @@ export type KeybindAction =
|
||||
// Default Keybinds
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Default leader key prefix (similar to vim leader or OpenCode's ctrl+x).
|
||||
*/
|
||||
export const DEFAULT_LEADER = "ctrl+x";
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,6 @@ export const TOKEN_WARNING_THRESHOLD = 0.75; // 75% - yellow warning
|
||||
export const TOKEN_CRITICAL_THRESHOLD = 0.9; // 90% - red warning
|
||||
export const TOKEN_OVERFLOW_THRESHOLD = 0.95; // 95% - trigger compaction
|
||||
|
||||
// Pruning thresholds (following OpenCode pattern)
|
||||
export const PRUNE_MINIMUM_TOKENS = 20000; // Min tokens to actually prune
|
||||
export const PRUNE_PROTECT_TOKENS = 40000; // Threshold before marking for pruning
|
||||
export const PRUNE_RECENT_TURNS = 2; // Protect last N user turns
|
||||
|
||||
@@ -199,14 +199,7 @@ export const CLAUDE_CODE_AGENTS: UnifiedAgentDefinition[] = [
|
||||
source: "claude-code",
|
||||
tier: "balanced",
|
||||
mode: "subagent",
|
||||
tools: [
|
||||
"glob",
|
||||
"grep",
|
||||
"read",
|
||||
"web_fetch",
|
||||
"web_search",
|
||||
"todo_write",
|
||||
],
|
||||
tools: ["glob", "grep", "read", "web_fetch", "web_search", "todo_write"],
|
||||
deniedTools: ["edit", "write", "bash"],
|
||||
maxTurns: 15,
|
||||
color: "yellow",
|
||||
@@ -220,14 +213,7 @@ export const CLAUDE_CODE_AGENTS: UnifiedAgentDefinition[] = [
|
||||
source: "claude-code",
|
||||
tier: "balanced",
|
||||
mode: "subagent",
|
||||
tools: [
|
||||
"glob",
|
||||
"grep",
|
||||
"read",
|
||||
"web_fetch",
|
||||
"web_search",
|
||||
"todo_write",
|
||||
],
|
||||
tools: ["glob", "grep", "read", "web_fetch", "web_search", "todo_write"],
|
||||
deniedTools: ["edit", "write", "bash"],
|
||||
maxTurns: 15,
|
||||
color: "green",
|
||||
@@ -380,15 +366,7 @@ export const CURSOR_AGENTS: UnifiedAgentDefinition[] = [
|
||||
source: "cursor",
|
||||
tier: "balanced",
|
||||
mode: "primary",
|
||||
tools: [
|
||||
"glob",
|
||||
"grep",
|
||||
"read",
|
||||
"edit",
|
||||
"write",
|
||||
"bash",
|
||||
"web_search",
|
||||
],
|
||||
tools: ["glob", "grep", "read", "edit", "write", "bash", "web_search"],
|
||||
maxTurns: 50,
|
||||
tags: ["general-purpose", "pair-programming"],
|
||||
systemPrompt: `You are a pair programmer. Keep going until the query is completely resolved.
|
||||
@@ -404,15 +382,7 @@ Never output code unless requested - use edit tools instead.`,
|
||||
source: "cursor",
|
||||
tier: "balanced",
|
||||
mode: "primary",
|
||||
tools: [
|
||||
"glob",
|
||||
"grep",
|
||||
"read",
|
||||
"edit",
|
||||
"write",
|
||||
"bash",
|
||||
"web_search",
|
||||
],
|
||||
tools: ["glob", "grep", "read", "edit", "write", "bash", "web_search"],
|
||||
maxTurns: 50,
|
||||
tags: ["cli", "terminal", "interactive"],
|
||||
systemPrompt: `You are an interactive CLI tool for software engineering tasks.
|
||||
@@ -546,18 +516,14 @@ export const getAgentsBySource = (
|
||||
/**
|
||||
* Get agents by tier
|
||||
*/
|
||||
export const getAgentsByTier = (
|
||||
tier: AgentTier,
|
||||
): UnifiedAgentDefinition[] => {
|
||||
export const getAgentsByTier = (tier: AgentTier): UnifiedAgentDefinition[] => {
|
||||
return UNIFIED_AGENT_REGISTRY.filter((a) => a.tier === tier);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get agents by mode
|
||||
*/
|
||||
export const getAgentsByMode = (
|
||||
mode: AgentMode,
|
||||
): UnifiedAgentDefinition[] => {
|
||||
export const getAgentsByMode = (mode: AgentMode): UnifiedAgentDefinition[] => {
|
||||
return UNIFIED_AGENT_REGISTRY.filter(
|
||||
(a) => a.mode === mode || a.mode === "all",
|
||||
);
|
||||
@@ -604,7 +570,15 @@ const mapTier = (tier: AgentTier): "fast" | "balanced" | "thorough" => {
|
||||
*/
|
||||
const mapColor = (
|
||||
color?: string,
|
||||
): "red" | "green" | "blue" | "yellow" | "cyan" | "magenta" | "white" | "gray" => {
|
||||
):
|
||||
| "red"
|
||||
| "green"
|
||||
| "blue"
|
||||
| "yellow"
|
||||
| "cyan"
|
||||
| "magenta"
|
||||
| "white"
|
||||
| "gray" => {
|
||||
const validColors = new Set([
|
||||
"red",
|
||||
"green",
|
||||
@@ -616,7 +590,15 @@ const mapColor = (
|
||||
"gray",
|
||||
]);
|
||||
if (color && validColors.has(color)) {
|
||||
return color as "red" | "green" | "blue" | "yellow" | "cyan" | "magenta" | "white" | "gray";
|
||||
return color as
|
||||
| "red"
|
||||
| "green"
|
||||
| "blue"
|
||||
| "yellow"
|
||||
| "cyan"
|
||||
| "magenta"
|
||||
| "white"
|
||||
| "gray";
|
||||
}
|
||||
return "cyan";
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Plan Mode Service
|
||||
*
|
||||
* Manages the plan approval workflow for complex tasks.
|
||||
* Implements the pattern from claude-code and opencode where
|
||||
* complex operations require user approval before execution.
|
||||
*/
|
||||
|
||||
@@ -50,14 +49,7 @@ const COMPLEXITY_KEYWORDS = {
|
||||
"system",
|
||||
"integration",
|
||||
],
|
||||
moderate: [
|
||||
"feature",
|
||||
"component",
|
||||
"service",
|
||||
"api",
|
||||
"endpoint",
|
||||
"module",
|
||||
],
|
||||
moderate: ["feature", "component", "service", "api", "endpoint", "module"],
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,7 +63,7 @@ export const analyzeTask = (
|
||||
const reasons: string[] = [];
|
||||
|
||||
// Check for critical keywords
|
||||
const hasCriticalKeyword = COMPLEXITY_KEYWORDS.critical.some(k =>
|
||||
const hasCriticalKeyword = COMPLEXITY_KEYWORDS.critical.some((k) =>
|
||||
lowerTask.includes(k),
|
||||
);
|
||||
if (hasCriticalKeyword) {
|
||||
@@ -79,7 +71,7 @@ export const analyzeTask = (
|
||||
}
|
||||
|
||||
// Check for complex keywords
|
||||
const hasComplexKeyword = COMPLEXITY_KEYWORDS.complex.some(k =>
|
||||
const hasComplexKeyword = COMPLEXITY_KEYWORDS.complex.some((k) =>
|
||||
lowerTask.includes(k),
|
||||
);
|
||||
if (hasComplexKeyword) {
|
||||
@@ -87,7 +79,7 @@ export const analyzeTask = (
|
||||
}
|
||||
|
||||
// Check for always-require operations
|
||||
const matchesAlwaysRequire = criteria.alwaysRequireFor.some(op => {
|
||||
const matchesAlwaysRequire = criteria.alwaysRequireFor.some((op) => {
|
||||
const opKeywords: Record<string, string[]> = {
|
||||
delete: ["delete", "remove", "drop"],
|
||||
refactor: ["refactor", "restructure", "rewrite"],
|
||||
@@ -96,14 +88,14 @@ export const analyzeTask = (
|
||||
database: ["database", "migration", "schema"],
|
||||
config: ["config", "environment", "settings"],
|
||||
};
|
||||
return opKeywords[op]?.some(k => lowerTask.includes(k));
|
||||
return opKeywords[op]?.some((k) => lowerTask.includes(k));
|
||||
});
|
||||
if (matchesAlwaysRequire) {
|
||||
reasons.push("Task matches always-require-approval criteria");
|
||||
}
|
||||
|
||||
// Check for skip-approval patterns
|
||||
const matchesSkipApproval = criteria.skipApprovalFor.some(op => {
|
||||
const matchesSkipApproval = criteria.skipApprovalFor.some((op) => {
|
||||
const skipPatterns: Record<string, RegExp> = {
|
||||
single_file_edit: /^(fix|update|change|modify)\s+(the\s+)?(\w+\.\w+)$/i,
|
||||
add_comment: /^add\s+(a\s+)?comment/i,
|
||||
@@ -119,7 +111,7 @@ export const analyzeTask = (
|
||||
complexity = "critical";
|
||||
} else if (hasComplexKeyword || matchesAlwaysRequire) {
|
||||
complexity = "complex";
|
||||
} else if (COMPLEXITY_KEYWORDS.moderate.some(k => lowerTask.includes(k))) {
|
||||
} else if (COMPLEXITY_KEYWORDS.moderate.some((k) => lowerTask.includes(k))) {
|
||||
complexity = "moderate";
|
||||
} else {
|
||||
complexity = "simple";
|
||||
@@ -128,7 +120,9 @@ export const analyzeTask = (
|
||||
// Determine if plan approval is required
|
||||
const requiresPlanApproval =
|
||||
!matchesSkipApproval &&
|
||||
(complexity === "critical" || complexity === "complex" || reasons.length > 0);
|
||||
(complexity === "critical" ||
|
||||
complexity === "complex" ||
|
||||
reasons.length > 0);
|
||||
|
||||
// Suggest approach
|
||||
const suggestedApproach = requiresPlanApproval
|
||||
@@ -262,9 +256,15 @@ export const submitPlanForApproval = (
|
||||
|
||||
for (const step of plan.steps) {
|
||||
for (const file of step.filesAffected) {
|
||||
if (step.title.toLowerCase().includes("create") || step.title.toLowerCase().includes("add")) {
|
||||
if (
|
||||
step.title.toLowerCase().includes("create") ||
|
||||
step.title.toLowerCase().includes("add")
|
||||
) {
|
||||
filesCreated.add(file);
|
||||
} else if (step.title.toLowerCase().includes("delete") || step.title.toLowerCase().includes("remove")) {
|
||||
} else if (
|
||||
step.title.toLowerCase().includes("delete") ||
|
||||
step.title.toLowerCase().includes("remove")
|
||||
) {
|
||||
filesDeleted.add(file);
|
||||
} else {
|
||||
filesModified.add(file);
|
||||
@@ -286,10 +286,7 @@ export const submitPlanForApproval = (
|
||||
/**
|
||||
* Approve a plan
|
||||
*/
|
||||
export const approvePlan = (
|
||||
planId: string,
|
||||
message?: string,
|
||||
): boolean => {
|
||||
export const approvePlan = (planId: string, message?: string): boolean => {
|
||||
const plan = activePlans.get(planId);
|
||||
if (!plan || plan.status !== "pending") {
|
||||
return false;
|
||||
@@ -307,10 +304,7 @@ export const approvePlan = (
|
||||
/**
|
||||
* Reject a plan
|
||||
*/
|
||||
export const rejectPlan = (
|
||||
planId: string,
|
||||
reason: string,
|
||||
): boolean => {
|
||||
export const rejectPlan = (planId: string, reason: string): boolean => {
|
||||
const plan = activePlans.get(planId);
|
||||
if (!plan || plan.status !== "pending") {
|
||||
return false;
|
||||
@@ -354,7 +348,7 @@ export const updateStepStatus = (
|
||||
return false;
|
||||
}
|
||||
|
||||
const step = plan.steps.find(s => s.id === stepId);
|
||||
const step = plan.steps.find((s) => s.id === stepId);
|
||||
if (!step) {
|
||||
return false;
|
||||
}
|
||||
@@ -400,7 +394,10 @@ export const getPlan = (planId: string): ImplementationPlan | undefined => {
|
||||
*/
|
||||
export const getActivePlans = (): ImplementationPlan[] => {
|
||||
return Array.from(activePlans.values()).filter(
|
||||
p => p.status !== "completed" && p.status !== "failed" && p.status !== "rejected",
|
||||
(p) =>
|
||||
p.status !== "completed" &&
|
||||
p.status !== "failed" &&
|
||||
p.status !== "rejected",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -428,7 +425,7 @@ export const formatPlanForDisplay = (plan: ImplementationPlan): string => {
|
||||
|
||||
if (plan.context.filesAnalyzed.length > 0) {
|
||||
lines.push("Files Analyzed");
|
||||
plan.context.filesAnalyzed.forEach(f => lines.push(` ${f}`));
|
||||
plan.context.filesAnalyzed.forEach((f) => lines.push(` ${f}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
@@ -453,7 +450,7 @@ export const formatPlanForDisplay = (plan: ImplementationPlan): string => {
|
||||
|
||||
if (plan.risks.length > 0) {
|
||||
lines.push("Risks");
|
||||
plan.risks.forEach(risk => {
|
||||
plan.risks.forEach((risk) => {
|
||||
lines.push(` [${risk.impact.toUpperCase()}] ${risk.description}`);
|
||||
lines.push(` Mitigation: ${risk.mitigation}`);
|
||||
});
|
||||
@@ -491,7 +488,7 @@ export const isApprovalMessage = (message: string): boolean => {
|
||||
/go\s*ahead\s*(with|and)/i,
|
||||
];
|
||||
|
||||
return approvalPatterns.some(p => p.test(message.trim()));
|
||||
return approvalPatterns.some((p) => p.test(message.trim()));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -504,7 +501,7 @@ export const isRejectionMessage = (message: string): boolean => {
|
||||
/don't\s*(proceed|do|execute)/i,
|
||||
];
|
||||
|
||||
return rejectionPatterns.some(p => p.test(message.trim()));
|
||||
return rejectionPatterns.some((p) => p.test(message.trim()));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -513,7 +510,7 @@ export const isRejectionMessage = (message: string): boolean => {
|
||||
export const subscribeToPlan = (
|
||||
planId: string,
|
||||
callback: PlanEventCallback,
|
||||
): () => void => {
|
||||
): (() => void) => {
|
||||
if (!planListeners.has(planId)) {
|
||||
planListeners.set(planId, new Set());
|
||||
}
|
||||
@@ -531,7 +528,7 @@ export const subscribeToPlan = (
|
||||
const emitPlanEvent = (planId: string, plan: ImplementationPlan): void => {
|
||||
const listeners = planListeners.get(planId);
|
||||
if (listeners) {
|
||||
listeners.forEach(callback => callback(plan));
|
||||
listeners.forEach((callback) => callback(plan));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Session Compaction Service
|
||||
*
|
||||
* Integrates auto-compaction with the agent loop and hooks system.
|
||||
* Follows OpenCode's two-tier approach: pruning (remove old tool output)
|
||||
* and compaction (summarize for fresh context).
|
||||
*/
|
||||
|
||||
@@ -62,7 +61,6 @@ export const isContextOverflow = (
|
||||
/**
|
||||
* Prune old tool outputs from messages
|
||||
*
|
||||
* Strategy (following OpenCode):
|
||||
* 1. Walk backwards through messages
|
||||
* 2. Skip first N user turns (protect recent context)
|
||||
* 3. Mark tool outputs for pruning once we accumulate enough tokens
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Plan Approval Tool
|
||||
*
|
||||
* Allows agents to submit implementation plans for user approval.
|
||||
* This tool implements the approval gate pattern from claude-code and opencode.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
@@ -35,24 +34,15 @@ const planApprovalSchema = z.object({
|
||||
.describe("The action to perform"),
|
||||
|
||||
// For create action
|
||||
title: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Title of the implementation plan"),
|
||||
title: z.string().optional().describe("Title of the implementation plan"),
|
||||
summary: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Summary of what the plan will accomplish"),
|
||||
|
||||
// For add_step action
|
||||
plan_id: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("ID of the plan to modify"),
|
||||
step_title: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Title of the step"),
|
||||
plan_id: z.string().optional().describe("ID of the plan to modify"),
|
||||
step_title: z.string().optional().describe("Title of the step"),
|
||||
step_description: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -81,18 +71,12 @@ const planApprovalSchema = z.object({
|
||||
.describe("Dependencies identified"),
|
||||
|
||||
// For add_risk action
|
||||
risk_description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Description of the risk"),
|
||||
risk_description: z.string().optional().describe("Description of the risk"),
|
||||
risk_impact: z
|
||||
.enum(["low", "medium", "high"])
|
||||
.optional()
|
||||
.describe("Impact level of the risk"),
|
||||
risk_mitigation: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("How to mitigate this risk"),
|
||||
risk_mitigation: z.string().optional().describe("How to mitigate this risk"),
|
||||
|
||||
// For submit action
|
||||
testing_strategy: z
|
||||
@@ -252,12 +236,18 @@ const handleAddContext = (params: PlanApprovalParams): ToolResult => {
|
||||
* Handle add_risk action
|
||||
*/
|
||||
const handleAddRisk = (params: PlanApprovalParams): ToolResult => {
|
||||
if (!params.plan_id || !params.risk_description || !params.risk_impact || !params.risk_mitigation) {
|
||||
if (
|
||||
!params.plan_id ||
|
||||
!params.risk_description ||
|
||||
!params.risk_impact ||
|
||||
!params.risk_mitigation
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
title: "Missing parameters",
|
||||
output: "",
|
||||
error: "plan_id, risk_description, risk_impact, and risk_mitigation are required",
|
||||
error:
|
||||
"plan_id, risk_description, risk_impact, and risk_mitigation are required",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -359,8 +349,10 @@ const handleCheckStatus = (params: PlanApprovalParams): ToolResult => {
|
||||
}
|
||||
|
||||
const statusMessages: Record<string, string> = {
|
||||
drafting: "Plan is being drafted. Add steps, context, and risks, then submit for approval.",
|
||||
pending: "Plan is awaiting user approval. Wait for the user to approve or provide feedback.",
|
||||
drafting:
|
||||
"Plan is being drafted. Add steps, context, and risks, then submit for approval.",
|
||||
pending:
|
||||
"Plan is awaiting user approval. Wait for the user to approve or provide feedback.",
|
||||
approved: "Plan has been approved. You may proceed with implementation.",
|
||||
rejected: `Plan was rejected. Reason: ${plan.rejectionReason ?? "No reason provided"}`,
|
||||
executing: "Plan is currently being executed.",
|
||||
@@ -375,7 +367,7 @@ const handleCheckStatus = (params: PlanApprovalParams): ToolResult => {
|
||||
metadata: {
|
||||
planId: plan.id,
|
||||
planStatus: plan.status,
|
||||
stepsCompleted: plan.steps.filter(s => s.status === "completed").length,
|
||||
stepsCompleted: plan.steps.filter((s) => s.status === "completed").length,
|
||||
totalSteps: plan.steps.length,
|
||||
},
|
||||
};
|
||||
@@ -407,7 +399,7 @@ const handleAnalyzeTask = (params: PlanApprovalParams): ToolResult => {
|
||||
|
||||
if (analysis.reasons.length > 0) {
|
||||
output.push(``, `**Reasons**:`);
|
||||
analysis.reasons.forEach(r => output.push(`- ${r}`));
|
||||
analysis.reasons.forEach((r) => output.push(`- ${r}`));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Activity Panel
|
||||
*
|
||||
* Right sidebar showing session summary: context usage, modified files, etc.
|
||||
* Inspired by OpenCode's clean summary view.
|
||||
*/
|
||||
|
||||
import { For, Show, createMemo } from "solid-js";
|
||||
@@ -29,8 +28,7 @@ const formatTokenCount = (tokens: number): string => {
|
||||
return tokens.toString();
|
||||
};
|
||||
|
||||
const formatPercent = (value: number): string =>
|
||||
`${Math.round(value)}%`;
|
||||
const formatPercent = (value: number): string => `${Math.round(value)}%`;
|
||||
|
||||
export function ActivityPanel() {
|
||||
const theme = useTheme();
|
||||
@@ -86,10 +84,7 @@ export function ActivityPanel() {
|
||||
>
|
||||
{/* Context Section */}
|
||||
<box flexDirection="column" marginBottom={1}>
|
||||
<text
|
||||
fg={theme.colors.text}
|
||||
attributes={TextAttributes.BOLD}
|
||||
>
|
||||
<text fg={theme.colors.text} attributes={TextAttributes.BOLD}>
|
||||
Context
|
||||
</text>
|
||||
<box flexDirection="row" marginTop={1}>
|
||||
@@ -109,24 +104,17 @@ export function ActivityPanel() {
|
||||
|
||||
{/* Separator */}
|
||||
<box marginBottom={1}>
|
||||
<text fg={theme.colors.border}>
|
||||
{"─".repeat(PANEL_WIDTH - 2)}
|
||||
</text>
|
||||
<text fg={theme.colors.border}>{"─".repeat(PANEL_WIDTH - 2)}</text>
|
||||
</box>
|
||||
|
||||
{/* Modified Files Section */}
|
||||
<box flexDirection="column">
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text
|
||||
fg={theme.colors.text}
|
||||
attributes={TextAttributes.BOLD}
|
||||
>
|
||||
<text fg={theme.colors.text} attributes={TextAttributes.BOLD}>
|
||||
Modified Files
|
||||
</text>
|
||||
<Show when={modifiedFiles().length > 0}>
|
||||
<text fg={theme.colors.textDim}>
|
||||
{modifiedFiles().length}
|
||||
</text>
|
||||
<text fg={theme.colors.textDim}>{modifiedFiles().length}</text>
|
||||
</Show>
|
||||
</box>
|
||||
|
||||
@@ -145,15 +133,11 @@ export function ActivityPanel() {
|
||||
</text>
|
||||
<text fg={theme.colors.textDim}> </text>
|
||||
<Show when={file.additions > 0}>
|
||||
<text fg={theme.colors.success}>
|
||||
+{file.additions}
|
||||
</text>
|
||||
<text fg={theme.colors.success}>+{file.additions}</text>
|
||||
</Show>
|
||||
<Show when={file.deletions > 0}>
|
||||
<text fg={theme.colors.textDim}> </text>
|
||||
<text fg={theme.colors.error}>
|
||||
-{file.deletions}
|
||||
</text>
|
||||
<text fg={theme.colors.error}>-{file.deletions}</text>
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
@@ -164,15 +148,11 @@ export function ActivityPanel() {
|
||||
<box marginTop={1}>
|
||||
<text fg={theme.colors.textDim}>Total: </text>
|
||||
<Show when={totalChanges().additions > 0}>
|
||||
<text fg={theme.colors.success}>
|
||||
+{totalChanges().additions}
|
||||
</text>
|
||||
<text fg={theme.colors.success}>+{totalChanges().additions}</text>
|
||||
</Show>
|
||||
<Show when={totalChanges().deletions > 0}>
|
||||
<text fg={theme.colors.textDim}> </text>
|
||||
<text fg={theme.colors.error}>
|
||||
-{totalChanges().deletions}
|
||||
</text>
|
||||
<text fg={theme.colors.error}>-{totalChanges().deletions}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
|
||||
Reference in New Issue
Block a user