mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-21 21:03:38 +08:00
Polish s16 READMEs into flowing essay voice
Rewrite EN/ZH/JA for tea-conversation cadence: soft transitions, insight before jargon, fewer checklist piles — same design spine, warmer prose. Co-authored-by: Xinlu Lai <CrazyBoyM@users.noreply.github.com>
This commit is contained in:
@@ -6,65 +6,58 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
|
||||
|
||||
> *"Chatting turn-by-turn is like texting the chef every ten seconds. A workflow is a recipe the kitchen can follow."*
|
||||
>
|
||||
> **Harness layer**: Orchestration — run a multi-agent script above the single-agent loop.
|
||||
> **Harness layer**: Orchestration — a multi-agent script above the single-agent loop.
|
||||
>
|
||||
> Trust the model; engineer the harness. Workflows are harness engineering at the orchestration layer.
|
||||
> Trust the model; engineer the harness. Workflows are that idea at the orchestration layer.
|
||||
|
||||
---
|
||||
|
||||
Imagine you are cooking with a friend over text. You send “chop the onions,” wait, ask “are they done?”, then “now the pan…”. It works for one dish. For a feast with twenty dishes, that chat becomes the bottleneck: you forget steps, repeat yourself, and if the phone dies you start over.
|
||||
Picture cooking with a friend over text. “Chop the onions.” Wait. “Done?” Then the pan, then the salt. One dish survives that rhythm. A feast with twenty plates does not: you forget steps, repeat yourself, and if the phone dies you start over cold.
|
||||
|
||||
That is ordinary model-as-orchestrator chatting. A **workflow** is the written recipe: the kitchen (runtime) follows it, helpers (subagents) do judgment, and intermediate bowls sit on the counter — not in the group chat.
|
||||
That is what it feels like when the model is both chef and clipboard — planning and doing inside the same chat. A **workflow** is the written recipe. The kitchen (the runtime) follows it. Helpers (subagents) taste and judge. The bowls of half-finished work sit on the counter, not in the group thread.
|
||||
|
||||
## Why a harness at all?
|
||||
## Why bother with another harness?
|
||||
|
||||
The default Claude Code harness is excellent at coding-shaped work: edit, run, read the error, try again — all in one loop.
|
||||
The default Claude Code harness is already good at coding-shaped work: change something, run it, read the error, try again. One loop, one mind, a surprising amount of craft.
|
||||
|
||||
Some jobs need a **custom harness on top**: deep research, security analysis, agent teams, large code review. You could hand-write that harness once in an SDK. Or — and this is the dynamic idea — Claude can **write a harness for this task on the fly**, run it, and optionally save the good ones.
|
||||
But some jobs are a different shape — deep research, security sweeps, agent teams, a review that fans out across a whole change set. For those, people have long built a second harness on top. You can still hand-write that layer in an SDK. Or — and this is the lively part — Claude can draft a harness **for this task**, run it, and keep the good ones.
|
||||
|
||||
Course motto, one layer up: trust the model inside each step; engineer the structure around the steps.
|
||||
Same course motto, one floor up: trust the model inside each step; decide the shape of the steps yourself.
|
||||
|
||||
## The problem: one window, three ways to fail
|
||||
## What goes wrong in a long chat
|
||||
|
||||
From s01 through s15, the model plans and executes in the **same** context. Great when the next move depends on what you just found. Weak when the job is long, massively parallel, rigidly structured, or adversarial.
|
||||
From s01 through s15, plan and action share one context window. That is wonderful when the next move depends on what you just found.
|
||||
|
||||
Claude Code’s designers name three failure modes that show up in that single window. In plain language:
|
||||
It frays when the job is long, massively parallel, rigidly structured, or needs a skeptical second opinion. Watch a long chat carefully and you will see familiar habits. It gets tired and declares victory after thirty-five of fifty review items. Asked to check its own homework, it grades kindly — the fox scoring the henhouse. And across many turns and compressions, the quiet constraint (“don’t touch X”) fades until nobody remembers why it was there.
|
||||
|
||||
| Failure mode | What it feels like |
|
||||
|--------------|--------------------|
|
||||
| **Agentic laziness** | Stops halfway through a fifty-item review and says “done” after thirty-five |
|
||||
| **Self-preferential bias** | Likes its own findings when asked to check itself — the fox grading the henhouse |
|
||||
| **Goal drift** | The original “don’t touch X” fades across many turns and compressions |
|
||||
Claude Code’s designers call these agentic laziness, self-preferential bias, and goal drift. The names matter less than the feeling: the same window that does the work is also trying to remember the plan. Chat history is a soft place to keep parallelism, stable result shapes, and a way to resume after a crash. Review-many-files, research-then-verify, migrate-N-modules — those jobs already know their shape. Soft memory is not enough.
|
||||
|
||||
Chat history is also a weak place to store parallelism, stable result shapes, and resume. You need those for review-many-files, research-then-verify, migrate-N-modules — jobs whose **shape** is already known.
|
||||
## The idea, once it clicks
|
||||
|
||||
## The idea in one breath
|
||||
What if the plan lived in code?
|
||||
|
||||
**Move orchestration from intelligence to structure.**
|
||||
Helpers still think — each at a clean desk, with one focused job. The **script** owns the loops, the fan-out, the merge. Intermediate results live in variables and a journal, not in the conversation. Laziness has a harder time stopping the fleet early. Self-checking bias meets a second helper who was not the author. Drift loses its grip because the topology is not rewritten every turn by a tired narrator.
|
||||
|
||||
Subagents still think — each in a clean context with a focused job. The **script** owns loops, fan-out, and merge. Intermediate results live in variables (and a journal), not in the conversation. Separate helpers + script-owned control flow is how you fight laziness, self-checking bias, and drift.
|
||||
In one line: workflows move orchestration from *intelligence* to *structure*. The model still judges inside each `agent()`; the script owns the map.
|
||||
|
||||

|
||||
|
||||
One `Workflow` tool call starts that scripted run. Lifecycle and progress events fire while it works; one tool result comes back with launch info, the result, and task state.
|
||||
One `Workflow` tool call starts that run. Progress ticks while it works; one tool result comes back with launch info, the outcome, and task state.
|
||||
|
||||
## Two doors — and dynamic vs static
|
||||
## Two doors into the same kitchen
|
||||
|
||||
Claude Code opens two doors into the same kitchen:
|
||||
Claude Code is straightforward about how you enter.
|
||||
|
||||
| Door | What you pass | When |
|
||||
|------|----------------|------|
|
||||
| **Dynamic** | A JavaScript orchestration script (`script`, or later `scriptPath`) | The model writes a recipe for *this* task |
|
||||
| **Saved** | `name` + `args` | A good recipe lives under e.g. `.claude/workflows/` and you rerun it |
|
||||
Sometimes the model writes a JavaScript orchestration script for *this* task and hands it over as `script` (or later edits `scriptPath`). That is the **dynamic** door — a harness tailored while the problem is still warm.
|
||||
|
||||
Same kitchen. Dynamic is “write the recipe now.” Saved is “pull the card from the box” — the reusable residue of a good dynamic run.
|
||||
Sometimes a good script has already been saved under something like `.claude/workflows/`. You call it by `name` and `args`. That is the **saved** door — the reusable residue of a run that earned its keep.
|
||||
|
||||
There is also a cousin outside this lesson: **static** harnesses (Agent SDK / `claude -p` orchestrations you write ahead of time). Static ones must work for every edge case, so they stay generic. Dynamic ones are tailor-made for *this* task; save them when the cut fits well.
|
||||
There is a cousin outside this lesson too: **static** harnesses you write ahead of time with the Agent SDK or `claude -p`. Those have to survive every edge case, so they stay generic. Dynamic ones are cut for *this* cloth; save them when the fit is right.
|
||||
|
||||
**This lesson is a Python teaching runtime.** Same ideas, every line readable. Our demo registers a saved workflow by name; concepts map 1:1 to Claude Code’s script world. We do **not** claim “the model cannot submit executable code” — that was wrong for Claude Code. We simply skip embedding a full JS interpreter here.
|
||||
**This chapter is a Python teaching runtime.** Same ideas, every line readable. Our demo registers one saved workflow by name; the concepts map one-to-one onto Claude Code’s script world. We will not pretend “the model cannot submit executable code” — that was never true of Claude Code. We simply do not embed a full JavaScript interpreter here.
|
||||
|
||||
```python
|
||||
# Teaching adapter: saved door (name + args).
|
||||
# Teaching adapter: the saved door (name + args).
|
||||
# Claude Code also accepts script / scriptPath / resumeFromRunId.
|
||||
WORKFLOW_TOOL = {
|
||||
"name": "Workflow",
|
||||
@@ -81,44 +74,35 @@ WORKFLOW_TOOL = {
|
||||
}
|
||||
```
|
||||
|
||||
## Primitives, taught with a kitchen story
|
||||
## A few kitchen verbs
|
||||
|
||||
You are running a school bake sale. Each table needs mix → bake → box. Helpers taste and judge; the recipe decides the order.
|
||||
Imagine a school bake sale. Every table needs mix → bake → box. Helpers taste; the recipe decides order.
|
||||
|
||||
| Primitive | Kitchen meaning |
|
||||
|-----------|-----------------|
|
||||
| `agent(prompt, {schema, label, phase})` | Ask one helper to do one job |
|
||||
| `pipeline(items, *stages)` | **Default.** Each cake goes through mix→bake→box on its own. Cake A can be boxing while cake B is still mixing |
|
||||
| `parallel(thunks)` | Wait until **every** tray comes back — only when the next step needs all of them together |
|
||||
| `phase(title)` | Announce “we’re in baking now” on the progress board |
|
||||
| `log(message)` | Shout a short status line |
|
||||
| `workflow(name, args)` | Call a smaller recipe (one level deep) |
|
||||
| `args` | The ingredients list passed into this run |
|
||||
| `budget` | How many “oven minutes” (tokens) you may spend |
|
||||
`agent(...)` is asking one helper to do one job. `pipeline(items, *stages)` is the default: each cake walks the stages on its own, so one can be boxing while another is still mixing. `parallel(...)` is the barrier — wait until every tray is back — and you only want that when the next step truly needs all of them together, like writing the scorecard after tasting the whole tray.
|
||||
|
||||
Default to `pipeline`. Reach for `parallel` only when the next step truly needs every prior result at once — like tasting all trays before writing the scorecard.
|
||||
Around those sit quieter verbs: `phase` to announce where you are on the board, `log` for a short shout, `workflow` to nest one smaller recipe, `args` for the ingredients list, `budget` for how many oven-minutes (tokens) you may burn.
|
||||
|
||||
```python
|
||||
# Each dimension walks audit → verify on its own (no barrier between stages).
|
||||
# Each review dimension walks audit → verify on its own.
|
||||
results = await ctx.pipeline(DIMENSIONS, audit, verify)
|
||||
confirmed = [f for r in results if r for f in r["confirmed"]]
|
||||
```
|
||||
|
||||
## Patterns with taste (not a laundry list)
|
||||
## Patterns, when they earn their keep
|
||||
|
||||
Think of patterns as recipe styles. Our sample `review-changes` leans on three:
|
||||
You do not need a catalog. Notice what our sample already does, and you have three styles in hand.
|
||||
|
||||
| Pattern | Plain meaning | In the sample |
|
||||
|---------|---------------|---------------|
|
||||
| **Fan-out-and-synthesize** | Split the work, give each piece a clean desk, then merge | Four dimensions audit in a `pipeline`, then one confirmed list |
|
||||
| **Adversarial verification** | A second helper tries to knock the first one’s work down | Each finding faces a verify agent before it counts |
|
||||
| **Generate-and-filter** | Produce candidates, keep only what survives a test | Findings in → only `isReal` out |
|
||||
It **fans out** the change across review dimensions, each on a clean desk, then **synthesizes** one confirmed list. That is fan-out-and-synthesize — useful when pieces would contaminate each other in one crowded context.
|
||||
|
||||
Same toolbox, other styles you will meet later: **classify-and-act** (route by type), **tournament** (compete, then pick a winner), **loop-until-done** (keep going until nothing new appears). Use a pattern only when its cost earns a clearer or safer result.
|
||||
Inside verify, a second helper tries to knock each finding down. That is adversarial verification — the structural answer to grading your own homework.
|
||||
|
||||
## Make answers machine-readable
|
||||
What survives is a filter over what was generated. Generate-and-filter: candidates in, only the ones that pass out.
|
||||
|
||||
If a helper returns a poem, the next stage cannot reliably zip findings to verdicts. Pass a `schema`: the runtime asks for JSON, validates it, and retries **once**. Fail again and that call errors (see null-isolation below).
|
||||
The same toolbox holds other cuts you will meet later — classify-and-act, tournament, loop-until-done. Borrow a style only when the extra cost buys clarity or safety you could not get cheaper.
|
||||
|
||||
## Answers the next stage can hold
|
||||
|
||||
If a helper returns a poem, the next stage cannot zip findings to verdicts. Pass a `schema`. The runtime asks for JSON, checks it, and gives **one** retry. Fail again and that call errors — which brings us to how the fleet stays kind under failure.
|
||||
|
||||
```python
|
||||
out = await ctx.agent(
|
||||
@@ -126,60 +110,49 @@ out = await ctx.agent(
|
||||
schema=FINDINGS_SCHEMA,
|
||||
label=f"audit:{dimension}",
|
||||
)
|
||||
# out is a dict with "findings", not a paragraph
|
||||
```
|
||||
|
||||
Free-form prose is fine for chatting with you. Pipelines need sockets that fit.
|
||||
Chat with you can stay prose. A pipeline needs sockets that fit.
|
||||
|
||||
## When one helper fails
|
||||
## When one tray burns
|
||||
|
||||
A fleet should not stop because one tray burned.
|
||||
The fleet should not stop because one helper had a bad oven.
|
||||
|
||||
- **`parallel`**: a failing thunk becomes `null` / `None` in that slot; the gather itself does not reject.
|
||||
- **`pipeline`**: a failing stage drops **that item** to `null` / `None` and skips its remaining stages; other items keep going.
|
||||
|
||||
Filter with care — usually `if r` / `.filter(Boolean)` — before you merge.
|
||||
In `parallel`, a failing thunk becomes `null` / `None` in that slot; the gather itself does not reject. In `pipeline`, a failing stage drops **that item** to null and skips its later stages; the other items keep walking. Filter with care before you merge — `if r`, or `.filter(Boolean)` in the JS world.
|
||||
|
||||
```python
|
||||
verdicts = await ctx.parallel([...]) # some entries may be None
|
||||
verdicts = await ctx.parallel([...]) # some slots may be None
|
||||
confirmed = [
|
||||
f for f, v in zip(findings, verdicts)
|
||||
if v and v.get("isReal")
|
||||
]
|
||||
```
|
||||
|
||||
## Journal + resume
|
||||
## A notebook you can reopen
|
||||
|
||||
Every run gets a `runId`. As each `agent()` finishes, the runtime appends a line to a journal on disk. Think of a notebook that lists helpers in the order you *called* them, not the order they wandered back from the oven.
|
||||
Every run gets a `runId`. As each `agent()` finishes, a line lands in a journal on disk — a notebook ordered by the moment you *called* the helper, not by who wandered back from the oven first.
|
||||
|
||||
On resume (`resume_from_run_id` / `resumeFromRunId`), the script runs from the top again, but:
|
||||
Resume (`resume_from_run_id` / `resumeFromRunId`) runs the script from the top again, but kindly. Call by call, in order, it matches the next journal line. The longest unchanged prefix replays from cache. At the first changed or unfinished call, the prefix breaks — and everything after runs live, even if an old key still sits further down the notebook. No silent leaps over a break.
|
||||
|
||||
1. Compare each `agent()` call, in call order, to the next journal line.
|
||||
2. **Longest unchanged prefix** → cache hits (instant replay).
|
||||
3. At the **first** changed or unfinished call, the prefix breaks.
|
||||
4. **Everything after that runs live** — even if an old key still sits later in the journal.
|
||||
|
||||
That is why real JS workflow runtimes ban `Date.now()`, `Math.random()`, and bare `new Date()`: nondeterministic clocks and dice change prompts or call order, and the notebook no longer matches. This Python demo does not fully sandbox that — still write deterministic scripts.
|
||||
That is also why real JavaScript workflow runtimes ban `Date.now()`, `Math.random()`, and bare `new Date()`. Clocks and dice make prompts or call order wobble, and the notebook stops lining up. This Python demo does not fully sandbox that. Write deterministic scripts anyway.
|
||||
|
||||
```text
|
||||
journal: [A ✓] [B ✓] [C ✓] [D ✓]
|
||||
resume: A hit → B hit → C changed → D runs live (no silent hit on old D)
|
||||
resume: A hit → B hit → C changed → D runs live
|
||||
```
|
||||
|
||||
## Walk the sample: `review-changes`
|
||||
## Walking `review-changes`
|
||||
|
||||
Four review dimensions walk the same two-stage path — fan-out, then adversarial verify, then filter:
|
||||
Four dimensions share one two-stage path — fan out, verify adversarially, keep what survives:
|
||||
|
||||
```text
|
||||
correctness ── audit ── verify ──┐
|
||||
security ── audit ── verify ──┤── merge confirmed findings
|
||||
security ── audit ── verify ──┤── confirmed findings
|
||||
performance ── audit ── verify ──┤
|
||||
style ── audit ── verify ──┘
|
||||
```
|
||||
|
||||
1. **Review** — each dimension’s auditor returns structured findings (clean desks → less cross-contamination).
|
||||
2. **Verify** — each finding gets an adversarial checker (`parallel` inside the verify stage) so the author is not also the judge.
|
||||
3. Keep only findings marked real; sort by severity.
|
||||
Review puts each auditor at its own desk so correctness talk does not bleed into security talk. Verify hands every finding to a skeptic who was not the author. Only the real ones remain, sorted by severity. You can almost feel the three failure modes losing their favorite seats.
|
||||
|
||||
```python
|
||||
async def sample_workflow(ctx, args):
|
||||
@@ -190,64 +163,46 @@ async def sample_workflow(ctx, args):
|
||||
return {"confirmed": confirmed}
|
||||
```
|
||||
|
||||
## How this plugs into s15
|
||||
## Hanging on s15 without replacing it
|
||||
|
||||
s15 is still the host loop. s16 adds one tool: `Workflow`. The model (or you) asks for a saved name; the adapter resolves the registry and runs the script.
|
||||
s15 is still the host loop. s16 only adds a tool named `Workflow`. You (or the model) ask for a saved name; the adapter finds the script and runs it.
|
||||
|
||||
| | Claude Code / Pi (product) | This teaching CLI |
|
||||
|--|----------------------------|-------------------|
|
||||
| Script language | JavaScript in a sandbox | Python functions you can read |
|
||||
| Dynamic door | Model writes `script` / edits `scriptPath` | Explained in docs; demo uses saved `name` |
|
||||
| Host while running | Background + notification; session stays responsive | `demo` / `resume` run in the foreground for clarity |
|
||||
| Ideas | Same primitives, journal, prefix resume | Teaching model — precise where we simplify |
|
||||
In the real product, that run can sit in the background with notifications while the session stays responsive. Our teaching CLI keeps `demo` and `resume` in the foreground so you can watch phases and cache hits without squinting. Same ideas; we say so when we simplify.
|
||||
|
||||
The main loop does not become a workflow engine. It borrows one tool, the way it borrows `bash` or `task`.
|
||||
The main loop does not become a workflow engine. It borrows one tool the way it borrows `bash` or `task`.
|
||||
|
||||
## Neighbors: who holds the plan?
|
||||
## Turning the gem: who holds the plan?
|
||||
|
||||
Workflows are not “more agents.” They change **who owns the topology**.
|
||||
Look at the neighbors and the same object shows a new face. The useful question is not “how many agents?” but **who owns the topology**, and where the half-finished bowls live.
|
||||
|
||||
| Neighbor | Who holds the plan | Where intermediate results live | Best for |
|
||||
|----------|--------------------|---------------------------------|----------|
|
||||
| [s06 Subagent](../s06_subagent/) | Model, one-shot | Discarded except final summary | Isolate one dirty subtask |
|
||||
| [s13 Agent Teams](../s13_agent_teams/) | Lead model turn-by-turn + mailbox | Shared tasks / messages | Long-running peers, human-like collaboration |
|
||||
| Neighbor | Who holds the plan | Where intermediates live | Best for |
|
||||
|----------|--------------------|--------------------------|----------|
|
||||
| [s06 Subagent](../s06_subagent/) | Model, one-shot | Mostly discarded | One dirty subtask, isolated |
|
||||
| [s13 Agent Teams](../s13_agent_teams/) | Lead, turn by turn + mailbox | Shared tasks / messages | Long-running peers |
|
||||
| [s15 Integrated Harness](../s15_integrated_harness/) | Model in one loop | Conversation `messages[]` | Cumulative coding agent |
|
||||
| **s16 Workflow** | **Script** | **Script variables + journal** | Known / large structured fan-out + verify |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | Evaluator at the stop boundary | Conversation as evidence | “Is the whole goal done?” |
|
||||
| **s16 Workflow** | **Script** | **Variables + journal** | Structured fan-out and verify |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | Evaluator at stop time | Conversation as evidence | “Is the whole goal done?” |
|
||||
|
||||
Cheaper alternatives still win often: a skill or prompt as a soft plan, a short multi-agent chat, a hand-written static SDK orchestrator, or simply one bigger model turn. Reach for a workflow when structure must outlast a single context — not because panels sound impressive.
|
||||
Cheaper paths still win often: a skill as a soft plan, a short multi-agent chat, a hand-written static orchestrator, or one larger model turn. Reach for a workflow when the structure must outlast a single context — not because a panel of reviewers sounds impressive.
|
||||
|
||||
## When *not* to use a workflow
|
||||
## And when to leave it on the shelf
|
||||
|
||||
Workflows cost tokens and coordination. Most ordinary coding does **not** need a panel of five reviewers.
|
||||
Workflows spend tokens and coordination. Most ordinary coding does not need five reviewers.
|
||||
|
||||
Ask: does this job really need more compute and a custom harness? If a normal s15 turn (or one s06 subagent) is enough, stop there. Restraint is part of the design thought — parallelism and specialization have to earn their keep.
|
||||
Before you spin one up, ask whether the job truly wants more compute and a custom harness. If a normal s15 turn — or one honest s06 subagent — will do, stop there. Restraint is part of the thought: parallelism and specialization have to earn their keep.
|
||||
|
||||
## Try it
|
||||
|
||||
```bash
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow tool (real API)
|
||||
python s16_workflow_runtime/code.py demo # fixed fixture: watch phases + agents
|
||||
python s16_workflow_runtime/code.py resume # same runId; prefix should be all cache hits
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow (real API)
|
||||
python s16_workflow_runtime/code.py demo # fixed fixture; watch phases
|
||||
python s16_workflow_runtime/code.py resume # same runId; expect cache hits
|
||||
```
|
||||
|
||||
What to watch for:
|
||||
Watch Review give way to Verify. Watch agents flip from `done` to `cached` on a full resume. At the end, a short confirmed list — and on a clean resume, `agents=0 tokens=0`, which is the notebook saying: nothing needed reheating.
|
||||
|
||||
- `workflow_phase` lines for Review, then Verify
|
||||
- each `workflow_agent` flip from `done` (first run) to `cached` (full resume)
|
||||
- a short confirmed list at the end; full resume shows `agents=0 tokens=0`
|
||||
## Next
|
||||
|
||||
## Relative to s15 → next is s17
|
||||
s16 is how a batch runs. [s17 Goal Loop](../s17_goal_loop/) asks a different question at the door: should we stop, or take another turn? Pair them when a repeatable recipe also needs a hard “done.”
|
||||
|
||||
| | s15 Integrated Harness | s16 Workflow Runtime |
|
||||
|--|------------------------|----------------------|
|
||||
| Loop | One model-driven loop | Same loop; one tool runs a script |
|
||||
| Who decides the next step | Model, each round | Script owns the batch shape |
|
||||
| Multi-agent | One-shot subagents | Scripted, resumable `agent()` calls |
|
||||
| Failure / resume | Conversation memory | Null-isolation + journal prefix |
|
||||
|
||||
**s16 = how a batch runs. s17 = whether the whole goal is done.**
|
||||
|
||||
[s17 Goal Loop](../s17_goal_loop/) asks an independent evaluator: should we stop, or take another turn? Pair them when a repeatable workflow also needs a hard completion check.
|
||||
|
||||
<!-- translation-sync: zh@v12, en@v12, ja@v12 -->
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
|
||||
Reference in New Issue
Block a user