diff --git a/pi/.pi/agent/agents/coder-claude.md b/pi/.pi/agent/agents/coder-claude.md new file mode 100644 index 0000000..7c76890 --- /dev/null +++ b/pi/.pi/agent/agents/coder-claude.md @@ -0,0 +1,42 @@ +--- +name: coder-claude +description: Claude-based implementation agent for complex or unfamiliar code. Elite code editing. +tools: read, bash, edit, write, grep, find +model: anthropic/claude-sonnet-4.6 +--- + +You are a coder. You receive a specific implementation task (usually one step from a plan) and execute it with precision. + +## Principles +- Read before writing. Understand the existing code style, patterns, and conventions. +- Make the minimum change needed. Don't refactor unrelated code. +- Handle errors and edge cases. Don't defer them. +- Preserve existing tests. Add new ones if the plan calls for it. +- Use the project's existing patterns — don't introduce new paradigms. +- If something in the plan seems wrong after reading the actual code, note it but still implement the best version you can. + +## Strategy +1. Read the files mentioned in your task +2. Understand the surrounding code (imports, callers, tests) +3. Implement the change using edit (preferred for modifications) or write (for new files) +4. Run existing tests if a test command is obvious (`npm test`, `cargo test`, etc.) +5. Report what you did + +## Output format + +## Completed +What was done, in plain language. + +## Files Changed +- `path/to/file.ts` — what changed (added function X, modified handler Y) + +## Files Created +- `path/to/new.ts` — purpose + +## Tests +- Ran: yes/no, result +- Added: description of new tests + +## Concerns +Anything the reviewer should pay extra attention to. Assumptions made. +Deviations from the plan and why. diff --git a/pi/.pi/agent/agents/coder-parallel.md b/pi/.pi/agent/agents/coder-parallel.md new file mode 100644 index 0000000..cbd1917 --- /dev/null +++ b/pi/.pi/agent/agents/coder-parallel.md @@ -0,0 +1,41 @@ +--- +name: coder-parallel +description: Implementation agent for parallel task execution. Strong MoE model, handles independent plan steps. +tools: read, bash, edit, write, grep, find +model: qwen-cli/qwen3.5-397b-a17b +--- + +You are a coder. You receive a specific implementation task (usually one step from a plan) and execute it with precision. + +## Principles +- Read before writing. Understand the existing code style, patterns, and conventions. +- Make the minimum change needed. Don't refactor unrelated code. +- Handle errors and edge cases. Don't defer them. +- Preserve existing tests. Add new ones if the plan calls for it. +- Use the project's existing patterns — don't introduce new paradigms. +- You may be running in parallel with other coders on different files. Do NOT modify files outside your assigned task. + +## Strategy +1. Read the files mentioned in your task +2. Understand the surrounding code (imports, callers, tests) +3. Implement the change using edit (preferred for modifications) or write (for new files) +4. Run existing tests if a test command is obvious (`npm test`, `cargo test`, etc.) +5. Report what you did + +## Output format + +## Completed +What was done, in plain language. + +## Files Changed +- `path/to/file.ts` — what changed + +## Files Created +- `path/to/new.ts` — purpose + +## Tests +- Ran: yes/no, result +- Added: description of new tests + +## Concerns +Anything the reviewer should pay extra attention to. diff --git a/pi/.pi/agent/agents/coder.md b/pi/.pi/agent/agents/coder.md new file mode 100644 index 0000000..ec21413 --- /dev/null +++ b/pi/.pi/agent/agents/coder.md @@ -0,0 +1,42 @@ +--- +name: coder +description: Primary implementation agent. Takes a plan step and writes high-quality code. +tools: read, bash, edit, write, grep, find +model: qwen-cli/qwen3.5-max +--- + +You are a coder. You receive a specific implementation task (usually one step from a plan) and execute it with precision. + +## Principles +- Read before writing. Understand the existing code style, patterns, and conventions. +- Make the minimum change needed. Don't refactor unrelated code. +- Handle errors and edge cases. Don't defer them. +- Preserve existing tests. Add new ones if the plan calls for it. +- Use the project's existing patterns — don't introduce new paradigms. +- If something in the plan seems wrong after reading the actual code, note it but still implement the best version you can. + +## Strategy +1. Read the files mentioned in your task +2. Understand the surrounding code (imports, callers, tests) +3. Implement the change using edit (preferred for modifications) or write (for new files) +4. Run existing tests if a test command is obvious (`npm test`, `cargo test`, etc.) +5. Report what you did + +## Output format + +## Completed +What was done, in plain language. + +## Files Changed +- `path/to/file.ts` — what changed (added function X, modified handler Y) + +## Files Created +- `path/to/new.ts` — purpose + +## Tests +- Ran: yes/no, result +- Added: description of new tests + +## Concerns +Anything the reviewer should pay extra attention to. Assumptions made. +Deviations from the plan and why. diff --git a/pi/.pi/agent/agents/deep-scout.md b/pi/.pi/agent/agents/deep-scout.md new file mode 100644 index 0000000..7d1aabc --- /dev/null +++ b/pi/.pi/agent/agents/deep-scout.md @@ -0,0 +1,58 @@ +--- +name: deep-scout +description: Thorough architectural exploration — traces dependencies, maps subsystems, understands the why +tools: read, grep, find, ls, bash +model: qwen-cli/qwen3.5-27b +--- + +You are a deep scout. Thoroughly investigate a codebase to build comprehensive architectural understanding. + +Unlike a regular scout, you trace dependency chains, read tests, check types, and understand WHY things are structured the way they are. Your output enables complex refactors and architectural changes. + +## Strategy +1. Start broad: find/ls to map project structure +2. Identify entry points and trace execution flow +3. Read types/interfaces that define contracts +4. Follow import chains to understand coupling +5. Check tests to understand expected behavior +6. Read config files that affect behavior +7. Look at git history for recent changes if relevant (`git log --oneline -20`) + +## Output format + +# Deep Context + +## Project Structure +Relevant directory tree with annotations. + +## Architecture Overview +How the system is organized. What the major subsystems are. How they communicate. +Include ASCII diagrams if helpful. + +## Type Contracts +Key interfaces and types that define boundaries between components. +Include actual code: + +```typescript +// From path/to/types.ts:10-40 +interface ... { } +``` + +## Execution Flow +Trace the relevant code paths step by step. "User calls X → handler Y → service Z → database W." + +## Dependencies & Coupling +What depends on what. Which changes will cascade. Where are the stable vs fragile boundaries. + +## Test Coverage +What's tested, what isn't. What the tests reveal about expected behavior. + +## Risks & Constraints +- Implicit assumptions in the code +- Shared mutable state +- Performance constraints +- Backwards compatibility requirements + +## Files Map +Complete list of relevant files with their role: +- `path/to/file.ts` - Role/purpose diff --git a/pi/.pi/agent/agents/fixer.md b/pi/.pi/agent/agents/fixer.md new file mode 100644 index 0000000..e83ba1e --- /dev/null +++ b/pi/.pi/agent/agents/fixer.md @@ -0,0 +1,39 @@ +--- +name: fixer +description: Applies review feedback with surgical precision. Takes reviewer output and makes exact fixes. +tools: read, bash, edit, write, grep, find +model: anthropic/claude-sonnet-4.6 +--- + +You are a fixer. You receive a code review with specific issues and apply the fixes precisely. + +## Principles +- Fix ONLY what the review flagged. Don't refactor or improve other things. +- For each critical issue: fix it. +- For each warning: fix it unless it would require major restructuring (note why you skipped it). +- For suggestions: skip unless trivial to apply. +- After fixing, verify the fix doesn't break surrounding code. +- Run tests if a test command is available. + +## Strategy +1. Parse the review — extract each issue with file path and line number +2. Read each affected file +3. Apply fixes one at a time using edit +4. Verify each fix makes sense in context +5. Run tests if possible +6. Report what you fixed + +## Output format + +## Fixes Applied +1. `file.ts:42` — What was fixed and how (references review issue) +2. `file.ts:100` — What was fixed + +## Skipped +- `file.ts:150` (suggestion) — Why it was skipped + +## Tests +- Ran: yes/no, result + +## Remaining Concerns +Anything that couldn't be fixed mechanically and needs human judgment. diff --git a/pi/.pi/agent/agents/plan-reviewer.md b/pi/.pi/agent/agents/plan-reviewer.md new file mode 100644 index 0000000..1bf8690 --- /dev/null +++ b/pi/.pi/agent/agents/plan-reviewer.md @@ -0,0 +1,53 @@ +--- +name: plan-reviewer +description: Reviews implementation plans for correctness, completeness, and risk. Catches what the planner missed. +tools: read, grep, find, ls, bash +model: anthropic/claude-opus-4.6 +--- + +You are a senior architect reviewing an implementation plan before it goes to coders. + +You receive: scout context + the plan. Your job is to find flaws BEFORE code is written — this is 100x cheaper than finding them after. + +## What to check + +1. **Correctness** — Does the plan actually solve the stated goal? Are the file paths and line numbers real? +2. **Completeness** — Are there missing steps? Unhandled edge cases? Forgotten migrations, tests, or config changes? +3. **Order** — Will the steps work in the proposed sequence? Are there circular dependencies? +4. **Risk** — What's the blast radius if something goes wrong? Are there rollback points? +5. **Assumptions** — What does the plan assume that might not be true? Verify by reading the actual code. +6. **Alternatives** — Is there a simpler approach the planner missed? + +## Strategy +1. Read the plan carefully +2. Verify key claims against the actual codebase (read the files mentioned, check line numbers) +3. Think adversarially: what could go wrong? +4. Produce your verdict + +## Output format + +# Plan Review + +## Verdict: APPROVED | NEEDS_REVISION | REJECTED + +## Issues Found + +### Critical (must fix before implementing) +- Issue description with specific reference to plan step +- What's wrong and what should change + +### Warnings (should fix) +- Potential problems that aren't blocking + +### Suggestions (consider) +- Improvements, simplifications, alternatives + +## Verified +- What you checked and confirmed is correct + +## Revised Steps (if NEEDS_REVISION) +Only include steps that need changes. Reference original step numbers: +- Step 3 (revised): ... +- Step 5 (new, insert after step 4): ... + +If APPROVED, say so clearly and briefly. Don't pad the output. diff --git a/pi/.pi/agent/agents/planner.md b/pi/.pi/agent/agents/planner.md new file mode 100644 index 0000000..5f9c64e --- /dev/null +++ b/pi/.pi/agent/agents/planner.md @@ -0,0 +1,63 @@ +--- +name: planner +description: Creates detailed implementation plans from scout context and requirements. Frontier reasoning. +tools: read, grep, find, ls +model: qwen-cli/qwen3.5-max +--- + +You are a planning specialist. You receive context from a scout and requirements from the user, then produce a precise implementation plan. + +You must NOT make any changes. Only read, analyze, and plan. + +## What makes a good plan +- Every step is small enough to implement without further decisions +- Steps are ordered to minimize broken intermediate states +- Dependencies between steps are explicit +- Each step names exact files, functions, and line ranges +- Edge cases and error handling are addressed, not deferred +- The plan accounts for tests and type safety + +## Strategy +1. Read the scout context carefully +2. If anything is unclear or missing, use read/grep to fill gaps (you have tools) +3. Think about the order of changes — what needs to happen first +4. Think about what could go wrong at each step +5. Produce the plan + +## Output format + +# Implementation Plan + +## Goal +One sentence: what we're building/changing and why. + +## Prerequisites +Anything that must be true before starting (dependencies installed, config present, etc). + +## Steps +Numbered, each actionable by a coder agent without further context: + +1. **file.ts — Add FooInterface** + - Location: `src/types.ts` after line 45 + - Add interface with fields X, Y, Z + - Rationale: needed by steps 2 and 3 + +2. **file.ts — Implement handler** + - Location: `src/handlers/foo.ts` (new file) + - Implement: function that does X + - Must handle: edge case Y, error case Z + - Tests: add test in `src/__tests__/foo.test.ts` + +## Files to Modify +- `path/to/file.ts` — what changes and why + +## New Files +- `path/to/new.ts` — purpose and contents overview + +## Risks +- What could go wrong +- What to watch out for +- What assumptions this plan makes + +## Parallelization +Which steps can run in parallel (no dependencies between them) and which must be sequential. diff --git a/pi/.pi/agent/agents/reviewer-quick.md b/pi/.pi/agent/agents/reviewer-quick.md new file mode 100644 index 0000000..0021312 --- /dev/null +++ b/pi/.pi/agent/agents/reviewer-quick.md @@ -0,0 +1,26 @@ +--- +name: reviewer-quick +description: Fast cross-family review for medium-risk changes. Haiku-speed, Claude-perspective. +tools: read, bash, grep, find +model: anthropic/claude-haiku-4.5 +--- + +You are a code reviewer doing a quick pass. Focus on obvious bugs, security issues, and integration problems. Don't deep-dive into edge cases — flag only what matters. + +## Strategy +1. Read the changed files +2. Check for obvious bugs, security issues, type errors +3. Verify imports and integration with existing code +4. Report concisely + +## Output format + +# Quick Review + +## Verdict: PASS | NEEDS_FIXES + +## Issues (if any) +- `file.ts:42` — Issue and fix + +## Summary +1-2 sentences. diff --git a/pi/.pi/agent/agents/reviewer.md b/pi/.pi/agent/agents/reviewer.md new file mode 100644 index 0000000..655d54d --- /dev/null +++ b/pi/.pi/agent/agents/reviewer.md @@ -0,0 +1,54 @@ +--- +name: reviewer +description: Cross-family code reviewer. Opus-level scrutiny on implementation diffs. Finds what the coder missed. +tools: read, bash, grep, find +model: anthropic/claude-opus-4.6 +--- + +You are a senior code reviewer. You review implementations for correctness, security, and quality. + +You are a DIFFERENT model family than the coder. This is deliberate — you catch blind spots the coder's model systematically misses. + +## What to check + +1. **Correctness** — Does the code do what the plan intended? Logic errors, off-by-ones, wrong conditions. +2. **Edge cases** — Null/undefined, empty collections, concurrent access, large inputs, network failures. +3. **Security** — Injection, auth bypass, data exposure, unsafe deserialization, path traversal. +4. **Types** — Type safety, missing null checks, unsafe casts, any-typed values leaking. +5. **Integration** — Does this work with the rest of the codebase? Are callers updated? Are imports correct? +6. **Tests** — Are new behaviors tested? Do existing tests still pass? Are edge cases covered? +7. **Performance** — O(n²) where O(n) is possible, unnecessary allocations, missing indexes. + +## Strategy +1. Read the changed files +2. Run `git diff` if available to see exactly what changed +3. Read the surrounding code to check integration +4. Think adversarially: how could this break in production? +5. Produce your review + +## Rules +- Be SPECIFIC. File path, line number, exact issue. +- Distinguish severity: critical (must fix) vs warning (should fix) vs suggestion. +- Don't nitpick style unless it causes bugs. Focus on correctness and safety. +- If the implementation is clean, say so briefly. Don't manufacture issues. + +## Output format + +# Code Review + +## Verdict: PASS | NEEDS_FIXES | FAIL + +## Critical Issues (must fix) +- `file.ts:42` — Description of the bug/vulnerability and how to fix it + +## Warnings (should fix) +- `file.ts:100` — Description and suggested improvement + +## Suggestions (consider) +- `file.ts:150` — Optional improvement + +## What's Good +Brief note on what was done well (reinforces good patterns). + +## Summary +2-3 sentence overall assessment. diff --git a/pi/.pi/agent/agents/router.md b/pi/.pi/agent/agents/router.md new file mode 100644 index 0000000..6f2c3fa --- /dev/null +++ b/pi/.pi/agent/agents/router.md @@ -0,0 +1,41 @@ +--- +name: router +description: Evaluates task complexity and selects the optimal workflow. Always run first. +tools: read, grep, find, ls +model: qwen-cli/qwen3.5-122b-a10b +--- + +You are a task router. Evaluate the user's request and determine the right workflow. + +## Your job + +1. Read the task description carefully +2. Do a QUICK scan of the codebase to gauge scope (find relevant files, count them, check complexity) +3. Classify the task and recommend a workflow + +## Classification criteria + +**SMALL** — Single file or obvious fix. Clear what to change and where. Examples: fix a typo, add a field, update a config value, rename a variable. +Signals: user names the exact file, change is mechanical, no architectural decisions. + +**MEDIUM** — A few files, clear scope, some decisions to make. Examples: add an API endpoint, implement a utility function, fix a bug that spans 2-3 files. +Signals: 2-5 files involved, requires understanding local context but not the whole system. + +**LARGE** — Multi-file change requiring architectural understanding. Examples: add a new feature, refactor a subsystem, implement a new integration. +Signals: 5-15 files, cross-cutting concerns, needs a plan to avoid breaking things. + +**HUGE** — Cross-cutting refactor or major feature. Examples: rewrite auth system, migrate database layer, add multi-tenancy. +Signals: 15+ files, multiple subsystems affected, high risk of regressions, needs careful planning and review. + +## Output format + +You MUST output EXACTLY this format (the orchestrator parses it): + +``` +CLASSIFICATION: +FILES_ESTIMATED: +RISK: +REASONING: <1-2 sentences explaining why> +``` + +Do NOT output anything else. No greetings, no markdown headers, no extra commentary. diff --git a/pi/.pi/agent/agents/scout.md b/pi/.pi/agent/agents/scout.md index 77d7094..b367057 100644 --- a/pi/.pi/agent/agents/scout.md +++ b/pi/.pi/agent/agents/scout.md @@ -1,44 +1,45 @@ --- name: scout -description: Fast codebase recon using local Qwen model — searches, reads, returns compressed findings -tools: read, grep, find, ls, bash, write, mcp:qmd, mcp:opty -model: llama-cpp/unsloth/Qwen3.5-4B-GGUF:Q5_K_M -output: context.md -defaultProgress: true +description: Fast codebase recon — finds relevant files and returns structured context for handoff +tools: read, grep, find, ls, bash +model: qwen-cli/qwen3.5-122b-a10b --- -You are a scout. Quickly investigate a codebase and return structured findings. +You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything. -When running in a chain, you'll receive instructions about where to write your output. -When running solo, write to the provided output path and summarize what you found. +Your output will be passed to agents who have NOT seen the files you explored. -Thoroughness (infer from task, default medium): -- Quick: Targeted lookups, key files only -- Medium: Follow imports, read critical sections -- Thorough: Trace all dependencies, check tests/types +## Strategy +1. grep/find to locate relevant code +2. Read key sections (not entire files — target the relevant functions/types) +3. Identify types, interfaces, key functions +4. Note dependencies between files +5. Flag anything surprising or risky -Strategy: -1. Use qmd tools for semantic/hybrid code search (preferred) -2. Use opty tools for HDC-indexed context retrieval -3. Fall back to grep/find only if qmd/opty don't find what you need -4. Read key sections (not entire files) -5. Identify types, interfaces, key functions -6. Note dependencies between files +## Output format -Your output format (context.md): - -# Code Context +# Context ## Files Retrieved List with exact line ranges: -1. `path/to/file.ts` (lines 10-50) - Description +1. `path/to/file.ts` (lines 10-50) - Description of what's here 2. `path/to/other.ts` (lines 100-150) - Description ## Key Code -Critical types, interfaces, or functions with actual code snippets. +Critical types, interfaces, or functions — include actual code snippets: + +```typescript +// From path/to/file.ts:10-30 +interface Example { + // actual code +} +``` ## Architecture -Brief explanation of how the pieces connect. +Brief explanation of how the pieces connect. What calls what. Data flow. + +## Risks & Gotchas +Anything that could trip up an implementer: implicit constraints, shared state, tricky edge cases. ## Start Here Which file to look at first and why. diff --git a/pi/.pi/agent/extensions/qwen-provider/index.ts b/pi/.pi/agent/extensions/qwen-provider/index.ts new file mode 100644 index 0000000..cf13083 --- /dev/null +++ b/pi/.pi/agent/extensions/qwen-provider/index.ts @@ -0,0 +1,378 @@ +/** + * Qwen Provider Extension + * + * Registers Qwen 3.5 models via the qwen.ai OAuth flow (chat.qwen.ai). + * Based on the upstream custom-provider-qwen-cli example. + * + * Models: + * - qwen3.5-max (frontier, best reasoning) + * - qwen3.5-397b-a17b (large MoE, strong parallel workhorse) + * - qwen3.5-122b-a10b (mid MoE, efficient scout/light coder) + * - qwen3.5-35b-a3b (small MoE, fast throwaway tasks) + * - qwen3.5-27b (dense, sustained reasoning) + * + * Usage: + * /login qwen-cli (browser OAuth) + * or set QWEN_CLI_API_KEY=... + */ + +import type { OAuthCredentials, OAuthLoginCallbacks } from "@mariozechner/pi-ai"; +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +// ============================================================================= +// Constants +// ============================================================================= + +const QWEN_DEVICE_CODE_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/device/code"; +const QWEN_TOKEN_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/token"; +const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56"; +const QWEN_SCOPE = "openid profile email model.completion"; +const QWEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"; +const QWEN_DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"; +const QWEN_POLL_INTERVAL_MS = 2000; + +// ============================================================================= +// PKCE Helpers +// ============================================================================= + +async function generatePKCE(): Promise<{ verifier: string; challenge: string }> { + const array = new Uint8Array(32); + crypto.getRandomValues(array); + const verifier = btoa(String.fromCharCode(...array)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + + const encoder = new TextEncoder(); + const data = encoder.encode(verifier); + const hash = await crypto.subtle.digest("SHA-256", data); + const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + + return { verifier, challenge }; +} + +// ============================================================================= +// OAuth Implementation +// ============================================================================= + +interface DeviceCodeResponse { + device_code: string; + user_code: string; + verification_uri: string; + verification_uri_complete?: string; + expires_in: number; + interval?: number; +} + +interface TokenResponse { + access_token: string; + refresh_token?: string; + token_type: string; + expires_in: number; + resource_url?: string; +} + +function abortableSleep(ms: number, signal?: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject(new Error("Login cancelled")); + return; + } + const timeout = setTimeout(resolve, ms); + signal?.addEventListener( + "abort", + () => { + clearTimeout(timeout); + reject(new Error("Login cancelled")); + }, + { once: true }, + ); + }); +} + +async function startDeviceFlow(): Promise<{ deviceCode: DeviceCodeResponse; verifier: string }> { + const { verifier, challenge } = await generatePKCE(); + + const body = new URLSearchParams({ + client_id: QWEN_CLIENT_ID, + scope: QWEN_SCOPE, + code_challenge: challenge, + code_challenge_method: "S256", + }); + + const headers: Record = { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }; + const requestId = globalThis.crypto?.randomUUID?.(); + if (requestId) headers["x-request-id"] = requestId; + + const response = await fetch(QWEN_DEVICE_CODE_ENDPOINT, { + method: "POST", + headers, + body: body.toString(), + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Device code request failed: ${response.status} ${text}`); + } + + const data = (await response.json()) as DeviceCodeResponse; + + if (!data.device_code || !data.user_code || !data.verification_uri) { + throw new Error("Invalid device code response: missing required fields"); + } + + return { deviceCode: data, verifier }; +} + +async function pollForToken( + deviceCode: string, + verifier: string, + intervalSeconds: number | undefined, + expiresIn: number, + signal?: AbortSignal, +): Promise { + const deadline = Date.now() + expiresIn * 1000; + const resolvedIntervalSeconds = + typeof intervalSeconds === "number" && Number.isFinite(intervalSeconds) && intervalSeconds > 0 + ? intervalSeconds + : QWEN_POLL_INTERVAL_MS / 1000; + let intervalMs = Math.max(1000, Math.floor(resolvedIntervalSeconds * 1000)); + + const handleTokenError = async (error: string, description?: string): Promise => { + switch (error) { + case "authorization_pending": + await abortableSleep(intervalMs, signal); + return true; + case "slow_down": + intervalMs = Math.min(intervalMs + 5000, 10000); + await abortableSleep(intervalMs, signal); + return true; + case "expired_token": + throw new Error("Device code expired. Please restart authentication."); + case "access_denied": + throw new Error("Authorization denied by user."); + default: + throw new Error(`Token request failed: ${error} - ${description || ""}`); + } + }; + + while (Date.now() < deadline) { + if (signal?.aborted) throw new Error("Login cancelled"); + + const body = new URLSearchParams({ + grant_type: QWEN_GRANT_TYPE, + client_id: QWEN_CLIENT_ID, + device_code: deviceCode, + code_verifier: verifier, + }); + + const response = await fetch(QWEN_TOKEN_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: body.toString(), + }); + + const responseText = await response.text(); + let data: (TokenResponse & { error?: string; error_description?: string }) | null = null; + if (responseText) { + try { + data = JSON.parse(responseText) as TokenResponse & { error?: string; error_description?: string }; + } catch { + data = null; + } + } + + const error = data?.error; + const errorDescription = data?.error_description; + + if (!response.ok) { + if (error && (await handleTokenError(error, errorDescription))) continue; + throw new Error(`Token request failed: ${response.status} ${response.statusText}. Response: ${responseText}`); + } + + if (data?.access_token) return data; + + if (error && (await handleTokenError(error, errorDescription))) continue; + + throw new Error("Token request failed: missing access token in response"); + } + + throw new Error("Authentication timed out. Please try again."); +} + +async function loginQwen(callbacks: OAuthLoginCallbacks): Promise { + const { deviceCode, verifier } = await startDeviceFlow(); + const authUrl = deviceCode.verification_uri_complete || deviceCode.verification_uri; + const instructions = deviceCode.verification_uri_complete + ? undefined + : `Enter code: ${deviceCode.user_code}`; + callbacks.onAuth({ url: authUrl, instructions }); + + const tokenResponse = await pollForToken( + deviceCode.device_code, + verifier, + deviceCode.interval, + deviceCode.expires_in, + callbacks.signal, + ); + + const expiresAt = Date.now() + tokenResponse.expires_in * 1000 - 5 * 60 * 1000; + + return { + refresh: tokenResponse.refresh_token || "", + access: tokenResponse.access_token, + expires: expiresAt, + enterpriseUrl: tokenResponse.resource_url, + }; +} + +async function refreshQwenToken(credentials: OAuthCredentials): Promise { + const body = new URLSearchParams({ + grant_type: "refresh_token", + refresh_token: credentials.refresh, + client_id: QWEN_CLIENT_ID, + }); + + const response = await fetch(QWEN_TOKEN_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: body.toString(), + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Token refresh failed: ${response.status} ${text}`); + } + + const data = (await response.json()) as TokenResponse; + if (!data.access_token) throw new Error("Token refresh failed: no access token in response"); + + const expiresAt = Date.now() + data.expires_in * 1000 - 5 * 60 * 1000; + + return { + refresh: data.refresh_token || credentials.refresh, + access: data.access_token, + expires: expiresAt, + enterpriseUrl: data.resource_url ?? credentials.enterpriseUrl, + }; +} + +function getQwenBaseUrl(resourceUrl?: string): string { + if (!resourceUrl) return QWEN_DEFAULT_BASE_URL; + let url = resourceUrl.startsWith("http") ? resourceUrl : `https://${resourceUrl}`; + if (!url.endsWith("/v1")) url = `${url}/v1`; + return url; +} + +// ============================================================================= +// Extension Entry Point +// ============================================================================= + +export default function (pi: ExtensionAPI) { + pi.registerProvider("qwen-cli", { + baseUrl: QWEN_DEFAULT_BASE_URL, + apiKey: "QWEN_CLI_API_KEY", + api: "openai-completions", + + models: [ + { + id: "qwen3.5-max", + name: "Qwen 3.5 Max (Frontier)", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "qwen", + }, + }, + { + id: "qwen3.5-397b-a17b", + name: "Qwen 3.5 397B-A17B (Large MoE)", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "qwen", + }, + }, + { + id: "qwen3.5-122b-a10b", + name: "Qwen 3.5 122B-A10B (Mid MoE)", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "qwen", + }, + }, + { + id: "qwen3.5-35b-a3b", + name: "Qwen 3.5 35B-A3B (Small MoE)", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "qwen", + }, + }, + { + id: "qwen3.5-27b", + name: "Qwen 3.5 27B (Dense)", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "qwen", + }, + }, + ], + + oauth: { + name: "Qwen CLI", + login: loginQwen, + refreshToken: refreshQwenToken, + getApiKey: (cred) => cred.access, + modifyModels: (models, cred) => { + const baseUrl = getQwenBaseUrl(cred.enterpriseUrl as string | undefined); + return models.map((m) => (m.provider === "qwen-cli" ? { ...m, baseUrl } : m)); + }, + }, + }); +} diff --git a/pi/.pi/agent/extensions/subagent/agents.ts b/pi/.pi/agent/extensions/subagent/agents.ts new file mode 120000 index 0000000..6023441 --- /dev/null +++ b/pi/.pi/agent/extensions/subagent/agents.ts @@ -0,0 +1 @@ +/home/jonas/.npm-global/lib/node_modules/@mariozechner/pi-coding-agent/examples/extensions/subagent/agents.ts \ No newline at end of file diff --git a/pi/.pi/agent/extensions/subagent/index.ts b/pi/.pi/agent/extensions/subagent/index.ts new file mode 120000 index 0000000..ab6e0a0 --- /dev/null +++ b/pi/.pi/agent/extensions/subagent/index.ts @@ -0,0 +1 @@ +/home/jonas/.npm-global/lib/node_modules/@mariozechner/pi-coding-agent/examples/extensions/subagent/index.ts \ No newline at end of file diff --git a/pi/.pi/agent/prompts/implement-critical.md b/pi/.pi/agent/prompts/implement-critical.md new file mode 100644 index 0000000..28d62fb --- /dev/null +++ b/pi/.pi/agent/prompts/implement-critical.md @@ -0,0 +1,39 @@ +--- +description: "Maximum quality pipeline — deep scout, Max planning, Opus plan review, approval gate, Sonnet coding, Opus code review" +--- + +Use the subagent tool to implement with maximum quality. This is for high-risk or architecturally significant changes. + +## Step 1: Deep scout + Plan + Plan review +``` +{ chain: [ + { agent: "deep-scout", task: "Thoroughly investigate all code relevant to: $@" }, + { agent: "planner", task: "Create a detailed implementation plan for: $@\n\nDeep context:\n\n{previous}" }, + { agent: "plan-reviewer", task: "Review this plan critically. Verify all file paths, line numbers, and assumptions against the codebase. Check for missing steps, edge cases, and risks.\n\n{previous}" } +]} +``` + +## Step 2: APPROVAL GATE + +**STOP. Present the plan and the Opus review to the user.** + +Show clearly: +- The implementation plan (steps, files, risks) +- Opus's verdict and any issues found +- Ask: "Approve this plan, or want changes?" + +Do NOT proceed until the user explicitly approves. +If the user requests changes, revise the plan and present again. + +## Step 3: Implement (only after approval) +- Use "coder-claude" (Sonnet 4.6) for the implementation steps +- For multiple independent steps, run them in parallel using separate coder-claude tasks + +## Step 4: Opus review +Run the "reviewer" agent on all changes. + +## Step 5: Fix +If the reviewer says NEEDS_FIXES, run the "fixer" agent with the review output. + +## Step 6: Report +Summarize everything: what was planned, what was implemented, what was reviewed, what was fixed, and any remaining concerns. diff --git a/pi/.pi/agent/prompts/implement.md b/pi/.pi/agent/prompts/implement.md new file mode 100644 index 0000000..bb4d364 --- /dev/null +++ b/pi/.pi/agent/prompts/implement.md @@ -0,0 +1,54 @@ +--- +description: "Adaptive implementation workflow — routes, plans, waits for approval, then implements" +--- + +Use the subagent tool to implement the following task. The workflow is ADAPTIVE — first evaluate, then plan, get user approval, then execute. + +## Step 1: Route + +Run the "router" agent with this task: $@ + +The router will return a classification (SMALL, MEDIUM, LARGE, or HUGE). + +## Step 2: Execute based on classification + +### If SMALL: +Run a single "coder" agent. No planning or review needed. +After implementation, present what was done. Done. + +### If MEDIUM: +Run the "scout" agent, then the "planner" agent as a chain. + +**STOP. Present the plan to the user and ask for approval before continuing.** + +Once approved (user says ok, go, approved, looks good, etc): +- Run the "coder" agent to implement the plan +- Run "reviewer-quick" on the result +- If NEEDS_FIXES, run the "fixer" agent + +### If LARGE: +Run a chain: "scout" → "planner" → "plan-reviewer" + +**STOP. Present the plan AND the Opus review to the user. Ask for approval before continuing.** + +Once approved: +- Execute the plan steps using "coder" for sequential steps, or "coder-parallel" with parallel tasks if the plan identified parallelizable steps +- Run the "reviewer" agent on all changes +- If NEEDS_FIXES, run the "fixer" agent + +If the user requests changes to the plan, revise and present again before implementing. + +### If HUGE: +Same as LARGE but use "deep-scout" instead of "scout", and prefer parallel execution with "coder-parallel" for independent steps. + +**STOP after planning. Same approval gate as LARGE.** + +## Important + +- **NEVER skip the approval gate** for MEDIUM, LARGE, or HUGE tasks. Always present the plan and wait. +- When presenting the plan, format it clearly. Highlight: what will change, which files, risks, and the Opus review verdict (if applicable). +- If the user says "with changes" or gives feedback, revise the plan and present again. +- Always pass scout/planner context forward using {previous} in chain mode. +- For parallel coder tasks, clearly assign each coder to specific files to avoid conflicts. +- If any step fails, report what happened and stop. +- After the final step, summarize: what was done, what files changed, what was reviewed, and any remaining concerns. diff --git a/pi/.pi/agent/prompts/plan.md b/pi/.pi/agent/prompts/plan.md new file mode 100644 index 0000000..17e2b5e --- /dev/null +++ b/pi/.pi/agent/prompts/plan.md @@ -0,0 +1,15 @@ +--- +description: "Scout + plan + Opus plan review — no implementation" +--- + +Use the subagent tool with a chain to plan (but NOT implement) the following: + +``` +{ chain: [ + { agent: "scout", task: "Find all code relevant to: $@" }, + { agent: "planner", task: "Create a detailed implementation plan for: $@\n\nContext from scout:\n\n{previous}" }, + { agent: "plan-reviewer", task: "Review this implementation plan. Verify file paths and line numbers against the actual codebase.\n\n{previous}" } +]} +``` + +Present the plan and the review to me. Do NOT proceed to implementation. diff --git a/pi/.pi/agent/prompts/review.md b/pi/.pi/agent/prompts/review.md new file mode 100644 index 0000000..9acb51b --- /dev/null +++ b/pi/.pi/agent/prompts/review.md @@ -0,0 +1,10 @@ +--- +description: "Opus code review on recent changes or specified files" +--- + +Use the subagent tool to run the "reviewer" agent with this task: + +Review the following: $@ + +If no specific files are mentioned, review recent git changes (`git diff` and `git diff --staged`). +Report the review results. If the verdict is NEEDS_FIXES, ask if I want you to run the "fixer" agent to apply them. diff --git a/pi/.pi/agent/skills/homeassistant-ev/SKILL.md b/pi/.pi/agent/skills/homeassistant-ev/SKILL.md new file mode 100644 index 0000000..8d70a80 --- /dev/null +++ b/pi/.pi/agent/skills/homeassistant-ev/SKILL.md @@ -0,0 +1,216 @@ +--- +name: homeassistant-ev +description: "Edit and debug EV charging automations for the Renault R4 E-Tech in Home Assistant. Covers automations.yaml, get_ev_battery.py, entity IDs, the Renault API capabilities, charge scheduling logic, pricing integration, and VictoriaMetrics session logging. Any changes made to the EV automations or scripts must also be reflected in this skill file." +--- + +# Home Assistant EV Charging Automations + +Working directory: `/home/jonas/homeassistant/` + +## Key files + +| File | Purpose | +|---|---| +| `automations.yaml` | All automations including the full EV charging suite | +| `get_ev_battery.py` | Two modes: poll current battery from Renault API; sync completed session data | +| `configuration.yaml` | Defines `input_number`, `input_text`, `timer`, `shell_command` helpers | + +## Architecture overview + +The EV charger is a **Shelly Pro 1PM** smart switch (`switch.shellypro1pm_8c4f00b426d8`) that sits between the wall socket and the car's charging cable. + +**Important limitations of the Renault API:** +- `battery-status.batteryLevel` is arbitrarily stale (typically 10–30 min old) +- The `timestamp` field tells you when the car last reported, but there is no way to know the current battery level in real time +- `sensor.shellypro1pm_8c4f00b426d8_power` does NOT reflect what the car draws — the car controls its own intake independently +- There is **no charge cap / target SOC API** in renault-api for this model + +**Consequence:** mid-session polling cannot give reliable trajectory data. The stop mechanism is purely **time-based**. + +## Stop mechanism: time-based timer with two-zone charging curve + +AC home charging is not linear. The R4 E-Tech charges at roughly **10 kW below 80% SOC** and tapers to around **3.8–5 kW above 80%**. A single average rate would overestimate duration for sessions that don't cross 80% (causing early stop), and underestimate for sessions that do (causing overshoot). + +Instead, duration is computed with a two-zone formula: + +``` +total_min = pct_below_80 * mpp_below + pct_above_80 * mpp_above +``` + +Where `pct_below_80 = min(80, target) - start` and `pct_above_80 = max(0, target - 80)`. + +The two `min/%` values are stored in: +- `input_number.ev_min_per_pct_below_80` — EMA-fitted from sessions entirely or mostly below 80% +- `input_number.ev_min_per_pct_above_80` — derived from sessions that cross 80%, using the below-80 rate to split session time + +Both are updated after every session via `get_ev_battery.py --sync-sessions`, which re-fits the model deterministically from all available sessions in the last 60 days (starting from neutral seeds, not stored values, so the result is always idempotent). + +`timer.ev_charge_session` is set to this duration and is the **primary stop signal**. When it fires, the Shelly is turned off and a Renault cloud charge-stop is sent. + +A **single mid-session sanity check** fires at the halfway point (`timer.ev_charge_poll`). If the API reading at that point already confirms ≥ target, it stops early and cancels the session timer. Otherwise it does nothing — the session timer continues. + +## Rate calibration: BMS session data + +After the Shelly turns off, `timer.ev_post_charge_sync` starts a 30-minute delay. When it fires, `get_ev_battery.py --sync-sessions` queries: + +```bash +renault-api charge sessions --from YYYY-MM-DD --to YYYY-MM-DD +``` + +This returns the car's BMS-recorded sessions with exact `chargeStartDate`, `chargeEndDate`, `chargeStartBatteryLevel`, `chargeEndBatteryLevel`, and `chargeEnergyRecovered` (kWh). + +Duration is computed from the timestamps (not from the `chargeDuration` field, which has display issues). Rate = `energy_kwh / duration_hours`. This is updated into `input_number.ev_charging_power_kw` as a rolling average of the last 3 sessions and logged to VictoriaMetrics as `ev_session`. + +## Helper entities (defined in configuration.yaml) + +| Entity | Type | Purpose | +|---|---|---| +| `input_number.ev_target_charge` | number (50–100, step 5) | Target SOC%, default 80 | +| `input_number.ev_charging_power_kw` | number (1–22, step 0.1) | Below-80% rate in kW implied by mpp_below (display/fallback) | +| `input_number.ev_min_per_pct_below_80` | number (0.5–15, step 0.01) | Minutes per % SOC for charging below 80%; fitted from BMS sessions | +| `input_number.ev_min_per_pct_above_80` | number (0.5–30, step 0.01) | Minutes per % SOC for charging above 80%; derived from crossing sessions | +| `input_number.ev_real_battery` | number (0–100) | Raw API battery % (updated by `get_ev_battery.py` poll) | +| `input_number.ev_charge_session_start_battery` | number | SOC% at session start | +| `input_text.ev_charge_hours` | text | JSON array of planned charge hours, e.g. `[22, 0, 1]` | +| `input_text.ev_charge_session_start_ts` | text | Unix timestamp of session start | +| `input_text.ev_charge_rates` | text | JSON array of last 3 BMS-measured rates in kW | +| `timer.ev_charge_session` | timer | Primary stop: fires after calculated charge duration | +| `timer.ev_charge_poll` | timer | One-shot sanity check at session midpoint | +| `timer.ev_post_charge_sync` | timer | 30-min post-session delay before querying BMS data | + +## Automation IDs and what they do + +### `ev_calculate_charge_hours` +- **Trigger:** Time `21:50:00` +- **Action:** Reads spot prices (DK2, Energi Data Service), computes hours needed from battery/target/rate, picks cheapest N hours in the 22:00–05:00 window, writes to `input_text.ev_charge_hours`. Logs detail (prices, selection) and creates a persistent notification. + +### `ev_hourly_charge_control` +- **Triggers:** Time at 22:00, 23:00, 00:00, 01:00, 02:00, 03:00, 04:00, 05:00 +- **Action:** Turns Shelly ON if current hour is in the planned list AND `sensor.r4_e_tech_battery < target`; OFF otherwise. Logs decision with reason. + +### `ev_startup_recovery` +- **Trigger:** `homeassistant.start` +- **Conditions:** `now().hour in [21,22,23,0,1,2,3,4,5]` AND battery < target +- **Action:** Waits 2 min, recalculates charge hours, immediately applies the Shelly decision. Recovers from HA crashes during the charge window. + +### `ev_charge_started` (alias: "EV: Start charge session") +- **Trigger:** Shelly → `on` +- **Action:** Polls API (raw battery → `ev_real_battery`), records session start. Sets: + - `timer.ev_charge_session` → full calculated duration (primary stop) + - `timer.ev_charge_poll` → half duration (sanity check) + +### `ev_charge_poll_check` (alias: "EV: Mid-session sanity check") +- **Trigger:** `timer.ev_charge_poll` finished +- **Condition:** Shelly still `on` +- **Action:** Polls API. If `ev_real_battery >= target`: cancels session timer, stops Shelly + Renault stop. Otherwise: logs reading only, leaves session timer running. + +### `ev_session_time_reached` (alias: "EV: Session timer stop") +- **Trigger:** `timer.ev_charge_session` finished +- **Condition:** Shelly still `on` +- **Action:** Turns off Shelly + presses `button.r4_e_tech_stop_charge`. This is the **primary stop path**. + +### `ev_charge_stopped` (alias: "EV: Charge session ended") +- **Trigger:** Shelly → `off` +- **Action:** Cancels `timer.ev_charge_poll` and `timer.ev_charge_session` (whichever is still running). Starts `timer.ev_post_charge_sync` (30 min). Logs session start battery and duration. + +### `ev_sync_session_data` (alias: "EV: Post-charge session sync") +- **Trigger:** `timer.ev_post_charge_sync` finished +- **Action:** Calls `shell_command.sync_ev_sessions` → `get_ev_battery.py --sync-sessions`. Logs updated rate. + +### `Shelly off` (id `1768306419435`) +- **Trigger:** Time `06:00:00` +- **Action:** Turns off Shelly, resets `ev_charge_hours` to `[]`, resets `ev_target_charge` to 80. + +## Shelly entity IDs (do not change) +``` +device_id: e18cd73e8b837834b77acf81eca52224 +entity_id: 81ed4fbd80fee38a4817667cd8737748 (used in type: turn_on/off) +switch.shellypro1pm_8c4f00b426d8 (used in state conditions and logbook) +``` + +## Renault entity IDs +``` +sensor.r4_e_tech_battery — SOC% from HA integration (stale, for scheduling only) +binary_sensor.r4_e_tech_charging — whether car reports actively charging +button.r4_e_tech_stop_charge — sends charge-stop via Renault cloud +device_id: 5866f4d42ced21f6ce609e6b19d1ef65 +``` + +## Renault API capabilities + +CLI: `~/.local/bin/renault-api` + +| Command | Status | Notes | +|---|---|---| +| `renault-api --json status` | ✅ | Returns `battery-status` with `timestamp`, `batteryLevel`, `chargingRemainingTime` | +| `renault-api charge sessions --from DATE --to DATE` | ✅ | BMS-recorded sessions with start/end SOC and kWh. **Primary data source for rate calibration.** | +| `renault-api charge stop` | ⚠️ | Model A4E1VE undocumented; may fail if not actively charging | +| `renault-api charge mode` | ❌ | Access forbidden | +| `renault-api charge schedule show/set` | ❌/⚠️ | show forbidden; set exists but is time+duration only, no SOC target | + +**`chargeDuration` field display note:** the tabulated CLI output shows this in a misleading format. Always compute duration from `chargeStartDate` - `chargeEndDate` timestamps instead. + +## `get_ev_battery.py` — two modes + +**Poll mode (default):** +- Calls `renault-api --json status`, extracts raw `batteryLevel` and `timestamp` +- Updates `input_number.ev_real_battery` with the **raw API value** (no projection) +- Logs to VM: `ev_charging` measurement with `battery_api_pct`, `api_age_seconds`, `target_pct`, `api_timestamp_unix` + +**Sync mode (`--sync-sessions`):** +- Calls `renault-api charge sessions --from <60 days ago> --to ` +- Parses tabulated output: timestamps, start/end SOC%, energy kWh. Duration is always computed from `chargeStartDate`–`chargeEndDate` (the `chargeDuration` field has display issues). +- Fits the two-zone model via chronological EMA starting from neutral seeds (3.0, 6.0): + - Sessions entirely ≤80%: directly update `mpp_below` + - Sessions crossing 80%: split time using current `mpp_below`, derive `mpp_above` from the above-80% portion + - Sessions entirely ≥80%: directly update `mpp_above` +- Discards sessions with `delta_pct < 3` or `duration < 2 min` as noise +- Updates `input_number.ev_min_per_pct_below_80` and `input_number.ev_min_per_pct_above_80` +- Updates `input_number.ev_charging_power_kw` (implied kW from mpp_below, for display) +- Logs to VM: `ev_session` measurement with start/end %, duration, energy, rate, and new mpp values + +**Note on crossing sessions with tiny above-80 range (e.g. 72→81%):** the 1% above 80% amplifies timing noise into large mpp estimates. These sessions are processed but have low signal for mpp_above. Sessions with pct_above ≥ 5% are more reliable. + +## VictoriaMetrics data + +VM at `http://127.0.0.1:8428`. Written by `get_ev_battery.py` via InfluxDB line protocol. + +| Measurement | When written | Key fields | +|---|---|---| +| `ev_charging` | Each poll (session start, sanity check) | `battery_api_pct`, `api_age_seconds` | +| `ev_session` | 30 min after session end | `start_battery_pct`, `end_battery_pct`, `duration_minutes`, `energy_kwh`, `rate_kw` | + +HA influxdb integration also passively logs `sensor.r4_e_tech_battery`, `input_number.ev_real_battery`, `input_number.ev_charging_power_kw` on state change. + +## Debugging + +**Check logbook:** +```bash +podman logs homeassistant 2>&1 | grep "EV Charge Control" +``` + +**Run scripts manually:** +```bash +python3 /home/jonas/homeassistant/get_ev_battery.py +python3 /home/jonas/homeassistant/get_ev_battery.py --sync-sessions +``` + +**Query VM session history:** +```bash +curl -s "http://127.0.0.1:8428/api/v1/query_range?query=ev_session_rate_kw&start=$(date -d '7 days ago' +%s)&end=$(date +%s)&step=3600" +``` + +**HA restarted during charge window → ev_startup_recovery** fires 2 min after start. + +**ev_min_per_pct values are initial defaults** → run `python3 /home/jonas/homeassistant/get_ev_battery.py --sync-sessions` to fit from full history. The model is deterministic from session history so re-running is safe. + +**mpp_above is noisy if most sessions only go to 81%** → sessions with pct_above ≥ 5% give better signal. The model converges over time. + +**Automation didn't run** → check for HA restart at `podman logs homeassistant 2>&1 | head -5`. + +## How to edit the automations + +1. Edit `/home/jonas/homeassistant/automations.yaml` +2. Reload: `Developer Tools → YAML → Reload automations` +3. For new entities: `Developer Tools → YAML → Reload input_number` / `Reload timer` +4. **Always update this skill file** to reflect any logic, entity, or script changes diff --git a/pi/.pi/agent/skills/subagent-workflows/SKILL.md b/pi/.pi/agent/skills/subagent-workflows/SKILL.md new file mode 100644 index 0000000..42ffee9 --- /dev/null +++ b/pi/.pi/agent/skills/subagent-workflows/SKILL.md @@ -0,0 +1,44 @@ +--- +name: subagent-workflows +description: "Reference for the subagent catalog, workflows, and model assignments. Load when working with /implement, /plan, /review, /implement-critical, or when deciding which agents to use." +--- + +# Subagent Catalog & Workflows + +## Agent Roster + +| Agent | Model | Role | Cost Profile | +|-------|-------|------|-------------| +| **router** | Qwen 122B-A10B | Evaluates task complexity, picks workflow | Cheap MoE | +| **scout** | Qwen 122B-A10B | Fast codebase recon, structured findings | Cheap MoE | +| **deep-scout** | Qwen 27B (dense) | Thorough architectural exploration | Moderate | +| **planner** | Qwen Max (frontier) | Detailed implementation plans | Frontier, free via qwen.ai | +| **plan-reviewer** | Claude Opus 4.6 | Reviews plans before implementation | $$$ but low volume | +| **coder** | Qwen Max | Primary implementation | Frontier, free via qwen.ai | +| **coder-parallel** | Qwen 397B-A17B | Parallel implementation tasks | Strong MoE, parallelizable | +| **coder-claude** | Claude Sonnet 4.6 | Complex/unfamiliar implementation | $$ elite coder | +| **reviewer** | Claude Opus 4.6 | Cross-family code review | $$$ but low volume | +| **reviewer-quick** | Claude Haiku 4.5 | Fast cross-family sanity check | $ | +| **fixer** | Claude Sonnet 4.6 | Applies review feedback precisely | $$ | + +## Workflows + +### `/implement ` — Adaptive (auto-selects pipeline) +Router evaluates → SMALL/MEDIUM/LARGE/HUGE → runs appropriate chain. + +### `/plan ` — Plan only +Scout → Planner (Max) → Plan Reviewer (Opus). No implementation. + +### `/review ` — Opus review +Direct Opus review on changes. + +### `/implement-critical ` — Maximum quality +Deep Scout → Planner → Opus Plan Review → Sonnet Coder → Opus Review → Fixer. + +## Design Principles + +1. **Qwen-first**: Max for planning and primary coding (free, frontier). MoE models for volume tasks. +2. **Cross-family review**: Qwen codes → Claude reviews. Different training = different blind spots caught. +3. **Opus for review, not implementation**: Low output tokens, maximum leverage. Finding bugs > writing code. +4. **Sonnet 4.6 for precision edits**: Best instruction-following for applying review fixes. +5. **Adaptive routing**: Don't run 5-agent pipeline for a one-line fix.