feat: initialize VS Code Mesh extension
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
out/
|
||||
.DS_Store
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Kill LIFE Mesh",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: compile"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "compile",
|
||||
"problemMatcher": "$tsc",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "npm: compile"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/**
|
||||
.vscode/**
|
||||
src/**
|
||||
tsconfig.json
|
||||
README.md
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 electron-rare
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Kill LIFE Mesh for VS Code
|
||||
|
||||
Extension VS Code orientee orchestration pour relier plusieurs projets locaux et coordonner les agents a travers plusieurs repos, contrats et handoffs.
|
||||
|
||||
## Positionnement
|
||||
|
||||
`Kill LIFE Mesh` est la version orchestration du trio :
|
||||
|
||||
- `Studio` pour le produit et la spec.
|
||||
- `Mesh` pour le maillage entre repos, la synchronisation et le sequencing.
|
||||
- `Operator` pour l'execution terrain et les runbooks.
|
||||
|
||||
## Ce que fait cette extension
|
||||
|
||||
- Scanne chaque projet ouvert dans le workspace, plus les racines ajoutees manuellement.
|
||||
- Detecte par defaut `agents/*.md` et `.github/agents/*.md`.
|
||||
- Affiche les agents trouves dans une sidebar dediee.
|
||||
- Permet d'ouvrir un prompt agent, de le copier, ou de l'inserer dans l'editeur courant.
|
||||
- Expose un participant de chat `@killmesh` avec des commandes `/agents`, `/pm`, `/architect`, `/firmware`, `/hw`, `/qa`, `/doc`.
|
||||
|
||||
Par defaut, `@killmesh` repond avec un biais orchestration : dependances entre repos, surfaces d'integration, ordre d'execution, contrats et synchronisation.
|
||||
|
||||
## Demarrage rapide
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run compile
|
||||
```
|
||||
|
||||
Ensuite dans VS Code :
|
||||
|
||||
1. Ouvre ce repo.
|
||||
2. Lance `F5` pour ouvrir une fenetre d'extension development host.
|
||||
3. Dans la nouvelle fenetre, ouvre ton vrai workspace projet.
|
||||
4. Si besoin, ajoute d'autres depots via la commande `Kill LIFE Mesh: Add Linked Project Root`.
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Sidebar
|
||||
|
||||
La vue `Kill LIFE Mesh` liste les projets detectes puis les agents trouves.
|
||||
|
||||
- Clic sur un agent : ouvre son fichier Markdown.
|
||||
- Menu contextuel : copie ou insere le prompt.
|
||||
|
||||
### Chat
|
||||
|
||||
Dans le chat Copilot / agent de VS Code :
|
||||
|
||||
- `@killmesh /agents` : liste les projets et agents lies.
|
||||
- `@killmesh /architect` : aide a aligner architecture et interfaces.
|
||||
- `@killmesh /firmware` : aide a relier runtime et integration.
|
||||
- `@killmesh /hw` : aide a relier contraintes hardware et flux projet.
|
||||
- `@killmesh /qa` : aide a cadrer validations et handoffs.
|
||||
- `@killmesh` sans commande : agit comme coordinateur multi-repo.
|
||||
|
||||
## Reglages
|
||||
|
||||
### `killLifeMesh.linkedProjectRoots`
|
||||
|
||||
Liste de chemins absolus supplementaires a scanner, en plus des dossiers deja ouverts dans le workspace.
|
||||
|
||||
### `killLifeMesh.agentSearchPatterns`
|
||||
|
||||
Globs utilises pour decouvrir les prompts d'agents dans chaque racine projet.
|
||||
|
||||
Valeurs par defaut :
|
||||
|
||||
```json
|
||||
[
|
||||
"agents/*.md",
|
||||
".github/agents/*.md"
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
|
||||
<rect width="128" height="128" rx="24" fill="#0E1A24"/>
|
||||
<rect x="18" y="18" width="92" height="92" rx="18" fill="#152A3A" stroke="#4FD1C5" stroke-width="4"/>
|
||||
<path d="M38 91V37H48V82H86V91H38Z" fill="#F7FAFC"/>
|
||||
<path d="M60 91V37H70V56L84 42H97L80 60L99 91H87L73 68L70 71V91H60Z" fill="#4FD1C5"/>
|
||||
<circle cx="93" cy="35" r="9" fill="#F6AD55"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 436 B |
Generated
+287
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"name": "kill-life-mesh",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kill-life-mesh",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-glob": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/vscode": "^1.110.0",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.110.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
"run-parallel": "^1.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.stat": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.walk": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
"fastq": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.110.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz",
|
||||
"integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.2",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"name": "kill-life-mesh",
|
||||
"displayName": "Kill LIFE Mesh",
|
||||
"description": "Orchestration-focused VS Code extension for coordinating linked local project agents across repos, contracts, and handoffs.",
|
||||
"version": "0.1.0",
|
||||
"publisher": "electron-rare",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.110.0"
|
||||
},
|
||||
"categories": [
|
||||
"AI",
|
||||
"Other"
|
||||
],
|
||||
"keywords": [
|
||||
"agents",
|
||||
"copilot",
|
||||
"chat",
|
||||
"embedded",
|
||||
"kill-life"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onChatParticipant:kill-life-mesh.orchestrator"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "killLifeMesh.refreshAgents",
|
||||
"title": "Kill LIFE Mesh: Refresh Agents"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.addLinkedProjectRoot",
|
||||
"title": "Kill LIFE Mesh: Add Linked Project Root"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.removeLinkedProjectRoot",
|
||||
"title": "Kill LIFE Mesh: Remove Linked Project Root"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.openAgentFile",
|
||||
"title": "Kill LIFE Mesh: Open Agent File"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.copyAgentPrompt",
|
||||
"title": "Kill LIFE Mesh: Copy Agent Prompt"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.insertAgentPrompt",
|
||||
"title": "Kill LIFE Mesh: Insert Agent Prompt"
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "killLifeMeshSidebar",
|
||||
"title": "Kill LIFE Mesh",
|
||||
"icon": "media/kill-life.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"killLifeMeshSidebar": [
|
||||
{
|
||||
"id": "killLifeMesh.agentsView",
|
||||
"name": "Linked Agents"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "killLifeMesh.refreshAgents",
|
||||
"when": "view == killLifeMesh.agentsView",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.addLinkedProjectRoot",
|
||||
"when": "view == killLifeMesh.agentsView",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.removeLinkedProjectRoot",
|
||||
"when": "view == killLifeMesh.agentsView",
|
||||
"group": "navigation@3"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "killLifeMesh.openAgentFile",
|
||||
"when": "view == killLifeMesh.agentsView && viewItem == agentItem",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.copyAgentPrompt",
|
||||
"when": "view == killLifeMesh.agentsView && viewItem == agentItem",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "killLifeMesh.insertAgentPrompt",
|
||||
"when": "view == killLifeMesh.agentsView && viewItem == agentItem",
|
||||
"group": "navigation@2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Kill LIFE Mesh",
|
||||
"properties": {
|
||||
"killLifeMesh.linkedProjectRoots": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Additional absolute project roots to scan for agent markdown files."
|
||||
},
|
||||
"killLifeMesh.agentSearchPatterns": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"agents/*.md",
|
||||
".github/agents/*.md"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Glob patterns resolved relative to each project root."
|
||||
}
|
||||
}
|
||||
},
|
||||
"chatParticipants": [
|
||||
{
|
||||
"id": "kill-life-mesh.orchestrator",
|
||||
"name": "killmesh",
|
||||
"fullName": "Kill LIFE Mesh",
|
||||
"description": "Coordinate multi-project orchestration from your linked local agents.",
|
||||
"isSticky": true,
|
||||
"commands": [
|
||||
{
|
||||
"name": "agents",
|
||||
"description": "List linked projects and discovered agents."
|
||||
},
|
||||
{
|
||||
"name": "pm",
|
||||
"description": "Use the project management agent prompt."
|
||||
},
|
||||
{
|
||||
"name": "architect",
|
||||
"description": "Use the architecture agent prompt."
|
||||
},
|
||||
{
|
||||
"name": "firmware",
|
||||
"description": "Use the firmware agent prompt."
|
||||
},
|
||||
{
|
||||
"name": "hw",
|
||||
"description": "Use the hardware schematic agent prompt."
|
||||
},
|
||||
{
|
||||
"name": "qa",
|
||||
"description": "Use the QA agent prompt."
|
||||
},
|
||||
{
|
||||
"name": "doc",
|
||||
"description": "Use the documentation agent prompt."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"check": "tsc --noEmit -p ./"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-glob": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/vscode": "^1.110.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import fg from 'fast-glob';
|
||||
import * as vscode from 'vscode';
|
||||
import { getAllProjectRoots, getSearchPatterns } from './config';
|
||||
|
||||
const SEARCH_IGNORES = [
|
||||
'**/.git/**',
|
||||
'**/node_modules/**',
|
||||
'**/out/**',
|
||||
'**/dist/**',
|
||||
'**/coverage/**',
|
||||
'**/target/**'
|
||||
];
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
architect: 'Architect Agent',
|
||||
doc: 'Documentation Agent',
|
||||
firmware: 'Firmware Agent',
|
||||
hw: 'Hardware Agent',
|
||||
pm: 'PM Agent',
|
||||
qa: 'QA Agent'
|
||||
};
|
||||
|
||||
export interface AgentDefinition {
|
||||
id: string;
|
||||
role: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: string;
|
||||
file: vscode.Uri;
|
||||
projectName: string;
|
||||
projectRoot: string;
|
||||
relativePath: string;
|
||||
}
|
||||
|
||||
export interface ProjectSummary {
|
||||
name: string;
|
||||
root: string;
|
||||
agents: AgentDefinition[];
|
||||
}
|
||||
|
||||
function inferRole(relativePath: string): string {
|
||||
const base = path.basename(relativePath, '.md').toLowerCase().replace(/[_-]?agent$/, '');
|
||||
if (base === 'hw_schematic' || base === 'hw-schematic' || base === 'hwschematic') {
|
||||
return 'hw';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
function toDisplayRole(role: string): string {
|
||||
return ROLE_LABELS[role] ?? `${role.replace(/[_-]+/g, ' ')} agent`;
|
||||
}
|
||||
|
||||
function extractTitle(body: string, role: string): string {
|
||||
for (const line of body.split(/\r?\n/u)) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith('# ')) {
|
||||
return trimmed.slice(2).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return toDisplayRole(role);
|
||||
}
|
||||
|
||||
function extractDescription(body: string, role: string): string {
|
||||
for (const line of body.split(/\r?\n/u)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('>') || trimmed.startsWith('-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return `Prompt source for ${toDisplayRole(role)}.`;
|
||||
}
|
||||
|
||||
function findOwningProjectRoot(filePath: string, projectRoots: string[]): string | undefined {
|
||||
return [...projectRoots]
|
||||
.sort((left, right) => right.length - left.length)
|
||||
.find(root => filePath === root || filePath.startsWith(`${root}${path.sep}`));
|
||||
}
|
||||
|
||||
function compareAgents(left: AgentDefinition, right: AgentDefinition): number {
|
||||
return (
|
||||
left.projectName.localeCompare(right.projectName) ||
|
||||
left.role.localeCompare(right.role) ||
|
||||
left.relativePath.localeCompare(right.relativePath)
|
||||
);
|
||||
}
|
||||
|
||||
export function groupAgentsByProject(agents: AgentDefinition[]): ProjectSummary[] {
|
||||
const grouped = new Map<string, ProjectSummary>();
|
||||
|
||||
for (const agent of agents) {
|
||||
const existing = grouped.get(agent.projectRoot);
|
||||
if (existing) {
|
||||
existing.agents.push(agent);
|
||||
continue;
|
||||
}
|
||||
|
||||
grouped.set(agent.projectRoot, {
|
||||
name: agent.projectName,
|
||||
root: agent.projectRoot,
|
||||
agents: [agent]
|
||||
});
|
||||
}
|
||||
|
||||
return [...grouped.values()]
|
||||
.map(project => ({
|
||||
...project,
|
||||
agents: [...project.agents].sort(compareAgents)
|
||||
}))
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}
|
||||
|
||||
export function resolveProjectRootForActiveEditor(projectRoots: string[]): string | undefined {
|
||||
const activeDocument = vscode.window.activeTextEditor?.document.uri;
|
||||
if (!activeDocument || activeDocument.scheme !== 'file') {
|
||||
return projectRoots[0];
|
||||
}
|
||||
|
||||
return [...projectRoots]
|
||||
.sort((left, right) => right.length - left.length)
|
||||
.find(root => activeDocument.fsPath === root || activeDocument.fsPath.startsWith(`${root}${path.sep}`))
|
||||
?? projectRoots[0];
|
||||
}
|
||||
|
||||
export function selectAgentForRole(
|
||||
agents: AgentDefinition[],
|
||||
role: string,
|
||||
preferredProjectRoot?: string
|
||||
): AgentDefinition | undefined {
|
||||
const candidates = agents.filter(agent => agent.role === role);
|
||||
if (!candidates.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return candidates.find(agent => agent.projectRoot === preferredProjectRoot) ?? candidates[0];
|
||||
}
|
||||
|
||||
export class AgentCatalog {
|
||||
private readonly onDidChangeEmitter = new vscode.EventEmitter<void>();
|
||||
private agents: AgentDefinition[] = [];
|
||||
private loaded = false;
|
||||
private lastError: string | undefined;
|
||||
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
getAgents(): AgentDefinition[] {
|
||||
return [...this.agents];
|
||||
}
|
||||
|
||||
getProjects(): ProjectSummary[] {
|
||||
return groupAgentsByProject(this.agents);
|
||||
}
|
||||
|
||||
getLastError(): string | undefined {
|
||||
return this.lastError;
|
||||
}
|
||||
|
||||
async ensureLoaded(): Promise<void> {
|
||||
if (!this.loaded) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
try {
|
||||
const projectRoots = getAllProjectRoots();
|
||||
const patterns = getSearchPatterns();
|
||||
const discoveredFiles = new Set<string>();
|
||||
|
||||
for (const projectRoot of projectRoots) {
|
||||
const matches = await fg(patterns, {
|
||||
cwd: projectRoot,
|
||||
absolute: true,
|
||||
onlyFiles: true,
|
||||
unique: true,
|
||||
followSymbolicLinks: false,
|
||||
ignore: SEARCH_IGNORES
|
||||
});
|
||||
|
||||
for (const match of matches) {
|
||||
discoveredFiles.add(path.resolve(match));
|
||||
}
|
||||
}
|
||||
|
||||
const nextAgents: AgentDefinition[] = [];
|
||||
const orderedFiles = [...discoveredFiles].sort((left, right) => left.localeCompare(right));
|
||||
|
||||
for (const filePath of orderedFiles) {
|
||||
const projectRoot = findOwningProjectRoot(filePath, projectRoots);
|
||||
if (!projectRoot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const body = await fs.readFile(filePath, 'utf8');
|
||||
const relativePath = path.relative(projectRoot, filePath);
|
||||
const role = inferRole(relativePath);
|
||||
|
||||
nextAgents.push({
|
||||
id: `${projectRoot}:${relativePath}`,
|
||||
role,
|
||||
title: extractTitle(body, role),
|
||||
description: extractDescription(body, role),
|
||||
body,
|
||||
file: vscode.Uri.file(filePath),
|
||||
projectName: path.basename(projectRoot),
|
||||
projectRoot,
|
||||
relativePath
|
||||
});
|
||||
}
|
||||
|
||||
this.agents = nextAgents.sort(compareAgents);
|
||||
this.lastError = undefined;
|
||||
this.loaded = true;
|
||||
} catch (error) {
|
||||
this.agents = [];
|
||||
this.lastError = error instanceof Error ? error.message : String(error);
|
||||
this.loaded = true;
|
||||
} finally {
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.onDidChangeEmitter.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import * as path from 'node:path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const NAMESPACE = 'killLifeMesh';
|
||||
const LINKED_PROJECT_ROOTS_KEY = 'linkedProjectRoots';
|
||||
const SEARCH_PATTERNS_KEY = 'agentSearchPatterns';
|
||||
|
||||
const DEFAULT_SEARCH_PATTERNS = ['agents/*.md', '.github/agents/*.md'];
|
||||
|
||||
function getConfiguration(): vscode.WorkspaceConfiguration {
|
||||
return vscode.workspace.getConfiguration(NAMESPACE);
|
||||
}
|
||||
|
||||
function normalizeProjectRoot(input: string): string {
|
||||
return path.resolve(input.trim());
|
||||
}
|
||||
|
||||
function getConfigurationTarget(): vscode.ConfigurationTarget {
|
||||
return vscode.workspace.workspaceFile || vscode.workspace.workspaceFolders?.length
|
||||
? vscode.ConfigurationTarget.Workspace
|
||||
: vscode.ConfigurationTarget.Global;
|
||||
}
|
||||
|
||||
export function getLinkedProjectRoots(): string[] {
|
||||
const configured = getConfiguration().get<string[]>(LINKED_PROJECT_ROOTS_KEY) ?? [];
|
||||
return [...new Set(configured.map(normalizeProjectRoot).filter(Boolean))];
|
||||
}
|
||||
|
||||
export function getSearchPatterns(): string[] {
|
||||
const configured = getConfiguration().get<string[]>(SEARCH_PATTERNS_KEY) ?? [];
|
||||
const cleaned = configured.map(pattern => pattern.trim()).filter(Boolean);
|
||||
return cleaned.length ? cleaned : DEFAULT_SEARCH_PATTERNS;
|
||||
}
|
||||
|
||||
export function getAllProjectRoots(): string[] {
|
||||
const workspaceRoots = (vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.fsPath);
|
||||
return [...new Set([...workspaceRoots, ...getLinkedProjectRoots()].map(normalizeProjectRoot))];
|
||||
}
|
||||
|
||||
export async function addLinkedProjectRoot(): Promise<void> {
|
||||
const selection = await vscode.window.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
openLabel: 'Link project root'
|
||||
});
|
||||
|
||||
const selected = selection?.[0];
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextRoot = normalizeProjectRoot(selected.fsPath);
|
||||
const current = getLinkedProjectRoots();
|
||||
if (current.includes(nextRoot)) {
|
||||
void vscode.window.showInformationMessage(`Project root already linked: ${nextRoot}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = [...current, nextRoot].sort((left, right) => left.localeCompare(right));
|
||||
await getConfiguration().update(LINKED_PROJECT_ROOTS_KEY, updated, getConfigurationTarget());
|
||||
void vscode.window.showInformationMessage(`Linked project root added: ${nextRoot}`);
|
||||
}
|
||||
|
||||
export async function removeLinkedProjectRoot(): Promise<void> {
|
||||
const current = getLinkedProjectRoots();
|
||||
if (!current.length) {
|
||||
void vscode.window.showInformationMessage('No linked project roots configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
const picked = await vscode.window.showQuickPick(current, {
|
||||
placeHolder: 'Select a linked project root to remove'
|
||||
});
|
||||
|
||||
if (!picked) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = current.filter(root => root !== picked);
|
||||
await getConfiguration().update(LINKED_PROJECT_ROOTS_KEY, updated, getConfigurationTarget());
|
||||
void vscode.window.showInformationMessage(`Linked project root removed: ${picked}`);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
AgentCatalog,
|
||||
AgentDefinition,
|
||||
resolveProjectRootForActiveEditor,
|
||||
selectAgentForRole
|
||||
} from './agentCatalog';
|
||||
import { addLinkedProjectRoot, getAllProjectRoots, removeLinkedProjectRoot } from './config';
|
||||
import { AgentsTreeProvider } from './tree';
|
||||
|
||||
const PARTICIPANT_ID = 'kill-life-mesh.orchestrator';
|
||||
|
||||
const ROLE_COMMANDS = new Set(['pm', 'architect', 'firmware', 'hw', 'qa', 'doc']);
|
||||
|
||||
function buildRosterMarkdown(agents: AgentDefinition[]): string {
|
||||
if (!agents.length) {
|
||||
return 'No agents discovered yet. Open a workspace or add linked project roots first.';
|
||||
}
|
||||
|
||||
const grouped = new Map<string, AgentDefinition[]>();
|
||||
for (const agent of agents) {
|
||||
const bucket = grouped.get(agent.projectName) ?? [];
|
||||
bucket.push(agent);
|
||||
grouped.set(agent.projectName, bucket);
|
||||
}
|
||||
|
||||
const lines: string[] = ['# Linked agents'];
|
||||
for (const [projectName, projectAgents] of [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
||||
lines.push(`## ${projectName}`);
|
||||
for (const agent of projectAgents.sort((left, right) => left.role.localeCompare(right.role))) {
|
||||
lines.push(`- \`${agent.role}\` - ${agent.title} (\`${agent.relativePath}\`)`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function buildGroundingPrompt(
|
||||
agents: AgentDefinition[],
|
||||
preferredProjectRoot: string | undefined,
|
||||
selectedAgent: AgentDefinition | undefined
|
||||
): string {
|
||||
const projectAgents = preferredProjectRoot
|
||||
? agents.filter(agent => agent.projectRoot === preferredProjectRoot)
|
||||
: agents;
|
||||
|
||||
const roster = (projectAgents.length ? projectAgents : agents)
|
||||
.map(agent => `- ${agent.role}: ${agent.title} (${agent.projectName} / ${agent.relativePath})`)
|
||||
.join('\n');
|
||||
|
||||
const projectLine = preferredProjectRoot
|
||||
? `Active project root: ${preferredProjectRoot}`
|
||||
: 'No active project root detected.';
|
||||
|
||||
const agentBlock = selectedAgent
|
||||
? `Selected agent prompt:\n${selectedAgent.body}`
|
||||
: 'No single agent command was selected. Act as a mesh orchestrator coordinating PM, architecture, firmware, hardware, QA, and docs across linked repos.';
|
||||
|
||||
return [
|
||||
'You are Kill LIFE Mesh inside VS Code.',
|
||||
'You focus on orchestration across linked repos, contracts, dependencies, handoffs, and sequencing of work.',
|
||||
'Ground every answer in the linked local project agents listed below.',
|
||||
'Do not invent files, commands, standards, or project state that were not provided.',
|
||||
'If information is missing, say what is missing and ask for the smallest useful next input.',
|
||||
projectLine,
|
||||
'Available agents:',
|
||||
roster || '- none',
|
||||
agentBlock
|
||||
].join('\n\n');
|
||||
}
|
||||
|
||||
function extractHistory(context: vscode.ChatContext): vscode.LanguageModelChatMessage[] {
|
||||
const previousMessages = context.history.filter(
|
||||
historyTurn => historyTurn instanceof vscode.ChatResponseTurn
|
||||
);
|
||||
|
||||
const messages: vscode.LanguageModelChatMessage[] = [];
|
||||
for (const previousMessage of previousMessages) {
|
||||
let fullMessage = '';
|
||||
previousMessage.response.forEach(part => {
|
||||
const markdownPart = part as vscode.ChatResponseMarkdownPart;
|
||||
fullMessage += markdownPart.value.value;
|
||||
});
|
||||
|
||||
if (fullMessage.trim()) {
|
||||
messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage));
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
async function pickAgent(
|
||||
catalog: AgentCatalog,
|
||||
placeHolder: string
|
||||
): Promise<AgentDefinition | undefined> {
|
||||
await catalog.ensureLoaded();
|
||||
const agents = catalog.getAgents();
|
||||
if (!agents.length) {
|
||||
void vscode.window.showWarningMessage('No agents discovered yet.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const picked = await vscode.window.showQuickPick(
|
||||
agents.map(agent => ({
|
||||
label: agent.title,
|
||||
description: `${agent.projectName} • ${agent.role}`,
|
||||
detail: agent.relativePath,
|
||||
agent
|
||||
})),
|
||||
{ placeHolder }
|
||||
);
|
||||
|
||||
return picked?.agent;
|
||||
}
|
||||
|
||||
async function openAgentFile(agent: AgentDefinition): Promise<void> {
|
||||
await vscode.window.showTextDocument(agent.file, {
|
||||
preview: false
|
||||
});
|
||||
}
|
||||
|
||||
async function copyAgentPrompt(agent: AgentDefinition): Promise<void> {
|
||||
await vscode.env.clipboard.writeText(agent.body);
|
||||
void vscode.window.showInformationMessage(`Copied prompt: ${agent.title}`);
|
||||
}
|
||||
|
||||
async function insertAgentPrompt(agent: AgentDefinition): Promise<void> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
const document = await vscode.workspace.openTextDocument({
|
||||
content: agent.body,
|
||||
language: 'markdown'
|
||||
});
|
||||
await vscode.window.showTextDocument(document, { preview: false });
|
||||
return;
|
||||
}
|
||||
|
||||
await editor.edit(editBuilder => {
|
||||
editBuilder.insert(editor.selection.active, agent.body);
|
||||
});
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
const catalog = new AgentCatalog();
|
||||
const treeProvider = new AgentsTreeProvider(catalog);
|
||||
|
||||
context.subscriptions.push(catalog, treeProvider);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('killLifeMesh.agentsView', treeProvider)
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.refreshAgents', async () => {
|
||||
await catalog.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.addLinkedProjectRoot', async () => {
|
||||
await addLinkedProjectRoot();
|
||||
await catalog.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.removeLinkedProjectRoot', async () => {
|
||||
await removeLinkedProjectRoot();
|
||||
await catalog.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.openAgentFile', async (agent?: AgentDefinition) => {
|
||||
const resolvedAgent = agent ?? (await pickAgent(catalog, 'Select an agent file to open'));
|
||||
if (resolvedAgent) {
|
||||
await openAgentFile(resolvedAgent);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.copyAgentPrompt', async (agent?: AgentDefinition) => {
|
||||
const resolvedAgent = agent ?? (await pickAgent(catalog, 'Select an agent prompt to copy'));
|
||||
if (resolvedAgent) {
|
||||
await copyAgentPrompt(resolvedAgent);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('killLifeMesh.insertAgentPrompt', async (agent?: AgentDefinition) => {
|
||||
const resolvedAgent = agent ?? (await pickAgent(catalog, 'Select an agent prompt to insert'));
|
||||
if (resolvedAgent) {
|
||||
await insertAgentPrompt(resolvedAgent);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration(event => {
|
||||
if (event.affectsConfiguration('killLifeMesh')) {
|
||||
void catalog.refresh();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeWorkspaceFolders(() => {
|
||||
void catalog.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
const chatHandler: vscode.ChatRequestHandler = async (
|
||||
request,
|
||||
chatContext,
|
||||
stream,
|
||||
token
|
||||
) => {
|
||||
await catalog.ensureLoaded();
|
||||
|
||||
const agents = catalog.getAgents();
|
||||
if (request.command === 'agents') {
|
||||
stream.markdown(buildRosterMarkdown(agents));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!agents.length) {
|
||||
stream.markdown(
|
||||
'No agents discovered yet. Open a project workspace or run `Kill LIFE Mesh: Add Linked Project Root` first.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectRoots = getAllProjectRoots();
|
||||
const preferredProjectRoot = resolveProjectRootForActiveEditor(projectRoots);
|
||||
const selectedRole = request.command && ROLE_COMMANDS.has(request.command) ? request.command : undefined;
|
||||
const selectedAgent = selectedRole
|
||||
? selectAgentForRole(agents, selectedRole, preferredProjectRoot)
|
||||
: undefined;
|
||||
|
||||
const messages: vscode.LanguageModelChatMessage[] = [
|
||||
vscode.LanguageModelChatMessage.User(
|
||||
buildGroundingPrompt(agents, preferredProjectRoot, selectedAgent)
|
||||
),
|
||||
...extractHistory(chatContext),
|
||||
vscode.LanguageModelChatMessage.User(
|
||||
request.prompt.trim() || 'Provide a concise orchestration next-step plan for the selected agent.'
|
||||
)
|
||||
];
|
||||
|
||||
const response = await request.model.sendRequest(messages, {}, token);
|
||||
for await (const fragment of response.text) {
|
||||
stream.markdown(fragment);
|
||||
}
|
||||
};
|
||||
|
||||
const participant = vscode.chat.createChatParticipant(PARTICIPANT_ID, chatHandler);
|
||||
participant.iconPath = vscode.Uri.joinPath(context.extensionUri, 'media', 'kill-life.svg');
|
||||
context.subscriptions.push(participant);
|
||||
|
||||
void catalog.refresh();
|
||||
}
|
||||
|
||||
export function deactivate(): void {}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { AgentCatalog, AgentDefinition, ProjectSummary } from './agentCatalog';
|
||||
|
||||
class ProjectNode {
|
||||
readonly kind = 'project';
|
||||
|
||||
constructor(readonly project: ProjectSummary) {}
|
||||
}
|
||||
|
||||
class AgentNode {
|
||||
readonly kind = 'agent';
|
||||
|
||||
constructor(readonly agent: AgentDefinition) {}
|
||||
}
|
||||
|
||||
class InfoNode {
|
||||
readonly kind = 'info';
|
||||
|
||||
constructor(readonly message: string) {}
|
||||
}
|
||||
|
||||
export type AgentsTreeNode = ProjectNode | AgentNode | InfoNode;
|
||||
|
||||
export class AgentsTreeProvider implements vscode.TreeDataProvider<AgentsTreeNode> {
|
||||
private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter<AgentsTreeNode | undefined>();
|
||||
|
||||
readonly onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;
|
||||
|
||||
constructor(private readonly catalog: AgentCatalog) {
|
||||
this.catalog.onDidChange(() => this.refresh());
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.onDidChangeTreeDataEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
getTreeItem(element: AgentsTreeNode): vscode.TreeItem {
|
||||
if (element.kind === 'info') {
|
||||
const infoItem = new vscode.TreeItem(element.message, vscode.TreeItemCollapsibleState.None);
|
||||
infoItem.contextValue = 'infoItem';
|
||||
infoItem.iconPath = new vscode.ThemeIcon('info');
|
||||
return infoItem;
|
||||
}
|
||||
|
||||
if (element.kind === 'project') {
|
||||
const projectItem = new vscode.TreeItem(
|
||||
element.project.name,
|
||||
vscode.TreeItemCollapsibleState.Expanded
|
||||
);
|
||||
projectItem.description = `${element.project.agents.length} agent${element.project.agents.length > 1 ? 's' : ''}`;
|
||||
projectItem.tooltip = element.project.root;
|
||||
projectItem.contextValue = 'projectItem';
|
||||
projectItem.iconPath = new vscode.ThemeIcon('repo');
|
||||
return projectItem;
|
||||
}
|
||||
|
||||
const agent = element.agent;
|
||||
const agentItem = new vscode.TreeItem(agent.title, vscode.TreeItemCollapsibleState.None);
|
||||
agentItem.description = agent.role;
|
||||
agentItem.tooltip = `${agent.description}\n${agent.file.fsPath}`;
|
||||
agentItem.resourceUri = agent.file;
|
||||
agentItem.contextValue = 'agentItem';
|
||||
agentItem.iconPath = new vscode.ThemeIcon('hubot');
|
||||
agentItem.command = {
|
||||
command: 'killLifeMesh.openAgentFile',
|
||||
title: 'Open Agent File',
|
||||
arguments: [agent]
|
||||
};
|
||||
return agentItem;
|
||||
}
|
||||
|
||||
getChildren(element?: AgentsTreeNode): vscode.ProviderResult<AgentsTreeNode[]> {
|
||||
if (!element) {
|
||||
const projects = this.catalog.getProjects();
|
||||
if (!projects.length) {
|
||||
const error = this.catalog.getLastError();
|
||||
if (error) {
|
||||
return [new InfoNode(`Unable to load agents: ${error}`)];
|
||||
}
|
||||
|
||||
return [
|
||||
new InfoNode('Open a project workspace or add linked project roots to discover agents.')
|
||||
];
|
||||
}
|
||||
|
||||
return projects.map(project => new ProjectNode(project));
|
||||
}
|
||||
|
||||
if (element.kind === 'project') {
|
||||
return element.project.agents.map(agent => new AgentNode(agent));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.onDidChangeTreeDataEmitter.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"outDir": "out",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"types": [
|
||||
"node",
|
||||
"vscode"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user