docs(rust): console commands built — command.run (PROTOCOL §20.11, PLAN_REDESIGNS §16.10, D385)
- rust-link/PROTOCOL.md §20.11: command.run in its three shapes (once with a
stored teardown, per online recipient with {steamid}/{name}, teardownOf),
the at-most-once key file, the frame budget, command.ran / command.error
and their reasons, how unknown-command is told (the game's log line, not
Run's return), POST /commands/run, and the website side.
- modules/rust/PLAN_REDESIGNS.md: D385 (the bridge keeps a step's teardown
with its key, asked during the build), §16.10 the build record (three
spike findings, the rig run, twelve settled-in-code points), a D385 walk
row in §16.9, pointers from the status paragraph, §9 and §10.
- website/EVENTS.md: an action can be text the module cannot read: what
core already bounds, and the two things a module copying it needs (a
failed step's revert has only the key; `multiple` drops duplicates).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -3400,3 +3400,103 @@ kit reward (§20.2), and it takes effect at once.
|
||||
|
||||
**The website (Module-Rust).** Server settings' plugin line says whether ZLevels is loaded and, where it is,
|
||||
that an event may lend its XP boosts as permissions. Nothing else reads the field.
|
||||
|
||||
### 20.11 Console commands — `command.run` (PLAN_REDESIGNS §16, D382–D385)
|
||||
|
||||
An event can run **console commands an admin typed**: once, as a step, with teardown commands for when the run
|
||||
ends, or once for each recipient of a reward who is online. This lets a plugin that has no support of its own
|
||||
here be used from the scheduler, for example RaidableBases' `rbe` or `sr add {steamid} 100`. **There is no
|
||||
allowlist (D384).** The site's switchboard is the switch, and here `EventsEnabled` is the server's, as for every
|
||||
world verb (§15). The design is [`PLAN_REDESIGNS.md`](../modules/rust/PLAN_REDESIGNS.md) §16, and what the
|
||||
build found is §16.10 there.
|
||||
|
||||
**`command.run`**, in three shapes:
|
||||
|
||||
```json
|
||||
{"cmd":"command.run","reqId":"r-64","key":"<40>","commands":["rbe 1","say a raid base is up"],"teardown":["rbe stop"]}
|
||||
{"cmd":"command.run","reqId":"r-65","key":"<40>","commands":["sr add {steamid} 100"],
|
||||
"recipients":[{"steamId":"76561198000000001","name":"Alice"}]}
|
||||
{"cmd":"command.run","reqId":"r-66","key":"<40>:teardown","teardownOf":"<40>"}
|
||||
```
|
||||
|
||||
- **Without `recipients`** the commands run once, in order. **`teardown` is stored with the key and not run**
|
||||
(D385).
|
||||
- **With `recipients`** (at most 100) the commands run once per recipient who is **connected at that moment**.
|
||||
The bridge fills **`{steamid}`** with the Steam id and **`{name}`** with the player's in-game name, **as one
|
||||
quoted argument**, quoted the way the game's own `BuildCommand` quotes one: `"` becomes `\"`, trailing
|
||||
backslashes are dropped, and control characters are removed first. A name with spaces, quotes or `;` stays
|
||||
inside it (`;` does not separate console commands either). Both placeholders ignore case. Anyone offline is
|
||||
**skipped, never held** (D383). A reward carries no `teardown`.
|
||||
- **`teardownOf`** runs the teardown stored under that key, under its own `key`, **when anything there may have
|
||||
run**: some of its commands ran, or a reload cut the run off. Otherwise it is answered with nothing run and
|
||||
nothing recorded, which is what a server the step never reached says.
|
||||
- At most **10** commands per field, each one line of at most **512** characters. Placeholders outside a
|
||||
reward are refused.
|
||||
|
||||
**At most once, by the key.** A command cannot be asked whether it already ran, so the key is written to
|
||||
`data/RunicGateway/commands-run.json` **before anything runs** (at most 500 keys, kept seven days):
|
||||
|
||||
- a repeat of a finished key is answered from its record, `repeat: true`, and runs nothing;
|
||||
- a repeat **while the first is still running** waits for it and gets the same answer (the sidecar's 10 s
|
||||
timeout is shorter than a step's 15 s budget, so a long reward can be retried while it runs);
|
||||
- a key whose record a restart or a reload left running is answered `interrupted`: some of it may have run,
|
||||
and none is run again.
|
||||
|
||||
**Main thread, spread over frames.** A recipient's commands run together in one frame, and a frame takes no
|
||||
more recipients past 30 commands or 8 ms. A run without recipients (at most 10 commands) is one frame. Each
|
||||
command runs through `ConsoleSystem.Run` **not quiet**, so the server console shows it and its answer as if
|
||||
typed.
|
||||
|
||||
**The answer:**
|
||||
|
||||
```json
|
||||
{"kind":"command.ran","type":"reply","reqId":"r-65","key":"<40>","ran":1,"rewarded":1,"repeat":false,
|
||||
"replies":[{"command":"sr add 76561198000000001 100","steamId":"76561198000000001","reply":"..."}],
|
||||
"skipped":["Bob"],"skippedCount":1}
|
||||
```
|
||||
|
||||
`ran` counts the commands run, and `rewarded` the recipients they ran for. **`replies` holds the first 20
|
||||
commands as run** (placeholders filled), each with what the console answered, trimmed to 200 characters:
|
||||
`Run`'s return, or, when it returns nothing, what the game logged while the command ran. **`skipped` names
|
||||
the first 50 skipped as offline**, and `skippedCount` counts them all. Refusals are
|
||||
`{"kind":"command.error","reason":…,"message":…}`, carrying the same `key`, `ran`, `replies` and `skipped`
|
||||
when anything ran first:
|
||||
|
||||
| `reason` | When |
|
||||
|---|---|
|
||||
| `malformed` | No key or one over 128 characters; no commands; more than 10, one over 512 characters or with a line break; a recipient that is not a Steam id, or more than 100; `{steamid}`/`{name}` with nobody to fill them; a reward with a `teardown` |
|
||||
| `unknown-command` | **The server does not know a command.** The run stops there, before the command runs for anyone else. The message adds that a plugin's command is not found on a framework where `console.plugins` is false (§20.5, D363) |
|
||||
| `interrupted` | A restart or a reload cut this key's run off (above) |
|
||||
| `busy` | A `teardownOf` for a run that is still going: ask again |
|
||||
| `failed` | The run threw, in its words |
|
||||
|
||||
`events-disabled` and the world's `not-ready` come first, as `world.error`, as for every world verb (§15).
|
||||
|
||||
**How `unknown-command` is told.** `ConsoleSystem.Run` returns `null` both for a command the server does not
|
||||
know and for one that replies nothing, so the return value cannot tell them apart. The game logs
|
||||
*`Command '<line>' not found`* **synchronously, inside the call**, on Oxide and on Carbon alike (spike,
|
||||
2026-10-16, Oxide 2.0.7830, Carbon 2.0.262, Rust 25902425). So the bridge listens to Unity's log
|
||||
(`Application.logMessageReceived`) while each command runs. That line, for this command, is
|
||||
`unknown-command`; anything else it hears is the reply when `Run` returns nothing.
|
||||
|
||||
**It knows only that a command ran and what the console said.** A plugin's reply is its own text, so "it ran"
|
||||
is not "it worked". Nothing that follows reaches the feed, the map, a title or a trigger.
|
||||
|
||||
`rg.world` prints `commandKeys=` (keys remembered) and `commandsRunning=`.
|
||||
|
||||
**The sidecar** adds `POST /commands/run`, a thin forward that keeps nothing.
|
||||
|
||||
**The website (Module-Rust).** Two steps, budget `rust.commands`, both `irreversible`, both with a required
|
||||
*"I am sure"* toggle; the commands are core's `multiple` string field, a free text box:
|
||||
|
||||
- **`rust.command.run`**, *Run console commands* (`reversible: 'ledger'`): `server`, `commands`, optional
|
||||
`teardown`. It sends `{ key, commands, teardown }`. Its ledger row (`command-teardown`) carries **its own
|
||||
copy** of the teardown, and core's revert sends it as `{ key: <key>:teardown, commands }`. **A step that failed
|
||||
or whose answer was lost has no row** (core records nothing then), so its revert has only the key. It sends
|
||||
`{ key: <key>:teardown, teardownOf: <key> }` to every enabled server (D385). Priced one unit per command,
|
||||
teardown included.
|
||||
- **`rust.command.reward`**, *Reward by console command* (`reversible: 'none'`): the kit reward's recipient
|
||||
modes, `linked` among them. It sends Steam ids and names, so no website account is needed, and never fills a
|
||||
placeholder itself. Priced commands × people, the 100-recipient bound for a mode that is not a count.
|
||||
- `unknown-command`, `malformed`, `interrupted`, `failed` and `events-disabled` are permanent. The error says
|
||||
how many commands ran before it. A game that is down retries on core's ordinary delay.
|
||||
|
||||
Reference in New Issue
Block a user