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..68db0d1952
--- /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/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.
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
+
+
+
+
+
+
+
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,
+ },
+});
diff --git a/nodejs/examples/README.md b/nodejs/examples/README.md
new file mode 100644
index 0000000000..3cb1342cb3
--- /dev/null
+++ b/nodejs/examples/README.md
@@ -0,0 +1,73 @@
+# 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-opus-4.5` - Responds to the joke (premium model)
+
+## 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-opus-4.5
+
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+š gpt-4.1: Knock knock!
+š§ claude-opus-4.5: Who's there?
+š gpt-4.1: Olive
+š§ claude-opus-4.5: Olive who?
+š gpt-4.1: Olive you and I miss you!
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+
+ā±ļø Response Times:
+
+š 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: 7035ms (3 turns)
+š§ claude-opus-4.5 total: 5191ms (2 turns)
+
+š claude-opus-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-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.
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/nodejs/examples/knock-knock.ts b/nodejs/examples/knock-knock.ts
new file mode 100644
index 0000000000..0d7a28f8b7
--- /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-opus-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();
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())