feat: initialize VS Code Operator 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 Operator",
|
||||||
|
"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 Operator for VS Code
|
||||||
|
|
||||||
|
Extension VS Code orientee operationnel pour relier plusieurs projets locaux et piloter les agents comme un cockpit d'execution, de runbooks et de prochaines actions.
|
||||||
|
|
||||||
|
## Positionnement
|
||||||
|
|
||||||
|
`Kill LIFE Operator` est la version operationnelle du trio :
|
||||||
|
|
||||||
|
- `Studio` pour la construction produit.
|
||||||
|
- `Mesh` pour la coordination multi-repo.
|
||||||
|
- `Operator` pour l'execution, la supervision, les checks et les actions terrain.
|
||||||
|
|
||||||
|
## 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 `@killops` avec des commandes `/agents`, `/pm`, `/architect`, `/firmware`, `/hw`, `/qa`, `/doc`.
|
||||||
|
|
||||||
|
Par defaut, `@killops` repond avec un biais operationnel : etat courant, checks, incidents, runbooks, priorite d'action et etapes executables.
|
||||||
|
|
||||||
|
## 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 Operator: Add Linked Project Root`.
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### Sidebar
|
||||||
|
|
||||||
|
La vue `Kill LIFE Operator` 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 :
|
||||||
|
|
||||||
|
- `@killops /agents` : liste les projets et agents lies.
|
||||||
|
- `@killops /firmware` : aide a piloter l'execution cote firmware.
|
||||||
|
- `@killops /hw` : aide a cadrer les impacts hardware et les actions.
|
||||||
|
- `@killops /qa` : aide a definir checks, validations et evidence packs.
|
||||||
|
- `@killops /doc` : aide a produire runbooks, notes et passages de relais.
|
||||||
|
- `@killops` sans commande : agit comme cockpit operationnel.
|
||||||
|
|
||||||
|
## Reglages
|
||||||
|
|
||||||
|
### `killLifeOperator.linkedProjectRoots`
|
||||||
|
|
||||||
|
Liste de chemins absolus supplementaires a scanner, en plus des dossiers deja ouverts dans le workspace.
|
||||||
|
|
||||||
|
### `killLifeOperator.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-operator",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "kill-life-operator",
|
||||||
|
"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-operator",
|
||||||
|
"displayName": "Kill LIFE Operator",
|
||||||
|
"description": "Operational VS Code extension for running linked local project agents as an execution cockpit for status, runbooks, and next actions.",
|
||||||
|
"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-operator.orchestrator"
|
||||||
|
],
|
||||||
|
"main": "./out/extension.js",
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.refreshAgents",
|
||||||
|
"title": "Kill LIFE Operator: Refresh Agents"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.addLinkedProjectRoot",
|
||||||
|
"title": "Kill LIFE Operator: Add Linked Project Root"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.removeLinkedProjectRoot",
|
||||||
|
"title": "Kill LIFE Operator: Remove Linked Project Root"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.openAgentFile",
|
||||||
|
"title": "Kill LIFE Operator: Open Agent File"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.copyAgentPrompt",
|
||||||
|
"title": "Kill LIFE Operator: Copy Agent Prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.insertAgentPrompt",
|
||||||
|
"title": "Kill LIFE Operator: Insert Agent Prompt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"viewsContainers": {
|
||||||
|
"activitybar": [
|
||||||
|
{
|
||||||
|
"id": "killLifeOperatorSidebar",
|
||||||
|
"title": "Kill LIFE Operator",
|
||||||
|
"icon": "media/kill-life.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"killLifeOperatorSidebar": [
|
||||||
|
{
|
||||||
|
"id": "killLifeOperator.agentsView",
|
||||||
|
"name": "Linked Agents"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"menus": {
|
||||||
|
"view/title": [
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.refreshAgents",
|
||||||
|
"when": "view == killLifeOperator.agentsView",
|
||||||
|
"group": "navigation@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.addLinkedProjectRoot",
|
||||||
|
"when": "view == killLifeOperator.agentsView",
|
||||||
|
"group": "navigation@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.removeLinkedProjectRoot",
|
||||||
|
"when": "view == killLifeOperator.agentsView",
|
||||||
|
"group": "navigation@3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"view/item/context": [
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.openAgentFile",
|
||||||
|
"when": "view == killLifeOperator.agentsView && viewItem == agentItem",
|
||||||
|
"group": "inline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.copyAgentPrompt",
|
||||||
|
"when": "view == killLifeOperator.agentsView && viewItem == agentItem",
|
||||||
|
"group": "navigation@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "killLifeOperator.insertAgentPrompt",
|
||||||
|
"when": "view == killLifeOperator.agentsView && viewItem == agentItem",
|
||||||
|
"group": "navigation@2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"title": "Kill LIFE Operator",
|
||||||
|
"properties": {
|
||||||
|
"killLifeOperator.linkedProjectRoots": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Additional absolute project roots to scan for agent markdown files."
|
||||||
|
},
|
||||||
|
"killLifeOperator.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-operator.orchestrator",
|
||||||
|
"name": "killops",
|
||||||
|
"fullName": "Kill LIFE Operator",
|
||||||
|
"description": "Drive operational next steps 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 = 'killLifeOperator';
|
||||||
|
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-operator.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 an operational cockpit coordinating runbooks, QA, firmware, hardware, and documentation for live execution.';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'You are Kill LIFE Operator inside VS Code.',
|
||||||
|
'You focus on execution, status, incidents, runbooks, checks, and the smallest reliable next operational action.',
|
||||||
|
'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('killLifeOperator.agentsView', treeProvider)
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('killLifeOperator.refreshAgents', async () => {
|
||||||
|
await catalog.refresh();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('killLifeOperator.addLinkedProjectRoot', async () => {
|
||||||
|
await addLinkedProjectRoot();
|
||||||
|
await catalog.refresh();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('killLifeOperator.removeLinkedProjectRoot', async () => {
|
||||||
|
await removeLinkedProjectRoot();
|
||||||
|
await catalog.refresh();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('killLifeOperator.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('killLifeOperator.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('killLifeOperator.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('killLifeOperator')) {
|
||||||
|
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 Operator: 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 operational 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: 'killLifeOperator.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