dshbase

插件目录 / UI & Skins / dsh-ai-battle

dsh-ai-battle

已验证 · 实测可装 Gxk96

✓ 持续维护

查看 GitHub ↗ ← 返回插件目录

0Stars
0Forks
0未关闭 issue
JavaScript语言
2026-08-14最近推送
跨平台平台

功能简介

将AI任务等待时间变成小游戏对决,难度随任务调整,并记录战绩。

我们的评价
可用 — 实测通过,早期项目

将AI任务等待时间变成小游戏对决,难度随任务调整,并记录战绩。 实测能干净安装、正常启动。早期项目,但功能可用。

「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。

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

安装

🧩 让 Agent 自动装(推荐)

装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:

dsh plugin add dshbase-catalog

然后对 agent 说「帮我装 dsh-ai-battle」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包

该插件是 GitHub 源码(未发 npm)——直接从仓库装:

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

实测报告

验证通过:从 GitHub 源码完成 L1 安装 + L2 加载 + L3 运行(dsh 0.1.0-rc.6)。

使用场景

改变 dsh 的外观或交互方式——一套主题、皮肤或新面板,重塑工作区。

适合谁

在 web UI 里一待几小时、想让它按自己的习惯好看又好用的人。

二次开发建议

皮肤、面板和主题 token 是扩展点——写新皮肤、加面板,或与上游配色同步 token。

安全:尚未扫描——我们的每日静态扫描将很快覆盖它。

分享徽章

UI & Skins 里更多

浏览全部 7795 个插件 →