Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions .claude/skills/wdl-deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ Open the relevant doc before answering:
- `docs/queues.md` — `[[queues.producers]]` / `[[queues.consumers]]` config,
queue handlers, message size and retry limits.
- `docs/workflows.md` — `[[workflows]]` config, the WDL Workflows surface,
`wdl workflows` instance management.
`wdl workflows` definition/instance management. Both list commands accept
`--limit`/`--cursor`; follow continuation even after an empty page, and
deduplicate definition entries by worker/name across concurrent changes. On
`workflow_metadata_contention` during definition listing, remove `--cursor`
before retrying.
- `docs/ai.md` — `[ai]` config, namespace provider/credential management,
Responses/tools/SSE, OpenAI SDK use, and WebSocket inference.
- `docs/kv.md` — `[[kv_namespaces]]`, immediately visible writes, batch reads,
Expand Down Expand Up @@ -74,24 +78,34 @@ Wrangler's temporary bundle config. `[ai]` is standard Wrangler configuration
and stays in that config for Wrangler validation. If a selected named
environment omits its own `ai`, the CLI warns that the top-level binding is not
inherited; WDL independently maps its `binding` into the WDL manifest. Other
fields retain their existing Wrangler passthrough behavior. Specific nested
fields that WDL cannot represent are rejected rather than silently dropped,
including Cloudflare Artifacts `triggers.events` subscriptions and R2
`local_dev.experimental_s3_credentials`. `[wdl] session_policy` accepts
`preserve` or `restart`. The default `preserve` leaves loaded Durable Object
facets on the version that built them until the host actor restarts or the facet
is deleted, and keeps established WebSockets draining while their backend stays
healthy. `restart` closes the worker's open WebSockets with code `1012` at
promotion and retires stale facets on their next dispatch, preserving SQLite
state. Wrangler's object-shaped declarative `exports` config is unsupported. The
dry-run child hides Wrangler's banner (and its normal update check) and disables
anonymous telemetry. Wrangler may still consult the configured npm registry when
reporting an unknown configuration field; project build hooks retain their
normal network access. For `[[services]]` and `[[exports]]`, read
`docs/deploy.md`: tenant JSRPC may delegate service or Durable Object class
stubs as opaque capabilities, but the receiver cannot rewrite their
host-authored caller properties. Keep delegated stubs in memory; long-term
irrevocable stub storage is unsupported.
fields retain their existing Wrangler passthrough behavior. `[[connect]]` TCP
listeners are unsupported and rejected before bundling. Specific nested fields
that WDL cannot represent are rejected rather than silently dropped, including
Cloudflare Artifacts `triggers.events` subscriptions and R2
`local_dev.experimental_s3_credentials`. `[[workflows]]` supports only `name`,
`binding`, and `class_name`; `script_name` and all other fields, including
`schedules`, `limits`, `default_retention`, and `concurrency`, are rejected
before bundling. Use per-instance `create()` retention instead of
`[[workflows]].default_retention`. `[wdl] session_policy` accepts `preserve` or
`restart`. The default `preserve` leaves loaded Durable Object facets on the
version that built them until the host actor restarts or the facet is deleted,
and keeps established WebSockets draining while their backend stays healthy.
`restart` closes the worker's open WebSockets with code `1012` at promotion and
retires stale facets on their next dispatch, preserving SQLite state. Wrangler's
object-shaped declarative `exports` config is unsupported. The dry-run child
hides Wrangler's banner (and its normal update check) and disables anonymous
telemetry and automatic agent skills installation, updates, and prompts.
Bundling keeps stdin closed even with `--verbose`, which still forwards
stdout/stderr. Wrangler may still write local metrics state and debug logs, and
consult the configured npm registry when reporting an unknown configuration
field; project build hooks retain their normal network access. For
`[[services]]` and `[[exports]]`, read `docs/deploy.md`: tenant JSRPC may
delegate service or Durable Object class stubs as opaque capabilities, but the
receiver cannot rewrite their host-authored caller properties. Keep delegated
stubs in memory; long-term irrevocable stub storage is unsupported.

If a command reports `Missing namespace`, supply the intended tenant with
`--ns <namespace>` or `WDL_NS` before retrying.

`CONTROL_URL` may include a path prefix, but embedded usernames/passwords, query
strings, and fragments are rejected. A bare `.local` host defaults to HTTPS
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

## Unreleased

### Changed

- Upgrade the bundled Wrangler to 4.131.0 and reject unmapped `[[connect]]` TCP
listeners and `[[workflows]]` fields, including `schedules`, `limits`,
`default_retention`, and `concurrency`, instead of silently dropping them.
- Support `--limit` and `--cursor` for Workflow definition listing and display
continuation cursors even for empty definition pages.
- Disable Wrangler skills installation/update prompts during deploy. Bundling
keeps stdin closed even with `--verbose`, which still forwards stdout/stderr.

### Fixed

- Distinguish empty intermediate Workflow instance pages from an empty result
set when Control returns a continuation cursor.
- Explain how to restart Workflow definition listing after cursor metadata
contention, even when Control redacts the error message.
- Explain how to set `--ns` or `WDL_NS` when a required namespace is missing.

### Security

- Update `smol-toml` to 1.7.1 and Wrangler's transitive `sharp` to 0.35.4,
clearing their dependency advisories without overrides.

## 1.8.1

### Changed
Expand Down
14 changes: 11 additions & 3 deletions GUIDE-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ ADMIN_TOKEN=<acme-staging-token>

CLI 只会从 `.env` 读取 WDL 平台变量:`ADMIN_TOKEN`、`CONTROL_URL`、`CONTROL_CONNECT_HOST`、`WDL_NS`。优先级是 `CLI flag > shell/CI env > [resolved-ns] section > base .env > wdl token store`,都没有提供时命令直接报错——没有内置默认值。namespace 解析顺序是 `--ns`,然后是 shell 或 base `.env` 里的 `WDL_NS`,再然后是 token store 的默认 namespace。section 名可以是 `[acme]` 这类 tenant namespace,也可以是 `[__name__]` 这种运维保留的不透明 section。Tenant Wrangler 配置默认仍使用普通 tenant namespace 语法,除非运维方明确给了这种 namespace token;否则不要把 `__name__` 形态写进 `[[services]].ns`、`allowed_callers` 或命令示例。如果没有解析出 namespace,section 会全部跳过;后续命令如果需要 namespace 或 token,会按正常校验报错。只有临时切换 namespace 时才需要显式传 `--ns`。不带 scheme 的生产 control host(例如 `api.wdl.dev`)默认补 `https://`;loopback 和保留的 `*.test` host 默认补 `http://`。既有的裸 `:8080` 例外对任何 host(包括 `.local`)仍默认使用 HTTP。除此之外,`.local` 是局域网 / mDNS 后缀而不是 loopback,因此裸 host 默认 HTTPS;所有 HTTP `.local` 目标都会显示明文 token 告警。需要强制使用其它协议时,请显式写 scheme。Control URL 可以包含 path prefix,但不能嵌入 username/password,也不能包含 query string 或 fragment,因为命令会在这个 base URL 后追加 endpoint path,并单独发送 admin token。

如果命令报告 `Missing namespace`,请传入 `--ns <namespace>` 或设置 `WDL_NS` 后重试。

`CONTROL_CONNECT_HOST` 是本地开发 / 调试用的覆盖开关:它改变请求实际连接的 TCP 目标,而 HTTP Host header 和 TLS SNI 仍跟随 `CONTROL_URL`(所以 HTTPS 下控制面证书仍会拒绝被重定向的连接;纯 http 没有这层保护)。只在本地开发用 —— 不要在 CI 或生产 shell 中持久设置,残留值可能把 admin token 路由到非预期目标。覆盖值写成 URL 时,scheme 只决定默认 TCP 端口(`http` 为 80,`https` 为 443);请求使用 HTTP 还是 HTTPS、Host 和 SNI 仍由 `CONTROL_URL` 决定。

推荐的做法是把这些凭证放进托管存储,而不是 shell export 或项目 `.env`:`wdl token set --ns <ns> --control-url <url>` 用隐藏输入读取 token、调 `/whoami` 校验后按 namespace 存入 `~/.config/wdl/credentials`(不进 shell 历史、也不落在项目文件里)。存储是优先级最低的层——命令行标志、shell env、项目 `.env` 仍然胜出——`wdl token list` / `wdl token rm` 管理它。第一个存入的 namespace 成为默认(一行 base `WDL_NS`,和项目 `.env` 一样),命令不带 `--ns` 也能跑;`wdl token use <ns>` 切换默认。CLI 会拒绝 symlink / 非普通文件形式的 credentials 路径;在 POSIX 上,如果 store 文件不属于当前用户、store 目录可被 group/other 写或文件能被 group/other 访问,也会拒绝读取。详见 [token-zh.md](./docs/token-zh.md)。
Expand Down Expand Up @@ -150,7 +152,7 @@ APP_NAME = "hello"

Control 会拒绝早于 `2026-04-01` 的显式日期、无效或未来日期,以及超出 bundled workerd 支持范围的日期。上游 experimental enable flags、`legacy_error_serialization` 和 `allow_irrevocable_stub_storage` 不受支持。这些校验由 control canonical 持有,CLI 不复制 workerd flag table。

你可以继续使用 `wrangler dev` 做本地开发;部署到本平台时改用 `wdl deploy`。平台部署命令会调用 `wrangler deploy --dry-run`(Wrangler v4)打包项目,解析顺序是 `WDL_WRANGLER_BIN`、Worker 项目本地 wrangler、CLI 包本地 wrangler、最后是 `PATH`。TypeScript、模块解析、esbuild 打包等流程仍走 wrangler 的标准路径。WDL 会隐藏这个 dry-run 子进程的 Wrangler banner(因此跳过常规 banner 更新检查)并关闭匿名遥测。Wrangler 在报告未知配置字段时仍可能访问已配置的 npm registry;项目 build hook 仍保留正常的网络访问能力。
你可以继续使用 `wrangler dev` 做本地开发;部署到本平台时改用 `wdl deploy`。平台部署命令会调用 `wrangler deploy --dry-run`(Wrangler v4)打包项目,解析顺序是 `WDL_WRANGLER_BIN`、Worker 项目本地 wrangler、CLI 包本地 wrangler、最后是 `PATH`。TypeScript、模块解析、esbuild 打包等流程仍走 wrangler 的标准路径。WDL 会隐藏这个 dry-run 子进程的 Wrangler banner(因此跳过常规 banner 更新检查),关闭匿名遥测及自动 agent skills 安装、更新和提示。即使使用 `--verbose`,打包时 stdin 也保持关闭,但仍透传 stdout/stderr。Wrangler 仍可能写入本地 metrics 状态和调试日志,并在报告未知配置字段时访问已配置的 npm registry;项目 build hook 仍保留正常的网络访问能力。

如果同时存在多个 Wrangler 配置文件,WDL 跟随 Wrangler 的优先级:`wrangler.json`,然后 `wrangler.jsonc`,最后 `wrangler.toml`。

Expand Down Expand Up @@ -268,6 +270,8 @@ Wrangler 能打包、但 WDL 不能运行的形状由 control plane 作为 canon

WDL 会自行消费 `[[exports]]`、`[[platform_bindings]]`、`[[triggers.schedules]]`、`[[services]].ns` 和 `[wdl]`,并从传给 Wrangler bundler 的临时配置中移除这些 WDL 扩展。`[ai]` 是 Wrangler 标准配置,会保留在临时配置中供 Wrangler 校验;如果选中的 named environment 没有自己的 `ai`,CLI 会提示顶层 binding 不会继承。WDL 另行只接受其中的 `binding` 字段,并把该声明映射到 WDL manifest。其它字段保持既有的 Wrangler 透传行为。WDL 不支持 Wrangler 对象形态的 declarative `exports` 配置。

`[[connect]]` TCP listener 没有对应的 WDL runtime 映射,顶层和所选 environment 内的声明都会在打包前被拒绝。

Cron triggers 和 queue consumers 是运行时 dispatch 能力。除非管理方明确给了 reserved namespace,否则只应声明在 tenant namespace 里的可路由 Worker 上。通过 `[[platform_bindings]]` 选择的 Worker 是冷加载的平台能力,不是公开/runtime dispatch 目标,不能声明 cron triggers 或 queue consumers。

R2 custom metadata key 读取时会按 HTTP header 语义归一成小写。R2 object head 会暴露 HTTP metadata 和 custom metadata,所以鉴权上与读取 object body 同级,不开放给 observer 角色。R2 支持条件请求、range GET 和 `list({ include: [...] })` metadata hydration。 `list({ include })` 会在并发上限内额外发起 HEAD;只有列表结果确实需要 metadata 时再打开。
Expand Down Expand Up @@ -541,8 +545,8 @@ class_name = "OrderWorkflow"
用 `wdl workflows` 查看定义和管理实例:

```bash
wdl workflows list
wdl workflows instances api orders
wdl workflows list [--limit <n>] [--cursor <c>]
wdl workflows instances api orders [--limit <n>] [--cursor <c>]
wdl workflows status api orders order-123 --include-steps
wdl workflows pause api orders order-123
wdl workflows resume api orders order-123
Expand All @@ -554,8 +558,12 @@ wdl workflows terminate api orders order-123 --yes

`wdl workflows list` 会把 active Worker version 不再导出的定义标为 `retired=yes`。既有实例仍可查看和 terminate,但 restart 会返回 `workflow_not_exported`;需要先部署一个重新导出该 workflow name 的 active version。

两个 Workflow list 命令都接受 `--limit` 与 `--cursor`;即使返回空页或短页,也应继续使用返回的 cursor。Definition 只保证页内排序;namespace 并发变化可能让同一个 `worker`/`name` 组合跨页重复,收集完整视图时应按该组合去重或去掉 cursor 重新开始。`workflows list` 返回 `workflow_metadata_contention` 时,如果传了 `--cursor`,CLI 会提示去掉它重新开始;未带 cursor 的首次列表请求可直接重试。

这是 WDL Workflows 支持,不是完整 Cloudflare Workflows parity。 `script_name`、跨 worker workflow、跨 worker callback、service-binding callback 和 Cloudflare source-AST visualizer 不支持。same-worker DO progress callback 和 runtime-observed parallel/DAG `step.do` execution 可用。

WDL 的 `[[workflows]]` 只支持 `name`、`binding` 和 `class_name`。`script_name` 及其它所有字段(包括 `schedules`、`limits`、`default_retention` 和 `concurrency`)都会在打包前被拒绝,所选 environment 内的声明也一样。单个 instance 的 retention 仍可通过 `create()` 配置。

重要运行限制和编程规则:

- `createBatch()` 每次最多接受 100 个 entry。单个 workflow result 上限是 1 MiB;一次 runtime 到 Workflows backend 的 JSON 请求上限是 2 MiB。
Expand Down
33 changes: 28 additions & 5 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ send the admin token separately. If no namespace resolves, section values are
skipped and the command will fail normally if it needs a namespace or token.
Pass `--ns` when you want to override the default for one command.

If a command reports `Missing namespace`, pass `--ns <namespace>` or set
`WDL_NS` before retrying.

`CONTROL_CONNECT_HOST` is a local-dev / debug override: it changes the TCP
target the request connects to while the HTTP Host header and TLS SNI keep
tracking `CONTROL_URL` (so over HTTPS the control plane's certificate still
Expand Down Expand Up @@ -255,9 +258,12 @@ wrangler, then `PATH`. TypeScript, module resolution, esbuild bundling, and
related build behavior still follow Wrangler.

WDL hides Wrangler's banner (which skips the normal banner update check) and
disables anonymous telemetry for this dry-run subprocess. Wrangler may still
consult the configured npm registry when reporting an unknown configuration
field. Project build hooks retain their normal network access.
disables anonymous telemetry and automatic agent skills installation, updates,
and prompts for this dry-run subprocess. Bundling keeps stdin closed even with
`--verbose`, which still forwards stdout/stderr. Wrangler may still write local
metrics state and debug logs, and consult the configured npm registry when
reporting an unknown configuration field. Project build hooks retain their
normal network access.

When several Wrangler config files exist, WDL follows Wrangler's priority:
`wrangler.json`, then `wrangler.jsonc`, then `wrangler.toml`. Both JSON
Expand Down Expand Up @@ -426,6 +432,9 @@ maps that declaration into the WDL manifest. Other fields retain their existing
Wrangler passthrough behavior. Wrangler's object-shaped declarative `exports`
configuration is not supported by WDL.

`[[connect]]` TCP listeners have no WDL runtime mapping and are rejected before
bundling, both at the top level and in the selected environment.

Cron triggers and queue consumers are dispatch features. Declare them only on
routeable Workers in tenant namespaces unless your operator gives you an
explicit reserved namespace. Workers selected through `[[platform_bindings]]`
Expand Down Expand Up @@ -847,8 +856,8 @@ class_name = "OrderWorkflow"
Use `wdl workflows` to inspect definitions and manage instances:

```bash
wdl workflows list
wdl workflows instances api orders
wdl workflows list [--limit <n>] [--cursor <c>]
wdl workflows instances api orders [--limit <n>] [--cursor <c>]
wdl workflows status api orders order-123 --include-steps
wdl workflows pause api orders order-123
wdl workflows resume api orders order-123
Expand All @@ -865,12 +874,26 @@ rejected locally outside that range. `--step-limit` may be used only with
restart returns `workflow_not_exported` until an active version exports that
workflow name again.

Both Workflow list commands accept `--limit` and `--cursor`. Follow the reported
cursor even after an empty or short page. Definition ordering is page-local;
concurrent namespace changes can repeat a `worker`/`name` pair across pages, so
deduplicate by that pair or restart without a cursor when collecting a complete
view. When `workflows list` returns `workflow_metadata_contention`, the CLI
advises restarting without `--cursor` when one was supplied; retry an initial
listing without a cursor.

This is WDL Workflows support, not full Cloudflare Workflows parity.
`script_name`, cross-worker workflows, cross-worker callbacks, service-binding
callbacks, and Cloudflare source-AST visualizer are not supported. Same-worker
DO progress callbacks and runtime-observed parallel/DAG `step.do` execution are
available.

WDL supports only `name`, `binding`, and `class_name` in `[[workflows]]`.
`script_name` and all other fields, including `schedules`, `limits`,
`default_retention`, and `concurrency`, are rejected before bundling, including
in the selected environment. Per-instance retention remains configurable through
`create()`.

Important runtime limits and programming rules:

- `createBatch()` accepts at most 100 entries per call. A single workflow result
Expand Down
4 changes: 3 additions & 1 deletion commands/ai.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
formatHelp,
isMain,
isPathInside,
missingNamespaceError,
optionHelp,
readJsonOrFailWithHint,
redactedArgumentError,
Expand Down Expand Up @@ -85,7 +86,8 @@ async function runAi({ values, positionals, context }) {
}

const ns = context.resolveNamespace();
if (!group || !ns) throw new CliError(usageText());
if (!group) throw new CliError(usageText());
if (!ns) throw missingNamespaceError();

if (group === "models") {
if (action) throw redactedArgumentError("ai models");
Expand Down
4 changes: 3 additions & 1 deletion commands/d1.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
formatHelp,
isMain,
isPathInside,
missingNamespaceError,
optionHelp,
unexpectedArgument,
} from "../lib/common.js";
Expand Down Expand Up @@ -74,7 +75,8 @@ async function runD1({ values, positionals, context }) {

const [subcommand, firstArg] = positionals;
const ns = context.resolveNamespace();
if (!subcommand || !ns) throw new CliError(usageText());
if (!subcommand) throw new CliError(usageText());
if (!ns) throw missingNamespaceError();

if (subcommand === "migrations") {
const action = firstArg;
Expand Down
Loading