Skip to content
Merged
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
1 change: 0 additions & 1 deletion .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ jobs:
tests/test_service.py
tests/test_service_bootstrap.py
tests/test_service_environment.py
tests/test_cli_workbuddy.py::test_setup_workbuddy_remote_checkout_refreshes_the_requested_ref

dify-tools:
needs: changes
Expand Down
2 changes: 1 addition & 1 deletion desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Register an explicitly trusted local installation in `%APPDATA%/com.powercontext
}
```

The native adapter verifies the absolute executable path and pinned digest before running either `service status --json` or `doctor integrations --json`. Registration does not require a version; legacy `version` fields are ignored. Compatibility is determined by the diagnostic response matching the adapter's supported output schema. The observed CLI version is collected for display after a valid diagnostic response; an unavailable version is shown as unknown and does not reject the response. This is a local installation pin, not publisher-signature verification; the Python environment and dependencies must also be trusted. Update the registered digest if the executable changes. No PATH-first CLI selection or renderer-provided commands are accepted.
The native adapter verifies the absolute executable path and pinned digest before running either `service status --json` or `doctor codex claude-code dsh openclaw opencode pi hermes workbuddy kimi-code zcode --json`. Registration does not require a version; legacy `version` fields are ignored. Compatibility is determined by the diagnostic response matching the adapter's supported output schema. The observed CLI version is collected for display after a valid diagnostic response; an unavailable version is shown as unknown and does not reject the response. This is a local installation pin, not publisher-signature verification; the Python environment and dependencies must also be trusted. Update the registered digest if the executable changes. No PATH-first CLI selection or renderer-provided commands are accepted.

The optional version query has a 15-second deadline, service status 20 seconds, and integration diagnostics 60 seconds. Each invocation limits combined stdout/stderr to 256 KiB. Helpers run hidden in an owned Windows Job; completion, timeout and cancellation clean up their descendants. Only allowlisted status fields reach the UI. Valid unhealthy JSON remains useful even with exit code 1. Isolated real-CLI checks pass; installed-application qualification remains open in [validation guide](VALIDATION.md).

Expand Down
2 changes: 1 addition & 1 deletion desktop/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pnpm --dir desktop desktop:dev
}
```

Desktop 在每次诊断前检查固定路径和摘要,然后只允许 `service status --json` 或 `doctor integrations --json`。收到符合格式的诊断响应后,尝试读取实际 CLI 版本用于显示;版本查询失败时显示“未知”,不阻止有效诊断结果。版本查询最长 15 秒,服务检查最长 20 秒,集成检查最长 60 秒;每次进程调用的 stdout/stderr 合计最多 256 KiB。窗口隐藏,进程及其后代归属于本次专属 Windows Job,结束或超时后清理。界面只显示允许的状态字段,不显示任意 detail、路径或原始输出。合法 JSON 配合退出码 1 仍可显示不健康结果。
Desktop 在每次诊断前检查固定路径和摘要,然后只允许 `service status --json` 或 `doctor codex claude-code dsh openclaw opencode pi hermes workbuddy kimi-code zcode --json`。收到符合格式的诊断响应后,尝试读取实际 CLI 版本用于显示;版本查询失败时显示“未知”,不阻止有效诊断结果。版本查询最长 15 秒,服务检查最长 20 秒,集成检查最长 60 秒;每次进程调用的 stdout/stderr 合计最多 256 KiB。窗口隐藏,进程及其后代归属于本次专属 Windows Job,结束或超时后清理。界面只显示允许的状态字段,不显示任意 detail、路径或原始输出。合法 JSON 配合退出码 1 仍可显示不健康结果。

未配置或未通过核验时,本机诊断不可用,独立远程业务仍可使用。进程树清理和隔离真实 CLI 测试已通过;安装后交互与完整 S2 资格仍待验收。

Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/examples/diagnostic_cli_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async fn main() {
.iter()
.any(|v| v.field == "registration" && v.status == "not_installed")
);
assert_eq!(integrations.hosts.len(), 8);
assert_eq!(integrations.hosts.len(), 10);
let mut legacy = registration.clone();
legacy["version"] = "0.0.0".into();
let legacy_path = invalid_directory.path().join("legacy.json");
Expand Down
76 changes: 62 additions & 14 deletions desktop/src-tauri/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,23 @@ pub enum DiagnosticKind {
Integrations,
}
impl DiagnosticKind {
pub fn arguments(self) -> [&'static str; 3] {
pub fn arguments(self) -> &'static [&'static str] {
match self {
Self::Service => ["service", "status", "--json"],
Self::Integrations => ["doctor", "integrations", "--json"],
Self::Service => &["service", "status", "--json"],
Self::Integrations => &[
"doctor",
"codex",
"claude-code",
"dsh",
"openclaw",
"opencode",
"pi",
"hermes",
"workbuddy",
"kimi-code",
"zcode",
"--json",
],
}
}
}
Expand All @@ -46,7 +59,7 @@ pub struct DiagnosticItem {
#[serde(rename_all = "camelCase")]
pub struct HostDiagnostic {
pub host: String,
pub presence: String,
pub status: String,
pub checks: Vec<DiagnosticItem>,
}
#[derive(Clone, Debug, PartialEq, Serialize, TS)]
Expand Down Expand Up @@ -116,18 +129,16 @@ pub fn project(
}
}
DiagnosticKind::Integrations => {
let status = selected(&value["status"], &["ok", "failed"])?;
if value["ok"].as_bool() != Some(status == "ok") {
return Err(SafeError::InvalidResponse);
}
let ok = value["ok"].as_bool().ok_or(SafeError::InvalidResponse)?;
let status = if ok { "ok" } else { "failed" }.to_owned();
report.items.push(DiagnosticItem {
field: "integrations".into(),
status,
});
let hosts = value["hosts"]
.as_object()
.ok_or(SafeError::InvalidResponse)?;
if hosts.len() > 8 {
if hosts.len() > 10 {
return Err(SafeError::InvalidResponse);
}
for (name, host) in hosts {
Expand All @@ -140,21 +151,30 @@ pub fn project(
"pi",
"hermes",
"workbuddy",
"kimi-code",
"zcode",
]
.contains(&name.as_str())
{
return Err(SafeError::InvalidResponse);
}
let mut row = HostDiagnostic {
host: name.clone(),
presence: selected(&host["presence"], &["present", "missing"])?,
status: selected(&host["status"], &["ok", "degraded", "failed"])?,
checks: vec![],
};
let fields = host.as_object().ok_or(SafeError::InvalidResponse)?;
for (field, check) in fields {
if field == "presence" {
if host["ok"].as_bool() != Some(row.status == "ok") {
return Err(SafeError::InvalidResponse);
}
let fields = match host.get("checks") {
Some(checks) => checks.as_object().ok_or(SafeError::InvalidResponse)?,
None if row.status == "failed" => {
report.hosts.push(row);
continue;
}
None => return Err(SafeError::InvalidResponse),
};
for (field, check) in fields {
if ![
"codex",
"claude_code",
Expand All @@ -170,6 +190,34 @@ pub fn project(
"settings",
"mcp",
"transport",
"authorization",
"mcp_configuration",
"mcp_tools",
"mcp_full_profile",
"kimi_code",
"node",
"hook",
"configuration",
"probe",
"credentials",
"scope",
"server",
"runtime",
"verification",
"mcp_session",
"protected_api",
"scope_probe",
"prepare_probe",
"zcode",
"provider",
"command_enabled",
"command_plugin",
"service_support",
"service_registration",
"service_definition",
"service_manager",
"server_liveness",
"server_readiness",
]
.contains(&field.as_str())
{
Expand Down Expand Up @@ -315,7 +363,7 @@ fn execute(
};
let result = crate::diagnostic_process::run(
&config.executable,
&kind.arguments(),
kind.arguments(),
Duration::from_secs(timeout),
cancelled.clone(),
)?;
Expand Down
8 changes: 5 additions & 3 deletions desktop/src-tauri/tests/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ fn unhealthy_service_json_survives_exit_one_without_leaking_private_details() {
}
#[test]
fn integration_status_is_projected_without_details_or_checks_text() {
let result = project(DiagnosticKind::Integrations, br#"{"ok":false,"status":"failed","hosts":{"codex":{"presence":"present","codex":{"ok":true,"status":"ok","detail":"private-path"},"mcp":{"ok":false,"status":"failed","detail":"secret","checks":{"token":"secret"}}}}}"#, 1).unwrap();
let result = project(DiagnosticKind::Integrations, br#"{"ok":false,"hosts":{"codex":{"status":"failed","ok":false,"checks":{"codex":{"ok":true,"status":"ok","detail":"private-path"},"mcp":{"ok":false,"status":"failed","detail":"secret","checks":{"token":"secret"}}}},"kimi-code":{"status":"failed","ok":false,"error":"private-path secret-token"}}}"#, 1).unwrap();
assert_eq!(result.hosts[0].host, "codex");
assert_eq!(result.hosts[0].checks.len(), 2);
assert_eq!(result.hosts[1].status, "failed");
assert!(result.hosts[1].checks.is_empty());
let output = serde_json::to_string(&result).unwrap();
assert!(!output.contains("private"));
assert!(!output.contains("secret"));
Expand All @@ -50,8 +52,8 @@ fn malformed_unknown_and_oversized_diagnostics_are_rejected() {
for output in [
b"not json".as_slice(),
br#"{}"#,
br#"{"ok":true,"status":"secret","hosts":{}}"#,
br#"{"ok":false,"status":"ok","hosts":{}}"#,
br#"{"ok":true,"hosts":{"codex":{"status":"secret","ok":true,"checks":{}}}}"#,
br#"{"ok":false,"hosts":{"codex":{"status":"ok","ok":false,"checks":{}}}}"#,
] {
assert_eq!(
project(DiagnosticKind::Integrations, output, 1).unwrap_err(),
Expand Down
4 changes: 1 addition & 3 deletions desktop/ui/src/app/Diagnostics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ const labels: Record<string, [string, string]> = {
ok: ["通过", "Passed"],
degraded: ["部分可用", "Degraded"],
skipped: ["未检查", "Skipped"],
present: ["已发现", "Present"],
missing: ["未发现", "Missing"],
plugin: ["插件", "Plugin"],
package: ["扩展包", "Package"],
skill: ["技能", "Skill"],
Expand Down Expand Up @@ -149,7 +147,7 @@ export function Diagnostics({ language }: { language: Language }) {
{report.hosts.map((host) => (
<section key={host.host}>
<h4>
{host.host} · {label(host.presence)}
{host.host} · {label(host.status)}
</h4>
<dl>
{host.checks.map((item) => (
Expand Down
2 changes: 1 addition & 1 deletion desktop/ui/src/generated/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type CredentialWriteRequest = { secret: string, storage: StorageChoice, }
export type CredentialWriteReceipt = { storage: StorageChoice, };
export type DiagnosticKind = "service" | "integrations";
export type DiagnosticItem = { field: string, status: string, };
export type HostDiagnostic = { host: string, presence: string, checks: Array<DiagnosticItem>, };
export type HostDiagnostic = { host: string, status: string, checks: Array<DiagnosticItem>, };
export type DiagnosticReport = { checkedAt: number, exitCode: number, cliVersion?: string, items: Array<DiagnosticItem>, hosts: Array<HostDiagnostic>, };
export type Authentication = "unauthenticated_loopback" | "bearer";
export type ProfileView = { id: string, revision: number, name: string, endpoint: string, authentication: Authentication, caPem: string | null, compatibility: string | null, credentialState: CredentialState, };
Expand Down
23 changes: 11 additions & 12 deletions docs/en/docs/develop/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ boundary flushing fail open; explicit durable writes require interactive confirm
Set `POWERCONTEXT_SCOPE_ID` to an existing ID returned by `create_scope` before running scoped content commands.

```text
powercontext setup <host>
powercontext setup select --host codex --host dsh
powercontext setup [agents...]
powercontext setup codex dsh
powercontext config agent codex dsh --server-url https://memory.example
powercontext config init --output .env
powercontext config show --env-file .env
powercontext config validate --env-file .env
powercontext doctor
powercontext doctor <host>
powercontext doctor integrations
powercontext doctor codex dsh
powercontext server run
powercontext server run --env-file .env
powercontext ready
Expand All @@ -165,15 +165,14 @@ The `config` group creates, redacts, and validates an explicit environment file.
current directory; pass `--env-file` to select another file or `--no-env-file` to disable discovery. See
[Configuration](../operate/configuration.md) for precedence and credential-handling rules.

Use `codex`, `claude-code`, `dsh`, `hermes`, `openclaw`, `opencode`, `pi`, or `workbuddy` for `<host>`.
The first-class catalog used by `setup select` and `doctor integrations` contains every listed host except WorkBuddy.
WorkBuddy remains available through the explicit `setup workbuddy` and `doctor workbuddy` commands.
Use `codex`, `claude-code`, `dsh`, `hermes`, `kimi-code`, `openclaw`, `opencode`, `pi`, `workbuddy`, or `zcode` for agents.
`powercontext setup` offers interactive selection; explicit agent names support non-interactive and multi-agent setup.
Setup installs, configures, activates where supported, and runs doctor. Failures remain isolated per agent; completed installation is reported even when diagnostics fail.

`powercontext doctor` checks the package and Server without requiring an integration. `powercontext doctor integrations`
prints a read-only matrix for every first-class host; a missing CLI is `missing` and does not fail the command.
Each `powercontext doctor <host>` command still fails when that host CLI is missing. The matrix preserves every
host-specific integration check, including OpenCode's separate `plugin` and `skill` results. DSH checks that
`dump-config` lists `powercontext-dsh`; Pi checks that the CLI lists the PowerContext package.
`powercontext config agent [agents...]` updates installed connections without reinstalling or enabling plugins.
`powercontext doctor [agents...]` checks the selected agents, or saved connections when none are selected.
With no saved connections, doctor checks the package and Server. Selected agents with a missing required CLI fail their own checks.
Doctor preserves host-specific checks, including OpenCode's separate `plugin` and `skill` results. DSH checks its native registration; Pi checks its registered package.

The unified HTTP review routes are `/v1/candidates/list`, `get`, `history`, `revise`, `approve`, and `reject` (all POST).
`candidate_kind=artifact|tag` distinguishes proposals. Old Artifact Candidate routes are removed; callers must upgrade together.
Expand Down
15 changes: 7 additions & 8 deletions docs/en/docs/integrations/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@ set +a
powercontext setup claude-code --server-url "$POWERCONTEXT_CLAUDE_SERVER_URL"
```

Before changing Claude Code settings, setup reports the settings entry, plugin cache, persistent data location,
required permissions, and exact rollback commands. It then registers or refreshes the Marketplace, updates or installs
the plugin at user scope, and verifies the enabled plugin through Claude Code's JSON output.

Claude Code owns the Marketplace registry, versioned plugin cache, and plugin data directory. Claude 2.1.133 does not
accept configuration flags on `plugin install`, so setup atomically merges `server_url` and `capture_prompts` into
the user-level `pluginConfigs` after installation while preserving unrelated settings; on failure it restores the
pre-install snapshot. PowerContext resolves these locations from `CLAUDE_CONFIG_DIR` or Claude Code's default
Setup registers or refreshes the Marketplace, then installs or updates the plugin at user scope. Updates preserve
explicit plugin disablement. The result reports the installed version, plugin cache, and persistent data location.

Claude Code owns the Marketplace registry, versioned plugin cache, and plugin data directory. After installation,
setup merges `server_url` and `capture_prompts` into the user-level `pluginConfigs`, preserving unrelated settings
and omitted capture preferences. If connection configuration fails, its local changes are restored; the completed
native installation remains. PowerContext resolves these locations from `CLAUDE_CONFIG_DIR` or Claude Code's default
configuration directory.

For a local checkout, pass its directory:
Expand Down
4 changes: 2 additions & 2 deletions docs/en/docs/integrations/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Prompt capture is enabled by default; see [Control prompt capture](#control-prom
`mcp_full_profile`. Full coverage requires every capability-bearing tool in the Codex integration's `server-mcp`
manifest, including Memory maintenance, Scope management and Handoff operations. Missing tools produce a degraded
result and are listed by name; discovery does not prove model readiness, Hook execution or write authorization.
Both `setup codex` and `setup select --host codex` can succeed with basic Memory support while `doctor` reports
incomplete full coverage. Authentication and basic connection failures still fail installation.
Setup reports installation completion separately from diagnostics. Basic Memory support can remain usable while `doctor` reports
incomplete full coverage. Authentication and basic connection failures still produce a failed result.

## Understand automatic recall, Memory, and Handoff

Expand Down
10 changes: 6 additions & 4 deletions docs/en/docs/integrations/hermes.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ powercontext setup hermes
powercontext doctor hermes
```

The setup command copies the provider to `$HERMES_HOME/plugins/powercontext`, installs
`$HERMES_HOME/plugins/powercontext-command`, and enables the companion without granting built-in tool override
permissions. It does not start the Server or select the provider in Hermes.
The setup command copies the provider to `$HERMES_HOME/plugins/powercontext` and installs
`$HERMES_HOME/plugins/powercontext-command`. It enables the companion unless explicitly disabled, without granting
built-in tool override permissions. If `memory.provider` is absent, setup selects PowerContext automatically;
an existing provider choice, including an explicitly empty value, is preserved. Setup does not start the Server.

Run the Hermes memory setup wizard and select `PowerContext`:
To switch from an existing provider or change provider settings, run the Hermes memory setup wizard and select
`PowerContext`:

```bash
hermes memory setup
Expand Down
Loading
Loading