fix(authority): bound the credential-upload scan and close its stdin, .pub and wget gaps (CW-7236)

Fix-up for 53ae15645c after review. All six review findings are addressed.

What
- Performance: shell_command_credential_uploads returns at once unless the
  text names curl or wget (names_upload_program: one linear byte pass,
  ASCII case-folded like command_word, skipping ' " and \ so c'u'rl and
  CURL still count). Above 64 KiB (CREDENTIAL_SCAN_MAX_BYTES) a text that
  names curl/wget is not parsed and fails closed with the marker. Before,
  every exec_shell stdin, interact payload and verifier row went through
  command_invocations (O(words^2) clones per segment) two or more times
  per call and on every approval-card render frame.
- Stdin fed from a secret: fed_from_credential is split into
  fed_from_credential_file (the old check) and fed_from_secret_variable:
  an argv headed by echo, printf or a <<< word with a secret-named
  variable, or printenv with a sensitive key name. The second applies only
  to body values, not -H @-, so a header read from stdin stays a header.
  The command is not scanned for $VAR wholesale, so
  -H "Authorization: Bearer $X" -d @- with a non-secret body stays
  unlabelled.
- .pub exemption per path: names_credential_file splits the text on
  , { } | & > into path alternatives first and applies the
  last-component-.pub exemption to each, so -T "{.env,x.pub}",
  -F "[email protected],x.pub" and -T ~/.ssh/id_rsa{,.pub} are held, and
  cat ~/.netrc|curl ... is read as two words.
- The <unparsed> marker is a named constant (UNPARSED_CREDENTIAL_UPLOAD).
  approval_summary drops it, so an unreadable call keeps its ordinary
  localized summary instead of "Upload credentials (`<unparsed>`)". The
  marker still counts in the audit credential_uploads field and still
  trips the safety gate.
- wget long options: an unambiguous prefix of at least 6 characters of
  post-data, body-data, post-file or body-file is matched. GNU Wget 1.25.0
  here accepts --post-d, --post-f, --body-d and --body-f, and rejects
  --post= and --post-= as ambiguous (exit 2).
- Docs: CHANGELOG [Unreleased] (plus the crates/tui mirror via
  sync-changelog.sh), CONFIGURATION and RUNTIME_API (en, zh_hans) now say
  what the scan covers: stdin fed from a credential file, a secret-named
  variable or env/printenv, and holds for a curl/wget over 64 KiB or nested
  too deeply. web/data/agent-docs.generated.json regenerated by
  `npm run prebuild`.

Verification (source: HEAD 856eefaa53 plus this diff; debug build
target/debug/codewhale built after the last Rust edit)
- cargo fmt --all -- --check: clean.
- scripts/dev-cargo.sh clippy -p codewhale-tui --all-targets --all-features
  --locked -- -D warnings -A clippy::uninlined_format_args
  -A clippy::too_many_arguments -A clippy::unnecessary_map_or: clean.
- scripts/dev-test.sh tui core::authority:: core::engine::tests
  tools::approval_summary tui::approval:
  739 tests run: 739 passed, 14612 skipped.
- sync-changelog.sh --check: up to date.
  scripts/release/check-versions.sh --require-dated-release: exit 0.
- npm test: root 101/101, runtime-sdk 19/19, extension-host 420 (413 pass,
  7 skipped), web node 7/7, vitest 772/772. npm run check:web: the first run
  failed "[check-facts] FAIL - generated agent docs are stale" because of
  the doc edits. It passes after `npm run prebuild`.
- Reviewer perf probe (review7236/perf.py: exec, Bash {command: "wc -w",
  stdin: "word " * N}, Never, isolated HOME, local stub provider,
  CODEWHALE_TELEMETRY=0), /usr/bin/time -l:
  - this debug build, N=10 / 2000 / 8000 / 16000: 2.47 / 2.18 / 2.19 /
    2.20 s real; max RSS 577.7 / 580.0 / 580.3 / 580.2 MB; peak footprint
    499.4 MB at every N.
  - installed 0.10.2 release (91dbd4f785), same N: 0.45 / 0.35 / 0.34 /
    0.34 s; max RSS 171.3 / 172.0 / 172.1 / 172.3 MB.
  - Both are flat in N. The debug build's constant of about 2.2 s and
    578 MB matches the review's N=2000 figure (2.4 s, 580 MB). The review
    measured a debug build of 53ae15645c at 6.0 s / 1.33 GB for N=8000 and
    18.1 s / 4.64 GB for N=16000.
- Reviewer runner probes (review7236/runner.py, Never, GITHUB_TOKEN set):
  R0 -d @.env, R1 echo "$GITHUB_TOKEN" |, R2 printenv GITHUB_TOKEN |,
  R3 -T "{.env,x.pub}", R4 cat ~/.netrc|curl, R5 wget --post-fi=.env and
  R6 -F "[email protected],x.pub" each gave action_kind destructive,
  credential_uploads 1, hold_for_review. The review's run on 53ae15645c
  (review7236/runs) gave credential_uploads 0 and ask_user for each of
  R1-R6.
  Extra cases held (1, hold_for_review): <<< "$GITHUB_TOKEN" and
  <<<"$GITHUB_TOKEN", printf '%s' "$GITHUB_TOKEN" | curl -T -,
  wget --post-d="$GITHUB_TOKEN", -T ~/.ssh/id_rsa{,.pub}, CURL -d @.env,
  c'u'rl -d @.env, and command sh with stdin "curl -d @.env ...".
  Extra cases not held (0, ask_user): echo '{"a":1}' | curl -H
  "Authorization: Bearer $GITHUB_TOKEN" --data-binary @-, printf
  'Authorization: Bearer %s' "$GITHUB_TOKEN" | curl -H @-, printenv HOME |,
  cat ~/.ssh/id_rsa.pub|curl --data-binary @-, -T ~/.ssh/id_ed25519.pub,
  wget --post=.env (ambiguous; wget itself refuses it), and the heredoc
  cat <<EOF | curl --data-binary @- with $GITHUB_TOKEN in the body (a
  stated limit).
- Original acceptance probes (probe/run.py, 18 cases) re-run on this
  build. Results match 53ae15645c: the U+200B, U+202E, ZWNJ and stdin-RLO
  cases are blocked with ASCII reasons, and Full Access runs them. The
  -F [email protected] and -d "$GITHUB_TOKEN" cases are held in Ask (denied) and
  Auto-Review, and still held under a curl allow rule. The Bearer header,
  plain curl, .pub upload and emoji cases are unlabelled.
- TUI in tmux (Ask), debug build:
  - -F [email protected] card: "Can't be undone  Upload credentials
    (`[email protected]`) with `curl ...`".
  - sh with stdin "curl -d @.env ...": "Can't be undone  Upload
    credentials (`@.env`) with `curl -sS -d @.env ...`".
  - The marker cases (sh -c nested 9 deep, and sh with that stdin plus
    70 KB of words) read "Can't be undone  Run `sh -c ...`" and "Can't be
    undone  Run `sh`". There is no <unparsed> on either card. Audit:
    destructive, credential_uploads 1, hold_for_review.

Not verified / known limits
- Residual cost below the cap: a text under 64 KiB that names curl or
  wget is still parsed by command_invocations, which is quadratic. Measured
  on this debug build with stdin "curl " + "word " * N under command
  wc -w: N=4000 3.1 s / 580 MB, N=8000 6.0 s / 1.30 GB, N=12000
  10.6 s / 2.73 GB; N=14000 (over 64 KiB) 2.2 s / 576 MB and held. 0.10.2
  already paid this cost for command text in shell_params_are_destructive_like.
  It is new only for stdin and interact payloads that name curl/wget. The
  real fix is to drop the per-suffix clone in
  codewhale_execpolicy::command_safety::command_invocations, which is a
  separate execpolicy slice.
- The gate reason for a marker-only hold still reads "credential upload
  requires durable review: the request body carries a credential file or
  secret-named variable". The deterministic reasons are English literals,
  and none was added here.
- A TUI card for a command the risk classifier rates benign (wc -w) whose
  stdin is held shows Routine stakes and offers "Always allow in this repo",
  because classify_stakes returns early for benign risk. The engine still
  forces the prompt, and no saved rule answers it.
- Not detected: here-documents, $(printenv NAME) or $(env) inside a value,
  and the existing limits listed in 53ae15645c.
- No CI, no release build, no Windows run, no desktop or mobile render.

Refs CW-7236

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-10-16 10:01:02 -07:00
1 parent 856eefaa53
commit 7af7549c1e
9 files changed
+127 -67

No files matched your search

+12 -8
View File
@@ -26,16 +26,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A `curl` or `wget` whose request body uploads a credential is now held by
the built-in safety gate. This covers `-d`/`--data*`/`--json`/`-F`/
`--url-query`/`-T`/`--upload-file`/`-H @file`/`--post-file`/`--post-data`
carrying `.env`, `~/.ssh/id_*` (public `.pub` keys excepted),
(and wget's abbreviations of them) carrying `.env`, `~/.ssh/id_*` (public
`.pub` keys excepted, judged per file in `{a,b}` and comma lists),
`~/.aws/credentials`, `.netrc`, a key file, a secret-named variable such as
`$GITHUB_TOKEN` or `$OPENAI_API_KEY`, or a credential piped in on stdin or
through command substitution. Ask shows a card headed "Upload credentials
(…) with `…`" that a session grant or saved allow rule cannot answer;
Auto-Review and Never deny the call. Runtime clients receive the same
heading as the approval `summary`. A literal request header
`$GITHUB_TOKEN` or `$OPENAI_API_KEY`, or stdin fed from one of those, from
`env`/`printenv`, or from `echo`/`printf`/`<<<` of a secret-named variable.
A `curl` or `wget` too large (over 64 KiB) or too deeply nested to read is
held the same way under its ordinary heading. Ask shows a card headed
"Upload credentials (…) with `…`" that a session grant or saved allow rule
cannot answer; Auto-Review and Never deny the call. Runtime clients receive
the same heading as the approval `summary`. A literal request header
(`-H "Authorization: Bearer $TOKEN"`) is not affected, and other upload
tools (scp, rsync, nc, httpie, `aws s3 cp`) and secrets inside an ordinary
file are not detected. Full Access is unchanged (Refs CW-7236).
tools (scp, rsync, nc, httpie, `aws s3 cp`), secrets inside an ordinary
file, here-documents and `$(printenv NAME)` inside a value are not
detected. Full Access is unchanged (Refs CW-7236).
- Switching provider, model route, profile or workspace, or changing plugins,
while a `/compact` is running or queued no longer leaves the session stuck.
The pass on the replaced engine is stopped and the transcript says "Making
+12 -8
View File
@@ -26,16 +26,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A `curl` or `wget` whose request body uploads a credential is now held by
the built-in safety gate. This covers `-d`/`--data*`/`--json`/`-F`/
`--url-query`/`-T`/`--upload-file`/`-H @file`/`--post-file`/`--post-data`
carrying `.env`, `~/.ssh/id_*` (public `.pub` keys excepted),
(and wget's abbreviations of them) carrying `.env`, `~/.ssh/id_*` (public
`.pub` keys excepted, judged per file in `{a,b}` and comma lists),
`~/.aws/credentials`, `.netrc`, a key file, a secret-named variable such as
`$GITHUB_TOKEN` or `$OPENAI_API_KEY`, or a credential piped in on stdin or
through command substitution. Ask shows a card headed "Upload credentials
(…) with `…`" that a session grant or saved allow rule cannot answer;
Auto-Review and Never deny the call. Runtime clients receive the same
heading as the approval `summary`. A literal request header
`$GITHUB_TOKEN` or `$OPENAI_API_KEY`, or stdin fed from one of those, from
`env`/`printenv`, or from `echo`/`printf`/`<<<` of a secret-named variable.
A `curl` or `wget` too large (over 64 KiB) or too deeply nested to read is
held the same way under its ordinary heading. Ask shows a card headed
"Upload credentials (…) with `…`" that a session grant or saved allow rule
cannot answer; Auto-Review and Never deny the call. Runtime clients receive
the same heading as the approval `summary`. A literal request header
(`-H "Authorization: Bearer $TOKEN"`) is not affected, and other upload
tools (scp, rsync, nc, httpie, `aws s3 cp`) and secrets inside an ordinary
file are not detected. Full Access is unchanged (Refs CW-7236).
tools (scp, rsync, nc, httpie, `aws s3 cp`), secrets inside an ordinary
file, here-documents and `$(printenv NAME)` inside a value are not
detected. Full Access is unchanged (Refs CW-7236).
- Switching provider, model route, profile or workspace, or changing plugins,
while a `/compact` is running or queued no longer leaves the session stuck.
The pass on the replaced engine is stopped and the transcript says "Making
+64 -28
View File
@@ -1234,20 +1234,49 @@ fn hidden_unicode_reason(input: ExecutionInput, position: usize, ch: char, count
)
}
pub(crate) const UNPARSED_CREDENTIAL_UPLOAD: &str = "<unparsed>";
const CREDENTIAL_SCAN_MAX_BYTES: usize = 64 * 1024;
fn names_upload_program(text: &str) -> bool {
let mut window = [0u8; 4];
text.bytes()
.filter(|byte| !matches!(byte, b'"' | b'\'' | b'\\'))
.any(|byte| {
window = [window[1], window[2], window[3], byte.to_ascii_lowercase()];
matches!(&window, b"curl" | b"wget")
})
}
fn shell_command_credential_uploads(command: &str) -> Vec<String> {
let Some(argvs) = codewhale_execpolicy::command_safety::command_invocations(command) else {
return if command.contains("curl") || command.contains("wget") {
vec!["<unparsed>".to_string()]
} else {
Vec::new()
};
if !names_upload_program(command) {
return Vec::new();
}
let argvs = (command.len() <= CREDENTIAL_SCAN_MAX_BYTES)
.then(|| codewhale_execpolicy::command_safety::command_invocations(command))
.flatten();
let Some(argvs) = argvs else {
return vec![UNPARSED_CREDENTIAL_UPLOAD.to_string()];
};
let fed_from_credential = || {
let fed_from_credential_file = || {
command.split_whitespace().any(names_credential_file)
|| argvs.iter().any(
|argv| matches!(argv.as_slice(), [word] if word == "env" || word == "printenv"),
)
};
let fed_from_secret_variable = || {
argvs.iter().any(|argv| match argv.as_slice() {
[program, names @ ..] if program == "printenv" => names
.iter()
.any(|name| codewhale_sanitize::sanitize::is_sensitive_key_name(name)),
[program, ..]
if matches!(program.as_str(), "echo" | "printf") || program.starts_with("<<<") =>
{
argv.iter().any(|word| names_secret_variable(word))
}
_ => false,
})
};
let mut uploads: Vec<String> = Vec::new();
for argv in &argvs {
let Some((program, args)) = argv.split_first() else {
@@ -1273,7 +1302,9 @@ fn shell_command_credential_uploads(command: &str) -> Vec<String> {
|| candidates
.iter()
.any(|candidate| names_credential_file(candidate))
|| ((reads_stdin || substitutes) && fed_from_credential());
|| ((reads_stdin || substitutes)
&& (fed_from_credential_file()
|| (kind != UploadValue::Header && fed_from_secret_variable())));
if credential && !uploads.iter().any(|upload| upload == value) {
uploads.push(value.to_string());
}
@@ -1291,18 +1322,21 @@ enum UploadValue {
}
fn upload_values<'a>(program: &str, args: &'a [String]) -> Vec<(UploadValue, &'a str)> {
let long_kind = |name: &str| match (program, name) {
(
"curl",
"data" | "data-ascii" | "data-binary" | "data-urlencode" | "json" | "form"
| "url-query",
) => Some(UploadValue::Data),
("curl", "data-raw" | "form-string") | ("wget", "post-data" | "body-data") => {
Some(UploadValue::Literal)
let long_kind = |name: &str| {
let wget = |option: &str| program == "wget" && name.len() >= 6 && option.starts_with(name);
match (program, name) {
(
"curl",
"data" | "data-ascii" | "data-binary" | "data-urlencode" | "json" | "form"
| "url-query",
) => Some(UploadValue::Data),
("curl", "data-raw" | "form-string") => Some(UploadValue::Literal),
("curl", "upload-file") => Some(UploadValue::File),
("curl", "header") => Some(UploadValue::Header),
_ if wget("post-data") || wget("body-data") => Some(UploadValue::Literal),
_ if wget("post-file") || wget("body-file") => Some(UploadValue::File),
_ => None,
}
("curl", "upload-file") | ("wget", "post-file" | "body-file") => Some(UploadValue::File),
("curl", "header") => Some(UploadValue::Header),
_ => None,
};
if !matches!(program, "curl" | "wget") {
return Vec::new();
@@ -1369,15 +1403,17 @@ fn names_secret_variable(value: &str) -> bool {
}
fn names_credential_file(text: &str) -> bool {
let components: Vec<String> = text
.split(|ch: char| ch.is_whitespace() || "@<=/\\:;,\"'()`{}[]".contains(ch))
.filter(|component| !component.is_empty())
.map(str::to_ascii_lowercase)
.collect();
!components.last().is_some_and(|last| last.ends_with(".pub"))
&& components
.iter()
.any(|component| crate::core::authority::is_credential_file_name(component))
text.split(|ch: char| ",{}|&>".contains(ch)).any(|path| {
let components: Vec<String> = path
.split(|ch: char| ch.is_whitespace() || "@<=/\\:;\"'()`[]".contains(ch))
.filter(|component| !component.is_empty())
.map(str::to_ascii_lowercase)
.collect();
!components.last().is_some_and(|last| last.ends_with(".pub"))
&& components
.iter()
.any(|component| crate::core::authority::is_credential_file_name(component))
})
}
pub(crate) fn credential_upload_texts<'a>(
+13 -2
View File
@@ -47,8 +47,19 @@ pub fn approval_summary_in(
tr(locale, id).replace(&format!("{{{slot}}}"), value)
};
let credential_uploads =
crate::core::authority::auto_review::credential_upload_texts(tool_name, input);
let credential_uploads: Vec<_> =
crate::core::authority::auto_review::credential_upload_texts(tool_name, input)
.into_iter()
.filter_map(|(command, uploads)| {
let uploads: Vec<String> = uploads
.into_iter()
.filter(|upload| {
upload != crate::core::authority::auto_review::UNPARSED_CREDENTIAL_UPLOAD
})
.collect();
(!uploads.is_empty()).then_some((command, uploads))
})
.collect();
if let Some((command, _)) = credential_uploads.first() {
let uploads: Vec<&str> = credential_uploads
.iter()
+4 -2
View File
@@ -2368,8 +2368,10 @@ reasoning contract, and all four membership ids omit generic sampling fields.
fails closed as a hard block. Full Access skips the built-in floor. The
safety floor covers publish-like actions, destructive background/headless
actions, and `curl`/`wget` uploads whose request body carries a credential
file, a secret-named variable or a credential fed through stdin, even if an
allow rule matches. Outside Full Access it also refuses, in every posture, a
file or a secret-named variable, or reads stdin fed from one of those or
from `env`/`printenv`, even if an allow rule matches; a `curl`/`wget` too
large (over 64 KiB) or too deeply nested to read is held too. Outside Full
Access it also refuses, in every posture, a
shell command, shell input or verifier containing a hidden Unicode character
(bidirectional controls, zero-width and other invisible characters, tag
characters, stray variation selectors, and terminal control characters
+5 -3
View File
@@ -1388,9 +1388,11 @@ Clients show it first and keep the raw arguments behind it. For task and
automation create/update, `summary` also names the requested trust mode,
shell, auto-approve, mode and workspace. For a shell command, task gate,
shell input or verifier whose `curl`/`wget` request body carries a credential
file, a secret-named variable or a credential fed through stdin, `summary`
reads ``Upload credentials (`…`) with `…` ``; the built-in safety gate holds
that call, so a session grant or saved allow rule cannot answer it. A shell
file or a secret-named variable, or reads stdin fed from one of those or from
`env`/`printenv`, `summary` reads ``Upload credentials (`…`) with `…` ``; the
built-in safety gate holds that call, so a session grant or saved allow rule
cannot answer it. A `curl`/`wget` too large (over 64 KiB) or too deeply nested
to read is held the same way under its ordinary `summary`. A shell
command containing a hidden Unicode character is refused before any
approval is created.
+1 -1
View File
@@ -1266,7 +1266,7 @@ DeepSeek V4 前缀缓存让 token 标签变得重要。这些数量保持分离
action = "session.compact"
```
- `[auto_review]`(表,可选):工具调用审查策略——确定性底线加模型守卫层。这一层位于现有权限档位之上;它可以拦住或阻断工具调用,但不是自动推送、自动合并或托管审查服务。先检查阻断规则,然后是内置安全底线,再是允许规则。在 Ask 中,安全拦阻打开审批,会话授权或 allow 规则都无法应答它;在 Auto-Review 或非交互 `never` 档位中,它作为硬阻断失败关闭。Full Access 跳过内置底线。即使 allow 规则匹配,安全底线仍覆盖类似发布的动作、破坏性的后台/无头动作,以及请求体携带凭据文件、名称表明是机密的变量或经 stdin 送入凭据的 `curl`/`wget` 上传。在 Full Access 之外,它还会在每个档位拒绝含有隐藏 Unicode 字符(双向控制符、零宽及其他不可见字符、标签字符、孤立的变体选择符,以及 shell 输入中 ASCII 控制键之外的终端控制字符)的 shell 命令、shell 输入或校验器,并写出该码位及其位置。
- `[auto_review]`(表,可选):工具调用审查策略——确定性底线加模型守卫层。这一层位于现有权限档位之上;它可以拦住或阻断工具调用,但不是自动推送、自动合并或托管审查服务。先检查阻断规则,然后是内置安全底线,再是允许规则。在 Ask 中,安全拦阻打开审批,会话授权或 allow 规则都无法应答它;在 Auto-Review 或非交互 `never` 档位中,它作为硬阻断失败关闭。Full Access 跳过内置底线。即使 allow 规则匹配,安全底线仍覆盖类似发布的动作、破坏性的后台/无头动作,以及请求体携带凭据文件或名称表明是机密的变量、或读取由这两者或 `env`/`printenv` 送入的 stdin 的 `curl`/`wget` 上传;过大(超过 64 KiB)或嵌套过深而无法读取的 `curl`/`wget` 同样会被拦住。在 Full Access 之外,它还会在每个档位拒绝含有隐藏 Unicode 字符(双向控制符、零宽及其他不可见字符、标签字符、孤立的变体选择符,以及 shell 输入中 ASCII 控制键之外的终端控制字符)的 shell 命令、shell 输入或校验器,并写出该码位及其位置。
```toml
[[auto_review.allow]]
+4 -3
View File
@@ -1192,10 +1192,11 @@ Engine 的工具执行 ID 另行以 `tool_call_id` 出现在
“Write notes/espresso.md”);工作区内的路径为工作区相对路径。
客户端应先展示它,并把原始参数留在其后。对任务与自动化的创建/更新,
`summary` 还会写出所请求的信任模式、shell、自动批准、模式和工作区。
对 shell 命令、任务门禁、shell 输入或校验器,若其 `curl`/`wget` 请求体携带凭据文件、
名称表明是机密的变量,或经 stdin 送入的凭据,`summary` 显示为
对 shell 命令、任务门禁、shell 输入或校验器,若其 `curl`/`wget` 请求体携带凭据文件
或名称表明是机密的变量,或读取由这两者或 `env`/`printenv` 送入的 stdin,`summary` 显示为
``Upload credentials (`…`) with `…` ``;内置安全闸会拦住该调用,
会话授权或已保存的 allow 规则都无法应答它。含有隐藏 Unicode 字符的 shell 命令
会话授权或已保存的 allow 规则都无法应答它。过大(超过 64 KiB)或嵌套过深而无法读取的
`curl`/`wget` 同样会被拦住,`summary` 保持普通内容。含有隐藏 Unicode 字符的 shell 命令
会在创建任何审批之前被拒绝。
在 `allow` 上带上 `"remember": true` 会为该工具及其参数类别记录一份**会话授权**
+12 -12
View File
@@ -32,8 +32,8 @@
"sources": [
{
"path": "docs/CONFIGURATION.md",
"sha256": "b4a1df17d67bba39c20523f8fb61f16da57cd7c499d61dd239b17c19e85f0896",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
"sha256": "37556e4b839550250162a2cc7ae91bb8f6db20d4e61f550f2b46c82e0a873760",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
},
{
"path": "docs/CODEWHALE_AGENT.md",
@@ -64,8 +64,8 @@
"sources": [
{
"path": "docs/CONFIGURATION.md",
"sha256": "b4a1df17d67bba39c20523f8fb61f16da57cd7c499d61dd239b17c19e85f0896",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
"sha256": "37556e4b839550250162a2cc7ae91bb8f6db20d4e61f550f2b46c82e0a873760",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
},
{
"path": "docs/LEGACY_PATHS.md",
@@ -96,8 +96,8 @@
},
{
"path": "docs/CONFIGURATION.md",
"sha256": "b4a1df17d67bba39c20523f8fb61f16da57cd7c499d61dd239b17c19e85f0896",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
"sha256": "37556e4b839550250162a2cc7ae91bb8f6db20d4e61f550f2b46c82e0a873760",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
}
]
},
@@ -224,8 +224,8 @@
},
{
"path": "docs/CONFIGURATION.md",
"sha256": "b4a1df17d67bba39c20523f8fb61f16da57cd7c499d61dd239b17c19e85f0896",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
"sha256": "37556e4b839550250162a2cc7ae91bb8f6db20d4e61f550f2b46c82e0a873760",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
}
]
},
@@ -256,8 +256,8 @@
},
{
"path": "docs/CONFIGURATION.md",
"sha256": "b4a1df17d67bba39c20523f8fb61f16da57cd7c499d61dd239b17c19e85f0896",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
"sha256": "37556e4b839550250162a2cc7ae91bb8f6db20d4e61f550f2b46c82e0a873760",
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Constitution, project instructions, and repo authority\n\nCodewhale has several instruction surfaces. They are deliberately separate so a\npersonal constitution, repo policy, project instructions, and runtime security\ncontrols do not blur together.\n\n- **Bundled global Constitution** — the compiled base law in the binary. It is\n the default floor for every session.\n- **User-global constitution** — the normal guided setup output. Manage it with\n `/constitution` or `/setup`; Codewhale stores structured data at\n `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)\n and renders it into a separate `<codewhale_user_constitution>` prose block.\n This can express preferences and stop conditions, but it does not change\n runtime approval policy, sandbox, shell, network, trust, or MCP permissions.\n- **Repo-local constitution** — optional project policy in\n `.codewhale/constitution.json`, described below.\n- **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the\n canonical file for \"how should an agent work in this repo.\" Run `/init` to\n scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as\n compatibility fallbacks.\n- **Memory and handoffs** — recalled state. Useful, but lower authority than\n constitutions and project instructions.\n\n### Managing the user-global constitution (`/setup` and `/constitution`)\n\nThe bundled **working agreement** is the safe default and no longer adds a\nrequired first-run screen. Customize it later through `/constitution` or the\nprogressive `/setup` guide. Provider/model readiness, workspace trust, and\nruntime posture stay separate from this guidance.\n\nOn the **Working agreement** (constitution) step:\n\n- **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again\n ratifies and saves a fresh structured `constitution.json`.\n- **`A`** (shown only when a provider is configured) asks your first configured\n model to draft the constitution. Drafting is **not** saving: the draft is\n rendered through the same preview and you still press **`G`** to ratify\n before anything persists.\n- **`K`** keeps your existing loaded constitution unchanged (shown only when a\n valid file is already present).\n- **`U`** (or `/coLine truncated
}
]
},
@@ -288,8 +288,8 @@
"sources": [
{
"path": "docs/RUNTIME_API.md",
"sha256": "2d87c4e47dfdba7f5a7d6b90e480ef0f24d0b2934cf48a316e7aac5abcbf06ea",
"content": "# Codewhale Engine Runtime API & Integration Contract\n\n> 阅读简体中文版:[zh_hans/RUNTIME_API.md](zh_hans/RUNTIME_API.md)。\n\n`codewhale app-server` exposes the [Codewhale Engine](ARCHITECTURE.md) through\nits canonical local Runtime API and control plane.\nLocal SDKs, mobile/remote-control clients, and editor integrations talk to it\ninstead of screen-scraping terminal output. It serves the full HTTP/SSE runtime\nAPI (`/v1/*`), a JSON-RPC control transport over stdio, and the phone-friendly\nmobile page. `codewhale doctor --json` provides machine-readable health, and\n`codewhale serve --acp` speaks the Agent Client Protocol over stdio for editors\nsuch as Zed.\n\n`codewhale serve --http` / `serve --mobile` remain as **compatibility aliases**\nfor `codewhale app-server --http` / `--mobile`; both launch the identical\nserver. New integrations should target `app-server`.\n\n`codewhale exec` is the separate one-shot headless worker path (stream-json,\nfleet worker subprocess, CI primitive). It is not part of this API, but it\nshares the same runtime, provider/model resolution, permission profiles, and\nevent vocabulary.\n\nThis document is the stable integration contract for native workbench\napplications (and other local supervisors) that embed the Codewhale Engine.\n\n## Architecture\n\n```\nlocal supervisor / SDK / automation harness\n │\n ├─ codewhale app-server --http → HTTP/SSE runtime API (/v1/*) [canonical]\n ├─ codewhale app-server --mobile → runtime API + mobile control page\n ├─ codewhale app-server --stdio → JSON-RPC control transport over stdio\n ├─ codewhale app-server --socket → same JSON-RPC over a unix domain socket (desktop daemon)\n ├─ codewhale doctor --json → machine-readable health & capability\n ├─ codewhale serve --acp → ACP stdio agent for editors such as Zed\n ├─ codewhale serve --mcp → MCP stdio server\n ├─ codewhale serve --http/--mobile → legacy aliases for `app-server --http/--mobile`\n └─ codewhale exec [args] → one-shot headless worker (stream-json)\n```\n\nThe engine runs as a local-only process. All APIs bind to `localhost` by\ndefault. No hosted relay, no provider-token custody, no secret leakage.\n\nFor the read-only record of what a thread or turn did, see\n[`docs/RECEIPTS.md`](RECEIPTS.md): `codewhale receipts` on the CLI and the\n`/receipt` routes under **Threads** below.\n\n## Runtime API entrypoints\n\n| Entry | Transport | Use |\n|---|---|---|\n| `codewhale web [--port 7878]` | HTTP/SSE on `127.0.0.1:7878` + embedded client | First-class loopback-only browser client; opens the default browser |\n| `codewhale app-server --http` | HTTP/SSE on `127.0.0.1:7878` | Full `/v1/*` runtime API (canonical) |\n| `codewhale app-server --mobile` | HTTP/SSE on loopback + `/mobile` | Runtime API + local mobile control page |\n| `codewhale app-server --stdio` | JSON-RPC 2.0 over stdio | Local SDK / control probe (no listener) |\n| `codewhale app-server --socket [--socket-path P]` | JSON-RPC 2.0 over a `0600` Unix domain socket (macOS/Linux) or an owner-only named pipe (Windows) | Desktop daemon: multi-client, kernel peer identity checked, `daemon/attach` claim handshake. Windows native-app qualification remains open. |\n| `codewhale app-server` | HTTP on `127.0.0.1:8787` | Legacy in-process app-server (`/healthz`, `/thread`, `/app`, `/prompt`); background jobs are `GET /v1/jobs` on the Runtime API; `/prompt` and `/thread` messages execute real turns via the runtime bridge. There is no direct `/tool` route: tools run only inside Engine turns, under the Engine's tool catalog and approval posture. This legacy server does not surface approvals: its bridge forwards only text deltas and the turn's completion, and it has no decision route, so an approval-gated call waits unanswered. Drive approval-gated work through the Runtime API (`/v1/threads/*` events and `POST /v1/approvals/{approval_id}`) |\n| `codewhale serve --http` / `--mobile` | same server as `app-server --http`/`--mobile` | Compatibility aliases |\n\n`app-server --http` and `--mobile` launch the same mature runtime API server\nhistorically reached through `serve --http` — no routes or behavior changed, so\nevery endpoint documented below is identical across both entrypoints. The\nruntime API token is read from `--auth-token`, then `CODEWHALE_RUNTIME_TOKEN`,\nthen `DEEPSEEK_RUNTIME_TOKEN`; use `--insecure-no-auth` only with a loopback\nbind. The `serve` compatibility aliases keep their `--insecure` flag.\nThe legacy in-process `codewhale app-server` also requires an explicit\n`--auth-token` or `CODEWHALE_APP_SERVER_TOKEN` before binding a non-loopback\nhost; its generated one-time `cwapp_*` token is loopback-only.\n\nDevice tokens minted by the master through `POST /v1/auth/client-tokens`\nhave an immutable `intent`: `Line truncated
"sha256": "35648e87626c3c29a98603e7ede4e308c3190221d6d3a147d960ce2b3175cd41",
"content": "# Codewhale Engine Runtime API & Integration Contract\n\n> 阅读简体中文版:[zh_hans/RUNTIME_API.md](zh_hans/RUNTIME_API.md)。\n\n`codewhale app-server` exposes the [Codewhale Engine](ARCHITECTURE.md) through\nits canonical local Runtime API and control plane.\nLocal SDKs, mobile/remote-control clients, and editor integrations talk to it\ninstead of screen-scraping terminal output. It serves the full HTTP/SSE runtime\nAPI (`/v1/*`), a JSON-RPC control transport over stdio, and the phone-friendly\nmobile page. `codewhale doctor --json` provides machine-readable health, and\n`codewhale serve --acp` speaks the Agent Client Protocol over stdio for editors\nsuch as Zed.\n\n`codewhale serve --http` / `serve --mobile` remain as **compatibility aliases**\nfor `codewhale app-server --http` / `--mobile`; both launch the identical\nserver. New integrations should target `app-server`.\n\n`codewhale exec` is the separate one-shot headless worker path (stream-json,\nfleet worker subprocess, CI primitive). It is not part of this API, but it\nshares the same runtime, provider/model resolution, permission profiles, and\nevent vocabulary.\n\nThis document is the stable integration contract for native workbench\napplications (and other local supervisors) that embed the Codewhale Engine.\n\n## Architecture\n\n```\nlocal supervisor / SDK / automation harness\n │\n ├─ codewhale app-server --http → HTTP/SSE runtime API (/v1/*) [canonical]\n ├─ codewhale app-server --mobile → runtime API + mobile control page\n ├─ codewhale app-server --stdio → JSON-RPC control transport over stdio\n ├─ codewhale app-server --socket → same JSON-RPC over a unix domain socket (desktop daemon)\n ├─ codewhale doctor --json → machine-readable health & capability\n ├─ codewhale serve --acp → ACP stdio agent for editors such as Zed\n ├─ codewhale serve --mcp → MCP stdio server\n ├─ codewhale serve --http/--mobile → legacy aliases for `app-server --http/--mobile`\n └─ codewhale exec [args] → one-shot headless worker (stream-json)\n```\n\nThe engine runs as a local-only process. All APIs bind to `localhost` by\ndefault. No hosted relay, no provider-token custody, no secret leakage.\n\nFor the read-only record of what a thread or turn did, see\n[`docs/RECEIPTS.md`](RECEIPTS.md): `codewhale receipts` on the CLI and the\n`/receipt` routes under **Threads** below.\n\n## Runtime API entrypoints\n\n| Entry | Transport | Use |\n|---|---|---|\n| `codewhale web [--port 7878]` | HTTP/SSE on `127.0.0.1:7878` + embedded client | First-class loopback-only browser client; opens the default browser |\n| `codewhale app-server --http` | HTTP/SSE on `127.0.0.1:7878` | Full `/v1/*` runtime API (canonical) |\n| `codewhale app-server --mobile` | HTTP/SSE on loopback + `/mobile` | Runtime API + local mobile control page |\n| `codewhale app-server --stdio` | JSON-RPC 2.0 over stdio | Local SDK / control probe (no listener) |\n| `codewhale app-server --socket [--socket-path P]` | JSON-RPC 2.0 over a `0600` Unix domain socket (macOS/Linux) or an owner-only named pipe (Windows) | Desktop daemon: multi-client, kernel peer identity checked, `daemon/attach` claim handshake. Windows native-app qualification remains open. |\n| `codewhale app-server` | HTTP on `127.0.0.1:8787` | Legacy in-process app-server (`/healthz`, `/thread`, `/app`, `/prompt`); background jobs are `GET /v1/jobs` on the Runtime API; `/prompt` and `/thread` messages execute real turns via the runtime bridge. There is no direct `/tool` route: tools run only inside Engine turns, under the Engine's tool catalog and approval posture. This legacy server does not surface approvals: its bridge forwards only text deltas and the turn's completion, and it has no decision route, so an approval-gated call waits unanswered. Drive approval-gated work through the Runtime API (`/v1/threads/*` events and `POST /v1/approvals/{approval_id}`) |\n| `codewhale serve --http` / `--mobile` | same server as `app-server --http`/`--mobile` | Compatibility aliases |\n\n`app-server --http` and `--mobile` launch the same mature runtime API server\nhistorically reached through `serve --http` — no routes or behavior changed, so\nevery endpoint documented below is identical across both entrypoints. The\nruntime API token is read from `--auth-token`, then `CODEWHALE_RUNTIME_TOKEN`,\nthen `DEEPSEEK_RUNTIME_TOKEN`; use `--insecure-no-auth` only with a loopback\nbind. The `serve` compatibility aliases keep their `--insecure` flag.\nThe legacy in-process `codewhale app-server` also requires an explicit\n`--auth-token` or `CODEWHALE_APP_SERVER_TOKEN` before binding a non-loopback\nhost; its generated one-time `cwapp_*` token is loopback-only.\n\nDevice tokens minted by the master through `POST /v1/auth/client-tokens`\nhave an immutable `intent`: `Line truncated
}
]
},