Merge pull request #5520 from Lstarsky0/feat/docs-sandbox-web

feat(web): move docs/sandbox and docs/web onto the dictionary spine (#5337)

Preserves Lstarsky0's original commits. Same merge shape as #5517.
This commit is contained in:
Hunter Bown
2026-08-20 00:35:14 -07:00
committed by GitHub
12 changed files with 322 additions and 182 deletions
+4
View File
@@ -247,6 +247,10 @@ abort under load.
- `feat(web): move docs/hooks and docs/troubleshooting onto the dictionary
spine` (Lstarsky0) — both pages now read copy from the locale dictionaries
instead of inline bilingual literals (#5337).
- `feat(web): move docs/constitution and docs/runtime-api onto the dictionary
spine` (Lstarsky0) — both pages now read copy from the locale dictionaries
instead of inline bilingual literals; another incremental phase of #5337,
not completion of the full epic (#5517).
- `docs(i18n): complete Tier 1 of Chinese docs localization` (SparkofSpike) —
Chinese and Indonesian docs move to `docs/zh_hans/` and `docs/id/`, with
redirect stubs at the old paths for one release cycle (#5482).
+4
View File
@@ -247,6 +247,10 @@ abort under load.
- `feat(web): move docs/hooks and docs/troubleshooting onto the dictionary
spine` (Lstarsky0) — both pages now read copy from the locale dictionaries
instead of inline bilingual literals (#5337).
- `feat(web): move docs/constitution and docs/runtime-api onto the dictionary
spine` (Lstarsky0) — both pages now read copy from the locale dictionaries
instead of inline bilingual literals; another incremental phase of #5337,
not completion of the full epic (#5517).
- `docs(i18n): complete Tier 1 of Chinese docs localization` (SparkofSpike) —
Chinese and Indonesian docs move to `docs/zh_hans/` and `docs/id/`, with
redirect stubs at the old paths for one release cycle (#5482).
+39 -104
View File
@@ -1,114 +1,61 @@
import { Fragment } from "react";
import { getDocsSandbox, splitTokens } from "@/lib/i18n/dictionaries";
import { buildPageMetadata } from "@/lib/page-meta";
/**
* The config values the policies paragraph typesets as inline `<code>`.
* `docs/VOICE.md` keeps key names code-owned, so the dictionaries carry
* `{token}`s rather than the literals.
*/
const CODE_SPANS: Record<string, string> = {
sandboxMode: "sandbox_mode",
readOnly: "read-only",
workspaceWrite: "workspace-write",
dangerFullAccess: "danger-full-access",
externalSandbox: "external-sandbox",
};
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const t = getDocsSandbox(locale);
return buildPageMetadata({
path: "/docs/sandbox",
locale,
title: isZh ? "沙箱与审批 · Codewhale 文档" : "Sandbox & Approval · Codewhale Docs",
description: isZh
? "macOS Seatbelt、Linux 可选 bubblewrap、平台缺口和审批策略的真实边界。"
: "The honest boundary: macOS Seatbelt, opt-in Linux bubblewrap, platform gaps, and approval policy.",
title: t.metaTitle,
description: t.metaDescription,
});
}
export default async function SandboxPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const bodyClass = isZh
? "text-ink-soft leading-[1.9] tracking-wide"
: "text-ink-soft leading-relaxed";
const platforms = isZh
? [
{
name: "macOS · Seatbelt",
detail:
"Codewhale 探测 /usr/bin/sandbox-exec;探测成功且策略要求沙箱时,子命令会被包上运行时生成的 Seatbelt profile:广泛的文件系统读取、按策略限制的写入、仅在策略允许时放行网络。探测失败则如实报告无 OS 沙箱。",
},
{
name: "Linux · 可选 bubblewrap",
detail:
"Linux 命令沙箱是显式启用的:设置 prefer_bwrap = true,且 /usr/bin/bwrap 是可执行文件时才选用。子命令得到只读根视图,writable 挂载来自解析后的策略;默认隔离网络命名空间,仅在策略开启 network_access 时加 --share-net。未启用或未安装 bwrap 时报告 none。",
},
{
name: "Windows · 无 OS 沙箱",
detail:
"Windows 命令路径目前报告无 OS 沙箱。主机权限和审批策略仍然有效,但它们不是 Codewhale 的 OS 命令沙箱。",
},
{
name: "外部 OpenSandbox 执行",
detail:
"配置 sandbox_backend = \"opensandbox\" 后,shell 执行会发往配置的 OpenSandbox 兼容 HTTP 端点,而不是启动本地子进程。隔离保证属于所配置的服务及其运营者。",
},
]
: [
{
name: "macOS · Seatbelt",
detail:
"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.",
},
{
name: "Linux · opt-in bubblewrap",
detail:
"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.",
},
{
name: "Windows · no OS sandbox",
detail:
"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.",
},
{
name: "External OpenSandbox execution",
detail:
"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.",
},
];
const t = getDocsSandbox(locale);
return (
<section className="space-y-10">
<section id="overview" className="scroll-mt-32">
<h1 className="font-display text-3xl mb-1">{isZh ? "沙箱与审批" : "Sandbox & Approval"}</h1>
<p className={`${bodyClass} mt-3`}>
{isZh
? "Codewhale 可以启动由模型提出的 shell 命令。审批策略、感知工作区的文件工具和操作系统命令包装器是三个独立的控制:一次审批不是沙箱,选择 workspace-write 也不代表当前平台有可用的 OS 包装器。本页只描述已经接入命令执行路径的行为。"
: "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."}
</p>
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
<div className="hairline-t mt-6">
{platforms.map((row) => (
<section key={row.name} className="py-4 hairline-b">
<h3 className="font-display text-lg">{row.name}</h3>
<p className={`${bodyClass} mt-1 text-sm`}>{row.detail}</p>
{t.platforms.map(([name, detail]) => (
<section key={name} className="py-4 hairline-b">
<h3 className="font-display text-lg">{name}</h3>
<p className={`${t.bodyClassName} mt-1 text-sm`}>{detail}</p>
</section>
))}
</div>
</section>
<section id="policies" className="scroll-mt-32">
<h2 className="font-display text-2xl mb-1">{isZh ? "策略与回退" : "Policies and fallbacks"}</h2>
<p className={`${bodyClass} mt-3`}>
{isZh ? (
<>
<code className="inline">sandbox_mode</code> {" "}
<code className="inline">read-only</code><code className="inline">workspace-write</code>
<code className="inline">danger-full-access</code> {" "}
<code className="inline">external-sandbox</code> Seatbelt
bubblewrap <code className="inline">danger-full-access</code>{" "}
OS <code className="inline">external-sandbox</code>{" "}
shell Codewhale OS
</>
) : (
<>
The local <code className="inline">sandbox_mode</code> values are{" "}
<code className="inline">read-only</code>, <code className="inline">workspace-write</code>,{" "}
<code className="inline">danger-full-access</code>, and{" "}
<code className="inline">external-sandbox</code>. The first two are enforced by Seatbelt or
bubblewrap only when that wrapper is selected and available;{" "}
<code className="inline">danger-full-access</code> deliberately bypasses the local OS
wrapper; <code className="inline">external-sandbox</code> 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.
</>
<h2 className="font-display text-2xl mb-1">{t.policiesTitle}</h2>
<p className={`${t.bodyClassName} mt-3`}>
{splitTokens(t.policiesLead).map((part, i) =>
"token" in part ? (
<code key={`${i}-${part.token}`} className="inline">
{CODE_SPANS[part.token] ?? `{${part.token}}`}
</code>
) : (
<Fragment key={`${i}-text`}>{part.text}</Fragment>
),
)}
</p>
<pre className="code-block mt-4">{`# config.toml
@@ -123,25 +70,13 @@ CODEWHALE_SANDBOX_API_KEY`}</pre>
</section>
<section id="diagnostics" className="scroll-mt-32">
<h2 className="font-display text-2xl mb-1">{isZh ? "诊断与限制" : "Diagnostics and limits"}</h2>
<p className={`${bodyClass} mt-3`}>
{isZh
? "codewhale setup --status、codewhale doctor、codewhale doctor --json 和 diagnostics 工具会报告应用 bubblewrap 偏好后本地可用的包装器。拒绝归因是保守的:子命令的通用 Permission denied 本身并不能证明是 Codewhale 的沙箱拦截了它,未沙箱化的命令失败永远不会被标记为沙箱拒绝。"
: "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."}
</p>
<p className={`${bodyClass} mt-3`}>
{isZh
? "限制同样如实说明:可用性在启动前检查,选中的包装器仍可能因主机策略、容器限制或竞态而失败;bubblewrap 会忽略缺失或不是目录的可写根;没有任何沙箱能防御内核漏洞或所有资源耗尽与侧信道攻击。"
: "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."}
</p>
<h2 className="font-display text-2xl mb-1">{t.diagnosticsTitle}</h2>
<p className={`${t.bodyClassName} mt-3`}>{t.diagnosticsLead}</p>
<p className={`${t.bodyClassName} mt-3`}>{t.diagnosticsLimits}</p>
</section>
<section id="source" className="hairline-t pt-8">
<p className="text-sm text-ink-mute">
{isZh
? "来源文档:docs/SANDBOX.md · 更新时请同步修改 docs-map.ts。"
: "Source document: docs/SANDBOX.md · Update docs-map.ts when changing."}
</p>
<p className="text-sm text-ink-mute">{t.sourceNote}</p>
</section>
</section>
);
+44 -78
View File
@@ -1,109 +1,75 @@
import { Fragment } from "react";
import { getDocsWeb, splitTokens } from "@/lib/i18n/dictionaries";
import { buildPageMetadata } from "@/lib/page-meta";
/**
* The commands, flags and addresses the overview and local paragraphs typeset
* as inline `<code>`. `docs/VOICE.md` keeps commands, flags and paths
* code-owned, so the dictionaries carry `{token}`s rather than the literals.
*/
const CODE_SPANS: Record<string, string> = {
webCommand: "codewhale web",
loopbackHost: "127.0.0.1",
defaultUrl: "http://127.0.0.1:7878",
portExample: "codewhale web --port 8788",
portFlag: "--port",
hostFlag: "--host",
mobileCommand: "codewhale app-server --mobile",
httpFlag: "--http",
};
function withCodeSpans(template: string) {
return splitTokens(template).map((part, i) =>
"token" in part ? (
<code key={`${i}-${part.token}`} className="inline">
{CODE_SPANS[part.token] ?? `{${part.token}}`}
</code>
) : (
<Fragment key={`${i}-text`}>{part.text}</Fragment>
),
);
}
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const t = getDocsWeb(locale);
return buildPageMetadata({
path: "/docs/web",
locale,
title: isZh ? "浏览器客户端 · Codewhale 文档" : "Browser Client · Codewhale Docs",
description: isZh
? "仅回环的内嵌浏览器客户端:一次性引导、会话 Cookie 与本地信任边界。"
: "The loopback-only embedded browser client: one-time bootstrap, session cookie, and the local trust boundary.",
title: t.metaTitle,
description: t.metaDescription,
});
}
export default async function WebClientPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const bodyClass = isZh
? "text-ink-soft leading-[1.9] tracking-wide"
: "text-ink-soft leading-relaxed";
const t = getDocsWeb(locale);
return (
<section className="space-y-10">
<section id="overview" className="scroll-mt-32">
<h1 className="font-display text-3xl mb-1">{isZh ? "浏览器客户端" : "Browser Client"}</h1>
<p className={`${bodyClass} mt-3`}>
{isZh ? (
<>
<code className="inline">codewhale web</code> canonical API
Codewhale {" "}
<code className="inline">127.0.0.1</code>{" "}
<code className="inline">http://127.0.0.1:7878</code>;端口冲突时用{" "}
<code className="inline">codewhale web --port 8788</code>{" "}
Ctrl+C
</>
) : (
<>
<code className="inline">codewhale web</code> opens Codewhale's embedded browser client
over the canonical Runtime API. It is a local surface: the server always binds to{" "}
<code className="inline">127.0.0.1</code>, cannot be rebound to a LAN address, and cannot
run with Runtime authentication disabled. The default address is{" "}
<code className="inline">http://127.0.0.1:7878</code>; on a port collision, pick another
loopback port with <code className="inline">codewhale web --port 8788</code>. Stop the
process with Ctrl+C and the browser session ends with it.
</>
)}
</p>
<p className={`${bodyClass} mt-3`}>
{isZh
? "当前客户端提供响应式的线程与搜索侧栏、由运行时持有的会话事实、transcript 与工具收据,以及输入区。它可以创建、选择、重命名和归档线程;发起或引导回合;中断工作;处理审批;回答运行时的用户输入请求。浏览器只是同一个本地运行时的另一视图——不会创建第二个云账号,不会把 provider 凭据复制进浏览器存储,也不会削弱已配置的审批与沙箱策略。"
: "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."}
</p>
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.overviewLead)}</p>
<p className={`${t.bodyClassName} mt-3`}>{t.overviewBody}</p>
</section>
<section id="auth" className="scroll-mt-32">
<h2 className="font-display text-2xl mb-1">{isZh ? "认证边界" : "Authentication boundary"}</h2>
<p className={`${bodyClass} mt-3`}>
{isZh
? "启动 URL 携带的是一个随机、短寿命、一次性的引导凭证——绝不是运行时 bearer 令牌。一次回环请求把它换成 HttpOnly、SameSite=Strict、进程本地的会话 Cookie,并立即使该凭证失效。重用、过期、畸形或非回环的引导尝试都会失败关闭。运行时令牌不会出现在渲染的 HTML、浏览器存储、URL 查询或片段、或浏览器启动参数中。携带 Cookie 的状态变更请求还必须出示精确的本地 web 源;跨源浏览器请求会被拒绝。"
: "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."}
</p>
<h2 className="font-display text-2xl mb-1">{t.authTitle}</h2>
<p className={`${t.bodyClassName} mt-3`}>{t.authLead}</p>
</section>
<section id="local" className="scroll-mt-32">
<h2 className="font-display text-2xl mb-1">{isZh ? "本地就是本地" : "Local means local"}</h2>
<p className={`${bodyClass} mt-3`}>
{isZh ? (
<>
<code className="inline">codewhale web</code> {" "}
<code className="inline">--port</code> <code className="inline">--host</code>
{" "}
<code className="inline">codewhale app-server --mobile</code> {" "}
<code className="inline">--http</code>{" "}
API
</>
) : (
<>
<code className="inline">codewhale web</code> accepts only{" "}
<code className="inline">--port</code> there is no <code className="inline">--host</code>{" "}
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{" "}
<code className="inline">codewhale app-server --mobile</code> and{" "}
<code className="inline">--http</code> modes carry different deployment and authentication
contracts; read the Runtime API documentation before operating either one, especially
before selecting a non-loopback bind.
</>
)}
</p>
<h2 className="font-display text-2xl mb-1">{t.localTitle}</h2>
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.localLead)}</p>
</section>
<section id="troubleshooting" className="scroll-mt-32">
<h2 className="font-display text-2xl mb-1">{isZh ? "常见问题" : "Troubleshooting"}</h2>
<p className={`${bodyClass} mt-3`}>
{isZh
? "端口 7878 被占用时用 --port 换一个。浏览器无法打开时命令会报错退出,而不会留下可重用的引导凭证;检查系统默认浏览器设置后重新启动。页面能打开但 provider 不可用时,查 codewhale doctor 和 /provider——web 命令不配置也不迁移 provider 凭据。会话过期后重启 codewhale web 以签发新的进程本地会话;重用旧的引导 URL 本来就会失败。"
: "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."}
</p>
<h2 className="font-display text-2xl mb-1">{t.troubleshootingTitle}</h2>
<p className={`${t.bodyClassName} mt-3`}>{t.troubleshootingLead}</p>
</section>
<section id="source" className="hairline-t pt-8">
<p className="text-sm text-ink-mute">
{isZh
? "来源文档:docs/WEB.md · 更新时请同步修改 docs-map.ts。"
: "Source document: docs/WEB.md · Update docs-map.ts when changing."}
</p>
<p className="text-sm text-ink-mute">{t.sourceNote}</p>
</section>
</section>
);
+41
View File
@@ -6,6 +6,8 @@ import {
EN_DOCS_CONSTITUTION,
EN_DOCS_HOOKS,
EN_DOCS_RUNTIME_API,
EN_DOCS_SANDBOX,
EN_DOCS_WEB,
EN_DOCS_SHELL,
EN_DOCS_TROUBLESHOOTING,
EN_HOME,
@@ -15,6 +17,8 @@ import {
getDocsConstitution,
getDocsHooks,
getDocsRuntimeApi,
getDocsSandbox,
getDocsWeb,
getDocsShell,
getDocsTroubleshooting,
getHome,
@@ -247,6 +251,8 @@ describe("website dictionaries", () => {
["docs-troubleshooting", getDocsTroubleshooting, EN_DOCS_TROUBLESHOOTING],
["docs-constitution", getDocsConstitution, EN_DOCS_CONSTITUTION],
["docs-runtime-api", getDocsRuntimeApi, EN_DOCS_RUNTIME_API],
["docs-sandbox", getDocsSandbox, EN_DOCS_SANDBOX],
["docs-web", getDocsWeb, EN_DOCS_WEB],
] as const) {
const enKeys = Object.keys(reference).sort();
for (const locale of [...DICTIONARY_LOCALES, "fr", "und"]) {
@@ -277,6 +283,9 @@ describe("website dictionaries", () => {
getDocsRuntimeApi(locale).entries.map(([key]) => key),
`${locale} runtime entries`,
).toEqual(["http", "mobile", "stdio", "web", "doctor", "acp", "exec"]);
// The platform rows are keyed by their own translated name rather than a
// code-owned key, so only the count is comparable across locales.
expect(getDocsSandbox(locale).platforms, `${locale} sandbox platforms`).toHaveLength(4);
}
});
@@ -317,6 +326,38 @@ describe("website dictionaries", () => {
}
});
it("carries every code-span token through the sandbox and web copy", () => {
const tokensOf = (template: string) =>
splitTokens(template).flatMap((part) => ("token" in part ? [part.token] : []));
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
// Two of these repeat, and the order is the sentence's, so this is a
// stricter check than check-locales.mjs, which compares token sets.
expect(tokensOf(getDocsSandbox(locale).policiesLead), `${locale} policiesLead`).toEqual([
"sandboxMode",
"readOnly",
"workspaceWrite",
"dangerFullAccess",
"externalSandbox",
"dangerFullAccess",
"externalSandbox",
]);
const web = getDocsWeb(locale);
expect(tokensOf(web.overviewLead), `${locale} web overviewLead`).toEqual([
"webCommand",
"loopbackHost",
"defaultUrl",
"portExample",
]);
expect(tokensOf(web.localLead), `${locale} localLead`).toEqual([
"webCommand",
"portFlag",
"hostFlag",
"mobileCommand",
"httpFlag",
]);
}
});
it("splitTokens interleaves literal text and token names in template order", () => {
expect(splitTokens("a {one} b {two}")).toEqual([
{ text: "a " },
@@ -0,0 +1,43 @@
import type { DocsSandboxDict } from "../types";
/**
* English reference dictionary for `app/[locale]/docs/sandbox/page.tsx`.
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
* belongs in its own commit, never mixed into a structural move.
*/
export const docsSandbox: DocsSandboxDict = {
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.",
};
+28
View File
@@ -0,0 +1,28 @@
import type { DocsWebDict } from "../types";
/**
* English reference dictionary for `app/[locale]/docs/web/page.tsx`.
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
* belongs in its own commit, never mixed into a structural move.
*/
export const docsWeb: DocsWebDict = {
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.",
};
+24
View File
@@ -20,9 +20,11 @@ import type {
DocsGuideDict,
DocsHooksDict,
DocsRuntimeApiDict,
DocsSandboxDict,
DocsShellDict,
DocsModesDict,
DocsTroubleshootingDict,
DocsWebDict,
HomeDict,
} from "./types";
import { chrome as enChrome } from "./en/chrome";
@@ -45,6 +47,10 @@ import { docsModes as enDocsModes } from "./en/docs-modes";
import { docsModes as zhDocsModes } from "./zh/docs-modes";
import { docsRuntimeApi as enDocsRuntimeApi } from "./en/docs-runtime-api";
import { docsRuntimeApi as zhDocsRuntimeApi } from "./zh/docs-runtime-api";
import { docsSandbox as enDocsSandbox } from "./en/docs-sandbox";
import { docsSandbox as zhDocsSandbox } from "./zh/docs-sandbox";
import { docsWeb as enDocsWeb } from "./en/docs-web";
import { docsWeb as zhDocsWeb } from "./zh/docs-web";
import { chrome as zhChrome } from "./zh/chrome";
import { home as zhHome } from "./zh/home";
import { chrome as jaChrome } from "./ja/chrome";
@@ -182,6 +188,14 @@ const DOCS_RUNTIME_API: Record<string, DocsRuntimeApiDict> = {
zh: zhDocsRuntimeApi,
};
const DOCS_SANDBOX: Record<string, DocsSandboxDict> = {
zh: zhDocsSandbox,
};
const DOCS_WEB: Record<string, DocsWebDict> = {
zh: zhDocsWeb,
};
export function getChrome(locale: string): ChromeDict {
return CHROME[locale] ?? enChrome;
}
@@ -226,6 +240,14 @@ export function getDocsRuntimeApi(locale: string): DocsRuntimeApiDict {
return DOCS_RUNTIME_API[locale] ?? enDocsRuntimeApi;
}
export function getDocsSandbox(locale: string): DocsSandboxDict {
return DOCS_SANDBOX[locale] ?? enDocsSandbox;
}
export function getDocsWeb(locale: string): DocsWebDict {
return DOCS_WEB[locale] ?? enDocsWeb;
}
/**
* Select one side of a legacy `{ en, zh }` content pair by locale. This is
* the transitional bridge for `web/lib/content/` modules that still carry
@@ -249,6 +271,8 @@ export const EN_DOCS_CONSTITUTION = enDocsConstitution;
export const EN_DOCS_FLEET = enDocsFleet;
export const EN_DOCS_MODES = enDocsModes;
export const EN_DOCS_RUNTIME_API = enDocsRuntimeApi;
export const EN_DOCS_SANDBOX = enDocsSandbox;
export const EN_DOCS_WEB = enDocsWeb;
/** Interpolate `{name}` tokens in a dictionary template. Unknown tokens are
* left intact so a template/variable drift is visible in review, not silent. */
+32
View File
@@ -463,3 +463,35 @@ export interface DocsRuntimeApiDict {
securityLead: string;
sourceNote: string;
}
export interface DocsSandboxDict {
metaTitle: string;
metaDescription: string;
bodyClassName: string;
overviewTitle: string;
overviewLead: string;
/** Four `[name, detail]` rows; the name is also the row's key. */
platforms: [string, string][];
policiesTitle: string;
policiesLead: string;
diagnosticsTitle: string;
diagnosticsLead: string;
diagnosticsLimits: string;
sourceNote: string;
}
export interface DocsWebDict {
metaTitle: string;
metaDescription: string;
bodyClassName: string;
overviewTitle: string;
overviewLead: string;
overviewBody: string;
authTitle: string;
authLead: string;
localTitle: string;
localLead: string;
troubleshootingTitle: string;
troubleshootingLead: string;
sourceNote: string;
}
@@ -0,0 +1,38 @@
import type { DocsSandboxDict } from "../types";
/** 中文对照见 `en/docs-sandbox.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
export const docsSandbox: DocsSandboxDict = {
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。",
};
+23
View File
@@ -0,0 +1,23 @@
import type { DocsWebDict } from "../types";
/** 中文对照见 `en/docs-web.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
export const docsWeb: DocsWebDict = {
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。",
};
+2
View File
@@ -35,6 +35,8 @@ const OPTIONAL_FILES = [
"docs-fleet.ts",
"docs-modes.ts",
"docs-runtime-api.ts",
"docs-sandbox.ts",
"docs-web.ts",
];
/** Top-level keys of the exported object literal (two-space indented `key:`). */