dsh-mama-cheer
A keyboard shortcut that drops one short, configurable encouraging message into the live conversation of dsh-TUI — without leaving the terminal and without waiting for the current turn to end.
press ctrl+alt+m → the message reaches the model at the earliest legal moment
What it actually does
A model request already in flight cannot be appended to: the request has left the process and the stream is one-way. There are exactly two places a new user message can reach the model, and this plugin picks between them per keypress:
| Model state | Delivery | What the model sees |
|---|
| idle | Agent.followup | a normal next turn |
| running, a tool call is in flight | Agent.steer | the message at the next step boundary — no interrupt, the running tool finishes untouched |
| running, only streaming text | Agent.cancel({ keepInbox: true }) + followup | the half-written answer is kept (interrupted: true) and the model reads the message in the new turn immediately |
The middle row is the point of the default smart policy: interrupting a tool call leaves a durable tool call aborted before dispatch record and the host's own "interrupted by user" notice, while interrupting a text stream costs almost nothing. Only the third row ever interrupts. always and never are one setting away if you disagree with that judgement.
Two facts this design rests on, both read from the shipped code and one of them verified locally:
- An interrupt does not discard the generated text.
dsh-agent-loop commits the partial answer as an assistant/message with interrupted: true on the model-visible surface, so it stays in the conversation and in the next request.
Agent.cancel() is called with keepInbox: true, the same option the host's own Ctrl+Enter path uses, so messages you already queued are not dropped.
Install
dsh plugin --profile dsh-tui add dsh-mama-cheer
Then restart the TUI with /restart.
Local checkout during development:
dsh plugin --profile dsh-tui add file:C:\path\to\dsh-mama-cheer
The installer hard-links the package's files list into the profile, so after adding a new module under lib/ you must remove and re-add — running add again prints "Already up to date" and changes nothing.
Compatibility
| Item | Value |
|---|
| Host | dsh-TUI 0.10.1-era seams: tuiShortcuts, tuiSettingsSections, tuiToast, plus the public ctx.agents registry |
| Manifest | manifestVersion 0.15, host facet only, no permissions requested |
| Runtime | Node `^22.19 |
| Platforms | The delivery path is platform-neutral (it drives the agent inbox). The toast feedback needs a TUI notification sink; without one it degrades to log-only. |
Every host service is optional and retried until it is ACTIVE: a missing seam disables exactly that one capability (no shortcut, no settings card, or no toast) and never the boot.
Configuration
Every key has a default; a missing key means "no behaviour change", never a failed boot.
| Key | Default | Meaning |
|---|
text | 妈妈加油! | Text delivered to the model |
combo | ctrl+alt+m | Shortcut to register; must carry ctrl or alt (the host refuses modifier-less and reserved combos) |
interruptMode | smart | smart = interrupt only while no tool is in flight · always = interrupt whenever the model is running · never = never interrupt, always steer |
display | toast | toast = transient notice, message stays out of the transcript · none = completely silent · bubble = the message appears as a normal user message |
toastMs | 3000 | Toast lifetime (host clamps to 500–12000 ms) |
keepInbox | true | Preserve already-queued messages across an interrupt |
retryMs / retryLimit | 400 / 50 | Readiness retry cadence and budget (composition-only; not shown in the settings screen) |
Settings screen
All six user-facing keys are also editable in /settings (section Mama Cheer), with zh/en labels, hints and select options:
/settings → Mama Cheer
Cheer message [text]
Shortcut [text]
Interrupt policy [select: Smart | Always interrupt | Never interrupt]
Display [select: Toast | Silent | User message]
Toast lifetime (ms) [number]
Keep my queued messages [boolean]
Values are read live on every keypress — no restart, no rebind dance:
- the composition entry is the base layer and the settings layer is merged on top at press time;
- changing
Shortcut rebinds immediately (the stale binding is disposed first, so the old combo stops firing);
- the two feedback toasts are zh/en pairs like the rest of the copy, resolved against the language
/lang mirrors into the dsh-tui settings namespace (the settings card's own labels are localized by the host).
Known limitations
- The interrupt notice row cannot be changed or removed. It is a host-owned projection (
dsh-adapter/channel/projection.js) and the renderer seam forbids shadowing built-in event types. It appears only when the policy actually interrupts.
- A custom transcript row is not possible in this build. The renderer seam expects a plugin to append a log-only session event, but
Session.append() has no way to set the ignorable: true envelope marker that the persistence contract requires for out-of-repo event types. Measured locally: an unknown non-ignorable event makes the session log unreadable on the next load (SessionFormatUnsupportedError, "refusing to interpret the log"). This plugin therefore never writes session events of its own.
- Keyboard only in the plain chat state. While a picker, dialog or scene is open, the keyboard belongs to that overlay and the shortcut does not match.
- Targeting is heuristic. One window normally means one live agent; with several live agents the plugin prefers a running one, then the session whose events were seen most recently.
- Headless hosts have no keyboard and no toast sink. The message path still works; the feedback does not.
Release and versioning
- Repository: VviLliAm-qwq/dsh-mama-cheer
- Published to npm as
dsh-mama-cheer (--access public --provenance).
- Releases are tag-driven: pushing a
v* tag runs .github/workflows/release.yml, which re-runs the full verification, checks that the tag equals the package.json version, and publishes. No long-lived npm token is stored — the workflow exchanges its OIDC token through npm trusted publishing.
- SemVer;
CHANGELOG.md records every version, and the manifest version moves with the package version.
Verification
npm run verify # encoding + manifest + tests + pack layout
node --test # 35 tests: entry export shape, placement matrix, delivery, settings wiring, shortcut readback
node tools/probe-plugin.mjs <dir> # headless real composition; exit 0 and "shortcut registered ..."
Probe output on a passing run:
seams settings=1 sections=1 shortcuts=0 agents=1 toast=0
settings namespace dsh-mama-cheer registered
settings section registered
shortcut registered ctrl+alt+m (confirmed by the host registry)
ready after 2 attempt(s)
VERDICT: PASS — the plugin applied and its registrations were accepted
shortcut registered <combo> is a verified claim, not an echo of the call. The host answers every refusal path — an unparsable combo, a reserved built-in, a duplicate, malformed options — with a no-op disposer, so the plugin reads its own registration back through the host's list() and only logs registered once the combo is really in the registry:
- that line, and nothing weaker, is what says the key works. Its absence, or a
shortcut refused <combo> (reserved, malformed, or duplicate) line, means the keypress will never reach the plugin — the readiness loop keeps retrying a refused combo until its retryLimit budget runs out;
- on a host that serves no usable
list() the binding cannot be confirmed: the plugin logs shortcut <combo> is UNVERIFIED, keeps whatever binding it may hold, and does not claim success.
The first-tick refusal followed by a successful retry is the documented seam behaviour, not a defect — and that retry now only stops at a confirmed registration.
Design notes
- The entry module (
lib/index.js) re-exports exactly name, Config, apply. Extra symbols change how the loader wraps the activation, and every seam registration is then refused with requires a live Cordis activation context — the plugin looks half-alive.
- Optional services are read strictly first, then loosely, and every registration is retried until the provider is ACTIVE.
- No permissions are requested: the plugin intercepts nothing, observes nothing, and stores nothing outside its own log file (
~/.dsh-tui/dsh-mama-cheer.log, capped at 64 KB, not written under node --test).
- A failing keypress is contained: it logs and toasts instead of throwing, so it can never take the keyboard down for other bindings.
src/ is intentionally absent — the plugin is plain ESM with no build step, so the published lib/ is the source of truth.
License
MIT