diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml index b8ff3f88c2..ffd6042015 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md index 02578674b4..c4a0023797 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md index 5764be3f85..0bda1e8405 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md @@ -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 策略。 diff --git a/packages/util/home-paths/README.i18n.yaml b/packages/util/home-paths/README.i18n.yaml index 686bf4de8b..25241e9680 100644 --- a/packages/util/home-paths/README.i18n.yaml +++ b/packages/util/home-paths/README.i18n.yaml @@ -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 diff --git a/packages/util/home-paths/README.md b/packages/util/home-paths/README.md index 2ce9810bd2..0fb09c3a9d 100644 --- a/packages/util/home-paths/README.md +++ b/packages/util/home-paths/README.md @@ -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. diff --git a/packages/util/home-paths/README.zh.md b/packages/util/home-paths/README.zh.md index 0bd46c2239..f02f49f799 100644 --- a/packages/util/home-paths/README.zh.md +++ b/packages/util/home-paths/README.zh.md @@ -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`。展示形式绝不会泄露机器的绝对路径。 diff --git a/packages/util/home-paths/src/index.ts b/packages/util/home-paths/src/index.ts index 356489ef9f..bc43dcc32b 100644 --- a/packages/util/home-paths/src/index.ts +++ b/packages/util/home-paths/src/index.ts @@ -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. * diff --git a/packages/util/home-paths/tests/home-paths.spec.ts b/packages/util/home-paths/tests/home-paths.spec.ts index 2162febc69..48fbcb8944 100644 --- a/packages/util/home-paths/tests/home-paths.spec.ts +++ b/packages/util/home-paths/tests/home-paths.spec.ts @@ -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'), )