🎉 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
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:
commit
b3489e1ede
983 files changed
+158475
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user