diff --git a/.changeset/26756-automation-skill-screen-progress.md b/.changeset/26756-automation-skill-screen-progress.md new file mode 100644 index 00000000000..4189e9d0221 --- /dev/null +++ b/.changeset/26756-automation-skill-screen-progress.md @@ -0,0 +1,12 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): objectstack-automation says a paused screen's Next / Submit and step count are engine-served, never authored + +Clause-②: no +Metadata-protocol: none + +- **Before.** The `screen` node row described the fields an author declares and said nothing about the primary button or the step counter. An agent writing a wizard from the skill could hand-roll a "Submit" label or a "Step 2 of 4" into `fields`, or try to author `progress` on the screen node, and the skill did not say that the runtime answers both from the flow graph. +- **Now.** The `screen` node row says that Next / Submit and "Step N of M" are not authored: when a screen pauses the engine reads the graph and serves `progress` (`nextScreen: 'none' | 'possible' | 'certain'`; `step` / `stepCount` only when every path agrees), and the runner labels the button and draws the counter from that alone. A `decision`, a `fault` edge, a loop back, a non-screen pause (`approval`, `wait`) or a `subflow` after a screen answers `possible`, which shows "Next" with no count. A `progress` key in the node `config` is refused; a hand-rolled label or counter in `fields` is wrong. +- No schema, key or behaviour changes. No eval prompt: `evals/flows-triggers-approvals.json` sits at token-ratchet headroom 0. diff --git a/scripts/check-skills-token-ratchet.mjs b/scripts/check-skills-token-ratchet.mjs index 74134bf99b9..797b59d45d0 100644 --- a/scripts/check-skills-token-ratchet.mjs +++ b/scripts/check-skills-token-ratchet.mjs @@ -420,7 +420,28 @@ export const CEILINGS = new Map([ // the measurement. Maintainer ruling 2026-10-27 on the seat's decision card // 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A verbatim and // untranslated: 「A:先削重复,余额按实测提高」. - ['skills/objectstack-automation/SKILL.md', 6358], + // 25429 -> 25864 (#26756): the `screen` node row says Next / Submit and + // "Step N of M" are engine-served, never authored — when a screen pauses + // the engine reads the graph and serves `progress` (`nextScreen` `none` / + // `possible` / `certain`; `step` / `stepCount` only when every path + // agrees), a `decision`, `fault` edge, loop back, non-screen pause or + // `subflow` after a screen answers `possible` ("Next", no count), a + // `progress` key in the node config is refused, and a hand-rolled label or + // counter in `fields` is wrong. Paid down first, three restatements each + // with a surviving home: the Filter-operands bullet's "every other `{…}` + // token is refused" (Valid-but-silently-wrong item 1 lists every CRUD + // `filter` operand, -42 bytes); the Trigger Configuration lead-in "The + // binding is the START NODE" (the Triggers intro says the binding lives on + // the start node `config`, -23 bytes); item 3's "; flow functions don't" + // (its own bold lead says a flow function does not read or write the + // database, -22 bytes), plus the trimmed bullet's re-wrap indentation (-2 + // bytes of whitespace). Arithmetic in priced bytes (stamp line excluded): + // 25429 -> 25864 (+524 added, -89 cut, +435 net) -> ceil(25864 / 4) = + // 6466; headroom was 3 bytes, 0 tokens, so the ceiling moves +108, pinned + // AT the measurement. Maintainer ruling 2026-10-27 on the seat's decision + // card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A + // verbatim and untranslated: 「A:先削重复,余额按实测提高」. + ['skills/objectstack-automation/SKILL.md', 6466], // 13783 -> 13892 (2026-08-31 app-repo-principles raise, see the block above). // The invariant-vs-transition-gate choice, placed in the field-conditional-rule // bullet list where the tool is actually picked. Both wrong picks are silent: diff --git a/skills/objectstack-automation/SKILL.md b/skills/objectstack-automation/SKILL.md index 3ebc073087e..e36ab008a0e 100644 --- a/skills/objectstack-automation/SKILL.md +++ b/skills/objectstack-automation/SKILL.md @@ -88,7 +88,7 @@ Flows are built from **20 built-in node types** (plugins register more via | `notify` | Send a notification through the messaging service (inbox channel by default) | | `connector_action` | Invoke a pre-built integration connector | | `script` | Call a **registered** function named by `config.function` (see *Valid-but-silently-wrong* #3) | -| `screen` | Display a UI form to the user (screen flows only). `config.fields[]`: `{ name, label, type, required, options, defaultValue }`, `type` an open widget hint; `'file'` renders the upload widget and resumes with the `sys_file` id bound to `name` — a later `create_record` / `update_record` writes it into a `file` field, which claims the file: collect first, write after the last screen. `'record_link'` (never `'record'`, an object field type) takes no input — it shows the `reference` record whose id `defaultValue` evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, `{ name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } }` over a `create_record` `outputVariable: 'account'`. Both keys required; `required` / `options` / `placeholder` / `min` / `max` refused; a resume value under its name refused (`400 INVALID_SCREEN_INPUT`). A completed `end` `message` is refused and a markdown link in a screen `description` renders literally | +| `screen` | Display a UI form to the user (screen flows only). `config.fields[]`: `{ name, label, type, required, options, defaultValue }`, `type` an open widget hint; `'file'` renders the upload widget and resumes with the `sys_file` id bound to `name` — a later `create_record` / `update_record` writes it into a `file` field, which claims the file: collect first, write after the last screen. `'record_link'` (never `'record'`, an object field type) takes no input — it shows the `reference` record whose id `defaultValue` evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, `{ name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } }` over a `create_record` `outputVariable: 'account'`. Both keys required; `required` / `options` / `placeholder` / `min` / `max` refused; a resume value under its name refused (`400 INVALID_SCREEN_INPUT`). A completed `end` `message` is refused and a markdown link in a screen `description` renders literally. Next / Submit and "Step N of M" are not authored: the engine reads the graph when the screen pauses and serves `progress` (`nextScreen: 'none' | 'possible' | 'certain'`; `step` / `stepCount` only when every path agrees), which alone labels the button and draws the counter. A `decision`, `fault` edge, loop back, non-screen pause (`approval`, `wait`) or `subflow` after a screen answers `possible` — "Next", no count. `progress` in a node `config` is refused; never hand-roll a button label or step counter into `fields` | #### Human Decision @@ -217,10 +217,9 @@ variables: [ evaluated before the query (`filter: { id: { dialect: 'cel', source: 'record.id' } }`). A whole-string **query placeholder** (`{current_user_id}`, `{today}`, `{current_year_start}`) passes through verbatim for the query - engine to expand; every other `{…}` token is refused, and a flow variable - never shadows a placeholder. Only - `filter` gets this hand-off — in a text slot `{{ current_year_start }}` is a - nonsense reference. + engine to expand; a flow variable never shadows a placeholder. Only `filter` + gets this hand-off — in a text slot `{{ current_year_start }}` is a nonsense + reference. - **An operand that evaluates to `null` refuses the node** (not `fault`-routable): a dropped condition does not narrow a query, it widens it, so a routed `delete_record` would empty the object while the run reports @@ -299,7 +298,7 @@ Legal metadata that authors — AI especially — get wrong; most are caught by ``` If you genuinely need data-lifecycle **side effects** (read/write other records), - that's an L2 **hook** (objectstack-data) — hooks get `ctx.api`; flow functions don't. + that's an L2 **hook** (objectstack-data) — hooks get `ctx.api`. A function that writes where the platform cannot see **declares** it, so the run reports "cannot say" rather than `acted: 0`: @@ -381,7 +380,7 @@ node `config`**: ### Trigger Configuration — on the `start` node -The binding is the START NODE — this is that node, not a whole flow (a flow also +This is the start node alone, not a whole flow (a flow also owns `label`, `type`, `nodes` and the `edges` `FlowSchema` requires): ```typescript