feat: skill/prompt frontmatter now supports model, model-group, and thinking - #20
feat: skill/prompt frontmatter now supports model, model-group, and thinking#20ofriw wants to merge 17 commits into
Conversation
…nd error blocking
…del, and thinking
…pers and unified test factories
grzegorznowak
left a comment
There was a problem hiding this comment.
Requesting changes on four issues, ordered by severity:
-
High — Block readonly-changing commands during streaming. Pi does not emit a new
before_agent_startfor queued steer/follow-up messages, so a deferred readonly intent can miss its own command and apply to a later unrelated prompt. Please treat validreadonly: trueandreadonly: falselike model-changing frontmatter during streaming: warn, returnhandled, enqueue nothing, and leave state unchanged. (index.ts:600,index.ts:607,index.ts:718) -
High — Reconcile partial
setModel()failures. Pi can commit the new model and clamp thinking before a latermodel_selecthook rejects. The current code then reports failure and blocks expansion even though the new model is active. Please roll back or reconcile the effective state, and cover mutation-then-reject for explicit and group routes. (index.ts:256,index.ts:327,index.ts:372) -
Medium — Make the tests match Pi's host lifecycle. Current mocks manually fire
before_agent_startafter streaming input and throw fromsetModel()before mutation, masking both issues above. Please add a host-faithful integration seam or equivalent lifecycle/mutation coverage. (tests/unit/model-group-frontmatter.test.ts:380,tests/unit/readonly-frontmatter.test.ts:386,scripts/test-package-host.mjs:36) -
Medium — Use Pi's full slash-command token. The extension treats
/review/typoas/review, while Pi looks forreview/typo. This can change model, thinking, or readonly state for a command Pi never expands. Please align parsing with Pi and add embedded-slash negative tests for prompts and skills. (index.ts:581,index.ts:582)
SUMMARY.md — longer-form explanations
Detailed review summary
1. High: block readonly-changing commands during streaming
Pi intentionally processes steer and follow-up messages inside the current agent run, without emitting another before_agent_start. That is safe for ordinary skill text, but our readonly implementation queues the command during input and waits for before_agent_start to apply its state change.
For a readonly-only command submitted during streaming, the expected hook never runs for that message. The command can therefore run under the old readonly state while its queued intent remains behind and is later consumed by an unrelated prompt.
Example:
readonly is off
→ agent is streaming
→ user queues /research with readonly: true
→ /research continues without readonly being activated
→ user later submits "implement the fix"
→ stale readonly: true is applied to the new prompt
This queue behavior predates the PR, but the PR refactors the shared frontmatter lifecycle and establishes a streaming policy for model-changing fields. Please apply that policy consistently: valid readonly: true and readonly: false frontmatter submitted as either steer or followUp should show a warning, return handled, enqueue nothing, and leave readonly unchanged.
Please test both values under both streaming behaviors and confirm that a subsequent unrelated prompt receives no delayed readonly change.
Sources: index.ts:600, index.ts:607, index.ts:718
2. High: reconcile partial setModel() failures
safeSetModel() assumes that a rejected pi.setModel() means the model was not changed. In Pi 0.82.0, the host commits the model, persists the change, and re-clamps thinking before awaiting model_select hooks. A hook can therefore throw after the requested model is already active.
The current handler then reports a failed switch, blocks command expansion, skips any requested thinking override, and records no frontmatter success entry. The operator sees failure while the session is actually using the new model. This affects both explicit model: and model-group: routes.
Example:
Current model: Claude
/review requests GPT-4o
→ Pi commits GPT-4o and clamps thinking
→ another extension's model_select hook throws
→ user sees "Failed to switch model"
→ /review is blocked
→ actual session model remains GPT-4o
Please either restore the previous model/thinking state or reconcile the effective post-error state and report it accurately. Add a mutation-then-reject test rather than a mock that throws before changing state.
Sources: index.ts:256, index.ts:327, index.ts:372
3. Medium: make the tests match Pi's host lifecycle
The owning tests manually pair streaming input with before_agent_start and use a side-effect-free setModel() mock. Those assumptions are stronger than Pi's real contract and hide both problems above:
- Pi does not emit
before_agent_startfor a queued steer/follow-up inside the existing run. - Pi can reject
setModel()after committing the model and thinking state.
The package-host lane only proves that the packaged extension loads. Please add a small host-faithful integration seam or adjust the lifecycle and mutation mocks to reproduce the locked Pi 0.82.0 behavior.
Sources: tests/unit/model-group-frontmatter.test.ts:380, tests/unit/readonly-frontmatter.test.ts:386, scripts/test-package-host.mjs:36
4. Medium: use Pi's full slash-command token
The extension stops parsing command names at an embedded slash, while Pi uses the complete whitespace-delimited token. For example, the extension treats /review/typo as /review, but Pi looks for a command named review/typo and leaves the input unexpanded if it does not exist.
That means a malformed or different command can still apply /review's model, thinking, or readonly frontmatter even though /review itself never runs. /skill:foo/bar has the same mismatch.
Please use Pi's complete command token and only apply frontmatter when that exact resolved prompt or skill is authoritative. Add negative tests for embedded-slash prompt and skill invocations.
Sources: index.ts:581, index.ts:582
Skill/prompt frontmatter model selection
Skills and prompt templates can now declare
model,model-group, andthinkingalongsidereadonly. Interactive invocations apply model selection during input preflight, before slash-command expansion.readonlyremains deferred tobefore_agent_start, where final skill metadata is available.What changed
frontmatter-cache.ts, which parses and validatesreadonly,model-group,model, andthinkingfor skills and prompts.model: provider/model-idselects that configured, authenticated model.model-group: group-nameresolves through the existing Model Group router.thinking:overrides the selected/current model’s level after capability clamping.modeltakes precedence overmodel-groupand shows a warning when both are present.Documentation
docs/architecture.mdnow documents the frontmatter fields, resolution order, and lifecycle split.Verification
npm run typechecknpm test(the existing audit-config check currently fails because npm audit reports an additional transitive advisory path; this PR does not change dependencies, the lockfile, or that test)