feat(paths): add resolveSessionsRoot as the one shared session-store root

Every surface that persists or lists sessions resolves one directory under
the Harness home, so history is shared across working directories instead
of scattered per project.
This commit is contained in:
Turtle
2026-07-28 22:19:13 +08:00
parent e10bf246fb
commit 42e3cceb64
5 changed files with 44 additions and 4 deletions

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/paths/README.md
README.md: b28e684f3183d739c8e229a9b341801dbf345d86
README.zh.md: ab4e8123d19fd56749e3e7a0d59e8cd6bea0c3d0
README.md: 837ee0cdf7687fac58058e04ff5971002124038a
README.zh.md: d34eeb023a506d5c07f32638ac74698b3a913efa

View File

@@ -16,6 +16,10 @@ Shared filesystem path helpers for DeepSeek Harness user data.
`expandHomePath()` expands `~`, `~/...`, and Windows-style `~\...` prefixes against the operating-system home directory. It leaves non-tilde paths and `~user/...` untouched.
## Session store
`resolveSessionsRoot()` resolves the shared session-store root under the Harness home, by the same precedence as `resolveDshHome()`. `SESSIONS_DIR_NAME` owns its directory name: `sessions`. Every surface that persists sessions resolves this one directory, so history is shared across working directories instead of scattered per project; a persistence backend may still partition inside it. Two surfaces resolving different roots would silently split one user's history into disjoint stores, which is why the location is owned here rather than joined per caller.
This package is intentionally small and harness-dep-free so product packages can share user-data path conventions without depending on one another.
## Known Limitations and Deferred Work

View File

@@ -16,9 +16,13 @@ DeepSeek Harness 用户数据的共享文件系统路径辅助工具。
`expandHomePath()` 使用操作系统主目录展开 `~``~/...` 和 Windows 风格的 `~\...` 前缀。它会保留非波浪号路径和 `~user/...` 原样不变。
该包package刻意保持规模小且不依赖 harness以便产品包共享用户数据路径约定而不必彼此依赖。
## 会话存储
## 已知限制与暂缓事项
`resolveSessionsRoot()` 按与 `resolveDshHome()` 相同的优先级,解析 Harness 主目录下的共享会话存储根目录。`SESSIONS_DIR_NAME` 定义其目录名:`sessions`。每个持久化会话的界面都解析这同一个目录,因此历史记录在各工作目录之间共享,而不是按项目分散;持久化后端仍可在其内部分区。若两个界面解析出不同的根目录,会静默地把同一用户的历史记录拆成互不相交的存储,这正是该位置由此处拥有、而非由各调用方自行拼接的原因。
该包刻意保持规模小且不依赖 harness以便产品包共享用户数据路径约定而不必彼此依赖。
## 已知限制与待完成工作
- **展开范围刻意保持狭窄**:只有单独的 `~``~/...``~\...` 使用当前操作系统主目录;`~alice/...` 等指定用户的形式、环境变量和 shell 表达式保持不变。
- **辅助工具不会操作文件系统**:调用方仍负责目录创建、存在性检查、权限,以及对结果路径应用信任策略。

View File

@@ -52,6 +52,27 @@ export function resolveDshHome(configured?: string, env: Record<string, string |
return resolve(expandHomePath(selected))
}
/** Directory name for persisted session logs under the Harness home. */
export const SESSIONS_DIR_NAME = 'sessions'
/**
* Resolve the shared session-store root under the Harness home.
*
* Every surface that persists sessions resolves this one directory, so history
* is shared across working directories instead of scattered per project. A
* persistence backend may still partition inside it. Two surfaces resolving
* different roots would silently split one user's history into disjoint stores,
* so this is a single owned fact rather than a per-caller `join`.
* @param configuredHome - explicit harness-home override, which has highest precedence.
* @param env - environment mapping used to read `DSH_HOME`.
* @returns the normalized absolute session-store root.
*/
export function resolveSessionsRoot(
configuredHome?: string, env: Record<string, string | undefined> = process.env,
): string {
return join(resolveDshHome(configuredHome, env), SESSIONS_DIR_NAME)
}
/**
* Describe a resolved harness home symbolically for user-facing display.
*

View File

@@ -4,10 +4,12 @@ import { describe, expect, it } from 'vitest'
import {
DEFAULT_DSH_HOME_DISPLAY,
DSH_HOME_DIR_NAME,
SESSIONS_DIR_NAME,
defaultDshHome,
dshHomeDisplay,
expandHomePath,
resolveDshHome,
resolveSessionsRoot,
} from '@deepseek-ai/dsh-paths'
describe('dsh path helpers', () => {
@@ -38,6 +40,15 @@ describe('dsh path helpers', () => {
expect(resolveDshHome(undefined, { DSH_HOME: ' ' })).toBe(defaultDshHome())
})
it('resolves the session store under the home it was given, by the same precedence', () => {
expect(SESSIONS_DIR_NAME).toBe('sessions')
expect(resolveSessionsRoot('/tmp/explicit-dsh', { DSH_HOME: '~/env-dsh' }))
.toBe(join(resolve('/tmp/explicit-dsh'), 'sessions'))
expect(resolveSessionsRoot(undefined, { DSH_HOME: '~/env-dsh' }))
.toBe(join(homedir(), 'env-dsh', 'sessions'))
expect(resolveSessionsRoot(undefined, {})).toBe(join(defaultDshHome(), 'sessions'))
})
it('labels a resolved home by whether it is the default root', () => {
expect(dshHomeDisplay(resolve(defaultDshHome()))).toBe('~/.dsh')
expect(dshHomeDisplay('/some/other/root')).toBe('$DSH_HOME')