Merge remote-tracking branch 'origin/master' into feat/ripgrep-packaged-binary

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md
#	.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md
This commit is contained in:
Huanqi Cao
2026-08-02 12:17:35 +08:00
140 changed files with 4649 additions and 862 deletions

View File

@@ -35,11 +35,6 @@ const EXPECTED_TUI_TOOLS = [
'get_goal',
'ralph',
'read',
'session_event_read',
'session_event_search',
'session_event_trace',
'session_search',
'session_trace',
'skill',
'str_replace_editor',
'subagent',

View File

@@ -1,4 +1,5 @@
import { createUserMessage, createMessage } from '@deepseek-ai/dsh-llm'
import { createHash } from 'node:crypto'
import { realpathSync } from 'node:fs'
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
@@ -6,6 +7,7 @@ import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
import { PREPARED_ENTRY_FILENAME, prepareDshPlugin } from '@deepseek-ai/dsh-repository-plugin'
import { packChunkRuns, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
import { logPath, toHeaderLine } from '../../../packages/session-persistence/session-persistence-jsonl/src/format.ts'
import { runTuiPtySmoke, type TuiPtySmokeOptions } from './pty-harness.ts'
@@ -66,6 +68,38 @@ function seedWorkspace(
}
}
/**
* Run the real `prepareDshPlugin` over an equivalent one-skill `.dsh-plugin`
* package and return the generated wrapper text, so the smoke's cache-seeded
* wrapper can never drift from the generator's template.
*/
async function generatePreparedWrapper(pluginName: string): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'dsh-smoke-wrapper-'))
try {
const plugin = join(root, '.dsh-plugin')
await mkdir(join(root, 'skills', 'config-only-repository'), { recursive: true })
await writeFile(join(root, 'skills', 'config-only-repository', 'SKILL.md'), [
'---',
'name: config-only-repository',
'description: Generator input; the seeded cache copy owns the visible text.',
'---',
'',
'Repository instructions.',
'',
].join('\n'))
await mkdir(plugin, { recursive: true })
await writeFile(join(plugin, 'package.json'), `${JSON.stringify({
name: pluginName,
version: '0.0.0',
dsh: { skills: ['../skills'] },
}, undefined, 2)}\n`)
await prepareDshPlugin(plugin)
return await readFile(join(plugin, PREPARED_ENTRY_FILENAME), 'utf8')
} finally {
await rm(root, { recursive: true, force: true })
}
}
/** Seed one real plaintext JSONL session for the `/resume` selector and host handoff smoke. */
async function seedResumeSession(cwd: string): Promise<void> {
const sessionCwd = realpathSync.native(cwd)
@@ -640,6 +674,54 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
expect(output).toContain('\u001B[?2004l')
}, PTY_SMOKE_TEST_TIMEOUT_MS)
it('loads a cached repository Plugin from personal config alone', async () => {
const source = 'github:fixture/repository#fixed-ref'
const specifier = `${source}&path:/.dsh-plugin`
const key = createHash('sha256').update(specifier).digest('hex')
const packageRoot = `cache/repository-plugins/${key}/node_modules/repository`
// Produced by the real generator (prepareDshPlugin over an equivalent
// .dsh-plugin package) rather than hand-written, so a wrapper-template
// change cannot leave this smoke exercising a stale shape. The cache
// LAYOUT below (sha256 key, marker, node_modules/repository) remains a
// deliberate external pin of the durable on-disk format.
const wrapper = await generatePreparedWrapper('config-only-fixture')
const output = await smoke({
label: 'dsh personal repository Plugin',
tempDirPrefix: 'dsh-personal-repository-plugin-',
binScript: dshBinScript,
configArgs: [],
prepare: seedWorkspace({
personal: {
'config.yaml': [
'- id: repository-plugins',
" name: '@deepseek-ai/dsh-repository-plugin'",
' config:',
' repositories:',
` - '${source}'`,
'',
].join('\n'),
[`cache/repository-plugins/${key}/.repository-cache.json`]: `${JSON.stringify({ specifier })}\n`,
[`${packageRoot}/dsh-plugin.mjs`]: wrapper,
[`${packageRoot}/dsh-plugin-assets/skills/0/config-only-repository/SKILL.md`]: [
'---',
'name: config-only-repository',
'description: CONFIG_ONLY_REPOSITORY_SKILL',
'---',
'',
'Repository instructions.',
'',
].join('\n'),
},
}),
actions: [
{ waitFor: 'main-session-', send: '/skill:config-only' },
{ waitFor: 'CONFIG_ONLY_REPOSITORY_SKILL', send: '\x03/exit\r' },
],
})
expect(output).toContain('CONFIG_ONLY_REPOSITORY_SKILL')
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('fails loud instead of booting when the personal config.yaml is invalid', async () => {
const output = await smoke({
label: 'dsh invalid personal config',