Skip to content
dsh.fish
Bundle

@gausszhou/dsh-where-am-i

Inject a neofetch-like device info block (host, OS, kernel, arch, CPU, memory, uptime, timezone, locale, shell, node) into each brand-new DeepSeek Harness session once; existing sessions are untouched.

Source
gausszhou
License
MIT
Updated
Updated 2 days ago

Readme

# dsh-where-am-i

> Neofetch for DeepSeek Harness.

**English** | [简体中文](./README.zh-CN.md)

A DeepSeek Harness (dsh) plugin that injects a **neofetch-style device info
block** into **every brand-new session** — OS distro, kernel, architecture,
hostname, CPU, memory, uptime, timezone, locale, shell/terminal, and Node
version. It is injected **exactly once, when a new session takes its first
step**, and then persists in that session's own history; existing sessions are
completely untouched.

## Behavior at a glance

| Scenario | Behavior |
|---|---|
| **Brand-new session** (first message after creation) | a device-info user message is injected; the model sees it at the start of the conversation and it persists until compaction |
| **Existing sessions** (created before a restart, resumed, or continued) | the system prompt is **byte-identical** and **no** message is appended — the request prefix and its KV cache are fully preserved |
| **Later steps of the same session** | never re-injected (the message is already durable history) |
| **Subagent sessions** (ephemeral workers spawned by the subagent/workflow tools) | skipped by default (avoids repeating per fan-out); opt in with `includeSubagents: true` |

## Usage

Install into a dsh profile (once published, by package name):

```bash
dsh plugin --profile web add "@gausszhou/dsh-where-am-i"
```

For local development use a link dependency (same pattern as
`dsh-opencode-session-id`): add `"@gausszhou/dsh-where-am-i":
"link:/home/gauss/Code/gausszhou/dsh-where-am-i"` to
`~/.dsh/profiles/web/package.json` dependencies and list it in
`dsh.profile.bundles`; the plugin's own `cordis.patch.yml` (via
`dsh.bundle.patch`) performs the insert.

After installing, **restart dsh web** (`systemctl --user restart dsh-web`) so
the bundle takes effect. Zero configuration needed.

## What the model sees in a new session (real machine)

```markdown
System info (the machine this dsh session runs on; sampled at session start):
host: gauss-KP
user: gauss
os: Ubuntu 26.04 LTS
kernel: 7.0.0-29-generic
arch: x86_64
cpu: 6 × Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
memory: 7.9 GiB used / 15.0 GiB total
uptime: 1d 4h 3m
timezone: Asia/Shanghai (GMT+08:00)
locale: zh_CN.UTF-8
shell: /bin/bash
term: dumb
node: v24.19.0
```

## Configuration

All options are optional, with the defaults shown:

| Key | Default | Meaning |
|---|---|---|
| `fields` | all 13 | emit only the listed field lines, in the default order, e.g. `[os, arch, timezone]`; unknown names are dropped; an empty-after-filter list falls back to all |
| `includeSubagents` | `false` | when `true`, also inject into subagent sessions (skipped by default to avoid repeating per fan-out) |
| `verbose` | `false` | print the injected snapshot to stdout (visible via `journalctl -u dsh-web` on a web deployment) |

Field list (emission order): `host` `user` `os` `kernel` `arch` `cpu`
`memory` `uptime` `timezone` `locale` `shell` `term` `node`. Fields that
cannot be sampled (e.g. `shell` on Windows without a `SHELL` variable) are
omitted automatically.

```yaml
# ~/.dsh/profiles/web/cordis.patch.yml
- id: where-am-i
  config:
    fields: [os, kernel, arch, cpu, memory, timezone]
```

## Design: what dsh prompts already carry

Before implementing, the existing prompt surface was surveyed
(`dsh-system-prompt` section/variable registration, `dsh-agent-loop` variable
providers, the runtime-context packages). Existing facts are listed below;
this plugin **only fills the gaps and never duplicates**:

| Existing fact | Source | Location |
|---|---|---|
| `You are an AI agent powered by DeepSeek Harness.` | `dsh-system-prompt` | system prompt section `harness:identity`, order −100 |
| DSH source checkout path | `dsh-app-boot` / `dsh-web-app` | section `harness:source`, order −99 |
| Web GUI URL | `dsh-web-app` (web profile only) | section `app:web-surface`, order −98 |
| Deployment persona | config | section `deployment:persona`, order 0 |
| Tool guidance (read/write/edit/bash/web_search…) | each `dsh-tool-*` | sections `tool:*`, orders 100–199 |
| `{{provider}}` `{{model}}` `{{cwd}}` (session cwd, evaluated per assembly) | `dsh-agent-loop` | prompt variables |
| File sandbox policy, approval policy | `dsh-sandbox-policy` / `dsh-user-approval` | runtime context (user-role snapshots) |
| Timestamp + **browser** timezone + elapsed duration | `dsh-time-context` (not enabled in the default composition) | runtime context |
| tmux session/window/pane location | `dsh-tmux-context` (not in the web composition) | runtime context |
| `AGENTS.md` / `CLAUDE.md` workspace instructions | `dsh-agent-instructions` | durable user message |

**Completely missing** (what this plugin adds): OS/distro name, kernel
release, CPU architecture, hostname, CPU model and core count, total/free
memory, uptime, host locale, shell/terminal, Node version, and the host
process timezone.

### Deliberate omissions

- **`cwd`** — already provided live by the `{{cwd}}` prompt variable, and the
  harness explicitly warns against inferring it from the checkout path.
- **date / current time** — a frozen wall clock in durable history would go
  stale; live timestamps come from `dsh-time-context` per request when
  enabled.
- **ASCII logo / desktop wallpaper** — token noise with no agent value.

## Why a persistent user message, not a system prompt section

The first version contributed a `systemPrompt.section()`, but that renders on
*every step of every session* once mounted — it can never be "once", and it
rewrites the shared system-prompt prefix of already-running sessions (KV
cache invalidated from the first changed token).

The current implementation uses the same seam as `dsh-time-context` /
`dsh-tmux-context`: a **prepended `agent/pre-step` waterfall listener** that
first delegates downstream (`await next()`) and, only when the step enters,
appends **one durable, source-attributed user message** to the batch. The
agent loop persists every message in the batch
(`session.append("user/message", …)`) after `step/start`, so:

- the injected message becomes **that session's own history** (visible from
  the start, retained until compaction);
- **no system prompt section is registered** → existing sessions' system
  prompt is byte-identical to before, prefix and KV cache fully preserved;
- later steps — of this session or any other — see the message already in
  history and never inject again (across restarts and plugin hot reloads,
  because the decision reads only the session log itself).

"Brand-new session" is decided from the session log alone: at pre-step time,
`agent.session.events` holds no `user/message`, `assistant/message`, or
`tool/result` event yet (the in-flight prompt of step 1 is persisted only
after the step enters — `dsh-time-context`'s README states this explicitly).
Any resumed, continued, or already-injected session has at least one such
event and is skipped, forever. A batch with no user-sourced message at all
(plugin-only runtime-context or scheduled turns) is also never injected into.

Device facts are sampled **at injection time** (the new session's first
step), so uptime/memory are as fresh as possible; the message is per-session
history, so byte-level differences between sessions cost nothing.

### Bottom line for "existing session prefixes"

- System prompt: the plugin registers **no section**; `renderPrompt` output
  for existing sessions is identical to before installation (asserted by the
  integration test).
- Session history: no message is appended to existing sessions.
- The only affected surface is **new sessions**: injection happens before
  their first request, i.e. it is part of that session's own prefix — there
  is no old cache to invalidate.

## Implementation details

- **Zero subprocesses**: no `sw_vers`, no `uname` — everything comes from
  `node:os`, one synchronous `/etc/os-release` read (Linux/WSL: `PRETTY_NAME`,
  falling back to `ID VERSION_ID`), and `Intl`.
- Architecture names follow neofetch's mapping: `x64→x86_64`,
  `arm64→aarch64`, `ia32→i386`, `arm→armv7l`.
- macOS: the marketing version is derived from the Darwin release (Darwin 24
  ↔ macOS 15, 19 ↔ 10.15; older releases are not guessed).
- Timezone is the **host process** timezone (IANA name + current UTC offset,
  DST-aware) — a different concept from `dsh-time-context`'s browser zone.
- The injected message satisfies the harness message contract
  (`assertMessageEventShape`: non-empty `id`, `role: "user"`, `source.kind`,
  `content` array); its source mirrors time-context's plugin-snapshot shape
  (`{ kind: 'plugin', plugin: 'where-am-i', form: 'snapshot', sections: [...] }`)
  so UI/trajectory attribution is consistent.
- `inject: ["agents"]` (same as time-context): the plugin does not load in a
  tree without the agents registry.

## Verification

```bash
node test/verify.mjs          # unit: os-release parsing / arch mapping / macOS naming / byte & uptime formatting / timezone offset / config normalization / render subset / decision gates (user source, history, subagent) / message shape / no-cwd-no-date invariants + real-machine snapshot
node test/smoke-apply.mjs     # wiring: new session injects once, existing session zero injection, same session never re-injects, no-user-source batches skipped, reject/abort passthrough, subagent gate, fields subset
node test/integration-real.mjs # real cordis + dsh-system-prompt + dsh-session: system prompt untouched, injected message persisted by a real Session and visible in derived history, no re-injection
```

## Notes & limitations

- The snapshot is sampled at each new session's **first step**: uptime/memory
  go stale during that session (neofetch-at-session-start semantics; the
  message is history and is not refreshed).
- Without `/etc/os-release` on non-Windows/macOS systems, the `os` line falls
  back to `os.version()`/`os.type()` and may be less friendly than a distro
  name.
- The `user` line is not redacted — the model can see the local username
  running dsh (like neofetch). Drop `user`/`host` via `fields` if you care
  about privacy.
- Host-machine facts are reported; if dsh runs inside a container or remote
  execution, the kernel reported is the container's/remote one (e.g. WSL
  shows the WSL kernel).
- The message costs tokens on every request until compaction hides it; this
  is why existing sessions and subagents are excluded by default — the cost
  is paid once, only by genuinely new conversations.

Install

dsh plugin --profile web add github:gausszhou/dsh-where-am-i

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source