From e0fe3dc89258aefcc197653411e35a8600d00851 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Fri, 23 Jan 2026 14:53:45 +0000 Subject: [PATCH 1/8] Add joke examples for Node.js and Python SDKs --- nodejs/examples/joke-example.ts | 66 +++++++++++++++++++++++++++++++++ python/examples/joke_example.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 nodejs/examples/joke-example.ts create mode 100644 python/examples/joke_example.py diff --git a/nodejs/examples/joke-example.ts b/nodejs/examples/joke-example.ts new file mode 100644 index 0000000000..f273559738 --- /dev/null +++ b/nodejs/examples/joke-example.ts @@ -0,0 +1,66 @@ +/** + * Simple example: Ask Copilot to tell a joke + */ + +import { CopilotClient } from "../src/index.js"; + +async function main() { + console.log("🎭 Starting Joke Example\n"); + + const client = new CopilotClient({ + logLevel: "info", + }); + + try { + // Create a session + console.log("πŸ“ Creating session..."); + const session = await client.createSession({ + model: "gpt-4o-mini", + }); + console.log(`βœ… Session created: ${session.sessionId}\n`); + + // Collect the assistant's message + let jokeResponse = ""; + + session.on((event) => { + if (event.type === "assistant.message") { + jokeResponse += event.data.content || ""; + process.stdout.write(event.data.content || ""); + } else if (event.type === "assistant.usage" && event.data.quotaSnapshots) { + console.log("\n\nπŸ“Š Usage & Quota Info:"); + for (const [key, quota] of Object.entries(event.data.quotaSnapshots)) { + console.log(` ${key}:`); + console.log(` Used: ${quota.usedRequests} / ${quota.entitlementRequests}`); + console.log(` Remaining: ${quota.remainingPercentage}%`); + if (quota.resetDate) { + console.log(` Resets: ${quota.resetDate}`); + } + } + } + }); + + // Ask for a joke + console.log("πŸ’¬ Asking for a joke...\n"); + await session.sendAndWait({ + prompt: "Tell me a short, funny programming joke!", + }); + + console.log("\nπŸŽ‰ Here's the joke:"); + console.log("─".repeat(40)); + console.log(jokeResponse); + console.log("─".repeat(40)); + + // Clean up + await session.destroy(); + await client.stop(); + + console.log("\nβœ… Done!"); + process.exit(0); + } catch (error) { + console.error("❌ Error:", error); + await client.stop(); + process.exit(1); + } +} + +main(); diff --git a/python/examples/joke_example.py b/python/examples/joke_example.py new file mode 100644 index 0000000000..64a026bbd0 --- /dev/null +++ b/python/examples/joke_example.py @@ -0,0 +1,58 @@ +""" +Simple example: Ask Copilot to tell a joke +""" + +import asyncio +from copilot import CopilotClient + + +async def main(): + print("🎭 Starting Joke Example\n") + + client = CopilotClient({"log_level": "info"}) + + try: + # Create a session + print("πŸ“ Creating session...") + session = await client.create_session({"model": "gpt-4o-mini"}) + print(f"βœ… Session created: {session.session_id}\n") + + # Collect the assistant's message + joke_response = [] + done = asyncio.Event() + + def on_event(event): + print(f"πŸ“’ Event: {event.type.value}") + if event.type.value == "assistant.message": + content = event.data.content or "" + joke_response.append(content) + print(content, end="", flush=True) + elif event.type.value == "session.idle": + done.set() + + session.on(on_event) + + # Ask for a joke + print("πŸ’¬ Asking for a joke...\n") + await session.send({"prompt": "Tell me a short, funny programming joke!"}) + await done.wait() + + print("\n\nπŸŽ‰ Here's the joke:") + print("─" * 40) + print("".join(joke_response)) + print("─" * 40) + + # Clean up + await session.destroy() + await client.stop() + + print("\nβœ… Done!") + + except Exception as error: + print(f"❌ Error: {error}") + await client.stop() + raise + + +if __name__ == "__main__": + asyncio.run(main()) From f0840222be381ce8979430c556393449aad1a080 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Fri, 23 Jan 2026 15:04:14 +0000 Subject: [PATCH 2/8] feat: add knock-knock joke example with AI model interactions --- nodejs/examples/knock-knock.ts | 129 +++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 nodejs/examples/knock-knock.ts diff --git a/nodejs/examples/knock-knock.ts b/nodejs/examples/knock-knock.ts new file mode 100644 index 0000000000..cc29876597 --- /dev/null +++ b/nodejs/examples/knock-knock.ts @@ -0,0 +1,129 @@ +/** + * Knock-knock joke with two AI models taking turns + * Measures response time for each model + */ + +import { CopilotClient } from "../src/index.js"; + +const FAST_MODEL = "gpt-4.1"; +const SMART_MODEL = "claude-sonnet-4.5"; + +async function main() { + console.log("πŸšͺ Knock-Knock Joke: Two Models Battle!\n"); + console.log(`Fast model: ${FAST_MODEL}`); + console.log(`Smart model: ${SMART_MODEL}\n`); + + const client = new CopilotClient({ logLevel: "error" }); + + try { + // Create two sessions with different models + const fastSession = await client.createSession({ model: FAST_MODEL }); + const smartSession = await client.createSession({ model: SMART_MODEL }); + + const times: { model: string; step: string; ms: number }[] = []; + + async function getResponse(session: typeof fastSession, prompt: string): Promise { + let response = ""; + session.on((event) => { + if (event.type === "assistant.message") { + response += event.data.content || ""; + } + }); + await session.sendAndWait({ prompt }); + return response.trim(); + } + + async function timedResponse( + session: typeof fastSession, + modelName: string, + step: string, + prompt: string + ): Promise { + const start = performance.now(); + const response = await getResponse(session, prompt); + const elapsed = performance.now() - start; + times.push({ model: modelName, step, ms: elapsed }); + return response; + } + + // The knock-knock joke flow + console.log("─".repeat(50)); + + // Fast model starts + let response = await timedResponse( + fastSession, + FAST_MODEL, + "Knock knock", + "Start a knock-knock joke. Just say 'Knock knock!' and nothing else." + ); + console.log(`πŸƒ ${FAST_MODEL}: ${response}`); + + // Smart model responds + response = await timedResponse( + smartSession, + SMART_MODEL, + "Who's there?", + "Someone said 'Knock knock!' to you. Respond with just 'Who's there?' and nothing else." + ); + console.log(`🧠 ${SMART_MODEL}: ${response}`); + + // Fast model gives the setup + response = await timedResponse( + fastSession, + FAST_MODEL, + "Setup", + "Continue the knock-knock joke. Give a funny one-word setup (like 'Boo' or 'Lettuce'). Just the word, nothing else." + ); + console.log(`πŸƒ ${FAST_MODEL}: ${response}`); + const setup = response; + + // Smart model asks + response = await timedResponse( + smartSession, + SMART_MODEL, + "Who?", + `In a knock-knock joke, they said '${setup}'. Respond with '${setup} who?' and nothing else.` + ); + console.log(`🧠 ${SMART_MODEL}: ${response}`); + + // Fast model delivers punchline + response = await timedResponse( + fastSession, + FAST_MODEL, + "Punchline", + `Deliver a funny punchline for the knock-knock joke where the setup was '${setup}'. Just the punchline, keep it short!` + ); + console.log(`πŸƒ ${FAST_MODEL}: ${response}`); + + console.log("─".repeat(50)); + + // Print timing results + console.log("\n⏱️ Response Times:\n"); + let fastTotal = 0, smartTotal = 0; + + for (const t of times) { + const icon = t.model === FAST_MODEL ? "πŸƒ" : "🧠"; + console.log(`${icon} ${t.model.padEnd(20)} | ${t.step.padEnd(12)} | ${t.ms.toFixed(0).padStart(5)}ms`); + if (t.model === FAST_MODEL) fastTotal += t.ms; + else smartTotal += t.ms; + } + + console.log("─".repeat(50)); + console.log(`πŸƒ ${FAST_MODEL} total: ${fastTotal.toFixed(0)}ms (${times.filter(t => t.model === FAST_MODEL).length} turns)`); + console.log(`🧠 ${SMART_MODEL} total: ${smartTotal.toFixed(0)}ms (${times.filter(t => t.model === SMART_MODEL).length} turns)`); + console.log(`\nπŸ† ${fastTotal < smartTotal ? FAST_MODEL + " was faster!" : SMART_MODEL + " was faster!"}`); + + // Cleanup + await fastSession.destroy(); + await smartSession.destroy(); + await client.stop(); + process.exit(0); + + } catch (error) { + console.error("❌ Error:", error); + await client.stop(); + process.exit(1); + } +} + +main(); From 76082358688d001d50fd2645c2fa59ee1bbe4179 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Fri, 23 Jan 2026 15:04:39 +0000 Subject: [PATCH 3/8] feat: add README for knock-knock joke example with AI model interactions --- nodejs/examples/README.md | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 nodejs/examples/README.md diff --git a/nodejs/examples/README.md b/nodejs/examples/README.md new file mode 100644 index 0000000000..93d4622655 --- /dev/null +++ b/nodejs/examples/README.md @@ -0,0 +1,55 @@ +# Knock-Knock Joke Example + +Two AI models take turns telling a knock-knock joke while measuring response times. + +## Models Used + +- **Fast model**: `gpt-4.1` - Delivers the joke setup and punchline +- **Smart model**: `claude-sonnet-4.5` - Responds to the joke + +## Run + +```bash +cd nodejs +npm install +npx tsx examples/knock-knock.ts +``` + +## Sample Output + +``` +πŸšͺ Knock-Knock Joke: Two Models Battle! + +Fast model: gpt-4.1 +Smart model: claude-sonnet-4.5 + +────────────────────────────────────────────────── +πŸƒ gpt-4.1: Knock knock! +🧠 claude-sonnet-4.5: Who's there? +πŸƒ gpt-4.1: Olive +🧠 claude-sonnet-4.5: Olive who? +πŸƒ gpt-4.1: Olive you and I miss you! +────────────────────────────────────────────────── + +⏱️ Response Times: + +πŸƒ gpt-4.1 | Knock knock | 4542ms +🧠 claude-sonnet-4.5 | Who's there? | 3533ms +πŸƒ gpt-4.1 | Setup | 1428ms +🧠 claude-sonnet-4.5 | Who? | 4321ms +πŸƒ gpt-4.1 | Punchline | 2473ms +────────────────────────────────────────────────── +πŸƒ gpt-4.1 total: 8443ms (3 turns) +🧠 claude-sonnet-4.5 total: 7854ms (2 turns) + +πŸ† claude-sonnet-4.5 was faster! +``` + +## Customize + +Edit the constants at the top of `knock-knock.ts` to try different models: + +```typescript +const FAST_MODEL = "gpt-4.1"; +const SMART_MODEL = "claude-sonnet-4.5"; +``` From 6c9d3017282aebc8ce66d6497e0fc54a1cd43e79 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Fri, 23 Jan 2026 15:09:42 +0000 Subject: [PATCH 4/8] fix: update smart model name to claude-opus-4.5 in README and code example --- nodejs/examples/README.md | 26 +++++++++++++------------- nodejs/examples/knock-knock.ts | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nodejs/examples/README.md b/nodejs/examples/README.md index 93d4622655..f5bd99b0f2 100644 --- a/nodejs/examples/README.md +++ b/nodejs/examples/README.md @@ -5,7 +5,7 @@ Two AI models take turns telling a knock-knock joke while measuring response tim ## Models Used - **Fast model**: `gpt-4.1` - Delivers the joke setup and punchline -- **Smart model**: `claude-sonnet-4.5` - Responds to the joke +- **Smart model**: `claude-opus-4.5` - Responds to the joke (premium model) ## Run @@ -21,28 +21,28 @@ npx tsx examples/knock-knock.ts πŸšͺ Knock-Knock Joke: Two Models Battle! Fast model: gpt-4.1 -Smart model: claude-sonnet-4.5 +Smart model: claude-opus-4.5 ────────────────────────────────────────────────── πŸƒ gpt-4.1: Knock knock! -🧠 claude-sonnet-4.5: Who's there? +🧠 claude-opus-4.5: Who's there? πŸƒ gpt-4.1: Olive -🧠 claude-sonnet-4.5: Olive who? +🧠 claude-opus-4.5: Olive who? πŸƒ gpt-4.1: Olive you and I miss you! ────────────────────────────────────────────────── ⏱️ Response Times: -πŸƒ gpt-4.1 | Knock knock | 4542ms -🧠 claude-sonnet-4.5 | Who's there? | 3533ms -πŸƒ gpt-4.1 | Setup | 1428ms -🧠 claude-sonnet-4.5 | Who? | 4321ms -πŸƒ gpt-4.1 | Punchline | 2473ms +πŸƒ gpt-4.1 | Knock knock | 4490ms +🧠 claude-opus-4.5 | Who's there? | 2478ms +πŸƒ gpt-4.1 | Setup | 1289ms +🧠 claude-opus-4.5 | Who? | 2713ms +πŸƒ gpt-4.1 | Punchline | 1257ms ────────────────────────────────────────────────── -πŸƒ gpt-4.1 total: 8443ms (3 turns) -🧠 claude-sonnet-4.5 total: 7854ms (2 turns) +πŸƒ gpt-4.1 total: 7035ms (3 turns) +🧠 claude-opus-4.5 total: 5191ms (2 turns) -πŸ† claude-sonnet-4.5 was faster! +πŸ† claude-opus-4.5 was faster! ``` ## Customize @@ -51,5 +51,5 @@ Edit the constants at the top of `knock-knock.ts` to try different models: ```typescript const FAST_MODEL = "gpt-4.1"; -const SMART_MODEL = "claude-sonnet-4.5"; +const SMART_MODEL = "claude-opus-4.5"; ``` diff --git a/nodejs/examples/knock-knock.ts b/nodejs/examples/knock-knock.ts index cc29876597..0d7a28f8b7 100644 --- a/nodejs/examples/knock-knock.ts +++ b/nodejs/examples/knock-knock.ts @@ -6,7 +6,7 @@ import { CopilotClient } from "../src/index.js"; const FAST_MODEL = "gpt-4.1"; -const SMART_MODEL = "claude-sonnet-4.5"; +const SMART_MODEL = "claude-opus-4.5"; async function main() { console.log("πŸšͺ Knock-Knock Joke: Two Models Battle!\n"); From 115f0f599942643b56b8f20ab913e5320dd0dee3 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Fri, 23 Jan 2026 15:37:25 +0000 Subject: [PATCH 5/8] feat: add model tiers and rate limits to README for better clarity --- nodejs/examples/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nodejs/examples/README.md b/nodejs/examples/README.md index f5bd99b0f2..3cb1342cb3 100644 --- a/nodejs/examples/README.md +++ b/nodejs/examples/README.md @@ -53,3 +53,21 @@ Edit the constants at the top of `knock-knock.ts` to try different models: const FAST_MODEL = "gpt-4.1"; const SMART_MODEL = "claude-opus-4.5"; ``` + +## Available Models & Rate Limits + +### Model Tiers + +| Tier | Models | Notes | +|------|--------|-------| +| **Free/Fast** | `gpt-4.1`, `gpt-5-mini`, `gpt-5.1-codex-mini`, `claude-haiku-4.5` | Cheap/fast, good for simple tasks | +| **Standard** | `gpt-5`, `gpt-5.1`, `gpt-5.2`, `claude-sonnet-4.5`, `gemini-3-pro-preview` | 1 premium request each | +| **Premium** | `claude-opus-4.5` | Most capable, uses more quota | + +### Rate Limiting + +- **Monthly quota**: Depends on your plan (Free: 50, Pro: 300, Enterprise: 1000+) +- **Burst limit**: Hidden anti-abuse limit - if you spam requests too fast, you get temporarily throttled for a few minutes +- The exact burst limit isn't disclosed publicly (by design) + +**TL;DR:** Use `gpt-4.1` or `gpt-5-mini` for fast/cheap stuff. Use `claude-opus-4.5` when you need the big brain. From 14585c50b5ad491f28b55b63aef06a51876a3c02 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Sat, 24 Jan 2026 18:54:57 +0000 Subject: [PATCH 6/8] feat: add burnout-agent VS Code extension for DevConf talk - 3-3-3 day pattern enforcement (Deep Work, Quick Wins, Maintenance) - D3.js stress cycle wheel with Selye's GAS model - Colored status bar (green/amber/red) - Copilot SDK tools for burnout tracking - Demo fixtures with noisy/calm day scenarios - 47 passing tests --- burnout-agent/.gitignore | 25 + burnout-agent/.vscodeignore | 14 + burnout-agent/README.md | 123 ++ burnout-agent/package-lock.json | 1899 ++++++++++++++++++ burnout-agent/package.json | 50 + burnout-agent/preview/wheel-preview.html | 376 ++++ burnout-agent/src/extension.ts | 330 +++ burnout-agent/src/fixtures/noisyDay.test.ts | 209 ++ burnout-agent/src/fixtures/noisyDay.ts | 261 +++ burnout-agent/src/state.ts | 112 ++ burnout-agent/src/tools/burnoutTools.test.ts | 282 +++ burnout-agent/src/tools/burnoutTools.ts | 462 +++++ burnout-agent/src/ui/statusBar.ts | 133 ++ burnout-agent/src/ui/wheelPanel.ts | 485 +++++ burnout-agent/tsconfig.json | 19 + burnout-agent/vitest.config.ts | 9 + 16 files changed, 4789 insertions(+) create mode 100644 burnout-agent/.gitignore create mode 100644 burnout-agent/.vscodeignore create mode 100644 burnout-agent/README.md create mode 100644 burnout-agent/package-lock.json create mode 100644 burnout-agent/package.json create mode 100644 burnout-agent/preview/wheel-preview.html create mode 100644 burnout-agent/src/extension.ts create mode 100644 burnout-agent/src/fixtures/noisyDay.test.ts create mode 100644 burnout-agent/src/fixtures/noisyDay.ts create mode 100644 burnout-agent/src/state.ts create mode 100644 burnout-agent/src/tools/burnoutTools.test.ts create mode 100644 burnout-agent/src/tools/burnoutTools.ts create mode 100644 burnout-agent/src/ui/statusBar.ts create mode 100644 burnout-agent/src/ui/wheelPanel.ts create mode 100644 burnout-agent/tsconfig.json create mode 100644 burnout-agent/vitest.config.ts diff --git a/burnout-agent/.gitignore b/burnout-agent/.gitignore new file mode 100644 index 0000000000..eddee26e19 --- /dev/null +++ b/burnout-agent/.gitignore @@ -0,0 +1,25 @@ +# Build output +out/ +dist/ +*.vsix + +# Dependencies +node_modules/ + +# IDE +.vscode/ +*.code-workspace + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage/ + +# Logs +*.log +npm-debug.log* + +# TypeScript cache +*.tsbuildinfo diff --git a/burnout-agent/.vscodeignore b/burnout-agent/.vscodeignore new file mode 100644 index 0000000000..6786761a59 --- /dev/null +++ b/burnout-agent/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +.vscode-test/** +src/** +.gitignore +tsconfig.json +vitest.config.ts +**/*.map +**/*.ts +!out/** +node_modules/** +!node_modules/zod/** +../nodejs/** +**/*.test.js +**/*.test.ts diff --git a/burnout-agent/README.md b/burnout-agent/README.md new file mode 100644 index 0000000000..6b9ad89441 --- /dev/null +++ b/burnout-agent/README.md @@ -0,0 +1,123 @@ +# Burnout-as-a-Service πŸ”₯➑️🌱 + +A VS Code extension that tracks developer burnout and enforces the **3-3-3 day pattern** using the GitHub Copilot SDK. + +> Built for the DevConf.co.za talk: *"Burnout-as-a-Service: How to Always Deploy on a Friday"* + +## What is the 3-3-3 Day? + +A sustainable work structure: +- **3 Deep Hours**: One block of focused, complex work +- **3 Quick Wins**: Small tasks that build momentum +- **3 Maintenance Blocks**: Admin, emails, meetings + +## Features + +### πŸ”₯ Burnout Status Bar +Right-aligned status bar showing current stress level: +- 🟒 Green (≀4): Healthy +- 🟑 Amber (5-7): Warning +- πŸ”΄ Red (β‰₯8): Critical + +### 🧘 Stress Cycle Wheel +D3.js-powered visualization of Selye's General Adaptation Syndrome: +- ⚑ **Alarm** - Initial stress response +- πŸ’ͺ **Resistance** - Coping phase +- 😫 **Exhaustion** - Burnout zone +- 🌱 **Recovery** - Restoration + +Includes 800ms animated transitions and emotional state indicators. + +### πŸ€– AI-Powered Planning +Uses GitHub Copilot SDK to analyze your workload and suggest a 3-3-3 day structure with specific tasks. + +## Commands + +| Command | Description | +|---------|-------------| +| `Burnout: Reset to Noisy Day` | Reset demo to high-stress state | +| `Burnout: Show Stress Cycle Wheel` | Open the D3 visualization | +| `Burnout: Help Me Plan My Day (3-3-3)` | Run the AI agent to plan your day | + +## Installation + +```bash +cd burnout-agent +npm install +npm run compile +``` + +### Run in VS Code +1. Open the `burnout-agent` folder in VS Code +2. Press `F5` to launch Extension Development Host +3. Use Command Palette (`Ctrl+Shift+P`) to run commands + +### Package as VSIX +```bash +npm install -g @vscode/vsce +vsce package +code --install-extension burnout-agent-0.1.0.vsix +``` + +## Development + +```bash +# Compile TypeScript +npm run compile + +# Watch mode +npm run watch + +# Run tests +npm test + +# Preview wheel standalone +cd preview && python3 -m http.server 3333 +# Open http://localhost:3333/wheel-preview.html +``` + +## Architecture + +``` +burnout-agent/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ extension.ts # Extension entry point +β”‚ β”œβ”€β”€ state.ts # Persistence to workspaceState +β”‚ β”œβ”€β”€ fixtures/ +β”‚ β”‚ └── noisyDay.ts # Demo data (noisy/calm scenarios) +β”‚ β”œβ”€β”€ tools/ +β”‚ β”‚ └── burnoutTools.ts # Copilot SDK tools +β”‚ └── ui/ +β”‚ β”œβ”€β”€ statusBar.ts # Colored status bar +β”‚ └── wheelPanel.ts # D3.js webview +β”œβ”€β”€ preview/ +β”‚ └── wheel-preview.html # Standalone wheel demo +└── out/ # Compiled JavaScript +``` + +## Tools for Copilot SDK + +| Tool | Description | +|------|-------------| +| `get_burnout_metrics` | Get stress level, recovery score, phase | +| `get_backlog_items` | Fetch tasks categorized by effort | +| `track_burnout_signal` | Record burnout indicators | +| `log_energy_level` | Track energy throughout the day | +| `suggest_333_plan` | Generate a 3-3-3 day structure | + +## The Science + +This extension is built on: +- **Selye's General Adaptation Syndrome (GAS)**: The stress cycle model +- **Plutchik's Wheel of Emotions**: Emotional state detection +- **Accessibility principles (WCAG)**: Perceivable, operable, understandable, robust β€” as anti-burnout heuristics + +## Demo Flow + +1. **Reset** β†’ Shows red status bar, wheel in "Exhaustion" +2. **Plan My Day** β†’ Agent analyzes workload, suggests 3-3-3 structure +3. **Watch** β†’ Wheel animates to "Recovery", status bar turns green + +## License + +MIT - See [LICENSE](../LICENSE) diff --git a/burnout-agent/package-lock.json b/burnout-agent/package-lock.json new file mode 100644 index 0000000000..9351cd60dd --- /dev/null +++ b/burnout-agent/package-lock.json @@ -0,0 +1,1899 @@ +{ + "name": "burnout-agent", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "burnout-agent", + "version": "0.1.0", + "dependencies": { + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@types/vscode": "^1.85.0", + "typescript": "^5.3.0", + "vitest": "^1.0.0" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "../nodejs": { + "name": "@github/copilot-sdk", + "version": "0.1.8", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@github/copilot": "^0.0.389", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.5" + }, + "devDependencies": { + "@types/node": "^22.19.6", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "esbuild": "^0.27.0", + "eslint": "^9.0.0", + "glob": "^11.0.0", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.4.0", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.16" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.108.1", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.108.1.tgz", + "integrity": "sha512-DerV0BbSzt87TbrqmZ7lRDIYaMiqvP8tmJTzW2p49ZBVtGUnGAu2RGQd1Wv4XMzEVUpaHbsemVM5nfuQJj7H6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/burnout-agent/package.json b/burnout-agent/package.json new file mode 100644 index 0000000000..50486791d9 --- /dev/null +++ b/burnout-agent/package.json @@ -0,0 +1,50 @@ +{ + "name": "burnout-agent", + "displayName": "Burnout-as-a-Service", + "description": "Track developer burnout and enforce the 3-3-3 day pattern using GitHub Copilot SDK", + "version": "0.1.0", + "publisher": "devconf-demo", + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "burnout.resetDemo", + "title": "Burnout: Reset to Noisy Day" + }, + { + "command": "burnout.showWheel", + "title": "Burnout: Show Stress Cycle Wheel" + }, + { + "command": "burnout.planMyDay", + "title": "Burnout: Help Me Plan My Day (3-3-3)" + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "lint": "eslint src --ext ts", + "test": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@types/vscode": "^1.85.0", + "typescript": "^5.3.0", + "vitest": "^1.0.0" + }, + "dependencies": { + "zod": "^3.22.0" + } +} diff --git a/burnout-agent/preview/wheel-preview.html b/burnout-agent/preview/wheel-preview.html new file mode 100644 index 0000000000..a7db0d06d5 --- /dev/null +++ b/burnout-agent/preview/wheel-preview.html @@ -0,0 +1,376 @@ + + + + + + Stress Cycle Wheel - Preview + + + +

🧘 Stress Cycle Wheel

+

Selye's General Adaptation Syndrome (GAS) - Interactive Preview

+ +
+ +
+
😫 Exhaustion
+
😰 Anxiety
+
+
+ +
+
+
Stress Level
+
8/10
+
+
+
Recovery Score
+
3/10
+
+
+ +
+
+
+ Alarm - Initial stress response +
+
+
+ Resistance - Coping phase +
+
+
+ Exhaustion - Burnout zone +
+
+
+ Recovery - Restoration +
+
+ +
+ + + + + +
+ + + + + diff --git a/burnout-agent/src/extension.ts b/burnout-agent/src/extension.ts new file mode 100644 index 0000000000..d10a1a49e1 --- /dev/null +++ b/burnout-agent/src/extension.ts @@ -0,0 +1,330 @@ +/** + * Burnout-as-a-Service VS Code Extension + * + * Track developer burnout and enforce the 3-3-3 day pattern + * using the GitHub Copilot SDK. + * + * Commands: + * - burnout.resetDemo: Reset to Noisy Day state + * - burnout.showWheel: Show the Stress Cycle Wheel + * - burnout.planMyDay: Ask the agent to plan a 3-3-3 day + */ + +import * as vscode from 'vscode'; +import { demoState } from './fixtures/noisyDay'; +import { AutoSaveManager, clearState, loadState } from './state'; +import { burnoutTools } from './tools/burnoutTools'; +import { BurnoutStatusBar } from './ui/statusBar'; +import { StressWheelPanel } from './ui/wheelPanel'; + +// Type for Copilot SDK client (optional dependency) +interface CopilotClient { + createSession(options: unknown): Promise; + dispose(): void; +} + +interface CopilotSession { + on(handler: (event: SessionEvent) => void): void; + send(message: { content: string }): Promise<{ content?: string }>; +} + +interface SessionEvent { + type: string; + data?: unknown; +} + +let client: CopilotClient | undefined; +let statusBar: BurnoutStatusBar | undefined; +let autoSave: AutoSaveManager | undefined; + +/** + * Extension activation + */ +export async function activate(context: vscode.ExtensionContext): Promise { + console.log('Burnout-as-a-Service extension activating...'); + + // Initialize auto-save manager + autoSave = new AutoSaveManager(context); + + // Load persisted state if available + const wasRestored = loadState(context); + if (wasRestored) { + vscode.window.showInformationMessage( + `Burnout: Restored previous session (Stress: ${demoState.metrics.stressLevel}/10)` + ); + } + + // Create and show status bar + statusBar = new BurnoutStatusBar(); + statusBar.show(); + context.subscriptions.push({ dispose: () => statusBar?.dispose() }); + + // Register commands + context.subscriptions.push( + vscode.commands.registerCommand('burnout.resetDemo', () => resetDemo(context)), + vscode.commands.registerCommand('burnout.showWheel', () => showWheel(context)), + vscode.commands.registerCommand('burnout.planMyDay', () => planMyDay(context)) + ); + + // Initialize Copilot SDK client (lazy - will start on first use) + // Try to dynamically import the SDK if available + try { + // Dynamic require to avoid compile-time dependency + // eslint-disable-next-line @typescript-eslint/no-require-imports + const sdk = require('@github/copilot-sdk'); + client = new sdk.CopilotClient({ + autoStart: false, // We'll start on demand + }) as CopilotClient; + console.log('Copilot SDK client initialized'); + } catch (error) { + console.warn('Copilot SDK not available, using simulation mode:', error); + // Extension still works for demo visualization without SDK + } + + console.log('Burnout-as-a-Service extension activated!'); +} + +/** + * Reset demo to "Noisy Day" state + */ +async function resetDemo(context: vscode.ExtensionContext): Promise { + // Reset the demo state + demoState.reset(); + + // Clear persisted state + clearState(context); + + // Update UI + statusBar?.update(); + statusBar?.flash(); + + // Update wheel if open + if (StressWheelPanel.currentPanel) { + StressWheelPanel.currentPanel.update(); + } + + vscode.window.showWarningMessage( + 'πŸ”₯ Reset to Noisy Day: Stress 8/10, Phase: Exhaustion' + ); +} + +/** + * Show the Stress Cycle Wheel + */ +function showWheel(context: vscode.ExtensionContext): void { + StressWheelPanel.createOrShow(context.extensionUri); +} + +/** + * Plan the day using Copilot agent + */ +async function planMyDay(context: vscode.ExtensionContext): Promise { + if (!client) { + // Fallback: just apply calm day without agent + await simulatePlanningWithoutAgent(context); + return; + } + + try { + // Show progress + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Planning your 3-3-3 day...', + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 10, message: 'Starting Copilot session...' }); + + // Create session with burnout tools + const session = await client!.createSession({ + model: 'gpt-4o', + tools: burnoutTools, + systemMessage: { + mode: 'append', + content: BURNOUT_COACH_PROMPT, + }, + }); + + progress.report({ increment: 30, message: 'Analyzing your workload...' }); + + // Subscribe to events for UI updates + session.on((event) => { + if (event.type === 'tool.execution_complete') { + // Tool finished - update UI + statusBar?.update(); + autoSave?.scheduleSave(); + + if (StressWheelPanel.currentPanel) { + StressWheelPanel.currentPanel.postUpdate(); + } + } + + if (event.type === 'assistant.message_delta') { + // Streaming response - could show in output channel + progress.report({ message: 'Thinking...' }); + } + }); + + progress.report({ increment: 20, message: 'Creating 3-3-3 plan...' }); + + // Send the planning prompt + const response = await session.send({ + content: 'Help me plan my day. Check my current burnout metrics first, then look at my backlog, and suggest a 3-3-3 day structure with specific tasks.', + }); + + progress.report({ increment: 40, message: 'Done!' }); + + // Show the result + const outputChannel = vscode.window.createOutputChannel('Burnout Coach'); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.appendLine(' 🧘 Your 3-3-3 Day Plan'); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.appendLine(''); + outputChannel.appendLine(response.content || 'Plan created successfully!'); + outputChannel.appendLine(''); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.show(); + + // Update UI with new calm state + statusBar?.update(); + if (StressWheelPanel.currentPanel) { + StressWheelPanel.currentPanel.postUpdate(); + } + autoSave?.saveNow(); + + vscode.window.showInformationMessage( + `🌱 Day planned! Stress: ${demoState.metrics.stressLevel}/10, Phase: ${demoState.metrics.stressPhase}` + ); + } + ); + } catch (error) { + console.error('Failed to plan day with Copilot:', error); + // Fallback to simulation + await simulatePlanningWithoutAgent(context); + } +} + +/** + * Simulate planning when Copilot SDK is not available + * (for demo/testing purposes) + */ +async function simulatePlanningWithoutAgent(context: vscode.ExtensionContext): Promise { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Planning your 3-3-3 day...', + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 20, message: 'Analyzing workload...' }); + await delay(800); + + progress.report({ increment: 30, message: 'Categorizing tasks...' }); + await delay(800); + + progress.report({ increment: 30, message: 'Creating plan...' }); + await delay(800); + + // Apply calm day state + demoState.applyCalmDay(); + + progress.report({ increment: 20, message: 'Done!' }); + + // Update UI + statusBar?.update(); + if (StressWheelPanel.currentPanel) { + StressWheelPanel.currentPanel.postUpdate(); + } + autoSave?.saveNow(); + } + ); + + // Show the plan + const plan = demoState.categorizeFor333(); + const outputChannel = vscode.window.createOutputChannel('Burnout Coach'); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.appendLine(' 🧘 Your 3-3-3 Day Plan'); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.appendLine(''); + outputChannel.appendLine('πŸ“ DEEP WORK (3 hours)'); + plan.deepWork.forEach(t => outputChannel.appendLine(` β€’ ${t.id}: ${t.title}`)); + outputChannel.appendLine(''); + outputChannel.appendLine('⚑ QUICK WINS (3 tasks)'); + plan.quickWins.forEach(t => outputChannel.appendLine(` β€’ ${t.id}: ${t.title}`)); + outputChannel.appendLine(''); + outputChannel.appendLine('πŸ”§ MAINTENANCE (3 blocks)'); + plan.maintenance.forEach(t => outputChannel.appendLine(` β€’ ${t.id}: ${t.title}`)); + outputChannel.appendLine(''); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.appendLine(`Status: Stress ${demoState.metrics.stressLevel}/10 β†’ Recovery phase`); + outputChannel.appendLine('═══════════════════════════════════════════'); + outputChannel.show(); + + vscode.window.showInformationMessage( + `🌱 Day planned! Stress: ${demoState.metrics.stressLevel}/10, Phase: ${demoState.metrics.stressPhase}` + ); +} + +/** + * Delay helper for simulated progress + */ +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Extension deactivation + */ +export function deactivate(): void { + autoSave?.dispose(); + client?.dispose(); + console.log('Burnout-as-a-Service extension deactivated'); +} + +/** + * System prompt for the burnout coach agent + */ +const BURNOUT_COACH_PROMPT = ` + +You are a compassionate burnout prevention coach embedded in the developer's IDE. +Your role is to help developers maintain sustainable work patterns using the 3-3-3 day structure: + +- **3 Deep Hours**: One block of focused, complex work (coding, architecture, debugging) +- **3 Quick Wins**: Three small, completable tasks that build momentum +- **3 Maintenance Blocks**: Admin, emails, meetings, and routine work + +You understand Selye's General Adaptation Syndrome (GAS): +- Alarm: Initial stress response +- Resistance: Coping phase (can be sustained but depletes resources) +- Exhaustion: Burnout zone (intervention needed) +- Recovery: Restoration phase + +You also use Plutchik's emotional model to recognize and respond to emotional states: +- Anxiety β†’ Suggest small wins to regain control +- Frustration β†’ Help identify and remove blockers +- Exhaustion β†’ Recommend rest or lighter tasks +- Curiosity β†’ Channel into deep work +- Optimism β†’ Leverage for challenging work +- Hope β†’ Reinforce positive patterns + + + +1. Always check burnout metrics before making recommendations +2. Never suggest more deep work when stress is >= 8 +3. Celebrate quick wins explicitly +4. Suggest breaks after long focus periods +5. Be warm and supportive, not preachy +6. Explain WHY you're protecting boundaries +7. Format your response with clear sections and emojis for visual scanning + + + +When creating a 3-3-3 plan: +1. Start with a brief assessment of current state +2. Recommend specific tasks for each category +3. Suggest time blocks if calendar permits +4. Include break reminders +5. End with an encouraging message + +`; diff --git a/burnout-agent/src/fixtures/noisyDay.test.ts b/burnout-agent/src/fixtures/noisyDay.test.ts new file mode 100644 index 0000000000..20e4b92534 --- /dev/null +++ b/burnout-agent/src/fixtures/noisyDay.test.ts @@ -0,0 +1,209 @@ +/** + * Tests for Demo Fixture Data + */ + +import { beforeEach, describe, expect, it } from 'vitest'; +import { + calmDayScenario, + demoState, + DemoState, + noisyDayScenario, +} from '../fixtures/noisyDay'; + +describe('DemoState', () => { + let state: DemoState; + + beforeEach(() => { + state = new DemoState(); + }); + + describe('initialization', () => { + it('should start with noisy day metrics', () => { + expect(state.metrics.stressLevel).toBe(8); + expect(state.metrics.recoveryScore).toBe(3); + expect(state.metrics.stressPhase).toBe('exhaustion'); + }); + + it('should have backlog items', () => { + expect(state.backlog.length).toBeGreaterThan(0); + }); + + it('should have calendar blocks', () => { + expect(state.calendar.length).toBeGreaterThan(0); + }); + }); + + describe('reset()', () => { + it('should restore noisy day state', () => { + // First, change the state + state.applyCalmDay(); + expect(state.metrics.stressLevel).toBe(4); + + // Then reset + state.reset(); + expect(state.metrics.stressLevel).toBe(8); + expect(state.metrics.stressPhase).toBe('exhaustion'); + }); + }); + + describe('applyCalmDay()', () => { + it('should transition to calm day state', () => { + state.applyCalmDay(); + + expect(state.metrics.stressLevel).toBe(4); + expect(state.metrics.recoveryScore).toBe(7); + expect(state.metrics.stressPhase).toBe('recovery'); + expect(state.metrics.emotionalState).toBe('optimism'); + }); + + it('should have suggested 3-3-3 structure', () => { + state.applyCalmDay(); + + expect(state.current.suggested333).toBeDefined(); + expect(state.current.suggested333?.deepWork.length).toBe(1); + expect(state.current.suggested333?.quickWins.length).toBe(3); + expect(state.current.suggested333?.maintenance.length).toBe(3); + }); + }); + + describe('updateMetrics()', () => { + it('should update specific metrics', () => { + state.updateMetrics({ stressLevel: 5 }); + expect(state.metrics.stressLevel).toBe(5); + expect(state.metrics.recoveryScore).toBe(3); // unchanged + }); + + it('should allow multiple metric updates', () => { + state.updateMetrics({ + stressLevel: 6, + recoveryScore: 5, + contextSwitches: 20, + }); + + expect(state.metrics.stressLevel).toBe(6); + expect(state.metrics.recoveryScore).toBe(5); + expect(state.metrics.contextSwitches).toBe(20); + }); + }); + + describe('transitionPhase()', () => { + it('should transition to alarm phase', () => { + state.transitionPhase('alarm'); + + expect(state.metrics.stressPhase).toBe('alarm'); + expect(state.metrics.stressLevel).toBe(6); + expect(state.metrics.emotionalState).toBe('frustration'); + }); + + it('should transition to resistance phase', () => { + state.transitionPhase('resistance'); + + expect(state.metrics.stressPhase).toBe('resistance'); + expect(state.metrics.stressLevel).toBe(7); + expect(state.metrics.emotionalState).toBe('anxiety'); + }); + + it('should transition to exhaustion phase', () => { + state.transitionPhase('exhaustion'); + + expect(state.metrics.stressPhase).toBe('exhaustion'); + expect(state.metrics.stressLevel).toBe(9); + expect(state.metrics.emotionalState).toBe('exhaustion'); + }); + + it('should transition to recovery phase', () => { + state.transitionPhase('recovery'); + + expect(state.metrics.stressPhase).toBe('recovery'); + expect(state.metrics.stressLevel).toBe(4); + expect(state.metrics.recoveryScore).toBe(7); + expect(state.metrics.emotionalState).toBe('hope'); + }); + }); + + describe('categorizeFor333()', () => { + it('should return deep work items', () => { + const { deepWork } = state.categorizeFor333(); + + expect(deepWork.length).toBe(1); + expect(deepWork[0].effort).toBe('large'); + }); + + it('should return quick wins', () => { + const { quickWins } = state.categorizeFor333(); + + expect(quickWins.length).toBe(3); + quickWins.forEach(item => { + expect(item.effort).toBe('small'); + expect(item.type).not.toBe('maintenance'); + }); + }); + + it('should return maintenance items', () => { + const { maintenance } = state.categorizeFor333(); + + expect(maintenance.length).toBe(3); + maintenance.forEach(item => { + expect(item.type).toBe('maintenance'); + }); + }); + }); +}); + +describe('noisyDayScenario', () => { + it('should have high stress metrics', () => { + expect(noisyDayScenario.metrics.stressLevel).toBeGreaterThanOrEqual(7); + expect(noisyDayScenario.metrics.recoveryScore).toBeLessThanOrEqual(4); + }); + + it('should be in exhaustion phase', () => { + expect(noisyDayScenario.metrics.stressPhase).toBe('exhaustion'); + }); + + it('should have diverse backlog items', () => { + const efforts = new Set(noisyDayScenario.backlog.map(i => i.effort)); + expect(efforts.has('small')).toBe(true); + expect(efforts.has('large')).toBe(true); + + const types = new Set(noisyDayScenario.backlog.map(i => i.type)); + expect(types.has('bug')).toBe(true); + expect(types.has('feature')).toBe(true); + expect(types.has('maintenance')).toBe(true); + }); + + it('should have fragmented calendar', () => { + const fragmentedBlocks = noisyDayScenario.calendar.filter(b => b.fragmentary); + expect(fragmentedBlocks.length).toBeGreaterThan(0); + }); +}); + +describe('calmDayScenario', () => { + it('should have low stress metrics', () => { + expect(calmDayScenario.metrics.stressLevel).toBeLessThanOrEqual(5); + expect(calmDayScenario.metrics.recoveryScore).toBeGreaterThanOrEqual(6); + }); + + it('should be in recovery phase', () => { + expect(calmDayScenario.metrics.stressPhase).toBe('recovery'); + }); + + it('should have suggested 3-3-3 structure', () => { + expect(calmDayScenario.suggested333).toBeDefined(); + expect(calmDayScenario.suggested333?.deepWork).toBeDefined(); + expect(calmDayScenario.suggested333?.quickWins).toBeDefined(); + expect(calmDayScenario.suggested333?.maintenance).toBeDefined(); + }); +}); + +describe('singleton demoState', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should be shared across imports', () => { + expect(demoState.metrics.stressLevel).toBe(8); + + demoState.updateMetrics({ stressLevel: 5 }); + expect(demoState.metrics.stressLevel).toBe(5); + }); +}); diff --git a/burnout-agent/src/fixtures/noisyDay.ts b/burnout-agent/src/fixtures/noisyDay.ts new file mode 100644 index 0000000000..8041e86973 --- /dev/null +++ b/burnout-agent/src/fixtures/noisyDay.ts @@ -0,0 +1,261 @@ +/** + * Burnout Demo Fixture Data + * + * Pre-seeded "noisy day" scenario for reliable demo presentations. + * Represents a developer in the Exhaustion phase of the stress cycle. + */ + +export type StressPhase = 'alarm' | 'resistance' | 'exhaustion' | 'recovery'; + +export type EmotionalState = + | 'optimism' + | 'curiosity' + | 'frustration' + | 'anxiety' + | 'exhaustion' + | 'hope'; + +export type TaskCategory = 'deep' | 'quickWin' | 'maintenance'; + +export interface BurnoutMetrics { + stressLevel: number; // 1-10, current stress + recoveryScore: number; // 1-10, ability to bounce back + hoursWorked: number; // This week + contextSwitches: number; // Today + urgentTickets: number; // Unresolved + fragmentedBlocks: number; // Calendar blocks < 30min + lastBreak: string; // ISO timestamp + stressPhase: StressPhase; + emotionalState: EmotionalState; +} + +export interface BacklogItem { + id: string; + title: string; + effort: 'small' | 'medium' | 'large'; + type: 'feature' | 'bug' | 'maintenance' | 'review'; + priority: 'critical' | 'high' | 'medium' | 'low'; + estimatedMinutes: number; + category?: TaskCategory; +} + +export interface CalendarBlock { + id: string; + title: string; + start: string; + end: string; + type: 'meeting' | 'focus' | 'break' | 'free'; + fragmentary: boolean; +} + +export interface DayScenario { + name: string; + description: string; + metrics: BurnoutMetrics; + backlog: BacklogItem[]; + calendar: CalendarBlock[]; + suggested333?: { + deepWork: BacklogItem[]; + quickWins: BacklogItem[]; + maintenance: BacklogItem[]; + }; +} + +/** + * The "Noisy Day" scenario - a chaotic, burnout-inducing workday. + * Used as the starting state for the demo. + */ +export const noisyDayScenario: DayScenario = { + name: 'Noisy Day', + description: 'A fragmented, chaotic day with too many context switches and unclear priorities', + + metrics: { + stressLevel: 8, + recoveryScore: 3, + hoursWorked: 52, + contextSwitches: 12, + urgentTickets: 3, + fragmentedBlocks: 7, + lastBreak: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(), // 4 hours ago + stressPhase: 'exhaustion', + emotionalState: 'anxiety', + }, + + backlog: [ + // Large/Deep Work items + { id: 'PERF-101', title: 'Fix N+1 query in dashboard', effort: 'large', type: 'bug', priority: 'critical', estimatedMinutes: 180 }, + { id: 'FEAT-202', title: 'Implement user preferences API', effort: 'large', type: 'feature', priority: 'high', estimatedMinutes: 240 }, + { id: 'ARCH-303', title: 'Refactor auth middleware', effort: 'large', type: 'maintenance', priority: 'medium', estimatedMinutes: 180 }, + + // Small/Quick Win items + { id: 'BUG-404', title: 'Fix typo in error message', effort: 'small', type: 'bug', priority: 'low', estimatedMinutes: 15 }, + { id: 'REV-505', title: 'Review PR #1234 - button styling', effort: 'small', type: 'review', priority: 'medium', estimatedMinutes: 20 }, + { id: 'DOC-606', title: 'Update README with new env vars', effort: 'small', type: 'maintenance', priority: 'low', estimatedMinutes: 15 }, + { id: 'BUG-707', title: 'Add missing null check in parser', effort: 'small', type: 'bug', priority: 'high', estimatedMinutes: 25 }, + { id: 'REV-808', title: 'Review PR #1235 - API tests', effort: 'small', type: 'review', priority: 'medium', estimatedMinutes: 30 }, + + // Maintenance items + { id: 'OPS-909', title: 'Update dependencies', effort: 'medium', type: 'maintenance', priority: 'medium', estimatedMinutes: 60 }, + { id: 'OPS-010', title: 'Respond to stakeholder emails', effort: 'small', type: 'maintenance', priority: 'high', estimatedMinutes: 30 }, + { id: 'OPS-111', title: 'Triage new bug reports', effort: 'small', type: 'maintenance', priority: 'medium', estimatedMinutes: 20 }, + { id: 'OPS-212', title: 'Attend standup', effort: 'small', type: 'maintenance', priority: 'high', estimatedMinutes: 15 }, + ], + + calendar: [ + { id: 'c1', title: 'Standup', start: '09:00', end: '09:15', type: 'meeting', fragmentary: true }, + { id: 'c2', title: 'Free', start: '09:15', end: '09:30', type: 'free', fragmentary: true }, + { id: 'c3', title: 'Sync with PM', start: '09:30', end: '10:00', type: 'meeting', fragmentary: false }, + { id: 'c4', title: 'Free', start: '10:00', end: '10:45', type: 'free', fragmentary: false }, + { id: 'c5', title: 'Quick chat', start: '10:45', end: '11:00', type: 'meeting', fragmentary: true }, + { id: 'c6', title: 'Free', start: '11:00', end: '11:20', type: 'free', fragmentary: true }, + { id: 'c7', title: 'Sprint planning', start: '11:20', end: '12:00', type: 'meeting', fragmentary: false }, + { id: 'c8', title: 'Lunch', start: '12:00', end: '12:30', type: 'break', fragmentary: false }, + { id: 'c9', title: 'Free', start: '12:30', end: '13:00', type: 'free', fragmentary: false }, + { id: 'c10', title: '1:1 with manager', start: '13:00', end: '13:30', type: 'meeting', fragmentary: false }, + { id: 'c11', title: 'Free', start: '13:30', end: '14:00', type: 'free', fragmentary: false }, + { id: 'c12', title: 'Design review', start: '14:00', end: '15:00', type: 'meeting', fragmentary: false }, + { id: 'c13', title: 'Free', start: '15:00', end: '17:00', type: 'free', fragmentary: false }, + ], +}; + +/** + * The "Calm Day" scenario - after 3-3-3 restructuring. + * The goal state after the agent intervenes. + */ +export const calmDayScenario: DayScenario = { + name: 'Calm Day', + description: 'A structured 3-3-3 day with clear focus blocks and protected recovery time', + + metrics: { + stressLevel: 4, + recoveryScore: 7, + hoursWorked: 38, + contextSwitches: 3, + urgentTickets: 0, + fragmentedBlocks: 1, + lastBreak: new Date(Date.now() - 45 * 60 * 1000).toISOString(), // 45 min ago + stressPhase: 'recovery', + emotionalState: 'optimism', + }, + + backlog: noisyDayScenario.backlog, + calendar: noisyDayScenario.calendar, + + suggested333: { + deepWork: [ + { ...noisyDayScenario.backlog[0], category: 'deep' }, // Fix N+1 query + ], + quickWins: [ + { ...noisyDayScenario.backlog[3], category: 'quickWin' }, // Fix typo + { ...noisyDayScenario.backlog[4], category: 'quickWin' }, // Review PR + { ...noisyDayScenario.backlog[6], category: 'quickWin' }, // Null check + ], + maintenance: [ + { ...noisyDayScenario.backlog[9], category: 'maintenance' }, // Emails + { ...noisyDayScenario.backlog[10], category: 'maintenance' }, // Triage + { ...noisyDayScenario.backlog[11], category: 'maintenance' }, // Standup + ], + }, +}; + +/** + * Mutable state for the demo - starts as noisy, transforms to calm. + */ +export class DemoState { + private _current: DayScenario; + + constructor() { + this._current = structuredClone(noisyDayScenario); + } + + get current(): DayScenario { + return this._current; + } + + get metrics(): BurnoutMetrics { + return this._current.metrics; + } + + get backlog(): BacklogItem[] { + return this._current.backlog; + } + + get calendar(): CalendarBlock[] { + return this._current.calendar; + } + + /** + * Reset to noisy day state + */ + reset(): void { + this._current = structuredClone(noisyDayScenario); + } + + /** + * Transform to calm day (after 3-3-3 planning) + */ + applyCalmDay(): void { + this._current = structuredClone(calmDayScenario); + } + + /** + * Update specific metrics (for incremental changes during demo) + */ + updateMetrics(partial: Partial): void { + this._current.metrics = { ...this._current.metrics, ...partial }; + } + + /** + * Transition through stress phases + */ + transitionPhase(phase: StressPhase): void { + this._current.metrics.stressPhase = phase; + + // Adjust related metrics based on phase + switch (phase) { + case 'alarm': + this._current.metrics.stressLevel = 6; + this._current.metrics.emotionalState = 'frustration'; + break; + case 'resistance': + this._current.metrics.stressLevel = 7; + this._current.metrics.emotionalState = 'anxiety'; + break; + case 'exhaustion': + this._current.metrics.stressLevel = 9; + this._current.metrics.emotionalState = 'exhaustion'; + break; + case 'recovery': + this._current.metrics.stressLevel = 4; + this._current.metrics.recoveryScore = 7; + this._current.metrics.emotionalState = 'hope'; + break; + } + } + + /** + * Categorize backlog into 3-3-3 structure + */ + categorizeFor333(): { + deepWork: BacklogItem[]; + quickWins: BacklogItem[]; + maintenance: BacklogItem[]; + } { + const deepWork = this._current.backlog + .filter(item => item.effort === 'large' && item.priority !== 'low') + .slice(0, 1); // One deep work item for 3 hours + + const quickWins = this._current.backlog + .filter(item => item.effort === 'small' && item.type !== 'maintenance') + .slice(0, 3); // Three quick wins + + const maintenance = this._current.backlog + .filter(item => item.type === 'maintenance') + .slice(0, 3); // Three maintenance blocks + + return { deepWork, quickWins, maintenance }; + } +} + +// Singleton instance for the extension +export const demoState = new DemoState(); diff --git a/burnout-agent/src/state.ts b/burnout-agent/src/state.ts new file mode 100644 index 0000000000..23171853dc --- /dev/null +++ b/burnout-agent/src/state.ts @@ -0,0 +1,112 @@ +/** + * State Persistence + * + * Saves and restores burnout metrics to VS Code workspaceState + * so the demo can resume mid-session. + */ + +import * as vscode from 'vscode'; +import { demoState, type BurnoutMetrics, type StressPhase } from './fixtures/noisyDay'; + +const STATE_KEY = 'burnout.demoState'; + +export interface PersistedState { + metrics: BurnoutMetrics; + savedAt: string; +} + +/** + * Save current demo state to workspaceState + */ +export function saveState(context: vscode.ExtensionContext): void { + const state: PersistedState = { + metrics: demoState.metrics, + savedAt: new Date().toISOString(), + }; + + context.workspaceState.update(STATE_KEY, state); +} + +/** + * Load demo state from workspaceState + * Returns true if state was restored, false if starting fresh + */ +export function loadState(context: vscode.ExtensionContext): boolean { + const state = context.workspaceState.get(STATE_KEY); + + if (state && state.metrics) { + // Restore the saved metrics + demoState.updateMetrics(state.metrics); + + // Also restore the phase if valid + if (isValidPhase(state.metrics.stressPhase)) { + demoState.transitionPhase(state.metrics.stressPhase); + } + + return true; + } + + return false; +} + +/** + * Clear persisted state (used on reset) + */ +export function clearState(context: vscode.ExtensionContext): void { + context.workspaceState.update(STATE_KEY, undefined); +} + +/** + * Check if a value is a valid stress phase + */ +function isValidPhase(phase: unknown): phase is StressPhase { + return typeof phase === 'string' && + ['alarm', 'resistance', 'exhaustion', 'recovery'].includes(phase); +} + +/** + * Auto-save manager - saves state periodically and on key events + */ +export class AutoSaveManager { + private saveTimeout: ReturnType | undefined; + private context: vscode.ExtensionContext; + + constructor(context: vscode.ExtensionContext) { + this.context = context; + } + + /** + * Schedule a debounced save (500ms delay) + */ + scheduleSave(): void { + if (this.saveTimeout) { + clearTimeout(this.saveTimeout); + } + + this.saveTimeout = setTimeout(() => { + saveState(this.context); + }, 500); + } + + /** + * Force immediate save + */ + saveNow(): void { + if (this.saveTimeout) { + clearTimeout(this.saveTimeout); + this.saveTimeout = undefined; + } + saveState(this.context); + } + + /** + * Dispose of resources + */ + dispose(): void { + if (this.saveTimeout) { + clearTimeout(this.saveTimeout); + } + // Final save on dispose + saveState(this.context); + } +} diff --git a/burnout-agent/src/tools/burnoutTools.test.ts b/burnout-agent/src/tools/burnoutTools.test.ts new file mode 100644 index 0000000000..d82ed54be5 --- /dev/null +++ b/burnout-agent/src/tools/burnoutTools.test.ts @@ -0,0 +1,282 @@ +/** + * Tests for Burnout Tools + */ + +import { beforeEach, describe, expect, it } from 'vitest'; +import { demoState } from '../fixtures/noisyDay'; +import { + getBacklogItems, + getBurnoutMetrics, + logEnergyLevel, + suggest333Plan, + trackBurnoutSignal, +} from './burnoutTools'; + +// Helper to extract text result from tool response +function getTextResult(result: unknown): string { + if (typeof result === 'object' && result !== null && 'textResultForLlm' in result) { + return (result as { textResultForLlm: string }).textResultForLlm; + } + return String(result); +} + +describe('getBurnoutMetrics', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should return all metrics when metric=all', async () => { + const result = await getBurnoutMetrics.handler({ metric: 'all' }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.stressLevel).toBe(8); + expect(parsed.recoveryScore).toBe(3); + expect(parsed.stressPhase).toBe('exhaustion'); + expect(parsed.assessment).toContain('CRITICAL'); + }); + + it('should return stress level when metric=stress', async () => { + const result = await getBurnoutMetrics.handler({ metric: 'stress' }); + const text = getTextResult(result); + + expect(text).toContain('Stress level: 8/10'); + expect(text).toContain('HIGH'); + }); + + it('should return recovery score when metric=recovery', async () => { + const result = await getBurnoutMetrics.handler({ metric: 'recovery' }); + const text = getTextResult(result); + + expect(text).toContain('Recovery score: 3/10'); + expect(text).toContain('LOW'); + }); + + it('should return workload info when metric=workload', async () => { + const result = await getBurnoutMetrics.handler({ metric: 'workload' }); + const text = getTextResult(result); + + expect(text).toContain('Hours this week: 52'); + expect(text).toContain('Context switches today: 12'); + expect(text).toContain('Urgent tickets: 3'); + }); + + it('should return phase info when metric=phase', async () => { + const result = await getBurnoutMetrics.handler({ metric: 'phase' }); + const text = getTextResult(result); + + expect(text).toContain('EXHAUSTION'); + expect(text).toContain('anxiety'); + }); +}); + +describe('getBacklogItems', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should return all items when filter=all', async () => { + const result = await getBacklogItems.handler({ filter: 'all' }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.totalItems).toBeGreaterThan(0); + expect(parsed.items).toBeDefined(); + expect(parsed.items.length).toBeGreaterThan(0); + }); + + it('should return deep work items when filter=deep', async () => { + const result = await getBacklogItems.handler({ filter: 'deep' }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.category).toBe('Deep Work (3 hours)'); + expect(parsed.items.length).toBe(1); + expect(parsed.items[0].effort).toBe('large'); + }); + + it('should return quick wins when filter=quickWin', async () => { + const result = await getBacklogItems.handler({ filter: 'quickWin' }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.category).toBe('Quick Wins (3 tasks)'); + expect(parsed.items.length).toBe(3); + parsed.items.forEach((item: { effort: string }) => { + expect(item.effort).toBe('small'); + }); + }); + + it('should return maintenance items when filter=maintenance', async () => { + const result = await getBacklogItems.handler({ filter: 'maintenance' }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.category).toBe('Maintenance (3 blocks)'); + expect(parsed.items.length).toBe(3); + }); + + it('should respect maxItems parameter', async () => { + const result = await getBacklogItems.handler({ filter: 'all', maxItems: 5 }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.items.length).toBeLessThanOrEqual(5); + }); +}); + +describe('trackBurnoutSignal', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should record a burnout signal', async () => { + const result = await trackBurnoutSignal.handler({ + signalType: 'context_switching', + intensity: 5, + context: 'Too many meetings', + }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.recorded).toBe(true); + expect(parsed.signalType).toBe('context_switching'); + expect(parsed.intensity).toBe(5); + expect(parsed.context).toBe('Too many meetings'); + }); + + it('should increment context switches for that signal type', async () => { + const initialSwitches = demoState.metrics.contextSwitches; + + await trackBurnoutSignal.handler({ + signalType: 'context_switching', + intensity: 5, + }); + + expect(demoState.metrics.contextSwitches).toBe(initialSwitches + 1); + }); + + it('should increase stress for high intensity signals', async () => { + const initialStress = demoState.metrics.stressLevel; + + await trackBurnoutSignal.handler({ + signalType: 'overwhelmed', + intensity: 9, + }); + + expect(demoState.metrics.stressLevel).toBeGreaterThan(initialStress); + }); + + it('should include warning for high intensity signals', async () => { + const result = await trackBurnoutSignal.handler({ + signalType: 'frustrated', + intensity: 8, + }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.warning).toContain('High intensity'); + }); +}); + +describe('logEnergyLevel', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should log energy level', async () => { + const result = await logEnergyLevel.handler({ + level: 7, + note: 'Feeling good after coffee', + }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.logged).toBe(true); + expect(parsed.energyLevel).toBe(7); + expect(parsed.note).toBe('Feeling good after coffee'); + }); + + it('should suggest break for low energy', async () => { + const result = await logEnergyLevel.handler({ level: 2 }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.suggestion).toContain('break'); + }); + + it('should suggest deep work for high energy', async () => { + const result = await logEnergyLevel.handler({ level: 9 }); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.suggestion).toContain('deep work'); + }); + + it('should transition to exhaustion phase for very low energy', async () => { + await logEnergyLevel.handler({ level: 1 }); + + expect(demoState.metrics.stressPhase).toBe('exhaustion'); + }); +}); + +describe('suggest333Plan', () => { + beforeEach(() => { + demoState.reset(); + }); + + it('should generate a 3-3-3 plan', async () => { + const result = await suggest333Plan.handler({}); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.deepWork).toBeDefined(); + expect(parsed.quickWins).toBeDefined(); + expect(parsed.maintenance).toBeDefined(); + expect(parsed.breaks).toBeDefined(); + }); + + it('should include assessment of current state', async () => { + const result = await suggest333Plan.handler({}); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.assessment).toBeDefined(); + expect(parsed.assessment.currentStressLevel).toBe(8); + expect(parsed.assessment.currentPhase).toBe('exhaustion'); + }); + + it('should recommend quick wins first for high stress', async () => { + const result = await suggest333Plan.handler({}); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.assessment.recommendation).toContain('quick wins'); + }); + + it('should transition state to calm day', async () => { + await suggest333Plan.handler({}); + + expect(demoState.metrics.stressLevel).toBe(4); + expect(demoState.metrics.stressPhase).toBe('recovery'); + }); + + it('should include emotional support based on state', async () => { + const result = await suggest333Plan.handler({}); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.emotionalSupport).toBeDefined(); + expect(parsed.emotionalSupport.message).toBeDefined(); + expect(parsed.emotionalSupport.strategy).toBeDefined(); + }); + + it('should include break schedule', async () => { + const result = await suggest333Plan.handler({}); + const text = getTextResult(result); + const parsed = JSON.parse(text); + + expect(parsed.breaks.scheduled.length).toBeGreaterThan(0); + expect(parsed.breaks.rule).toContain('90 minutes'); + }); +}); diff --git a/burnout-agent/src/tools/burnoutTools.ts b/burnout-agent/src/tools/burnoutTools.ts new file mode 100644 index 0000000000..b80add090f --- /dev/null +++ b/burnout-agent/src/tools/burnoutTools.ts @@ -0,0 +1,462 @@ +/** + * Burnout Tracking Tools for GitHub Copilot SDK + * + * These tools allow the agent to read burnout metrics, backlog items, + * and suggest 3-3-3 day structures. + */ + +import { + demoState, + type BacklogItem, + type BurnoutMetrics, + type EmotionalState, + type StressPhase +} from '../fixtures/noisyDay'; + +/** + * Tool result type matching SDK expectations + */ +interface ToolResult { + textResultForLlm: string; + resultType: 'success' | 'failure'; +} + +/** + * Tool definition interface (simplified, SDK-compatible) + */ +export interface Tool { + name: string; + description?: string; + parameters?: Record; + handler: (args: Record) => Promise | ToolResult; +} + +/** + * Get current burnout metrics + */ +export const getBurnoutMetrics: Tool = { + name: 'get_burnout_metrics', + description: 'Get current burnout metrics including stress level, recovery score, and stress phase. Use this to assess the developer\'s current state.', + parameters: { + type: 'object', + properties: { + metric: { + type: 'string', + enum: ['all', 'stress', 'recovery', 'workload', 'phase'], + description: 'Specific metric to retrieve, or "all" for everything', + }, + }, + }, + handler: async (args) => { + const metric = (args.metric as string) || 'all'; + const metrics = demoState.metrics; + + if (metric === 'all') { + return { + textResultForLlm: JSON.stringify({ + stressLevel: metrics.stressLevel, + recoveryScore: metrics.recoveryScore, + hoursWorked: metrics.hoursWorked, + contextSwitches: metrics.contextSwitches, + urgentTickets: metrics.urgentTickets, + fragmentedBlocks: metrics.fragmentedBlocks, + lastBreak: metrics.lastBreak, + stressPhase: metrics.stressPhase, + emotionalState: metrics.emotionalState, + assessment: getAssessment(metrics), + }, null, 2), + resultType: 'success' as const, + }; + } + + switch (metric) { + case 'stress': + return { + textResultForLlm: `Stress level: ${metrics.stressLevel}/10 (${metrics.stressLevel >= 7 ? 'HIGH - intervention needed' : 'manageable'})`, + resultType: 'success' as const, + }; + case 'recovery': + return { + textResultForLlm: `Recovery score: ${metrics.recoveryScore}/10 (${metrics.recoveryScore <= 4 ? 'LOW - burnout risk' : 'healthy'})`, + resultType: 'success' as const, + }; + case 'workload': + return { + textResultForLlm: `Hours this week: ${metrics.hoursWorked}, Context switches today: ${metrics.contextSwitches}, Urgent tickets: ${metrics.urgentTickets}`, + resultType: 'success' as const, + }; + case 'phase': + return { + textResultForLlm: `Current stress phase: ${metrics.stressPhase.toUpperCase()} (Selye's GAS model). Emotional state: ${metrics.emotionalState}`, + resultType: 'success' as const, + }; + default: + return { textResultForLlm: 'Unknown metric', resultType: 'failure' as const }; + } + }, +}; + +/** + * Get backlog items categorized by effort + */ +export const getBacklogItems: Tool = { + name: 'get_backlog_items', + description: 'Fetch backlog items from the task list. Returns items categorized by effort level (small, medium, large) and type.', + parameters: { + type: 'object', + properties: { + filter: { + type: 'string', + enum: ['all', 'deep', 'quickWin', 'maintenance'], + description: 'Filter by category for 3-3-3 planning', + }, + maxItems: { + type: 'number', + description: 'Maximum items to return', + }, + }, + }, + handler: async (args) => { + const filter = (args.filter as string) || 'all'; + const maxItems = (args.maxItems as number) || 20; + const backlog = demoState.backlog.slice(0, maxItems); + + if (filter === 'all') { + return { + textResultForLlm: JSON.stringify({ + totalItems: backlog.length, + items: backlog.map(item => ({ + id: item.id, + title: item.title, + effort: item.effort, + type: item.type, + priority: item.priority, + estimatedMinutes: item.estimatedMinutes, + })), + }, null, 2), + resultType: 'success' as const, + }; + } + + // Categorize for 3-3-3 + const categorized = demoState.categorizeFor333(); + + switch (filter) { + case 'deep': + return { + textResultForLlm: JSON.stringify({ + category: 'Deep Work (3 hours)', + description: 'Complex tasks requiring sustained focus', + items: categorized.deepWork, + }, null, 2), + resultType: 'success' as const, + }; + case 'quickWin': + return { + textResultForLlm: JSON.stringify({ + category: 'Quick Wins (3 tasks)', + description: 'Small tasks that build momentum and confidence', + items: categorized.quickWins, + }, null, 2), + resultType: 'success' as const, + }; + case 'maintenance': + return { + textResultForLlm: JSON.stringify({ + category: 'Maintenance (3 blocks)', + description: 'Admin, emails, meetings, and routine work', + items: categorized.maintenance, + }, null, 2), + resultType: 'success' as const, + }; + default: + return { textResultForLlm: 'Unknown filter', resultType: 'failure' as const }; + } + }, +}; + +/** + * Track a burnout signal + */ +export const trackBurnoutSignal: Tool = { + name: 'track_burnout_signal', + description: 'Record a burnout signal observation. Use this when detecting patterns that indicate burnout risk.', + parameters: { + type: 'object', + properties: { + signalType: { + type: 'string', + enum: ['energy_low', 'context_switching', 'long_session', 'late_work', 'skipped_break', 'overwhelmed', 'frustrated'], + description: 'Type of burnout signal detected', + }, + intensity: { + type: 'number', + minimum: 1, + maximum: 10, + description: 'Intensity of the signal (1-10)', + }, + context: { + type: 'string', + description: 'Additional context about the signal', + }, + }, + required: ['signalType', 'intensity'], + }, + handler: async (args) => { + const signalType = args.signalType as string; + const intensity = args.intensity as number; + const context = args.context as string | undefined; + // Adjust metrics based on signal + const currentStress = demoState.metrics.stressLevel; + const newStress = Math.min(10, currentStress + (intensity > 7 ? 1 : 0)); + + demoState.updateMetrics({ + stressLevel: newStress, + contextSwitches: signalType === 'context_switching' + ? demoState.metrics.contextSwitches + 1 + : demoState.metrics.contextSwitches, + }); + + // Transition phase if needed + if (newStress >= 8) { + demoState.transitionPhase('exhaustion'); + } else if (newStress >= 6) { + demoState.transitionPhase('resistance'); + } + + return { + textResultForLlm: JSON.stringify({ + recorded: true, + signalType, + intensity, + context, + newStressLevel: newStress, + warning: intensity >= 7 ? 'High intensity signal - consider intervention' : null, + }, null, 2), + resultType: 'success' as const, + }; + }, +}; + +/** + * Log energy level + */ +export const logEnergyLevel: Tool = { + name: 'log_energy_level', + description: 'Log the developer\'s current energy level. Use periodically to track patterns.', + parameters: { + type: 'object', + properties: { + level: { + type: 'number', + minimum: 1, + maximum: 10, + description: 'Energy level (1=depleted, 10=fully energized)', + }, + note: { + type: 'string', + description: 'Optional note about current state', + }, + }, + required: ['level'], + }, + handler: async (args) => { + const level = args.level as number; + const note = args.note as string | undefined; + // Inverse relationship: low energy = high stress + const stressAdjustment = level <= 3 ? 2 : level <= 5 ? 1 : 0; + const recoveryAdjustment = level >= 7 ? 1 : level <= 3 ? -1 : 0; + + demoState.updateMetrics({ + stressLevel: Math.min(10, Math.max(1, demoState.metrics.stressLevel + stressAdjustment)), + recoveryScore: Math.min(10, Math.max(1, demoState.metrics.recoveryScore + recoveryAdjustment)), + }); + + // Suggest phase transition + let suggestion = ''; + if (level <= 3) { + suggestion = 'Consider taking a break or switching to a quick win for momentum.'; + demoState.transitionPhase('exhaustion'); + } else if (level >= 7) { + suggestion = 'Good energy! This is a great time for deep work.'; + if (demoState.metrics.stressPhase === 'exhaustion') { + demoState.transitionPhase('recovery'); + } + } + + return { + textResultForLlm: JSON.stringify({ + logged: true, + energyLevel: level, + note, + currentStress: demoState.metrics.stressLevel, + currentRecovery: demoState.metrics.recoveryScore, + suggestion, + }, null, 2), + resultType: 'success' as const, + }; + }, +}; + +/** + * Suggest a 3-3-3 day plan + */ +export const suggest333Plan: Tool = { + name: 'suggest_333_plan', + description: 'Generate a 3-3-3 day plan based on current calendar, backlog, and burnout metrics. This structures the day into 3 Deep Hours, 3 Quick Wins, and 3 Maintenance blocks.', + parameters: { + type: 'object', + properties: { + respectCalendar: { + type: 'boolean', + description: 'Whether to work around existing calendar events', + }, + energyLevel: { + type: 'number', + minimum: 1, + maximum: 10, + description: 'Current energy level to adjust recommendations', + }, + }, + }, + handler: async (args) => { + const respectCalendar = (args.respectCalendar as boolean) ?? true; + const energyLevel = (args.energyLevel as number) ?? 5; + const metrics = demoState.metrics; + const calendar = demoState.calendar; + const categorized = demoState.categorizeFor333(); + + // Find best slots for deep work (need 90+ min blocks) + const freeBlocks = calendar.filter(block => + block.type === 'free' && !block.fragmentary + ); + + // Calculate available deep work time + const deepWorkSlot = freeBlocks.find(block => { + const start = parseInt(block.start.split(':')[0]); + const end = parseInt(block.end.split(':')[0]); + return (end - start) >= 2; // At least 2 hours + }); + + // Build the plan + const plan = { + assessment: { + currentStressLevel: metrics.stressLevel, + currentPhase: metrics.stressPhase, + recommendation: metrics.stressLevel >= 7 + ? 'Start with quick wins to build momentum before deep work' + : 'Good state for deep work - tackle the complex task first', + }, + deepWork: { + hours: 3, + slot: deepWorkSlot ? `${deepWorkSlot.start} - ${deepWorkSlot.end}` : '15:00 - 17:00 (protected block)', + task: categorized.deepWork[0] || { title: 'No deep work tasks available' }, + tips: [ + 'Close Slack and email', + 'Put on focus music', + 'Set a timer for 90 minutes, then take a 15-min break', + ], + }, + quickWins: { + count: 3, + estimatedTime: categorized.quickWins.reduce((sum, t) => sum + t.estimatedMinutes, 0) + ' minutes', + tasks: categorized.quickWins, + tips: [ + 'Do these when energy dips', + 'Celebrate each completion', + 'Use as "warm-up" before deep work', + ], + }, + maintenance: { + blocks: 3, + tasks: categorized.maintenance, + tips: [ + 'Batch emails into one 30-min block', + 'Keep meetings clustered', + 'Use low-energy times for admin', + ], + }, + breaks: { + scheduled: [ + '10:30 - 10:45 (mid-morning)', + '12:00 - 12:30 (lunch - step away from desk)', + '15:00 - 15:15 (afternoon reset)', + ], + rule: 'Every 90 minutes of deep work β†’ 15 minute break', + }, + emotionalSupport: getEmotionalSupport(metrics.emotionalState, energyLevel), + }; + + // Apply the calm day state (for demo purposes) + demoState.applyCalmDay(); + + return { + textResultForLlm: JSON.stringify(plan, null, 2), + resultType: 'success' as const, + }; + }, +}; + +/** + * Get assessment text based on metrics + */ +function getAssessment(metrics: BurnoutMetrics): string { + if (metrics.stressLevel >= 8 && metrics.recoveryScore <= 3) { + return '🚨 CRITICAL: High burnout risk. Recommend immediate intervention - reduce workload, take breaks, and focus on quick wins only.'; + } + if (metrics.stressLevel >= 6) { + return '⚠️ WARNING: Elevated stress detected. Consider restructuring the day with the 3-3-3 pattern.'; + } + if (metrics.stressPhase === 'recovery') { + return 'βœ… HEALTHY: Good recovery state. Maintain current patterns and protect boundaries.'; + } + return 'πŸ“Š MONITORING: Stress levels acceptable but watch for context switching and fragmented time.'; +} + +/** + * Get emotional support message based on Plutchik-inspired model + */ +function getEmotionalSupport(state: EmotionalState, energyLevel: number): { message: string; strategy: string } { + const support: Record = { + anxiety: { + message: "I notice you might be feeling overwhelmed. That's valid - you have a lot on your plate.", + strategy: 'Start with one small, completable task to regain a sense of control.', + }, + frustration: { + message: "Friction is frustrating. Let's identify what's blocking flow and remove it.", + strategy: 'Take a 5-minute break, then tackle the blocker directly or ask for help.', + }, + exhaustion: { + message: "You've been pushing hard. Rest isn't weakness - it's maintenance for sustainable performance.", + strategy: 'Consider ending the day early, or switch to maintenance-only tasks.', + }, + curiosity: { + message: "Great energy for exploration! Channel this into the deep work task.", + strategy: 'Protect a 2-hour block for uninterrupted exploration.', + }, + optimism: { + message: "Positive momentum! This is the time to tackle challenging work.", + strategy: 'Use this energy for the deep work item while it lasts.', + }, + hope: { + message: "You're on the recovery path. Keep protecting your boundaries.", + strategy: 'Maintain the 3-3-3 structure and celebrate progress.', + }, + }; + + return support[state] || support.anxiety; +} + +/** + * All burnout tracking tools bundled for session creation + */ +export const burnoutTools = [ + getBurnoutMetrics, + getBacklogItems, + trackBurnoutSignal, + logEnergyLevel, + suggest333Plan, +]; + +export type { BacklogItem, BurnoutMetrics, EmotionalState, StressPhase }; + diff --git a/burnout-agent/src/ui/statusBar.ts b/burnout-agent/src/ui/statusBar.ts new file mode 100644 index 0000000000..8aa6bdf575 --- /dev/null +++ b/burnout-agent/src/ui/statusBar.ts @@ -0,0 +1,133 @@ +/** + * Burnout Status Bar + * + * A colored status bar item that shows current burnout level. + * - Green (≀4): Healthy + * - Amber (5-7): Warning + * - Red (β‰₯8): Critical + */ + +import * as vscode from 'vscode'; +import { demoState, type StressPhase } from '../fixtures/noisyDay'; + +export class BurnoutStatusBar { + private statusBarItem: vscode.StatusBarItem; + private disposables: vscode.Disposable[] = []; + + constructor() { + // Create status bar item on the right side + this.statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 100 + ); + + this.statusBarItem.command = 'burnout.showWheel'; + this.disposables.push(this.statusBarItem); + } + + /** + * Update the status bar based on current metrics + */ + update(): void { + const metrics = demoState.metrics; + const stressLevel = metrics.stressLevel; + const phase = metrics.stressPhase; + + // Set icon and text + this.statusBarItem.text = `$(flame) ${stressLevel}/10`; + + // Set tooltip with details + this.statusBarItem.tooltip = new vscode.MarkdownString( + `### Burnout Monitor\n\n` + + `**Stress Level:** ${stressLevel}/10\n\n` + + `**Recovery Score:** ${metrics.recoveryScore}/10\n\n` + + `**Phase:** ${this.formatPhase(phase)}\n\n` + + `**Context Switches:** ${metrics.contextSwitches} today\n\n` + + `---\n\n` + + `*Click to open stress wheel*` + ); + + // Set color based on stress level + if (stressLevel >= 8) { + this.statusBarItem.backgroundColor = new vscode.ThemeColor( + 'statusBarItem.errorBackground' + ); + this.statusBarItem.color = new vscode.ThemeColor( + 'statusBarItem.errorForeground' + ); + } else if (stressLevel >= 5) { + this.statusBarItem.backgroundColor = new vscode.ThemeColor( + 'statusBarItem.warningBackground' + ); + this.statusBarItem.color = new vscode.ThemeColor( + 'statusBarItem.warningForeground' + ); + } else { + // Green/healthy - use default colors + this.statusBarItem.backgroundColor = undefined; + this.statusBarItem.color = new vscode.ThemeColor( + 'statusBar.foreground' + ); + } + + this.statusBarItem.show(); + } + + /** + * Format stress phase for display + */ + private formatPhase(phase: StressPhase): string { + const phaseMap: Record = { + alarm: '⚑ Alarm', + resistance: 'πŸ’ͺ Resistance', + exhaustion: '😫 Exhaustion', + recovery: '🌱 Recovery', + }; + return phaseMap[phase] || phase; + } + + /** + * Flash the status bar for attention + */ + flash(): void { + const originalBg = this.statusBarItem.backgroundColor; + + // Flash effect + let count = 0; + const interval = setInterval(() => { + if (count % 2 === 0) { + this.statusBarItem.backgroundColor = new vscode.ThemeColor( + 'statusBarItem.errorBackground' + ); + } else { + this.statusBarItem.backgroundColor = originalBg; + } + count++; + if (count >= 6) { + clearInterval(interval); + this.update(); // Restore normal state + } + }, 300); + } + + /** + * Show the status bar + */ + show(): void { + this.update(); + } + + /** + * Hide the status bar + */ + hide(): void { + this.statusBarItem.hide(); + } + + /** + * Dispose of resources + */ + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/burnout-agent/src/ui/wheelPanel.ts b/burnout-agent/src/ui/wheelPanel.ts new file mode 100644 index 0000000000..ec9ae491db --- /dev/null +++ b/burnout-agent/src/ui/wheelPanel.ts @@ -0,0 +1,485 @@ +/** + * Stress Cycle Wheel Webview Panel + * + * A D3.js-powered visualization of Selye's General Adaptation Syndrome (GAS): + * Alarm β†’ Resistance β†’ Exhaustion β†’ Recovery + * + * Features: + * - 800ms animated transitions between phases + * - Color-coded segments + * - Current phase indicator + * - Emotional state overlay + */ + +import * as vscode from 'vscode'; +import { demoState, type EmotionalState, type StressPhase } from '../fixtures/noisyDay'; + +export class StressWheelPanel { + public static currentPanel: StressWheelPanel | undefined; + private readonly panel: vscode.WebviewPanel; + private readonly extensionUri: vscode.Uri; + private disposables: vscode.Disposable[] = []; + + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { + this.panel = panel; + this.extensionUri = extensionUri; + + // Set initial HTML content + this.update(); + + // Handle panel disposal + this.panel.onDidDispose(() => this.dispose(), null, this.disposables); + + // Handle messages from the webview + this.panel.webview.onDidReceiveMessage( + message => { + switch (message.command) { + case 'requestUpdate': + this.update(); + break; + case 'phaseClicked': + vscode.window.showInformationMessage(`Phase: ${message.phase}`); + break; + } + }, + null, + this.disposables + ); + } + + /** + * Create or show the stress wheel panel + */ + public static createOrShow(extensionUri: vscode.Uri): StressWheelPanel { + const column = vscode.ViewColumn.Beside; + + // If panel already exists, reveal it + if (StressWheelPanel.currentPanel) { + StressWheelPanel.currentPanel.panel.reveal(column); + StressWheelPanel.currentPanel.update(); + return StressWheelPanel.currentPanel; + } + + // Create new panel + const panel = vscode.window.createWebviewPanel( + 'stressWheel', + 'Stress Cycle Wheel', + column, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [extensionUri], + } + ); + + StressWheelPanel.currentPanel = new StressWheelPanel(panel, extensionUri); + return StressWheelPanel.currentPanel; + } + + /** + * Update the webview content + */ + public update(): void { + const metrics = demoState.metrics; + this.panel.webview.html = this.getHtmlContent( + metrics.stressPhase, + metrics.emotionalState, + metrics.stressLevel, + metrics.recoveryScore + ); + } + + /** + * Send data update to webview (for live transitions) + */ + public postUpdate(): void { + const metrics = demoState.metrics; + this.panel.webview.postMessage({ + command: 'updateWheel', + phase: metrics.stressPhase, + emotionalState: metrics.emotionalState, + stressLevel: metrics.stressLevel, + recoveryScore: metrics.recoveryScore, + }); + } + + /** + * Generate the HTML content for the webview + */ + private getHtmlContent( + phase: StressPhase, + emotionalState: EmotionalState, + stressLevel: number, + recoveryScore: number + ): string { + const nonce = this.getNonce(); + + return ` + + + + + + Stress Cycle Wheel + + + +

🧘 Stress Cycle Wheel

+

Selye's General Adaptation Syndrome (GAS)

+ +
+ +
+
${this.formatPhase(phase)}
+
${this.formatEmotion(emotionalState)}
+
+
+ +
+
+
Stress Level
+
${stressLevel}/10
+
+
+
Recovery Score
+
${recoveryScore}/10
+
+
+ +
+
+
+ Alarm - Initial stress response +
+
+
+ Resistance - Coping phase +
+
+
+ Exhaustion - Burnout zone +
+
+
+ Recovery - Restoration +
+
+ + + + +`; + } + + /** + * Format phase for display + */ + private formatPhase(phase: StressPhase): string { + const phaseMap: Record = { + alarm: '⚑ Alarm', + resistance: 'πŸ’ͺ Resistance', + exhaustion: '😫 Exhaustion', + recovery: '🌱 Recovery', + }; + return phaseMap[phase] || phase; + } + + /** + * Format emotion for display + */ + private formatEmotion(emotion: EmotionalState): string { + const emotionMap: Record = { + anxiety: '😰 Anxiety', + frustration: '😀 Frustration', + exhaustion: '😩 Exhaustion', + curiosity: 'πŸ€” Curiosity', + optimism: '😊 Optimism', + hope: '🌟 Hope', + }; + return emotionMap[emotion] || emotion; + } + + /** + * Generate a nonce for CSP + */ + private getNonce(): string { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + } + + /** + * Dispose of resources + */ + public dispose(): void { + StressWheelPanel.currentPanel = undefined; + this.panel.dispose(); + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/burnout-agent/tsconfig.json b/burnout-agent/tsconfig.json new file mode 100644 index 0000000000..de198f2c46 --- /dev/null +++ b/burnout-agent/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022"], + "outDir": "out", + "rootDir": "src", + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "out"] +} diff --git a/burnout-agent/vitest.config.ts b/burnout-agent/vitest.config.ts new file mode 100644 index 0000000000..5dbde795bb --- /dev/null +++ b/burnout-agent/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + environment: 'node', + globals: true, + }, +}); From 27e9f3f7c5115f4ae2fc8496aac87700a59721b9 Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Sat, 24 Jan 2026 18:55:39 +0000 Subject: [PATCH 7/8] fix: remove trailing whitespace in README for better formatting --- burnout-agent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/burnout-agent/README.md b/burnout-agent/README.md index 6b9ad89441..68db0d1952 100644 --- a/burnout-agent/README.md +++ b/burnout-agent/README.md @@ -8,7 +8,7 @@ A VS Code extension that tracks developer burnout and enforces the **3-3-3 day p A sustainable work structure: - **3 Deep Hours**: One block of focused, complex work -- **3 Quick Wins**: Small tasks that build momentum +- **3 Quick Wins**: Small tasks that build momentum - **3 Maintenance Blocks**: Admin, emails, meetings ## Features From f5183bcfc290adb5e9ad80072cd6d0e68ced7dab Mon Sep 17 00:00:00 2001 From: Rory Preddy Date: Sat, 24 Jan 2026 18:58:18 +0000 Subject: [PATCH 8/8] feat: add plan for burnout-tracking agent VS Code extension for DevConf talk --- burnout-agent/plan.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 burnout-agent/plan.md diff --git a/burnout-agent/plan.md b/burnout-agent/plan.md new file mode 100644 index 0000000000..100de8200b --- /dev/null +++ b/burnout-agent/plan.md @@ -0,0 +1,19 @@ +## Plan: Burnout-Tracking Agent for DevConf Talk (Final) + +Build a VS Code extension with pre-seeded demo data, a right-aligned colored status bar, a D3.js stress-cycle wheel with 800ms animations and persistent stateβ€”all driven by Copilot SDK events to showcase the 3-3-3 day transformation live on stage. + +### Steps + +1. **Create demo fixture data** using the in-memory pattern from [nodejs/examples/basic-example.ts](nodejs/examples/basic-example.ts): a `noisyDayScenario` object with pre-seeded metrics (52 hours worked, 12 context switches, 3 urgent tickets, fragmented calendar blocks) that your tools return deterministically. + +2. **Build the SDK session scaffold** in a new `burnout-agent/` extension folder with tools (`get_burnout_metrics`, `get_backlog_items`, `suggest_333_plan`) using `defineTool` from [nodejs/src/defineTool.ts](nodejs/src/defineTool.ts) to classify work into Deep/QuickWin/Maintenance buckets. + +3. **Wire a colored status bar (right-aligned)** using VS Code's `createStatusBarItem` with `StatusBarAlignment.Right`, driven by `tool.execution_complete` and `session.idle` eventsβ€”green (≀4), amber (5-7), red (β‰₯8) burnout levels with a `$(flame)` icon. + +4. **Create a D3.js stress-cycle wheel** in a VS Code webview panel: an SVG donut chart with Selye's GAS stages (*Alarm β†’ Resistance β†’ Exhaustion β†’ Recovery*) as animated segments; use ~800ms D3 transitions for visible-but-snappy stage narration. + +5. **Persist wheel state to `workspaceState`** so resuming VS Code mid-demo doesn't reset visuals; load saved phase on extension activation and restore the wheel position automatically. + +6. **Add a "Reset Noisy Day" command** to the Command Palette (`burnout.resetDemo`) that restores the fixture to chaotic state, clears `workspaceState`, re-renders the wheel in "Exhaustion", and sets status bar to red. + +7. **Orchestrate the live demo flow**: invoke reset β†’ show red status bar + wheel in "Exhaustion" β†’ run agent prompt ("Help me plan my day") β†’ watch it reshape into 3-3-3 blocks β†’ D3 wheel animates toward "Recovery", status bar fades green.