Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,19 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Set up project tools
uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2

- name: Install and resolve native DSH configuration test APIs
run: |
pnpm --dir integrations/dsh/plugins/powercontext/tests/config-runtime install --frozen-lockfile
boot="$(node --input-type=module -e "import { createRequire } from 'node:module'; import { resolve } from 'node:path'; const require = createRequire(resolve('integrations/dsh/plugins/powercontext/tests/config-runtime/package.json')); process.stdout.write(require.resolve('@deepseek-ai/dsh-app-boot'))")"
test -n "$boot" && test -f "$boot" || { echo "DSH configuration runtime unresolved"; exit 1; }
echo "DSH_TEST_CONFIG_BOOT=$boot" >> "$GITHUB_ENV"

- name: Run unit tests
env:
PYTEST_ADDOPTS: --require-dsh-config-runtime
run: make unit-test

- name: Run end-to-end tests
Expand Down Expand Up @@ -181,6 +193,21 @@ jobs:
- name: Test built plugin in the real DSH runtime
run: make dsh-runtime-test

- name: Install native DSH configuration test APIs
run: pnpm --dir integrations/dsh/plugins/powercontext/tests/config-runtime install --frozen-lockfile

- name: Resolve required DSH test runtimes
run: |
bin="$(node --input-type=module -e "import { dshBin } from './integrations/dsh/plugins/powercontext/tests/runtime/fixture.mjs'; process.stdout.write(dshBin)")"
test -n "$bin" && test -f "$bin" || { echo "DSH test executable unresolved"; exit 1; }
boot="$(node --input-type=module -e "import { createRequire } from 'node:module'; import { resolve } from 'node:path'; const require = createRequire(resolve('integrations/dsh/plugins/powercontext/tests/config-runtime/package.json')); process.stdout.write(require.resolve('@deepseek-ai/dsh-app-boot'))")"
test -n "$boot" && test -f "$boot" || { echo "DSH configuration runtime unresolved"; exit 1; }
echo "DSH_TEST_EXECUTABLE=$bin" >> "$GITHUB_ENV"
echo "DSH_TEST_CONFIG_BOOT=$boot" >> "$GITHUB_ENV"

- name: Verify DSH setup with customized profiles
run: uv run pytest tests/test_dsh_transport.py --require-dsh-runtime

- name: Verify DSH guidance adapter requests and results
run: uv run pytest "tests/test_integration_guidance_evaluation.py::test_native_adapter_handoff_uses_real_request_mapping_and_response_envelopes[dsh]"

Expand Down
13 changes: 13 additions & 0 deletions docs/en/docs/integrations/dsh.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ repeating that command does not update a moving branch. A broken checkout is rep

`setup dsh` calls `dsh plugin --profile web add`; it does not start the Server. Restart DSH after installation.

Existing UI and model patches can remain in place. Setup checks the composed PowerContext connection settings;
see [remote connection configuration](../operate/connect-remote-server.md) for conflicts and dynamic configuration limits.
The check includes children of both `group: true` groups and groups named `@deepseek-ai/cordis-plugin-group`;
multiple PowerContext entries are rejected before installation and during the installed-configuration check.
Native `@deepseek-ai/cordis-plugin-include` and `cordis:include` trees are checked with the selected host's
YAML/JSON parser and include patches. Relative paths start at the profile directory; nested includes resolve
from their containing file's directory. Unrelated UI/model expressions remain unevaluated during setup.
Enabled include files must already exist. Cycles, unsupported file types or URLs, dynamic paths or patch structure,
and unverifiable group children fail before connection settings or credentials are saved.

An unset, empty, or whitespace-only `DSH_HOME` selects `~/.dsh`. A nonblank path keeps its leading and trailing
spaces. These rules apply to configuration inspection, stored credentials, and plugin dependency lookup.

## Start the Server and the host

For automatic Source-to-Memory extraction, generate and validate a Server configuration:
Expand Down
29 changes: 25 additions & 4 deletions docs/en/docs/operate/connect-remote-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,31 @@ Setup configures both the native MCP URL and hook endpoint for Codex, Claude Cod
Codex hooks intentionally use the installed plugin's `.mcp.json`; rerun setup after a plugin update that replaces
that file. Do not change only a hook URL environment variable and assume the native MCP URL changed too.

DSH custom `cordis.patch.yml` layers can override setup-managed settings. Setup accepts the standard empty
profile patch, but stops before installation when custom overlays are present: align the endpoint/consent
manually or remove those overrides before rerunning setup. Doctor reports unsupported native composition
(including unsupported JSON5/includes) as unknown/failed instead of claiming a safe loopback connection.
DSH setup preserves existing UI, model, and other unrelated `cordis.patch.yml` customizations. It uses the
installed DSH's native parser and patch composition to check PowerContext's `baseUrl` and `allowInsecureHttp`,
then checks the actual candidate against the complete bundle stack the native installer will enable, including
installed but inactive dependencies on DSH versions that reactivate them. Existing bundle order and the selected
CLI's activation rules are preserved. Matching settings pass; an override that would
undo the selected endpoint or HTTP consent must be aligned or removed. The check does not rewrite user patches,
start plugins, or evaluate `!!js`. Dynamic PowerContext transport fields, disabled/ambiguous entries, and unreadable
bundles are reported explicitly. PowerContext's own version incompatibility blocks setup. On DSH versions that
skip incompatible third-party bundles, setup warns and excludes their patch layers as the host does; explicit
version exemptions retain those layers and their transport overrides. Unrelated dynamic plugin configuration
does not block setup.

After installation, setup reads the actual enabled configuration again before saving connection settings or
credentials. If the result cannot be verified or disagrees with the selected transport, setup fails and leaves
those settings unsaved; inspect the modified DSH profile before restarting. This readback does not roll back
native package installation. Standalone doctor checks only currently enabled bundles.

Configuration inspection requires Node.js and an npm/pnpm DSH installation exposing the native composition APIs.
The APIs are loaded from that DSH installation's `@deepseek-ai/dsh-app-boot`, which DSH declares as its own
dependency. PowerContext does not install a replacement parser into the plugin. Missing packages or required
APIs produce upgrade/reinstallation guidance before installation effects. Profiles and credentials use the same
`DSH_HOME`; an unset, empty, or whitespace-only value uses `~/.dsh`.
The CLI observes the selected configuration files and its own environment, not a running DSH session's extra
`--patch` arguments or environment; use `/pc doctor` inside that session. Doctor reports unsupported native
composition (including unsupported JSON5/includes in other hosts) as unknown/failed rather than claiming safety.

MiniMax and the generic Agent Plugin delegate MCP transport to the host and have no PowerContext setup subcommand
or independent HTTP client. Their host may have its own restrictions; this flag cannot override host policy.
Expand Down
12 changes: 12 additions & 0 deletions docs/zh/docs/integrations/dsh.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ powercontext setup dsh --source ./powercontext-dsh-dev

`setup dsh` 调用 `dsh plugin --profile web add`,不会启动 Server。安装完成后重启 DSH。

已有界面和模型 patch 可以保留。setup 检查合成后的 PowerContext 连接设置;
冲突处理及动态配置限制见[远程连接配置](../operate/connect-remote-server.md)。
检查会遍历 `group: true` 分组及名称为 `@deepseek-ai/cordis-plugin-group` 的分组内的子项;
安装前检查和安装后复查都会拒绝多个 PowerContext 条目。
原生 `@deepseek-ai/cordis-plugin-include` 和 `cordis:include` 配置树使用所选宿主的 YAML/JSON 解析器及
include patches 检查。相对路径从 profile 目录解析,嵌套 include 从所在文件的目录解析。
setup 不求值无关的界面和模型表达式。启用的 include 文件必须已存在;循环引用、不支持的文件类型或 URL、
动态路径或 patch 结构、无法验证的分组子项,都会在保存连接设置或凭据前报错。

`DSH_HOME` 未设置、为空或仅包含空白时使用 `~/.dsh`;非空路径保留开头和末尾的空格。
配置检查、凭据保存与读取、插件依赖查找都遵循这两条规则。

## 启动 Server 和宿主

需要自动将 Source 提取为 Memory 时,生成并校验 Server 配置:
Expand Down
22 changes: 19 additions & 3 deletions docs/zh/docs/operate/connect-remote-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,25 @@ Codex、Claude Code、WorkBuddy 的 setup 会同时配置原生 MCP 地址与 Ho
Codex Hook 特意读取已安装插件中的 `.mcp.json`,插件升级覆盖该文件后需重新运行 setup。
只修改 Hook 的 URL 环境变量,不能视为宿主原生 MCP 的地址也已修改。

DSH 自定义 `cordis.patch.yml` 可能覆盖 setup 保存的地址与同意状态。标准空白 profile patch 可直接安装;
检测到自定义覆盖层时,setup 会在安装前退出,请先手动对齐地址及同意设置,或移除覆盖后重试。
对无法解析的原生配置组合(包括未支持的 JSON5/include),doctor 会报告未知/失败,不会假定连接是安全的环回地址。
DSH setup 保留已有的界面、模型及其他无关 `cordis.patch.yml` 自定义配置。它使用已安装 DSH 的原生解析和合成接口,
检查 PowerContext 的 `baseUrl` 与 `allowInsecureHttp`,并在安装前用实际待安装 bundle 检查原生安装器最终会启用的
完整 bundle 组合,包括旧版 DSH 会重新启用的已安装但未启用的依赖。检查保留原有 bundle 顺序,遵循所选 CLI 的启用规则。
配置一致时允许安装;会覆盖所选地址或 HTTP 同意状态的设置,需要先对齐或移除。
检查不会改写用户 patch、启动插件或执行 `!!js`。动态 PowerContext 连接字段、被禁用或不唯一的插件条目,
以及无法读取的 bundle 会明确报错。PowerContext 自身不兼容会阻止安装;如果所选 DSH 会跳过不兼容的第三方 bundle,
setup 会告警并同样排除其 patch。用户明确授权的版本豁免仍保留这些 patch,其连接覆盖也会继续检查。
其他插件的动态配置不会因此阻止安装。

安装后,setup 会再次读取实际已启用的配置,验证通过才保存连接设置或凭据。若结果无法验证或与所选连接配置冲突,
setup 会失败并保留原有连接设置;请在重启前检查已改动的 DSH profile。这项读回检查不会回滚原生包安装。
独立 doctor 只检查当前已启用的 bundle。

配置检查需要 Node.js,以及提供原生配置合成接口的 npm/pnpm DSH 安装。
这些接口从所选 DSH 安装的 `@deepseek-ai/dsh-app-boot` 加载,该包由 DSH 自己声明依赖;PowerContext 不会向插件中
安装另一份解析器来替代宿主。包或必需接口缺失时,会在安装前提示升级或重新安装 DSH。
Profile 和凭据使用同一个 `DSH_HOME`;未设置、空值或纯空格均使用 `~/.dsh`。
独立 CLI 只能观察所选配置文件和自身环境,无法观察运行中 DSH 会话额外的 `--patch` 参数或环境;请在该会话内运行 `/pc doctor`。
对无法解析的原生配置组合(包括其他宿主未支持的 JSON5/include),doctor 会报告未知/失败,不会假定连接安全。

MiniMax 和通用 Agent Plugin 由宿主负责 MCP 传输,没有单独的 PowerContext HTTP Client 或 setup 子命令;
本选项不能绕过宿主自己的限制。LangChain、LangGraph、Pydantic AI 及 Bub 评测适配器支持客户端显式同意,
Expand Down
8 changes: 5 additions & 3 deletions integrations/dsh/plugins/powercontext/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2840,13 +2840,14 @@ function contextAssembly(raw, fallback) {
return structuredClone(value);
}
function storedAuthorization(env, baseUrl) {
const path = join(env.DSH_HOME?.trim() || join(homedir(), ".dsh"), "powercontext", "credentials.json");
const configuredHome = env.DSH_HOME;
const path = join(configuredHome?.trim() ? configuredHome : join(homedir(), ".dsh"), "powercontext", "credentials.json");
try {
if (process.platform !== "win32" && (statSync(path).mode & 63) !== 0) return void 0;
const parsed = JSON.parse(readFileSync(path, "utf8"));
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
const payload = parsed;
if (payload.version !== 1 || typeof payload.server_url !== "string" || stripSlash(payload.server_url) !== baseUrl) return void 0;
if (payload.version !== 1 || typeof payload.server_url !== "string" || normalizeServerUrl(payload.server_url, true) !== baseUrl) return void 0;
if (typeof payload.authorization !== "string") return void 0;
const authorization = payload.authorization;
return /^Bearer [^\s]+$/.test(authorization) ? authorization : void 0;
Expand Down Expand Up @@ -2881,7 +2882,8 @@ function resolveConfig(config = {}, env = process.env) {
//#endregion
//#region src/peers.ts
function profileNodeModulesDir(env = process.env) {
return join(env.DSH_HOME?.trim() || join(homedir(), ".dsh"), "profiles", env.DSH_PROFILE?.trim() || "web", "node_modules");
const configuredHome = env.DSH_HOME;
return join(configuredHome?.trim() ? configuredHome : join(homedir(), ".dsh"), "profiles", env.DSH_PROFILE?.trim() || "web", "node_modules");
}
function profileModulesAnchor(env = process.env) {
return join(profileNodeModulesDir(env), "powercontext-dsh-resolver.cjs");
Expand Down
8 changes: 5 additions & 3 deletions integrations/dsh/plugins/powercontext/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { resolveTransport } from './transport.ts'
import { normalizeServerUrl, resolveTransport } from './transport.ts'
import { readFileSync, statSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
Expand Down Expand Up @@ -97,14 +97,16 @@ function contextAssembly(raw: string | undefined, fallback?: Record<string, unkn
}

function storedAuthorization(env: NodeJS.ProcessEnv, baseUrl: string): string | undefined {
const root = env.DSH_HOME?.trim() || join(homedir(), '.dsh')
const configuredHome = env.DSH_HOME
const root = configuredHome?.trim() ? configuredHome : join(homedir(), '.dsh')
const path = join(root, 'powercontext', 'credentials.json')
try {
if (process.platform !== 'win32' && (statSync(path).mode & 0o077) !== 0) return undefined
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined
const payload = parsed as Record<string, unknown>
if (payload.version !== 1 || typeof payload.server_url !== 'string' || stripSlash(payload.server_url) !== baseUrl) return undefined
if (payload.version !== 1 || typeof payload.server_url !== 'string'
|| normalizeServerUrl(payload.server_url, true) !== baseUrl) return undefined
if (typeof payload.authorization !== 'string') return undefined
const authorization = payload.authorization
return /^Bearer [^\s]+$/.test(authorization) ? authorization : undefined
Expand Down
3 changes: 2 additions & 1 deletion integrations/dsh/plugins/powercontext/src/peers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { join } from 'node:path'
import { pathToFileURL } from 'node:url'

export function profileNodeModulesDir(env: NodeJS.ProcessEnv = process.env): string {
const home = env.DSH_HOME?.trim() || join(homedir(), '.dsh')
const configuredHome = env.DSH_HOME
const home = configuredHome?.trim() ? configuredHome : join(homedir(), '.dsh')
const profile = env.DSH_PROFILE?.trim() || 'web'
return join(home, 'profiles', profile, 'node_modules')
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "powercontext-dsh-config-runtime-tests",
"private": true,
"type": "module",
"devDependencies": {
"@deepseek-ai/dsh-app-boot": "0.2.0-rc.2"
}
}
Loading
Loading