feat(agents): version the dsh web profile and add a proxied launcher #387

This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-10-06 00:54:30 +08:00
1 parent 975d2668a8
commit 205f7c622b
12 files changed
+226 -3

No files matched your search

+5
View File
@@ -6,3 +6,8 @@ vercel.json
cache
temp
.temp
# dsh writes its profile files itself (the Settings UI and `dsh plugin`), and
# its YAML dumper wraps prose differently from prettier; formatting them here
# would be undone on the next write.
home/base/tui/agents/dsh/web
+17
View File
@@ -254,6 +254,23 @@ kana mode="switch":
k3s-test mode="switch":
colmena apply {{mode}} --on '@k3s-test-*' --verbose --show-trace
# =================================================
#
# AI agent commands
#
# =================================================
# Start the dsh web UI without opening a browser, proxied via mihomo's mixed port.
[group('agents')]
dsh-web:
#!/usr/bin/env nu
# dsh's web fetch refuses a DNS answer it does not call public, and a fake-ip
# resolver answers with 198.18.0.0/15. A proxied hop lets mihomo resolve the
# origin instead, which skips that check and leaves fake-ip acceleration
# intact. The policy reaches every Node fetch the harness makes, so mihomo
# becomes a dependency for LLM, web search and HTTP MCP traffic too.
with-env { HTTPS_PROXY: "http://127.0.0.1:7897", HTTP_PROXY: "http://127.0.0.1:7897" } { ^dsh web --no-open }
# =================================================
#
# Other useful commands
+2 -3
View File
@@ -13,6 +13,8 @@ Nix dependency; this module only wires it into place.
- `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, plus
`pkgs.mcp-grafana` for the Grafana MCP server.
- `env.nix`: telemetry and auto-update opt-outs for the agents.
- `dsh/`: the dsh `web` profile that lives in `~/.dsh`. See [dsh/README.md](./dsh/README.md) for the
link granularity, the tracked members and which hosts import it.
## Deployed rule targets
@@ -30,6 +32,3 @@ Out-of-store means an edit to `agents/AGENTS.md` takes effect on the next agent
Home Manager switch. The trade-off is that the rules stay writable in the checkout; use a store
symlink (`home.file.<target>.source = ../../../../agents/AGENTS.md`) if immutability matters more
than fast iteration.
The module is imported through `home/base/tui`, so it applies to every host that imports
`home/linux/gui.nix` or the macOS `home/darwin` stack. Core-only servers do not get it.
+46
View File
@@ -0,0 +1,46 @@
# dsh
The dsh (DeepSeek Harness) `web` profile, linked into `~/.dsh/profiles/web` from the checked-in
files under [`web/`](./web).
## Why the link targets the directory
dsh persists Settings through `cordis.patch.yml` with an atomic write (a random-suffix sibling, then
a rename), and that rename replaces a symlinked target instead of writing through to its referent. A
file-level link — store or out-of-store — is destroyed by the first Settings save, while a directory
link keeps the write inside the checkout. Same reasoning as `home/base/tui/tuios`, one level up.
| Profile member | Tracked | Role |
| --------------------- | ------- | ------------------------------------------ |
| `cordis.patch.yml` | yes | the user patch layer the Settings UI edits |
| `package.json` | yes | the profile's `dsh.profile.bundles` list |
| `pnpm-workspace.yaml` | yes | the profile's pnpm settings |
| `cordis.yml` | no | rewritten on every boot |
| `node_modules/` | no | written by `dsh plugin --profile web ...` |
`web/.gitignore` owns the untracked rows. As with the rules links in the parent module, the
trade-off is a config that stays writable in the checkout: a Settings change and a commit are the
same edit, and the configuration is shared by every host that deploys the profile.
## Which hosts deploy it
A host only gets this link by importing the module. `home/linux/gui.nix` and `home/darwin` pull it
in through `home/base/tui`, so core-only servers miss it — `idols-ruby` installs dsh from
`environment.systemPackages` and imports `home/base/tui/agents/dsh` from its own home module. A new
host that ships dsh needs one of those two wirings.
## Default model route
`web/cordis.patch.yml` pins `agent-default-model` to the API-key route (`deepseek-official`). The
account route (`deepseek-account`) never falls back to an API key, so a host holding only
`DEEPSEEK_API_KEY` fails every freshly created session with `ACCOUNT_SIGN_IN_REQUIRED`. A host that
signs in instead can select an account model per session, or override the field in its own
`$DSH_HOME/cordis.patch.yml`, which the home layer applies last and which therefore wins.
## Running it
`just dsh-web` starts the harness without opening a browser and routes its outbound HTTP through
mihomo's mixed port. dsh's web fetch refuses a DNS answer it does not call public, and a fake-ip
resolver answers with `198.18.0.0/15`; a proxied hop lets mihomo resolve the origin instead, which
skips that check and leaves fake-ip acceleration intact. The policy reaches every Node fetch the
harness makes, so mihomo becomes a dependency for LLM, web search and HTTP MCP traffic too.
+21
View File
@@ -0,0 +1,21 @@
{
config,
...
}:
let
# dsh persists Settings through the profile's cordis.patch.yml with an atomic
# write (a random-suffix sibling, then a rename), and that rename replaces a
# symlinked target instead of writing through to its referent. A file-level
# link, store or out-of-store, therefore survives only until the first save.
# Linking the whole profile directory keeps the write inside the checkout and
# leaves the directory symlink intact. Same reasoning as
# `home/base/tui/tuios`, one level up; the checkout path is hardcoded like the
# repo's other out-of-store links.
profileDir = "${config.home.homeDirectory}/nix-config/home/base/tui/agents/dsh/web";
in
{
# The `web` profile's configuration, owned by hand and by the Settings UI.
# Generated members of the profile (cordis.yml, node_modules, lock files) are
# gitignored inside that directory rather than tracked.
home.file.".dsh/profiles/web".source = config.lib.file.mkOutOfStoreSymlink profileDir;
}
+10
View File
@@ -0,0 +1,10 @@
# Regenerated by dsh on every boot (always the empty root entry list).
cordis.yml
# Written by `dsh plugin --profile web ...`.
node_modules/
pnpm-lock.yaml
# Transient siblings of the atomic-write and file-lock protocols.
*.tmp
*.lock
@@ -0,0 +1,77 @@
# Your patch layer for this dsh profile, applied after every bundle layer:
# a top-level YAML array of loader patch entries (id-targeted config
# overrides, disables, and insert lists; `!!js` expressions allowed).
- id: ui-settings-general
name: "@deepseek-ai/dsh-client-ui-settings-general"
config:
welcomeNoticeVersion: 2026-09-28.1
- id: agent-default-model
name: "@deepseek-ai/dsh-agent-default-model"
config:
provider: deepseek-official
model: deepseek-flash
reasoningEffort: high
- id: ui-settings-account
name: "@deepseek-ai/dsh-client-ui-settings-account"
config:
version: 1
step: done
purpose: development
process: standard
completion: completed
usage: detailed
developerTools: true
- id: ui-chat
name: "@deepseek-ai/dsh-client-ui-chat"
config:
transcriptView: standard
performanceUsage: detailed
- id: ui-settings
name: "@deepseek-ai/dsh-client-ui-settings"
config:
enabled: true
- id: locale
name: "@deepseek-ai/dsh-client-locale"
config:
preference: zh
- id: agent-preset-registry
name: "@deepseek-ai/dsh-agent-preset-registry"
config:
default: standard
selectedDefault: ptc
- id: llm-deepseek-account
name: "@deepseek-ai/dsh-llm-deepseek-account"
config:
models:
- id: deepseek-flash
name: DeepSeek-V41-Flash
inputModalities:
- text
- image
systemPromptUpdate: in-history
toolUpdate: addition-only
contextWindow: 1000000
- id: deepseek-v4-pro
name: DeepSeek-V4-Pro
description: Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.
contextWindow: 500000
inputModalities:
- text
- id: agent-loop
name: "@deepseek-ai/dsh-agent-loop"
config:
agents: []
maxParallelToolCalls: 8
- id: subagent
name: "@deepseek-ai/dsh-subagent"
config:
maxActiveSubagents: 3
- id: compaction-basic
name: "@deepseek-ai/dsh-compaction-basic"
config:
thresholdRatio: 0.35
retainRatio: 0.16
headroomTokens: 65536
compactionRetries: 1
maxOverflowRetries: 1
auto: true
+14
View File
@@ -0,0 +1,14 @@
{
"name": "dsh-profile-web",
"private": true,
"dependencies": {},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"@deepseek-ai/dsh-experimental-auto-review"
]
}
}
}
@@ -0,0 +1,5 @@
packages:
- .
nodeLinker: hoisted
autoInstallPeers: false
+5
View File
@@ -2,6 +2,11 @@
imports = [
../../linux/core.nix
../../linux/gui/i3
# Ruby installs the dsh CLI system-wide from `hosts/idols-ruby/packages.nix`
# while running the core home stack, so it misses the agents module that the
# GUI hosts pull in through `home/linux/gui.nix`. Import just the dsh
# profile, which keeps its Settings-owned configuration in this repository.
../../base/tui/agents/dsh
];
# Headless X11/i3 session for computer use.
@@ -0,0 +1,7 @@
{
# The link targets the whole profile directory, not a file inside it: dsh's
# atomic Settings write replaces a symlinked file on the first save, while a
# directory link keeps the write inside the checkout.
keys = [ ".dsh/profiles/web" ];
source = "/home/tester/nix-config/home/base/tui/agents/dsh/web";
}
@@ -0,0 +1,17 @@
# The dsh module is evaluated on its own here. Reading `home.file` out of a real
# host would force the catppuccin theme derivations, which turns an eval test
# into a build, and the profile link is system-independent anyway. The stub
# supplies the only two config values the module reads.
let
module = import ../../../../home/base/tui/agents/dsh/default.nix;
evaluated = module {
config = {
home.homeDirectory = "/home/tester";
lib.file.mkOutOfStoreSymlink = path: path;
};
};
in
{
keys = builtins.attrNames evaluated.home.file;
source = toString evaluated.home.file.".dsh/profiles/web".source;
}