Bundle
dsh-plugin-cli-session
Resume-capable CLI runner for DeepSeek Harness: conversational text or machine-readable JSON from one plugin
- Source
- ghbhiee
- License
- MIT
- Updated
- Updated 3 days ago
Readme
# dsh-plugin-cli-session
A resume-capable CLI runner for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). It replaces the shipped one-shot headless runner with one that can continue a previous session and, when asked, print a machine-readable envelope instead of prose.
It is the merge of two hand-copied profile runners (a conversational one and a `claude -p`-style one) whose `runner.js`/`startup.js` had drifted into near-duplicates. Everything they actually shared — session resolution, the drive loop, summarisation, exit handling — is one implementation now; what differed is configuration.
## Install
Not on npm yet. The built `lib/` is committed, so it installs straight from
GitHub — no download, no build step:
```sh
dsh plugin --profile chat add github:ghbhiee/dsh-plugin-cli-session
```
Or from a local clone (a `link:`, so a local rebuild is picked up):
```sh
git clone https://github.com/ghbhiee/dsh-plugin-cli-session.git
dsh plugin --profile chat add ./dsh-plugin-cli-session
```
Sibling plugins install the same way: [dsh-plugin-workbench](https://github.com/ghbhiee/dsh-plugin-workbench),
[dsh-plugin-mobile-shell](https://github.com/ghbhiee/dsh-plugin-mobile-shell),
[dsh-plugin-cli-session](https://github.com/ghbhiee/dsh-plugin-cli-session).
## Develop
```sh
pnpm install
pnpm run check # typecheck → vitest → tsdown build
```
Because `lib/` is versioned (it is what a git install serves), rebuild and
commit it with every source change.
## Two flavours, one plugin
Conversational (`~/.dsh/profiles/chat/cordis.patch.yml`):
```yaml
- id: cli-runner
config:
request: !!js ctx.cliStartup
sessionTag: chat-cli
announceSessionId: true
```
Machine-readable (`~/.dsh/profiles/api/cordis.patch.yml`):
```yaml
- id: cli-runner
config:
request: !!js ctx.cliStartup
sessionTag: api
announceSessionId: false
```
A patch replaces the targeted row's whole `config`, so `request` has to be restated in both.
| Field | Default | Meaning |
|---|---|---|
| `sessionTag` | `cli` | Written to each session's `agentPreset`; also scopes `--list` and `--resume`, so two profiles in the same directory never see each other's sessions |
| `announceSessionId` | `false` | Write `session: <id>` to stderr (stdout stays clean for the answer) |
| `exitGraceMs` | `1500` | How long to wait for a graceful exit before forcing one; must be a non-negative number |
## Usage
```sh
dsh --profile chat "explain this file" # new session
dsh --profile chat -r "follow-up" # resume the newest session here
dsh --profile chat -s <id> "follow-up" # resume a specific session
dsh --profile chat -w ./scratch "start here" # new session in another directory
dsh --profile chat -l # list this profile's sessions here
dsh --profile api -o json "summarize this" # {"type":"result",…}
dsh --profile api -o stream-json "summarize" # init line, then the result line
```
| Flag | Meaning |
|---|---|
| `-o, --output-format <text\|json\|stream-json>` | Shape of what is printed; `text` is the conversational one |
| `-n, --new` | Start a new session (the default) |
| `-r, --resume` | Continue the newest session of this profile in this directory |
| `-s, --session <id>` | Continue a named session |
| `-w, --workdir <dir>` | Create and enter a directory before starting a new session |
| `-l, --list` | List this profile's sessions here and exit |
| `--json-schema <schema>` | Ask the model for JSON in this shape (a prompt, not a constraint) |
`-o json` / `-o stream-json` emit `{type, subtype, is_error, duration_ms, num_turns, result, session_id, usage?}`. `stream-json` prefixes a `{type:"system",subtype:"init",…}` line carrying the session id, cwd, provider, and model.
## Misconfiguration fails at boot
The row's `request` comes from `!!js ctx.cliStartup`. Wire it wrong and the config schema's defaults quietly produce `action: 'new'` with an empty task — a session created and a model call spent on nothing. The runner refuses that at load instead, and says what to write:
```
cli-runner: no task to run. The row's config.request should be wired to the
startup service, as in `request: !!js ctx.cliStartup`.
```
A blank `sessionTag` (the label `--list` and `--resume` scope by) and a negative `exitGraceMs` are refused the same way.
## Tests
`pnpm test` covers both halves without an API key: the runner's pure core (which assistant message is returned, how sessions are scoped by tag, that `--json-schema` is prompt text) and the command-line contract (every accepted shape, every rejected combination, and that `--workdir` chdirs *before* publishing so the sandbox root follows).
## Known limitations
- **`--json-schema` is a prompt, not a constraint.** The schema is appended to the task text asking for conforming JSON; it is not the provider's structured-output mode. A model can still answer with code fences or commentary, so parse defensively.
- **Only the last assistant message is returned.** Intermediate turns are in the session log, not in `result`.
- **`is_error` covers any non-`completed` turn reason**, including a turn that never started.
- **`sessionTag` is stored in `agentPreset`**, a field the harness defines for agent presets. It works as a session label today, but it is a borrowed field, not a documented extension point.
- **The hard exit is a fallback, not a design.** `exitGraceMs` after the graceful exit request the process is killed, which can truncate a very slow flush.
Install
dsh plugin --profile web add github:ghbhiee/dsh-plugin-cli-session#0f487e74c81ed102c6899440d9f5d65e8e9eabda
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 dsh-plugin-cli-session 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.