feat(composer): configurable prompt enhancement with a substantive default prompt - #586
Merged
Merged
Conversation
added 3 commits
September 18, 2026 17:46
The old prompt asked for a "clearer, more concise" draft and returned it with
"at most minor polish" when it looked fine, which made the action read as inert
and left the rewrite weak. Replace it with the structure mature coding
assistants use for the same job: role, analysis, rewrite principles, an explicit
do-not list, language-following rules, a length brake, and an output contract.
Two constraints the old text lacked are now explicit. Code, commands, file
paths, identifiers, API names, and other proper nouns are reproduced exactly, so
a rewrite cannot corrupt a draft's technical content. The answer carries no
language meta note such as "The draft is in Chinese".
The user side becomes a template: the draft is injected at a {{draft}}
placeholder inside <draft> tags, with few-shot examples for Chinese, English,
mixed-language input, and the meta-note failure mode. Substitution uses
split/join, so a draft containing $&, $', $` or $1 is inserted literally instead
of expanding as a String.replace pattern, and every occurrence is replaced. One
matching pair of wrapping quotation marks is stripped from the answer.
The defaults live in @pi-desktop/shared so the runtime, the settings UI, and the
restore-default action read one copy; the runtime keeps its existing export
names.
…onfigurable Issue vastsa#562 asks for a way to customize the prompt behind the Composer's Enhance prompt action. Three surfaces change, each next to the thing it configures. The prompt card on AI settings carries a switch, `Use a custom template`, and the settings icon button the subagent rows use for editing, which opens an editor sheet. The sheet holds only the user template and saves on Save, so closing it abandons the edit. The switch is the gate, not the text: it is disabled until a usable template exists, turning on when one is saved, and turning it off keeps the stored text so turning it back on restores it. Editing the field back to the exact built-in default clears the override rather than storing a frozen copy, so a later improvement to the default still reaches users who never customized it. An insert action writes the draft variable at the caret, and a save that would leave the template without it is refused locally. host-core enforces the same rules for any writer: a non-blank user template must contain {{draft}}, each value is bounded by PROMPT_ENHANCEMENT_TEMPLATE_MAX_LENGTH, a blank value is stored as absent rather than as an empty string, and a stored system-prompt override is dropped. The system prompt stays built in because it carries the contract the spec and E2E scenario assert; a stored override could silently remove a rule. The enhancement model and its reasoning effort get their own card on the Model configuration page, because both are model decisions and the model picker needs a title and a current value rather than a bare control. The model row reuses the default-model row's anchored, searchable menu, so the page shows one kind of picker. An unresolvable pin falls back to the Composer's current model with a warning: a stale pin must not disable the action. The reasoning row lists the levels the selected model actually supports, resolved exactly as a turn resolves them (binding thinkingLevels, then the live catalog, then the provider default), is disabled when the model supports none, defaults to off, and offers no follow-the-session entry. Changing model re-clamps and rewrites the stored level, so a persisted level is always runnable. One request is now bounded by a 60-second ceiling. The transport only consults its abort signal between provider retries, so aborting alone cannot release a stalled call; the handler races the promise to guarantee the caller is freed, and reports TIMEOUT with the budget rather than retrying on another model, which would double the wait. The prompt/enhance payload, process boundaries, and storage ownership are unchanged. The eight shipped catalogs carry the new copy, and settings search indexes the new rows.
Revise ADR 0121 rather than adding a second ADR: the one-shot, main-owned decision stands, so only the prompt shape, the settings surfaces, and the model pin change. Record the rejected alternatives, including letting the user override the system prompt (it would let a stored value silently violate a contract the spec and E2E scenario assert), storing the default text as the user's value (a later improvement to the default would then never reach those users), and failing when a pinned model is unresolvable (a stale pin would disable the action long after the user forgot the choice). Add D447 for the default-value decisions: the substantive-rewrite orientation, the proper-noun and no-meta-note rules, the switch semantics, the model page split, and the model-aware reasoning ladder. Update the UX spec (request boundary, and the user template, model, and reasoning sections) with its zh-CN mirror, plus the settings IA and data-storage specs and their mirrors. Extend E2E-218 and add E2E-259 for the switch, sheet, restore, validation, timeout, language, proper-noun, and quote-stripping contract, and refresh the traceability matrix.
panda-z519
force-pushed
the
feat/enhance-prompt-config
branch
from
September 18, 2026 09:57
810138c to
aab10e3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Composer's Enhance prompt action shipped one fixed sentence as its system prompt plus a
Draft:\n<draft>user message. Two problems followed: the rewrite was weak — the old text asked for a "clearer, more concise" draft and returned it with "at most minor polish" once it looked fine, which made the action read as inert — and there was no way to change either the prompt or the model behind it (#562).This PR rewrites the default prompt, makes the user template, the model, and the reasoning effort configurable, and bounds the request so a slow provider cannot hang the action.
Screenshots
Settings → AI — the prompt card, with the editor sheet open. The system prompt is built in and intentionally has no field; the sheet edits the user template only.
Settings → Models — the enhancement model and reasoning effort, in their own card below Defaults.
What changes
1. A substantive rewrite instead of a conservative polish
The default system prompt becomes the structure comparable tools converge on for the same one-shot job: role, analysis, rewrite principles, an explicit do-not list, language-following rules, a length brake, and an output contract. Two constraints the old text lacked are now explicit:
"The draft is in Chinese".The user side becomes a template: the draft is injected at a
{{draft}}placeholder inside<draft>tags, with few-shot examples for Chinese, English, mixed-language input, and the meta-note failure mode. Substitution usessplit/joinrather thanString.replace, so a draft containing$&,$',$`or$1is inserted literally, and every occurrence is replaced. One matching pair of wrapping quotation marks is stripped from the answer.2. A configurable user template
The AI settings card carries a switch,
Use a custom template, and the settings icon button the subagent rows use for editing, which opens an editor sheet that saves onSave.{{draft}}, values are bounded byPROMPT_ENHANCEMENT_TEMPLATE_MAX_LENGTH, a blank value is stored as absent rather than as an empty string, and a stored system-prompt override is dropped.The system prompt stays built in. It carries the contract this spec and E2E scenario assert, so a stored override could silently remove a rule; changing it remains a source change with a spec update.
3. Model and reasoning on the Model page
The enhancement model and reasoning effort get their own card on Settings → Models, below Defaults, because both are model decisions and the model picker needs a title and a current value rather than a bare control. The model row reuses the default-model row's anchored, searchable menu, so the page shows one kind of picker.
thinkingLevels, then the live catalog, then the provider default), and is disabled when the model supports none. It defaults tooffand offers no follow-the-session entry: a rewrite rarely benefits from reasoning, and reasoning is the slow path.4. A bounded request
One enhancement request is now capped at 60 seconds. The transport only consults its abort signal between provider retries, so aborting alone cannot release a stalled call — verified with a probe against a provider that never answers. The handler therefore races the promise, which guarantees the caller is freed, and reports
TIMEOUTwith the budget rather than retrying on another model (a hidden second attempt would double the wait).Impacted specs and scenarios
docs/adr/0121-one-shot-composer-prompt-enhancement.md— revised (no second ADR: the one-shot, main-owned decision stands)docs/spec/08-meta/decisions-log.md— D447docs/spec/04-ux/12-prompt-enhancement.md+ zh-CN mirrordocs/spec/04-ux/06-settings-ia.md,docs/spec/03-runtime/04-data-storage.md+ zh-CN mirrorsdocs/spec/06-delivery/04-e2e-test-plan.md— E2E-218 extended, E2E-259 added, traceability matrix refreshedValidation
Task candidate:
aab10e3d· Base main:aa02e8e3pnpm --filter @pi-desktop/shared testpnpm --filter @pi-desktop/agent-runtime testpnpm --filter @pi-desktop/desktop testpnpm --filter @pi-desktop/i18n testpnpm -r --if-present typecheckpnpm lint:biomecargo test -p host-core --bin pi-desktop-host-corecargo fmt -p host-core --checkcargo clippy -p host-core --all-targetspnpm test:e2ePI_DESKTOP_TEST_API_KEYunset)pnpm docs:checkpnpm check:agent-policyNot run, with reason: E2E-259's full UI journey needs a graphical environment and a configured model, so it is documented as
Draftalongside E2E-218; its deterministic parts (template resolution, host-core validation, quote stripping, the timeout) are covered by unit and source-contract tests.cargo clippywas run with the toolchain's component installed locally.Pre-existing failures on
main, not from this change. Every failing gate below fails on the base commitaa02e8e3on its own; I reproduced each one on a cleanorigin/mainworktree. They are recorded here so no reviewer mistakes them for this branch's regressions.pnpm --filter @pi-desktop/desktop testsession-message-presentation)main, only timings differnode scripts/check-style-tokens.mjscomposer.css,messages.css,composer-menus.css,theme-overrides.cssapps/desktop/src/styles/pnpm lint(full)check-style-tokens.mjssessions.rs,user_skills.rs,user_skills/tests.rsrpc/mod.rs, which is cleanThe two
CIjob failures on this PR are exactly the second and third rows.cargo fmt -p host-core --checkandcargo clippy -p host-core --all-targetsboth pass for the crate this branch edits.Notes for review
prompt-enhancement-timeout.ts57 lines,EnhancementModelCard.tsx305,prompt-enhancement-card.tsx285,packages/shared/src/prompt-enhancement.ts171), and the hotspot files named in AGENTS.md §7 are untouched:electron/main/index.ts,Composer.tsx, andapp-store.tsare byte-identical. No newany,@ts-ignore, or Rustunwrap/expect.prompt/enhancepayload, process boundaries, storage ownership, and security boundary are unchanged:packages/shared/src/protocol.tsis untouched.packages/i18n/test/catalogs.test.mjsenforces.