npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tensorgrid-ui

v2.65.0

Published

TENSOR GRID — фирменный интерфейс поверх DeepSeek Harness: своя палитра и айдентика, полная русская локализация, независимый обзор работы несколькими моделями, живой каталог моделей, установка и откат одной командой

Readme

TENSOR GRID

Фирменный продукт поверх DeepSeek Harness: своя палитра и айдентика, полная русская локализация, независимый обзор работы несколькими моделями, живой каталог моделей, установка и откат одной командой.

Ставится плагином, а не форком: ни один поставочный компонент не замещается, поэтому обновления dsh продолжают доезжать до интерфейса.

Инструкция для того, кто ставит впервые, — docs/установка.html. Как выпускать — docs/ВЫПУСК.md. Как переходить на новую dsh — docs/ОБНОВЛЕНИЕ-DSH.md. План работ — docs/ПЛАН-РАБОТ.md.

Про имя. Репозиторий и папка до сих пор называются dsh-ui-obsidian-ion — так проект звался, когда был одной лишь палитрой. Переименование тянет за собой адреса и историю, поэтому отложено. Сам продукт, пакет tensorgrid-ui и всё, что видит человек, называются TENSOR GRID.

Из чего состоит

| Файл | Роль | |---|---| | package.json | секция dsh.client — по ней рантайм понимает, что у пакета есть браузерная половина | | lib/index.js | стабильная host-оболочка: маршруты, иконки, манифест и получение Cordis-служб | | lib/http-runtime.js | перечитываемые HTTP-обработчики входа и обновлений | | lib/auth-bridge.js | состояние разговора входа | | lib/update-check.js | проверка версий и установка из реестра | | lib/client.js | браузерная половина: палитра, стили, компонент слоя |

lib/client.js написан руками в том же формате, что и поставочные @deepseek-ai/dsh-client-ui-*: регистрация в window.__ModuleLoader__, зависимости через require, экспорт apply и inject. Сборщик не нужен.

Что пакет занимает

Только аддитивные слоты — те, у которых replaceRisk: none. Ни один поставочный компонент не замещается, поэтому улучшения dsh продолжают доезжать до интерфейса. Это условие закреплено в тесте белым списком.

| Слот | Вид | Что даёт | |---|---|---| | shell.overlay | list | ambient-слой: сферы, проход, кромка, зерно, виньетка, отклик | | settings.general.item | list | строка «Атмосфера» — регулятор интенсивности | | settings.general.item | list | строка «Акцент» — выбор ведущего тона | | conversation.input.dock | list | невидимый драйвер отклика на работу агента | | conversation.hero.brand.mark | single | знак на экране пустой сессии |

Плюс слой переопределения токенов через theme.overrideTokens — публичный метод сервиса темы.

Две CSS-переменные вместо связи компонентов

  • --dsx-intensity — пользовательский регулятор (0 / 0.5 / 1 / 1.7), умножает непрозрачность всех слоёв. Хранится в localStorage.
  • --dsx-activity — 0 в покое, 1 пока идёт ход агента.
  • --dsx-accent-rgb и --dsx-accent2-rgb — ведущий тон и контрапункт выбранного акцента, тройками каналов для rgba(var(…), α).

Смена акцента бьёт по двум сторонам сразу: свечение ведут переменные, а --dsw-alias-brand-primary переписывается повторным вызовом overrideTokens с тем же источником — служба документирует это как замену всего слоя целиком.

Переменная на documentElement — единственный мост между двумя поддеревьями: ambient-слой живёт в корневом shell.overlay, у которого нет сессии, а хук useSession раздаётся только сессионным слотам. Драйвер сидит в сессионном слоте, ничего не рисует и лишь ведёт переменную.

Почему нет своих карточек инструментов

tool.call.toolview ключуется именем инструмента, и механизм сохранения дочерних слотов существует: в опциях регистрации есть children, а компонент получает renderSlot — так поставочная строка read_image объявляет себе tool.call.images. То есть технически заменить карточку, ничего не потеряв, можно.

Отказ по другой причине. Владельческие пропсы — { callId, toolName, block, openFile, cwd, home, loadImage, inspect }, и вся суть карточки в разборе block: варианты, заголовок, вывод, ошибки, diff, терминал, поиск, файл. Своя карточка обязана воспроизвести этот разбор и застывает на сегодняшней форме внутренней структуры. Это ровно тот случай замороженной копии, ради исключения которого пакет и держится аддитивных слотов.

Почему тема не регистрируется через theme.register

ThemeDefinition пиннит одну colorScheme, поэтому выбор такой темы ломает режим «следовать системе». Слой переопределения несёт пару значений на каждый токен и переключение схемы переживает сам. Вдобавок поставочная строка «Оформление» знает ровно три значения — appearance.light, appearance.dark, appearance.system — и отрисовать стороннюю тему не может. Регистрация темы была бы регрессом, а не улучшением.

Установка одной командой

git clone https://github.com/tayyarg07/tensorgrid-ui.git
cd tensorgrid-ui
.\install.ps1

Установщик проверит требования, поставит dsh (если его нет), создаст профиль, разложит пакет, патч-слой и агент-пресет, заведёт автозапуск и проверит, что всё на месте. Тем же файлом обновляют: он повторно запускаемый и чужого не трогает — настройки, ключи и историю сессий не переписывает.

| Ключ | Что делает | |---|---| | -Preflight | только проверить требования и выйти | | -DryRun | показать план, ничего не меняя | | -NoAutostart | не заводить автозапуск при входе в Windows | | -ProfileId <имя> | другой идентификатор профиля и пресета |

Что должно быть на машине

| Нужно | Зачем | Если нет | |---|---|---| | Node.js 22.12 или новее | CLI самого dsh зависит от commander@15, а он требует эту версию | https://nodejs.org | | npm | ставит dsh | приходит вместе с Node.js | | pnpm | dsh plugin add — это переходник к pnpm; без него dsh отказывается | npm install -g pnpm или corepack enable |

Проверяется всё сразу, до первого изменения, и о недостающем говорится за один заход:

.\install.ps1 -Preflight
npx --yes tensorgrid-ui -Preflight    # то же самое без git

Раньше этого не было, и аудит показал цену: pnpm не проверялся вовсе, установка доходила до середины, ответ dsh «pnpm not found on PATH» выбрасывался, а человек читал «Проверьте доступ в интернет» и шёл проверять роутер. Теперь чужой ответ при отказе показывается целиком, а самая частая причина названа прямо.

Ниже — то же самое вручную, если нужно понимать каждый шаг.

Что у меня стоит и не отстало ли

.\status.ps1
.\status.ps1 -Port 3081             # если приложение на другом порту
npx --yes tensorgrid-ui status      # то же самое без git

Ничего не меняет, только смотрит. Отвечает на четыре вопроса, которые иначе выясняются перепиской: версия dsh и есть ли новее, версия продукта и не отстала ли от репозитория, все ли части на месте, работает ли приложение прямо сейчас и на том ли профиле.

Ключ -Offline пропускает обращение к npm.

Про метку latest в npm

Она указывает на 0.1.5-rc.1, тогда как 0.1.5-rc.2 опубликован позже, — обычное дело для предрелизов. Поэтому установщик ставит закреплённую версию из env/snapshot.json, а не latest: иначе сотрудник получил бы версию старше той, на которой продукт проверялся.

status.ps1 показывает обе и подсвечивает расхождение.

Обновления приходят с двух сторон

| Откуда | Что | Как применяется | |---|---|---| | от вас | новый коммит в репозитории | git pull + install.ps1 | | от DeepSeek | новая версия dsh в npm | npm install -g "@deepseek-ai/dsh@X" |

Каналы независимы, но не безобидны по отдельности. Опасен второй без первого: пакет опирается на контракты dsh — имена слотов, методы сервисов, пространства имён перевода. Если обновить dsh, не переснимая снимок и не прогоняя тест, поломка обнаружится у сотрудника.

Поэтому обновление dsh должно приходить через вас: вы проверяете новую версию и выпускаете версию продукта с отметкой «проверено на такой-то». Установщик ставит закреплённую версию из env/snapshot.json, а не latest, — сотрудник не получит непроверенный dsh случайно.

Порядок выпуска

Когда вышла новая версия dsh:

npm install -g "@deepseek-ai/dsh@<новая>"   # 1. поставить у себя
.\verify.ps1                                 # 2. прогнать всю цепочку
git diff env/snapshot.json                   # 3. посмотреть, что изменилось
# 4. починить, если что-то отвалилось
git commit -am "проверено на dsh <новая>"    # 5. зафиксировать
git push                                     # 6. выпустить

verify.ps1 — единственная проверка, которую нужно помнить перед выпуском. Она сверяет версию dsh со снимком, переснимает окружение, обновляет эталон строк, проверяет связность перевода, пересобирает словари, гоняет все проверки контракта и смотрит, всё ли зафиксировано.

Две честные оговорки, найденные аудитом.

Она не останавливается на первом провале — копит замечания и завершается неуспехом в конце. Здесь раньше было обещано обратное.

Число проверок тут больше не называется. Оно меняется каждый выпуск, и в трёх местах этого файла стояло «172» при фактических 232. Устаревшее число врёт тише всего и потому опаснее всего.

Порядок шагов не случаен: снимок снимается до проверки контракта, потому что тест сверяется именно с ним.

Обновление без новой версии dsh

Продукт сам сверяет установленную версию с меткой latest в реестре и показывает результат в настройках. Кнопка «Обновить» ставит новую версию, после чего просит либо обновить страницу, либо перезапустить приложение — решение принимается по отпечатку host-половины, а не наугад.

То же самое из консоли:

dsh plugin --profile tensorgrid add tensorgrid-ui@latest

У самого dsh автообновления нет, и никто о новых версиях не уведомляет — это проверено, а не предположено. Обновлять его нужно вручную:

npm install -g "@deepseek-ai/dsh@<версия>"

После него стоит переснять снимок окружения и посмотреть, что изменилось (см. раздел «После обновления dsh»).

Установка вручную

Единица приложения — профиль, а не пакет. Профиль задаёт, какие бандлы составляют сборку, и добавляет поверх них свои строки. Поставочный профиль web при этом не трогается и остаётся запасным вариантом.

Всё лежит в ~/.dsh/, который не перезаписывается обновлением dsh — в отличие от каталога установки node_modules/@deepseek-ai/dsh.

  1. Создать профиль из поставочного шаблона. Флаг --dump-config заставляет команду создать профиль и выйти, не поднимая сервер:

    dsh --profile tensorgrid --from-default-profile web --dump-config
  2. Поставить пакет из реестра штатной командой dsh — она пробрасывает установку в pnpm внутри профиля:

    dsh plugin --profile tensorgrid add tensorgrid-ui@<версия>

    Версию задаёт манифест репозитория, а не метка latest: у всех должно встать ровно то, что проверено, а не то, что оказалось свежим в момент установки.

    Предупреждение «declares no dsh.bundle» здесь нормально: пакет не бандл, а один плагин, который монтируется своей строкой ниже.

  3. Положить патч-слой профиля — готовый файл лежит в репозитории:

    profile/cordis.patch.yml  →  ~/.dsh/profiles/tensorgrid/cordis.patch.yml

    Его содержимое:

    - insert:
        - id: authorization
          name: '@deepseek-ai/dsh-authorization'
    
        - id: ui-tensorgrid
          name: 'tensorgrid-ui'

    Пакет монтируется по имени: спецификатор разрешается штатным ESM-резолвом из node_modules профиля, куда его кладёт dsh plugin add, а манифест dsh.client находится подъёмом до ближайшего package.json. Регистрировать пакет где-то ещё не нужно.

    Строка authorization — стойка входа. Ни один поставочный профиль её не монтирует, а без неё llm-pi-ai не регистрирует ни одного потока входа, и в настройках остаются только ключи API. Подробнее — в разделе «Вход по подписке».

  4. Положить агент-пресет:

    preset/  →  ~/.dsh/.agent-presets/tensorgrid/

    Профиль задаёт приложение — какие бандлы его составляют. Пресет задаёт агента — его персону, набор инструментов и делегирование. Это две разные плоскости, и живут они в разных местах.

  5. Запустить:

    dsh --profile tensorgrid

    Пресет выбирается в интерфейсе, в настройках агента: в списке появится «TENSOR GRID» рядом с поставочными.

Вход по подписке

Провайдеры моделей приходят из библиотеки pi-ai, и у части из них есть вход по подписке, а не только ключ API. Потоки входа регистрирует llm-pi-ai, но только если смонтирована стойка dsh-authorization — ни один поставочный профиль её не монтирует, поэтому в стоковом интерфейсе видны одни ключи. Одна строка в патч-слое включает все 39 потоков, шесть из которых с подпиской:

| Провайдер | Ключ | Способы | |---|---|---| | Anthropic | llm-pi-ai/anthropic | Claude Pro/Max, ключ API | | OpenAI Codex | llm-pi-ai/openai-codex | ChatGPT Plus/Pro | | GitHub Copilot | llm-pi-ai/github-copilot | вход, токен | | Kimi For Coding | llm-pi-ai/kimi-coding | вход, ключ API | | OpenRouter | llm-pi-ai/openrouter | вход, ключ API | | xAI | llm-pi-ai/xai | SuperGrok или X Premium, ключ API |

Учётные данные ложатся в ~/.dsh/.credentials.yamlвне профилей, поэтому вход, сделанный однажды, работает во всех, а подписка у каждого сотрудника своя.

Строка настроек ведёт разговор входа целиком: показывает ссылку и код, задаёт вопросы с выбором и вводом, позволяет отменить попытку. Отмена не украшение — стойка допускает одну попытку на ключ, и осиротевшая заявка иначе держит ключ занятым до перезапуска.

Три ошибки, на которых это строилось

Чтение службы из Cordis оказалось не тем, чем выглядит, и стоило трёх выпусков подряд:

  1. ctx.authorization как свойство — запрещено без объявления в inject;
  2. ctx.get('authorization') возвращает службу, но её методы всё равно охраняются контекстом, который службу не объявил;
  3. объявить стойку в inject всего ряда нельзя — она необязательна, и профиль без неё остался бы без палитры, айдентики и перевода.

Работает только четвёртый вариант: вложенный ctx.inject, который ждёт службы, не задерживая остальное, и отдаёт ссылки, связанные с контекстом, их объявившим. Правило закреплено проверками в дымовом тесте.

Проверка установки

Профиль можно поднять на свободном порту, не мешая уже запущенному:

dsh --profile tensorgrid --port 3081 --no-open

Команда напечатает адрес с токеном. Две проверки по нему:

  • GET /favicon.svg обязан вернуть SVG с aria-label="TENSOR GRID" — значит host-половина заняла маршрут;
  • в HTML страницы (с токеном) обязано встречаться tensorgrid-ui — значит пакет попал в загрузочный граф __DSH_BOOT__ и клиентская половина будет загружена. Имя берётся из package.json, а не из имени папки: проверка по старому dsh-ui-obsidian-ion дала бы ложный отказ.

Откат

dsh web поднимает стоковый интерфейс. Удаление продукта — удалить каталог ~/.dsh/profiles/tensorgrid.

Что проверено, а что нет

Проверено по живому рантайму:

  • контракт window.__ModuleLoader__.load({ id, factory }) и экспорт apply/inject — снято с @deepseek-ai/dsh-client-ui-brand-official;

  • способ вставки CSS тегом <style data-plugin-css> — снят с @deepseek-ai/dsh-client-ui-layout;

  • имена и требования всех 13 токенов — через Theme.listTokens;

  • контракт слота shell.overlay (list, replaceRisk: none) — через Slots.listSubTree;

  • сам дизайн работает: он уже запущен как динамический Package obsid-1/pkg-2.

  • обнаружение пакета — прослежено по коду @deepseek-ai/dsh-client-modules (lib/index.js, locatePkgJson / nearestPackage / resolveMeta). Цепочка для нашей строки:

    1. спецификатор начинается с . → считается путём;
    2. резолвится через ту же ESM-резолюцию, что импортировала host-половину;
    3. nearestPackage() идёт вверх от файла до ближайшего package.json. Имя заранее не ожидается, поэтому подходит любое — пакету не нужно быть разрешимым в node_modules, workspace и dependencies не нужны;
    4. dsh.client.platform должен быть ровно "web";
    5. exports["./client"] принимается и строкой, и объектом с default;
    6. путь бандла = dirname(package.json) + exports["./client"].
  • исполнение бандлаnode test/smoke.mjs. Секция про манифест проверена негативным контролем: при platform: "node" и снятом exports["./client"] тест валится на четырёх проверках.

Не проверено запуском:

  • фактическая отрисовка в браузере.

Почему загрузку нельзя проверить со стороны Host

Хотелось бы спросить рантайм «применились ли мои токены», но такого ответа нет:

  • маршрут /plugins/<id>/client.js требует токен авторизации, снаружи отдаёт 401/404;
  • theme.getTheme() возвращает { preference, fontSize, active, themes, revision }, где active.tokens — карта токенов определения темы (у встроенных тем пустая). Переопределения из overrideTokens лежат отдельным слоем, который снимок не публикует;
  • динамический клиентский плагин по устройству песочницы не имеет ни document, ни window, поэтому прочитать вычисленную CSS-переменную или найти вставленный тег стилей нельзя.

Что проверяется со стороны Host — это регистрация: сервис clientModules, метод clientPath(id). Если он возвращает путь к lib/client.js внутри профиля, значит манифест найден и принят. Остальное показывает только перезагрузка страницы.

Агент-пресет

preset/ — копия поставочного standard, сделанная штатной службой agentPresets. Поставочные пресеты править нельзя: их затирает обновление dsh, а порча cordis отключила бы саму возможность авторства пресетов.

Отличий от поставки немного — 47 добавленных строк и 3 убранных:

  • persona брендирована и задаёт язык ответа;
  • строки делегирования в чужие продукты убраны.

Всё остальное дословно как в поставке — чтобы при обновлении dsh diff показал ровно то, что изменили авторы, и это было легко перенести.

Почему чужих продуктов здесь больше нет

Раньше задачу можно было отдать наружу — в Codex или Claude Code, — и ревизорами могли быть они же. И то и другое убрано целиком, а не спрятано.

Довод не «они плохи», а «они ничего не добавляли». Чистый опыт: одна и та же модель в чужой обвязке и у нас дала совпадение тринадцати находок из четырнадцати. Разное находят разные МОДЕЛИ, а не разные обвязки. Обе подписки — Anthropic и OpenAI — работают прямо здесь, плюс любая модель по ключу, включая сотни через OpenRouter.

А стоили они дорого: два пакета, которых нет в поставке dsh, две лишних установки сотруднику и почти все дефекты, которые пришлось чинить, — сборка аргументов, поток ввода, забор ответа отдельным файлом, обёртки Windows, ключи авторизации в окружении ребёнка, отмена, тайм-аут, обрезанный вывод. Вместе с ними ушёл целый класс поломок: запуск чужого процесса, временные каталоги и разбор чужого формата. Файл запуска ревизоров похудел с 546 строк до 267.

Осталась одна дорога исполнения, и её видно целиком.

Порядок не случаен: наличие провайдера на Host само по себе прав не даёт, а снятый disabled без установленного бандла уронит ряд.

Проверка пресета

Композиция проверяется до запуска сессии — через agentPresets.resolve() и standingKeyFor(). Если пресет сломан, они скажут об этом сразу, а не молчаливым отсутствием инструментов в сессии.

Эксплуатация

Запуск и перезапуск

dsh web

Перезапуск — закрыть окно (или Ctrl+C) и запустить снова. Автозапуск при входе в Windows настроен файлом DeepSeek Harness.cmd в папке shell:startup; чтобы отключить — удалить его.

| Команда | Что делает | |---|---| | dsh --profile web --dump-config | показать собранное дерево плагинов | | dsh plugin --profile web add <пакет> | поставить плагин в профиль | | dsh --version | версия |

Добавить модель, которой нет в списке

Список моделей OpenRouter в dsh — это вшитый каталог пакета pi-ai, снятый на день его сборки. Модель, появившаяся в OpenRouter позже, в списке не окажется, сколько ни жми «Обновить»: обновление перечитывает провайдеров, а не каталог.

Насколько велик разрыв — измерено, а не предположено: OpenRouter отдаёт 444 модели, вшитый каталог маршрута openrouter знает 366, живых моделей не хватает 89; ещё одиннадцать, наоборот, у OpenRouter уже исчезли, а в каталоге остались.

Прямой путь — дописать модель в маршрут — не годится: поле models заменяет каталог маршрута целиком, то есть добавление одной модели стирает остальные триста с лишним. Поэтому для ручных моделей заводится ОТДЕЛЬНЫЙ маршрут, а основной openrouter остаётся нетронутым.

Кнопкой TENSOR GRID — обычный путь

Настройки → Общие → строка «Список моделей OpenRouter» → Сверить. Она спрашивает живой список у OpenRouter, сравнивает со вшитым каталогом и показывает три числа: сколько у провайдера, сколько знает dsh и сколько годных новинок нашлось. Вторая кнопка — Добавить новые — записывает их в маршрут openrouter-extra.

Два отсева, и оба не вкусовые.

Уже известные каталогу пропускаются. Иначе рядом с каждой моделью встал бы её двойник, причём наш — беднее: цен и поправок совместимости в ответе OpenRouter нет.

Те, что здесь не заработают, отсеиваются с названной причиной. Агенту нужен вызов инструментов — без него он не прочитает ни файла; пакетные модели (:batch) отвечают часами и в разговоре просто повиснут. Отбор совпадает с политикой самого dsh: моделей с tools и tool_choice у OpenRouter 367, а во вшитом каталоге — 366.

Измерено в день выпуска: из 89 отсутствующих 74 отсеиваются, остаётся 15 годных.

Сверка не запускается сама — только нажатием. Она ходит в сеть и меняет настройки, а настройки открывают и ради размера шрифта. Запись точечная: у существующего маршрута меняется ТОЛЬКО список моделей, ключ и заголовок остаются как были. Когда каталог dsh догоняет всё, что мы добавляли, маршрут убирается целиком — чтобы двойников не осталось.

Через поставочный интерфейс — тот же механизм, но вручную

Настройки → Модели → строка OpenRouter (вручную) с тегом «Custom» → кнопка получения моделей. Она спрашивает живой список у самого OpenRouter, показывает поиск по нему и добавляет отмеченные галочками; уже добавленные не трогаются. YAML руками при этом не нужен.

Там же кнопкой «добавить своего провайдера» создаётся и сам такой маршрут — это тот же механизм, которым описывают любой сторонний шлюз.

Чего живой список НЕ приносит: цену, модальности, уровни рассуждения и поправки совместимости. В ответе OpenRouter на /models для каждой модели есть только id, имя и две ёмкости — остальное во вшитом каталоге. Поэтому подтянутая вручную запись беднее каталожной, и подменять каталог целиком живым списком было бы ухудшением, а не улучшением.

Через файл — когда нужно указать больше, чем даёт список

В ~/.dsh/settings.yaml:

llm-pi-ai:
  providers:
    openrouter:
      apiKeyEnv: OPENROUTER_API_KEY
    openrouter-extra:
      displayName: OpenRouter (вручную)
      apiKeyEnv: OPENROUTER_API_KEY      # тот же ключ, второй записи не нужно
      api: openai-completions
      baseURL: https://openrouter.ai/api/v1
      models:
        - id: stealth/union-alpha
          name: Union Alpha
          contextWindow: 262144
          maxTokens: 131072
          input: [text, image]
          reasoningEfforts: false        # модель без уровней рассуждения

Числа не выдумываются, а берутся из самого OpenRouter:

$r = Invoke-RestMethod https://openrouter.ai/api/v1/models
$r.data | Where-Object id -eq 'stealth/union-alpha' | ConvertTo-Json -Depth 6

context_lengthcontextWindow, top_provider.max_completion_tokensmaxTokens, architecture.input_modalitiesinput. Если в supported_parameters нет reasoning, ставится reasoningEfforts: false; иначе перечисляются уровни.

Перезапуск не нужен: настройки перечитываются перед следующим запросом. Странице нужно обновление, чтобы забрать новый список.

Проверено на живом приложении: маршрут поднялся без перезапуска, модель разобралась (262144 / 131072, вход text,image) и ответила на пробный запрос — finish: stop, 6 входных и 5 выходных токенов. Опрос живого списка для этого маршрута вернул все 444 модели — то есть кнопка в интерфейсе опирается на рабочий механизм, а не на обещание.

Отдельно про «скрытые» модели вроде stealth/*: они бесплатны, живут на общем пуле и отвечают 429 ... temporarily rate-limited upstream, когда пул занят. Это ответ провайдера, а не поломка настройки. Для работы такие модели не годятся — только для проб.

После обновления dsh

Переустанавливать ничего не нужно: npm update -g @deepseek-ai/dsh меняет только каталог установки, а пакет и строка композиции лежат в ~/.dsh/. При первом же запуске всё смонтируется само.

Но контракты, на которые опирается пакет, могли сдвинуться. Проверка:

cd C:\тест\dsh-ui-obsidian-ion
node tools/extract-locales.mjs   # обновить эталон, увидеть новые ключи
node tools/audit-ru.mjs          # связность перевода
node tools/build-ru.mjs          # пересобрать словари
node test/smoke.mjs              # проверки контракта

Новые строки интерфейса, которых нет в русском словаре, покажутся по-английски — это штатный фолбэк, а не поломка.

Доставка изменений и перезапуск

Пакет приезжает из реестра; копирования файлов больше нет нигде.

Что чем подхватывается:

| Изменилось | Достаточно | |---|---| | lib/client.js | обновить страницу | | lib/http-runtime.js, update-check.js | ничего — следующий запрос возьмёт новую версию | | lib/model-sync.js | ничего — следующее нажатие возьмёт новую версию | | lib/review-log.js, lib/auth-bridge.js | перезапустить приложение | | lib/index.js, состав маршрутов, зависимости | перезапустить приложение |

Два файла в этом списке — исключение, и не по недосмотру. Они хранят состояние: журнал держит очередь записи, стойка входа — незавершённую попытку авторизации. Горячая загрузка приписывает к адресу время правки и создаёт НОВЫЙ экземпляр модуля, а значит вторую очередь и второго владельца попытки.

Чем это кончалось: журнал пишут две стороны — панель по нажатию и инструмент обзора в конце прогона, — и вторая импортировала модуль без версии. Экземпляра было два, очереди две; одновременная запись читала одно состояние и сохраняла каждая своё, так что чужая правка исчезала бесследно. Атомарная замена файла от этого не спасает — она защищает от половины файла. Воспроизведено испытанием: восемь одновременных записей через два экземпляра сохраняются не все, через один — все восемь.

Поэтому эти два модуля грузятся один раз за процесс, а их правка требует перезапуска. Плата честная: она дешевле потерянного вердикта и зависшего входа.

Так стало с 1.1.4. До неё обработчики жили в index.js, а он читается только при старте — поэтому каждый выпуск требовал перезапуска, и пользователь трижды подряд на это натыкался.

Переход на 1.1.4 сам требует одного перезапуска: старая оболочка про новый модуль ещё не знает.

Две честные границы. Это не горячая перезагрузка всего приложения — всё, что регистрируется в Cordis, по-прежнему живёт до перезапуска. И обновление во время идущего входа обрывает разговор: состояние попытки привязано к экземпляру модуля, а замена файла создаёт новый.

Проверено запуском, а не рассуждением: правка ответа обработчика доехала до следующего запроса, возврат исходного текста — тоже, PID процесса не менялся.

Цикл правок

Источник истины — копия в рабочем каталоге; в профиле лежит рабочая копия.

  1. Править lib/client.js здесь.
  2. Прогнать node test/smoke.mjs. Файл уезжает в браузер как есть — ни сборщик, ни тайпчекер его не смотрят, поэтому опечатка ломает загрузку молча. Тест исполняет бандл в Node с подставными window, document и require и проверяет: регистрацию в __ModuleLoader__, экспорт apply/inject, вставку тега стилей, все 13 токенов со светлой и тёмной парой, занятие слота shell.overlay, гашение pointer-events на слоях, совпадение имён классов в разметке и CSS, определённость всех @keyframes и отсутствие неиспользуемых.
  3. Скопировать каталог в профиль (каталог test/ в рантайме не нужен).
  4. Обновить страницу. Путь к бандлу кэшируется, содержимое — нет: граф пересобирается на загрузке страницы и отдаёт свежий файл.

Перезапуск профиля не нужен даже при правке cordis.patch.yml: у профиля web выставлен patchReload: live.

Откат

Вернуть предыдущую установку

Два пути, и выбор между ними — по обстоятельствам.

.\rollback.ps1 -List         # что есть: версии в реестре и местные копии
.\rollback.ps1               # предыдущая версия из реестра (нужна сеть)
.\rollback.ps1 -To 2.4.1     # конкретная версия из реестра

.\rollback.ps1 -FromBackup   # из местной копии, БЕЗ СЕТИ
.\rollback.ps1 -FromBackup -Backup 2026-09-15_12-41-55

Без git: npx --yes tensorgrid-ui rollback -List и так далее.

Из реестра возвращается только пакет интерфейса — реестр npm хранит опубликованные версии навсегда.

Из местной копии возвращаются пакет, патч-слой и пресет сразу. install.ps1 перед каждой перезаписью складывает их в ~/.dsh/profiles/<id>/.backup/<дата>/; хранятся три последние копии, и убираются старые только после того, как новая установка прошла проверку.

Второй путь появился по итогам аудита: первый бесполезен ровно тогда, когда нужен больше всего — сеть недоступна, реестр молчит, сломался не пакет, а патч-слой. Копии при этом лежали рядом и не читались ничем.

Оговорка про местный откат: файлы кладутся обычным каталогом, и pnpm временно перестаёт считать пакет своим. Следующая штатная установка возвращает его под управление pnpm. Для аварийного восстановления это допустимо, для повседневной работы — нет.

Откат возвращает ровно три вещи: пакет, патч-слой и пресет. Настройки, ключи и история сессий не трогаются — они не входят в установку и от неё сломаться не могли.

Версию dsh откат не меняет: она глобальная и общая для всех профилей, включая стоковый web. Скрипт лишь сообщит, если сохранённая копия ставилась на другой версии, и подскажет команду.

Выключить продукт целиком

Убрать строку из cordis.patch.yml — перезапуск не нужен. Оба эффекта (токены и слой) сняты диспозерами, следов не остаётся.

Стоковый интерфейс всегда доступен командой dsh web.

Удалить полностью

Удалить каталог профиля ~/.dsh/profiles/<id> и каталог пресета ~/.dsh/.agent-presets/<id>.

Почему здесь нет mix-blend-mode

Первая версия полагалась на mix-blend-mode: screen, рассчитывая, что свечение сложится с фоном приложения и само погаснет на светлой теме. Это не работает: контейнер слота shell.overlay объявлен с z-index: 20, то есть создаёт изолированный контекст наложения, и смешивание происходит не с приложением, а с прозрачным фоном самого контейнера.

Поэтому режимы наложения убраны как мёртвый код, а различие тем берётся из штатного контракта самой темы — атрибута data-ds-dark-theme на body. Его выставляет bootstrap-скрипт ui-theme ещё до монтирования плагинов и дальше поддерживает ThemePresenter из ui-layout. Базовые правила в client.js рассчитаны на светлую тему и сдержанны; селекторы body[data-ds-dark-theme] … поднимают интенсивность для тёмной.

Это единственная опора на DOM приложения, и она сознательная: атрибут — объявленный интерфейс темы, а не внутреннее имя класса, которое сменится при следующей сборке.