Files
orca/docs/site
Neil 45f3512a33 feat(agents): add first-class DeepSeek Harness (dsh) support (#22468)
* feat(agents): add first-class DeepSeek Harness (dsh) support

Register DSH as a supervised Orca agent: catalog entry and detection for its
dsh-tui profile, status/question hooks through DeepSeek's own Claude-Code hook
bridge, composer-ready prompt delivery, session resume, headless Source Control
AI, and title identity that no longer collides with Gemini's.

* fix(dsh): reach Orca through DSH's credential scrub and stop reading its title as Gemini

DSH runs command hooks through its own shell executor, which drops every env var whose
name contains KEY, TOKEN, SECRET or PASSWORD — taking ORCA_PANE_KEY and
ORCA_AGENT_LAUNCH_TOKEN with it, so every hook exited without posting. Mirror both onto
scrub-safe aliases at spawn and restore them at the top of the DSH hook script.

Its title collided too: DSH rests on the same glyph Gemini works on, so a resting DSH
pane was relabelled Gemini CLI and reported working forever. Defer both the Gemini
classifier and the title status detector on DSH's whale, in the base module both copies
of that classifier read.

* test(mobile): repin the session-route closure for the DSH agent icon

* fix(dsh): address review — never splice user rows, cover remote panes, keep the diff off argv

- findManagedDshPatchRegion paired an orphan start marker with a later block's end, so a
  truncated write made install/remove delete the user's own rows. Pair each end with the
  nearest preceding start; regression test fails without the fix.
- The relay PTY env builder never applied the scrub-safe aliases, so remote DSH status
  silently never appeared even with the remote hook installed.
- Source Control AI sent the whole diff on argv; send it over stdin with DSH's '-' marker.
- dsh-tui/dst already chose the interactive profile, so a workspace folder named 'web' or
  'plugin' no longer marks a live agent pane non-interactive.
- Isolate USERPROFILE as well as HOME so a Windows run cannot edit the real home.
- Drop the duplicate README badge and revert an incidental doc reformat.

* refactor(dsh): share the managed-hooks reader and tighten the new modules

Reuse before reimplementing: readManagedDshHookEvents was a near-verbatim copy of Muse's,
with byte-identical private helpers. Both now call one readManagedHookEventsFromJson.

Also: one readTextOrAbsent instead of two spellings of the same read (dropping an
existsSync TOCTOU), one status() builder instead of four inline literals, rmSync(force)
instead of exists-then-unlink, and a redundant empty-string guard before JSON.parse.
The patch-file transforms lose their index juggling for a predicate plus a filter.

* fix(dsh): refuse a flow-style patch file, keep its mode, and stop the relay inheriting a pane

- applyManagedDshPatch matched only an exact `[]`, so `[] # keep empty` or a non-empty
  flow sequence got a block entry appended after it — invalid YAML that would leave DSH
  unable to load the user's own patch layer either. It now strips the token from an empty
  sequence (keeping a trailing comment) and returns null for a non-empty one; install
  reports that and changes nothing.
- The patch rewrite dropped an owner-only file to the umask default (CWE-732); pass
  preserveMode.
- The relay PTY env never dropped inherited pane identity the way the local and daemon
  builders do, so a spawn that specified none could inherit the relay's own and every
  agent's hook would report against that pane.

* fix(dsh): keep the flow-style refusal in every status read, and scope the mode test to POSIX

A refused patch file carries no managed region, so getStatus() fell through to a bare
not_installed with detail null — the actionable 'rewrite it as a block sequence' message
only ever reached the one-shot install() return. Export the predicate and check it first,
behind one shared message constant.

The owner-only mode assertion cannot hold on Windows, where chmod only toggles the
read-only attribute and mode & 0o777 reads 0o666 for any writable file.

* docs(readme): restore the DeepSeek Harness badge lost in the rebase

* test(mobile): repin the session-route closure to the measured 4221

Measured, not derived: 4220 without the DSH icon entry, 4221 with it. Two of the three
modules above main's 4218 pin are not this change's — they arrived with the mobile work
after #22570 and were never repinned; the changelog records that split explicitly.

* fix(dsh): settle tui-idle on the agent's own hook, so supervised workers see it ready

Reported by a tester on the adhoc build: `terminal wait --for tui-idle` ran to its 90s
timeout against an already-ready DSH composer, so a supervised worker never sees the agent
as ready.

Every existing tier reads the title, and DSH deliberately carries no title status: its rest
prefix is Gemini's working glyph, so the detector reports none. A fresh first-party `done`
is better evidence than any title anyway — it is the agent's own account of its own turn,
and normalizeDshEvent drops subagent events, so it is the lead's. Scoped to DSH: for agents
whose hooks report child turns, a mid-turn `done` is the #6011 class this file prevents.

* test(daemon): record the DSH transcript's true-colour I2 divergences

Adding the dsh-tui capture to __fixtures__ enrolled it in the serialize replay sweep, where
it reports 10 I2 divergences and failed the unlisted-transcript default of 0.

Every one is the same shape — visible-grid row=0, a 24-bit background the round trip does
not restore to default — which is DSH's whale intro painting whole rows of true colour.
Verified as an upstream limitation rather than a regression by replaying against the
previous build (build-serialize-addon-at-ref.mjs --ref origin/main): I1 and I3 both hold.

* fix(dsh): return the new tui-idle verdict from the first-party done lane

Main refactored isTuiIdleSatisfied into evaluateTuiIdle, which returns a verdict rather
than a boolean. The DSH lane still returned `true`; it is tier-1 positive evidence, so it
returns READY_STRONG like the title/body lane above it. Re-verified the regression test
still fails without the lane.

* test(relay): pin the scrub-safe pane-identity aliases on the relay spawn path

The relay builds a remote pane's env itself, so the alias mirroring there had no
test: removing the call left every suite green while remote DSH status silently
vanished. Both cases fail without it.

* docs(dsh): point the hook service at the integration reference

The reference doc had no inbound link from anywhere in the repo.
2026-09-27 22:44:18 -07:00
..

@orca/docs

This package contains the product documentation and public media intended to ship alongside Orca's source code.

Open-source product documentation for Orca, served at /docs (same URL shape as https://www.onorca.dev/docs).

This package is a self-contained Next.js app. It is intentionally not a root monorepo workspace member, so installing Electron app dependencies does not pull Next/fumadocs.

Local development

cd docs/site
pnpm --ignore-workspace install
pnpm --ignore-workspace dev

Open http://localhost:3004/docs.

Production build

cd docs/site
pnpm --ignore-workspace install
pnpm --ignore-workspace build
pnpm --ignore-workspace start

pnpm start serves the production build on port 3004. Paths:

Path Purpose
/ Redirects to /docs
/docs Docs index
/docs/... Nested doc pages from content/docs
/docs/api/search Fumadocs search index
/docs/og/... Per-page Open Graph image route

Layout

  • content/docs/ — MDX pages + meta.json navigation
  • public/docs/ — docs-only media, logo, and favicon (GIFs, posters, screenshots)
  • src/app/docs/ — fumadocs routes, OG images
  • src/components/ — docs-scoped chrome (header/footer/search), not marketing site

Updating content

Treat the documentation tree as a deliberate publication boundary. Before importing source material, review the diff for internal references, credentials, third-party media rights, and feature/version claims, then copy only approved pages and assets. Keep the app shell and deployment configuration in this repository so a docs-only pull request can be reviewed and built independently.

Same-domain routing

The docs app is a separate Vercel project (the docs zone) and keeps the public /docs URL namespace. The marketing site remains the default zone for www.onorca.dev; configure its Next/Vercel proxy with these beforeFiles rewrites, replacing DOCS_ORIGIN with the docs project's production URL:

return {
  beforeFiles: [
    {
      source: '/docs',
      destination: `${DOCS_ORIGIN}/docs`
    },
    {
      source: '/docs/:path*',
      destination: `${DOCS_ORIGIN}/docs/:path*`
    },
    {
      source: '/docs-static/:path*',
      destination: `${DOCS_ORIGIN}/docs-static/:path*`
    }
  ]
}

/docs-static is the docs zone's assetPrefix; it prevents _next asset collisions with the marketing zone. Keep the rewrites in the default zone and use ordinary <a> links when navigating between zones. Do not add basePath: '/docs' to this app: its route tree and Fumadocs baseUrl already include /docs, so doing so would publish /docs/docs/... URLs. If a future deployment needs basePath, first move the route tree to an unprefixed src/app/[[...slug]] shape and update every generated/link URL together.

Deploy (Vercel)

  1. Create a Vercel project with Root Directory unset (.). The workflow invokes Vercel from docs/site, so that directory is already the deployment root; setting it again would resolve docs/site/docs/site. Leave automatic Git deployments disabled so this workflow remains the only deployment path.
  2. Framework preset: Next.js. Use pnpm --ignore-workspace install --frozen-lockfile for install and pnpm --ignore-workspace build for build; this package has its own lockfile beside the root workspace.
  3. Set GitHub Actions secrets (required by the production deploy job):
    • VERCEL_TOKEN
    • VERCEL_ORG_ID
    • VERCEL_PROJECT_ID (docs project, not the marketing site)
  4. Protect the docs-production GitHub environment with required reviewers and custom deployment branch policies for main and v* tags. The release-cut dispatch runs from main; the direct published-release fallback runs from a stable tag, while the workflow still authorizes only exact stable tags.
  5. Prepare the three rewrites above in the www.onorca.dev marketing project, but leave them disabled until the docs deployment is verified. A Vercel custom domain cannot delegate only /docs by itself; the default zone must proxy both page/API/media requests and /docs-static assets. Keep the marketing project's old docs routes available for rollback during the transition; putting the proxy rules in beforeFiles ensures they win once enabled.
  6. Deploy a stable desktop tag that contains docs/site, verify the docs origin, then enable the marketing rewrites. Tags cut before this package was added cannot bootstrap the docs project because production intentionally checks out the tag's exact commit. Remove the old marketing docs routes in a follow-up after the proxy is stable.

.github/workflows/docs.yml runs credential-free checks for every pull request. It intentionally does not deploy PR previews: a PR-controlled build must not receive Vercel credentials. A maintainer can add a separate trusted preview workflow later. Production deploys only from an authorized stable desktop release: an exact vX.Y.Z tag, a published non-prerelease release, and the github-actions[bot] release author. Manual dispatch must run from the default branch and name an existing release that meets the same checks. Mobile, prerelease, draft, and human-authored releases are skipped. Fork pull requests remain build-only because GitHub does not expose deployment secrets to fork jobs.

Versioning

versioning.config.ts keeps the current unversioned /docs URLs stable while reserving content/versions/<id> for future snapshots. Versioned routes are not live yet; when they are needed, add the corresponding loader/routes and a version entry. The release workflow can then deploy them without a trigger change.

Isolation from the desktop app

  • Own package.json + pnpm-lock.yaml under docs/site/
  • Not listed in the root pnpm-workspace.yaml; install with pnpm --ignore-workspace
  • Engineering notes remain in repo-root docs/ — do not confuse with this package