Bundle
agpa-dsh-plugin
AGPA (Agent Player Achievements) for DeepSeek Harness — native achievement_* tools bridging the AGPA engine, plus automatic event tracking from dsh session events
- Source
- eiainano
- License
- MIT
- Updated
- Updated 10 hours ago
Readme
# agpa-dsh-plugin
English | [中文](README.zh.md)
Connects [AGPA](https://www.npmjs.com/package/@eiainano/agpa) (Agent Player
Achievements) to **DeepSeek Harness (dsh)** as a Cordis plugin.
**Design goal (maximum value per unit of code)**: the DSH side **reimplements no
achievement logic**. Every `achievement_*` tool is a thin shell that forwards its
arguments verbatim to the published AGPA MCP server (the `agpa-mcp` bin from
`@eiainano/agpa`). The AGPA engine, achievement definitions, profiles and the
`~/.agent-achievements` data store are therefore shared across Claude Code, DSH
and any other harness — so `cross_agent` achievements fire automatically.
## Layers and files
| Layer | File | Purpose |
|---|---|---|
| Entry (Cordis row) | `src/index.ts` + `cordis.patch.yml` | `name='agpa'`, the patch `id` must match |
| Tool bridge | `src/tools.ts` | 7 native `achievement_*` tool shells |
| MCP client | `src/agpa-bridge.ts` | Zero-runtime-dependency MCP-over-stdio (Node builtins only), lazily spawns one AGPA process |
| Auto-tracking | `src/events.ts` | Subscribes to dsh `session/event`, normalizes into CC-style payloads (`source:'dsh'`) |
| Ingest subprocess | `src/hook-runner.ts` | Serially spawns short-lived `agpa-hook auto` processes, feeds them stdin |
| Model workflow manual | `skills/agpa/SKILL.md` | Teaches the model when to track / poll / announce unlocks |
## Directory layout
```
forDSH/agpa-dsh-plugin/
├─ cordis.patch.yml # insert row: id=agpa name=agpa-dsh-plugin
├─ package.json # dsh.bundle.patch → cordis.patch.yml
├─ tsconfig.json # NodeNext → lib/
├─ src/
│ ├─ index.ts # export name='agpa'; inject=['tools']; apply()
│ ├─ tools.ts # 7 × ctx.tools.register(defineTool(...))
│ ├─ agpa-bridge.ts # minimal MCP client (spawns agpa-mcp)
│ ├─ events.ts # session/event → CC-style normalization (AutoTrackFeed)
│ └─ hook-runner.ts # serially spawns `agpa-hook auto` ingest processes
├─ scripts/
│ ├─ install-skills.mjs # copies skills → $DSH_AGENTS_HOME/skills
│ └─ smoke-bridge.mjs # on-machine smoke test: reads AGPA stats (read-only)
└─ skills/agpa/SKILL.md # AGPA workflow manual
```
## Commands
```bash
npm install --legacy-peer-deps # dev dependencies, for typechecking only
npm run typecheck # typecheck against the real @deepseek-ai/dsh-tools types
npm run build # tsc → lib/
npm run smoke:bridge # on-machine check of bridge ↔ AGPA MCP (read-only stats)
npm run install-skills # copy skills/agpa → ~/.agents/skills/agpa
npm pack # produce agpa-dsh-plugin-<version>.tgz
```
## Installing into dsh
Published to npm as **`agpa-dsh-plugin`**.
```bash
# 1) add to a profile from the registry (a bundle layer; takes effect after restart)
dsh plugin --profile web add agpa-dsh-plugin
# 2) install the model workflow skill: the package ships skills/agpa, just copy it
# (when working from a git clone, use: npm run install-skills)
cp -r ~/.dsh/profiles/web/node_modules/agpa-dsh-plugin/skills/agpa ~/.agents/skills/
# 3) after restarting, check offline that the agpa row appears in the composed tree
dsh --profile web --dump-config | grep -A3 agpa
```
Local development (when editing the source):
```bash
npm run build && npm pack # prepack runs build automatically
dsh plugin --profile web add ./agpa-dsh-plugin-<version>.tgz
```
If the row appears but the tools do not, it is a code problem (check the dsh
logs); if the row never appears, it is a composition problem (the `name` did not
resolve, or `files` dropped `cordis.patch.yml`).
## Environment variables
| Variable | Purpose |
|---|---|
| `AGPA_MCP_CMD` | Override the AGPA MCP launch command (space-separated). Defaults to `npx -y -p @eiainano/agpa@0.1.10 agpa-mcp`. For local development, point it at `tsx /path/to/agpa/src/main.ts` |
| `AGPA_AUTOTRACK=1` | Enable automatic event tracking: one `agpa-hook auto` ingest process per normalized event |
| `AGPA_HOOK_CMD` | Override the ingest command (space-separated). Defaults to `npx -y -p @eiainano/agpa@0.1.10 agpa-hook auto` (works out of the box); for local development point it at `tsx /path/to/agpa/src/cli/hook.ts auto` |
| `AGPA_DEBUG=1` | Print AGPA MCP stderr plus every received event type (`[agpa][ev]`) and normalized payload (`[agpa][autotrack]`) |
| `DSH_AGENTS_HOME` | Target skills root for `install-skills` (defaults to `~/.agents`) |
The bridge and ingest subprocesses always inject `AGPA_TOOL_SOURCE=dsh`, so
events are tagged with the correct tool source (stored as `tool_source:"dsh"`,
coexisting with Claude Code's `claude-code` in the same store).
## Status / roadmap
- [x] **Phase 1 (this repo)**: 7 tool shells + MCP bridge (verified against the real AGPA engine — returns stats with `tool_source=dsh`) + SKILL
- 2026-09-09: verified end-to-end on a **real dsh 0.1.2-rc.1**: installed into both profiles with
`dsh plugin --profile web|headless add <tgz>`; a real headless task successfully called
`achievement_stats`, and the bridge returned real data from the shared store (75/212, level 6, 10,592 XP).
- [x] **Phase 2 (this repo, 0.1.11 on npm)**: automatic event tracking, verified end-to-end
on a real machine and working out of the box.
- `events.ts` subscribes to dsh `session/event` and normalizes `tool/call` + `tool/result` →
PostToolUse(Failure), and `user/message` (`source.kind==='user'`) → UserPromptSubmit, into
CC-style payloads (`source:'dsh'`); each payload spawns one `agpa-hook auto` ingest process
(`hook-runner.ts`).
- After a real headless run (Write/Bash), `~/.agent-achievements/profiles/neo/event.log` gained
rows with `tool_source:"dsh"` — `file.create` / `file.write` / `command.run` / `tool.complete`
and friends.
- **Key gotcha found on the real machine**: `tool/result.data` has **no** top-level `callId`
(only `tool/call` does); the call id needed for the `${turn}:${step}:${callId}` correlation key
lives at `result.data.message.source.callId` (mirrored at `content[].toolCallId`). Event names
are always taken from the pinned dsh 0.1.2-rc.1 (`session/event` also broadcasts noise types
such as `permission/preset` and `assistant/chunk`, all of which are ignored).
- AGPA **0.1.10 is published** with the `agpa-hook` bin, so the plugin's default ingest command
`npx -y -p @eiainano/agpa@0.1.10 agpa-hook auto` works out of the box (verified with
`AGPA_AUTOTRACK=1` alone). For local development you can still point `AGPA_HOOK_CMD` at a
checkout's `tsx src/cli/hook.ts auto`.
- The ingest subprocess is spawned `detached`: a short-lived headless process exiting will not
SIGTERM an in-flight ingest (the first `npx` run has a cold-cache download and can be slow;
detaching guarantees no events are lost — long-lived web sessions were never affected).
- [ ] **Phase 3**: an optional `dsh.client` XP/achievement widget, or simply reusing the AGPA
dashboard (:3867).
## Known caveats
1. ~~dsh cannot start on this machine~~ (fixed 2026-09-09, re-fixed 2026-09-15): the managed Node
runtime **replaces its versioned prefix on upgrade** (`versions/22.22.2-2` →
`versions/22.22.2-3`), wiping any globals installed inside it. So dsh and pnpm no longer live
in that prefix — they live in the stable `~/.local/share/dsh-runtime`. `~/.local/bin/dsh`
reads the Node version dynamically from `~/.workbuddy/binaries/node/versions/current` and puts
`$DSH_HOME/bin` (pnpm) plus that Node's bin on PATH. When installing a global package into
such a prefix you must invoke npm explicitly as
`"$NODE" "$PREFIX/lib/node_modules/npm/bin/npm-cli.js" i -g <pkg> --prefix "$PREFIX"`,
otherwise the npm wrapper's shebang resolves to whatever `node` is on PATH.
2. **Runtime resolution**: an out-of-tree plugin's bare imports of `@deepseek-ai/dsh-tools`
(a value import) and `@deepseek-ai/cordis` (type-only) are mapped by the dsh host into the
in-box tree, and both resolve on 0.1.2-rc.1 (this plugin's `defineTool` value import is
verified by a real run).
3. **DSH is a developer preview**: `cordis.patch.yml`, the `dsh` field and event names can all
change between versions. `package.json` only declares `@deepseek-ai/cordis` as a peer (types
and closure injection are provided by dsh).
4. **Event names are taken from the pinned version**: the types this plugin subscribes to were
verified on the pinned 0.1.2-rc.1 (see Phase 2). If dsh is upgraded, check that version's
`SessionEventMap` first — do not copy the master docs.
5. The tool schemas use JSON-schema-style literals; AGPA does not currently do strict validation
of numeric ranges, and the descriptions state the allowed range.
6. If it will not install into a profile, first confirm that `npm pack`'s `files` really includes
`cordis.patch.yml`.
Install
dsh plugin --profile web add github:eiainano/agpa-dsh-plugin
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install agpa-dsh-plugin from the hub
- 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.