🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s

A professional workspace for AI coding agents with:
- Real-time streaming chat with SSE
- Session management (fork, clone, tree navigation)
- MCP tools browser & metadata panel
- Skills browser (global, project, package sources)
- Voice input & STT provider configuration
- Integrated terminal & file explorer
- Model selector with capability metadata
- Keyboard-first UI with command palette
- 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi)
- Tauri 2 desktop app
- React 19 + TypeScript + Vite 8 + Tailwind CSS 4
- Pi Agent extension runtime

Originally forked from OpenCodeUI, now an independent project.
License: GPL-3.0-only
This commit is contained in:
Evandro committed 2026-06-01 00:50:06 -03:00
commit b3489e1ede
983 files changed
+158475

No files matched your search

@@ -0,0 +1,845 @@
# Pi Agent Extension Migration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Migrate PiAgentUi from OpenCode to Pi Agent while preserving the current UI and interaction model.
**Architecture:** Build a Pi package/extension as the primary integration. Pi loads `extensions/piagentui-server.ts`, which starts a loopback local server, serves the existing React build, and exposes an owned compatibility API shaped to the current frontend. Tauri/Rust desktop service cleanup is a second track after the Pi package MVP works.
**Tech Stack:** React 19, Vite 8, TypeScript 5.9, Vitest, Tauri 2/Rust, Pi extension API (`@earendil-works/pi-coding-agent`), Node HTTP, SSE/WebSocket event streaming.
---
## Decisions Required Before Implementation
Default recommendations are listed; please confirm or override them before code changes.
1. **MVP session model:** recommended = active Pi session mirror first. The UI controls the currently running Pi session; full session CRUD/fork/switch is deferred.
2. **Desktop/Tauri scope:** recommended = defer process spawning. Tauri can connect to an already-running Pi extension server later, but Track A must not depend on Tauri.
3. **Unsupported features:** recommended = disable/stub PTY, MCP, LSP, provider OAuth, worktrees, and multi-session operations until Pi equivalents are verified.
4. **Token/bootstrap:** recommended = same-origin injected boot data in HTML served by the extension; bearer token stays in memory, never query strings/localStorage.
5. **Endpoint convention:** recommended = keep OpenCode-shaped endpoint paths/envelopes where the current frontend already expects them, then gradually move wrappers to owned names internally.
6. **Product name:** needs user decision. Options: `PiAgentUi`, `Pi Agent UI`, or another exact name.
Implementation should not begin until these are confirmed.
---
## Non-Negotiable Scope
- No UI redesign.
- No `/hud` command.
- No `Ctrl+Shift+H` HUD toggle.
- Remove OpenCode runtime usage completely by final migration.
- Remove `@opencode-ai/sdk` by final migration.
- Reuse user Pi settings/auth/model setup; do not collect provider credentials in PiAgentUi.
- Use `better-tau/extensions/mirror-server.ts` as the main reference for a Pi extension serving HTTP/WebSocket from inside Pi.
- Current known `better-tau` unrelated changes must not be touched: `.gitignore`, `extensions/helper-bridge.ts`, `extensions/helper/`.
---
## Core Corrections from Critical Review
- **Do not conflate Pi package and Tauri app.** Track A is Pi package/extension; Track B is desktop cleanup.
- **Do not flatten message DTOs.** Current UI expects `MessageWithParts { info, parts }`, and many callsites read `message.info.*`.
- **Do not emit raw event envelopes unless `events.ts` is rewritten first.** Current frontend accepts `{ directory, payload: { type, properties } }` from `/global/event`.
- **Use actual Pi lifecycle events.** The shutdown event is `session_shutdown`, not `session_end`.
- **Typecheck extension files.** Existing `tsc -b` does not include `extensions/**/*.ts`.
- **Resolve token bootstrap early.** The UI cannot call protected APIs unless it receives base URL/token safely.
- **Treat `pi.sendUserMessage(...)` as asynchronous fire-and-stream.** It should return `202 accepted`; real message IDs arrive through events.
---
## File/Module Responsibility Map
### New files
- `extensions/piagentui-server.ts`
- Pi extension entrypoint.
- Starts loopback HTTP/SSE/WS server.
- Serves built `dist/` assets.
- Injects safe runtime boot data into `index.html`.
- Bridges Pi session lifecycle and messages to current frontend-compatible events.
- `extensions/piagentui-server-core.ts`
- Pure helpers for testable server behavior: loopback validation, auth validation, body-size guard, path traversal prevention, event envelope helpers.
- `extensions/piagentui-server-core.test.ts`
- Unit tests for server security helpers.
- `tsconfig.extension.json`
- TypeScript project for extension files and extension tests/helpers.
- `src/api/piClient.ts`
- Typed fetch client replacing SDK usage.
- Sends local bearer auth in headers only.
- Normalizes typed errors.
- `src/api/piClient.test.ts`
- Request/auth/error behavior tests.
- `src/types/api/compat.ts`
- Owned compatibility DTOs shaped for current UI.
- `src/api/piContract.ts`
- Owned request/response contracts and current event envelope definitions.
- `src/api/piEventTranslator.test.ts`
- Contract tests for frontend event processing.
- `docs/superpowers/specs/2026-05-28-pi-agent-extension-migration-design.md`
- Design spec, endpoint matrix, unsupported features, security, acceptance criteria.
### Modified files
- `package.json`
- Add `pi.extensions`.
- Add package metadata needed for Pi package usage.
- Add extension typecheck script.
- Add/remove dependencies in the correct sequence.
- Remove `@opencode-ai/sdk` when no imports remain.
- `tsconfig.json`
- Include `tsconfig.extension.json` in references.
- `vite.config.ts`
- Remove OpenCode dev proxy when API layer no longer uses OpenCode server.
- Add Pi extension dev proxy only if chosen.
- `src/constants/api.ts`, `src/api/http.ts`, `src/store/serverStore.ts`
- Convert from OpenCode server/basic-auth concepts to PiAgentUi local server boot/connection state.
- No provider credential storage.
- `src/api/sdk.ts`
- Delete after all imports are removed, or temporarily replace with a non-SDK compatibility facade.
- `src/api/*.ts`
- Replace SDK calls with owned client calls or unsupported-feature stubs.
- Files likely in scope: `agent.ts`, `client.ts`, `command.ts`, `config.ts`, `file.ts`, `global.ts`, `lsp.ts`, `mcp.ts`, `message.ts`, `permission.ts`, `pty.ts`, `session.ts`, `skill.ts`, `todo.ts`, `tool.ts`, `vcs.ts`, `worktree.ts`.
- `src/types/api/*.ts`
- Replace SDK-derived type aliases with owned DTOs or explicit unsupported types.
- Files likely in scope: `agent.ts`, `common.ts`, `config.ts`, `event.ts`, `file.ts`, `mcp.ts`, `message.ts`, `model.ts`, `permission.ts`, `project.ts`, `pty.ts`, `session.ts`, `skill.ts`, `tool.ts`, `vcs.ts`, `worktree.ts`.
- `src/main.tsx`, `src/features/settings/components/ServiceSettings.tsx`, `src/hooks/useCloseServiceDialog.ts`, `src/store/serviceStore.ts`
- Track A: ensure OpenCode auto-start does not run while Pi extension mode is active.
- Track B: rename desktop service integration.
- `src-tauri/src/app/commands/opencode.rs`, `src-tauri/src/app/commands/mod.rs`, `src-tauri/src/app/mod.rs`
- Track B only: rename commands and remove OpenCode process management.
- `src-tauri/src/app/bridge/args.rs`, `src-tauri/src/app/commands/bridge.rs`
- Track B only: enforce loopback URL allowlist in Rust bridge.
- `src-tauri/capabilities/default.json`, `src-tauri/tauri.conf.json`, `src-tauri/Cargo.toml`
- Track B only: remove OpenCode branding and tighten security.
---
## Compatibility Contract
### DTO shape must match current UI
Current UI expects message shape similar to:
```ts
export interface MessageWithParts {
info: Message
parts: Part[]
}
```
Therefore owned DTOs must preserve this shape first, then adapt internally:
```ts
export interface CompatMessageInfo {
id: string
sessionID: string
role: 'user' | 'assistant'
time?: { created?: number; completed?: number }
summary?: string
modelID?: string
providerID?: string
}
export interface CompatMessageWithParts {
info: CompatMessageInfo
parts: CompatPart[]
}
export type CompatPart =
| CompatTextPart
| CompatReasoningPart
| CompatToolPart
| CompatFilePart
| CompatStepStartPart
| CompatStepFinishPart
| CompatUnsupportedPart
export interface CompatTextPart {
id: string
sessionID: string
messageID: string
type: 'text'
text: string
}
export interface CompatReasoningPart {
id: string
sessionID: string
messageID: string
type: 'reasoning'
text: string
}
export interface CompatToolPart {
id: string
sessionID: string
messageID: string
type: 'tool'
tool: string
state: { status: 'pending' | 'running' | 'completed' | 'error'; input?: unknown; output?: unknown; error?: string }
}
export interface CompatFilePart {
id: string
sessionID: string
messageID: string
type: 'file'
mime?: string
filename?: string
url?: string
}
export interface CompatStepStartPart {
id: string
sessionID: string
messageID: string
type: 'step-start'
}
export interface CompatStepFinishPart {
id: string
sessionID: string
messageID: string
type: 'step-finish'
}
export interface CompatUnsupportedPart {
id: string
sessionID: string
messageID: string
type: 'unsupported'
label: string
data?: unknown
}
```
Exact fields must be finalized by a field inventory before implementation.
### Event envelope must match current frontend first
Current `src/api/events.ts` expects:
```ts
export interface CompatGlobalEvent {
directory?: string
payload: {
type: string
properties: unknown
}
}
```
For MVP, the extension should emit this envelope on the current path or the frontend parser must be rewritten before server events are implemented.
Recommended MVP: keep `/global/event` and OpenCode-style event names already handled by the UI:
- `server.connected`
- `session.updated`
- `session.status`
- `session.idle`
- `session.error`
- `message.updated`
- `message.part.updated`
- `message.part.delta`
- `message.part.removed`
- `permission.asked` only if mapped; otherwise unsupported notification
- `question.asked` only if mapped; otherwise unsupported notification
For text deltas, current store expects payload shape compatible with its `PartDeltaPayload`, including `sessionID`, `messageID`, `partID`, `field`, and `delta`.
### Send message contract
Current frontend send params include:
```ts
interface SendMessageParams {
sessionId: string
text: string
attachments: Attachment[]
model: { providerID: string; modelID: string }
agent?: string
variant?: string
directory?: string
}
```
MVP behavior:
- `text`: supported.
- `directory`: supported only as metadata/active project hint if Pi context allows it.
- `model`: support only if Pi allows model switch before send; otherwise ignore with warning event or reject with unsupported error.
- `attachments`: defer unless image/file input mapping is verified.
- `agent`, `variant`: defer unless Pi equivalent is verified.
- response: `202 accepted` plus optional synthetic client request ID; real assistant message ID is learned from events.
---
## Endpoint Coverage Matrix
### MVP supported
| Capability | Existing frontend area | Compatibility path | Backend source |
| ----------------------- | --------------------------------- | ---------------------------------------------------- | ------------------------------------- |
| Health | `serverStore`, `client.ts` | `/global/health` or current health path | extension runtime state |
| Event stream | `events.ts` | `/global/event` | Pi extension lifecycle/events |
| Active models | `client.ts`, model hooks | wrapper-owned endpoint, final path decided in Task 1 | Pi model registry/settings |
| Active session snapshot | `session.ts`, stores | wrapper-owned endpoint, OpenCode-shaped response | active Pi context/session mirror |
| Send message | `message.ts`, `useChatSession.ts` | wrapper-owned endpoint | `pi.sendUserMessage(...)` |
| Abort turn | message/session controls | wrapper-owned endpoint | current Pi context abort if available |
| Static assets | browser/webview | `/`, `/assets/*` | extension static server |
### Explicitly unsupported/deferred for MVP
- PTY terminal sessions.
- MCP management.
- LSP diagnostics endpoints.
- Provider OAuth/login.
- Full multi-session create/fork/switch/delete.
- Worktrees.
- VCS operations unless trivially provided by current UI independent of OpenCode.
- Permission/question flows unless Pi extension UI/RPC gives equivalent semantics.
- Share/export unless Pi exposes safe API from extension context.
- Config, project, agent, skill, todo, tool, file APIs that currently depend on OpenCode SDK.
Every deferred feature must return a typed unsupported error and must not call an OpenCode endpoint.
---
## Security Contract
Required for Track A:
- Bind only to `127.0.0.1` and optionally `::1`.
- Use OS-selected random high port by default.
- Generate per-run bearer token.
- Bootstrap token via same-origin injected boot data in served HTML, unless user chooses another mechanism.
- Keep bearer token in memory/session only; do not store it in `localStorage` or settings backups.
- Reject API and stream requests without `Authorization: Bearer <token>`.
- For browser-native SSE/WS where custom headers are constrained, prefer same-origin cookie/header strategy from extension-served page; do not put token in query strings or userinfo URLs.
- Validate `Host` and `Origin`.
- No wildcard CORS.
- Limit request body and WS frame sizes.
- Reject static path traversal.
- Never send provider API keys/OAuth tokens to browser.
- Never log local bearer token, provider secrets, or prompts by default.
- Close HTTP server and sockets on `session_shutdown`.
Required for Track B if touched:
- Rust bridge must validate URLs server-side, not only via Tauri capabilities.
- Allow only `http://127.0.0.1:*`, `http://localhost:*`, `ws://127.0.0.1:*`, `ws://localhost:*` as appropriate.
- Reject userinfo URLs, LAN IPs, non-loopback hosts, and external HTTPS URLs.
- Narrow `src-tauri/capabilities/default.json` from broad `http://**` / `https://**` to loopback-only.
- Reintroduce CSP instead of `csp: null` if compatible with current app.
---
## Validation Contract
Run during implementation milestones:
```bash
cd PiAgentUi
npm run typecheck
npm run lint
npm run test:run
npm run build
```
After extension tsconfig is added:
```bash
cd PiAgentUi
npm run typecheck:extensions
```
If Track B is touched:
```bash
cd PiAgentUi/src-tauri
cargo test
```
OpenCode cleanup check must use an allowlist because docs may mention migration history:
```bash
cd PiAgentUi
rg -n "@opencode-ai/sdk|getSDKClient|unwrap\(|opencode serve|OpenCode|opencode" src src-tauri extensions package.json vite.config.ts
```
Expected final runtime result:
- no `@opencode-ai/sdk` imports;
- no `opencode serve` launcher;
- no OpenCode user-facing app title;
- only temporary deprecated aliases if Track B explicitly keeps them for one release.
Pi package smoke:
```bash
cd PiAgentUi
npm run build
npm pack --dry-run
```
Then install/load the package in Pi using the local package workflow chosen for this repo and verify:
1. Pi loads the extension.
2. Extension serves the UI.
3. UI receives boot data.
4. UI sends a message to current Pi session.
5. Assistant response streams into existing chat UI.
6. Refresh/reconnect recovers current snapshot.
7. Invalid HTTP/SSE/WS auth is rejected.
---
## Implementation Tasks
### Task 1: Confirm Decisions and Write Design Spec
**Files:**
- Create: `docs/superpowers/specs/2026-05-28-pi-agent-extension-migration-design.md`
- [ ] Confirm all decisions in “Decisions Required Before Implementation”.
- [ ] Write the design spec with: MVP, non-goals, endpoint matrix, DTO/event contract, security contract, validation contract, unsupported-feature behavior, Track A/Track B split.
- [ ] Include exact better-tau reference: `D:/Documents/Github/PiAgentUI/better-tau/extensions/mirror-server.ts`.
- [ ] Run:
```bash
cd PiAgentUi
npm run typecheck
```
Expected: unchanged project still typechecks before migration starts.
### Task 2: Inventory Current Frontend API Fields and SDK Coupling
**Files:**
- Create: `docs/superpowers/specs/2026-05-28-pi-agent-ui-field-inventory.md`
- [ ] Run:
```bash
cd PiAgentUi
rg -n "@opencode-ai/sdk|getSDKClient|unwrap\(|from './sdk'|from '../api/sdk'|Client\[|SDK[A-Za-z]" src
```
- [ ] Inventory all files importing SDK-derived types or `getSDKClient`.
- [ ] Inventory consumed fields for:
- `Session`
- `MessageWithParts`
- `Message.info`
- every `Part` variant rendered by components/stores
- `GlobalEvent`
- `PartDeltaPayload`
- `SendMessageParams`
- [ ] Mark each API module as: supported, stubbed, hidden, or deferred.
- [ ] Do not edit runtime code in this task.
### Task 3: Add Owned Compatibility Types Without Breaking UI Shape
**Files:**
- Create: `src/types/api/compat.ts`
- Create: `src/api/piContract.ts`
- Modify: `src/types/api/message.ts`, `src/types/api/event.ts`, `src/types/api/session.ts` only after inventory confirms exact fields.
- [ ] Add owned compatibility DTOs preserving current UI shape.
- [ ] Replace SDK aliases gradually with owned DTO aliases that match current field names.
- [ ] Do not flatten `MessageWithParts`.
- [ ] Keep `GlobalEvent` envelope compatible with current `events.ts` unless rewriting `events.ts` in the same task.
- [ ] Run:
```bash
cd PiAgentUi
npm run typecheck
npm run test:run -- src/store/messageStore.test.ts src/api/events.test.ts src/hooks/useChatSession.test.tsx
```
Expected: failures only from intentional type differences fixed before proceeding.
### Task 4: Add Extension Typecheck and Package Metadata
**Files:**
- Create: `tsconfig.extension.json`
- Modify: `tsconfig.json`
- Modify: `package.json`
- [ ] Add `tsconfig.extension.json` including `extensions/**/*.ts`.
- [ ] Add script:
```json
"typecheck:extensions": "tsc -p tsconfig.extension.json --noEmit"
```
- [ ] Include `typecheck:extensions` in `validate` after extension files exist.
- [ ] Add Pi package metadata:
```json
"pi": {
"extensions": ["./extensions/piagentui-server.ts"]
}
```
- [ ] Add package contents/prepack strategy so `dist/` and `extensions/` are included when packed/installed.
- [ ] Decide dependency strategy for `@earendil-works/pi-coding-agent`:
- runtime should be supplied by Pi package loader if supported;
- local typecheck still needs a devDependency or path-compatible type resolution.
- [ ] Run:
```bash
cd PiAgentUi
npm run typecheck
npm run typecheck:extensions
npm pack --dry-run
```
### Task 5: Implement Extension Server Core Helpers and Tests
**Files:**
- Create: `extensions/piagentui-server-core.ts`
- Create: `extensions/piagentui-server-core.test.ts`
- [ ] Write tests first for:
- loopback host accepted;
- LAN/external host rejected;
- userinfo URL rejected;
- malformed auth rejected;
- valid bearer auth accepted;
- Origin/Host mismatch rejected;
- static path traversal rejected;
- body-size guard rejects oversized body;
- boot data injection does not put token in URL.
Use test token construction that is obviously fake and not a credential:
```ts
const sampleToken = ['test', 'token', 'value'].join('-')
```
- [ ] Implement pure helpers to pass tests.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run -- extensions/piagentui-server-core.test.ts
npm run typecheck:extensions
```
### Task 6: Implement Pi Extension Server Skeleton
**Files:**
- Create: `extensions/piagentui-server.ts`
- Modify: extension tests/helpers as needed
- [ ] Implement extension entrypoint importing `ExtensionAPI` from `@earendil-works/pi-coding-agent`.
- [ ] Start server on `session_start` or the earliest correct lifecycle event.
- [ ] Cleanup on `session_shutdown`.
- [ ] Bind loopback on random high port.
- [ ] Serve `dist/index.html`, injecting boot data same-origin.
- [ ] Serve `/assets/*` safely.
- [ ] Implement authenticated health endpoint compatible with frontend/serverStore path decision.
- [ ] Run:
```bash
cd PiAgentUi
npm run typecheck:extensions
npm run build
npm pack --dry-run
```
### Task 7: Replace Basic Auth Server Store with Pi Local Boot State
**Files:**
- Modify: `src/store/serverStore.ts`
- Modify: `src/store/serverStore.test.ts`
- Modify: `src/api/http.ts`
- Modify: `src/constants/api.ts`
- [ ] Remove provider/server password persistence from active Pi mode.
- [ ] Store only base URL and in-memory/session-scoped local token.
- [ ] Add migration tests proving old Basic auth/password values are not exported in backups after migration.
- [ ] Ensure auth headers use bearer token and never query strings.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run -- src/store/serverStore.test.ts src/api/http.test.ts
npm run typecheck
```
### Task 8: Add `piClient` and Replace SDK Wrapper Entry Points
**Files:**
- Create: `src/api/piClient.ts`
- Create: `src/api/piClient.test.ts`
- Modify: `src/api/client.ts`
- Modify: `src/api/session.ts`
- Modify: `src/api/message.ts`
- [ ] Write `piClient` tests for bearer headers, no token in URL, JSON errors, base URL normalization.
- [ ] Implement `get`, `post`, `delete`, and stream helper if needed.
- [ ] Replace model/session/message wrappers first because they are MVP-supported.
- [ ] Preserve existing exported function names consumed by hooks/components.
- [ ] For send message, return accepted state if Pi returns no message ID; rely on events for message updates.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run -- src/api/piClient.test.ts src/hooks/useSessions.test.tsx src/hooks/useChatSession.test.tsx src/hooks/useModelSelection.test.tsx
npm run typecheck
```
### Task 9: Implement Pi Event Translation Compatible with Existing `events.ts`
**Files:**
- Modify: `extensions/piagentui-server.ts`
- Create: `src/api/piEventTranslator.test.ts`
- Modify: `src/api/events.ts` only if necessary and covered by tests.
- [ ] Add tests for the current envelope `{ directory, payload: { type, properties } }`.
- [ ] Add tests for `message.part.delta` shape expected by `messageStore`.
- [ ] Map Pi events to existing frontend events:
- turn start -> `session.status` running;
- turn end -> `session.idle` or `session.status` idle;
- message update -> `message.updated`;
- part delta/update -> `message.part.delta` / `message.part.updated`;
- tool events -> tool part updates;
- errors -> `session.error`.
- [ ] Add initial snapshot event on connect.
- [ ] Add reconnect behavior using existing generation/snapshot logic.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run -- src/api/events.test.ts src/api/sse.test.ts src/api/piEventTranslator.test.ts src/store/messageStore.test.ts
npm run typecheck
```
### Task 10: Gate or Stub All Unsupported OpenCode-Only Features
**Files:**
- Modify: remaining `src/api/*.ts`
- Modify: affected hooks/components only when they crash on unsupported errors.
- [ ] Run:
```bash
cd PiAgentUi
rg -n "getSDKClient|unwrap\(|@opencode-ai/sdk|opencode|OpenCode" src
```
- [ ] For every remaining API module, implement one of:
- supported Pi-backed wrapper;
- typed unsupported error;
- hidden/disabled UI action with existing notification style.
- [ ] Cover at least:
- `agent.ts`
- `command.ts`
- `config.ts`
- `file.ts`
- `global.ts`
- `lsp.ts`
- `mcp.ts`
- `permission.ts`
- `pty.ts`
- `skill.ts`
- `todo.ts`
- `tool.ts`
- `vcs.ts`
- `worktree.ts`
- [ ] Ensure `pty.ts` never embeds credentials/tokens in URLs.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run
npm run typecheck
```
### Task 11: Prevent OpenCode Auto-Start in Pi Extension Mode
**Files:**
- Modify: `src/main.tsx`
- Modify: `src/store/serviceStore.ts`
- Modify: `src/features/settings/components/ServiceSettings.tsx`
- Modify: `src/hooks/useCloseServiceDialog.ts`
- [ ] Detect Pi extension mode from boot data.
- [ ] In Pi extension mode, do not auto-start `opencode serve`.
- [ ] Hide/disable OpenCode service settings without changing layout structure more than necessary.
- [ ] Do not rename desktop commands yet unless Track B is approved.
- [ ] Run:
```bash
cd PiAgentUi
npm run test:run -- src/store/serviceStore.test.ts src/utils/settingsBackup.test.ts
npm run typecheck
```
### Task 12: Remove `@opencode-ai/sdk` and SDK Facade
**Files:**
- Modify: `package.json`, lockfile if present
- Delete or rewrite: `src/api/sdk.ts`
- Modify: tests that mock SDK
- [ ] Confirm no imports remain:
```bash
cd PiAgentUi
rg -n "@opencode-ai/sdk|getSDKClient|unwrap\(" src
```
- [ ] Remove dependency using the repo's package manager:
```bash
cd PiAgentUi
npm uninstall @opencode-ai/sdk
```
If package manager/lockfile state is unclear, stop and ask.
- [ ] Delete `src/api/sdk.ts` if unused.
- [ ] Update tests to mock `piClient` or API wrappers instead of SDK.
- [ ] Run:
```bash
cd PiAgentUi
npm run validate
npm run typecheck:extensions
```
### Task 13: Track B — Tauri Desktop Cleanup and Security Hardening
Only start after Track A smoke passes and user approves desktop scope.
**Files:**
- Rename: `src-tauri/src/app/commands/opencode.rs` -> `src-tauri/src/app/commands/pi_agent.rs`
- Modify: `src-tauri/src/app/commands/mod.rs`
- Modify: `src-tauri/src/app/mod.rs`
- Modify: `src-tauri/src/app/bridge/args.rs`
- Modify: `src-tauri/src/app/commands/bridge.rs`
- Modify: `src/store/serviceStore.ts`
- Modify: `src-tauri/capabilities/default.json`
- Modify: `src-tauri/tauri.conf.json`
- Modify: `src-tauri/Cargo.toml`
- [ ] Decide desktop behavior: connect to existing Pi extension server, spawn `pi --mode rpc`, or defer.
- [ ] Rename service commands to Pi names if desktop remains.
- [ ] Keep old command aliases for one transition release only if required by existing frontend code.
- [ ] Migrate storage keys:
- `opencode-auto-start-service` -> `pi-agent-auto-start-service`
- `opencode-binary-path` -> `pi-agent-binary-path`
- `opencode-service-env-vars` -> `pi-agent-service-env-vars`
- [ ] Add Rust-side URL allowlist tests for bridge connect/send paths.
- [ ] Narrow Tauri HTTP permissions to loopback-only.
- [ ] Reintroduce CSP if compatible.
- [ ] Run:
```bash
cd PiAgentUi
npm run typecheck
npm run test:run
cd src-tauri
cargo test
```
### Task 14: Final Product Metadata and OpenCode Cleanup
**Files:**
- Modify: `package.json`
- Modify: `src-tauri/Cargo.toml`
- Modify: `src-tauri/tauri.conf.json`
- Modify: README/docs/user-facing strings
- [ ] Apply user-approved final product name.
- [ ] Update package name/description.
- [ ] Update Tauri product name/window title/identifier if Track B is in scope.
- [ ] Remove OpenCode user-facing strings.
- [ ] Run allowlisted grep:
```bash
cd PiAgentUi
rg -n "@opencode-ai/sdk|getSDKClient|unwrap\(|opencode serve|OpenCode|opencode" src src-tauri extensions package.json vite.config.ts README.md
```
Expected: no runtime/user-facing OpenCode references except approved temporary aliases.
- [ ] Run full validation:
```bash
cd PiAgentUi
npm run validate
npm run typecheck:extensions
npm run build
npm pack --dry-run
```
If Track B is in scope:
```bash
cd PiAgentUi/src-tauri
cargo test
```
---
## Review Checklist for Implementation Milestones
After Tasks 6, 9, 12, and 13, run a critical review pass before continuing:
- Correctness/regression review against this plan.
- Test coverage and validation review.
- Security review for local HTTP/SSE/WS, token handling, and Tauri bridge if touched.
- Simplicity review to catch unnecessary OpenCode compatibility that can be deleted.
The reviewer output should be synthesized before any fixes are applied. Apply fixes with one writer only.
---
## Current Recommended Milestone Split
1. **Milestone 1:** decisions + design spec + field inventory.
2. **Milestone 2:** owned DTOs + extension typecheck/package metadata.
3. **Milestone 3:** secure extension server skeleton + boot data.
4. **Milestone 4:** model/session/message wrappers + event streaming.
5. **Milestone 5:** unsupported feature gating + SDK removal.
6. **Milestone 6:** Pi package smoke and final OpenCode cleanup.
7. **Milestone 7:** optional Tauri desktop cleanup/hardening.
@@ -0,0 +1,531 @@
# Plano de Implementação — Troca Correta de Sessões no PiAgentUI
> **Para agentes executores:** SUB-SKILL OBRIGATÓRIA: use `superpowers:subagent-driven-development` (recomendado) ou `superpowers:executing-plans` para implementar este plano tarefa por tarefa. Os passos usam checkbox (`- [ ]`) para acompanhamento.
**Objetivo:** garantir que uma mensagem enviada no front para a sessão B seja enviada para a sessão real B do Pi, nunca para a sessão que estava ativa quando o Pi abriu.
**Arquitetura:** o backend da extensão PiAgentUI será autoritativo para roteamento de mensagens. Antes de chamar `sendUserMessage`, ele compara `parsed.sessionId` com a sessão real ativa; se divergir, resolve o arquivo `.jsonl`, troca a sessão real do Pi com `switchSession`, e envia dentro do `withSession(replacedCtx)`.
**Tech Stack:** TypeScript, extensão Pi (`ExtensionContext` / `ReplacedSessionContext`), HTTP local Node, Vitest, React frontend sem mudança estrutural.
---
## Estrutura de arquivos
- Modificar: `../pi/packages/coding-agent/src/core/extensions/types.ts`
- Adicionar `switchSession()` em `ExtensionContext`.
- Modificar: `../pi/packages/coding-agent/src/core/extensions/runner.ts`
- Expor `switchSession` também em `createContext()` usando o handler já existente.
- Modificar: `../pi/packages/coding-agent/dist/core/extensions/types.d.ts`
- Atualizar declaração usada pelo typecheck da extensão, caso o build do Pi não regenere dist durante a sessão.
- Modificar: `PiAgentUi/extensions/piagentui-server.ts`
- Resolver arquivo da sessão por ID.
- Enviar mensagem para sessão solicitada com `switchSession(..., { withSession })`.
- Preservar servidor HTTP/SSE em `session_shutdown` causado por `resume` web-iniciado.
- Modificar: `PiAgentUi/extensions/piagentui-server.test.ts`
- Cobrir envio para sessão ativa, envio para sessão diferente, sessão inexistente e switch cancelado.
- Modificar: `PiAgentUi/src/api/message.ts`
- Garantir que erro HTTP de envio não seja engolido; `sendMessageAsync()` já envia `sessionId`.
- Modificar opcionalmente: `PiAgentUi/src/hooks/useChatSession.ts`
- Se necessário, manter erro visível via `handleError('send message', error)` sem mudar UX.
---
## Tarefa 1: Expor switchSession no contexto normal da extensão Pi
**Arquivos:**
- Modificar: `../pi/packages/coding-agent/src/core/extensions/types.ts`
- Modificar: `../pi/packages/coding-agent/src/core/extensions/runner.ts`
- Modificar: `../pi/packages/coding-agent/dist/core/extensions/types.d.ts`
- [ ] **Passo 1: Atualizar o contrato TypeScript do `ExtensionContext`**
Em `../pi/packages/coding-agent/src/core/extensions/types.ts`, dentro de `export interface ExtensionContext`, adicionar logo após `compact(options?: CompactOptions): void;`:
```ts
/**
* Switch to a different session file from long-lived extension services.
* Use `withSession` for work that must run after the replacement context is active.
*/
switchSession(
sessionPath: string,
options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },
): Promise<{ cancelled: boolean }>;
```
- [ ] **Passo 2: Expor o método no contexto criado pelo runner**
Em `../pi/packages/coding-agent/src/core/extensions/runner.ts`, dentro de `createContext(): ExtensionContext`, adicionar antes de `getSystemPrompt: () => {`:
```ts
switchSession: (sessionPath, options) => {
runner.assertActive();
return runner.switchSessionHandler(sessionPath, options);
},
```
O trecho final deve ficar assim:
```ts
compact: (options) => {
runner.assertActive();
runner.compactFn(options);
},
switchSession: (sessionPath, options) => {
runner.assertActive();
return runner.switchSessionHandler(sessionPath, options);
},
getSystemPrompt: () => {
runner.assertActive();
return runner.getSystemPromptFn();
},
```
- [ ] **Passo 3: Atualizar a declaração dist se o build do Pi não for executado**
Em `../pi/packages/coding-agent/dist/core/extensions/types.d.ts`, adicionar assinatura equivalente dentro de `ExtensionContext`:
```ts
/**
* Switch to a different session file from long-lived extension services.
* Use `withSession` for work that must run after the replacement context is active.
*/
switchSession(sessionPath: string, options?: {
withSession?: (ctx: ReplacedSessionContext) => Promise<void>;
}): Promise<{
cancelled: boolean;
}>;
```
- [ ] **Passo 4: Verificar tipos do Pi core**
Executar:
```bash
cd D:/Documents/Github/PiAgentUI/pi/packages/coding-agent
pnpm test -- --runInBand test/suite/regressions/2860-replaced-session-context.test.ts
```
Se esse pacote não usa `pnpm test` com esse formato, executar o comando de teste já documentado no pacote. Resultado esperado: testes existentes passam sem erro de tipo. Se o comando correto não existir, registrar a saída e seguir para o typecheck do PiAgentUI na Tarefa 4.
---
## Tarefa 2: Roteamento de mensagem por sessão no servidor PiAgentUI
**Arquivos:**
- Modificar: `PiAgentUi/extensions/piagentui-server.ts`
- [ ] **Passo 1: Adicionar tipo local para contexto com switch**
Abaixo de `const MIME_TYPES`, adicionar:
```ts
type SwitchableExtensionContext = ExtensionContext & {
switchSession?: (
sessionPath: string,
options?: { withSession?: (ctx: any) => Promise<void> },
) => Promise<{ cancelled: boolean }>
}
```
- [ ] **Passo 2: Trocar o tipo de `latestCtx`**
Substituir:
```ts
let latestCtx: ExtensionContext | null = null
```
por:
```ts
let latestCtx: SwitchableExtensionContext | null = null
```
- [ ] **Passo 3: Criar helper de arquivo de sessão**
Adicionar depois de `getCurrentSessionId()`:
```ts
function getSessionFileForId(sessionId: string): string | null {
const sessionDir = latestCtx?.sessionManager?.getSessionDir()
if (!sessionDir || !fs.existsSync(sessionDir)) return null
const files = fs.readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'))
const matchedFile = files.find(f => f.endsWith(`_${sessionId}.jsonl`))
return matchedFile ? path.join(sessionDir, matchedFile) : null
}
```
- [ ] **Passo 4: Extrair seleção de modelo para helper reutilizável**
Adicionar depois de `getSessionFileForId()`:
```ts
async function setRequestedModel(ctx: SwitchableExtensionContext, model: any) {
if (!model?.providerID || !model?.modelID) return
try {
const targetModel = ctx.modelRegistry?.find(model.providerID, model.modelID)
if (!targetModel) return
const currentModel = ctx.model
if (!currentModel || currentModel.id !== targetModel.id || currentModel.provider !== targetModel.provider) {
console.log(`[PiAgentUi] Switching session model to ${targetModel.provider}:${targetModel.id}`)
await pi.setModel(targetModel)
}
} catch (err) {
console.error('[PiAgentUi] Failed to set model:', err)
}
}
```
- [ ] **Passo 5: Adicionar flag para preservar servidor durante switch web-iniciado**
Perto dos estados do servidor, adicionar:
```ts
let preserveServerForSessionSwitch = false
```
- [ ] **Passo 6: Criar helper de envio correto por sessão**
Adicionar depois de `setRequestedModel()`:
```ts
async function sendUserMessageToRequestedSession(parsed: any): Promise<{ status: number; body: any }> {
const requestedSessionId = parsed.sessionId
const activeSessionId = getCurrentSessionId()
if (!parsed.text) {
return { status: 400, body: { error: 'Missing message text' } }
}
if (!requestedSessionId || requestedSessionId === activeSessionId) {
if (latestCtx) {
await setRequestedModel(latestCtx, parsed.model)
}
const p = pi.sendUserMessage(parsed.text) as any
if (p && typeof p.catch === 'function') {
p.catch((err: any) => {
console.error('[PiAgentUi] Failed to send user message:', err)
})
}
return { status: 202, body: { sessionID: activeSessionId } }
}
const sessionFile = getSessionFileForId(requestedSessionId)
if (!sessionFile) {
return { status: 404, body: { error: `Session not found: ${requestedSessionId}` } }
}
if (!latestCtx?.switchSession) {
return { status: 501, body: { error: 'Pi session switching is not available in this runtime' } }
}
preserveServerForSessionSwitch = true
try {
const result = await latestCtx.switchSession(sessionFile, {
withSession: async replacedCtx => {
latestCtx = replacedCtx as SwitchableExtensionContext
await setRequestedModel(latestCtx, parsed.model)
await replacedCtx.sendUserMessage(parsed.text)
},
})
if (result.cancelled) {
return { status: 409, body: { error: 'Session switch cancelled' } }
}
return { status: 202, body: { sessionID: requestedSessionId } }
} finally {
preserveServerForSessionSwitch = false
}
}
```
- [ ] **Passo 7: Substituir lógica atual de `/api/messages/send`**
Dentro do handler `if (pathParts[1] === 'messages' && pathParts[2] === 'send' && method === 'POST')`, substituir o bloco que faz `setModel`, calcula `activeSessionId`, alerta divergência e chama `pi.sendUserMessage(parsed.text)` por:
```ts
const result = await sendUserMessageToRequestedSession(parsed)
res.writeHead(result.status, { 'Content-Type': 'application/json' })
res.end(
JSON.stringify({
messageID: 'msg-' + Math.random().toString(36).substring(2) + '-' + Date.now(),
...result.body,
}),
)
```
O `catch` do `JSON.parse` permanece igual.
- [ ] **Passo 8: Preservar servidor em `session_shutdown` de switch interno**
Substituir:
```ts
pi.on('session_shutdown', async () => {
stopServer()
})
```
por:
```ts
pi.on('session_shutdown', async event => {
if (preserveServerForSessionSwitch && event?.reason === 'resume') {
return
}
stopServer()
})
```
- [ ] **Passo 9: Verificar typecheck da extensão**
Executar:
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npx tsc --noEmit -p tsconfig.extension.json
```
Resultado esperado: nenhum erro.
---
## Tarefa 3: Testes do servidor de extensão
**Arquivos:**
- Modificar: `PiAgentUi/extensions/piagentui-server.test.ts`
- [ ] **Passo 1: Adicionar sessão alternativa no teste existente**
Depois da criação de `sessionFile`, adicionar:
```ts
const otherSessionId = 'test-session-uuid-other'
const otherSessionFile = path.join(tempSessionDir, `session_${otherSessionId}.jsonl`)
fs.writeFileSync(otherSessionFile, JSON.stringify({
type: 'session',
id: otherSessionId,
timestamp: new Date().toISOString(),
cwd: '/mock/cwd'
}) + '\n')
```
- [ ] **Passo 2: Adicionar mocks de switch no contexto**
Antes de `const mockContext = {`, adicionar:
```ts
const replacedSendUserMessage = vi.fn()
const switchSession = vi.fn(async (_sessionPath: string, options?: { withSession?: (ctx: any) => Promise<void> }) => {
await options?.withSession?.({
...mockContext,
sessionManager: {
getSessionDir: () => tempSessionDir,
getSessionId: () => otherSessionId,
},
sendUserMessage: replacedSendUserMessage,
})
return { cancelled: false }
})
```
Dentro de `mockContext`, adicionar:
```ts
switchSession,
```
- [ ] **Passo 3: Testar envio para sessão ativa sem switch**
Manter o teste existente de `/api/messages/send` sem `sessionId` e ajustar expectativa para aceitar qualquer segundo argumento opcional:
```ts
expect(mockPi.sendUserMessage).toHaveBeenCalledWith('hello world')
expect(switchSession).not.toHaveBeenCalled()
```
- [ ] **Passo 4: Testar envio para sessão diferente com switch**
Depois do teste de envio ativo, adicionar:
```ts
const sendOtherRes = await fetch(`${baseUrl}/api/messages/send`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: otherSessionId,
text: 'hello other session',
model: { providerID: 'google', modelID: 'gemini-2.5-pro' }
})
})
expect(sendOtherRes.status).toBe(202)
expect(switchSession).toHaveBeenCalledWith(otherSessionFile, expect.objectContaining({
withSession: expect.any(Function),
}))
expect(replacedSendUserMessage).toHaveBeenCalledWith('hello other session')
```
- [ ] **Passo 5: Testar sessão inexistente**
Adicionar:
```ts
const missingSessionRes = await fetch(`${baseUrl}/api/messages/send`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'missing-session-id',
text: 'must not be sent',
model: { providerID: 'google', modelID: 'gemini-2.5-pro' }
})
})
expect(missingSessionRes.status).toBe(404)
expect(replacedSendUserMessage).not.toHaveBeenCalledWith('must not be sent')
```
- [ ] **Passo 6: Rodar teste da extensão**
Executar:
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npx vitest run extensions/piagentui-server.test.ts
```
Resultado esperado: teste passa. Se o teste monolítico ficar instável por porta/servidor, dividir em `describe` separado com porta limpa e `session_shutdown` no final.
---
## Tarefa 4: Garantir que erros de envio sejam visíveis no frontend
**Arquivos:**
- Modificar: `PiAgentUi/src/api/message.ts`
- Testar: `PiAgentUi/src/api/piClient.test.ts` ou criar caso em `PiAgentUi/src/api/message.test.ts`
- [ ] **Passo 1: Confirmar comportamento de `piClient.post`**
Ler `PiAgentUi/src/api/piClient.ts`. Se `piClient.post` já lança erro em status `>=400`, não alterar código. Se não lança, ajustar para lançar com status e body.
O comportamento esperado para `sendMessageAsync()` é:
```ts
export async function sendMessageAsync(params: SendMessageParams): Promise<void> {
await piClient.post('/api/messages/send', {
sessionId: params.sessionId,
text: params.text,
model: params.model,
directory: params.directory,
})
}
```
- [ ] **Passo 2: Adicionar teste de erro HTTP se necessário**
Se `piClient` não tiver cobertura de erro, adicionar teste que simula `fetch` retornando `404` para `/api/messages/send` e espera rejeição.
Código esperado do teste:
```ts
it('rejects sendMessageAsync when the server rejects the target session', async () => {
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ error: 'Session not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
})))
await expect(sendMessageAsync({
sessionId: 'missing-session',
text: 'hello',
model: { providerID: 'google', modelID: 'gemini-2.5-pro' },
directory: '/mock/cwd',
})).rejects.toThrow()
})
```
- [ ] **Passo 3: Rodar testes de API relevantes**
Executar:
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npx vitest run src/api/piClient.test.ts src/api/events.test.ts src/store/messageStore.test.ts
```
Resultado esperado: todos passam.
---
## Tarefa 5: Verificação integrada
**Arquivos:**
- Nenhum arquivo novo obrigatório.
- [ ] **Passo 1: Rodar typecheck completo do app**
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npx tsc --noEmit
npx tsc --noEmit -p tsconfig.extension.json
```
Resultado esperado: ambos sem erro.
- [ ] **Passo 2: Rodar testes focados**
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npx vitest run extensions/piagentui-server.test.ts src/api/events.test.ts src/store/messageStore.test.ts
```
Resultado esperado: todos passam.
- [ ] **Passo 3: Rodar build**
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
npm run build
```
Resultado esperado: build passa. Avisos de chunk grande do Vite são aceitáveis se não forem novos erros.
- [ ] **Passo 4: Verificação manual no navegador**
1. Reiniciar o Pi Agent para carregar a extensão atualizada.
2. Abrir PiAgentUI em `http://127.0.0.1:<porta>`.
3. Abrir sessão A e anotar o arquivo `.jsonl` dela.
4. Selecionar sessão B no sidebar.
5. Enviar mensagem `teste roteamento sessao B`.
6. Confirmar visualmente que user + assistant aparecem em B sem F5.
7. Confirmar que o arquivo `.jsonl` da sessão A não recebeu essa mensagem.
8. Confirmar que o arquivo `.jsonl` da sessão B recebeu essa mensagem.
9. Confirmar que `/global/event` continua com uma conexão viva e sem reconectar em loop.
- [ ] **Passo 5: Commit final**
```bash
cd D:/Documents/Github/PiAgentUI/PiAgentUi
git status --short
git add extensions/piagentui-server.ts extensions/piagentui-server.test.ts src/api/message.ts src/api/message.test.ts docs/superpowers/plans/2026-05-30-piagentui-active-session-switch.md
git commit -m "fix: route messages to selected Pi session"
```
Se a implementação modificar arquivos em `../pi`, fazer um commit separado dentro do repositório/árvore correspondente apenas se ele também for um repositório Git local. Como `D:/Documents/Github/PiAgentUI` não é repo e `PiAgentUi` é o repo local atual, registrar explicitamente os arquivos de Pi alterados no resumo final.
---
## Auto-revisão do plano
- Cobertura da spec: o plano cobre exposição de `switchSession`, roteamento backend, preservação do servidor, testes de erro e verificação manual.
- Placeholders: nenhum `TBD`, `TODO`, `implementar depois` ou instrução genérica sem ação concreta.
- Consistência de tipos: `SwitchableExtensionContext`, `switchSession`, `withSession`, `replacedCtx.sendUserMessage` e `sessionID` são usados consistentemente.
- Escopo: limitado ao sistema de sessão/roteamento; não redesenha UI nem lista de sessões.
@@ -0,0 +1,51 @@
# PiAgentUI SQL Settings and Voice Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Move backend-owned PiAgentUI settings to a local SQLite database and fix/extend voice settings for local OpenAI-compatible STT/TTS providers such as Speaches.
**Architecture:** The backend extension becomes the source of truth for backend-owned settings using `~/.pi/agent/piagentui.db`, with a SQL table for namespaced JSON settings and migration from the legacy `piagentui-settings.json`. Frontend voice settings continue using HTTP APIs, now with STT API-key-required control and TTS model/voice configuration.
**Tech Stack:** Node `node:sqlite` `DatabaseSync`, TypeScript, Vitest, React settings components.
---
## Files
- Create: `extensions/piagentui-settings-store.ts` — SQLite settings repository, schema, defaults, legacy JSON migration.
- Create: `extensions/piagentui-settings-store.test.ts` — unit tests for SQL persistence and migration.
- Modify: `extensions/piagentui-server.ts` — replace JSON helpers with SQLite store; add TTS settings/speech endpoints; allow keyless OpenAI-compatible STT when configured.
- Modify: `extensions/piagentui-server.test.ts` — integration coverage for keyless STT and TTS settings.
- Modify: `src/types/api/stt.ts`, `src/api/stt.ts`, `src/hooks/useVoiceTranscription.ts` — expose `apiKeyRequired` and avoid blocking local Speaches.
- Create/Modify: `src/types/api/tts.ts`, `src/api/tts.ts`, `src/features/settings/components/TtsSettings.tsx`, `src/features/settings/components/VoiceSettings.tsx` — TTS settings UI with voice selection.
- Modify tests under `src/hooks` and `src/features/settings/components`.
## Tasks
### Task 1: SQL settings store
- [ ] Write failing store tests for SQLite persistence and legacy JSON migration.
- [ ] Implement `extensions/piagentui-settings-store.ts` with `app_meta` and `settings` tables.
- [ ] Run targeted store tests and commit.
### Task 2: Backend voice behavior
- [ ] Write failing server/integration tests for keyless OpenAI-compatible STT and TTS settings sanitization.
- [ ] Update backend STT to require API key only when `apiKeyRequired` is true.
- [ ] Add TTS settings and `/api/tts/speech` proxy.
- [ ] Run extension tests and commit.
### Task 3: Frontend voice UI
- [ ] Write failing hook/settings tests for keyless STT start and TTS voice selection.
- [ ] Add STT `apiKeyRequired` toggle.
- [ ] Add TTS settings card with model, voice, format, endpoint, API key behavior.
- [ ] Run targeted frontend tests and commit.
### Task 4: Validation
- [ ] Run `npx tsc --noEmit`.
- [ ] Run `npm run typecheck:extensions`.
- [ ] Run targeted voice/storage tests.
- [ ] Run `npm run build`.
- [ ] Run `git diff --check`.
@@ -0,0 +1,93 @@
# PiAgentUI STT LAN Real Sessions Project Folders Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Implement real Pi session creation, project-folder session history, LAN access with QR code, and file-based STT voice input for PiAgentUI.
**Architecture:** Keep runtime changes inside PiAgentUI's extension backend and focused frontend surfaces. Add pure helpers for network/session grouping/STT settings, test them first, then wire endpoints and UI with minimal component changes.
**Tech Stack:** TypeScript, React 19, Vite 8, Vitest, Testing Library, Node HTTP extension server, browser MediaRecorder, OpenAI-compatible transcription API.
---
## File Map
- `extensions/piagentui-server-core.ts`: host/origin validation helpers, LAN URL discovery, STT URL/settings sanitization helpers.
- `extensions/piagentui-server-core.test.ts`: unit tests for backend helpers.
- `extensions/piagentui-server.ts`: real session creation, LAN settings endpoints, STT settings/transcription endpoints, dynamic boot base URL.
- `extensions/piagentui-server.test.ts`: integration tests for endpoints.
- `src/types/api/network.ts`: network access response/request types.
- `src/api/network.ts`, `src/api/network.test.ts`: client API for LAN access.
- `src/types/api/stt.ts`: STT settings/transcription types.
- `src/api/stt.ts`, `src/api/stt.test.ts`: client API for STT.
- `src/hooks/useVoiceTranscription.ts`, `src/hooks/useVoiceTranscription.test.tsx`: browser recording/transcription state machine.
- `src/features/chat/input/VoiceInputButton.tsx`, `src/features/chat/input/VoiceInputButton.test.tsx`: mic UI.
- `src/features/chat/InputBox.tsx`: accept voice transcript insertion.
- `src/features/chat/input/InputToolbar.tsx`: render voice button.
- `src/features/sessions/sessionProjectGroups.ts`, `src/features/sessions/sessionProjectGroups.test.ts`: pure project grouping.
- `src/features/sessions/SessionList.tsx`, `src/features/sessions/SessionList.test.tsx`: project folder rendering.
- `src/features/settings/components/NetworkAccessSettings.tsx`, `src/features/settings/components/NetworkAccessSettings.test.tsx`: LAN toggle/URL/QR UI.
- `src/features/settings/components/ServersSettings.tsx`: include network section.
- `src/locales/en/*.json`, `src/locales/zh-CN/*.json`: minimal labels.
- `package.json`: add `qrcode` only if frontend QR generation needs it.
## Task 1: Backend helper tests and helpers
- [ ] Write tests in `extensions/piagentui-server-core.test.ts` for LAN host validation, local URL discovery injection, and STT URL/settings sanitization.
- [ ] Implement helpers in `extensions/piagentui-server-core.ts`.
- [ ] Run `npx vitest run extensions/piagentui-server-core.test.ts`.
- [ ] Commit `test/feat: add network and stt backend helpers`.
## Task 2: Real session creation endpoint
- [ ] Add failing tests in `extensions/piagentui-server.test.ts` proving `POST /api/sessions` creates a new `.jsonl`, calls `switchSession`, returns the new id, and returns `501` without `switchSession`.
- [ ] Implement minimal real session file creation in `extensions/piagentui-server.ts`.
- [ ] Run `npx vitest run extensions/piagentui-server.test.ts`.
- [ ] Commit `feat: create real pi sessions from ui`.
## Task 3: Project folder grouping
- [ ] Add failing tests in `src/features/sessions/sessionProjectGroups.test.ts` for Windows/POSIX grouping, basename labels, ordering, and search behavior.
- [ ] Implement `sessionProjectGroups.ts`.
- [ ] Add/adjust `SessionList.test.tsx` for folders expand/collapse and selection.
- [ ] Update `SessionList.tsx` minimally to render project folders by default when grouped.
- [ ] Run `npx vitest run src/features/sessions/sessionProjectGroups.test.ts src/features/sessions/SessionList.test.tsx`.
- [ ] Commit `feat: group sessions by project folder`.
## Task 4: LAN access endpoints and UI
- [ ] Add backend endpoint tests for `GET/POST /api/network/access`, LAN host allowed only when enabled, and host-based boot data.
- [ ] Implement network settings persistence and endpoint behavior.
- [ ] Add frontend API tests for `src/api/network.ts`.
- [ ] Add `NetworkAccessSettings` tests for toggle, URL, and QR placeholder/rendering.
- [ ] Implement API and settings component.
- [ ] Wire it into `ServersSettings.tsx`.
- [ ] Run targeted backend/frontend tests.
- [ ] Commit `feat: expose local network access settings`.
## Task 5: STT settings and transcription backend/client
- [ ] Add backend tests for redacted settings, save semantics, invalid URLs, and transcription provider errors.
- [ ] Implement settings persistence and transcription endpoint.
- [ ] Add `src/api/stt.test.ts` and implement `src/api/stt.ts`.
- [ ] Run targeted tests.
- [ ] Commit `feat: add stt settings and transcription api`.
## Task 6: Voice recording UI
- [ ] Add hook tests for permission denied, record/transcribe success, and error path.
- [ ] Implement `useVoiceTranscription.ts`.
- [ ] Add `VoiceInputButton` tests and component.
- [ ] Wire `InputToolbar` and `InputBox` to append transcript to composer without auto-send.
- [ ] Run targeted tests.
- [ ] Commit `feat: add voice transcription composer control`.
## Task 7: Final validation and review
- [ ] Run `npx tsc --noEmit`.
- [ ] Run `npx tsc --noEmit -p tsconfig.extension.json`.
- [ ] Run `npm run test:run`.
- [ ] Run `npm run build`.
- [ ] Run `git diff --check`.
- [ ] Review changed files for unnecessary edits.
- [ ] Commit any final fixes.
@@ -0,0 +1,81 @@
# Roadmap Contexto, Skills e MCP Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** concluir a integração de contexto real, skills reais, avisos de compaction/overflow e MCP nativo no PiAgentUI.
**Architecture:** O backend da extensão PiAgentUI deve ser a fonte de verdade para dados do runtime Pi. A UI deve consumir endpoints pequenos e tipados, preservando fallback seguro quando dados runtime não existirem.
**Tech Stack:** TypeScript, React, Vite, Vitest, extensão PiAgentUI HTTP/SSE, Pi runtime APIs.
---
## Task 1: Context metadata e uso real
**Status:** concluído em `f5a3fa5 feat: enrich context details with model metadata`.
## Task 2: Tool results somente em tool cards
**Status:** concluído em `41adf23 fix: keep tool results in tool cards`.
## Task 3: Skills reais por origem
**Status:** concluído em `257d63c feat: show real skills by source`.
## Task 4: Avisos de overflow e compaction
**Files:**
- Modify: `src/hooks/useSessionStats.ts`
- Modify: `src/features/chat/sidebar/SidebarFooter.tsx`
- Modify: `src/features/chat/sidebar/ContextDetailsDialog.tsx`
- Test: `src/hooks/useSessionStats.test.tsx`
- [ ] Adicionar estado derivado para `contextStatus`: `unknown`, `normal`, `warning`, `danger`.
- [ ] Mostrar fonte do dado: runtime, tokens ou estimativa.
- [ ] Mostrar aviso quando `contextKnown=false` após compaction.
- [ ] Mostrar aviso quando uso >= 70% e >= 90%.
- [ ] Rodar testes focados e build.
- [ ] Commit: `feat: surface context overflow notices`.
## Task 5: MCP backend real
**Files:**
- Modify/Create: `extensions/piagentui-server.ts`
- Modify/Create: `src/api/mcp.ts`
- Test: `extensions/piagentui-server.test.ts`
- [ ] Ler configuração MCP do Pi/agent config quando disponível.
- [ ] Expor `/api/mcp/servers` com servidores, status e origem.
- [ ] Preservar fallback vazio seguro.
- [ ] Testar config válida, ausente e inválida.
- [ ] Commit: `feat: expose real mcp servers`.
## Task 6: MCP UI real
**Files:**
- Modify: `src/components/McpPanel.tsx`
- Modify: `src/types/api/mcp.ts`
- Test: `src/components/McpPanel.test.tsx` se existir; caso contrário criar.
- [ ] Agrupar servidores por status/origem.
- [ ] Mostrar tools quando disponíveis.
- [ ] Mostrar erro de inicialização/config.
- [ ] Commit: `feat: render real mcp server status`.
## Task 7: Skills avançado
**Files:**
- Modify: `extensions/piagentui-server.ts`
- Modify: `src/components/SkillPanel.tsx`
- [ ] Expor diagnósticos/collisions quando Pi disponibilizar.
- [ ] Mostrar `disableModelInvocation` quando disponível.
- [ ] Melhorar origem com package/source quando disponível.
- [ ] Commit: `feat: add skill diagnostics metadata`.
## Task 8: Hardening e docs
- [ ] Extrair parsers grandes de `extensions/piagentui-server.ts` se necessário.
- [ ] Atualizar Obsidian/task list.
- [ ] Rodar full test/build.
- [ ] Fazer checkpoint commit/tag local se solicitado.
@@ -0,0 +1,58 @@
# PiAgentUI Git Composer Control Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Implement a Git branch/control popover in the message composer using GitHub Octicons and safe backend Git endpoints.
**Architecture:** Add focused Git logic in `extensions/piagentui-git-manager.ts`, expose `/api/vcs/*` endpoints from the extension server, expand frontend VCS API/types, and add `GitComposerControl` inside `InputToolbar` before agent/variant selectors.
**Tech Stack:** React 19, TypeScript, Vite, Vitest, Node child_process, Git CLI, `@primer/octicons-react`.
---
## Files
- Create: `extensions/piagentui-git-manager.ts`
- Create: `extensions/piagentui-git-manager.test.ts`
- Modify: `extensions/piagentui-server.ts`
- Modify: `extensions/piagentui-server.test.ts`
- Modify: `src/types/api/vcs.ts`
- Modify: `src/types/api/index.ts`
- Modify: `src/api/vcs.ts`
- Create/modify: `src/api/vcs.test.ts`
- Create: `src/features/chat/input/GitComposerControl.tsx`
- Create: `src/features/chat/input/GitComposerControl.test.tsx`
- Modify: `src/features/chat/input/InputToolbar.tsx`
- Modify: `package.json`, `package-lock.json`
## Task 1 — Backend Git manager
- [ ] Write failing tests for branch parsing, status parsing, safe argv commands and commit validation.
- [ ] Implement `runGit`, `getGitStatus`, `listGitBranches`, `checkoutGitBranch`, `createGitBranch`, `stageGitPaths`, `unstageGitPaths`, `commitGitChanges`, `pullGitBranch`, `pushGitBranch`.
- [ ] Verify focused test passes.
## Task 2 — Backend endpoints
- [ ] Write failing server endpoint tests for `/api/vcs/status`, `/api/vcs/branches`, `/api/vcs/commit`.
- [ ] Add endpoint routing in `piagentui-server.ts` with request parsing and errors.
- [ ] Verify server tests pass.
## Task 3 — Frontend API contract
- [ ] Write failing API tests for query/body shapes.
- [ ] Expand `src/types/api/vcs.ts` and `src/api/vcs.ts`.
- [ ] Verify API tests pass.
## Task 4 — Composer UI
- [ ] Install `@primer/octicons-react`.
- [ ] Write failing component tests for branch chip, popover opening and commit action.
- [ ] Implement `GitComposerControl` with Octicons.
- [ ] Integrate into `InputToolbar` before agent selector.
- [ ] Verify component tests pass.
## Task 5 — Full verification
- [ ] Format touched files with `node node_modules/prettier/bin/prettier.cjs --write ...`.
- [ ] Run LSP diagnostics on touched files.
- [ ] Run `npm run typecheck`, `npm run typecheck:extensions`, focused tests, `npm run test:run`, `npm run build`, and `git diff --check`.