test(tui): pin the personal overlay to the environment layers the CLI loads

The personal-config smoke asserted that `$DSH_HOME/.env` feeds a `!!js`
expression in the personal `config.yaml` — the hoist this branch removed so
`credentials-local` can own that document and keep stored keys rotatable.

Seed both layers instead and let one expression separate them: the welcome
prefers the personal variable, so it can only render the invoking directory's
value while the harness home's `.env` stays out of `process.env`. The negative
that made the removal worth doing is now asserted in the assembled
application, not just in the provider's unit tests.
This commit is contained in:
Yichen Jiang
2026-07-30 17:09:29 +08:00
parent e7894f4152
commit 52ae578982
@@ -355,18 +355,23 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
expect(output).toContain('\u001B[?2004l') expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS) }, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('applies the personal overlay: config.yaml patches the tree and .env feeds its !!js', async () => { it('applies the personal overlay: config.yaml patches the tree, the invoking directory\'s .env feeds its !!js, and the home .env stays out of the environment', async () => {
// The whole personal-config chain in one boot: the personal .env supplies // The whole personal-config chain in one boot, plus the environment layer
// the variable, config.yaml patches the tui-agent entry with a `!!js` // it deliberately excludes. The single `!!js` expression prefers the
// reference to it, and the banner renders the patched welcome verbatim. // personal variable, so the patched welcome can only render the project
// value when the harness home's .env — the credential store of
// `dsh-credentials-local` — is NOT hoisted into `process.env`; hoisting it
// would make every stored key read as a read-only launch override on the
// next run and hand it to every subprocess the agent starts.
const output = await smoke({ const output = await smoke({
label: 'dsh personal overlay', label: 'dsh personal overlay',
tempDirPrefix: 'dsh-personal-overlay-', tempDirPrefix: 'dsh-personal-overlay-',
binScript: dshBinScript, binScript: dshBinScript,
configArgs: [], configArgs: [],
prepare: seedWorkspace({ prepare: seedWorkspace({
workspace: { '.env': 'DSH_PROJECT_WELCOME=PROJECT OVERLAY READY.\n' },
personal: { personal: {
'.env': 'DSH_PERSONAL_WELCOME=PERSONAL OVERLAY READY.\n', '.env': 'DSH_PERSONAL_WELCOME=HOME ENV LEAKED.\n',
'config.yaml': [ 'config.yaml': [
'- id: tui-agent', '- id: tui-agent',
" name: '@deepseek-ai/dsh-tui-demo'", " name: '@deepseek-ai/dsh-tui-demo'",
@@ -374,14 +379,15 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
' provider: deepseek', ' provider: deepseek',
' model: deepseek-v4-flash', ' model: deepseek-v4-flash',
' workspaceContext: false', ' workspaceContext: false',
' welcome: !!js process.env.DSH_PERSONAL_WELCOME', ' welcome: !!js process.env.DSH_PERSONAL_WELCOME ?? process.env.DSH_PROJECT_WELCOME',
'', '',
].join('\n'), ].join('\n'),
}, },
}), }),
actions: [{ waitFor: 'PERSONAL OVERLAY READY.', send: '/exit\r' }], actions: [{ waitFor: 'PROJECT OVERLAY READY.', send: '/exit\r' }],
}) })
expect(output).toContain('PERSONAL OVERLAY READY.') expect(output).toContain('PROJECT OVERLAY READY.')
expect(output).not.toContain('HOME ENV LEAKED.')
expect(output).toContain('\u001B[?2004l') expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS) }, LOADER_SMOKE_TEST_TIMEOUT_MS)