dshbase

Plugin directory / UI & Skins / dsh-ai-battle

dsh-ai-battle

Verified · install-tested on dsh Gxk96

✓ Actively maintained

View on GitHub ↗ ← Back to plugin directory

0Stars
0Forks
0Open issues
JavaScriptLanguage
2026-08-14Last push
Cross-platformPlatform

What it does

Turn AI task waiting time into mini-game duels with difficulty scaling and record book.

Our take
Works — verified, early-stage project

Turn AI task waiting time into mini-game duels with difficulty scaling and record book. It installs cleanly and boots without issues in our testing. It's early-stage but functional.

“Verified” means our automated CI actually ran dsh plugin add in a clean profile and it booted — nothing more. Feature descriptions and version compatibility are the author’s claims. This is not a security audit and not an endorsement of third-party code.

README

dsh-ai-battle

English | 中文

A DeepSeek Harness plugin that turns waiting time into a mini-game duel:

While the AI grinds a troublesome task, a random enabled mini-game challenges you (never the same game twice in a row while more than one is enabled), with its difficulty scaled to the task. Reach the game's goal before the AI finishes the task and you win — otherwise the AI wins. You can quit any battle (non-training quits count as a loss), and the full record book is kept and browsable any time.

Screenshots

Accepting a challenge Choosing games
In-game The match
Records Training mode

What is this?

In one sentence: it turns "waiting for the AI to run a task" into "racing the AI through a mini-game".

When you hand DeepSeek Harness a long-running job, this plugin turns the idle wait into a game with a real winner — and it's a multi-game platform: Minesweeper ships built-in, and adding more games is a small, documented extension task.

  • Task battles (automatic) — after a troublesome task starts, the plugin scores its complexity, picks a random game from your enabled pool (rotating, never the previous one), maps the score onto that game's difficulty presets, and pops a dialog stating the game-specific win condition. Battles are never time-limited — the only win anchor is "did you finish before the AI finished its task": finish first and you win, task first and the AI wins.
  • Training mode (manual) — tap the 💣 floating button → "🏋️ 训练模式" → pick a game, then a difficulty for an untimed training round. Results are never persisted; finishing counts as a win and you can quit anytime.
  • Game multi-select — a dedicated Games tab (after Records) lets you tick/untick which games appear in auto-challenges (all enabled by default). Untick ones you dislike; at least one game must stay selected (the last one can't be turned off).
  • Record book — every task-battle outcome (game, difficulty, duration, triggering task) is persisted (up to 200 entries); win rate and tallies are visible and clearable. Quitting a non-training battle counts as a loss.

Built-in games (all untimed — the only win anchor is finishing before the AI task does):

Game Difficulties Goal
💣 Minesweeper 简单 9×9·10 / 中等 16×16·40 / 困难 16×30·99 / 地狱 20×24·130 Clear the field
🧩 Sudoku easy 32 holes / medium 44 / hard 52 / inferno 54 Fill the whole 9×9 grid
🪜 Klotski (15-puzzle) easy 12 shuffles / medium 28 / hard 48 / inferno 80 Arrange 1–15 in order

Sudoku: tap an empty cell, then a digit below to fill it (mistakes turn red and can be corrected). Klotski: tap a tile to slide it into the gap, arrow keys work too. Both games get the platform's score→difficulty mapping, random rotation, training mode, and records for free.

Games that fit this "race the AI" frame (for extension)

Prefer untimed games whose only completion anchor is "finishing" — the outcome must hinge on "did you reach the goal before the AI finished its task", not on a built-in clock. Quick-fire games that end in seconds (e.g. memory matching) have no sense of contest against a task that runs for a while. Good candidates: 2048 (difficulty = target tile 128→2048, full example below), Tetris (clear N lines), Snake (reach a high target score), etc.

How it works

When does a battle start? — text score and AI trajectory

The auto-start decision uses two independent opinions, combined into one score:

  1. Text score (lib/complexity.js, evaluated the moment your message lands): keyword weights (CN + EN), brief length, numbered requirements, separators, code fences / file paths, polish words, and an imperative-opener bonus. A score ≥ 0.25 pops the challenge immediately — short explicit instructions ("实现一个…") still trigger instantly.
  2. AI trajectory score (lib/trajectory.js, evaluated while the AI is running): the host watches the session event stream and accumulates real work signals — elapsed time since the task started, tool calls (fs writes, bash runs, …), model steps, and output tokens (from each step's usage). When the trajectory score crosses 0.25 and no challenge/battle is pending for this turn, the challenge is created mid-task — so a task that looks trivial in text but turns into a long AI grind still gets a battle. The difficulty uses whichever score is higher, and such challenges are tagged "⚡ 轨迹判定(AI 运行中)" in the dialog.

Rules that keep it sane: one offer per turn (declining once means no re-offer for that task), per-project opt-out and the enabled-game pool apply to both paths, the watcher stops the moment a battle starts or the task ends, and a 30-minute safety cap prevents any leak. Battles remain untimed — the only win anchor is "did you finish before the AI finished its task".

Platform layer (lib/platform.js)

The game-agnostic state machine: task lifecycle (turn/start / user/message / turn/end), challenge creation with random game picking (pickGame, excludes the previous game), score→difficulty mapping (presetForScore, clamps to the game's preset count), accept/skip/quit, untimed training mode (never recorded), and records. Games plug in as adapters.

Game adapter (lib/games/minesweeper.js)

Each game implements a small contract and is fully hosted by the platform:

  • id / name / icon / rules (win-condition clauses shown in the challenge dialog)
  • presets: ascending difficulty presets {key, label, limitMs, desc} + game-private fields
  • createGame(battleId, preset, at) → private game state
  • applyMove(gameState, move){ state, win?, exploded? }
  • restart(gameState, at) and view(gameState, ended) (client render data)
  • recordMeta(gameState) (extra fields persisted with each record)

Host half (lib/index.js)

  • Watches the session/event firehose; user/message (source kind:'user') → text score → pick game + preset → create challenge; feeds the trajectory tracker from step/start / tool/call / assistant/message(usage) and offers a challenge mid-task when the trajectory score clears the threshold; turn/end settles open battles as AI wins (the only loss anchor — no timers anywhere).
  • HTTP surface /ms/api (same-origin, loopback): GET /ms/api/state, GET /ms/api/records, POST /ms/api/cmdaccept / decline / move / restart / forfeit / startSolo / setPreference (per-project challenge opt-out) / setGames (game multi-select) / clearRecords.
  • Persists to $DSH_HOME/storages/dsh-ai-battle.json (atomic writes, capped at 200; legacy dsh-minesweeper.json records migrate automatically on first boot). Preferences: per-project opt-out (disabledCwds) + global enabled-game set (disabledGames).

Client half (lib/client.js)

A hand-written __ModuleLoader__ bundle (no build step). Registered into the root-scoped shell.overlay layer — a draggable floating "battle一下" button (position in localStorage), independent of any session/project/header. The popover has:

  • 对局 (Battle) — live game (generic head: game/difficulty/timer/quit + a per-game renderer), result overlays, in-panel challenge fallback; idle view offers the training entry and a last-result banner.
  • 战绩 (Records) — tallies, win rate, the record list (game, difficulty, duration, task excerpt), clearable.
  • 游戏 (Games) — the auto-challenge game multi-select (all on by default, at least one must stay selected).

The challenge dialog states the game-specific rules (1. rules[0] → you win 🏆; 2. rules[1] → AI wins 🤖), labels your prompt with "💬 你的提问", shows the auto difficulty, and offers accept/skip plus the per-project opt-out.

State is polled from the host every 2s (cheap localhost JSON).

Adding a game (extension)

  1. Write an adapter in lib/games/mygame.js implementing the contract above (see minesweeper.js).
  2. Register it in lib/index.js: GAME_LIST = [minesweeperGame, myGame].
  3. Write a renderer in lib/client.js's GAME_RENDERERS map (mygame: MyGameView, consuming battle.view and calling onMove(move)).
  4. Run node test/smoke.mjs and node scripts/sync-to-profile.mjs, then restart dsh web.

The platform gives new games random rotation, score→difficulty mapping, training mode, records, and quit handling for free.

How difficulties work — 2048 as the worked example

The platform's only rule: each game's presets array must be sorted ascending by difficulty, and the task score is mapped automatically (presetForScore):

Task score Auto-selected preset
0.25 – 0.55 presets[0] (easiest)
0.55 – 0.70 presets[1]
0.70 – 0.85 presets[2]
≥ 0.85 presets[3] (games with 2/3 presets clamp to the last one)

2048's natural difficulty axis is the target tile (reaching it = finishing; untimed, completion-anchored — exactly this plugin's win model):

key Label Goal desc
easy 简单 reach 128 4×4 · 目标 128
medium 中等 reach 512 4×4 · 目标 512
hard 困难 reach 1024 4×4 · 目标 1024
inferno 地狱 reach 2048 4×4 · 目标 2048

Adapter skeleton (lib/games/2048.js):

export const game2048 = {
  id: '2048',
  name: '2048',
  icon: '🔢',
  rules: ['在 AI 完成任务前合成到目标方块', '任务先完成(或你放弃)'],
  presets: [
    { key: 'easy', label: '简单', target: 128, desc: '4×4 · 目标 128' },
    { key: 'medium', label: '中等', target: 512, desc: '4×4 · 目标 512' },
    { key: 'hard', label: '困难', target: 1024, desc: '4×4 · 目标 1024' },
    { key: 'inferno', label: '地狱', target: 2048, desc: '4×4 · 目标 2048' },
  ],
  createGame(battleId, preset) {
    // 4×4 grid seeded with two 2s; the goal tile comes from preset.target
    // (an adapter-private field the platform never touches).
    return { grid: new Array(16).fill(0), target: preset.target, over: false };
  },
  applyMove(gameState, move) {
    // move: { type: 'swipe', dir: 'up' | 'down' | 'left' | 'right' }
    // Merge on swipe; reaching the target tile → { state, win: true }.
    return { state: gameState };
  },
  view(gameState) {
    return { size: 4, cells: gameState.grid, target: gameState.target };
  },
  recordMeta(gameState) {
    return { target: gameState.target, maxTile: Math.max(...gameState.grid) };
  },
};

Notes:

  • presets fields key/label/desc are for the platform and UI; private fields like target belong to the adapter.
  • Preset count is not forced to 4 — 2 or 3 presets clamp fine (see presetForScore).
  • A win is triggered solely by applyMove returning { win: true }; if the AI task ends (turn/end) before that, the AI wins automatically — the game itself must not carry a clock.
  • Training mode reuses the same presets: pick a game, then one of its difficulty tiers (shown via desc).

Installing into the web profile

The package ships in profiles/node_modules/dsh-ai-battle (a copy — resync after edits with node scripts/sync-to-profile.mjs), referenced by:

  • C:\Users\gxkor\.dsh\profiles\web\package.json"dsh-ai-battle": "file:D:/WorkSpeace/dsh/dsh-ai-battle"
  • C:\Users\gxkor\.dsh\profiles\web\cordis.patch.yml → one dual-face row:
- insert:
    - id: ai-battle
      name: 'dsh-ai-battle'

Restart required. The new host row and boot-manifest entry take effect when dsh web restarts. The old plugin row (dsh-minesweeper) has been removed from the profile; records migrate automatically.

Development

node test/smoke.mjs        # platform + minesweeper adapter + scoring (29 checks)
node test/client-load.mjs  # loads lib/client.js like the browser does
node test/e2e-driver.mjs   # task-battle flow (:3090 test instance)
node test/e2e-solo.mjs     # training + game multi-select + scorer
node test/e2e-pref.mjs     # per-project challenge opt-out
node scripts/sync-to-profile.mjs

Zero runtime dependencies: host uses only node:fs/node:path, client only the platform seed modules (react + slot/locale services).

Files

dsh-ai-battle/
├── package.json          # exports: "." (host), "./client" (bundle); dsh.client declaration
├── README.md / README.zh.md
├── lib/
│   ├── index.js          # Cordis host plugin: events, /ms/api HTTP, persistence & migration
│   ├── platform.js       # multi-game platform state machine
│   ├── complexity.js     # task → difficulty score (text, at task start)
│   ├── trajectory.js     # AI-run-time score (elapsed, tool calls, steps, tokens)
│   ├── games/
│   │   ├── minesweeper.js # built-in Minesweeper game adapter
│   │   ├── sudoku.js      # built-in Sudoku game adapter
│   │   └── klotski.js     # built-in Klotski (15-puzzle) game adapter
│   └── client.js         # browser bundle: floating button + challenge dialog + game renderers + training + records
├── scripts/sync-to-profile.mjs
└── test/                 # smoke, client-load, host-trajectory, e2e-driver, e2e-solo, e2e-pref

Install

🧩 Let your agent install it (recommended)

Install the catalog once, then DeepSeek Harness can find and install any plugin from this site automatically:

dsh plugin add dshbase-catalog

Then say "install dsh-ai-battle for me" — your agent finds it in the directory and installs it. Docs: dshbase-catalog · verified packs.

This plugin is GitHub source (not published to npm) — install it straight from the repo:

Web profile:

dsh plugin --profile web add github:Gxk96/dsh-ai-battle

Headless (CLI) profile:

dsh plugin --profile headless add github:Gxk96/dsh-ai-battle

Test report

Verified: L1 install + L2 load + L3 runtime from GitHub source on dsh 0.1.0-rc.6.

When to use it

Change how dsh looks or how you interact with it — a theme, a skin, or a new panel that reshapes the workspace.

Who it's for

Users who spend hours in the web UI and want it to look and feel the way they work.

For developers — extending it

Skins, panels, and theme tokens are the extension points — author a new skin, add a panel, or sync tokens with an upstream palette.

Security: not yet scanned — our daily static scan will cover it shortly.

Share this badge

More in UI & Skins

Browse all 7795 plugins →