Merge commit '0c177e17a23692d018a79bae59ac9a2db6eba59c' into codex/subprocess-native-containment

# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/input.json
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/bundle/acp-app/package.json
#	packages/examples/jsonrpc-demo/src/packaged-bin.ts
#	packages/subagent/subagent-acp/src/run.ts
#	packages/subagent/subagent-acp/tests/subagent-acp.spec.ts
#	packages/subprocess/subprocess-local/README.i18n.yaml
#	packages/subprocess/subprocess-local/README.md
#	packages/subprocess/subprocess-local/README.zh.md
#	pnpm-lock.yaml
#	scripts/smoke-python-runtime.py
This commit is contained in:
pku-xht
2026-08-25 23:22:25 +08:00
4260 changed files with 141534 additions and 56369 deletions
+31 -24
View File
@@ -11,6 +11,8 @@ import { fileURLToPath } from 'node:url'
import { loadLayeredEnv } from '@deepseek-ai/dsh-app-boot'
import { parseDshArgs } from './args.ts'
const PACKAGED_RUNNER_ARG = '--dsh-internal-subprocess-runner'
// Both the source tree (apps/cli/src) and the bundled bin (apps/cli/lib) sit
// one directory under apps/cli, so the checked-in manifest resolves with the
// same relative hop from either artifact.
@@ -21,30 +23,35 @@ function readVersion(): string {
return typeof manifest.version === 'string' ? manifest.version : '0.0.0'
}
const invocation = parseDshArgs(process.argv.slice(2), readVersion())
if (process.argv[2] === PACKAGED_RUNNER_ARG) {
process.argv.splice(2, 1)
await import('@deepseek-ai/dsh-subprocess-local/spawn-runner')
} else {
const invocation = parseDshArgs(process.argv.slice(2), readVersion())
switch (invocation.mode) {
case 'profile': {
const { runProfile } = await import('./profile-boot.ts')
await runProfile({
environment: loadLayeredEnv('dsh'),
profile: invocation.profile,
patchFiles: invocation.patches,
args: invocation.args,
})
break
switch (invocation.mode) {
case 'profile': {
const { runProfile } = await import('./profile-boot.ts')
await runProfile({
environment: loadLayeredEnv('dsh'),
profile: invocation.profile,
patchFiles: invocation.patches,
args: invocation.args,
})
break
}
case 'plugin': {
const { runPlugin } = await import('./plugin.ts')
process.exit(runPlugin(invocation.profile, invocation.args))
break
}
case 'dump-config': {
const { runDumpConfig } = await import('./dump-config.ts')
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches)
break
}
default:
invocation satisfies never
throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`)
}
case 'plugin': {
const { runPlugin } = await import('./plugin.ts')
process.exit(runPlugin(invocation.profile, invocation.args))
break
}
case 'dump-config': {
const { runDumpConfig } = await import('./dump-config.ts')
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches)
break
}
default:
invocation satisfies never
throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`)
}
+6 -1
View File
@@ -120,7 +120,12 @@ function anchorPathSpec(argument: string, cwd: string): string {
export function runPlugin(profile: string, args: readonly string[]): number {
const dir = resolveProfileDir(profile)
if (!existsSync(join(dir, 'package.json'))) {
initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_BUNDLES)
const template = PROFILE_TEMPLATES[profile]
initProfile(
dir,
template?.bundles ?? DEFAULT_PROFILE_BUNDLES,
template?.patchReload,
)
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`)
}
const before = readProfileManifest(NAME, dir)
+64 -57
View File
@@ -2,8 +2,8 @@
* Shared profile boot for every `dsh` surface: resolve the profile, stack its
* patch layers (bundle layers in `dsh.profile.bundles` order, the profile's
* own `cordis.patch.yml`, `--patch` overlays, the telemetry switch), mount the
* tree over the profile's empty root config, keep the profile patch layer
* live, and wire fail-loud plus bounded shutdown.
* tree over the profile's empty root config, apply its selected patch-reload
* lifecycle, and wire fail-loud plus bounded shutdown.
*
* App flags are not the launcher's business: the invocation's inner arguments
* are provided to the tree through `ctx.cmdlineArgs`, where any injected app
@@ -30,16 +30,36 @@ import {
type Profile,
} from '@deepseek-ai/dsh-app-boot'
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
/** Shipped agent-preset root: beside this app's own config, in both source and built layouts. */
const SHIPPED_PRESET_ROOT = fileURLToPath(new URL('../config/agent-presets/', import.meta.url))
import { DSH_LAUNCH_ENVIRONMENT_KEY, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
import { provideCmdline, type AppReady } from '@deepseek-ai/dsh-cmdline'
import { createProcessShutdown, type ProcessShutdown } from './process-shutdown.ts'
const NAME = 'dsh'
/** Launcher-owned readiness signal committed only after boot and host setup succeed. */
function createAppReady(): { service: AppReady; commit(): void } {
let ready = false
const listeners = new Set<() => void>()
return {
service: {
onReady(listener) {
if (ready) {
listener()
return () => {}
}
listeners.add(listener)
return () => { listeners.delete(listener) }
},
},
commit() {
if (ready) return
ready = true
for (const listener of [...listeners]) listener()
listeners.clear()
},
}
}
/**
* The home-level user patch layer (`$DSH_HOME/cordis.patch.yml`), applied
* over every profile's own layer. Resolved per call, not at module load:
@@ -83,26 +103,25 @@ export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: b
}
/**
* Load a resolved profile for `name`: heal the shared module fallback, then
* (re)write the empty root config. The root is always rewritten: the whole
* composition is patch layers, and the vendored Loader's tree write-back (a
* plugin self-disposing persists the current tree) can bake composed rows
* into this file — which would duplicate every bundle insert on the next
* boot. The file exists on disk only because the Loader needs a real include
* root to anchor `baseUrl` at the profile directory (the config dump anchors
* on the same file, so both compose over the identical base).
* Load a resolved profile for `name` and (re)write the empty root config. The
* root is always rewritten: the whole composition is patch layers, and the
* vendored Loader's tree write-back (a plugin self-disposing persists the
* current tree) can bake composed rows into this file — which would duplicate
* every bundle insert on the next boot. The file exists on disk only because
* the Loader needs a real include root to anchor `baseUrl` at the profile
* directory (the config dump anchors on the same file, so both compose over
* the identical base).
* @param name - the profile name.
* @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump).
* @returns the loaded profile.
*/
export function prepareProfile(name: string, userLayer = true): Profile {
healProfilesModuleFallback(INSTALL_ANCHOR)
const profile = loadProfile(NAME, name, INSTALL_ANCHOR, undefined, { userLayer })
writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG)
return profile
}
/** One profile's patch layers (application order) and the row index of its pre-flag composition. */
/** One profile's patch layers, in application order. */
interface ComposedProfile {
profile: Profile
/** Bundle layers concatenated — the part below the user layers on a live reload. */
@@ -111,11 +130,6 @@ interface ComposedProfile {
homePatches: PatchOptions[]
/** Layers above the user layers on a live reload: `--patch` overlays and the telemetry switch. */
overlays: PatchOptions[]
/**
* id → row of the composed tree (bundles + user layers + overlays), for the
* launcher's own row checks.
*/
rows: ReadonlyMap<string, EntryOptions>
}
/** The full patch stack of one composed profile, in application order. */
@@ -130,19 +144,20 @@ function allPatches(composed: ComposedProfile): PatchOptions[] {
/**
* Load `name` and compose its effective patch stack: bundle layers in
* `dsh.profile.bundles` order (the base bundle gates the shell stacks by
* platform on its own rows), the profile's user layer, the home-level user
* `dsh.profile.bundles` order (a base-backed profile gets the base bundle's
* platform-gated shell rows), the profile's user layer, the home-level user
* layer (`$DSH_HOME/cordis.patch.yml` — machine-local preferences that apply
* to every profile, so it outranks the per-profile layer), `--patch` overlays,
* then the telemetry switch.
* @param name - the profile name.
* @param patchFiles - `--patch` overlay paths, in argv order.
* @returns the profile, its patch layers, and the composed row index.
* @returns the profile and its patch layers.
*/
function composeProfile(
async function composeProfile(
name: string,
patchFiles: readonly string[],
): ComposedProfile {
): Promise<ComposedProfile> {
await healProfilesModuleFallback(INSTALL_ANCHOR)
const profile = prepareProfile(name)
const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? []
const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)))
@@ -152,22 +167,9 @@ function composeProfile(
if (typeof row.id === 'string') rows.set(row.id, row)
}
const composedOverlays = [...overlays]
// The SHIPPED root is the part of the roster only this app can resolve: it
// sits beside this app's own config, in both the source and built layouts.
// The writable root the roster appends is `dsh-agent-presets`' own, so a
// launcher that never reaches this patch still finds a person's presets.
if (rows.has('agent-presets')) {
composedOverlays.push({
id: 'agent-presets',
config: {
...(rows.get('agent-presets')?.config ?? {}) as Record<string, unknown>,
roots: [{ path: SHIPPED_PRESET_ROOT, trust: 'system' }],
},
})
}
const telemetryPatch = resolveTelemetryPatch(process.env.DSH_TELEMETRY_DISABLED, rows.has(TELEMETRY_ROW_ID))
if (telemetryPatch !== undefined) composedOverlays.push(telemetryPatch)
return { profile, bundlePatches, homePatches, overlays: composedOverlays, rows }
return { profile, bundlePatches, homePatches, overlays: composedOverlays }
}
/** Options for {@link runProfile}. */
@@ -205,8 +207,9 @@ function suppressShutdownError(ctx: Context, signal: AbortSignal, error: unknown
* @returns the settled root context and the shutdown controller.
*/
export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Context; shutdown: ProcessShutdown }> {
const composed = composeProfile(options.profile, options.patchFiles)
const composed = await composeProfile(options.profile, options.patchFiles)
const app: { current?: Context } = {}
const appReady = createAppReady()
const shutdown = createProcessShutdown(async () => { await app.current?.fiber.dispose() })
const signalShutdown = new AbortController()
const interrupt = (code: number): void => {
@@ -255,27 +258,26 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
provideCmdline(hostCtx, {
args: options.args,
exit: code => void shutdown.shutdown(code),
ready: appReady.service,
})
})
app.current = ctx
// A surface can dispose the whole tree while boot or this post-boot watcher
// setup is still in flight — a signal, or a fast one-shot's appExit. Loader
// presence and fiber state own liveness; the initial check skips a tree
// that already exited, and the catch below re-checks for an exit that
// landed mid-setup. Watching is unconditional: a one-shot surface exits
// through its bounded shutdown, which disposes the watchers before the
// loop drains.
if (!signalShutdown.signal.aborted
// A live-reload profile can dispose the whole tree while post-boot watcher
// setup is in flight — a signal or appExit. Loader presence and fiber state
// own liveness; the initial check skips a tree that already exited, and the
// catch below re-checks for an exit that landed mid-setup. Startup-frozen
// profiles apply every user layer above but install no HMR fallback or watcher.
if (composed.profile.patchReload === 'live'
&& !signalShutdown.signal.aborted
&& ctx.fiber.state === FiberState.ACTIVE
&& ctx.get('loader') !== undefined) {
try {
// Config-only HMR for the live profile patch layer: the web bundle
// disables the shared module-reload `hmr` row (its reload lifecycle is
// untested), so when the composition leaves no HMR service, mount a
// watch-only instance with no module roots — cordis.patch.yml edits stay
// live on every long-lived surface. A silent skip would break the
// documented hot-reload contract. HMR injects the timer service, which a
// bare custom profile may not mount either.
// Config-only HMR for the live profile patch layer: dsh-base disables
// module reload by default, so when no profile explicitly enabled that
// service, mount a watch-only instance with no module roots —
// cordis.patch.yml edits stay live without replacing source modules. A
// silent skip would break the documented reload contract. HMR injects
// the timer service, which a bare custom profile may not mount either.
if (ctx.get('hmr') === undefined) {
if (ctx.get('timer') === undefined) {
await ctx.loader.create({ name: '@deepseek-ai/cordis-plugin-timer' })
@@ -296,5 +298,10 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
suppressShutdownError(ctx, signalShutdown.signal, error)
}
}
if (!signalShutdown.signal.aborted
&& ctx.fiber.state === FiberState.ACTIVE
&& ctx.get('loader') !== undefined) {
appReady.commit()
}
return { ctx, shutdown }
}
+5
View File
@@ -0,0 +1,5 @@
# Clean source checkouts have no build-generated Typert contributor modules.
# The SDK JSON-RPC application does not consume the Typert remote gateway;
# installed builds retain the complete dsh-base row.
- id: typert-loader
disabled: true