fix(CW-7087): retain shell inspection and align local Skills setup [skip ci]

Completed and stopped shell cards keep their job inspector. Stopped jobs use a
neutral stopped status rather than failure; completion summaries distinguish
stopped and failed counts. Terminal jobs without an exit code say unavailable.
Delegated shell ownership uses the assigned agent display name.

Local Skills setup creates .codewhale/skills, the owned project root discovered
after workspace trust. Both doctor surfaces reuse the runtime root resolver;
setup/help and English/Chinese guides describe the same trust and root policy.
Preserve flat tools/skills directories and existing customized templates.

Update 0.10.2 changelog and regenerate its embedded slice. Reconcile the release
checklist with the founder's final-step-only CI/full-validation policy.

Validation:
- Fresh CLI build passes; 2462 frozen native inputs unchanged after acceptance.
- Actual isolated TUI: completed card Alt+V opens retained inspector (exit0);
  selected Stop yields stopped card and killed inspector with unavailable exit,
  no Stop action, while the other job remains running. Named child Owner is
  shell-child; 72x30 row activation opens that exact job.
- Actual offline Skills setup/doctor: 5/5 commands exit0. Owned project root is
  created, repeat preserves edits, global root before trust/project root after
  trust, plain/JSON agree, competing compatible root and sentinels preserved.
  OS sandbox denies network and writes outside the isolated fixture.
- Scoped formatting, diff check and changelog regeneration pass.

Binary SHA256 a3c8ee6ba2db6e20b4aab451c6e3391e400ad40a751a4d896c2d2adc298ce06a.
Receipts under CW/artifacts/engine-terminal-finish-20261020,
engine-0102-shell-display-20261020/run1, engine-skills-setup-20261020/run1.

Earlier monolithic test compile was interrupted before tests; it is not passing
unit-test evidence. Initial probe mock exited with its launcher; restarting the
same loopback fixture in the private terminal fixed transport, not product code.
No live provider, Windows package, final hosted gate, tag, publish or deploy.
Remaining broad shell interactions and release gates retain explicit limits.
This commit is contained in:
CodeWhale Bot committed 2026-10-20 23:36:27 -07:00
1 parent be92b4c1d2
commit 79debb5fd9
16 files changed
+121 -88

No files matched your search

+8
View File
@@ -26,6 +26,14 @@ guidance.
### Latest fixes
- Local Skills setup creates its example in the project folder Codewhale
discovers, and its setup and doctor output explain the workspace trust
requirement. Skills help now points to the searchable Skills view and
distinguishes it from management.
- Stopped shell jobs read as stopped instead of failed. Their detail view stays
available after completion, a missing terminal exit code reads as unavailable,
and delegated jobs identify their named agent. Refs #6931.
- `model list` respects the selected route's catalog instead of adding unrelated
bundled models. `app/models` reads the captured Runtime's current catalog,
including named custom routes, regional routes, limits and capability facts.
+8
View File
@@ -26,6 +26,14 @@ guidance.
### Latest fixes
- Local Skills setup creates its example in the project folder Codewhale
discovers, and its setup and doctor output explain the workspace trust
requirement. Skills help now points to the searchable Skills view and
distinguishes it from management.
- Stopped shell jobs read as stopped instead of failed. Their detail view stays
available after completion, a missing terminal exit code reads as unavailable,
and delegated jobs identify their named agent. Refs #6931.
- `model list` respects the selected route's catalog instead of adding unrelated
bundled models. `app/models` reads the captured Runtime's current catalog,
including named custom routes, regional routes, limits and capability facts.
+17 -5
View File
@@ -5619,7 +5619,17 @@ pub(super) fn shell_completion_status_text(
let count = events.len();
let failed = events
.iter()
.filter(|event| event.status != crate::tools::shell::ShellStatus::Completed)
.filter(|event| {
matches!(
event.status,
crate::tools::shell::ShellStatus::Failed
| crate::tools::shell::ShellStatus::TimedOut
)
})
.count();
let stopped = events
.iter()
.filter(|event| event.status == crate::tools::shell::ShellStatus::Killed)
.count();
let noun = if count == 1 { "job" } else { "jobs" };
let prefix = if timing.trim().is_empty() {
@@ -5627,11 +5637,13 @@ pub(super) fn shell_completion_status_text(
} else {
format!("{} ", timing.trim())
};
let mut status = if failed == 0 {
format!("{prefix}{count} background shell {noun} completed")
} else {
format!("{prefix}{count} background shell {noun} finished ({failed} failed)")
let outcome = match (failed, stopped) {
(0, 0) => "completed".to_string(),
(0, stopped) => format!("finished ({stopped} stopped)"),
(failed, 0) => format!("finished ({failed} failed)"),
(failed, stopped) => format!("finished ({failed} failed, {stopped} stopped)"),
};
let mut status = format!("{prefix}{count} background shell {noun} {outcome}");
if count == 1
&& let Some(event) = events.first()
+10 -29
View File
@@ -1231,7 +1231,7 @@ struct SetupArgs {
/// Initialize MCP config, skills, tools, and plugins
#[arg(long, default_value_t = false)]
all: bool,
/// Create a local workspace skills directory (./skills)
/// Create a local workspace skills directory (./.codewhale/skills)
#[arg(long, default_value_t = false)]
local: bool,
/// Overwrite existing template files
@@ -4345,7 +4345,7 @@ fn run_setup(
if run_skills {
let skills_dir = if args.local {
workspace.join("skills")
workspace.join(".codewhale").join("skills")
} else {
config.skills_dir()
};
@@ -4366,7 +4366,7 @@ fn run_setup(
}
if args.local {
println!(
" Local skills dir enabled for this workspace: {}",
" Project skills dir (loads after workspace trust): {}",
crate::utils::display_path(&skills_dir)
);
} else {
@@ -5595,18 +5595,8 @@ async fn run_doctor(
// to the merged catalogue.
let opencode_skills_dir = workspace.join(".opencode").join("skills");
let claude_skills_dir = workspace.join(".claude").join("skills");
let selected_skills_dir = if agents_skills_dir.exists() {
agents_skills_dir.clone()
} else if local_skills_dir.exists() {
local_skills_dir.clone()
} else if config.skills_dir.is_none()
&& let Some(global_agents) = agents_global_skills_dir.as_ref()
&& global_agents.exists()
{
global_agents.clone()
} else {
global_skills_dir.clone()
};
let selected_skills_dir =
crate::tui::app::resolve_skills_dir(workspace, &global_skills_dir, config);
let describe_dir = |dir: &Path| -> usize {
std::fs::read_dir(dir)
@@ -5711,14 +5701,15 @@ async fn run_doctor(
unrecognized_system_skills.join(", ")
);
}
if !agents_skills_dir.exists()
if !workspace.join(".codewhale").join("skills").is_dir()
&& !agents_skills_dir.exists()
&& !local_skills_dir.exists()
&& !agents_global_skills_dir
.as_ref()
.is_some_and(|dir| dir.exists())
&& !global_skills_dir.exists()
{
println!(" Run `codewhale setup --skills` (or add --local for ./skills).");
println!(" Run `codewhale setup --skills` (or add --local for ./.codewhale/skills).");
}
// Tools directory
@@ -8151,18 +8142,8 @@ fn run_doctor_json(
// global agentskills.io content is contributing to the merged catalogue.
let opencode_skills_dir = workspace.join(".opencode").join("skills");
let claude_skills_dir = workspace.join(".claude").join("skills");
let selected_skills_dir = if agents_skills_dir.exists() {
agents_skills_dir.clone()
} else if local_skills_dir.exists() {
local_skills_dir.clone()
} else if config.skills_dir.is_none()
&& let Some(global_agents) = agents_global_skills_dir.as_ref()
&& global_agents.exists()
{
global_agents.clone()
} else {
global_skills_dir.clone()
};
let selected_skills_dir =
crate::tui::app::resolve_skills_dir(workspace, &global_skills_dir, config);
let agents_global_summary = agents_global_skills_dir
.as_ref()
.map(|path| {
+13 -4
View File
@@ -1497,14 +1497,23 @@ pub(super) async fn run_child_agent(
} else {
prepare_child_membership(runtime, &assignment, &agent_id).await?
};
let owner_agent_name = runtime
.manager
.read()
.await
.get_result(&agent_id)
.map(|agent| subagent_result_display_name(&agent))
.unwrap_or_else(|_| {
assignment
.role
.clone()
.unwrap_or_else(|| agent_type.as_str().to_owned())
});
let authority = ChildAuthority::capture(
runtime.clone(),
agent_type.clone(),
agent_id.clone(),
assignment
.role
.clone()
.unwrap_or_else(|| agent_type.as_str().to_owned()),
owner_agent_name,
allowed_tools.clone(),
);
let system =
+21 -11
View File
@@ -1043,6 +1043,7 @@ pub enum ToolStatus {
Running,
Success,
Hydrated,
Stopped,
/// Terminal result with usable output that still needs attention.
Warning,
Failed,
@@ -1514,6 +1515,8 @@ impl ExploringCell {
ToolStatus::Failed
} else if any(ToolStatus::Warning) {
ToolStatus::Warning
} else if any(ToolStatus::Stopped) {
ToolStatus::Stopped
} else if any(ToolStatus::Hydrated) {
ToolStatus::Hydrated
} else {
@@ -1585,15 +1588,15 @@ impl ExploringCell {
// Dot-grid status strip — one glyph per entry, showing parallel
// fanout at a glance: ●=done ◐=running ✕=failed.
if self.entries.len() > 1 {
let (done, running, failed) =
self.entries
.iter()
.fold((0usize, 0usize, 0usize), |(d, r, f), e| match e.status {
ToolStatus::Success | ToolStatus::Hydrated => (d + 1, r, f),
ToolStatus::Warning => (d, r, f + 1),
ToolStatus::Running => (d, r + 1, f),
ToolStatus::Failed => (d, r, f + 1),
});
let (done, running, failed, stopped) = self.entries.iter().fold(
(0usize, 0usize, 0usize, 0usize),
|(d, r, f, s), e| match e.status {
ToolStatus::Success | ToolStatus::Hydrated => (d + 1, r, f, s),
ToolStatus::Warning | ToolStatus::Failed => (d, r, f + 1, s),
ToolStatus::Running => (d, r + 1, f, s),
ToolStatus::Stopped => (d, r, f, s + 1),
},
);
// Each dot carries its own entry's state. Painting the strip one
// flat colour made a failed parallel read indistinguishable from a
// finished one, which is exactly the narration the cell is
@@ -1603,6 +1606,7 @@ impl ExploringCell {
let glyph = match e.status {
ToolStatus::Success | ToolStatus::Hydrated => "\u{25CF}",
ToolStatus::Warning => "!",
ToolStatus::Stopped => crate::tui::glyphs::NEUTRAL,
ToolStatus::Running => "\u{25D0}",
ToolStatus::Failed => "\u{2715}",
};
@@ -1621,6 +1625,10 @@ impl ExploringCell {
failed,
codewhale_localization::MessageId::WorkflowCountFailed,
),
(
stopped,
codewhale_localization::MessageId::WorkflowCountCancelled,
),
]
.into_iter()
.filter(|(count, _)| *count > 0)
@@ -1649,6 +1657,7 @@ impl ExploringCell {
ToolStatus::Running => "live",
ToolStatus::Success => "done",
ToolStatus::Hydrated => "loaded",
ToolStatus::Stopped => "stopped",
ToolStatus::Warning => "issue",
ToolStatus::Failed => "failed",
};
@@ -2907,7 +2916,7 @@ fn status_symbol(
crate::tui::spinner::braille_spinner_frame(started_at, low_motion).to_string()
}
ToolStatus::Success => TOOL_DONE_SYMBOL.to_string(),
ToolStatus::Hydrated => crate::tui::glyphs::NEUTRAL.to_string(),
ToolStatus::Hydrated | ToolStatus::Stopped => crate::tui::glyphs::NEUTRAL.to_string(),
ToolStatus::Warning => "!".to_string(),
ToolStatus::Failed => TOOL_FAILED_SYMBOL.to_string(),
}
@@ -3336,7 +3345,7 @@ fn tool_detail_label_style() -> Style {
fn tool_rail_color(status: ToolStatus) -> Color {
match status {
ToolStatus::Running => palette::WHALE_ACTION,
ToolStatus::Success => palette::TEXT_MUTED,
ToolStatus::Success | ToolStatus::Stopped => palette::TEXT_MUTED,
ToolStatus::Hydrated => palette::TEXT_DIM,
ToolStatus::Warning => palette::WHALE_HUMAN,
ToolStatus::Failed => palette::WHALE_ERROR,
@@ -3356,6 +3365,7 @@ pub(crate) fn tool_status_label(status: ToolStatus) -> &'static str {
ToolStatus::Running => "running",
ToolStatus::Success => "done",
ToolStatus::Hydrated => "loaded, not run",
ToolStatus::Stopped => "stopped",
ToolStatus::Warning => "issue",
ToolStatus::Failed => "failed",
}
+9 -5
View File
@@ -48,7 +48,7 @@ pub(super) fn format_shell_job_list(jobs: &[ShellJobSnapshot]) -> String {
job.id,
status_label(&job.status, job.stale),
codewhale_command_contract::elapsed::format_elapsed_ms(job.elapsed_ms),
exit_label(job.exit_code),
exit_label(job.exit_code, &job.status),
task
));
lines.push(format!(" cwd: {}", crate::utils::display_path(&job.cwd)));
@@ -71,11 +71,12 @@ pub(super) fn format_shell_job_list(jobs: &[ShellJobSnapshot]) -> String {
lines.join("\n")
}
fn exit_label(code: Option<i64>) -> String {
fn exit_label(code: Option<i64>, status: &ShellStatus) -> String {
match code {
Some(0) => "exit 0".to_string(),
Some(code) => format!("exit {code}"),
None => "no exit yet".to_string(),
None if *status == ShellStatus::Running => "no exit yet".to_string(),
None => "unavailable".to_string(),
}
}
@@ -85,7 +86,7 @@ pub(super) fn format_shell_poll(result: &ShellResult) -> String {
"Bash job {}: {} {} elapsed={}",
result.task_id.as_deref().unwrap_or("(unknown)"),
status_label(&result.status, false),
exit_label(result.exit_code),
exit_label(result.exit_code, &result.status),
codewhale_command_contract::elapsed::format_elapsed_ms(result.duration_ms)
),
String::new(),
@@ -251,7 +252,10 @@ pub(crate) fn format_shell_job_detail(detail: &ShellJobDetail) -> String {
"Elapsed: {}",
codewhale_command_contract::elapsed::format_elapsed_ms(job.elapsed_ms)
));
lines.push(format!("Exit Code: {}", exit_label(job.exit_code)));
lines.push(format!(
"Exit Code: {}",
exit_label(job.exit_code, &job.status)
));
lines.push(format!("Stdin Available: {}", job.stdin_available));
if let Some(task_id) = job.linked_task_id.as_ref() {
lines.push(format!("Linked Task: {task_id}"));
+8 -9
View File
@@ -1622,15 +1622,14 @@ fn push_orphan_tool_completion(
fn tool_status_from_result(result: &Result<ToolResult, ToolError>) -> ToolStatus {
match result.as_ref() {
Ok(tool_result) if is_deferred_schema_hydration(tool_result) => ToolStatus::Hydrated,
Ok(tool_result) => match tool_result.metadata.as_ref() {
Some(meta)
if meta
.get("status")
.and_then(|v| v.as_str())
.is_some_and(|s| s == "Running") =>
{
ToolStatus::Running
}
Ok(tool_result) => match tool_result
.metadata
.as_ref()
.and_then(|meta| meta.get("status"))
.and_then(serde_json::Value::as_str)
{
Some("Running") => ToolStatus::Running,
Some("Killed") => ToolStatus::Stopped,
_ => {
if tool_result.success {
ToolStatus::Success
+1 -7
View File
@@ -380,13 +380,7 @@ fn canonical_owned_file(
}
pub(crate) fn open_details_pager_for_cell(app: &mut App, cell_index: usize) -> bool {
if app.cell_at_virtual_index(cell_index).is_some_and(|cell| {
matches!(
cell,
HistoryCell::Tool(ToolCell::Exec(exec)) if exec.status == ToolStatus::Running
)
}) && let Some(detail) = crate::tui::shell_job_routing::shell_job_for_cell(app, cell_index)
{
if let Some(detail) = crate::tui::shell_job_routing::shell_job_for_cell(app, cell_index) {
crate::tui::shell_job_routing::open_shell_job_pager(app, &detail);
return true;
}
+2 -1
View File
@@ -665,7 +665,8 @@ pub(super) fn shell_job_tool_status(status: &ShellStatus) -> ToolStatus {
match status {
ShellStatus::Running => ToolStatus::Running,
ShellStatus::Completed => ToolStatus::Success,
ShellStatus::Failed | ShellStatus::Killed | ShellStatus::TimedOut => ToolStatus::Failed,
ShellStatus::Killed => ToolStatus::Stopped,
ShellStatus::Failed | ShellStatus::TimedOut => ToolStatus::Failed,
}
}
+10 -8
View File
@@ -2514,17 +2514,19 @@ reasoning contract, and all four membership ids omit generic sampling fields.
`model` fields (including user-named custom providers such as `lm-studio`)
and call `agent(profile: "...")`; see [SUBAGENTS.md](SUBAGENTS.md).
- `skills_dir` (string, optional): defaults to `~/.codewhale/skills` (each skill is
a directory containing `SKILL.md`). Workspace-local `.agents/skills` or
`./skills` are preferred when present; the runtime also discovers global
agentskills.io-compatible `~/.agents/skills` and the broader Claude-ecosystem
a directory containing `SKILL.md`). After workspace trust, project skills
take precedence, with `.codewhale/skills` ahead of compatible roots such as
`.agents/skills`. Flat `./skills` requires explicit opt-in below. The runtime
also discovers global agentskills.io-compatible `~/.agents/skills` and the
broader Claude-ecosystem
`~/.claude/skills`. First launch installs versioned bundled skills for common
workflows including skill creation, delegation, MCP/plugin scaffolding,
documents, presentations, spreadsheets, PDFs, and Feishu/Lark. Only
Codewhale-owned roots (`<workspace>/.codewhale/skills` and
`~/.codewhale/skills`) are writable install/import targets; compatible harness
roots stay read-only. Bare `/skills` opens the Skills Manager (owned-only,
zero network). See [SKILLS.md](SKILLS.md) for the manager, audit statuses,
provenance markers, and mutation rules, and
roots stay read-only. Bare `/skills` opens Extensions → Skills (owned-only,
zero network); `/skills manage` opens the manager. See [SKILLS.md](SKILLS.md)
for the manager, audit statuses, provenance markers, and mutation rules, and
[CLAUDE_PLUGIN_COMPAT.md](CLAUDE_PLUGIN_COMPAT.md) for the supported boundary
between portable `SKILL.md` bundles and Claude Code plugin runtimes.
- `[skills].scan_codewhale_only` (bool, default `false`): when `true`, session
@@ -3459,8 +3461,8 @@ TUI. That means `--config`, `CODEWHALE_CONFIG_PATH`, and the legacy
checks use the resolved `mcp_config_path` / `skills_dir` (including env overrides).
To bootstrap missing MCP/skills paths, run `codewhale setup --all`. You can
also run `codewhale setup --skills --local` to create a workspace-local
`./skills` dir.
also run `codewhale setup --skills --local` to create the owned project
`.codewhale/skills` directory. Project skills load after workspace trust.
Both plain `codewhale doctor` and `codewhale doctor --json` are structural and
offline by default. They do not check the release service, hosted provider
+2 -1
View File
@@ -616,7 +616,8 @@ Use skills when a task has a repeatable process:
- Using a project-specific memory or wiki workflow.
Inside the TUI, `/skill <name>` activates a skill when one is available, and
bare `/skills` opens the Skills Manager (owned-only inventory, no network). Use
bare `/skills` opens Extensions → Skills (owned-only inventory, no network).
Use `/skills manage` for the manager and
`/skills <prefix>`, `/skills inspect`, `/skills --remote`, `/skills suggest <task>`,
or `/skills sync` for the text/registry paths. Suggestions rank the remote
catalog but never install or activate anything. The command palette can also
+8 -4
View File
@@ -90,10 +90,14 @@ known limitations and publication approval. These proofs are separate.
## 3. Verify and land preparation slices
- [ ] Run the existing checks appropriate to each changed surface once; batch
coherent Rust edits before compiling. Web changes use
`npm test && npm run check:web` from the repository root. Record actual
results and failures in commit messages; hosted CI owns exhaustive
workspace and platform coverage.
coherent Rust edits before compiling. During implementation, use the
smallest local check or running-product observation that proves the
change. After implementation and product review, freeze the candidate
commit and run final validation once, including
`npm test && npm run check:web` from the repository root. Hosted CI owns
exhaustive workspace and platform coverage at that final step. Rerun
only checks invalidated by a fix. Record actual results and failures in
commit messages.
- [ ] Commit verified owned paths directly to `main` and push normally. Resolve
push rejection with the other writer while preserving dirty work; never
force-push. Reuse valid receipts instead of rebuilding merely to commit
+2 -2
View File
@@ -1307,7 +1307,7 @@ DeepSeek V4 前缀缓存让 token 标签变得重要。这些数量保持分离
`/config subagents status` 打印全局值和活动 provider 的解析 profile,这样速率限制调整在 TUI 中可见。`[subagents.models]` 接受小写的 Fleet 角色键,如 `worker`、`scout`、`planner`、`reviewer`、`builder` 和 `verifier`;旧类型键在 v0.9.x 期间仍被接受。值在派生时对照活动 provider 验证;直接 DeepSeek 需要 DeepSeek ID,而 OpenAI 兼容/自定义 provider 路由把显式模型 ID 透传给该 provider。要把子智能体路由到不同于父会话的 provider,保存带显式 `provider` 和 `model` 字段的 Fleet/AgentProfile(包括 `lm-studio` 这样的用户命名自定义 provider),并调用 `agent(profile: "...")`;见 [SUBAGENTS.md](SUBAGENTS.md)。
- `skills_dir`(字符串,可选):默认 `~/.codewhale/skills`(每个技能是包含 `SKILL.md` 的目录)。存在时优先使用工作区本地的 `.agents/skills` 或 `./skills`;运行时还发现全局 agentskills.io 兼容的 `~/.agents/skills` 和更广的 Claude 生态系统 `~/.claude/skills`。首次启动为常见工作流安装带版本的捆绑技能,包括技能创建、委派、MCP/插件脚手架、文档、演示文稿、电子表格、PDF 和飞书/Lark。只有 CodeWhale 自有的根(`<workspace>/.codewhale/skills` 和 `~/.codewhale/skills`)是可写安装/导入目标;兼容 harness 根保持只读。裸 `/skills` 打开技能管理器(仅自有,零网络)。管理器、审计状态、来源标记和变更规则见 [SKILLS.md](SKILLS.md),可移植 `SKILL.md` 包与 Claude Code 插件运行时之间的受支持边界见 [CLAUDE_PLUGIN_COMPAT.md](../CLAUDE_PLUGIN_COMPAT.md)。
- `skills_dir`(字符串,可选):默认 `~/.codewhale/skills`(每个技能是包含 `SKILL.md` 的目录)。工作区获得信任后,项目技能优先于全局技能,其中 `.codewhale/skills` 优先于 `.agents/skills` 等兼容目录。顶层 `./skills` 需要显式设置 `[skills] flat_workspace_root = true` 或 `skills_dir` 才会加载。运行时还发现全局 agentskills.io 兼容的 `~/.agents/skills` 和更广的 Claude 生态系统 `~/.claude/skills`。首次启动为常见工作流安装带版本的捆绑技能,包括技能创建、委派、MCP/插件脚手架、文档、演示文稿、电子表格、PDF 和飞书/Lark。只有 CodeWhale 自有的根(`<workspace>/.codewhale/skills` 和 `~/.codewhale/skills`)是可写安装/导入目标;兼容 harness 根保持只读。裸 `/skills` 打开 Extensions → Skills(仅自有,零网络);`/skills manage` 打开技能管理器。管理器、审计状态、来源标记和变更规则见 [SKILLS.md](SKILLS.md),可移植 `SKILL.md` 包与 Claude Code 插件运行时之间的受支持边界见 [CLAUDE_PLUGIN_COMPAT.md](../CLAUDE_PLUGIN_COMPAT.md)。
- `[skills].scan_codewhale_only`(bool,默认 `false`):为 `true` 时,会话技能发现忽略跨工具根,如 `.claude/skills`、`.opencode/skills`、`.cursor/skills` 和 `~/.agents/skills`。Codewhale 仍扫描 `<workspace>/.codewhale/skills`、`~/.codewhale/skills` 和任何显式 `skills_dir` 覆盖。技能管理器仍可独立于这个运行时旋钮切换本地兼容审计扫描——见 [SKILLS.md](SKILLS.md)。
@@ -1869,7 +1869,7 @@ allowed_sandbox_modes = ["read-only", "workspace-write"]
`codewhale doctor` 遵循与 TUI 其余部分相同的配置解析规则。这意味着 `--config`、`CODEWHALE_CONFIG_PATH` 和旧 `DEEPSEEK_CONFIG_PATH` 都被尊重,MCP/skills 检查使用解析出的 `mcp_config_path` / `skills_dir`(含环境覆盖)。
要引导缺失的 MCP/skills 路径,运行 `codewhale setup --all`。你也可以运行 `codewhale setup --skills --local` 创建工作区本地的 `./skills` 目录。
要引导缺失的 MCP/skills 路径,运行 `codewhale setup --all`。你也可以运行 `codewhale setup --skills --local` 创建自有的项目目录 `.codewhale/skills`。项目技能在工作区获得信任后加载。
普通 `codewhale doctor` 和 `doctor --json` 都是结构性的,默认离线。它们不检查 release 服务、托管 provider API、本地 provider 端点或 MCP 进程,也不加载工作区凭据 `.env`。用 `--check-updates`、`--probe-api`、`--probe-local` 或 `--probe-mcp` 选择加入对应的实时边界;`--probe-local` 可能启动 Ollama 这样的桌面托管服务。只有显式 API/local 探测路径可以加载工作区凭据 `.env` 值。实时标志与 `--json` 冲突,所以机器可读 doctor 输出总是离线。顶层键包括 `version`、`paths`、`secret_backend`、`config_path`、`config_present`、`workspace`、`api_key.source`、`api_key.availability`、`base_url`、`default_text_model`、`mcp`、`skills`、`tools`、`plugins`、`sandbox`、`platform`、`api_connectivity` 和 `capability`。CI 消费者应依赖 `api_key.source`(`config_declared`/`env_declared`/`external_auth_declared`/`secret_store_unprobed`/`secret_store_unavailable`/`oauth_unprobed`/`external_consent`/`none`/`local_runtime`/`unknown`)和 `api_key.availability`(`present`/`not_required`/`not_probed`/`unavailable`/`unknown`),而不是解析人类可读的 `doctor` 文本。Source 是声明元数据,不是凭据存在或工作的证明。只有非空、非哨兵的字面配置值才在结构上 `present`;无认证和本地路由是 `not_required`。环境、外部认证、OAuth、consent 和 secret-store 声明保持 `not_probed`,不能让结构 Setup 或 Fleet 就绪成为真。被禁止使用共享存储的命名/自定义端点上的 secret-store 哨兵是 `secret_store_unavailable`/`unavailable`,而 `unknown` 保留给缺乏受支持结构结论的情况。确切和空白包裹的旧哨兵从不被当作字面凭据。结构加载器仍遵循安全环境路由/模型/策略字段,但从不物化环境 HTTP 头、沙箱 API key 或搜索 API key;只有显式 API/local 探测切换到正常凭据加载路径。选择加入的更新检查也只发出类型化通用失败:不可信 release 元数据和传输错误不被回显。
+1 -1
View File
@@ -390,7 +390,7 @@ Codewhale 还可以在 `.codewhale/harness/state.json` 维护一个小型项目
- 遵循团队发布检查清单。
- 使用项目特定的记忆或 wiki 工作流。
在 TUI 里,`/skill <name>` 在可用时激活技能,裸 `/skills` 打开技能管理器(仅限自有清单,无网络)。用 `/skills <prefix>`、`/skills inspect`、`/skills --remote`、`/skills suggest <task>` 或 `/skills sync` 走文本/注册表路径。建议会对远程目录排序,但绝不安装或激活任何东西。命令面板也能把技能条目和普通斜杠命令一起展示。
在 TUI 里,`/skill <name>` 在可用时激活技能,裸 `/skills` 打开 Extensions → Skills(仅限自有清单,无网络)。`/skills manage` 打开技能管理器。用 `/skills <prefix>`、`/skills inspect`、`/skills --remote`、`/skills suggest <task>` 或 `/skills sync` 走文本/注册表路径。建议会对远程目录排序,但绝不安装或激活任何东西。命令面板也能把技能条目和普通斜杠命令一起展示。
好的技能要窄。它们应该告诉模型遵循什么工作流、收集什么证据、避免什么。它们不应该隐藏凭据或取代正常的仓库文档。
+1 -1
View File
@@ -412,7 +412,7 @@ default_text_model = "deepseek-v4-pro"
| `/setup` | 重新打开首次设置流程 |
| `/compact` | 对话太长时压缩上下文、省 token |
| `/review` | 让 AI 对代码做结构化审查 |
| `/skills` | 打开技能管理器 |
| `/skills` | 打开 Extensions → Skills;`/skills manage` 打开技能管理器 |
| `/status` | 查看当前模型/路由等状态 |
| `/export` | 导出对话 |
| `/constitution` | 管理宪章(高级) |