拷问 (Grill) mode — DSH plugin
A per-conversation switch in the composer tool row, leading the
conversation.input.left group. The permission selector and the plan chip
render before that group in their own mode cluster, so the switch is not
literally adjacent to the permission selector. While it is on, every user
request must pass an analysis gate before the agent plans or executes:
- analyze the request (goal, scope, inputs, constraints, vague terms, contradictions, silent assumptions);
- classify each finding as blocking or non-blocking;
- on any blocking finding, stop and ask through
ask_user_question, naming the exact ambiguity, why it blocks, 2–4 concrete options (recommended first) and a concrete repair proposal; - re-analyze after each answer until no blocking finding remains;
- weigh the task: trivial work (one file, read-only, reversible, obvious acceptance) is done directly with a one-line reading; anything non-trivial (multiple files, a new feature, a change to shared or user-visible behavior, an irreversible step, or a change to plugins/profiles/prompt policy) is planned and then stopped — no state-changing tool call until the user approves;
- the non-trivial plan message carries the confirmed requirements, exact file
paths and per-file changes, behavior before/after, verification, risks and
rollback, the choices that would otherwise be decided silently, and a
go-ahead line; approval is requested through
ask_user_questionin the same turn (Approve and execute/I want to change the plan/Stop for now).
The gate applies to later messages too, including short follow-ups such as "继续" or "改一下", but a follow-up that continues an already approved plan is acted on rather than re-planned.
How it works
- The mode is per Session and log-derived.
/grill onand/grill offare ordinary slash commands; agrillsession projection folds the existingcommand/run/command/doneevents, so resume, fork and replay restore the switch position without any custom session event type. - While the folded state is active, the host contributes the
grill:policyprompt section to every model request of that Session (placed just after the plan-mode policy section). Inactive Sessions carry no extra prompt text. - The composer switch is only a surface: clicking it executes
/grill onor/grill offthrough the same command path, and the projection frame updates the switch. There is no separate client-side mode state. - The command is also usable directly: type
/grill,/grill onor/grill offin any conversation./grillwith no argument turns the mode on (it never toggles), and there is nostatussubcommand. - Subagents. A forked subagent is seeded with the parent's log prefix, so an
enabled mode reaches it and the protocol applies there too. A delegated child
cannot ask a human — its
ask_user_questionis refused withDELEGATED_CALLER— so the protocol carries a subagent exception that turns a blocking question into "report it in your final result". That exception is appended to the built-in text and to any customsection(see Configuration).
Bundle layout
| File | Role |
|---|---|
index.js |
Host half: projection unit, /grill command, grill:policy prompt section, row-config validation |
client.js |
Browser half: the composer switch registered in conversation.input.left |
cordis.patch.yml |
Inserts the Host row into the profile |
locale/*.json |
Plugin Manager metadata (title/description) |
icon.svg |
Original artwork for the plugin inventory |
scripts/test.mjs |
Runs both integration scripts against a DSH source checkout |
tests/*.mts |
Integration scripts for the host and browser halves |
Configuration
The plugin row accepts two optional fields:
- id: grill
name: 'dsh-grill'
config:
section: |-
...custom model-facing protocol text...
appendSubagentNote: true
section replaces the default prompt protocol; leave it out (or blank) to use
the built-in text documented above. appendSubagentNote (default true)
appends the subagent exception to whichever text is in effect, so overriding
the protocol never re-enables ask_user_question inside a delegated child. Set
it to false only when the custom section already carries its own delegated
agent rule; an author-supplied text that names DELEGATED_CALLER is detected
and left alone.
Invalid configuration fails the read at plugin load instead of being ignored:
section and appendSubagentNote must have their declared types, unknown keys
are rejected with the key names, a blank section falls back to the built-in
text, and the effective protocol text is capped at 20000 characters.
Install
Install the bundle from its git repository:
dsh plugin add github:Kingzhoudk/dsh-grill
# or the explicit git spec
dsh plugin add git+https://github.com/Kingzhoudk/dsh-grill.git
The plugin manager records the dependency and enables the bundle. The Host row activates through the live profile patch; the browser half is served as a client module and appears in the composer of every conversation after the page picks up the new client graph.
The package declares engines.dsh: ">=0.2.2-rc.1 <0.3.0", so the plugin manager
refuses it on an incompatible runtime. The range is written explicitly because
prereleases participate in the check and ^0.2.2 would reject 0.2.2-rc.1.
Testing
tests/*.mts are integration programs, not node:test suites: they import real
Harness internals, so they need a DSH source checkout on the module-resolution
path. scripts/test.mjs copies them into that checkout, runs both with tsx,
and removes the scratch directory:
# from this package
DSH_CHECKOUT=/path/to/deepseek-harness node scripts/test.mjs # or: DSH_CHECKOUT=... npm test
DSH_CHECKOUT is required and must point at a deepseek-harness source checkout;
DSH_GRILL_PLUGIN defaults to this package. The runner exits non-zero when a
script fails or the checkout lacks tsx, jsdom, or @testing-library/react.
It prints GRILL_HOST_INTEGRATION_OK / GRILL_CLIENT_OK on success.
Verification
Both halves were exercised before delivery and are re-exercised by npm test:
- Host — a real Harness host context (session log, projection registry,
system prompt, command registry, agent loop with a scripted LLM adapter) runs
with the plugin mounted.
/grill oncommits through the real command executor and publishes{ active: true }on the client-visiblegrillprojection; the next model request's system prompt carries the protocol (and its subagent exception);/grill offremoves it; an invalid argument errors without changing state. The fold is checked directly for replay safety and reference stability, and the row-config validation is checked for type, unknown-key, and size rejection. - Client — the real
client.jsbundle is loaded through a stand-inwindow.__ModuleLoader__and the registered slot component mounted with the Harness React instance: it rendersrole="switch"in both states, clicking executes/grill on//grill off, a failed command surfaces in the title without crashing the slot, a deniedcommands/executedisables it, a handler error outcome is shown, a response that settles after a Session switch is dropped, and missing optional props degrade gracefully. - Installed profile — the bundle installs with
application: applied, the host row reportsfiberPhase: active, the live page'sconversation.input.leftslot lists the activegrill-switchoccupant atorder: 10(leading that group; the permission selector and plan chip render before the group), and the localized Plugin Manager title/description and icon resources resolve from the package.
Not verified without browser control: the rendered pixels (exact spacing and light/dark appearance) and a literal mouse click in the running page. If an already-open tab does not show the switch, refresh the page once to pick up the new client module graph.
Uninstall
Remove the bundle with the Harness plugin manager (remove_bundle,
target dsh-grill); the profile patch selection and dependency entry are
cleaned up by the manager.
License
MIT — see LICENSE.
No comments yet. Be the first to write one.