Skip to content
dsh.fish
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

  • 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