feat(web): add local GT catalog pipeline for website and docs

Keep the dictionary runtime as the one i18n path and give updated English marketing/docs copy a fail-closed, lockfile-pinned translation lane. Do not wrap the TUI, completions, or /translate.
This commit is contained in:
Hunter Bown
2026-08-28 17:01:15 -07:00
committed by CodeWhale Bot
parent e5a6046cbc
commit fd3d5d8ac9
10 changed files with 4591 additions and 6 deletions

View File

@@ -81,6 +81,17 @@ locale, Chinese included.** `web/app/[locale]/page.tsx`,
`getChrome(locale)`. `web/lib/i18n/dictionaries/zh/` now exists (it used to
be inline TSX), and nav/footer link sets are generated once in
`web/lib/i18n/links.ts` so every locale gets the identical route shape.
**Website/docs translation pipeline (General Translation CLI, 2026-08-28).**
Runtime stays the dictionaries above — do not add `gt-next` beside them.
`web/gt-catalog/[locale].json` is the local JSON interchange (`en` + live
`zh` first). `npm run i18n:gt -- export` writes catalogs from dictionaries;
`check` (hooked from `check:locales`) requires them to match; `import`
writes reviewed JSON back to website dictionary TS only. `translate` is
fail-closed unless BYOK `GT_API_KEY` and `GT_PROJECT_ID` are set in the
environment — never commit those values, never point this config at
`crates/tui/locales`, and never wrap model completions. `gt generate` is
not used: it is a framework JSX scanner, not a JSON-catalog tool.
Reference shape: **`ChromeDict` 52 keys, `HomeDict` 62 keys.** Bilingual
secondary nav labels, the masthead seal and issue line, the ticker live
label, and the per-locale `Intl` date tag are dictionary values — no locale

View File

@@ -25,3 +25,10 @@ MAINTAINER_GITHUB_PAT=
# Set to 1 once the Gitee mirror at gitee.com/Hmbown/...
# exists. Until then leave blank to hide Gitee links.
NEXT_PUBLIC_GITEE_ENABLED=
# Optional BYOK for website/docs machine translation via the General
# Translation CLI (`npm run i18n:gt -- translate`). Never commit values.
# Check/export/import are local and do not need these. Runtime does not
# call GT. Fail-closed without both. Never wrap model completions.
# GT_API_KEY=
# GT_PROJECT_ID=

View File

@@ -12,7 +12,10 @@ site.
- Localization migrations are one-way. New or touched pages use the shared
dictionary/content spine; do not add page-local language booleans, `isZh`
copy forks, or hard-coded translated page clones. A compatibility exception
must name the external contract that still requires it.
must name the external contract that still requires it. Website/docs
machine translation uses `npm run i18n:gt` (local JSON catalogs into the
same dictionaries). Do not add `gt-next` beside that path, and do not
point GT at TUI locale packs.
- Real-session media is owned by `lib/media-manifest.ts`. `pending` is the
truthful state until the complete asset set passes manifest tests.
- `AGENT.md` (singular) documents maintainer-owned community automation. Do not

504
web/gt-catalog/en.json Normal file
View File

@@ -0,0 +1,504 @@
{
"chrome": {
"navDocs": "Docs",
"navStart": "Start",
"navInstall": "Install",
"navFaq": "FAQ",
"navCommunity": "Community",
"navContribute": "Contribute",
"navDocsSecondary": "文档",
"navStartSecondary": "指引",
"navInstallSecondary": "安装",
"navFaqSecondary": "问答",
"navCommunitySecondary": "社区",
"navContributeSecondary": "贡献",
"skipToContent": "Skip to main content",
"navPrimaryAria": "Primary",
"navHomeAria": "Codewhale home",
"installCta": "Install →",
"authSignIn": "Sign in",
"authRegister": "Register",
"authGroupAria": "Account",
"wordmarkSeal": "深",
"wordmarkTag": "any model, on your machine",
"issueLabel": "Issue {date}",
"dateLocale": "en-US",
"starsAria": "GitHub stars",
"githubFallback": "GitHub",
"tickerLiveLabel": "实 时",
"tickerLiveTag": "LIVE",
"tickerMerged": "merged",
"tickerOpened": "opened",
"tickerClosed": "closed",
"tickerReleased": "released",
"tickerFirstContribution": "first contribution",
"tickerBy": "by {handle}",
"tickerAria": "Recent repository activity",
"traceLabel": "reasoning trace",
"traceTabsAria": "Session excerpts",
"menuOpen": "Open menu",
"menuClose": "Close menu",
"themeAuto": "auto",
"themeLight": "light",
"themeDark": "dark",
"themeAria": "Docs theme: {mode} (click to cycle)",
"themeTitle": "Docs theme · auto / light / dark",
"footerTagline": "Codewhale dives into the deep so you don't have to — docs, source, and community for the open-source runtime.",
"footerProduct": "Product",
"footerProject": "Project",
"footerDocs": "Docs",
"footerGuide": "Getting started",
"footerInstall": "Install",
"footerModels": "Models",
"footerRuntime": "Runtime",
"footerFaq": "FAQ",
"footerIssues": "Issues",
"footerContribute": "Contribute",
"footerLicense": "MIT license",
"footerCanonicalSource": "Canonical source: ",
"footerReleases": " · Releases: ",
"footerReleasesLink": "GitHub Releases",
"footerSecurity": "Security",
"switcherLabel": "Language",
"switcherSwitchTo": "Switch to {label}",
"partialBadge": "(partial)"
},
"home": {
"metaTitle": "Codewhale — dives into the deep so you don't have to.",
"metaDescription": "Codewhale dives into the deep so you don't have to — an open-source terminal coding agent. Bring your own model. Runs on your machine. Rust, MIT.",
"kicker": "Open source · Bring your own model · Runs in your terminal",
"heroTitleA": "Codewhale dives into the deep",
"heroTitleB": "so you don't have to.",
"heroIntro": "{brand} is an open-source coding agent for your terminal. Give it a model and a task. It reads your code, edits files, runs the checks, and stops when the job is done or it needs you. Use any model, or a different one for each role.",
"install": "Install",
"docs": "Docs",
"copy": "Copy",
"copied": "Copied ✓",
"installEyebrow": "one-line install",
"installRequirement": "needs Node 18+ — no Rust toolchain",
"installOtherWays": "other ways →",
"latestRelease": "Latest release {tag}",
"releaseUnavailable": "Release status unavailable",
"currentSource": "Source",
"sourceCandidate": "Unreleased",
"providerRoutes": "{count} providers",
"publishedRelease": "released",
"figcaptionSourceCandidate": "unreleased",
"shotSession": "Session",
"screenshotAlt": "Codewhale terminal session in Operate mode: the whale, the composer, and the status footer",
"figcaption": "Codewhale session · Operate mode · permissions: Ask",
"proofHeading": "A coding agent in your terminal. Any model. On your machine.",
"proofBody": "Use the model you already have — hosted, through a gateway, or local. Pick a mode: Plan, Work, or Operate. Pick how much it does without asking: Ask, Auto-Review, or Full Access.",
"sealDecides": "法",
"decidesEyebrow": "How it decides",
"decidesHeading": "The reasoning, in its own words",
"decidesLede": "Session excerpts. Each shows the project rule the model applied and what it did next.",
"sealWorkflow": "行",
"workflowHeading": "From task to verified change.",
"workflow": [
[
"Inspect",
"Read the repository, its instructions, and the task."
],
[
"Act",
"Edit files, asking first where you told it to."
],
[
"Verify",
"Run the checks and read the result."
],
[
"Report",
"Say what changed and what passed."
]
],
"receiptAria": "Example run summary",
"receiptInspect": "repository and instructions",
"receiptAct": "edit files under the permissions you set",
"receiptReport": "checks passed · summary saved",
"sealStart": "起",
"startHeading": "New here? Four steps.",
"startLede": "Install → first session, no key → connect a provider → set up a fleet.",
"startGuideLink": "Read the getting-started guide →",
"startVocabularyLink": "Look up a term →",
"sealBoundaries": "界",
"boundariesHeadingA": "Your model.",
"boundariesHeadingB": "Your boundaries.",
"boundariesBody": "You choose the model, the mode, and how much it may do without asking. The provider and model never change unless you change them. Preview features are marked preview.",
"hostedGatewayLocal": "Hosted, gateway, and local models",
"planActOperateDesc": "From read-only planning to autonomous operation",
"askAutoReviewDesc": "How much it does before asking you",
"tuiExecWebDesc": "Interactive or scripted",
"sealSurfaces": "面",
"surfacesHeading": "Use it where the work happens.",
"surfaces": [
[
"TUI",
"Interactive terminal work"
],
[
"codewhale exec",
"Scripts and CI"
],
[
"Web client",
"Browser client, localhost only"
],
[
"Runtime API + MCP",
"Local integrations"
],
[
"Fleet",
"Several agents on one job"
]
],
"runtimeLink": "Runtime surfaces and what is stable →",
"installBandHeading": "Start with one command.",
"binaries": "Binaries",
"chinaMirrors": "China mirrors",
"installGuideLink": "Read the install guide →",
"sealCommunity": "众",
"communityHeading": "Built in public",
"communityBody": "MIT license. Contributors work on the runtime, providers, platforms, docs, and tests.",
"communityLinksAria": "Community links",
"contribute": "Contribute"
},
"docs-guide": {
"metaTitle": "Getting started · Codewhale Docs",
"metaDescription": "The full path from install to your ideal fleet: install, a first keyless session, provider connection, and fleet setup.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Getting started",
"overviewLead": "Four steps from one install command to a fleet set up for your work.",
"sessionTitle": "Watch a real session",
"sessionLead": "A recording of a real session will go here. There is no recording yet, so nothing is shown.",
"nextTitle": "Where next",
"sourceNote": "Source documents: docs/GUIDE.md, docs/KEYBINDINGS.md · Step copy lives in web/lib/content/getting-started.ts; update docs-map.ts when changing."
},
"docs-shell": {
"metaTitle": "Docs · Codewhale",
"metaDescription": "Codewhale documentation: install, user guide, configuration, providers, core concepts, tools, MCP, skills, sandbox, runtime API, troubleshooting.",
"portalMark": "Codewhale documentation",
"heroTitle": "Find the guidance you need.",
"heroLead": "Start with the guide and install pages, or go straight to vocabulary, modes, permissions, tools, providers, Fleet, hooks, MCP, and the Runtime API. Each page links to its source document in the repository.",
"installCta": "Install Codewhale",
"sourceDocsCta": "Browse source docs ↗"
},
"docs-hooks": {
"metaTitle": "Hooks · Codewhale Docs",
"metaDescription": "The shipped lifecycle hooks: mutable message_submit, tool_call_before decisions, turn_end, and sub-agent observer events.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Hooks",
"overviewLead": "Hooks attach your own commands to Codewhale's lifecycle: inject context before a message is submitted, enforce policy before a tool call, and audit turns or sub-agent activity. This page describes what currently ships; docs/rfcs/1364-hooks-lifecycle.md is the design RFC for this surface, and docs/CONFIGURATION.md carries the full configuration schema.",
"configIntro": "Hooks are configured under {hooksTable} entries in config.toml; run {hooksCommand} in the TUI to see every configured hook grouped by event — name, command preview, timeout, and condition — plus the global {enabledKey} state.",
"events": [
[
"message_submit (mutable)",
"Runs before a submitted message is added to history or sent to the model. The hook receives JSON on stdin; exit 0 with stdout JSON carrying a non-empty text field replaces the submitted text, and exit 2 blocks the submission before the turn starts. Multiple hooks run serially in config order, each receiving the previous hook's output. Hooks marked background = true are observer-only and cannot transform or block."
],
[
"tool_call_before (decision)",
"Runs before each tool call executes. Beyond the exit-2 hard deny (which always wins), a foreground hook may print a JSON decision on stdout with exit 0: allow / deny / ask, plus updatedInput to rewrite the tool input and additionalContext appended to the tool result the model sees. When several hooks match, precedence is deny > ask > allow; tool_name conditions support * globs (mcp__* matches every MCP tool). Full Access does not open tool-approval prompts, so ask does not downgrade that posture."
],
[
"turn_end (observer)",
"Fires after each model turn ends, once usage, cost, notifications, receipts, and queue-recovery state have settled. The stdin JSON carries fields such as status, duration_ms, usage, totals, and queued_message_count. Stdout is ignored and failures are warn-only — the hook cannot block input, mutate the transcript, or change the next queued follow-up."
],
[
"subagent_spawn / subagent_complete (observer)",
"Observe sub-agent start and completion with bounded JSON metadata on stdin (agent_id, status, truncated prompt/result previews). Failures are warn-only and never block scheduling or change prompts or results; use the transcript handle returned by agent when full detail is needed."
]
],
"projectTitle": "Project-local hooks",
"projectLead": "Repositories can ship policy in <workspace>/.codewhale/hooks.toml. Because project hooks are executable shell configuration, Codewhale loads them only after the workspace is trusted through the trust prompt or a trust_level = \"trusted\" entry in user-owned config — session /trust on and legacy .deepseek/trusted markers do not enable project hooks by themselves. Once trusted, project hooks are appended after the global hooks from config.toml, so they run last and win updatedInput ties. A malformed trusted project file logs a warning and startup falls back to global hooks only.",
"sourceNote": "Source documents: docs/rfcs/1364-hooks-lifecycle.md (design RFC), docs/CONFIGURATION.md (configuration schema) · Update docs-map.ts when changing."
},
"docs-troubleshooting": {
"metaTitle": "Troubleshooting · Codewhale Docs",
"metaDescription": "Quick triage for common issues: hung turns, the offline queue, crash recovery, schema errors, MCP failures, and Docker notes.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Troubleshooting",
"overviewLead": "Start with quick triage: confirm the binary and config (codewhale --version, ~/.codewhale/config.toml), enable verbose logs with RUST_LOG=deepseek_cli=debug when needed (RUST_LOG=deepseek_cli::client=debug for HTTP retries/reconnects), and capture the current state of ~/.codewhale/sessions and ~/.codewhale/tasks.",
"incidents": [
[
"Turn hangs or the stream stops",
"If a foreground shell command is still running, press Ctrl+B to move it to the background (the turn keeps running and the command becomes a background job under /jobs); use Esc or Ctrl+C to cancel the turn itself. Inspect deepseek_cli::client retry logs and endpoint connectivity, and after a restart confirm the previously in-flight turn shows as interrupted rather than running."
],
[
"Network outage / offline behavior",
"New prompts queue while offline, persisted to ~/.codewhale/sessions/checkpoints/offline_queue.json. Inspect with /queue list, restore connectivity, then re-send queued entries (/queue edit <n> plus Enter, or the normal input flow); the queue file clears when the queue empties."
],
[
"Crash recovery",
"The checkpoint lives at ~/.codewhale/sessions/checkpoints/latest.json; startup begins a fresh session unless --resume/--continue is supplied. Resume explicitly with codewhale --resume <id> or Ctrl+R in the TUI; if the checkpoint schema is newer than the binary supports, upgrade the binary or remove the stale checkpoint."
],
[
"Persistent state schema errors",
"Errors like schema vX is newer than supported vY affect sessions, runtime thread/turn/item records, and tasks. Confirm the binary version, back up the state directory before editing, then either run a newer compatible binary or archive the incompatible records and regenerate state."
],
[
"MCP / tool execution failures",
"Validate the ~/.codewhale/mcp.json schema and server command paths, confirm the server process starts manually, and check sandbox denials in TUI history/logs. Use /mcp validate for diagnostics, temporarily disable a failing server to isolate the issue, and re-enable after verification."
]
],
"dockerTitle": "Docker notes",
"dockerLead": "Each release publishes a multi-arch Linux image to GitHub Container Registry. The default image is a conservative runtime image: it runs as the non-root codewhale user (UID/GID 1000:1000), grants no passwordless sudo, and keeps user state in a volume mounted at /home/codewhale/.codewhale. Pin a release tag instead of latest for reproducible installs.",
"dockerToolboxNote": "When a project needs apt-get, compiler toolchains, or package managers inside the container, do not change the default image contract — build an explicit toolbox image from docs/examples/Dockerfile.toolbox, and use one named state volume per project so sessions, config, and the offline queue do not bleed across workspaces. Never bake API keys or SSH private keys into custom images.",
"sourceNote": "Source documents: docs/OPERATIONS_RUNBOOK.md, docs/DOCKER.md · Update docs-map.ts when changing."
},
"docs-configuration": {
"metaTitle": "Configuration · Codewhale Docs",
"metaDescription": "Where config.toml is read from, the per-project overlay, credential precedence, and legacy path migration.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Configuration",
"overviewLead": "Codewhale reads its configuration from ~/.codewhale/config.toml (the legacy ~/.deepseek/config.toml is still read as a fallback). The --config flag and the CODEWHALE_CONFIG_PATH environment variable can point elsewhere; --config wins when both are set, and environment variable overrides are applied after the file is loaded.",
"auditLead": "Inside the TUI, {auditCommand} shows which documented keys can change in the current session, which can also be persisted, and which stay file-only or restart-only — treat its “Command / reason” column as the source of truth before editing by hand.",
"overlayTitle": "Per-project overlay",
"overlayLead": "When a workspace contains a regular-file <workspace>/.codewhale/config.toml, the safe values it declares are merged on top of the global config (legacy <workspace>/.deepseek/config.toml files are still read when the Codewhale path is absent; symlinked project configs are rejected). This lets a repository suggest a model or tighten the local safety posture without touching the user's global config. Pass --no-project-config to skip the overlay for one launch.",
"overlayLimits": "The overlay is intentionally narrow: it supports model, reasoning_effort, approval_policy and sandbox_mode (tightening values only), notes_path, max_subagents (clamped to 1..=20), and allow_shell (false applies, true is ignored). Credentials, endpoints, provider selection, MCP config, hooks, skills, and instructions = [...] stay user-global — a repo-local config.toml that declares api_key, base_url, or provider is ignored, so a cloned repository cannot pick arbitrary local files into the prompt.",
"credentialsTitle": "Credential lookup",
"credentialsLead": "After any explicit {apiKey}, credentials resolve in config → keyring → env order. {authStatus} inspects the active provider's config file, OS keyring backend, environment variable, winning source, and last-four label without printing the key itself. Hosted, generic OpenAI-compatible, self-hosted, or native Anthropic routes are selected with {providerConfig} or {providerFlag}; the full registry lives on the Models & providers page and in docs/PROVIDERS.md.",
"legacyTitle": "Legacy .deepseek/ paths",
"legacyLead": "Codewhale was renamed from DeepSeek-TUI. To avoid breaking existing installs, the runtime reads state from the new ~/.codewhale/ location but falls back to ~/.deepseek/ when only the legacy directory exists, and always writes to ~/.codewhale/ — read-with-fallback, write-to-new. State-dir resolution is consolidated in resolve_state_dir / ensure_state_dir in crates/config/src/lib.rs, and every legacy path reference carries an audited keep decision.",
"sourceNote": "Source documents: docs/CONFIGURATION.md, docs/LEGACY_PATHS.md · Update docs-map.ts when changing."
},
"docs-constitution": {
"metaTitle": "Constitution and /constitution · Codewhale Docs",
"metaDescription": "User-global constitution, repo-local law, project instructions, and runtime boundaries.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Constitution and /constitution",
"overviewTitleAside": "宪章与 /constitution",
"overviewLead": "Codewhale gives the agent an accountable address, then a legal system for context conflicts. {constitutionCommand} is the primary personal constitution surface: guided setup stores structured user-global data in {homeConfig} and renders it as model-facing prose. Repos can still add local law via {repoConfig}; runtime policy separately encodes modes, approval, sandbox, cost, and tool boundaries.",
"principles": [
[
"userGlobal",
"Use /constitution for standing personal law across projects. It is structured data rendered to prose, not a raw prompt editor."
],
[
"repoLocal",
".codewhale/constitution.json is optional project policy for protected invariants, branch rules, verification, and escalation."
],
[
"runtime",
"Constitution text may express preferences, but approval, sandbox, shell, network, trust, and MCP permissions remain enforced config."
]
],
"authorityNote": "Standard project instructions still live in AGENTS.md; memory and handoffs rank below constitutions and project instructions; the full base-prompt Markdown override is an expert escape hatch, not the normal setup path. See {configDocs}.",
"configDocsLabel": "configuration docs",
"sourceNote": "Source document: docs/ARCHITECTURE.md · Update docs-map.ts when changing."
},
"docs-fleet": {
"metaTitle": "Fleet & Workflow · Codewhale Docs",
"metaDescription": "The durable Agent roster and member-selection layer, plus the optional Workflow orchestration overlay.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Fleet & Workflow",
"overviewLead": "Fleet is the durable roster: who is available and which member is selected. It is not an execution or authority engine. Runtime launches and tracks the selected member as a headless codewhale exec run, owns retry and remote placement, and writes the durable receipts and ledger projection.",
"runTitle": "Run a fleet",
"runLead": "The Runtime's Fleet-run projection lives in the workspace's .codewhale/fleet.jsonl ledger, with worker logs under .codewhale/fleet/. codewhale fleet resume <run-id> asks Runtime to replay the ledger and reconcile stale leases; it is idempotent after a manager exit, laptop sleep, or runtime restart.",
"statusLead": "Two similarly named status surfaces exist: in the TUI, {fleetStatusTui} (or {subagents}) shows the sub-agents attached to the current interactive session; in a shell, {fleetStatusShell} reads the durable Fleet ledger.",
"profilesTitle": "Roles and /fleet setup",
"profilesLead": "/fleet setup opens a progressive wizard for authoring a reusable roster member: one focused choice at a time — semantic role, model (inherit or a concrete configured route), thinking tier, then an exact identity/route review before save. Profiles live in project scope (.codewhale/agents/<role>.toml) or personal scope ($CODEWHALE_HOME/agents/<role>.toml); a same-id project profile wins. Runtime separately owns trust, filesystem/network reach, secrets, approvals, sandboxing, and tools, so profile storage scope never widens execution authority.",
"workflowTitle": "Workflow orchestration",
"workflowLead": "Ordinary multi-agent work does not need Workflow: send normal messages in Operate and let Codewhale prefer background workers when parallelism, isolation, or duration makes delegation useful. Use Workflow when ordered phases, gates, shared budgets, replay, or deterministic fan-in matter. A Workflow script coordinates only: it selects Fleet members but has no filesystem or shell; Runtime launches the real workers under live authority policy. Scripts use a declarative compile-only JS subset that lowers to a typed WorkflowSpec validated and executed by Rust; import, fetch, process, eval, and async/await are rejected.",
"workflowLimits": "Default validation bounds: up to 1,000 worker agents per Workflow run, Workflow IR structural nesting no deeper than 5, loops must declare max_iterations, and dynamic expand nodes must declare max_children plus a template. Runtime child delegation is a separate execution budget: it defaults to 3 levels and has an opt-in hard ceiling of 8. These are population and shape limits, not launch concurrency: Runtime admits at most 16 live workers for one run and queues the rest. Omitted or zero max_steps stays unbounded; only a positive value adds a model-turn ceiling.",
"sourceNote": "Source documents: docs/FLEET.md, docs/WORKFLOW_AUTHORING.md · Update docs-map.ts when changing."
},
"docs-mcp": {
"metaTitle": "MCP · Codewhale Docs",
"metaDescription": "Consume external tool servers over the Model Context Protocol, or expose Codewhale itself as an MCP server.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewLead": "Codewhale can load additional tools via MCP (Model Context Protocol). MCP servers can be local stdio processes that the TUI starts, or remote URL-based servers that speak Streamable HTTP with legacy SSE fallback. A successfully connected server registers its tools into the model catalog; a failed or disabled server is never presented as an available tool.",
"overviewConfig": "The config file defaults to {configPath} (the legacy {legacyConfigPath} is still read when the Codewhale file is absent), overridable with {configPathOption} or {configEnvVar}. The {serversKey} key used by other clients is accepted too.",
"setupTitle": "Setup and management",
"setupLead": "Bootstrap a starter config with {initCommand}; inside the TUI, {mcpCommand} opens a compact manager showing each server's enabled state, transport, command or URL, timeouts, and connection errors. Common commands:",
"setupReload": "Config edits made from the TUI are written immediately, but the model-visible MCP tool pool is not hot-reloaded — the manager marks it restart-required. /mcp validate and /mcp reload reconnect to refresh the on-screen snapshot.",
"authTitle": "Remote authentication",
"authLead": "URL-based servers can use static headers, env-derived env_headers, bearer_token_env_var, or OAuth. Precedence is conservative: headers and env_headers apply first; bearer_token_env_var adds an Authorization header only when one is not already set; OAuth login tokens likewise never override an explicit header. Avoid committing literal Authorization headers — prefer env_headers, bearer_token_env_var, or OAuth login so secrets stay outside the MCP file.",
"toolsTitle": "Tool naming and safety",
"toolsLead": "Discovered MCP tools are exposed to the model as {toolNamePattern} — a server named {gitServer} with a {statusTool} tool becomes {gitStatusTool}. MCP tools flow through the same approval framework as built-in tools: read-only MCP helpers can run without prompts when policy permits, side-effectful MCP tools require approval, and Full Access does not bypass hard policy holds.",
"toolsTrust": "Only configure MCP servers you trust, and treat MCP server configuration as equivalent to running code on your machine. Reviewed local plugin bundles can also contribute MCP servers: they reuse the same MCP manager, approval, and network-policy paths, appear under namespaced <plugin>-<server> identities, and are held to a stricter boundary than hand-written mcp.json.",
"serverTitle": "Codewhale as an MCP server",
"serverLead": "{serveMcp} runs Codewhale as an stdio MCP server so other sessions (or any MCP client) can call its tools; {mcpServerCommand} is the equivalent dispatcher entrypoint. {addSelfCommand} resolves the current binary path and writes the server into your MCP config. Keep the modes distinct: {serveHttp} is the runtime HTTP/SSE API, a separate surface.",
"sourceNote": "Source document: docs/MCP.md · Update docs-map.ts when changing."
},
"docs-modes": {
"metaTitle": "Modes · Codewhale Docs",
"metaDescription": "Plan, Work, Operate modes and independent permission postures.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Modes",
"overviewLead": "A mode decides how Codewhale handles the work. A permission posture decides how it handles consequential tool calls. They are separate controls.",
"modes": [
[
"Plan",
"Read-only investigation and planning. Codewhale can inspect the workspace, but it cannot run shell commands or edit files."
],
[
"Act",
"Normal interactive coding. Codewhale can inspect, edit, and use tools; shell availability and approval prompts follow the active configuration and permission posture."
],
[
"Operate",
"Multitask coordination from the same composer. The parent can inspect, edit, and use shell or MCP tools under the same permission posture, sandbox, and safety rules as Act. Fleet workers are preferred for independent, parallel, background, or long-running work, but delegation is not required for every executable step. Workflow is optional unless the work needs ordered phases, gates, or deterministic fan-in."
]
],
"switchingTitle": "Switch modes",
"switchingLead": "When the composer is idle, press {tab} to cycle Plan → Act → Operate. When a completion menu is open, Tab accepts the completion; during an active turn, it can queue the current draft as the next follow-up.",
"switchingCommandLead": "Run /mode to open the picker, or switch directly:",
"permissionsTitle": "Permission postures",
"permissionsLead": "Plan is always Read Only. When the composer is idle in Act or Operate, press {shiftTab} to cycle Ask → Auto-Review → Full Access. Run {configCommand} to inspect or edit the current session permission; project or managed policy may lock or tighten it.",
"postures": [
[
"Ask",
"Ask before tools that can make consequential changes."
],
[
"Auto-Review",
"Review tool risk automatically and ask when a decision needs you."
],
[
"Full Access",
"Run tools without approval prompts and enable trusted-workspace access. Repository rules and managed constraints still apply; use it only in a workspace you trust."
]
],
"sourceNote": "Source document: docs/MODES.md · Update docs-map.ts when changing."
},
"docs-runtime-api": {
"metaTitle": "Runtime API · Codewhale Docs",
"metaDescription": "Local HTTP/SSE, JSON-RPC stdio, and ACP entrypoints for integrations, bridges, and automation.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Runtime API",
"overviewLead": "codewhale app-server is the canonical local runtime API and control plane. Local SDKs, mobile/remote-control clients, and editor integrations talk to it instead of screen-scraping terminal output. The engine runs as a local-only process: every API binds to localhost by default — no hosted relay, no provider-token custody, no secret leakage. codewhale serve --http / --mobile remain compatibility aliases for app-server --http / --mobile and launch the identical server; new integrations should target app-server.",
"entries": [
[
"http",
"The full /v1/* HTTP/SSE runtime API (canonical entry), default 127.0.0.1:7878."
],
[
"mobile",
"The runtime API plus the /mobile phone control page."
],
[
"stdio",
"Newline-delimited JSON-RPC 2.0 control transport with no listener, for local SDKs and probes."
],
[
"web",
"The loopback-only browser client, embedded in the binary and opened in the default browser."
],
[
"doctor",
"Machine-readable health and capability report."
],
[
"acp",
"ACP (Agent Client Protocol) stdio adapter for editors such as Zed."
],
[
"exec",
"The one-shot headless worker (stream-json, fleet subprocess, CI primitive) — not part of this API, but it shares the same runtime and event vocabulary."
]
],
"stdioTitle": "Probe without model tokens",
"stdioLead": "The stdio control transport can be probed without spending model tokens. capabilities returns the advertised method families (thread/*, app/*, prompt/*) and the full method list; the method set is pinned by a drift test in crates/app-server/src/lib.rs, so SDK and local integration clients can rely on it not changing silently.",
"interruptNote": "A live turn can be asked to stop with thread/interrupt (or POST /v1/threads/{id}/turns/{turn_id}/interrupt over HTTP); when no turn is streaming the reply carries interrupted: false — not an error, just nothing to stop.",
"securityTitle": "Security boundary",
"securityLead": "The runtime API token is read from {authToken}, then {runtimeTokenEnv}, then {legacyTokenEnv}; {insecureFlag} is only accepted with a loopback bind. Cross-origin browser requests are rejected by the CORS allow-list. Before selecting a non-loopback bind — especially {mobileFlag} — read the full deployment and authentication contract in docs/RUNTIME_API.md.",
"sourceNote": "Source document: docs/RUNTIME_API.md · Update docs-map.ts when changing."
},
"docs-sandbox": {
"metaTitle": "Sandbox & Approval · Codewhale Docs",
"metaDescription": "The honest boundary: macOS Seatbelt, opt-in Linux bubblewrap, platform gaps, and approval policy.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Sandbox & Approval",
"overviewLead": "Codewhale can launch shell commands proposed by a model. Approval policy, workspace-aware tools, and an operating-system command wrapper are separate controls: an approval is not a sandbox, and selecting workspace-write does not prove the current platform has an OS wrapper available. This page describes only behavior wired into the command execution path.",
"platforms": [
[
"macOS · Seatbelt",
"Codewhale probes /usr/bin/sandbox-exec; when the probe succeeds and the policy requests a sandbox, the child command is wrapped in a generated Seatbelt profile: broad filesystem reads, policy-limited writes, and network only when the policy enables it. A failed probe is reported honestly as no OS sandbox."
],
[
"Linux · opt-in bubblewrap",
"Linux command sandboxing is opt-in: set prefer_bwrap = true and keep /usr/bin/bwrap executable. The child gets a read-only root view with writable mounts derived from the resolved policy; the network namespace is isolated by default and --share-net is added only when the policy enables network access. Without the opt-in, Codewhale reports none."
],
[
"Windows · no OS sandbox",
"The Windows command path currently reports no OS sandbox. Host permissions and approval policy still apply, but they are not a Codewhale OS command sandbox."
],
[
"External OpenSandbox execution",
"With sandbox_backend = \"opensandbox\", shell execution is sent to the configured OpenSandbox-compatible HTTP endpoint instead of starting a local child. Isolation guarantees belong to the configured service and its operator."
]
],
"policiesTitle": "Policies and fallbacks",
"policiesLead": "The local {sandboxMode} values are {readOnly}, {workspaceWrite}, {dangerFullAccess}, and {externalSandbox}. The first two are enforced by Seatbelt or bubblewrap only when that wrapper is selected and available; {dangerFullAccess} deliberately bypasses the local OS wrapper; {externalSandbox} declares that execution is already externally isolated. When no wrapper is selected, the shell command runs without Codewhale OS isolation — approval rules and workspace-aware native file tools remain separate controls.",
"diagnosticsTitle": "Diagnostics and limits",
"diagnosticsLead": "codewhale setup --status, codewhale doctor, codewhale doctor --json, and the diagnostics tool report the locally available wrapper after applying the resolved bubblewrap preference. Denial attribution is intentionally conservative: a child command's generic Permission denied is not by itself proof that Codewhale's sandbox blocked it, and unsandboxed command failures are never labeled sandbox denials.",
"diagnosticsLimits": "The limitations are stated just as plainly: availability is checked before launch, yet the selected wrapper can still fail because of host policy, container restrictions, or a race after the probe; bubblewrap ignores a configured writable root that is missing or not a directory; and no sandbox protects against kernel vulnerabilities or all resource-exhaustion and side-channel attacks.",
"sourceNote": "Source document: docs/SANDBOX.md · Update docs-map.ts when changing."
},
"docs-subagents": {
"metaTitle": "Sub-Agents · Codewhale Docs",
"metaDescription": "The agent tool, Fleet roles, context forking, worktree isolation, and concurrency caps.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Sub-Agents",
"overviewLead": "A parent session launches one focused sub-agent through the agent tool and immediately gets back an agent_id, a compact receipt, and a transcript handle while the worker runs in the background. Sub-agents inherit the parent's tool registry by default, but they are leaf workers: they do not receive agent or nested lifecycle tools. agent launches detached background work — cancelling the parent turn stops the parent's wait path, but it does not kill already-opened child runs.",
"overviewFleetNote": "For work that must survive process restarts, sleep, or remote execution, prefer Fleet or a Workflow-backed fleet run over a short in-session agent call.",
"roles": [
[
"worker",
"Flexible multi-step execution of the parent's brief; writes and shell allowed. The default role."
],
[
"scout",
"Read-only, maps the relevant code fast — “find every call site of Foo.”"
],
[
"planner",
"Analyse and produce a strategy without executing — “design the migration; don't run it.”"
],
[
"reviewer",
"Read-and-grade with severity scores — “audit this PR for bugs.”"
],
[
"builder",
"Land a specific change with minimal edits; writes and shell allowed."
],
[
"verifier",
"Run tests and validation gates and report the outcome; no code edits."
],
[
"consultant",
"Read-only high-reasoning counsel for judgement calls and design critique."
],
[
"custom",
"An explicit narrow tool allowlist for locked-down dispatch."
]
],
"forkTitle": "Context forking",
"forkLead": "{agentTool} starts fresh by default: the child gets its role prompt plus the task you pass. When the task depends on decisions, files, todos, or plan state already in the parent transcript, use {forkContext} — the runtime keeps the parent's request prefix byte-identical where available (preserving prefix-cache reuse), appends a structured state snapshot, then adds the sub-agent role instructions and task at the tail. Use fresh sessions for independent exploration and forked sessions for continuation, review, summarization, or compaction work.",
"worktreeTitle": "Worktree isolation",
"worktreeLead": "Launch parallel edit lanes with {worktreeFlag}: Codewhale creates a fresh git worktree and branch for the child (default {branchPattern}, checked out beside the parent repo under {worktreeDir}) so the parent checkout stays clean. Isolation is not write authority: a prompt-only worker starts read-only, and a writer also declares {writeAuthority} plus at least one normalized {writeRoots}, {exactFiles}, or {coordinationContracts} value. Overlapping shared write claims fail before any mutation.",
"capacityTitle": "Concurrency caps",
"capacityLead": "The sub-agent capacity source of truth is crates/tui/src/config/subagent_limits.rs: default configured concurrency is 64, maximum configured concurrency is 128, and maximum admitted running-plus-queued work is 1024. These are capacity ceilings, not advice to dispatch every slot — a manager should use the smallest useful fan-out, keep a single fan-in owner, and verify worker receipts before reporting combined completion.",
"sourceNote": "Source document: docs/SUBAGENTS.md · Update docs-map.ts when changing."
},
"docs-web": {
"metaTitle": "Browser Client · Codewhale Docs",
"metaDescription": "The loopback-only embedded browser client: one-time bootstrap, session cookie, and the local trust boundary.",
"bodyClassName": "text-ink-soft leading-relaxed",
"overviewTitle": "Browser Client",
"overviewLead": "{webCommand} opens Codewhale's embedded browser client over the canonical Runtime API. It is a local surface: the server always binds to {loopbackHost}, cannot be rebound to a LAN address, and cannot run with Runtime authentication disabled. The default address is {defaultUrl}; on a port collision, pick another loopback port with {portExample}. Stop the process with Ctrl+C and the browser session ends with it.",
"overviewBody": "The current client provides a responsive thread and search rail, Runtime-owned session facts, transcript and tool receipts, and a composer. It can create, select, rename, and archive threads; start or steer turns; interrupt work; resolve approvals; and answer Runtime user-input requests. The browser is another view of the same local Runtime — it does not create a second cloud account, copy provider credentials into browser storage, or weaken the configured approval and sandbox policies.",
"authTitle": "Authentication boundary",
"authLead": "The browser-launch URL carries a random, short-lived, one-time bootstrap capability — never the Runtime bearer token. A loopback request exchanges it for an HttpOnly, SameSite=Strict, process-local session cookie and immediately invalidates the capability. Reused, expired, malformed, and non-loopback bootstrap attempts fail closed. The Runtime token is never placed in rendered HTML, browser storage, URL queries or fragments, or browser-launch arguments. Cookie-authenticated state-changing requests must also present the exact local web origin; cross-origin browser requests are rejected.",
"localTitle": "Local means local",
"localLead": "{webCommand} accepts only {portFlag} — there is no {hostFlag} and no insecure-auth option on this command. Do not treat it as a public website or expose its port through router forwarding, a public reverse proxy, or a tunnel. The separate {mobileCommand} and {httpFlag} modes carry different deployment and authentication contracts; read the Runtime API documentation before operating either one, especially before selecting a non-loopback bind.",
"troubleshootingTitle": "Troubleshooting",
"troubleshootingLead": "If port 7878 is occupied, pass an unused --port. If the browser cannot be opened, the command exits with an error rather than leaving a reusable bootstrap capability behind; check the OS default-browser setup and start again. If the page loads but a provider is unavailable, inspect codewhale doctor and /provider — the web command does not configure or move provider credentials. If a session expired, restart codewhale web to mint a new process-local session; reusing an old bootstrap URL is expected to fail.",
"sourceNote": "Source document: docs/WEB.md · Update docs-map.ts when changing."
}
}

504
web/gt-catalog/zh.json Normal file
View File

@@ -0,0 +1,504 @@
{
"chrome": {
"navDocs": "文档",
"navStart": "指引",
"navInstall": "安装",
"navFaq": "常见问题",
"navCommunity": "社区",
"navContribute": "贡献",
"navDocsSecondary": "Docs",
"navStartSecondary": "Start",
"navInstallSecondary": "Install",
"navFaqSecondary": "FAQ",
"navCommunitySecondary": "Community",
"navContributeSecondary": "Contribute",
"skipToContent": "跳转到主要内容",
"navPrimaryAria": "主导航",
"navHomeAria": "Codewhale 首页",
"installCta": "安装 →",
"authSignIn": "登录",
"authRegister": "注册",
"authGroupAria": "账户",
"wordmarkSeal": "深",
"wordmarkTag": "任意模型,本机运行",
"issueLabel": "第 {date} 期",
"dateLocale": "zh-CN",
"starsAria": "GitHub 星标数",
"githubFallback": "GitHub",
"tickerLiveLabel": "实 时",
"tickerLiveTag": "LIVE",
"tickerMerged": "已合并",
"tickerOpened": "已开启",
"tickerClosed": "已关闭",
"tickerReleased": "已发布",
"tickerFirstContribution": "首次贡献",
"tickerBy": "作者 {handle}",
"tickerAria": "仓库近期动态",
"traceLabel": "推理痕迹",
"traceTabsAria": "会话片段",
"menuOpen": "打开菜单",
"menuClose": "关闭菜单",
"themeAuto": "自动",
"themeLight": "浅色",
"themeDark": "深色",
"themeAria": "文档主题:{mode}(点击切换)",
"themeTitle": "文档主题 · 自动 / 浅色 / 深色",
"footerTagline": "Codewhale 潜入深海,你不必亲自下潜——开源运行时的文档、源码与社区。",
"footerProduct": "产品",
"footerProject": "项目",
"footerDocs": "文档",
"footerGuide": "新手指引",
"footerInstall": "安装",
"footerModels": "模型",
"footerRuntime": "运行时",
"footerFaq": "常见问题",
"footerIssues": "议题",
"footerContribute": "参与贡献",
"footerLicense": "MIT 许可证",
"footerCanonicalSource": "官方源码:",
"footerReleases": " · 发布:",
"footerReleasesLink": "GitHub 发布页",
"footerSecurity": "安全",
"switcherLabel": "语言",
"switcherSwitchTo": "切换到 {label}",
"partialBadge": "(部分)"
},
"home": {
"metaTitle": "Codewhale — 一入码门深似海,它替你潜。",
"metaDescription": "Codewhale 潜入深海你不必亲自下潜——开源的终端编程智能体。模型自带跑在你自己的机器上。Rust 编写MIT 许可。",
"kicker": "开源 · 自带模型 · 运行在你的终端",
"heroTitleA": "一入码门深似海,",
"heroTitleB": "Codewhale 替你潜。",
"heroIntro": "{brand} 是一个跑在终端里的开源编程智能体。给它一个模型和一个任务。它会读你的代码、改文件、跑检查,活干完了或需要你拿主意时就停下来。模型随便用,也可以给每个角色各配一个。",
"install": "安装",
"docs": "文档",
"copy": "复制",
"copied": "已复制 ✓",
"installEyebrow": "一行安装",
"installRequirement": "需要 Node 18+,无需 Rust 工具链",
"installOtherWays": "其他方式 →",
"latestRelease": "最新发布 {tag}",
"releaseUnavailable": "发布状态暂不可用",
"currentSource": "源码",
"sourceCandidate": "未发布",
"providerRoutes": "{count} 个提供商",
"publishedRelease": "已发布",
"figcaptionSourceCandidate": "未发布",
"shotSession": "会话",
"screenshotAlt": "Codewhale 终端会话Operate 模式:鲸鱼、输入区与状态栏",
"figcaption": "Codewhale 会话 · Operate 模式 · 权限Ask",
"proofHeading": "终端里的编程智能体。任意模型。本机运行。",
"proofBody": "用你手头已有的模型——托管、网关或本地都行。选一个模式Plan、Work 或 Operate。再选它不问你就能做多少Ask、Auto-Review 或 Full Access。",
"sealDecides": "法",
"decidesEyebrow": "它如何决策",
"decidesHeading": "推理过程,原话呈现",
"decidesLede": "会话摘录。每一段都写明模型依据了哪条项目规则,以及接着做了什么。",
"sealWorkflow": "行",
"workflowHeading": "从任务到验证过的改动。",
"workflow": [
[
"检查",
"读取仓库、项目说明与任务。"
],
[
"执行",
"修改文件,你要求先问的地方会先问。"
],
[
"验证",
"运行检查,核对结果。"
],
[
"报告",
"说明改了什么、通过了什么。"
]
],
"receiptAria": "运行摘要示例",
"receiptInspect": "仓库与项目说明",
"receiptAct": "在你设定的权限内修改文件",
"receiptReport": "检查通过 · 摘要已保存",
"sealStart": "起",
"startHeading": "第一次用?四步。",
"startLede": "安装 → 首次会话,无需密钥 → 接入提供商 → 配置 Fleet。",
"startGuideLink": "阅读新手指引 →",
"startVocabularyLink": "查名词 →",
"sealBoundaries": "界",
"boundariesHeadingA": "你的模型。",
"boundariesHeadingB": "你的边界。",
"boundariesBody": "模型、模式、以及它不问你能做多少,都由你来选。你不改,提供商和模型就不会变。预览功能会标注预览。",
"hostedGatewayLocal": "托管、网关与本地模型",
"planActOperateDesc": "从只读规划到自主执行",
"askAutoReviewDesc": "它在问你之前能做多少",
"tuiExecWebDesc": "交互式或脚本化",
"sealSurfaces": "面",
"surfacesHeading": "活在哪里干,就在哪里用。",
"surfaces": [
[
"TUI",
"交互式终端工作"
],
[
"codewhale exec",
"脚本与 CI"
],
[
"Web 客户端",
"浏览器客户端,仅限本机"
],
[
"运行时 API + MCP",
"本地集成"
],
[
"Fleet",
"多个智能体协作一件事"
]
],
"runtimeLink": "运行时界面与稳定程度 →",
"installBandHeading": "从一条命令开始。",
"binaries": "预编译包",
"chinaMirrors": "中国镜像",
"installGuideLink": "阅读安装指南 →",
"sealCommunity": "众",
"communityHeading": "公开构建",
"communityBody": "MIT 许可。贡献者的工作覆盖运行时、提供商、平台、文档与测试。",
"communityLinksAria": "社区链接",
"contribute": "参与贡献"
},
"docs-guide": {
"metaTitle": "新手指引 · Codewhale 文档",
"metaDescription": "从安装到配置理想 Fleet 的完整路径:安装、无需密钥的首次会话、连接提供商、设置 Fleet。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "新手指引",
"overviewLead": "从一条安装命令到配置好你的 Fleet四步走完。",
"sessionTitle": "看一次真实会话",
"sessionLead": "这里将放一段真实会话的录像。目前还没有录制,所以什么也不显示。",
"nextTitle": "接下来",
"sourceNote": "来源文档docs/GUIDE.md, docs/KEYBINDINGS.md · 步骤文案来自 web/lib/content/getting-started.ts更新时请同步修改 docs-map.ts。"
},
"docs-shell": {
"metaTitle": "文档 · Codewhale",
"metaDescription": "Codewhale 文档安装、使用指南、配置、提供商、核心概念、工具、MCP、技能、沙箱、运行时 API、排障。",
"portalMark": "Codewhale 文档",
"heroTitle": "查找准确的使用说明。",
"heroLead": "从新手指引和安装开始或直接查看名词、模式、权限、工具、提供商、Fleet、钩子、MCP 与运行时 API。每页都链接到仓库中的源文档。",
"installCta": "安装 Codewhale",
"sourceDocsCta": "浏览源文档 ↗"
},
"docs-hooks": {
"metaTitle": "钩子 · Codewhale 文档",
"metaDescription": "已发布的生命周期钩子:可变 message_submit、tool_call_before 决策、turn_end 与子 Agent 观察事件。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "钩子",
"overviewLead": "钩子让你把自己的命令挂进 Codewhale 的生命周期:在消息提交前注入上下文、在工具调用前执行策略、在回合结束或子 Agent 启停时做审计。本页描述当前已发布的行为docs/rfcs/1364-hooks-lifecycle.md 是这组能力的设计 RFC完整配置 schema 见 docs/CONFIGURATION.md。",
"configIntro": "钩子配置在 config.toml 的 {hooksTable} 条目下TUI 里运行 {hooksCommand} 可以按事件分组查看每个钩子的名称、命令预览、超时和条件,以及 {enabledKey} 的全局开关状态。",
"events": [
[
"message_submit可变",
"在用户消息进入历史或发给模型之前运行。钩子从 stdin 收到 JSONexit 0 且 stdout 打印含非空 text 字段的 JSON 时替换提交文本exit 2 在回合开始前阻止提交。多个钩子按配置顺序串行执行,每个钩子收到上一个钩子的输出文本。标记 background = true 的钩子只能观察,不能改写或阻止。"
],
[
"tool_call_before决策",
"在每次工具调用执行前运行。除 exit 2 硬拒绝(始终生效)外,前台钩子可在 exit 0 时用 stdout JSON 给出决策allow / deny / ask并可附带 updatedInput 改写工具输入、additionalContext 追加进给模型的工具结果。多个钩子命中时优先级为 deny > ask > allowtool_name 条件支持 * 通配(如 mcp__* 匹配所有 MCP 工具。Full Access 不打开工具审批提示,因此 ask 不会降低该姿态。"
],
[
"turn_end观察",
"在每个模型回合结束后触发此时用量、成本、通知、收据和队列恢复状态都已更新。stdin 收到包含 status、duration_ms、usage、totals、queued_message_count 等字段的 JSON。stdout 被忽略,失败只记警告——不能阻止输入、改写 transcript 或改变下一个排队消息。"
],
[
"subagent_spawn / subagent_complete观察",
"观察子 Agent 的启动与完成stdin 收到有界的 JSON 元数据agent_id、状态、截断后的 prompt/result 预览)。失败只记警告,不阻塞调度、不改 prompt 或结果;需要完整细节时使用 agent 返回的 transcript 句柄。"
]
],
"projectTitle": "项目级钩子",
"projectLead": "仓库可以在 <workspace>/.codewhale/hooks.toml 中携带策略。因为项目钩子是可执行的 shell 配置Codewhale 只有在工作区通过信任提示或用户配置中的 trust_level = \"trusted\" 被信任后才加载它们——会话内的 /trust on 和旧版 .deepseek/trusted 标记都不会单独启用项目钩子。受信任后,项目钩子追加在 config.toml 的全局钩子之后运行,因此对 updatedInput 而言最后生效。格式错误但已受信任的项目文件会记警告并回退到只用全局钩子。",
"sourceNote": "来源文档docs/rfcs/1364-hooks-lifecycle.md设计 RFC, docs/CONFIGURATION.md配置 schema· 更新时请同步修改 docs-map.ts。"
},
"docs-troubleshooting": {
"metaTitle": "排障 · Codewhale 文档",
"metaDescription": "常见问题的快速分诊挂起的回合、离线队列、崩溃恢复、schema 错误、MCP 故障与 Docker 说明。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "排障",
"overviewLead": "先快速分诊确认二进制与配置codewhale --version、~/.codewhale/config.toml需要更详细日志时用 RUST_LOG=deepseek_cli=debug 启动HTTP 重试/重连用 RUST_LOG=deepseek_cli::client=debug并看一眼 ~/.codewhale/sessions 与 ~/.codewhale/tasks 的当前状态。",
"incidents": [
[
"回合挂起或流停止",
"前台 shell 命令还在跑时按 Ctrl+B 把它移到后台(回合继续,命令变成 /jobs 下的后台任务);想取消回合本身用 Esc 或 Ctrl+C。检查 deepseek_cli::client 的重试日志和端点连通性,重启后确认此前在途的回合被标记为中断,而不是停在运行态。"
],
[
"网络中断 / 离线行为",
"离线时新提示词会排队,队列持久化在 ~/.codewhale/sessions/checkpoints/offline_queue.json。用 /queue list 查看,恢复连接后重新发送(/queue edit <n> 加回车,或走正常输入流程),队列清空后文件随之清除。"
],
[
"崩溃恢复",
"检查点保存在 ~/.codewhale/sessions/checkpoints/latest.json除非传入 --resume/--continue启动会开新会话。用 codewhale --resume <id> 或 TUI 里的 Ctrl+R 显式恢复;若检查点 schema 比二进制新,升级二进制或移除过期检查点。"
],
[
"持久状态 schema 错误",
"形如 schema vX is newer than supported vY 的错误涉及 sessions、运行时 thread/turn/item 记录和 tasks。先确认二进制版本编辑前备份状态目录然后用更新的兼容二进制运行或归档不兼容记录并重建状态。"
],
[
"MCP / 工具执行失败",
"校验 ~/.codewhale/mcp.json 的 schema 和服务器命令路径,手动确认服务器进程能启动,并在 TUI 历史/日志中检查沙箱拒绝。用 /mcp validate 诊断,可暂时禁用出问题的服务器隔离原因,验证后再启用。"
]
],
"dockerTitle": "Docker 说明",
"dockerLead": "每个发布都会向 GitHub Container Registry 推送多架构 Linux 镜像。默认镜像是保守的运行时镜像:以非 root 的 codewhale 用户UID/GID 1000:1000运行不授予免密 sudo用户状态放在挂载到 /home/codewhale/.codewhale 的卷里。可复现的安装请固定发布标签而不是 latest。",
"dockerToolboxNote": "需要在容器内使用 apt-get、编译工具链或包管理器时不要改默认镜像约定——基于 docs/examples/Dockerfile.toolbox 构建显式的 toolbox 镜像,并为每个项目使用独立的命名状态卷,避免会话、配置和离线队列跨工作区串扰。不要把 API 密钥或 SSH 私钥烘进自定义镜像。",
"sourceNote": "来源文档docs/OPERATIONS_RUNBOOK.md, docs/DOCKER.md · 更新时请同步修改 docs-map.ts。"
},
"docs-configuration": {
"metaTitle": "配置 · Codewhale 文档",
"metaDescription": "config.toml 的查找顺序、项目级覆盖、凭据优先级和旧版路径迁移。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "配置",
"overviewLead": "Codewhale 从 ~/.codewhale/config.toml 读取配置(旧版 ~/.deepseek/config.toml 仍作为回退读取)。--config 标志和 CODEWHALE_CONFIG_PATH 环境变量可以指定别的路径,两者同时设置时 --config 优先;文件加载之后再应用环境变量覆盖。",
"auditLead": "在 TUI 里运行 {auditCommand} 可以查看哪些文档化的键能在当前会话修改、哪些能持久化、哪些只能改文件或需要重启——改动前以它输出的“Command / reason”列为准。",
"overlayTitle": "项目级覆盖",
"overlayLead": "当工作区包含常规文件 <workspace>/.codewhale/config.toml 时,其中声明的安全取值会合并到全局配置之上(旧版 <workspace>/.deepseek/config.toml 在新路径缺失时仍会读取;符号链接的项目配置会被拒绝)。这让仓库可以建议模型或收紧本地安全姿态,而不动用户的全局配置。单次启动可用 --no-project-config 跳过覆盖。",
"overlayLimits": "覆盖层有意保持狭窄:支持 model、reasoning_effort、approval_policy 与 sandbox_mode只能收紧、notes_path、max_subagents夹紧到 1..=20、allow_shellfalse 生效true 被忽略。凭据、端点、提供商选择、MCP 配置、hooks、skills 和 instructions = [...] 始终属于用户全局配置——仓库里的 config.toml 声明 api_key、base_url 或 provider 会被忽略,克隆的仓库无法借此选择任意本地文件进入提示词。",
"credentialsTitle": "凭据查找",
"credentialsLead": "在显式 {apiKey} 之后,凭据按 config → keyring → env 的顺序解析。{authStatus} 可以查看当前提供商的配置文件、系统 keyring 后端、环境变量、生效来源和末四位标签而不会打印密钥本身。托管、OpenAI 兼容、自托管或 Anthropic 原生路由用 {providerConfig} 或 {providerFlag} 选择;完整注册表见模型与提供商页和 docs/PROVIDERS.md。",
"legacyTitle": "旧版 .deepseek/ 路径",
"legacyLead": "Codewhale 由 DeepSeek-TUI 更名而来。为了不破坏既有安装,运行时从新的 ~/.codewhale/ 位置读取状态,但在只有旧目录存在时回退到 ~/.deepseek/,并且始终写入 ~/.codewhale/——读取带回退、写入新位置。状态目录解析集中在 crates/config/src/lib.rs 的 resolve_state_dir / ensure_state_dir 中,每一处旧路径引用都有审计过的保留决定。",
"sourceNote": "来源文档docs/CONFIGURATION.md, docs/LEGACY_PATHS.md · 更新时请同步修改 docs-map.ts。"
},
"docs-constitution": {
"metaTitle": "宪章与 /constitution · Codewhale 文档",
"metaDescription": "用户全局宪章、仓库本地法、项目说明和运行时边界。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "宪章与 /constitution",
"overviewTitleAside": "Constitution",
"overviewLead": "Codewhale 先给 Agent 一个可追责的地址,再给上下文冲突一套法律。{constitutionCommand} 是管理个人常驻宪章的主入口: 它把结构化的用户全局设置保存在 {homeConfig} 再渲染成模型可读的 prose block。仓库仍可通过 {repoConfig} 增加本地 lawruntime policy 独立负责模式、审批、沙箱、成本和工具边界。",
"principles": [
[
"userGlobal",
"用 /constitution 管理跨项目个人常驻法。它是结构化数据渲染成 prose不是裸 prompt 编辑器。"
],
[
"repoLocal",
".codewhale/constitution.json 是可选项目 law用于不变量、分支规则、验证和升级条件。"
],
[
"runtime",
"宪章文本可以表达偏好审批、沙箱、Shell、网络、信任和 MCP 权限仍由运行时配置强制执行。"
]
],
"authorityNote": "普通项目说明仍放在 AGENTS.md记忆和交接低于宪章与项目说明完整 base prompt Markdown 覆盖只是专家逃生口,不是普通设置路径。详见 {configDocs}。",
"configDocsLabel": "配置文档",
"sourceNote": "来源文档docs/ARCHITECTURE.md · 更新时请同步修改 docs-map.ts。"
},
"docs-fleet": {
"metaTitle": "Fleet 与 Workflow · Codewhale 文档",
"metaDescription": "持久 Agent 花名册与成员选择层,以及可选的 Workflow 编排层。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "Fleet 与 Workflow",
"overviewLead": "Fleet 是持久花名册记录有哪些成员以及选中了哪一位。它不是执行或权限引擎。Runtime 将选定成员作为无头 codewhale exec 运行来启动和跟踪,负责重试与远程放置,并写入持久收据和台账投影。",
"runTitle": "运行一次 Fleet",
"runLead": "Runtime 的 Fleet 运行投影存放在工作区的 .codewhale/fleet.jsonl 台账中worker 日志在 .codewhale/fleet/ 下。codewhale fleet resume <run-id> 会让 Runtime 重放台账并调和过期租约;该操作幂等,可在管理进程退出、笔记本睡眠或运行时重启后安全执行。",
"statusLead": "注意两个同名状态面TUI 里的 {fleetStatusTui}(或 {subagents})只显示当前交互会话的子 Agentshell 里的 {fleetStatusShell} 才读取持久 Fleet 台账。",
"profilesTitle": "角色与 /fleet setup",
"profilesLead": "/fleet setup 打开渐进式向导来编写可复用的花名册成员:依次选择语义角色、模型(继承或具体已配置路由)和思考档位,再核对准确的身份与路由后保存。档案可写在项目级(.codewhale/agents/<role>.toml或个人级$CODEWHALE_HOME/agents/<role>.toml同 ID 的项目档案优先。Runtime 另行负责信任、文件系统/网络范围、密钥、审批、沙箱和工具,因此档案存储范围不会扩大执行权限。",
"workflowTitle": "Workflow 编排",
"workflowLead": "普通多 Agent 工作不需要 Workflow在 Operate 里直接发消息,需要并行、隔离或长时间工作时让 Codewhale 优先委派后台 worker 即可。只有当工作需要有序阶段、门禁、共享预算、回放或确定性汇总时才用 Workflow。Workflow 脚本只负责协调:它选择 Fleet 成员,但没有自己的文件系统或 shellRuntime 在实时权限策略下启动真正的 worker。脚本使用编译专用的声明式 JS 子集,降低到类型化 WorkflowSpec 后由 Rust 校验与执行import、fetch、process、eval、async/await 会被拒绝。",
"workflowLimits": "默认校验边界:每次 Workflow 运行最多 1,000 个 worker Agent、最多 8 层递归 Fleet 环(默认 3 层)、循环必须声明 max_iterations、动态 expand 节点必须声明 max_children 和模板。这些是数量上限而非并发要求Runtime 每个运行最多接纳 16 个存活 worker其余排队。省略 max_steps 或设为 0 都保持无界;只有正值才增加模型轮次上限。",
"sourceNote": "来源文档docs/FLEET.md, docs/WORKFLOW_AUTHORING.md · 更新时请同步修改 docs-map.ts。"
},
"docs-mcp": {
"metaTitle": "MCP · Codewhale 文档",
"metaDescription": "通过 Model Context Protocol 消费外部工具服务器,或把 Codewhale 作为 MCP 服务器暴露。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewLead": "Codewhale 可以通过 MCPModel Context Protocol加载额外的工具。MCP 服务器可以是由 TUI 启动的本地 stdio 进程,也可以是远程 URL 服务器Streamable HTTP带旧版 SSE 回退)。连接成功的服务器会把工具注册进模型目录;失败或被禁用的服务器不会作为可用工具呈现给模型。",
"overviewConfig": "配置文件默认在 {configPath}(新文件缺失时仍读取旧版 {legacyConfigPath}),可用 {configPathOption} 或 {configEnvVar} 覆盖。也兼容其他客户端使用的 {serversKey} 键名。",
"setupTitle": "配置与管理",
"setupLead": "用 {initCommand} 生成初始配置TUI 内的 {mcpCommand} 打开紧凑管理器,显示每个服务器的启用状态、传输方式、命令或 URL、超时和连接错误。常用命令",
"setupReload": "在 TUI 里做的配置编辑会立即写盘,但模型可见的 MCP 工具池不会热加载——管理器会把它标记为需要重启。/mcp validate 和 /mcp reload 会重新连接以刷新界面快照。",
"authTitle": "远程认证",
"authLead": "URL 服务器可以使用静态 headers、从环境变量派生的 env_headers、bearer_token_env_var 或 OAuth。优先级是保守的先应用 headers 和 env_headersbearer_token_env_var 只在尚未设置 Authorization 时添加OAuth 登录获取的令牌同样不会覆盖已有的显式 header。应避免提交字面量 Authorization header——优先用 env_headers、bearer_token_env_var 或 OAuth 登录,让秘密留在 MCP 文件之外。",
"toolsTitle": "工具命名与安全",
"toolsLead": "发现的 MCP 工具以 {toolNamePattern} 的形式暴露给模型——例如名为 {gitServer} 的服务器的 {statusTool} 工具会变成 {gitStatusTool}。MCP 工具和内置工具走同一套审批框架:只读的 MCP 辅助工具在策略允许时可免提示运行,有副作用的 MCP 工具需要审批Full Access 也不会绕过硬策略拦截。",
"toolsTrust": "只配置你信任的 MCP 服务器,并把 MCP 服务器配置视为等同于在本机运行代码。经过审查的本地插件包也可以贡献 MCP 服务器:它们复用同一个 MCP 管理器、审批和网络策略路径,以 <plugin>-<server> 的命名空间身份出现,边界比手写的 mcp.json 更严格。",
"serverTitle": "把 Codewhale 作为 MCP 服务器",
"serverLead": "{serveMcp} 会把 Codewhale 作为 stdio MCP 服务器运行,让其他会话(或任何 MCP 客户端)调用它的工具;{mcpServerCommand} 是 dispatcher 暴露的等价入口。{addSelfCommand} 会自动解析当前二进制路径并把服务器写进你的 MCP 配置。注意区分:{serveHttp} 是运行时 HTTP/SSE API是另一种模式。",
"sourceNote": "来源文档docs/MCP.md · 更新时请同步修改 docs-map.ts。"
},
"docs-modes": {
"metaTitle": "模式 · Codewhale 文档",
"metaDescription": "Plan、Work、Operate 三种运行模式与独立的权限姿态。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "模式",
"overviewLead": "模式决定 Codewhale 如何组织工作;权限姿态决定它如何处理具有后果的工具调用。两者相互独立。",
"modes": [
[
"Plan",
"用于只读调查与规划。Codewhale 可以检查工作区,但不能执行 Shell 命令或修改文件。"
],
[
"Act",
"用于常规交互式编码。Codewhale 可以检查、编辑并使用工具Shell 是否可用以及何时请求批准,取决于当前配置和权限姿态。"
],
[
"Operate",
"用于从同一个输入区协调多项任务。父回合可以直接检查、编辑并使用 Shell 或 MCP 工具,其权限姿态、沙箱和安全规则与 Act 相同。独立、并行、后台或长时间工作会优先交给 Fleet worker但并非所有可执行步骤都必须委派。只有需要有序阶段、门禁或确定性汇总时才需要 Workflow。"
]
],
"switchingTitle": "切换模式",
"switchingLead": "输入区空闲时,按 {tab} 循环 Plan → Act → Operate。补全菜单打开时Tab 接受补全;回合运行时,它可以把当前草稿排入下一个跟进消息。",
"switchingCommandLead": "运行 /mode 打开模式选择器,或使用以下命令直接切换:",
"permissionsTitle": "权限姿态",
"permissionsLead": "Plan 始终为只读。在 Act 或 Operate 中且输入区空闲时,按 {shiftTab} 循环 Ask → Auto-Review → Full Access。运行 {configCommand} 可查看或编辑当前会话权限;项目或托管策略可能会锁定或收紧它。",
"postures": [
[
"Ask",
"在可能产生重要后果的工具执行前询问你。"
],
[
"Auto-Review",
"自动评估工具风险,只在确实需要你决定时询问。"
],
[
"Full Access",
"无需批准提示即可运行工具,并启用受信任工作区访问。仓库规则和托管约束仍然有效;仅在你信任的工作区中使用。"
]
],
"sourceNote": "来源文档docs/MODES.md · 更新时请同步修改 docs-map.ts。"
},
"docs-runtime-api": {
"metaTitle": "运行时 API · Codewhale 文档",
"metaDescription": "面向集成、桥接和自动化的本地 HTTP/SSE、JSON-RPC stdio 与 ACP 入口。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "运行时 API",
"overviewLead": "codewhale app-server 是 canonical 的本地运行时 API 与控制平面。本地 SDK、移动/远控客户端和编辑器集成直接与它对话,而不是抓终端输出。引擎只作为本地进程运行:所有 API 默认绑定 localhost——没有托管中继不托管 provider 令牌不泄露秘密。codewhale serve --http / --mobile 保留为 app-server --http / --mobile 的兼容别名,启动的是同一个服务器;新集成应面向 app-server。",
"entries": [
[
"http",
"完整 /v1/* HTTP/SSE 运行时 APIcanonical 入口),默认 127.0.0.1:7878。"
],
[
"mobile",
"运行时 API 加 /mobile 手机控制页。"
],
[
"stdio",
"换行分隔的 JSON-RPC 2.0 控制传输,无监听端口,适合本地 SDK 和探针。"
],
[
"web",
"仅回环的浏览器客户端,内嵌于二进制并打开默认浏览器。"
],
[
"doctor",
"机器可读的健康与能力报告。"
],
[
"acp",
"面向 Zed 等编辑器的 ACPAgent Client Protocolstdio 适配器。"
],
[
"exec",
"一次性无头 workerstream-json、Fleet 子进程、CI 原语)——不属于本 API但共享同一运行时与事件词汇。"
]
],
"stdioTitle": "零成本探测",
"stdioLead": "stdio 控制传输可以不花模型 token 地探测。capabilities 返回声明的方法族thread/*、app/*、prompt/*)和完整方法列表;方法集由 crates/app-server/src/lib.rs 中的漂移测试固定SDK 和本地集成可以放心依赖它不会悄悄变化。",
"interruptNote": "进行中的回合可以用 thread/interrupt或 HTTP 的 POST /v1/threads/{id}/turns/{turn_id}/interrupt请求中断没有正在流式输出的回合时返回 interrupted: false——这不是错误只是没有可停的东西。",
"securityTitle": "安全边界",
"securityLead": "运行时 API 令牌按 {authToken}、{runtimeTokenEnv}、{legacyTokenEnv} 的顺序读取;{insecureFlag} 只允许与回环绑定一起使用。浏览器侧的跨源请求会被 CORS 允许列表拒绝。选择非回环绑定(尤其是 {mobileFlag})之前,请阅读 docs/RUNTIME_API.md 的完整部署与认证约定。",
"sourceNote": "来源文档docs/RUNTIME_API.md · 更新时请同步修改 docs-map.ts。"
},
"docs-sandbox": {
"metaTitle": "沙箱与审批 · Codewhale 文档",
"metaDescription": "macOS Seatbelt、Linux 可选 bubblewrap、平台缺口和审批策略的真实边界。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "沙箱与审批",
"overviewLead": "Codewhale 可以启动由模型提出的 shell 命令。审批策略、感知工作区的文件工具和操作系统命令包装器是三个独立的控制:一次审批不是沙箱,选择 workspace-write 也不代表当前平台有可用的 OS 包装器。本页只描述已经接入命令执行路径的行为。",
"platforms": [
[
"macOS · Seatbelt",
"Codewhale 探测 /usr/bin/sandbox-exec探测成功且策略要求沙箱时子命令会被包上运行时生成的 Seatbelt profile广泛的文件系统读取、按策略限制的写入、仅在策略允许时放行网络。探测失败则如实报告无 OS 沙箱。"
],
[
"Linux · 可选 bubblewrap",
"Linux 命令沙箱是显式启用的:设置 prefer_bwrap = true且 /usr/bin/bwrap 是可执行文件时才选用。子命令得到只读根视图writable 挂载来自解析后的策略;默认隔离网络命名空间,仅在策略开启 network_access 时加 --share-net。未启用或未安装 bwrap 时报告 none。"
],
[
"Windows · 无 OS 沙箱",
"Windows 命令路径目前报告无 OS 沙箱。主机权限和审批策略仍然有效,但它们不是 Codewhale 的 OS 命令沙箱。"
],
[
"外部 OpenSandbox 执行",
"配置 sandbox_backend = \"opensandbox\" 后shell 执行会发往配置的 OpenSandbox 兼容 HTTP 端点,而不是启动本地子进程。隔离保证属于所配置的服务及其运营者。"
]
],
"policiesTitle": "策略与回退",
"policiesLead": "本地 {sandboxMode} 取值为 {readOnly}、{workspaceWrite}、{dangerFullAccess} 或 {externalSandbox}。前两者只在选中且可用的 Seatbelt 或 bubblewrap 包装器下被强制执行;{dangerFullAccess} 有意绕过本地 OS 包装器;{externalSandbox} 声明执行已被外部隔离。没有选中包装器时shell 命令在没有 Codewhale OS 隔离的情况下运行——审批规则和感知工作区的原生文件工具仍是独立的控制。",
"diagnosticsTitle": "诊断与限制",
"diagnosticsLead": "codewhale setup --status、codewhale doctor、codewhale doctor --json 和 diagnostics 工具会报告应用 bubblewrap 偏好后本地可用的包装器。拒绝归因是保守的:子命令的通用 Permission denied 本身并不能证明是 Codewhale 的沙箱拦截了它,未沙箱化的命令失败永远不会被标记为沙箱拒绝。",
"diagnosticsLimits": "限制同样如实说明可用性在启动前检查选中的包装器仍可能因主机策略、容器限制或竞态而失败bubblewrap 会忽略缺失或不是目录的可写根;没有任何沙箱能防御内核漏洞或所有资源耗尽与侧信道攻击。",
"sourceNote": "来源文档docs/SANDBOX.md · 更新时请同步修改 docs-map.ts。"
},
"docs-subagents": {
"metaTitle": "子 Agent · Codewhale 文档",
"metaDescription": "agent 工具、Fleet 角色、上下文分叉、worktree 隔离和并发上限。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "子 Agent",
"overviewLead": "父会话通过 agent 工具启动一个有明确职责的子 Agent并立即拿回 agent_id、compact 收据和 transcript 句柄;子 Agent 在后台运行。子 Agent 默认继承父级的工具注册表,但它们是叶子 worker不会再拿到 agent 或嵌套生命周期工具。agent 启动的是分离的后台工作——取消父回合会停止父级的等待路径,但不会杀死已经启动的子运行。",
"overviewFleetNote": "对于必须跨进程重启、睡眠或远程执行存活的工作,优先选择 Fleet 或 Workflow 支撑的 Fleet 运行,而不是会话内的短寿命 agent 调用。",
"roles": [
[
"worker",
"灵活执行父级交代的多步任务;可写、可用 shell。默认角色。"
],
[
"scout",
"只读,快速摸清相关代码——例如“找出 Foo 的所有调用点”。"
],
[
"planner",
"分析并产出策略,不执行——“设计迁移方案,不要动手”。"
],
[
"reviewer",
"只读审查并按严重度打分——“审一遍这个 PR 的 bug”。"
],
[
"builder",
"以最小改动落地一个明确的变更;可写、可用 shell。"
],
[
"verifier",
"运行测试和校验并汇报结果,不写代码。"
],
[
"consultant",
"只读的高推理力度顾问,用于判断类问题和设计评审。"
],
[
"custom",
"手工指定狭窄的工具白名单,用于锁定的派发。"
]
],
"forkTitle": "上下文分叉",
"forkLead": "{agentTool} 默认开启全新会话:子 Agent 只拿到角色提示词和你给的任务。当任务依赖父 transcript 里已有的决定、文件、待办或计划状态时,用 {forkContext}——运行时在可用时保持父级前缀逐字节一致(保留前缀缓存复用),追加一份结构化状态快照,再把子 Agent 的角色说明和任务放在末尾。独立探索用新会话,延续、审查、总结或压缩类工作用分叉会话。",
"worktreeTitle": "Worktree 隔离",
"worktreeLead": "并行编辑通道用 {worktreeFlag} 启动Codewhale 为子 Agent 创建新的 git worktree 和分支(默认 {branchPattern},检出在父仓库旁的 {worktreeDir} 下),父检出保持干净。隔离不等于写权限:只带 prompt 的 worker 从只读开始;要写代码的子 Agent 还需声明 {writeAuthority} 和至少一个规范化的 {writeRoots}、{exactFiles} 或 {coordinationContracts}值;重叠的共享写声明会在任何改动之前失败。",
"capacityTitle": "并发上限",
"capacityLead": "子 Agent 容量的权威来源是 crates/tui/src/config/subagent_limits.rs默认配置并发 64最大配置并发 128运行加排队的最大准入 1024。这些是容量上限不是建议把每个槽位都派出去——管理者应使用最小的有效扇出保持单一汇总负责人并在汇报整体完成前验证 worker 收据。",
"sourceNote": "来源文档docs/SUBAGENTS.md · 更新时请同步修改 docs-map.ts。"
},
"docs-web": {
"metaTitle": "浏览器客户端 · Codewhale 文档",
"metaDescription": "仅回环的内嵌浏览器客户端:一次性引导、会话 Cookie 与本地信任边界。",
"bodyClassName": "text-ink-soft leading-[1.9] tracking-wide",
"overviewTitle": "浏览器客户端",
"overviewLead": "{webCommand} 在 canonical 运行时 API 之上打开 Codewhale 内嵌的浏览器客户端。它是一个纯本地界面:服务器始终绑定 {loopbackHost},无法改绑到局域网地址,也无法在关闭运行时认证的情况下运行。默认地址是 {defaultUrl};端口冲突时用 {portExample} 换一个回环端口。Ctrl+C 停止进程,浏览器会话随之结束。",
"overviewBody": "当前客户端提供响应式的线程与搜索侧栏、由运行时持有的会话事实、transcript 与工具收据,以及输入区。它可以创建、选择、重命名和归档线程;发起或引导回合;中断工作;处理审批;回答运行时的用户输入请求。浏览器只是同一个本地运行时的另一视图——不会创建第二个云账号,不会把 provider 凭据复制进浏览器存储,也不会削弱已配置的审批与沙箱策略。",
"authTitle": "认证边界",
"authLead": "启动 URL 携带的是一个随机、短寿命、一次性的引导凭证——绝不是运行时 bearer 令牌。一次回环请求把它换成 HttpOnly、SameSite=Strict、进程本地的会话 Cookie并立即使该凭证失效。重用、过期、畸形或非回环的引导尝试都会失败关闭。运行时令牌不会出现在渲染的 HTML、浏览器存储、URL 查询或片段、或浏览器启动参数中。携带 Cookie 的状态变更请求还必须出示精确的本地 web 源;跨源浏览器请求会被拒绝。",
"localTitle": "本地就是本地",
"localLead": "{webCommand} 只接受 {portFlag}——没有 {hostFlag},也没有关闭认证的选项。不要把它当公开网站,也不要通过路由器转发、公开反向代理或隧道暴露它的端口。单独的 {mobileCommand} 和 {httpFlag} 模式有不同的部署与认证约定,操作它们(尤其是选择非回环绑定)之前请阅读运行时 API 文档。",
"troubleshootingTitle": "常见问题",
"troubleshootingLead": "端口 7878 被占用时用 --port 换一个。浏览器无法打开时命令会报错退出,而不会留下可重用的引导凭证;检查系统默认浏览器设置后重新启动。页面能打开但 provider 不可用时,查 codewhale doctor 和 /provider——web 命令不配置也不迁移 provider 凭据。会话过期后重启 codewhale web 以签发新的进程本地会话;重用旧的引导 URL 本来就会失败。",
"sourceNote": "来源文档docs/WEB.md · 更新时请同步修改 docs-map.ts。"
}
}

9
web/gt.config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"defaultLocale": "en",
"locales": ["zh"],
"files": {
"json": {
"include": ["gt-catalog/[locale].json"]
}
}
}

View File

@@ -0,0 +1,45 @@
import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { getChrome, getDocsGuide, getHome } from "./dictionaries";
const webRoot = fileURLToPath(new URL("../..", import.meta.url));
function runGtSite(command: string, extraEnv: NodeJS.ProcessEnv = {}) {
return spawnSync(process.execPath, [join(webRoot, "scripts", "gt-site.mjs"), command], {
cwd: webRoot,
encoding: "utf8",
env: {
...process.env,
GT_API_KEY: "",
GT_PROJECT_ID: "",
...extraEnv,
},
});
}
describe("website GT catalog pipeline", () => {
it("keeps local catalogs in sync with the dictionary runtime", () => {
const check = runGtSite("check");
expect(check.status, check.stderr || check.stdout).toBe(0);
expect(check.stdout).toContain("TUI packs untouched");
const zh = JSON.parse(readFileSync(join(webRoot, "gt-catalog", "zh.json"), "utf8"));
expect(zh.chrome.navDocs).toBe(getChrome("zh").navDocs);
expect(zh.home.kicker).toBe(getHome("zh").kicker);
expect(zh["docs-guide"].overviewTitle).toBe(getDocsGuide("zh").overviewTitle);
const config = readFileSync(join(webRoot, "gt.config.json"), "utf8");
expect(config).toContain("gt-catalog/[locale].json");
expect(config).not.toContain("crates/tui");
expect(config).not.toContain("prompts/text.rs");
});
it("fails closed when translate is asked to call the GT API without BYOK env", () => {
const translate = runGtSite("translate");
expect(translate.status).not.toBe(0);
expect(`${translate.stderr}${translate.stdout}`).toMatch(/fail-closed|GT_API_KEY/);
});
});

3159
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "codewhale-web",
"version": "0.1.0",
"private": true,
"description": "Community site for Codewhale \u2014 codewhale.net",
"description": "Community site for Codewhale codewhale.net",
"scripts": {
"dev": "node scripts/derive-facts.mjs && next dev",
"prebuild": "node scripts/derive-facts.mjs",
@@ -13,7 +13,8 @@
"sync:latest-release": "node scripts/sync-latest-release.mjs",
"check:latest-release": "node scripts/sync-latest-release.mjs --check",
"check:docs": "node scripts/check-docs.mjs",
"check:locales": "node scripts/check-locales.mjs",
"check:locales": "node scripts/check-locales.mjs && node scripts/gt-site.mjs check",
"i18n:gt": "node scripts/gt-site.mjs",
"check:deploy-env": "node scripts/check-cloudflare-deploy-env.mjs",
"compare:deployed-facts": "node scripts/compare-deployed-facts.mjs",
"check:deployed-facts": "node scripts/compare-deployed-facts.mjs --require-current",
@@ -36,6 +37,7 @@
"autoprefixer": "^10.5.4",
"eslint": "^9.39.4",
"eslint-config-next": "^15.5.18",
"gt": "2.17.2",
"postcss": "^8.5.18",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",

347
web/scripts/gt-site.mjs Normal file
View File

@@ -0,0 +1,347 @@
#!/usr/bin/env node
/**
* Website/docs General Translation pipeline.
*
* Runtime authority stays web/lib/i18n/dictionaries (one logical path).
* This script exports those dictionaries to web/gt-catalog/[locale].json
* so the MIT `gt` CLI can translate updated English copy, then imports
* reviewed JSON back into the same dictionary files.
*
* Not for the TUI. Not for model completions. Not a /translate replacement.
* Never call `gt generate` here — that scanner is framework-only and would
* look for <T> JSX this site does not use.
*/
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync } from "node:fs";
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const webRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repoRoot = path.resolve(webRoot, "..");
const configPath = path.join(webRoot, "gt.config.json");
const catalogDir = path.join(webRoot, "gt-catalog");
const dictDir = path.join(webRoot, "lib", "i18n", "dictionaries");
const pinnedCli = "2.17.2";
const tuiLocaleDir = path.join(repoRoot, "crates", "tui", "locales");
const promptPath = path.join(repoRoot, "crates", "tui", "src", "prompts", "text.rs");
const STEMS = {
chrome: { exportName: "chrome", typeName: "ChromeDict" },
home: { exportName: "home", typeName: "HomeDict" },
"docs-guide": { exportName: "docsGuide", typeName: "DocsGuideDict" },
"docs-shell": { exportName: "docsShell", typeName: "DocsShellDict" },
"docs-hooks": { exportName: "docsHooks", typeName: "DocsHooksDict" },
"docs-troubleshooting": { exportName: "docsTroubleshooting", typeName: "DocsTroubleshootingDict" },
"docs-configuration": { exportName: "docsConfiguration", typeName: "DocsConfigurationDict" },
"docs-constitution": { exportName: "docsConstitution", typeName: "DocsConstitutionDict" },
"docs-fleet": { exportName: "docsFleet", typeName: "DocsFleetDict" },
"docs-mcp": { exportName: "docsMcp", typeName: "DocsMcpDict" },
"docs-modes": { exportName: "docsModes", typeName: "DocsModesDict" },
"docs-runtime-api": { exportName: "docsRuntimeApi", typeName: "DocsRuntimeApiDict" },
"docs-sandbox": { exportName: "docsSandbox", typeName: "DocsSandboxDict" },
"docs-subagents": { exportName: "docsSubagents", typeName: "DocsSubagentsDict" },
"docs-web": { exportName: "docsWeb", typeName: "DocsWebDict" },
};
const REQUIRED_STEMS = ["chrome", "home"];
function fail(message) {
throw new Error(message);
}
function assertExactKeys(value, expected, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
fail(`${label} must be an object`);
}
const actual = Object.keys(value).sort();
const wanted = [...expected].sort();
if (JSON.stringify(actual) !== JSON.stringify(wanted)) {
fail(`${label} keys must be exactly: ${wanted.join(", ")} (found: ${actual.join(", ")})`);
}
}
function stableStringify(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
function deepEqual(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
async function readConfig() {
const config = JSON.parse(await readFile(configPath, "utf8"));
assertExactKeys(config, ["defaultLocale", "locales", "files"], "gt.config.json");
assertExactKeys(config.files, ["json"], "gt.config.json files");
assertExactKeys(config.files.json, ["include"], "gt.config.json files.json");
if (config.defaultLocale !== "en") fail("defaultLocale must remain en");
if (
!Array.isArray(config.locales) ||
config.locales.length === 0 ||
!config.locales.every((locale) => typeof locale === "string" && locale.length > 0) ||
new Set(config.locales).size !== config.locales.length
) {
fail("locales must be a non-empty, duplicate-free string array");
}
if (config.locales.includes("en")) fail("locales must not include the default locale");
const include = config.files.json.include;
if (!Array.isArray(include) || include.length !== 1 || include[0] !== "gt-catalog/[locale].json") {
fail("the JSON source must remain web/gt-catalog/[locale].json");
}
for (const locale of config.locales) {
if (locale.includes("..") || locale.includes("/") || locale.includes("\\")) {
fail(`locale ${locale} is not a safe catalog name`);
}
}
return config;
}
async function assertWebsiteOnlyAsync() {
const source = await readFile(configPath, "utf8");
if (source.includes("crates/tui") || source.includes("prompts/text.rs")) {
fail("gt.config.json must not mention TUI paths");
}
if (!source.includes("gt-catalog/[locale].json")) {
fail("gt.config.json must target the website catalog only");
}
}
async function loadDictionaryModule(locale, stem) {
const spec = STEMS[stem];
if (!spec) fail(`unknown dictionary stem ${stem}`);
const filePath = path.join(dictDir, locale, `${stem}.ts`);
if (!existsSync(filePath)) return null;
const ts = await import("typescript");
const source = await readFile(filePath, "utf8");
const { outputText } = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ES2022,
},
fileName: filePath,
});
const mod = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`);
const value = mod[spec.exportName];
if (value == null) fail(`missing export ${spec.exportName} in ${filePath}`);
return value;
}
async function loadCatalogFromDictionaries(locale) {
const catalog = {};
for (const stem of Object.keys(STEMS)) {
const value = await loadDictionaryModule(locale, stem);
if (value == null) {
if (REQUIRED_STEMS.includes(stem) && locale === "en") {
fail(`English is missing required dictionary ${stem}.ts`);
}
continue;
}
catalog[stem] = value;
}
return catalog;
}
async function readCatalogFile(locale) {
const filePath = path.join(catalogDir, `${locale}.json`);
const source = await readFile(filePath, "utf8");
let parsed;
try {
parsed = JSON.parse(source);
} catch (error) {
fail(`${filePath} is not valid JSON: ${error.message}`);
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
fail(`${filePath} must be a JSON object of dictionary stems`);
}
for (const stem of Object.keys(parsed)) {
if (!STEMS[stem]) fail(`${filePath} contains unknown stem ${stem}`);
}
for (const stem of REQUIRED_STEMS) {
if (!parsed[stem]) fail(`${filePath} is missing required stem ${stem}`);
}
return parsed;
}
async function writeCatalogFile(locale, catalog) {
await mkdir(catalogDir, { recursive: true });
const filePath = path.join(catalogDir, `${locale}.json`);
await writeFile(filePath, stableStringify(catalog));
return filePath;
}
function emitTsValue(value, indent) {
const pad = " ".repeat(indent);
if (typeof value === "string") return JSON.stringify(value);
if (Array.isArray(value)) {
if (value.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === "string"))) {
const rows = value.map((row) => `${pad} [${row.map((cell) => JSON.stringify(cell)).join(", ")}]`);
return `[\n${rows.join(",\n")},\n${pad}]`;
}
fail("catalog arrays must be [string, string] rows");
}
if (value && typeof value === "object") {
const keys = Object.keys(value);
const lines = keys.map((key) => `${pad} ${key}: ${emitTsValue(value[key], indent + 2)},`);
return `{\n${lines.join("\n")}\n${pad}}`;
}
fail(`unsupported catalog value type ${typeof value}`);
}
async function writeDictionaryFile(locale, stem, value) {
const spec = STEMS[stem];
const filePath = path.join(dictDir, locale, `${stem}.ts`);
await mkdir(path.dirname(filePath), { recursive: true });
const body = `import type { ${spec.typeName} } from "../types";
/**
* Website dictionary for locale \`${locale}\`.
* Runtime authority is this file (web/lib/i18n/dictionaries).
* Source/sink for General Translation is web/gt-catalog/${locale}.json.
*/
export const ${spec.exportName}: ${spec.typeName} = ${emitTsValue(value, 0)};
`;
await writeFile(filePath, body);
return filePath;
}
async function check() {
const config = await readConfig();
await assertWebsiteOnlyAsync();
if (existsSync(tuiLocaleDir)) {
const tuiPacks = (await readdir(tuiLocaleDir)).filter((name) => name.endsWith(".json"));
if (tuiPacks.length === 0) fail("TUI locale directory exists but is empty — refusing to proceed");
}
if (existsSync(promptPath)) {
const digest = createHash("sha256").update(await readFile(promptPath)).digest("hex");
if (digest.length !== 64) fail("could not fingerprint prompts/text.rs");
}
const locales = [config.defaultLocale, ...config.locales];
for (const locale of locales) {
const fromDict = await loadCatalogFromDictionaries(locale);
const fromFile = await readCatalogFile(locale);
if (!deepEqual(fromDict, fromFile)) {
fail(
`gt-catalog/${locale}.json is out of sync with web/lib/i18n/dictionaries/${locale}/ — run npm run i18n:gt -- export`,
);
}
for (const stem of Object.keys(fromFile)) {
if (REQUIRED_STEMS.includes(stem)) continue;
const enValue = (await loadCatalogFromDictionaries("en"))[stem];
if (enValue && !deepEqual(Object.keys(fromFile[stem]).sort(), Object.keys(enValue).sort())) {
fail(`gt-catalog/${locale}.json stem ${stem} does not have English key parity`);
}
}
}
console.log(
`Website GT catalog OK — ${config.locales.join(", ")} (local JSON; TUI packs untouched; no API)`,
);
}
async function exportCatalogs() {
const config = await readConfig();
const locales = [config.defaultLocale, ...config.locales];
const written = [];
for (const locale of locales) {
const catalog = await loadCatalogFromDictionaries(locale);
written.push(await writeCatalogFile(locale, catalog));
}
console.log(`Exported ${written.length} website catalogs:\n${written.map((file) => ` ${path.relative(webRoot, file)}`).join("\n")}`);
}
async function importCatalogs() {
const config = await readConfig();
const written = [];
for (const locale of config.locales) {
const catalog = await readCatalogFile(locale);
for (const [stem, value] of Object.entries(catalog)) {
written.push(await writeDictionaryFile(locale, stem, value));
}
}
console.log(
`Imported ${written.length} website dictionaries from gt-catalog (TUI packs not written):\n${written
.map((file) => ` ${path.relative(webRoot, file)}`)
.join("\n")}`,
);
}
async function validatePinnedCli() {
const packagePath = path.join(webRoot, "node_modules", "gt", "package.json");
let packageJson;
try {
packageJson = JSON.parse(await readFile(packagePath, "utf8"));
} catch {
fail("gt is not installed locally; run npm ci in web/ (this wrapper never downloads packages)");
}
if (packageJson.version !== pinnedCli) {
fail(`expected the locked gt ${pinnedCli} package (found ${packageJson.version})`);
}
const bin = packageJson.bin;
const binPath = typeof bin === "string" ? bin : bin.gt;
if (!binPath) fail("gt package.json is missing a bin");
return path.join(path.dirname(packagePath), binPath);
}
function translateEnvironment() {
const key = process.env.GT_API_KEY?.trim();
const project = process.env.GT_PROJECT_ID?.trim();
if (!key || !project) {
fail(
"gt translate is fail-closed. Set BYOK env GT_API_KEY and GT_PROJECT_ID (never commit them). Local export/import/check do not need a key.",
);
}
const environment = { ...process.env };
environment.NO_COLOR = "1";
environment.GT_API_KEY = key;
environment.GT_PROJECT_ID = project;
return environment;
}
async function translate() {
await readConfig();
await assertWebsiteOnlyAsync();
const environment = translateEnvironment();
const cli = await validatePinnedCli();
const result = spawnSync(
process.execPath,
[cli, "--skip-version-check", "translate", "--config", "gt.config.json"],
{
cwd: webRoot,
env: environment,
stdio: "inherit",
windowsHide: true,
},
);
if (result.error) fail(result.error.message);
if (result.status !== 0) fail(`gt translate exited with status ${result.status ?? "unknown"}`);
console.log("gt translate finished — review gt-catalog/*.json then run npm run i18n:gt -- import");
}
function usage() {
console.log(`Usage: node scripts/gt-site.mjs <check|export|import|translate>
check catalogs match dictionaries; website-only schema
export dictionaries → gt-catalog/[locale].json (no API)
import gt-catalog → website dictionary TS only (no TUI writes)
translate fail-closed without GT_API_KEY + GT_PROJECT_ID
Never wraps inference. Never edits crates/tui/src/prompts/text.rs.`);
}
const command = process.argv[2] ?? "check";
try {
if (command === "check") await check();
else if (command === "export") await exportCatalogs();
else if (command === "import") await importCatalogs();
else if (command === "translate") await translate();
else if (command === "-h" || command === "--help") usage();
else {
usage();
fail(`unknown command ${command}`);
}
} catch (error) {
console.error(`[gt-site] FAIL — ${error.message}`);
process.exitCode = 1;
}