feat(home-paths): add dshCachePath

This commit is contained in:
turtle1999 committed 2026-09-10 14:16:35 +08:00
1 parent 40606d1558
commit e4e0e78dd2
8 files changed
+43 -6

No files matched your search

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md
2026-07-24-single-harness-home-resolver.md: 02578674b4547bae54b2edce99c6c4ef53748588
2026-07-24-single-harness-home-resolver.zh.md: 5764be3f85901196cd2a2db77d1fc3838668163f
2026-07-24-single-harness-home-resolver.md: c4a0023797c5946f979cfc7b895512e362ac956b
2026-07-24-single-harness-home-resolver.zh.md: 0bda1e8405b788ce2939b47a9ca3199ef21fc81c
@@ -23,6 +23,8 @@ explicit configured path > $DSH_HOME > ~/.dsh
An empty or whitespace-only `$DSH_HOME` is treated as unset; otherwise `resolve('')` would silently place the home at the current working directory. The harness keeps all user data under one root; there is no XDG config/data/cache split. `dshHomePath(...segments)` joins deployment-owned children onto that root, and `dsh-app-boot` exposes it to Loader `!!js` config expressions before mounting entries, so shipped compositions derive `sessions` and `storages` without copying the resolver. `dshHomeDisplay()` names a resolved root symbolically for user-facing paths — `~/.dsh` for the default home, `$DSH_HOME` for any configured home — so the user-global `AGENTS.md` label never leaks an absolute machine path. It replaces agent-instructions's bespoke default-vs-`$DSH_HOME` check.
`dshCachePath(...segments)` derives cache paths through `dshHomePath('cache', ...segments)`, keeping cache placement under the same home policy. It resolves paths without creating directories; callers own directory creation.
`@deepseek-ai/dsh-home` is deleted. Home-owning providers and boot packages import `resolveDshHome` from `dsh-home-paths`; composition bundles contain only the resolved configuration rows.
`dsh-telemetry` and its separate home policy are absent under the [SDK project toolchain removal](../../archived/simplification/2026-08-11-remove-sdk-project-toolchain.md), leaving this resolver as the sole home policy.
@@ -23,6 +23,8 @@ explicit configured path > $DSH_HOME > ~/.dsh
空或仅含空白的 `$DSH_HOME` 被当作未设置处理;否则,`resolve('')` 会悄悄把 home 落在当前工作目录。harness 把所有用户数据都放在同一个根目录下;不存在 XDG 的 config/data/cache 拆分。`dshHomePath(...segments)` 将部署负责的子路径拼接到该根目录下,`dsh-app-boot` 在挂载条目前向 Loader `!!js` 配置表达式暴露它,因此出厂组合无需复制解析器即可派生 `sessions` 和 `storages`。`dshHomeDisplay()` 为面向用户的路径以符号形式命名已解析的根目录——默认 home 显示为 `~/.dsh`,任何已配置的 home 显示为 `$DSH_HOME`——这样用户全局的 `AGENTS.md` 标签就绝不会泄露机器上的绝对路径。它取代了 agent-instructions 中自定义的「默认值 vs `$DSH_HOME`」判断。
`dshCachePath(...segments)` 通过 `dshHomePath('cache', ...segments)` 派生缓存路径,使缓存位置遵循同一套 home 策略。它只解析路径,不创建目录;目录创建由调用方负责。
`@deepseek-ai/dsh-home` 被删除。拥有 home 配置的提供方与 boot 包从 `dsh-home-paths` 导入 `resolveDshHome`;组合包只包含解析后的配置行。
`dsh-telemetry` 及其独立 home 策略已随 [SDK 项目工具链移除](../../archived/simplification/2026-08-11-remove-sdk-project-toolchain.md)一并消失,因此该解析器是唯一的 home 策略。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/util/home-paths/README.md
README.md: 2ce9810bd29bdb00f5eb2e09d886f31e2ca3b176
README.zh.md: 0bd46c223992c37be01876597aa239cfc8eb768d
README.md: 0fb09c3a9d2deede5d23e9d720e23a3e534bbf81
README.zh.md: f02f49f799d28904241c2ca4550e5c42e03c5186
+4 -1
View File
@@ -29,14 +29,17 @@ Use these helpers wherever a package must agree with the rest of the harness abo
### Resolving the home
```ts
import { resolveDshHome, dshHomePath } from '@deepseek-ai/dsh-home-paths'
import { resolveDshHome, dshHomePath, dshCachePath } from '@deepseek-ai/dsh-home-paths'
const home = resolveDshHome() // configured path, else $DSH_HOME, else ~/.dsh
const settings = dshHomePath('settings') // join one child onto the resolved home
const cache = dshCachePath('models') // $DSH_HOME/cache/models, default ~/.dsh/cache/models
```
An explicit configured path has the highest precedence, then `$DSH_HOME`, then the default `~/.dsh`. An empty or whitespace-only `$DSH_HOME` is treated as unset, so a blank override never resolves the home to the current working directory.
`dshCachePath(...segments)` derives paths from the resolved home's `cache` directory. With no segments it returns the cache directory itself. It returns an absolute path without creating directories.
### Displaying a home
For user-facing paths, render the root symbolically rather than as a machine path: the default home displays as `~/.dsh` and any configured home displays as `$DSH_HOME`. The display form never leaks an absolute machine path.
+4 -1
View File
@@ -29,14 +29,17 @@ kind: "package-library"
### 解析主目录
```ts
import { resolveDshHome, dshHomePath } from '@deepseek-ai/dsh-home-paths'
import { resolveDshHome, dshHomePath, dshCachePath } from '@deepseek-ai/dsh-home-paths'
const home = resolveDshHome() // configured path, else $DSH_HOME, else ~/.dsh
const settings = dshHomePath('settings') // join one child onto the resolved home
const cache = dshCachePath('models') // $DSH_HOME/cache/models, default ~/.dsh/cache/models
```
显式配置的路径优先级最高,然后是 `$DSH_HOME`,最后是默认的 `~/.dsh`。空或仅含空白的 `$DSH_HOME` 视为未设置,因此空白的覆盖值绝不会把主目录解析到当前工作目录。
`dshCachePath(...segments)` 从解析出的主目录下的 `cache` 目录派生路径。不传路径段时返回缓存目录本身。它返回绝对路径,不会创建目录。
### 展示主目录
面向用户的路径请以符号形式渲染根目录,而不是机器路径:默认主目录显示为 `~/.dsh`,任何已配置的主目录显示为 `$DSH_HOME`。展示形式绝不会泄露机器的绝对路径。
+9
View File
@@ -99,6 +99,15 @@ export function dshHomePath(...segments: string[]): string {
return join(resolveDshHome(), ...segments)
}
/**
* Join path segments onto the resolved Harness home's `cache` directory without creating it.
* @param segments - path segments appended to the cache directory; an empty list returns the cache directory itself.
* @returns the normalized absolute cache path.
*/
export function dshCachePath(...segments: string[]): string {
return dshHomePath('cache', ...segments)
}
/**
* Describe a resolved harness home symbolically for user-facing display.
*
@@ -7,6 +7,7 @@ import {
DSH_HOME_DIR_NAME,
canonicalizeWatchPath,
defaultDshHome,
dshCachePath,
dshHomeDisplay,
dshHomePath,
expandHomePath,
@@ -56,6 +57,22 @@ describe('dsh path helpers', () => {
expect(dshHomeDisplay('/some/other/root')).toBe('$DSH_HOME')
})
it.each([
[undefined, join(homedir(), '.dsh')],
['', join(homedir(), '.dsh')],
[' ', join(homedir(), '.dsh')],
['~/env-dsh', join(homedir(), 'env-dsh')],
['./relative-dsh', resolve('./relative-dsh')],
] as const)('resolves cache paths with DSH_HOME=%j', (home, expectedHome) => {
vi.stubEnv('DSH_HOME', home)
try {
expect(dshCachePath()).toBe(join(expectedHome, 'cache'))
expect(dshCachePath('models', 'index.json')).toBe(join(expectedHome, 'cache', 'models', 'index.json'))
} finally {
vi.unstubAllEnvs()
}
})
it('canonicalizes a watcher ancestor while preserving a missing suffix', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-watch-path-'))
const target = join(root, 'target')
@@ -63,6 +80,7 @@ describe('dsh path helpers', () => {
try {
await mkdir(target)
await symlink(target, alias, process.platform === 'win32' ? 'junction' : 'dir')
await expect(canonicalizeWatchPath(alias)).resolves.toBe(await realpath(target))
await expect(canonicalizeWatchPath(join(alias, 'later', 'config.yml'))).resolves.toBe(
join(await realpath(target), 'later', 'config.yml'),
)