Skip to content
dsh.fish
Bundle

dsh-goal-pause-guard

DSH plugin: asks for confirmation in a modal before pausing a running goal. Confirming pauses the goal, which also aborts the in-flight round so running subtasks stop immediately; cancelling does nothing.

Source
frederico-kluser
stars
1 stars
License
MIT
Updated
Updated 14 hours ago

Readme

<div align="center">

# dsh-goal-pause-guard

**Plugin para o [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): modal de confirmação antes de pausar uma atividade rodando.**

[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![node](https://img.shields.io/badge/node-%E2%89%A524-brightgreen.svg)](package.json)
[![dsh](https://img.shields.io/badge/DSH-0.1.5--alpha.2-4176e6.svg)](https://github.com/deepseek-ai/deepseek-harness)
[![ci](https://github.com/frederico-kluser/dsh-goal-pause-guard/actions/workflows/ci.yml/badge.svg)](../../actions/workflows/ci.yml)
[![topic: dsh-plugin](https://img.shields.io/badge/topic-dsh--plugin-8b5cf6.svg)](https://github.com/topics/dsh-plugin)

*Pausar uma goal não é um gesto pequeno: ela interrompe a rodada atual e todas
as subtarefas em execução na hora. O plugin pede confirmação antes.*

![Modal de confirmação antes de pausar a goal](docs/pause-modal-closeup.png)

</div>

---

## Porquê

No harness, **pausar uma goal armada dispara uma cascata** (motorizada no
`goal-round-driver`, `goal/changed` → `agent.cancel`, `keepInbox`):

1. a rodada em andamento é **abortada** — o agente para de executar no meio do
   que estava fazendo;
2. as **subtarefas em execução** param junto (tools, comandos, subagentes);
3. a continuação automática fica **desarmada** — nada de novas rodadas até um
   `resume` humano.

É exatamente esse efeito que o plugin coloca atrás de um portão: um clique
acidental no botão de pause não pode desligar uma atividade que está rodando.

## O que o plugin faz

```
clique em "Pause goal" (clique humano, trusted)
        │
        ▼
  ┌──────────────┐   captura no document (capture phase, antes do React)
  │ GUARD        │   isPauseButton? ── não ──► o clique passa (fail-open)
  └──────┬───────┘
         ▼ sim
  MODAL DE CONFIRMAÇÃO  (pt-BR / en / zh, tema claro-escuro do host)
     ├── Cancelar / Esc / fundo ──► nada acontece
     └── Confirmar ──► re-clique UNTRUSTED no botão de pause ATUAL
                          │  (passa pelo guard sem re-gate)
                          ▼
                    GoalBar → goals.pause (Remote RPC)
                          ▼
                    host: goal paused + rodada abortada + subtasks param
```

- **Detecção por dois canais** — o botão é reconhecido pelo `aria-label`
  localizado (`Pause goal` / `暂停目标`, os dois dicionários da barra de goal)
  **ou** pelo path SVG do ícone de pause (`IconPauseOutline16`), sempre dentro
  de `[data-goal-bar]` (o marcador que o próprio e2e oficial usa).
- **Fail-open por projeto** — se o botão não for identificável, o clique passa e
  o pause se comporta exatamente como sem o plugin. Um portão de confirmação
  nunca pode quebrar a ação que protege.
- **Sem loop, sem referência velha** — o confirm re-localiza o botão de pause
  **atual** antes de re-clicar (um re-render entre abrir e confirmar nunca
  transforma "confirmar" em "clicar outra coisa"); botão desabilitado ou
  removido nesse meio-tempo = nada acontece.
- **Um modal por vez** — Esc, fundo e Cancelar fecham; o foco vai ao botão de
  confirmar e volta ao botão de pause no fechar.
- **Determinismo** — sem timers, sem rede, sem storage. A metade host é um
  `apply` vazio (mesmo padrão do `@deepseek-ai/dsh-client-ui-goal`): nenhum
  estado do harness é tocado.

## Instalação

Como bundle do profile `web` — o `dsh.bundle.patch` do pacote ativa a camada
sozinho (insert próprio, sem colidir com nenhum outro id):

```bash
dsh plugin --profile web add /caminho/para/dsh-goal-pause-guard
dsh web   # reinicie a instância; plugins entram no boot
```

Desinstalar: remova a linha do bundle do `~/.dsh/profiles/web/package.json`
(`dsh plugin --profile web remove dsh-goal-pause-guard`) e reinicie.

## Verificação

Toda a verificação roda **isolada** — nenhum teste toca uma instância real do
DSH (nem a porta 3080, nem o `$DSH_HOME`):

```bash
pnpm install
pnpm run typecheck   # tsc estrito
pnpm run build       # dist/ (host) + lib/client.js (browser, closure-factory)
pnpm test            # 23 testes: detector + comportamento + e2e do artefato
```

| Camada | Prova |
| --- | --- |
| **Detector** (unit, jsdom) | os dois canais de identificação; rejeição de todo o resto (resume/edit/clear, botões fora da barra, ícones estrangeiros, prefixos parecidos) |
| **Comportamento** (jsdom) | clique humano abre o modal e nunca chega ao app; confirmar re-clica UMA vez como untrusted; cancelar/Esc/fundo não pausam; segundo clique engolido; botão removido/desabilitado no confirm não dispara; disposer remove tudo |
| **E2E do artefato** | o `lib/client.js` enviado é carregado como o harness carrega (`window.__ModuleLoader__.load({id, factory})`) e o fluxo inteiro é dirigido sobre ele |

O screenshot acima é real: capturado num boot isolado ( `$DSH_HOME`
temporária + porta própria) com Chromium headless, clicando no botão de pause
de verdade (evento trusted via CDP) — o guard abriu o modal e o cancelamento
não enviou pausa nenhuma.

## Estrutura

```
dsh-goal-pause-guard/
├── package.json             # dsh.bundle.patch + dsh.client (platform web) + exports["./client"]
├── cordis.patch.yml         # camada 1 (bundle): insert próprio, sem !!js, sem config
├── src/index.ts             # host half: apply vazio (entrada do Loader)
├── client/
│   ├── guard-core.ts        # TODA a lógica: detector, textos, modal, install (0 imports)
│   ├── index.ts             # plugin body: inject [] + apply(ctx) via ctx.effect
│   ├── guard.css            # modal com tokens --dsw-* (claro/escuro), classes gpg-
│   └── client.d.ts          # declaração do subpath ./client
├── scripts/build-client.mjs # esbuild → lib/client.js (closure-factory, CSS embutido)
├── test/                    # jsdom + node:test, offline por projeto
└── dist/, lib/              # produtos do build (host half / browser bundle)
```

## Compatibilidade

Verificado contra **DSH 0.1.5-alpha.2** (a linha viva instalada, bootada do
source). O detector ancora em dois fatos estáveis da superfície — `data-goal-bar`
e o `aria-label` do `action.pause` do dicionário `goal`. Se o harness mudar os
dois ao mesmo tempo, o guard fica fail-open (pause sem modal) — nunca quebrado.

## Licença

[MIT](LICENSE) © Frederico Kluser

Install

dsh plugin --profile web add github:frederico-kluser/dsh-goal-pause-guard

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source