Commit Graph
6 Commits
Author SHA1 Message Date
lefarcenandGitHub 6788a8667b fix(dsh): support DeepSeek Harness 0.1.2-rc.1, now npm's latest (#7825)
`@deepseek-ai/dsh@0.1.2-rc.1` was published on 2026-09-03 and holds npm's
`latest` dist-tag, so it is what a user who installs without naming a version
receives. Both of our authorities rejected that line:

  - the agent def did not accept `0.1.2-rc.1`, so Settings labelled the user's
    CLI untested;
  - `packages/dsh-runtime`'s peer ranges evaluated to false for it, because
    semver admits a prerelease only against a comparator sharing its exact
    major.minor.patch and carrying a prerelease of its own — so the companion
    component could not resolve at all. That half is silent, which makes it the
    worse one.

Move the installers to `0.1.2-rc.1` with a resolution cutoff between its publish
time and any successor, add the `0.1.2-rc.N` alternative to the accepted line,
and add the matching comparator to all five dsh peers. All five, because a
single updated peer would otherwise vouch for the other four.

The Alpine fallback in install-dsh.sh named `0.1.0-rc.6` — two patch lines stale,
because nothing pointed at it when the pin moved. It now interpolates
$DSH_VERSION.

Both test files previously pinned the broken state, asserting `0.1.2-rc.1` was
rejected; they now assert the invariant and went red before any source change.
`0.2.0-rc.1` stays refused by both authorities so the widening cannot decay into
accepting everything.

`packages/dsh-runtime`'s dependencies and devDependencies stay at `0.1.1-rc.2`
on purpose: bumping them resolves a mixed-generation tree (dsh-scope and
dsh-settings stay on 0.1.0-rc.6) and wants a cordis bump too. See the PR body.

Claude-Session: https://claude.ai/code/session_01B8MaA1ABkvbnfaYUKw3UH3
2026-09-07 07:33:19 +00:00
lefarcenandGitHub fb26b5cb8a fix(runtimes): support the dsh release line users are actually served (#7339)
* fix(runtimes): support the dsh release line users are actually served

Two days after #7153 widened the version policy from one release candidate
to the `0.1.0-rc.N` line, upstream published `0.1.1-rc.1` and then
`0.1.1-rc.2`, and made the latter the `latest` tag. Scoping to `0.1.0-rc.N`
was still a pin, just a longer one: every user on what npm now serves is
told their CLI is untested, our installer hands out a version two releases
behind, and the connection component's peers do not resolve against the
host they land on.

That last one is not a warning. semver only lets a prerelease satisfy a
range when some comparator carries the SAME major.minor.patch tuple AND a
prerelease tag, so `>=0.1.0-rc.6` matches nothing on the 0.1.1 line — a
verified `satisfies('0.1.1-rc.2', '>=0.1.0-rc.6') === false`, the same rule
that made the original pin uninstallable.

Move all three to the 0.1.x line: the policy accepts any `0.1.N`, release
candidate or stable; both installers pin `0.1.1-rc.2` with the resolution
window reset to just after its publish time; the peers gain a comparator
for the new line.

This will expire again. A comparator per prerelease line is what semver
allows, so `0.1.2-rc.1` will not resolve until someone adds one, and
nothing in the repo notices when upstream moves. The check that catches
installer/policy drift only compares the two against each other.

* ci: watch upstream dsh releases instead of waiting for a bug report

Twice in one week `@deepseek-ai/dsh` published a new release line while
this repo kept pinning the old one, and both times the detection mechanism
was a person reporting that their install did not work.

The check added with the first fix compares our installers against our
agent def. Both are our own config, so it stays green precisely when they
are equally out of date — which is the state that shipped, twice. Nothing
here looks at the registry.

Add a daily scheduled comparison against npm's `latest` that separates the
two failures worth telling apart: `behind` means our installer hands out an
older version than the registry serves, everything still works;
`unsupported` means a user who has what npm serves is outside what we claim
to support, which is an "untested" warning in Settings and peer ranges that
do not resolve. The card names all three files that must move together,
because forgetting the peer ranges is the one that breaks an install rather
than warning about it.

Standalone and outside the merge gate, like nix.yml and docker-image.yml:
an upstream release is not a reason to fail someone's PR. The workflow runs
`self-check` before touching the registry so a broken checker is loud
rather than quietly in-sync.

* fix(ci,runtimes): stop the drift watch losing what it exists to report

Four defects from review, each of which would have made this quieter than
having built nothing.

The widened policy over-claimed. `/^0\.1\.\d+(?:-rc\.\d+)?$/` accepts
`0.1.2-rc.1`, which the peer ranges cannot install — so the daemon would
suppress the untested warning for a version whose companion fails to
resolve, which is worse than the warning and silent. Narrow prerelease
acceptance to the tuples and floors the peers actually carry; stable
releases on the line need no comparator and stay accepted. This also
retires an over-claim that predates the branch: `0.1.0-rc.2` and `rc.3`
are published and below the `>=0.1.0-rc.6` floor.

`e2e/tests/dsh-installer-version-policy.test.ts` now runs both authorities
over a version matrix and fails when the def accepts anything the peers
reject, so the two cannot drift apart again.

Importing the script ran the CLI. Collecting the new test fetched the live
registry — measured at 354ms with the verdict printed — and once drift
existed would have attempted a real Feishu post before any assertion. Move
the dispatch behind a direct-entry check and prove it with a test that
stubs fetch and re-imports.

Feishu answers a rejected webhook with HTTP 200 and a nonzero code, so
treating every 2xx as delivered discards the alert and logs success doing
it. Mirror the landing notifier's contract: code 0 or absent is delivered,
429/5xx/9499 retry, anything else raises.

The workflow chose the landing webhook but signed with an unpaired secret,
so a bot that verifies signatures would reject the card. Pair them the way
the landing workflow does, and pin the mapping in the topology test.

* test(e2e): check each dsh peer range on its own

The compatibility guard scanned the whole manifest and merged every
comparator into one tuple table, so a single updated peer answered for the
other four. A bump that updates one range and forgets the rest — the
realistic way this goes wrong — read as compatible, which is the partial
state the guard exists to catch.

Parse `peerDependencies`, keep the `@deepseek-ai/dsh-*` entries, and require
every one of them to admit a version before calling it installable.
`cordis` is excluded: it has its own version line and never moves with dsh.

Two cases cover it: a fixture where one peer carries the new comparator and
the others do not, and the floor itself, since a comparator for the right
tuple still rejects a candidate below it. Verified against the real
manifest by downgrading four of the five ranges, which reds the matrix with
`expected [ '0.1.1-rc.1', '0.1.1-rc.2' ] to deeply equal []`.
2026-08-24 17:24:12 +00:00
lefarcenandGitHub d85e78ab7d fix(runtimes): reach a working agent CLI past a broken shim (#7153)
* fix(runtimes): reach a working agent CLI past a broken shim

Resolving an agent's name on PATH only proves a file exists there, never
that it runs. Detection stopped at the first hit, so a wrapper orphaned by
a half-finished `npm i -g` shadowed a perfectly good CLI of the same name
in a later search directory — and because the not-invocable branch dropped
the resolved path, the picker (which renders an unavailable agent only when
it carries one) hid the agent entirely. The user saw "scan found nothing"
with no way to act.

Detection now walks past candidates that cannot be executed, and reports
the path it tried when every candidate fails, so the row stays actionable.

Also on this chain:

- `@open-design/dsh-runtime` pinned its DeepSeek Harness peers to one exact
  release candidate, so it became uninstallable the moment upstream shipped
  the next one. Peers now take ranges. `scripts/guard.ts` gains a narrow,
  logged exemption for external-host plugin manifests — their peers describe
  a host this repo neither installs nor controls, which is the one case
  where an exact pin is wrong rather than reproducible.
- The DSH installers pinned only the entry package while its ~190 siblings
  used caret ranges over prerelease floors. Once a newer release candidate
  existed, the transitive tree floated onto it and its mutually exclusive
  peer sets sent npm into an ERESOLVE backtrack that never converged. Both
  installers now freeze the resolution window alongside the pin.
- The rescan notice counted agents the list never renders, so it announced
  "3 available" above two rows.

* fix(runtimes): make detection's chosen executable the one spawn sites use

The candidate walk taught detection to skip a binary it cannot execute, but
every other resolution — chat, the connection test, the memory summariser,
companion install — called `resolveAgentLaunch` with no skip list and got
back the first file that merely exists on PATH: the broken shim detection
had just rejected. Settings advertised the agent as installed while each
turn exec'd the wrapper, so the fix only ever reached the picker.

Detection is the only stage that spawns anything, so it is the only one that
learns which candidate works. It now publishes that winner, and resolution
prefers it over a fresh first-hit walk. An explicit `*_BIN` override and a
packaged built-in still outrank it, a winner whose file has since vanished
is ignored, and detection clears the entry before each pass so a rescan
after a repair never resolves against a stale one.

This restores the invariant the file already documented — detection probes
the exact path the runtime will spawn — which the walk had broken.

Reported by @mrcfps in review.

* fix(runtimes): keep the remembered executable inside the live candidate set

The winner map made resolution stop being a pure function of the current
environment. It only checked that the remembered file still existed, so a
path learned in a richer environment survived an emptied PATH, a sandboxed
OD_AGENT_HOME, and any other narrowing of the search — resurrecting a binary
the caller could no longer see.

That is not just a test artifact: `GET /api/integrations/vela/status` must
answer `amr-runtime-unavailable` when the vela binary cannot be resolved, and
it started answering "ready" instead, because an earlier detection in the
same process had remembered a path.

The memory now reorders candidates rather than introducing them: it applies
only when the live search already offers the remembered path. An emptied
PATH, a sandbox, and an uninstalled CLI all keep meaning "not found", while
a broken shim shadowing a working binary is still resolved to the winner.

This also settles the scope question raised in review — no separate cache key
is needed, because the candidate set already carries the resolution scope.

* fix(runtimes): treat a launcher whose target is gone as not invocable

A stale global npm wrapper on Windows is a `.CMD`, which OpenDesign runs
through `cmd.exe /d /s /c`. When the package behind it has been
uninstalled, cmd.exe starts, node starts, and only then does node fail to
load the script the wrapper names — a plain exit 1 with MODULE_NOT_FOUND
on stderr. None of the spawn-level signals the classifier looked for
(ENOENT / EACCES / ENOTDIR, exits 126 and 127) can occur along that
chain, so detection read the dead wrapper as a healthy CLI that merely
disliked `--version`, stopped the candidate walk on it, and never reached
the working `dsh.CMD` later on PATH.

Classify invocability from the launcher's own vocabulary as well: cmd.exe's
9009 (the Windows analogue of POSIX 127), node's missing-module report, and
cmd.exe / PowerShell's missing-command and missing-path reports. Exit status
alone still means nothing — a CLI that runs and exits non-zero is a real
answer from the right binary and keeps its place in the walk.

Drop the win32 early return from every case in the fallback suite and write
fixtures per platform, so `dsh.CMD` resolution through PATHEXT and the
cmd.exe wrapping are exercised natively. The orphaned-wrapper regression is
reproduced on every platform — the defect is a classification bug, not an OS
API one, and the daemon suite only runs on Linux in CI.

* fix(runtimes): carry forward the dead paths, not detection's winner

Detection published the candidate it settled on and resolution preferred
it over the first live PATH hit. That pinned the daemon to one binary for
its whole lifetime and let it silently outrank the caller's own PATH: a
CLI that became visible earlier afterwards — a fresh install, a version
manager swapping shims, a caller resolving under its own environment —
could no longer be reached.

The daemon suite caught it on a machine that has a real `vela` installed:
`chat-route` and `connection-test` inject a fake agent CLI ahead of PATH
per case, and the remembered winner outranked it, so 15 cases spawned the
machine's real CLI instead of the fixture. CI never saw it because its
runners have no such CLI to remember.

Carry the proven-dead paths forward instead. Skipping them keeps every
candidate still standing in plain PATH order, while chat, the connection
test, the memory summariser, and companion setup still avoid the shim
detection rejected — which is the whole point of publishing anything.

* docs(runtimes): say why detection keeps its own launcher-error list

`isCliNotInstalledText` in run-failure-classification.ts reads similar
output, and the next reader will reasonably ask why the two are not one.
They answer different questions: that one buckets a finished run for
telemetry, where over-matching costs a mislabelled event, while this one
decides whether to abandon a binary mid-resolution, where over-matching
sends a working CLI's user to a different install.

* fix(runtimes): accept the dsh release line, not one release candidate

This branch moved the installers to 0.1.0-rc.8 and left the agent def
naming 0.1.0-rc.6 as the only supported version, so following our own
one-line install produced "0.1.0-rc.8 has not been tested with this
OpenDesign build" in Settings on a clean machine. That is the same
pinning defect the installers had, one layer up.

`dsh` publishes a stream of release candidates whose sibling packages
peer-require each other, so the version our installer hands the user
moves with upstream. Version policies can now declare the release line
they accept alongside the exact versions exercised, and DeepSeek Harness
declares its own. A version off that line still warns — the point is to
stop pinning, not to stop checking.

Add a cross-boundary guard for the drift itself: the installers and the
agent def cannot import each other, so `e2e/tests` reads both and fails
when an installer pins a version the daemon would not accept.

* docs(contracts): correct what shim-broken now means

The reason's docstring said "exit 126/127", which stopped being the whole
story once detection learned to read a Windows wrapper's stderr: a `.cmd`
starts an interpreter successfully and reports the missing target there,
so no POSIX exit code appears at all.

* test(runtimes): cover the launcher signatures no fixture can produce

`launcherTargetMissing` recognises six ways a launcher says it never
reached its target, and until now two of them had any coverage — the two
a POSIX shell can print. cmd.exe's wording, PowerShell's, and exit 9009
were load-bearing for the Windows fix and untested anywhere.

A fixture cannot close that gap. POSIX masks exit statuses to eight bits,
so a shell asked for 9009 reports 49, and /bin/sh does not speak cmd.exe.
The daemon suite runs on Linux in CI, so driving the classifier directly
is the only way the merge gate can catch a typo in a signature the fix
depends on for the platform the bug came from.

Extract the catch arm verbatim as `classifyVersionProbeFailure` — same
branches, same order, no behaviour change — and feed it the rejection
shape `promisify(execFile)` actually produces. Verified by mutation:
deleting the 9009 branch reds one case, deleting the stderr branch reds
six.

The suite also pins the constraint from the review by name: exit 1 with an
ordinary argument complaint, with no stderr, or with wording that belongs
to run-failure telemetry ("not installed") all stay `spawned`. That last
one is why detection keeps a narrower list than `isCliNotInstalledText`.
2026-08-24 13:04:31 +00:00
f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

Rename the user-facing product name from "Open Design" to "OpenDesign"
across documentation, web/desktop UI strings, i18n locales, prompts,
plugin/skill/design-system content, changelogs, and specs, matching the
landing-page rename already in flight.

Deliberately excluded so installed-app identity stays untouched:
- packages/release PRODUCT_NAME and everything derived from it
  (app bundle names, installer/registry identity, updater feeds)
- tools/pack, tools/release, apps/packaged, landing page (PR #6983)
- mocks/ recorded traces and LICENSE
- test fixtures and comments that encode real on-disk install paths
  (Open Design.app, Program Files\Open Design, AppData paths)

Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host
test runs over the renamed strings.

* fix(web): keep two-letter author monogram for CamelCase brand names

authorInitials split words on whitespace only, so the renamed single-token
author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper
CamelCase boundaries as word breaks so the avatar fallback stays "OD".

* fix(release): restore spaced Windows source artifact names in win.ps1

tools/pack still derives builder outputs from the unchanged PRODUCT_NAME
("Open Design"), so the asset script must look for the spaced filenames.
Matches the mac/mac-intel/linux asset scripts, which were already kept on
the original names.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00
lefarcenandGitHub f9c07517b7 fix: make DeepSeek Harness cancellation authoritative (#6880)
* fix deepseek harness cancellation lifecycle

* fix DeepSeek canceled status rendering

* fix DeepSeek cancellation handshake race

* feat support reasoning selection in composer

* test DeepSeek pre-execute cancellation protocol

* test reasoning selector visual surface
2026-08-14 06:40:10 +00:00
f9fe73c836 feat: add DeepSeek Harness support (#6874)
* feat(daemon): scaffold DeepSeek Harness profile protocol

* feat(daemon): integrate DeepSeek Harness profile runtime

* fix(daemon): preserve headless prompts on native resume

* refactor(daemon): model Harness profile resume explicitly

* fix(daemon): resume Harness sessions after cancel

* test(daemon): verify DeepSeek Harness Windows carrier

* test(daemon): reap DeepSeek Harness Windows process tree

* feat(agents): surface DeepSeek Harness models and branding

* fix(daemon): surface Harness credential guidance

* feat: add bundled DeepSeek Harness setup

* fix: link DeepSeek Harness installer

* fix: link official Harness docs

* fix(daemon): distinguish Harness companion install

* test(pack): verify bundled Harness resources on Windows

* fix: harden Harness profile turn lifecycle

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(dsh-runtime): stop cancelled restore follow-ups

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(daemon): cancel Harness startup before execute

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(pack): build DeepSeek runtime on cold cache

---------

Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: Looper <looper@noreply.github.com>
2026-08-14 02:07:55 +00:00