refactor(paths): collapse harness home resolution into one resolver
Delete @deepseek-ai/dsh-home and make dsh-paths the sole owner of the single-root harness home ($DSH_HOME || ~/.dsh). Migrate tool-bash, skill-local, and agent-spine-demo off dsh-home, and fold telemetry's divergent globalConfigDir onto the shared resolver, dropping its second XDG/APPDATA policy and the deepseek-harness namespace so the anonymous id lives under the harness home. Add dshHomeDisplay() for symbolic user-facing paths, replacing workspace-context's bespoke check.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
2026-07-24-single-harness-home-resolver.md: ec733dedc812ac029fb8aace074c35b7c3ccb873
|
||||
2026-07-24-single-harness-home-resolver.zh.md: 61bdee119f6cb729f751c8a65b4360da5a259028
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: One harness home resolver
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-24-single-harness-home-resolver.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The harness had three inconsistent conventions for "where does DeepSeek Harness user data live":
|
||||
|
||||
- `@deepseek-ai/dsh-home` resolved `configured ?? $DSH_HOME ?? ~/.dsh`.
|
||||
- `@deepseek-ai/dsh-paths` shipped a **second** `resolveDshHome` with the same precedence plus tilde expansion — a near-duplicate of `dsh-home` that no gate flagged because the two lived in different packages and had already drifted (only one expanded tildes).
|
||||
- `@deepseek-ai/dsh-telemetry`'s `globalConfigDir` used a *different* policy entirely: `DSH_CONFIG_HOME > $XDG_CONFIG_HOME/deepseek-harness > %APPDATA%/deepseek-harness > ~/.config/deepseek-harness`.
|
||||
|
||||
So most of the product parked everything under one `~/.dsh` root while telemetry alone stored its anonymous id elsewhere, under a `deepseek-harness` namespace that contradicts the repo-wide `dsh` shorthand (`DSH_HOME`, `@deepseek-ai/dsh-*`, `~/.dsh`). Two resolvers plus a divergent third policy means no single home fact.
|
||||
|
||||
## Decision
|
||||
|
||||
One resolver owns the harness home, in `@deepseek-ai/dsh-paths`, single-root:
|
||||
|
||||
```
|
||||
explicit configured path > $DSH_HOME > ~/.dsh
|
||||
```
|
||||
|
||||
The harness keeps all user data under one root; there is no XDG config/data/cache split. `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 workspace-context's bespoke default-vs-`$DSH_HOME` check.
|
||||
|
||||
`@deepseek-ai/dsh-home` is deleted. Its three importers (`dsh-tool-bash`, `dsh-skill-local`, `dsh-agent-spine-demo`) now import `resolveDshHome` from `dsh-paths`. `dsh-telemetry`'s `globalConfigDir` delegates to `resolveDshHome`, dropping its second resolver, the `DSH_CONFIG_HOME` override, the XDG/`%APPDATA%` branches, and the `deepseek-harness` namespace; the anonymous id now lives directly under the harness home.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Leave the two `resolveDshHome` copies in place.** They had already drifted (one expands tildes, one didn't) and encode the same cross-cutting fact twice. Consolidation is the point of the `util/` layer; a duplicate resolver is a latent divergence bug.
|
||||
|
||||
**Adopt XDG (honor `$XDG_CONFIG_HOME`, or split config/data/cache into separate trees).** Considered and dropped in favor of one obvious root. A single `$DSH_HOME || ~/.dsh` ground truth matches `~/.claude` / `~/.aws`, needs no per-kind reclassification of every `~/.dsh` consumer, and leaves no resolver asymmetry to reconcile. Telemetry aligning onto the same root — rather than keeping its own XDG path — is precisely the divergence this removes.
|
||||
|
||||
**Keep telemetry's own config dir.** Its `deepseek-harness` namespace and separate XDG policy were the lone exception to the `dsh`/`~/.dsh` convention. Folding it onto the shared resolver is what makes "one home fact" true.
|
||||
|
||||
## Consequences
|
||||
|
||||
- One home fact, one resolver. `dsh-paths` is the sole owner; the `util/` group loses the `home` package.
|
||||
- Telemetry's anonymous id moves from `~/.config/deepseek-harness/telemetry.json` to the harness home (`~/.dsh/telemetry.json` by default). Under the pre-release "backends reject old formats" stance this needs no migration: an orphaned old id simply regenerates once, and the id is anonymous by construction.
|
||||
- Telemetry drops Windows `%APPDATA%` handling. `resolveDshHome` uses `os.homedir()`, which is correct on Windows; the harness does not special-case `%APPDATA%` for its single root.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note:单一 harness home 解析器
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-24-single-harness-home-resolver.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
对于"DeepSeek Harness 用户数据存放在哪里",harness 里存在三套互不一致的约定:
|
||||
|
||||
- `@deepseek-ai/dsh-home` 按 `configured ?? $DSH_HOME ?? ~/.dsh` 解析。
|
||||
- `@deepseek-ai/dsh-paths` 又提供了**第二个** `resolveDshHome`,优先级相同但额外做了波浪号展开——它几乎是 `dsh-home` 的重复实现,却没有任何门禁发现,因为两者分属不同的包,而且早已漂移(只有一个会展开波浪号)。
|
||||
- `@deepseek-ai/dsh-telemetry` 的 `globalConfigDir` 采用了*完全不同*的策略:`DSH_CONFIG_HOME > $XDG_CONFIG_HOME/deepseek-harness > %APPDATA%/deepseek-harness > ~/.config/deepseek-harness`。
|
||||
|
||||
于是产品的大部分内容都停放在同一个 `~/.dsh` 根目录下,唯独 telemetry 把匿名 id 存到别处,落在一个 `deepseek-harness` 命名空间里,这与全仓库通行的 `dsh` 简写(`DSH_HOME`、`@deepseek-ai/dsh-*`、`~/.dsh`)相冲突。两个解析器再加上一个各行其是的第三套策略,意味着不存在单一的 home 事实。
|
||||
|
||||
## 决策
|
||||
|
||||
由一个解析器统一掌管 harness home,落在 `@deepseek-ai/dsh-paths`,采用单一根目录:
|
||||
|
||||
```
|
||||
explicit configured path > $DSH_HOME > ~/.dsh
|
||||
```
|
||||
|
||||
harness 把所有用户数据都放在同一个根目录下;不存在 XDG 的 config/data/cache 拆分。`dshHomeDisplay()` 为面向用户的路径以符号形式命名已解析的根目录——默认 home 显示为 `~/.dsh`,任何已配置的 home 显示为 `$DSH_HOME`——这样面向用户全局的 `AGENTS.md` 标签就绝不会泄露机器上的绝对路径。它取代了 workspace-context 中自定义的"默认值 vs `$DSH_HOME`"判断。
|
||||
|
||||
`@deepseek-ai/dsh-home` 被删除。它的三个引用方(`dsh-tool-bash`、`dsh-skill-local`、`dsh-agent-spine-demo`)现在从 `dsh-paths` 导入 `resolveDshHome`。`dsh-telemetry` 的 `globalConfigDir` 转而委托给 `resolveDshHome`,去掉了它的第二个解析器、`DSH_CONFIG_HOME` 覆盖项、XDG/`%APPDATA%` 分支以及 `deepseek-harness` 命名空间;匿名 id 现在直接存放在 harness home 之下。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**保留两份 `resolveDshHome` 副本。** 它们早已漂移(一个展开波浪号,一个不展开),并把同一条横切事实编码了两遍。`util/` 层的意义正是在于合并,重复的解析器是一个潜在的分歧 bug。
|
||||
|
||||
**采用 XDG(遵从 `$XDG_CONFIG_HOME`,或把 config/data/cache 拆分到各自的目录树)。** 经过考虑后放弃,转而采用一个显而易见的根目录。单一的 `$DSH_HOME || ~/.dsh` 基准事实与 `~/.claude` / `~/.aws` 一致,无需对每个 `~/.dsh` 消费方按类别重新归类,也不留下任何需要协调的解析器不对称。telemetry 对齐到同一根目录——而不是保留自己的 XDG 路径——正是本决策所要消除的那种分歧。
|
||||
|
||||
**保留 telemetry 自己的 config 目录。** 它的 `deepseek-harness` 命名空间和独立的 XDG 策略是唯一违背 `dsh`/`~/.dsh` 约定的例外。把它折叠到共享解析器上,才让"单一 home 事实"成真。
|
||||
|
||||
## 影响
|
||||
|
||||
- 单一 home 事实,单一解析器。`dsh-paths` 是唯一归属方;`util/` 组失去了 `home` 包。
|
||||
- telemetry 的匿名 id 从 `~/.config/deepseek-harness/telemetry.json` 移到 harness home(默认为 `~/.dsh/telemetry.json`)。在预发布的"后端拒绝旧格式"立场下,这无需迁移:一个遗留的旧 id 只会重新生成一次,而且该 id 本就是匿名构造的。
|
||||
- telemetry 去掉了 Windows `%APPDATA%` 处理。`resolveDshHome` 使用 `os.homedir()`,这在 Windows 上是正确的;harness 不会为它的单一根目录对 `%APPDATA%` 做特殊处理。
|
||||
+2
-2
@@ -31,7 +31,7 @@ The model-facing bash package owns a `ctx.bashEnv` registry. A contributor decla
|
||||
|
||||
The registry rebuilds a trusted overlay for every foreground and background bash `ToolExecution`:
|
||||
|
||||
- `DSH_HOME` is always the absolute configured Harness home. The standalone [`@deepseek-ai/dsh-home`](../../../../packages/util/home/README.md) utility owns its precedence: explicit `dshHome`, then ambient `$DSH_HOME`, then `~/.dsh`.
|
||||
- `DSH_HOME` is always the absolute configured Harness home. The standalone [`@deepseek-ai/dsh-paths`](../../../../packages/util/paths/README.md) utility owns its precedence: explicit `dshHome`, then ambient `$DSH_HOME`, then `~/.dsh`.
|
||||
- `DSH_SHELL=1` is always present and identifies a model bash child managed by DeepSeek Harness.
|
||||
- `DSH_SESSION_ID` is present when the execution has an agent and equals `agent.session.header.id`.
|
||||
- The built-in persistence translator contributes `DSH_SESSION_JSONL` only when `ctx.sessionPersistence.locate(header)` returns `kind: 'jsonl'`.
|
||||
@@ -54,7 +54,7 @@ A fresh session receives its id before the first turn, so its first bash call ca
|
||||
|
||||
Resume reuses the loaded header and therefore the same id and location. Fork and spawn create new session ids and locations. Parent and child calls resolve from their own `ToolExecution.agent`; each command receives an immutable snapshot even when calls overlap. A persistence service replacement affects later collections because the translator queries `ctx.get('sessionPersistence')` at execution time; the registry itself is effect-scoped and HMR-safe.
|
||||
|
||||
`dshHome` is session-independent deployment context. Agent-core resolves one value through `@deepseek-ai/dsh-home` and routes it to both tool-bash and local skill discovery; standalone consumers call the same resolver. If top-level `dshHome` and `skills.local.dshHome` are both supplied and resolve differently, composition fails instead of exposing contradictory homes. Persistence may change independently without freezing its facts into the session prefix.
|
||||
`dshHome` is session-independent deployment context. Agent-core resolves one value through `@deepseek-ai/dsh-paths` and routes it to both tool-bash and local skill discovery; standalone consumers call the same resolver. If top-level `dshHome` and `skills.local.dshHome` are both supplied and resolve differently, composition fails instead of exposing contradictory homes. Persistence may change independently without freezing its facts into the session prefix.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -1619,7 +1619,6 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
|
||||
- `@deepseek-ai/dsh-app-boot` ([`packages/ui/app-boot/src/index.ts`](../packages/ui/app-boot/src/index.ts))
|
||||
- `@deepseek-ai/dsh-brand` ([`packages/util/brand/src/index.ts`](../packages/util/brand/src/index.ts))
|
||||
- `@deepseek-ai/dsh-helper` ([`packages/sdk/helper/src/index.ts`](../packages/sdk/helper/src/index.ts))
|
||||
- `@deepseek-ai/dsh-home` ([`packages/util/home/src/index.ts`](../packages/util/home/src/index.ts))
|
||||
- `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts))
|
||||
- `@deepseek-ai/dsh-jsonrpc-demo` ([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts))
|
||||
- `@deepseek-ai/dsh-loader-smoke` ([`packages/support/loader-smoke/src/index.ts`](../packages/support/loader-smoke/src/index.ts))
|
||||
|
||||
@@ -9,7 +9,6 @@ Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, deri
|
||||
flowchart TD
|
||||
subgraph group_util["packages/util"]
|
||||
pkg_brand["brand"]
|
||||
pkg_home["home"]
|
||||
pkg_paths["paths"]
|
||||
pkg_retention["retention"]
|
||||
pkg_timeout["timeout"]
|
||||
@@ -167,6 +166,7 @@ flowchart TD
|
||||
pkg_helper --> pkg_brand
|
||||
pkg_scripts --> pkg_app_boot
|
||||
pkg_telemetry --> pkg_brand
|
||||
pkg_telemetry --> pkg_paths
|
||||
pkg_llm_deepseek --> pkg_llm
|
||||
pkg_llm_deepseek --> pkg_timeout
|
||||
pkg_llm_pi_ai --> pkg_llm
|
||||
@@ -222,7 +222,7 @@ flowchart TD
|
||||
pkg_fs_local --> pkg_fs
|
||||
pkg_fs_policy --> pkg_fs
|
||||
pkg_skill_local --> pkg_fs
|
||||
pkg_skill_local --> pkg_home
|
||||
pkg_skill_local --> pkg_paths
|
||||
pkg_skill_local --> pkg_skill
|
||||
pkg_compact_basic --> pkg_agent
|
||||
pkg_compact_basic --> pkg_compact
|
||||
@@ -304,8 +304,8 @@ flowchart TD
|
||||
pkg_tool_goal --> pkg_tools
|
||||
pkg_tool_bash --> pkg_agent
|
||||
pkg_tool_bash --> pkg_bash
|
||||
pkg_tool_bash --> pkg_home
|
||||
pkg_tool_bash --> pkg_llm
|
||||
pkg_tool_bash --> pkg_paths
|
||||
pkg_tool_bash --> pkg_sandbox
|
||||
pkg_tool_bash --> pkg_sandbox_policy
|
||||
pkg_tool_bash --> pkg_session_persistence
|
||||
@@ -442,10 +442,10 @@ flowchart TD
|
||||
pkg_agent_spine_demo --> pkg_agent_loop
|
||||
pkg_agent_spine_demo --> pkg_goal
|
||||
pkg_agent_spine_demo --> pkg_goal_session
|
||||
pkg_agent_spine_demo --> pkg_home
|
||||
pkg_agent_spine_demo --> pkg_invariants
|
||||
pkg_agent_spine_demo --> pkg_llm
|
||||
pkg_agent_spine_demo --> pkg_llm_retry
|
||||
pkg_agent_spine_demo --> pkg_paths
|
||||
pkg_agent_spine_demo --> pkg_session
|
||||
pkg_agent_spine_demo --> pkg_skill
|
||||
pkg_agent_spine_demo --> pkg_skill_local
|
||||
@@ -512,7 +512,6 @@ flowchart TD
|
||||
| Package | Group | Depends on |
|
||||
| --- | --- | --- |
|
||||
| [`brand`](../packages/util/brand) | `util` | — |
|
||||
| [`home`](../packages/util/home) | `util` | — |
|
||||
| [`paths`](../packages/util/paths) | `util` | — |
|
||||
| [`retention`](../packages/util/retention) | `util` | — |
|
||||
| [`timeout`](../packages/util/timeout) | `util` | — |
|
||||
@@ -528,7 +527,7 @@ flowchart TD
|
||||
| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime) |
|
||||
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand) |
|
||||
| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot) |
|
||||
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand) |
|
||||
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand), [`paths`](../packages/util/paths) |
|
||||
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
|
||||
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
|
||||
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
|
||||
@@ -555,7 +554,7 @@ flowchart TD
|
||||
| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`timeout`](../packages/util/timeout) |
|
||||
| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) |
|
||||
| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs) |
|
||||
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`home`](../packages/util/home), [`skill`](../packages/skill/skill) |
|
||||
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`paths`](../packages/util/paths), [`skill`](../packages/skill/skill) |
|
||||
| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) |
|
||||
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`session`](../packages/core/session) |
|
||||
@@ -577,7 +576,7 @@ flowchart TD
|
||||
| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`home`](../packages/util/home), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||
@@ -602,7 +601,7 @@ flowchart TD
|
||||
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
|
||||
| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`commands`](../packages/ui/commands), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
|
||||
@@ -50,11 +50,6 @@
|
||||
"project": ["src/**/*.ts"],
|
||||
"ignoreDependencies": ["cordis"]
|
||||
},
|
||||
"packages/util/home": {
|
||||
"entry": ["tests/**/*.spec.ts"],
|
||||
"project": ["src/**/*.ts", "tests/**/*.ts"],
|
||||
"ignoreDependencies": ["cordis"]
|
||||
},
|
||||
"packages/util/timeout": {
|
||||
"entry": ["tests/**/*.spec.ts"],
|
||||
"project": ["src/**/*.ts", "tests/**/*.ts"],
|
||||
|
||||
@@ -26,7 +26,7 @@ The plugin also contributes the `tool:bash` prompt section (order 105): check th
|
||||
|
||||
### Managed shell environment
|
||||
|
||||
Every foreground and background model bash call receives a newly collected trusted `DSH_*` environment. `DSH_HOME` is the absolute Harness home resolved by [`@deepseek-ai/dsh-home`](../../util/home/README.md) (`dshHome` config, then ambient `$DSH_HOME`, then `~/.dsh`) and `DSH_SHELL=1` identifies the managed child. Agent calls additionally receive `DSH_SESSION_ID=agent.session.header.id`; when the active persistence seam locates a JSONL artifact they also receive `DSH_SESSION_JSONL=<absolute target path>`. The JSONL path is a location hint: it may not exist before the first flush or contain the current buffered turn, and it is not an authorization credential.
|
||||
Every foreground and background model bash call receives a newly collected trusted `DSH_*` environment. `DSH_HOME` is the absolute Harness home resolved by [`@deepseek-ai/dsh-paths`](../../util/paths/README.md) (`dshHome` config, then ambient `$DSH_HOME`, then `~/.dsh`) and `DSH_SHELL=1` identifies the managed child. Agent calls additionally receive `DSH_SESSION_ID=agent.session.header.id`; when the active persistence seam locates a JSONL artifact they also receive `DSH_SESSION_JSONL=<absolute target path>`. The JSONL path is a location hint: it may not exist before the first flush or contain the current buffered turn, and it is not an authorization credential.
|
||||
|
||||
`ctx.bashEnv` owns collection. Other plugins can register an effect-scoped contributor with a stable name, declared keys/descriptions, and `resolve(execution: ToolExecution)`; duplicate ownership and undeclared runtime keys fail loudly, while `list()` enumerates declarations without executing providers. Harness built-ins reserve `DSH_HOME`, `DSH_SHELL`, and `DSH_SESSION_ID`; tool-bash's persistence translator owns `DSH_SESSION_JSONL` by reading the backend-neutral `sessionPersistence.locate()` seam.
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-bash": "^0.0.1",
|
||||
"@deepseek-ai/dsh-home": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-paths": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-persistence": "^0.0.1",
|
||||
"@deepseek-ai/dsh-sandbox": "^0.0.1",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "^0.0.1",
|
||||
@@ -45,8 +45,8 @@
|
||||
"@deepseek-ai/dsh-user-approval": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-home": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-sandbox": "workspace:^",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ESCALATION_TARGETS, approveEscalation, validateEscalationArgs } from '@
|
||||
import { effectiveSandboxMode } from '@deepseek-ai/dsh-sandbox-policy'
|
||||
import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-bash'
|
||||
import type { DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-bash'
|
||||
import { DSH_HOME_ENV, resolveDshHome } from '@deepseek-ai/dsh-home'
|
||||
import { DSH_HOME_ENV, resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
import { processOutcome } from './background.ts'
|
||||
import { parseExitStatus, renderProcessRead, renderResult } from './render.ts'
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"path": "../../bash/bash"
|
||||
},
|
||||
{
|
||||
"path": "../../util/home"
|
||||
"path": "../../util/paths"
|
||||
},
|
||||
{
|
||||
"path": "../../tasks/tasks"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { lstat, stat } from 'node:fs/promises'
|
||||
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
||||
import type { FileSystem, FsInfo, FsPathInfo, FsTarget, FsVersion } from '@deepseek-ai/dsh-fs'
|
||||
import { assertNever } from '@deepseek-ai/dsh-llm'
|
||||
import { DEFAULT_DSH_HOME_DISPLAY, defaultDshHome } from '@deepseek-ai/dsh-paths'
|
||||
import { dshHomeDisplay } from '@deepseek-ai/dsh-paths'
|
||||
import { resolveConfig, resolveDiscoveryConfig, type ResolvedConfig } from './config.ts'
|
||||
import { renderWorkspaceContext, type RenderedWorkspaceContext } from './render.ts'
|
||||
|
||||
@@ -469,5 +469,5 @@ export async function readScopeInstruction(
|
||||
}
|
||||
|
||||
function userGlobalDisplayPath(dshHome: string): string {
|
||||
return dshHome === resolve(defaultDshHome()) ? `${DEFAULT_DSH_HOME_DISPLAY}/AGENTS.md` : '$DSH_HOME/AGENTS.md'
|
||||
return `${dshHomeDisplay(dshHome)}/AGENTS.md`
|
||||
}
|
||||
@@ -50,7 +50,7 @@ import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
// workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults.
|
||||
```
|
||||
|
||||
The bundle FORWARDS each field to the child that owns it: `agents` and `maxParallelToolCalls` to `agent-loop` (`agents` defaults to `[]`; the cap defaults there), so each app supplies its own pre-created agents — TUI and headless apps pre-create `main`, while the ACP app creates agents on demand at `session/new`; `llmRetry` to the bounded retry policy; `persona` and `toolOrder` to `dsh-system-prompt`; `tools` to the tool registry for its presentation mode; `skills.registry`, `skills.local`, and `skills.tool` to the skill registry, local provider, and model-facing consumer; the required `workspaceContext` choice to `dsh-workspace-context` (`{ maxBytes }` enables loading and `false` disables it); and `toolBash`/`toolTasks` to the two model-facing tool plugins the bundle owns. A `goals` object opts into the persisted domain, model tools, and same-session driver while forwarding `goals.domain` and `goals.tool` to their owners; omission or `false` leaves the stack absent so headless callers retain one-turn settlement. Set `skills.enabled: false` to omit both the local provider and model-facing skill tool, and set `toolTasks: false` to retain the task service for foreground producers without exposing `task_output` / `task_list` / `task_kill`. It resolves `dshHome` once through [`@deepseek-ai/dsh-home`](../../util/home/README.md) and forwards that absolute value to tool-bash's managed environment and enabled local skill discovery. An absent top-level `dshHome` adopts `skills.local.dshHome`; supplying both with different resolved paths fails loudly. `toolBash.enableRunInBackground` controls only the bash producer; independently loaded producers keep their own config. Workspace instructions register before the skill catalog so their session-prefix message renders first. App packages use `pickSpineConfig()` to copy only these bundle-owned fields.
|
||||
The bundle FORWARDS each field to the child that owns it: `agents` and `maxParallelToolCalls` to `agent-loop` (`agents` defaults to `[]`; the cap defaults there), so each app supplies its own pre-created agents — TUI and headless apps pre-create `main`, while the ACP app creates agents on demand at `session/new`; `llmRetry` to the bounded retry policy; `persona` and `toolOrder` to `dsh-system-prompt`; `tools` to the tool registry for its presentation mode; `skills.registry`, `skills.local`, and `skills.tool` to the skill registry, local provider, and model-facing consumer; the required `workspaceContext` choice to `dsh-workspace-context` (`{ maxBytes }` enables loading and `false` disables it); and `toolBash`/`toolTasks` to the two model-facing tool plugins the bundle owns. A `goals` object opts into the persisted domain, model tools, and same-session driver while forwarding `goals.domain` and `goals.tool` to their owners; omission or `false` leaves the stack absent so headless callers retain one-turn settlement. Set `skills.enabled: false` to omit both the local provider and model-facing skill tool, and set `toolTasks: false` to retain the task service for foreground producers without exposing `task_output` / `task_list` / `task_kill`. It resolves `dshHome` once through [`@deepseek-ai/dsh-paths`](../../util/paths/README.md) and forwards that absolute value to tool-bash's managed environment and enabled local skill discovery. An absent top-level `dshHome` adopts `skills.local.dshHome`; supplying both with different resolved paths fails loudly. `toolBash.enableRunInBackground` controls only the bash producer; independently loaded producers keep their own config. Workspace instructions register before the skill catalog so their session-prefix message renders first. App packages use `pickSpineConfig()` to copy only these bundle-owned fields.
|
||||
|
||||
## Why a code bundle, not a shared YAML include
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
"@deepseek-ai/dsh-goal": "^0.0.1",
|
||||
"@deepseek-ai/dsh-goal-session": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-home": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-paths": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm-retry": "^0.0.1",
|
||||
"@deepseek-ai/dsh-workspace-context": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
@@ -52,8 +52,8 @@
|
||||
"@deepseek-ai/dsh-goal-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-home": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-retry": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
|
||||
@@ -29,7 +29,7 @@ import * as toolSkill from '@deepseek-ai/dsh-tool-skill'
|
||||
import * as toolTasks from '@deepseek-ai/dsh-tool-tasks'
|
||||
import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as llmRetry from '@deepseek-ai/dsh-llm-retry'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-home'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
|
||||
export const name = 'agent-spine-demo'
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"path": "../../support/invariants"
|
||||
},
|
||||
{
|
||||
"path": "../../util/home"
|
||||
"path": "../../util/paths"
|
||||
},
|
||||
{
|
||||
"path": "../../bash/tool-bash"
|
||||
|
||||
@@ -7,7 +7,7 @@ Launcher-side telemetry primitives for the dsh-sdk toolchain. This is a plain li
|
||||
| `SecretRedactor` | Conservative safety backstop: replaces secret-shaped values (secret-like keys, known token shapes, PEM blocks, URL credentials, high-entropy opaque tokens) with a placeholder in both parsed values (`redactValue`) and raw text (`redactText`). Never drops a field or line. |
|
||||
| `ConsentResolver` | Parses (never boots) a project `cordis.yml` and reads the telemetry entry's enabled/disabled state as consent; `DO_NOT_TRACK`/CI env force a hard opt-out. |
|
||||
| `buildTelemetryPayload` | Assembles `{command, durationMs, success, cordisYmlContent, packageJsonContent}`, running the redactor over the full `cordis.yml` and `package.json` text. Never reads `.env`; `package.json` ships only alongside a `cordis.yml`, so a command run in a non-SDK directory never uploads that directory's unrelated manifest. |
|
||||
| `getOrCreateAnonymousId` | Random UUID persisted in a per-user GLOBAL config file (never in the project, never derived from git). |
|
||||
| `getOrCreateAnonymousId` | Random UUID persisted in the per-user harness home resolved by [`@deepseek-ai/dsh-paths`](../../util/paths/README.md) (never in the project, never derived from git). |
|
||||
| `TelemetryReporter` | Fire-and-forget send: `report()` never blocks or throws; delivery resolves on every path; `flush()` optionally drains in-flight sends within a cap. |
|
||||
|
||||
Consent is carried by the telemetry entry in `cordis.yml`, so disabling telemetry is disabling that entry. Telemetry reports by default and is off only when a present telemetry entry is explicitly `disabled`: a missing `cordis.yml` (first `create`), an enabled entry, or a `cordis.yml` with no telemetry entry all report. `DO_NOT_TRACK`/CI always deny. The no-config and absent-entry defaults are configurable on `ConsentResolver`.
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-brand": "^0.0.1",
|
||||
"@deepseek-ai/dsh-paths": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-brand": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Per-machine anonymous telemetry id.
|
||||
*
|
||||
* The id is a random UUID persisted in a per-user GLOBAL config file — never in
|
||||
* The id is a random UUID persisted in the per-user harness home — never in
|
||||
* the project, and never derived from the git remote, repository URL, or any
|
||||
* other identifying source (a derived id would make "anonymous" a fiction). The
|
||||
* same id is reused across projects on one machine so telemetry counts machines,
|
||||
@@ -12,52 +12,36 @@
|
||||
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
|
||||
/** A machine-scoped anonymous telemetry id (random UUID v4). */
|
||||
export type AnonymousId = Branded<'AnonymousId'>
|
||||
|
||||
/** Config directory name owned by the DeepSeek Harness across tools. */
|
||||
const CONFIG_NAMESPACE = 'deepseek-harness'
|
||||
|
||||
/** Default file, inside the global config dir, storing the anonymous id. */
|
||||
/** Default file, inside the harness home, storing the anonymous id. */
|
||||
export const ANONYMOUS_ID_FILE_NAME = 'telemetry.json'
|
||||
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
|
||||
/** Ambient seams for locating and generating the id; every field has a default. */
|
||||
export interface AnonymousIdOptions {
|
||||
/** Environment consulted for `DSH_CONFIG_HOME`/`XDG_CONFIG_HOME`/`APPDATA`; defaults to `process.env`. */
|
||||
/** Environment consulted for `DSH_HOME`; defaults to `process.env`. */
|
||||
env?: NodeJS.ProcessEnv
|
||||
/** Platform string used to pick the Windows path; defaults to `process.platform`. */
|
||||
platform?: NodeJS.Platform
|
||||
/** Home directory resolver; defaults to `os.homedir`. */
|
||||
homeDir?: () => string
|
||||
/** UUID generator; defaults to `crypto.randomUUID` (test seam). */
|
||||
randomUUID?: () => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the per-user global config directory for harness tooling.
|
||||
* Precedence: `DSH_CONFIG_HOME` (explicit override) > `XDG_CONFIG_HOME` >
|
||||
* platform default (`%APPDATA%` on Windows, else `~/.config`).
|
||||
* @param options - environment, platform, and home-directory seams.
|
||||
* @returns absolute config directory path for the harness namespace.
|
||||
* Resolve the single-root harness home that stores the anonymous id.
|
||||
* Delegates to {@link resolveDshHome} so telemetry shares the harness's one
|
||||
* home-resolution policy (`DSH_HOME` > `~/.dsh`) instead of maintaining a
|
||||
* second config-directory convention.
|
||||
* @param options - environment seam.
|
||||
* @returns absolute harness home path.
|
||||
*/
|
||||
export function globalConfigDir(options: AnonymousIdOptions = {}): string {
|
||||
const env = options.env ?? process.env
|
||||
const platform = options.platform ?? process.platform
|
||||
const home = options.homeDir ?? homedir
|
||||
if (env.DSH_CONFIG_HOME !== undefined && env.DSH_CONFIG_HOME.length > 0) return env.DSH_CONFIG_HOME
|
||||
if (env.XDG_CONFIG_HOME !== undefined && env.XDG_CONFIG_HOME.length > 0) {
|
||||
return join(env.XDG_CONFIG_HOME, CONFIG_NAMESPACE)
|
||||
}
|
||||
if (platform === 'win32' && env.APPDATA !== undefined && env.APPDATA.length > 0) {
|
||||
return join(env.APPDATA, CONFIG_NAMESPACE)
|
||||
}
|
||||
return join(home(), '.config', CONFIG_NAMESPACE)
|
||||
return resolveDshHome(undefined, options.env ?? process.env)
|
||||
}
|
||||
|
||||
/** Read a valid persisted id from the store, or `undefined` when absent/corrupt. */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { defaultDshHome } from '@deepseek-ai/dsh-paths'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
ANONYMOUS_ID_FILE_NAME,
|
||||
@@ -23,37 +24,24 @@ afterEach(async () => {
|
||||
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
|
||||
describe('globalConfigDir', () => {
|
||||
it('prefers an explicit DSH_CONFIG_HOME override', () => {
|
||||
expect(globalConfigDir({ env: { DSH_CONFIG_HOME: '/custom/dsh' } })).toBe('/custom/dsh')
|
||||
it('prefers an explicit DSH_HOME override', () => {
|
||||
expect(globalConfigDir({ env: { DSH_HOME: '/custom/dsh' } })).toBe('/custom/dsh')
|
||||
})
|
||||
|
||||
it('falls back to XDG_CONFIG_HOME under the harness namespace', () => {
|
||||
expect(globalConfigDir({ env: { XDG_CONFIG_HOME: '/xdg' } })).toBe(join('/xdg', 'deepseek-harness'))
|
||||
})
|
||||
|
||||
it('uses %APPDATA% on Windows', () => {
|
||||
expect(globalConfigDir({ env: { APPDATA: 'C:/Users/x/AppData/Roaming' }, platform: 'win32' }))
|
||||
.toBe(join('C:/Users/x/AppData/Roaming', 'deepseek-harness'))
|
||||
})
|
||||
|
||||
it('falls back to ~/.config on Windows without APPDATA and on posix', () => {
|
||||
const home = () => '/home/dev'
|
||||
expect(globalConfigDir({ env: {}, platform: 'win32', homeDir: home }))
|
||||
.toBe(join('/home/dev', '.config', 'deepseek-harness'))
|
||||
expect(globalConfigDir({ env: {}, platform: 'linux', homeDir: home }))
|
||||
.toBe(join('/home/dev', '.config', 'deepseek-harness'))
|
||||
it('falls back to ~/.dsh when DSH_HOME is unset', () => {
|
||||
expect(globalConfigDir({ env: {} })).toBe(resolve(defaultDshHome()))
|
||||
})
|
||||
|
||||
it('reads process.env by default', () => {
|
||||
// No override supplied: the call must not throw and must return an absolute path.
|
||||
expect(globalConfigDir()).toContain('deepseek-harness')
|
||||
expect(globalConfigDir()).toContain('.dsh')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateAnonymousId', () => {
|
||||
it('creates, persists, and returns a UUID on first use', async () => {
|
||||
const dir = await tempDir()
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
|
||||
expect(id).toMatch(UUID)
|
||||
const stored: unknown = JSON.parse(await readFile(join(dir, ANONYMOUS_ID_FILE_NAME), 'utf8'))
|
||||
expect(stored).toEqual({ anonymousId: id })
|
||||
@@ -61,15 +49,15 @@ describe('getOrCreateAnonymousId', () => {
|
||||
|
||||
it('returns the same persisted id on subsequent calls', async () => {
|
||||
const dir = await tempDir()
|
||||
const first = await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })
|
||||
const second = await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })
|
||||
const first = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
|
||||
const second = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
|
||||
expect(second).toBe(first)
|
||||
})
|
||||
|
||||
it('uses the injected UUID generator', async () => {
|
||||
const dir = await tempDir()
|
||||
const id = await getOrCreateAnonymousId({
|
||||
env: { DSH_CONFIG_HOME: dir },
|
||||
env: { DSH_HOME: dir },
|
||||
randomUUID: () => '00000000-0000-4000-8000-000000000000',
|
||||
})
|
||||
expect(id).toBe('00000000-0000-4000-8000-000000000000')
|
||||
@@ -78,23 +66,23 @@ describe('getOrCreateAnonymousId', () => {
|
||||
it('regenerates when the stored file is corrupt JSON', async () => {
|
||||
const dir = await tempDir()
|
||||
await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), 'not json', 'utf8')
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
|
||||
expect(id).toMatch(UUID)
|
||||
})
|
||||
|
||||
it('regenerates when the stored value is not a valid UUID or object', async () => {
|
||||
const dir = await tempDir()
|
||||
await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), JSON.stringify({ anonymousId: 'nope' }), 'utf8')
|
||||
expect(await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })).toMatch(UUID)
|
||||
expect(await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })).toMatch(UUID)
|
||||
await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), '123', 'utf8')
|
||||
expect(await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: dir } })).toMatch(UUID)
|
||||
expect(await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })).toMatch(UUID)
|
||||
})
|
||||
|
||||
it('returns a usable id even when persistence fails', async () => {
|
||||
const dir = await tempDir()
|
||||
// A regular file where a directory is expected makes mkdir/writeFile fail.
|
||||
await writeFile(join(dir, 'blocker'), 'x', 'utf8')
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_CONFIG_HOME: join(dir, 'blocker') } })
|
||||
const id = await getOrCreateAnonymousId({ env: { DSH_HOME: join(dir, 'blocker') } })
|
||||
expect(id).toMatch(UUID)
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,7 @@
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../util/brand" }
|
||||
{ "path": "../../util/brand" },
|
||||
{ "path": "../../util/paths" }
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ Requires `ctx.skills` (`inject: ['skills']`).
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `dshHome` | `$DSH_HOME` or `~/.dsh` | DeepSeek Harness config root resolved by [`@deepseek-ai/dsh-home`](../../util/home/README.md); scans `skills` under this directory. |
|
||||
| `dshHome` | `$DSH_HOME` or `~/.dsh` | DeepSeek Harness config root resolved by [`@deepseek-ai/dsh-paths`](../../util/paths/README.md); scans `skills` under this directory. |
|
||||
| `agentsHome` | `$DSH_AGENTS_HOME` or `~/.agents` | Shared agent config root scanned for compatible skills. |
|
||||
| `customSkillDirs` | `[]` | Additional local skill roots scanned after project roots and before user roots. |
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-fs": "^0.0.1",
|
||||
"@deepseek-ai/dsh-home": "^0.0.1",
|
||||
"@deepseek-ai/dsh-paths": "^0.0.1",
|
||||
"@deepseek-ai/dsh-skill": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-fs": "workspace:^",
|
||||
"@deepseek-ai/dsh-home": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-skill": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import z from 'schemastery'
|
||||
import type Schema from 'schemastery'
|
||||
import { parse as parseYaml } from 'yaml'
|
||||
import type { FileSystem, FsDirEntry, FsTarget } from '@deepseek-ai/dsh-fs'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-home'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
import {
|
||||
isSkillName,
|
||||
type SkillCandidate,
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
{ "path": "../../../vendor/cosmokit" },
|
||||
{ "path": "../../../vendor/cordis" },
|
||||
{ "path": "../../../vendor/schemastery" },
|
||||
{ "path": "../../util/home" },
|
||||
{ "path": "../../fs/fs" },
|
||||
{ "path": "../../util/paths" },
|
||||
{ "path": "../skill" }
|
||||
]
|
||||
}
|
||||
@@ -5,14 +5,13 @@ Zero-dependency primitives shared across the other groups. A package lands here
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `brand/` | The type-only `Branded<B>` nominal-typing primitive (no runtime code, no harness deps) |
|
||||
| `home/` | Canonical `DSH_HOME` resolution from explicit config, environment, or `~/.dsh` (no harness deps) |
|
||||
| `paths/` | Shared filesystem path constants and helpers for harness user data |
|
||||
| `paths/` | Canonical single-root `DSH_HOME` resolution plus shared filesystem path constants and helpers for harness user data (no harness deps) |
|
||||
| `timeout/` | The timing/classification half of a timeout — `clampTimeout`/`deadline`/`timeoutOf`/`TimeoutReason` (pure functions, no harness deps); termination stays in each capability |
|
||||
| `retention/` | Bounded model-facing output — `ItemRetainer`/`TextRetainer` + neutral notice helpers (pure, no harness deps); business semantics stay in each tool |
|
||||
|
||||
`dsh-brand` is the canonical case: it owns ONLY the `Branded<B>` helper, so a capability package can brand the ids it owns (`dsh-tasks`'s `TaskId`, `dsh-session`'s `SessionId`, …) by depending on `dsh-brand` alone, without pulling in an unrelated package just to reach `Branded`.
|
||||
|
||||
`dsh-home` gives every package the same configurable Harness home without assigning that cross-cutting fact to bash, skills, or a composition bundle. It resolves an explicit value before `$DSH_HOME`, falls back to `~/.dsh`, and returns an absolute path without caching, creating, or mutating anything.
|
||||
`dsh-paths` gives every package the same configurable Harness home without assigning that cross-cutting fact to bash, skills, telemetry, or a composition bundle. It resolves an explicit value before `$DSH_HOME`, falls back to `~/.dsh`, and returns an absolute path without caching, creating, or mutating anything. The harness keeps all user data under one root.
|
||||
|
||||
`dsh-timeout` follows the same shape for the timeout family: `dsh-bash` and `dsh-web-fetch-local` each fuse a caller's cancellation with a deadline and later classify "timed out" vs "cancelled" by depending on `dsh-timeout` alone. It deliberately owns only the timing/classification half — the *termination* (SIGKILL a process group, tear down a fetch socket) stays in each capability, because no shared layer can own every capability's kill (see [the timeout-library Agent Note](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)).
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# @deepseek-ai/dsh-home
|
||||
|
||||
`@deepseek-ai/dsh-home` is the single owner of DeepSeek Harness home-directory resolution. `resolveDshHome(configured?)` returns an absolute path using this precedence:
|
||||
|
||||
1. The explicit `configured` path.
|
||||
2. The `DSH_HOME` environment variable.
|
||||
3. The `.dsh` directory under the current user's home directory.
|
||||
|
||||
The resolver reads its inputs at call time. It does not cache a result, create the directory, or mutate `process.env`; consumers keep ownership of their own configuration fields and pass the configured value when resolving the shared home.
|
||||
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through `dsh-tool-bash`, which exposes the resolved path to model bash as `DSH_HOME` without adding a prompt section.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Resolution only** — the resolver makes a path absolute but does not create it, check access, or canonicalize symlinks; each consumer owns those filesystem decisions.
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-home",
|
||||
"description": "Canonical DeepSeek Harness home-directory resolver",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.d.ts.map",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"cordis": "^4.0.0-rc.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cordis": "^4.0.0-rc.6"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Canonical DeepSeek Harness home-directory resolution.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-home
|
||||
*/
|
||||
|
||||
import { homedir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const DEFAULT_DSH_HOME_DIRNAME = '.dsh'
|
||||
|
||||
/** Environment variable that overrides the default Harness home directory. */
|
||||
export const DSH_HOME_ENV = 'DSH_HOME' as const
|
||||
|
||||
/**
|
||||
* Resolve the DeepSeek Harness home directory without caching or mutating the environment.
|
||||
*
|
||||
* @param configured - Optional configured path, which takes precedence over the environment.
|
||||
* @returns The absolute configured path, `$DSH_HOME`, or `~/.dsh`, in that order.
|
||||
*/
|
||||
export function resolveDshHome(configured?: string): string {
|
||||
return resolve(configured ?? process.env[DSH_HOME_ENV] ?? join(homedir(), DEFAULT_DSH_HOME_DIRNAME))
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { homedir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { DSH_HOME_ENV, resolveDshHome } from '@deepseek-ai/dsh-home'
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs())
|
||||
|
||||
describe('resolveDshHome', () => {
|
||||
it('prefers an explicit configured path and resolves it absolutely', () => {
|
||||
vi.stubEnv(DSH_HOME_ENV, './environment-home')
|
||||
|
||||
expect(resolveDshHome('./configured-home')).toBe(resolve('./configured-home'))
|
||||
})
|
||||
|
||||
it('uses DSH_HOME when no configured path is supplied', () => {
|
||||
vi.stubEnv(DSH_HOME_ENV, './environment-home')
|
||||
|
||||
expect(resolveDshHome()).toBe(resolve('./environment-home'))
|
||||
})
|
||||
|
||||
it('defaults to the .dsh directory under the user home', () => {
|
||||
vi.stubEnv(DSH_HOME_ENV, undefined)
|
||||
|
||||
expect(resolveDshHome()).toBe(join(homedir(), '.dsh'))
|
||||
})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
@@ -4,6 +4,10 @@ Shared filesystem path helpers for DeepSeek Harness user data.
|
||||
|
||||
## DSH home
|
||||
|
||||
`resolveDshHome()` resolves the single-root DeepSeek Harness home. Precedence, highest first: an explicit configured path, `$DSH_HOME`, then `~/.dsh`. The harness keeps all user data under one root.
|
||||
|
||||
`dshHomeDisplay()` names an active root symbolically for user-facing paths: `~/.dsh` for the default home, `$DSH_HOME` for any configured home. It never leaks an absolute machine path.
|
||||
|
||||
`DSH_HOME_DIR_NAME` owns the default user-data directory name: `.dsh`.
|
||||
|
||||
`defaultDshHome()` returns the default DeepSeek Harness home by joining the operating-system home directory with `.dsh`, using Node's platform path rules.
|
||||
|
||||
@@ -36,7 +36,10 @@ export function expandHomePath(path: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an explicitly configured, environment-selected, or default DSH home.
|
||||
* Resolve the single-root DeepSeek Harness home.
|
||||
*
|
||||
* Precedence, highest first: an explicit configured path, `$DSH_HOME`, then
|
||||
* `~/.dsh`. The harness keeps all user data under one root.
|
||||
* @param configured - explicit harness-home override, which has highest precedence.
|
||||
* @param env - environment mapping used to read `DSH_HOME`.
|
||||
* @returns the normalized absolute harness home path.
|
||||
@@ -45,3 +48,15 @@ export function resolveDshHome(configured?: string, env: Record<string, string |
|
||||
const selected = configured ?? env[DSH_HOME_ENV] ?? defaultDshHome()
|
||||
return resolve(expandHomePath(selected))
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a resolved harness home symbolically for user-facing display.
|
||||
*
|
||||
* It never returns an absolute machine path: the default home is labelled
|
||||
* `~/.dsh`, and any configured home is labelled `$DSH_HOME`.
|
||||
* @param resolvedHome - the absolute path returned by {@link resolveDshHome}.
|
||||
* @returns `~/.dsh` for the default home, otherwise `$DSH_HOME`.
|
||||
*/
|
||||
export function dshHomeDisplay(resolvedHome: string): string {
|
||||
return resolvedHome === resolve(defaultDshHome()) ? DEFAULT_DSH_HOME_DISPLAY : `$${DSH_HOME_ENV}`
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
DEFAULT_DSH_HOME_DISPLAY,
|
||||
DSH_HOME_DIR_NAME,
|
||||
defaultDshHome,
|
||||
dshHomeDisplay,
|
||||
expandHomePath,
|
||||
resolveDshHome,
|
||||
} from '@deepseek-ai/dsh-paths'
|
||||
@@ -24,11 +25,16 @@ describe('dsh path helpers', () => {
|
||||
expect(expandHomePath('~other/.dsh')).toBe('~other/.dsh')
|
||||
})
|
||||
|
||||
it('resolves explicit DSH home before environment and default locations', () => {
|
||||
it('resolves explicit path before DSH_HOME and the default', () => {
|
||||
const envHome = join(homedir(), 'env-dsh')
|
||||
|
||||
expect(resolveDshHome(undefined, { DSH_HOME: '~/env-dsh' })).toBe(envHome)
|
||||
expect(resolveDshHome('/tmp/explicit-dsh', { DSH_HOME: '~/env-dsh' })).toBe('/tmp/explicit-dsh')
|
||||
expect(resolveDshHome(undefined, { DSH_HOME: '~/env-dsh' })).toBe(envHome)
|
||||
expect(resolveDshHome(undefined, {})).toBe(defaultDshHome())
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
Generated
+11
-17
@@ -314,12 +314,12 @@ importers:
|
||||
'@deepseek-ai/dsh-bash-local':
|
||||
specifier: workspace:^
|
||||
version: link:../bash-local
|
||||
'@deepseek-ai/dsh-home':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/home
|
||||
'@deepseek-ai/dsh-llm':
|
||||
specifier: workspace:^
|
||||
version: link:../../llm/llm
|
||||
'@deepseek-ai/dsh-paths':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/paths
|
||||
'@deepseek-ai/dsh-sandbox':
|
||||
specifier: workspace:^
|
||||
version: link:../../sandbox/sandbox
|
||||
@@ -777,9 +777,6 @@ importers:
|
||||
'@deepseek-ai/dsh-goal-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../goal/goal-session
|
||||
'@deepseek-ai/dsh-home':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/home
|
||||
'@deepseek-ai/dsh-invariants':
|
||||
specifier: workspace:^
|
||||
version: link:../../support/invariants
|
||||
@@ -789,6 +786,9 @@ importers:
|
||||
'@deepseek-ai/dsh-llm-retry':
|
||||
specifier: workspace:^
|
||||
version: link:../../llm/llm-retry
|
||||
'@deepseek-ai/dsh-paths':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/paths
|
||||
'@deepseek-ai/dsh-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../core/session
|
||||
@@ -1604,6 +1604,9 @@ importers:
|
||||
'@deepseek-ai/dsh-brand':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/brand
|
||||
'@deepseek-ai/dsh-paths':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/paths
|
||||
cordis:
|
||||
specifier: ^4.0.0-rc.7
|
||||
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
|
||||
@@ -1690,9 +1693,9 @@ importers:
|
||||
'@deepseek-ai/dsh-fs':
|
||||
specifier: workspace:^
|
||||
version: link:../../fs/fs
|
||||
'@deepseek-ai/dsh-home':
|
||||
'@deepseek-ai/dsh-paths':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/home
|
||||
version: link:../../util/paths
|
||||
'@deepseek-ai/dsh-skill':
|
||||
specifier: workspace:^
|
||||
version: link:../skill
|
||||
@@ -2495,12 +2498,6 @@ importers:
|
||||
specifier: ^4.0.0-rc.7
|
||||
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
|
||||
|
||||
packages/util/home:
|
||||
devDependencies:
|
||||
cordis:
|
||||
specifier: ^4.0.0-rc.6
|
||||
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
|
||||
|
||||
packages/util/paths:
|
||||
devDependencies:
|
||||
cordis:
|
||||
@@ -2854,9 +2851,6 @@ importers:
|
||||
'@deepseek-ai/dsh-goal-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/goal/goal-session
|
||||
'@deepseek-ai/dsh-home':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/util/home
|
||||
'@deepseek-ai/dsh-hook-protocol':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/hooks/hook-protocol
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-goal-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-home": "workspace:^",
|
||||
"@deepseek-ai/dsh-hook-protocol": "workspace:^",
|
||||
"@deepseek-ai/dsh-hooks-claude": "workspace:^",
|
||||
"@deepseek-ai/dsh-hooks-codex": "workspace:^",
|
||||
|
||||
@@ -76,7 +76,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/examples/jsonrpc-demo': { kind: 'indirect', reason: 'Only the externally configured plugin tree contributes model context.' },
|
||||
'packages/ui/permission': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' },
|
||||
'packages/ui/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
|
||||
'packages/util/home': { kind: 'indirect', reason: 'Only dsh-tool-bash exposes the resolved home to model commands.' },
|
||||
'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' },
|
||||
'packages/util/retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' },
|
||||
'packages/web/web': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-web.' },
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
{ "path": "./vendor/hmr" },
|
||||
{ "path": "./vendor/logger-console" },
|
||||
{ "path": "./packages/util/brand" },
|
||||
{ "path": "./packages/util/home" },
|
||||
{ "path": "./packages/util/paths" },
|
||||
{ "path": "./packages/util/timeout" },
|
||||
{ "path": "./packages/util/retention" },
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
{ "path": "./vendor/hmr" },
|
||||
{ "path": "./vendor/logger-console" },
|
||||
{ "path": "./packages/util/brand" },
|
||||
{ "path": "./packages/util/home" },
|
||||
{ "path": "./packages/util/paths" },
|
||||
{ "path": "./packages/util/timeout" },
|
||||
{ "path": "./packages/util/retention" },
|
||||
|
||||
Reference in New Issue
Block a user