Files
deepseek-harness/packages/host/directory-picker-auto/src/index.ts
T
turtle1999 2abb542a22 Adapt current consumers to nontransactional Cordis Loader
Move exact user-patch watching into app boot and audit activation explicitly.
Wait for CLI fallback HMR and preset subtrees. Join the first fiber-disposal
result in the directory chooser and browser package runner. Keep async
update errors observable without restoring generic Loader rollback.

Restore the current workspace-link and Schemastery manifests and their gate.
Preserve unconditional patch-input cloning, awaited Include initialization,
and durable teardown writes. Update diagnostics, snapshots, bilingual docs,
and the exhaustive vendor divergence ledger.

Validation: focused Loader, watcher, preset, inventory, and cleanup tests;
100% watcher coverage; 32 Web preset cases; real CLI webhook model session;
SDK smokes; 13 headless expected cases; 3 recorded-session replays; build,
typecheck, lint contracts, and all 34 doc-sync gates.

A full Node 24/Python 3.12 Vitest run had 21806 passes and two failures:
a baseline-reproduced experimental CSS import failure and a Python probe
timeout that passed alone. The default Node 26/Python 3.9 run additionally
exposed unsupported Python and FileHandle-GC host failures.
2026-09-09 17:33:32 +08:00

105 lines
4.9 KiB
TypeScript

/**
* Adaptive chooser of the directory-picker seam: resolves the host's
* situation once at boot (bind host, SSH launch, display session, Linux
* chooser binary) and mounts the matching interaction — `native` or `browse`
* — as real Loader entries in the in-memory root tree. Each interaction is a
* pair: the Host backend serving the seam capability and the client surface
* occupying ui-workspace's directory-flow holes. Both arrive as ordinary
* entries, so the surface is discovered exactly as a config-row's would be
* and one resolved choice still swaps both faces; pinning an interaction
* remains composing that pair directly instead of this row.
* @module @deepseek-ai/dsh-host-directory-picker-auto
*/
import type { Context } from '@deepseek-ai/cordis'
// Empty type imports carry the `loader` and `webServer` Context merges for the reads below.
import type {} from '@deepseek-ai/cordis-plugin-loader'
import type {} from '@deepseek-ai/dsh-host-webserver'
import { launchedThroughSsh, launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
import { canExecute, hasLinuxChooserBinary } from './probe.ts'
import type { DirectoryPickerBackendKind } from './resolve.ts'
import { resolveDirectoryPickerBackend } from './resolve.ts'
export { canExecute, hasLinuxChooserBinary } from './probe.ts'
export type { DirectoryPickerBackendKind, DirectoryPickerEnv, DirectoryPickerHostFacts } from './resolve.ts'
export { resolveDirectoryPickerBackend } from './resolve.ts'
/** Cordis plugin name. */
export const name = 'directory-picker-auto'
/** Required services: the effective bind host (`webServer`) and the entry tree the backend mounts into (`loader`). */
export const inject = ['webServer', 'loader']
/**
* Host backend package per resolved kind — fixed composition vocabulary, not a
* tunable. Exported because the reference is a runtime string the static
* config gate cannot see in a yml row: `verify-cordis-config` requires every
* app composing this chooser to declare both values as dependencies.
*/
export const BACKEND_PACKAGES: Record<DirectoryPickerBackendKind, string> = {
native: '@deepseek-ai/dsh-host-directory-picker-native',
browse: '@deepseek-ai/dsh-host-directory-picker-browse',
}
/**
* Client surface package per resolved kind, mounted with its backend so one
* resolved interaction still composes both faces. Declared as dependencies by
* every composing app for the same reason as {@link BACKEND_PACKAGES}. Only the
* specifier is referenced here because the packages belong to the Client
* program, so no import of them exists on this side.
*/
export const SURFACE_PACKAGES: Record<DirectoryPickerBackendKind, string> = {
native: '@deepseek-ai/dsh-client-ui-directory-picker-native',
browse: '@deepseek-ai/dsh-client-ui-directory-picker-browse',
}
/**
* Resolve the interaction from one boot-time sample and mount its backend and
* surface as Loader entries; the effect's disposer removes both entries and
* joins their fibers' teardown, so unloading this plugin returns only after
* both faces of the mounted interaction (and their dependents) quiesced.
* @param ctx - cordis context carrying the injected `webServer` and `loader`.
*/
export async function apply(ctx: Context): Promise<void> {
const backend = resolveDirectoryPickerBackend({
bindHost: ctx.webServer.host,
platform: process.platform,
ssh: launchedThroughSsh(launchEnvironmentOf(ctx)),
env: process.env,
linuxChooser: hasLinuxChooserBinary(process.env.PATH, canExecute),
})
await ctx.effect(async () => {
// Root-tree create: the Loader root is in-memory (write() is a no-op), so
// the mounted rows can never be persisted back into a config file. The
// backend lands first: the surface's browser half drives the capability
// the backend registers.
const ids: string[] = []
const unmount = async () => {
for (const id of [...ids].reverse()) {
// Tree teardown (group.stop) can have removed the entry already;
// nothing is left to unmount or await then.
const entry = ctx.loader.store[id]
if (entry === undefined) continue
const disposal = entry.fiber?.dispose()
ctx.loader.remove(id)
await disposal
}
}
try {
for (const name of [BACKEND_PACKAGES[backend], SURFACE_PACKAGES[backend]]) {
const id = await ctx.loader.create({ name })
ids.push(id)
const entry = ctx.loader.resolve(id)
if (entry.fiber === undefined) throw new Error(`directory-picker-auto: failed to load ${name}`)
await entry.fiber.await()
}
} catch (cause) {
// Setup owns the entries it created until it returns the disposer: leaving
// the backend mounted would make a retry collide with its own
// directoryPicker registration.
await unmount()
throw cause
}
return unmount
}, 'directory-picker-auto: interaction entries')
}