Merge remote-tracking branch 'origin/master' into stack/agent-profiles-1-seam

# Conflicts:
#	packages/README.i18n.yaml
#	packages/README.zh.md
#	packages/boot/app-boot/README.i18n.yaml
#	packages/boot/app-boot/README.zh.md
#	scripts/gen-cordis-catalog.ts
#	scripts/verify-package-readme-model-experience.ts
This commit is contained in:
Yichen Jiang
2026-08-09 20:28:03 +08:00
1623 changed files with 15418 additions and 5455 deletions

View File

@@ -166,7 +166,7 @@ function readEnvLayer(
* Load the product CLI's inherited > invoking-directory `.env` > Harness-home
* `.env` snapshot. The Harness home resolves before either file; both files
* are checked before either is applied, and accepted values are materialized
* without replacing inherited ones. The snapshot preserves source provenance.
* without replacing inherited ones. The snapshot preserves which layer supplied each value.
* @param binName - the diagnostic prefix on the diagnostics.
* @param cwd - the invoking directory whose `.env` is the project layer.
* @param warn - sink for the one-line misconfiguration diagnostics.
@@ -336,9 +336,9 @@ function parsePatchList(
return parsed as PatchOptions[]
}
/** One overlay patch list with the label provenance comments print for it. */
/** One overlay patch list with the source label printed in dump comments. */
export interface ConfigDumpLayer {
/** Source name shown in provenance comments (a file basename or path). */
/** Source name shown in dump comments (a file basename or path). */
label: string
/** The layer's patches, from {@link loadOverlayPatches} / {@link loadOptionalPatches}. */
patches: PatchOptions[]
@@ -354,10 +354,10 @@ export interface ConfigDumpLayer {
* sees) compose identically — then render the result as YAML in the same
* dialect (`!!js` expressions print verbatim, unevaluated).
*
* Every run of rows with the same provenance is preceded by a `# ==` comment
* Every run of rows from the same file and patch layers is preceded by a `# ==` comment
* naming the file that contributed the rows and any layers that patched them,
* so the output stays a loadable YAML document while showing which section
* comes from which file. Provenance is derived from single-call prefix
* comes from which file. The file and patch labels are derived from single-call prefix
* snapshots (base + layers 1..k), diffed positionally: the patch algorithm
* only rewrites rows in place or appends, so a top-level index identifies one
* row across snapshots, and a layer whose addition changes the row (config
@@ -373,7 +373,7 @@ export interface ConfigDumpLayer {
* @param layers - overlay layers in application order (later wins).
* @param warn - sink for skipped-patch diagnostics; defaults to stderr.
* @returns the composed entry list rendered as a YAML document with
* provenance comment separators.
* source comment separators.
*/
export function renderConfigDump(
binName: string,
@@ -440,7 +440,7 @@ export function renderConfigDump(
return groupedDump(composed, provenance)
}
/** Render the composed rows grouped under one provenance comment per contiguous run. */
/** Render the composed rows grouped under one source-and-patches comment per contiguous run. */
function groupedDump(
composed: readonly unknown[],
provenance: readonly { origin: string; patchedBy: string[] }[],
@@ -456,7 +456,7 @@ function groupedDump(
}
for (let index = 0; index < composed.length; index += 1) {
const record = provenance[index]
/* v8 ignore next -- provenance is index-aligned with composed by construction */
/* v8 ignore next -- this array is index-aligned with composed by construction */
if (record === undefined) continue
const label = record.patchedBy.length === 0
? record.origin

View File

@@ -114,6 +114,11 @@ export function resolveProfileDir(name: string, home: string = resolveDshHome())
/** The shipped profile templates auto-initialized on first use, by name. */
export const PROFILE_TEMPLATES: Record<string, readonly string[]> = {
web: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
headless: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'],
}
/** Installation-owned bundle tuples normalized to the shipped template. */
const INSTALLATION_OWNED_PROFILE_TUPLES: Record<string, readonly string[]> = {
headless: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless'],
}
@@ -203,10 +208,10 @@ function ensureSymlink(link: string, target: string): void {
* directory after the profile's own `node_modules`, so every in-box plugin
* resolves without pnpm ever managing it — the exact "bundles come from the
* installation" contract. The closure (not just direct dependencies) is
* required for out-of-tree plugins: their peer dependencies name seam
* packages (`dsh-compact`, `dsh-invariants`, ...) that the app reaches only
* through its implementation packages. Symlinked packages resolve their own
* dependencies from their real directories (Node's default
* required for out-of-tree plugins: their peer dependencies name Service
* Definition packages (`dsh-compact`, `dsh-invariants`, ...) that the app
* reaches only through its Service provider packages. Symlinked packages
* resolve their own dependencies from their real directories (Node's default
* symlink-following), so each package needs only its one flat link.
* Idempotent: correct links are kept and moved installations are
* re-pointed; a stale link to a vanished package stays until its name is
@@ -226,7 +231,7 @@ export function healProfilesModuleFallback(installAnchor: string, home: string =
// map itself (first resolution wins, matching Node's own nearest-wins).
const queue: { anchor: string; manifest: ProfileManifest }[] = [{ anchor: installAnchor, manifest: appManifest }]
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
// Peer dependencies participate: seam packages (dsh-subprocess,
// Peer dependencies participate: Service Definition packages (dsh-subprocess,
// dsh-compact, ...) are peers of their implementations, never plain
// dependencies, yet out-of-tree plugins import them directly.
/* v8 ignore next -- a real app manifest always declares dependencies */
@@ -279,6 +284,32 @@ export function writeProfileManifest(dir: string, manifest: ProfileManifest): vo
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest, undefined, 2) + '\n')
}
/** Return whether two bundle lists have the same values in the same order. */
function sameBundles(left: readonly string[], right: readonly string[]): boolean {
return left.length === right.length && left.every((value, index) => value === right[index])
}
/**
* Normalize an exact installation-owned bundle tuple to its shipped template
* while preserving every other manifest field. Any other list is user-owned.
*/
function normalizeShippedProfile(name: string, dir: string, manifest: ProfileManifest): ProfileManifest {
const installationOwned = INSTALLATION_OWNED_PROFILE_TUPLES[name]
const current = PROFILE_TEMPLATES[name]
const bundles = manifest.dsh?.profile?.bundles
if (installationOwned === undefined || current === undefined || bundles === undefined
|| !sameBundles(bundles, installationOwned)) return manifest
const normalized: ProfileManifest = {
...manifest,
dsh: {
...manifest.dsh,
profile: { ...manifest.dsh?.profile, bundles: [...current] },
},
}
writeProfileManifest(dir, normalized)
return normalized
}
/**
* Resolve a package's root directory from one anchor without depending on the
* package exporting `./package.json` (`require.resolve` would need that):
@@ -350,7 +381,7 @@ export function loadProfile(
}
initProfile(dir, template)
}
const manifest = readProfileManifest(binName, dir)
const manifest = normalizeShippedProfile(name, dir, readProfileManifest(binName, dir))
// A hand-written profile manifest may omit the dsh section entirely.
const bundles = manifest.dsh?.profile?.bundles ?? []
const layers = bundles.map((packageName): ProfileLayer => {