fix(cli): close shared config review gaps
This commit is contained in:
@@ -12,7 +12,6 @@ import { createRequire } from 'node:module'
|
||||
import { networkInterfaces } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { Context } from 'cordis'
|
||||
import type { FiberState } from 'cordis'
|
||||
import type { PatchOptions } from '@cordisjs/plugin-include'
|
||||
import yaml from 'js-yaml'
|
||||
import { boot, installFailLoud, loadEnv, loadOverlayPatches, loadPersonalPatches } from '@deepseek-ai/dsh-app-boot'
|
||||
@@ -88,14 +87,6 @@ const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
})
|
||||
const includeYamlSchema = yaml.JSON_SCHEMA.extend(jsExprType)
|
||||
|
||||
/**
|
||||
* Value mirror of cordis's `FiberState` const enum members the sweep needs
|
||||
* (a const enum has no runtime object to import; same rationale as the
|
||||
* client-side mirror in dsh-client-web).
|
||||
*/
|
||||
const FIBER_ACTIVE = 2 as FiberState.ACTIVE
|
||||
const FIBER_PENDING = 0 as FiberState.PENDING
|
||||
|
||||
/** Constructor facts for one dsh invocation over the shared composition (argv already parsed by the surface bin). */
|
||||
export interface AppCLIEntryOptions {
|
||||
/** Absolute path of the shared base config the Loader includes. */
|
||||
@@ -174,10 +165,8 @@ export class AppCLIEntry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the patch set from the non-yml config sources: computed
|
||||
* engineering defaults (the global session root), profile json (user
|
||||
* config, overriding those defaults), CLI flags, and the resolved frontend
|
||||
* dist. Patches replace a row's config wholesale, so each patched row's yml
|
||||
* Compose the patch set from profile json, CLI flags, and the resolved
|
||||
* frontend dist. Patches replace a row's config wholesale, so each patched row's yml
|
||||
* static values are re-read here (bypass parse) and merged under the overrides.
|
||||
*/
|
||||
private composePatches(): void {
|
||||
@@ -189,7 +178,6 @@ export class AppCLIEntry {
|
||||
overrides.set(entryId, bag)
|
||||
}
|
||||
|
||||
|
||||
// Source 1: profile json (missing file = empty; unmapped key = loud).
|
||||
for (const [key, value] of Object.entries(this.readProfile())) {
|
||||
const mapping = PROFILE_MAPPINGS.find(m => m.jsonPath === key)
|
||||
@@ -215,7 +203,6 @@ export class AppCLIEntry {
|
||||
// user config. Workspace knowledge stays here.
|
||||
put('webserver', 'distIndex', this.resolveDistIndex())
|
||||
|
||||
|
||||
this.patches = [...overrides.entries()].map(([id, bag]) => {
|
||||
const yml = rows.get(id)
|
||||
if (yml === undefined) throw new Error(`dsh: patch target row "${id}" not found in ${this.options.configPath}`)
|
||||
@@ -241,28 +228,9 @@ export class AppCLIEntry {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared boot catches import failures, installFailLoud catches late apply
|
||||
* rejections, and the all-ACTIVE sweep
|
||||
* below catches PENDING fibers (cordis inject waiting has no timeout).
|
||||
*/
|
||||
/** Install the diagnostic for plugin rejections that happen after settled boot. */
|
||||
private assertBoot(): void {
|
||||
installFailLoud('dsh')
|
||||
const failures: string[] = []
|
||||
for (const entry of this.ctx.loader.entries()) {
|
||||
if (entry.fiber === undefined || entry.disabled) continue
|
||||
const state = entry.fiber.state
|
||||
if (state === FIBER_ACTIVE) continue
|
||||
if (state === FIBER_PENDING) {
|
||||
const missing = Object.keys(entry.fiber.inject).filter(service => this.ctx.get(service) === undefined)
|
||||
failures.push(`${entry.options.name}: pending (waiting for service${missing.length === 1 ? '' : 's'}: ${missing.join(', ') || 'unknown'})`)
|
||||
} else {
|
||||
failures.push(`${entry.options.name}: fiber state ${String(state)}`)
|
||||
}
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
throw new Error(`dsh: ${String(failures.length)} entr${failures.length === 1 ? 'y' : 'ies'} did not activate\n${failures.join('\n')}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,6 +126,9 @@ Examples:
|
||||
dsh --resume <id> continue a past session
|
||||
`)
|
||||
.exitOverride()
|
||||
// Stop parent options at a subcommand boundary so `web --config` belongs to
|
||||
// Web while `--config ... web` remains a leaked default-surface option.
|
||||
.enablePositionalOptions()
|
||||
// Default surface: option-only (no positional), so `web` can be a real
|
||||
// subcommand without a positional collision.
|
||||
.option('-p, --prompt <task>', 'answer this task without the interactive UI, then exit')
|
||||
|
||||
+8
-8
@@ -10,8 +10,8 @@
|
||||
* workspace, and an in-place resume enters the selected session's own directory.
|
||||
* `dsh meta`
|
||||
* ({@link runMeta}) is the one exception — it makes this harness checkout the
|
||||
* workspace. `dsh upgrade` ({@link runSkillSession}) are fresh
|
||||
* sessions whose first turn auto-invokes a bundled skill. After boot, the
|
||||
* workspace. `dsh upgrade` ({@link runSkillSession}) is a fresh
|
||||
* session whose first turn auto-invokes a bundled skill. After boot, the
|
||||
* agent's system prompt is told the path to this harness checkout so it can
|
||||
* find its own source.
|
||||
* @module @deepseek-ai/dsh/tui
|
||||
@@ -146,7 +146,7 @@ export async function runTui(
|
||||
const app: { current?: Context } = {}
|
||||
// Resume always enters the default surface because meta rejects parent
|
||||
// options, including `--resume`. The resumed session already persists its cwd.
|
||||
const resumeArgs = (sessionId: string, _targetCwd?: string): string[] => [
|
||||
const resumeArgs = (sessionId: string): string[] => [
|
||||
`--resume=${sessionId}`,
|
||||
// Both config flags must survive the handoff: resuming into a different
|
||||
// tree than the session was created in would silently change the agent.
|
||||
@@ -167,7 +167,7 @@ export async function runTui(
|
||||
process.execPath,
|
||||
...process.execArgv,
|
||||
entry,
|
||||
...resumeArgs(sessionId, cwd),
|
||||
...resumeArgs(sessionId),
|
||||
]
|
||||
// `execve` inherits the cwd, and the target session may belong to another
|
||||
// workspace. Enter it BEFORE teardown commits: an unreachable directory
|
||||
@@ -199,14 +199,14 @@ export async function runTui(
|
||||
const replaceTree = configReplace !== undefined
|
||||
const patches = replaceTree ? [] : [
|
||||
...loadOverlayPatches(NAME, TUI_OVERLAY),
|
||||
...config === undefined
|
||||
...resolvedConfig === undefined
|
||||
? loadPersonalPatches(NAME) ?? []
|
||||
: loadOverlayPatches(NAME, resolveConfigPath(resolve(config), undefined)),
|
||||
: loadOverlayPatches(NAME, resolveConfigPath(resolvedConfig, undefined)),
|
||||
]
|
||||
const queryIndexPath = join(tmpdir(), SESSION_QUERY_DB)
|
||||
const ctx = await boot(
|
||||
NAME,
|
||||
replaceTree ? resolveConfigPath(resolve(configReplace), undefined) : BASE_CONFIG,
|
||||
resolvedConfigReplace === undefined ? BASE_CONFIG : resolveConfigPath(resolvedConfigReplace, undefined),
|
||||
patches,
|
||||
(hostCtx) => {
|
||||
// The launcher owns session identity and the exit line: a config-mounted
|
||||
@@ -230,7 +230,7 @@ export async function runTui(
|
||||
rm(`${queryIndexPath}-wal`, { force: true }),
|
||||
rm(`${queryIndexPath}-shm`, { force: true }),
|
||||
])
|
||||
}, 'launcherSessionQueryPath.cleanup')
|
||||
}, `${SESSION_QUERY_SQLITE_PATH_KEY}.cleanup`)
|
||||
if (resumeHost !== undefined) hostCtx.provide('tuiResumeHost', resumeHost)
|
||||
// Seed the first turn only for a fresh session, so resuming never
|
||||
// re-invokes the skill.
|
||||
|
||||
Reference in New Issue
Block a user