feat(agents): steerable turns, PTC, and a Nushell-only dsh (#473)

* feat(agents): use the shell's own dialect; allow 5 s retry backoff

Assisted-by: DeepSeek Harness (deepseek-flash)

* feat(dsh): make Nushell the only shell

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): wait for CI in the background

Pushes to a PR often start multi-minute CI runs, and an agent that holds
the turn open on a foreground watch reports nothing until it ends. Require
the pushed head and running checks to be reported first, the watch to run
in the background, and the result to be collected after independent work
or after ending the turn.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): keep the shell-handoff rule platform-neutral

The global rules carry no Windows assumptions: drop the cmd, pwsh, and
wsl handoff examples and keep the POSIX forms plus the dsh refusal.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(dsh): keep the shell-swap docs POSIX-only

This repository manages Linux and Darwin hosts, not Windows: drop the
Windows handoff examples and terminal-backend wording from dsh.md. The home
patch also disabled pwsh-sandbox redundantly because dsh-base already
disables that row on POSIX; keep only the active bash-sandbox disable and
verify the composed rows with --dump-config.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): keep the global rules runtime-neutral

Global rules are loaded by every agent CLI, so they must not describe one
runtime's behavior. Drop the dsh-specific sentence and wording while keeping
the expectation to prefer Nushell structured pipelines and Python. Runtime
behavior stays documented in home/base/tui/agents/dsh.md.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): default shell work to Nushell

Keep the Nushell preference explicit in the runtime-neutral global rules, and
keep runtime behavior in runtime docs. Bash stays a narrow fallback for tasks
Nushell cannot do.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): tighten global-rule scope and CI guidance

Keep global rules runtime- and project-neutral, and state that boundary in the
file. Remove the duplicated CI turn-steering text in favour of the Communication
rule, resolve the watch run before watching instead of relying on
gh pr checks --watch alone, name the working-directory parameter generically,
and distinguish a bounded retry backoff from a fixed completion wait.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): never estimate a CI run's duration

A CI run has no predictable length, so do not turn "it looks short" into a
reason to wait. The rule is unconditional: report the head and started checks,
start a background watch, and end the turn; collect the result on completion.
The eval scenario now uses an unknown duration.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): move maintenance guidance to the README

The runtime/project boundary is guidance for editing the rules, not a rule
agents need loaded every session. The README's Maintaining global rules
section already owns that audience; the loaded rules keep only behavior.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(dsh): render the home patch from a YAML template

Move the home-layer cordis patch into dsh-cordis.patch.yml, substitute the
bundle and Nushell store paths with replaceVars, and link the rendered file
through home.file.source. This module is the only writer of
~/.dsh/cordis.patch.yml, the current host has no such file, and the profile
patch is a separate layer.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(dsh): install the shell plugin through NUR withPlugins

Replace the local runCommand bundle, peer symlink, and home-layer YAML file
with the plugin package and dsh.withPlugins from ryan4yin/nur-packages#43. The
package wrapper adds a generated --patch overlay after the profile layer, so
dsh no longer writes or manages ~/.dsh/cordis.patch.yml. The NUR input is
temporarily pinned to the PR branch and its commit; point it back at main once
the NUR PR merges.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): expose the NUR package set as nurPkgs

Pass one per-system NUR package set through specialArgs instead of repeating
nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system} at each call site.
The agents, rime, and idols-ruby modules now use the short binding.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): use nurPkgs in package overlays

The overlays receive specialArgs too, so they can use the per-system NUR
package set instead of rebuilding the long input path from prev. This leaves
only the definition in outputs/default.nix.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): name the per-system NUR set myNurPkgs

Leave room for consuming another NUR repository later, and match the existing
mylib/myvars naming.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): consume the merged NUR package set

nur-ryan4yin now points at main after ryan4yin/nur-packages#43 was squash
merged; the lock records the merge commit. No branch ref remains.

Assisted-by: DeepSeek Harness (deepseek-flash)

* fix(dsh): pass the web profile explicitly

The withPlugins launcher prepends --patch, which historically prevented dsh
from auto-expanding "dsh web" into "--profile web". Pass the profile
explicitly in the Just recipe and the verification commands so they work with
both plain dsh and the wrapper.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): pick up the wrapped-profile fix

Update nur-ryan4yin to the squash merge that keeps an implicit profile name
first, so "dsh web" and "just dsh-web" work through the wrapper again.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): pick up the plugin peer-link fix

Update nur-ryan4yin to the squash merge that links the dsh peer tree at
node_modules/@deepseek-ai instead of nesting it one level deeper. This is the
import failure that stopped nushell-executor from activating after deploy.

Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-10-18 12:28:18 +08:00
1 parent 4258538588
commit 373b233f32
14 files changed
+196 -105

No files matched your search

+38 -4
View File
@@ -8,13 +8,14 @@ profile: an ordered stack of patch layers over an empty root entry list, where l
| 1 | bundles | shipped with the profile | upstream |
| 2 | profile | `$DSH_HOME/profiles/<name>/cordis.patch.yml` | dsh, at runtime |
| 3 | home | `$DSH_HOME/cordis.patch.yml` | you |
| 4 | `--patch` | whatever the launcher passes | you |
| 4 | `--patch` | whatever the launcher passes | package wrapper |
`$DSH_HOME` defaults to `~/.dsh`. Settings UI changes are written to the profile patch; a home-layer
row outranks it, so the UI rejects that setting with
`Configuration for "..." is overridden by a home patch`. This repository therefore ships no home
layer — profile patches are machine-local runtime state, the place for private endpoints (LAN
servers, staging gateways) too, and Nix only installs the CLI.
`Configuration for "..." is overridden by a home patch`. This repository ships no home layer:
profile patches remain machine-local runtime state, the place for private endpoints (LAN servers,
staging gateways) too. The shell swap is a package concern instead: Nix installs `dsh` from
`nur-ryan4yin` with `dsh.withPlugins`, which adds a `--patch` overlay after the profile layer.
Three behaviours that are easy to get wrong:
@@ -49,3 +50,36 @@ on, `$DSH_AGENTS_HOME/AGENTS.md` — so no patch row is needed; identical conten
- The scopes come from the environment: `0.2.1-alpha.2` removed the row's `dshHome` / `agentsHome`.
- Nested `AGENTS.md` files are discovered by `read` / `write` / `edit`, not by `cd` inside `bash`,
and nothing is watched: an external edit lands on the next such call, or on resume.
## Nushell-only shell
`nur-ryan4yin`'s `dsh.withPlugins [ ...dsh-nushell-only ]` replaces the two first-party shell
providers with Nushell: every `ctx.shell` consumer (model shell tools, background jobs, hook
bridges, in-process callers) runs `nu --no-config-file -c`, and a command that hands execution to
another shell is refused before it spawns. The plugin adds Nushell rules and a syntax guide, and
rewrites the shell-tool descriptions, so the model is told what actually runs. `nu` comes from the
NUR package, so it does not depend on `PATH`.
The source is the `develop` branch of ryan4yin's fork carrying the reviewed patch set (Nu-builtin
denial facts, test-harness fixes, the `find` hint, persistent-terminal scoping), pinned by
`fetchFromGitHub` and `hashes.json` in `nur-packages`; nix-config locks that package set in its
`flake.lock`. `dsh.withPlugins` wraps the binary: each invocation gets a generated `--patch <file>`
overlay after the profile layer, while `dsh plugin` stays untouched. No `$DSH_HOME/cordis.patch.yml`
is written, so there is no home-layer state to resolve or roll back. Boundaries: preset rows that
spawn a shell themselves (`custom-bash.mjs`, `persistent-shell`) bypass `ctx.shell`. So does
`@deepseek-ai/dsh-experimental-terminal-bundle`: its `terminal_*` tools use a separate
`ctx.terminals` seam and run the platform shell (bash on the POSIX hosts this repository manages),
so they coexist with this plugin but stay outside the Nushell-only guarantee — keep the bundle off
for a strict Nu-only machine (the fork's `--with-terminal-bundle` integration run covers the
coexistence). Bash hooks now run under Nushell and need rewriting; remote hosts still run their own
POSIX shell. Rollback: install plain `dsh` or pass an empty plugin list; there is no home file to
delete. Tested on Linux; the same package also builds for Darwin, where it has not been boot-tested.
Verify after a switch:
```sh
dsh --profile web --dump-config | grep -n "nushell"
# expect the first-party shell rows disabled, nushell-executor and nushell-teaching inserted
dsh --profile web 2>&1 | grep "disabling profile plugin"
# a dsh bump can disable a row whose manifest no longer matches; nothing prints when healthy
```