mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 09:19:42 +02:00
feat(deepseek): read the model a dsh session's TUI route config pins
A DeepSeek session whose screen names no model (dsh-TUI's status bar model field off, or not drawn yet) can still name the model its route config pins (owner request). src/deepseek-route-config.ts resolves it the way dsh and dsh-TUI 0.10.0-beta.1 do, for the session's profile (else the one the launch boots) under the session's dsh home: - dsh composes a profile from patch layers: the bundles, then profiles/<profile>/cordis.patch.yml, then $DSH_HOME/cordis.patch.yml (which outranks it). A patch's `config` replaces the dsh-tui row's whole config, a `name` mismatch skips it, `disabled` turns the row off. - dsh-TUI takes its route from that config only when it names BOTH provider and model (modelRoute.js); anything less falls back to state the config does not hold, so the answer is nothing. The bundle row pins a provider alone by design and is not read (it resolves outside the dsh home); settings.yaml's agent-default-model is the headless default and is never read. - Any doubt answers nothing: a profile without dsh-TUI, a half-pinned route, an unreadable or oversized layer, a symlink out of the dsh home, a mount that does not answer, a file beyond a narrow strict YAML subset (no dependency added: plain keys, single-line string scalars for the values it needs; tags, anchors, aliases, merge keys, multi-line scalars, flow or block-scalar config, duplicate keys, typed scalars, a second document, or a nested row re-defining dsh-tui all answer null). - Bounded and read-only: every path is probed with probePathKind() first, read async with a 64 KiB cap, and must realpath inside the dsh home. Only the model id leaves the module. The default-profile inventory reuses the resolver's classification through a new pure deepSeekProfileFromManifest(), read with the same bounded rules. Not wired to sessions yet; the next commit does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -156,6 +156,11 @@ const STOCK_NON_INTERACTIVE_PROFILES = new Map<string, DeepSeekProfileKind>([
|
||||
/** Profile directory names that are not profiles. */
|
||||
const NON_PROFILE_DIRS = new Set(['node_modules', '.bin', '.pnpm']);
|
||||
|
||||
/** Whether a directory under `$DSH_HOME/profiles` can be a profile at all (not `node_modules`, not hidden). */
|
||||
export function isProfileDirName(name: string): boolean {
|
||||
return !NON_PROFILE_DIRS.has(name) && !name.startsWith('.');
|
||||
}
|
||||
|
||||
function classifyProfile(name: string, bundles: string[]): DeepSeekProfileKind {
|
||||
const haystack = [name, ...bundles].join(' ');
|
||||
// Order matters: a profile that composes BOTH a web app and a tui bundle is a
|
||||
@@ -174,7 +179,19 @@ function classifyProfile(name: string, bundles: string[]): DeepSeekProfileKind {
|
||||
*/
|
||||
function readProfile(profilesDir: string, name: string): DeepSeekProfile | null {
|
||||
try {
|
||||
const raw = readFileSync(join(profilesDir, name, 'package.json'), 'utf-8');
|
||||
return deepSeekProfileFromManifest(name, readFileSync(join(profilesDir, name, 'package.json'), 'utf-8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A profile from its directory name and the text of its `package.json`, or null when
|
||||
* that text is not JSON. Pure, so a caller with its own (bounded, async) reads gets the
|
||||
* same classification as the inventory below.
|
||||
*/
|
||||
export function deepSeekProfileFromManifest(name: string, raw: string): DeepSeekProfile | null {
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as { dsh?: { profile?: { bundles?: unknown } } };
|
||||
const rawBundles = parsed?.dsh?.profile?.bundles;
|
||||
const bundles = Array.isArray(rawBundles) ? rawBundles.filter((b): b is string => typeof b === 'string') : [];
|
||||
@@ -198,7 +215,7 @@ export function listDeepSeekProfiles(): DeepSeekProfile[] {
|
||||
let entries: string[];
|
||||
try {
|
||||
entries = readdirSync(profilesDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory() && !NON_PROFILE_DIRS.has(e.name) && !e.name.startsWith('.'))
|
||||
.filter((e) => e.isDirectory() && isProfileDirName(e.name))
|
||||
.map((e) => e.name);
|
||||
} catch {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user