(MOT-4173) console: prompts page for the prompt store#565
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe console adds a prompts route and editor backed by directory-worker prompt APIs. Real-backend chats can select per-session system prompts, while slash commands dynamically load and inject command prompt bodies. ChangesConsole prompt functionality
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant PromptPicker
participant PromptStore
participant ChatView
participant RealBackend
User->>PromptPicker: choose system prompt
PromptPicker->>PromptStore: getPrompt(name)
PromptStore-->>PromptPicker: prompt body
PromptPicker->>ChatView: update session prompt
ChatView->>RealBackend: send systemPrompt override
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
skill-check — worker0 verified, 47 skipped (no docs/).
Four for four. Nicely done. |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@console/web/src/components/chat/ChatView.tsx`:
- Around line 161-163: Replace the single ChatView-level sessionPrompt state
with prompt selections keyed by conversation.id, preferably in the existing
conversation state. Derive the selected override for the active conversation
before both send paths, so switching conversations neither leaks the previous
selection nor loses each conversation’s prior selection; update the
prompt-switching logic to write to the active conversation’s entry.
In `@console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx`:
- Around line 100-113: Update the async callback in the SlashCommandsPlugin
prompt-loading flow to replace the node only if it still contains the original
`${entry.command} ` placeholder. Preserve the node and all user-entered text
when its content has changed while getPrompt(name) is pending.
In `@console/web/src/components/chat/PromptPicker.tsx`:
- Around line 74-81: Update the PromptPicker onChange handler to invalidate any
in-flight getPrompt request on every selection, including DEFAULT, and track the
latest selection so stale responses cannot call onChange. Handle getPrompt
failures without applying a result, and ensure only the current successful
request updates the selected prompt.
In `@console/web/src/lib/prompts.ts`:
- Around line 17-33: Update the Rust implementations of directory::prompts::list
and directory::prompts::get, including PromptEntry and PromptGetOutput
serialization, to emit each prompt’s actual kind and source fields. Ensure the
values are populated before promptRowSchema or promptDetailSchema defaults are
applied so system prompts retain their true kind and user library prompts retain
their true source and remain editable/deletable.
In `@console/web/src/pages/Prompts/index.tsx`:
- Around line 78-89: Update the open callback to handle both getPrompt failures
and null results through the existing error state: catch rejected requests and
set a useful error message, and set an appropriate error when no prompt detail
is returned instead of returning silently. Preserve the existing draft
population for successful results.
- Around line 132-136: Update onDelete to require explicit user confirmation
before calling deletePrompt, preserving the existing early return and clearing
draft only after successful deletion. Ensure every prompt-deletion entry point,
including the additional onDelete occurrence, uses the same confirmation flow
rather than relying on deletePrompt’s hardcoded yes option.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6722d1bd-48c0-4aec-9e31-38642143ba1f
📒 Files selected for processing (16)
console/web/src/App.tsxconsole/web/src/components/chat/ChatView.tsxconsole/web/src/components/chat/Composer.tsxconsole/web/src/components/chat/PromptPicker.tsxconsole/web/src/components/chat/lexical/SlashCommandsPlugin.tsxconsole/web/src/hooks/use-hash-route.tsconsole/web/src/hooks/use-prompts-status.tsconsole/web/src/lib/backend/harness-send.tsconsole/web/src/lib/backend/real.tsconsole/web/src/lib/backend/types.tsconsole/web/src/lib/conversations-context.tsxconsole/web/src/lib/nav-options.test.tsconsole/web/src/lib/nav-options.tsconsole/web/src/lib/prompts.tsconsole/web/src/lib/slash-commands.tsconsole/web/src/pages/Prompts/index.tsx
| // System prompt override for this conversation (prompt store, kind: | ||
| // system). Applied to every send until switched back to default. | ||
| const [sessionPrompt, setSessionPrompt] = useState<SessionPrompt | null>(null) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Scope the selected prompt by conversation.id.
This state can leak the previous chat’s override into the next selected conversation; if ChatView remounts instead, it loses the prior conversation’s selection. Store prompt selection keyed by conversation id (preferably in conversation state) and derive the current override from that mapping before both send paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/web/src/components/chat/ChatView.tsx` around lines 161 - 163, Replace
the single ChatView-level sessionPrompt state with prompt selections keyed by
conversation.id, preferably in the existing conversation state. Derive the
selected override for the active conversation before both send paths, so
switching conversations neither leaks the previous selection nor loses each
conversation’s prior selection; update the prompt-switching logic to write to
the active conversation’s entry.
| const promptRowSchema = z.object({ | ||
| name: z.string(), | ||
| description: z.string().default(''), | ||
| kind: z.string().default('command'), | ||
| source: z.string().default('worker'), | ||
| modified_at: z.string().default(''), | ||
| }) | ||
| export type PromptRow = z.infer<typeof promptRowSchema> | ||
|
|
||
| const promptListSchema = z.object({ | ||
| prompts: z.array(z.unknown()).default([]), | ||
| }) | ||
|
|
||
| const promptDetailSchema = promptRowSchema.extend({ | ||
| body: z.string().default(''), | ||
| }) | ||
| export type PromptDetail = z.infer<typeof promptDetailSchema> |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm whether directory::prompts::list/get currently emit `kind`/`source`.
fd prompts.rs --full-path iii-directory
rg -n -A5 'struct PromptEntry|struct PromptGetOutput' iii-directory/src/functions/prompts.rsRepository: iii-hq/workers
Length of output: 580
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== prompts schema/usages =="
sed -n '1,80p' console/web/src/lib/prompts.ts
echo
rg -n "getPrompt|open\\(|existing:|readonly:|kind|source" console/web/src/pages/Prompts/console/web/src -S || true
echo
echo "== iii-directory prompts.rs relevant sections =="
sed -n '1,180p' iii-directory/src/functions/prompts.rs
echo
sed -n '1,140p' iii-directory/tests/steps/prompts.rs
echo
echo "== zod package reference =="
fd -i package.json . -x sh -c 'echo "--- $1"; rg -n "\"zod\"|zod@|zod:" "$1" || true' sh {}Repository: iii-hq/workers
Length of output: 14413
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== console/prompts page files =="
fd -a "index.tsx|.*\.ts(x)?$" console/web/src/pages 2>/dev/null | grep -i "prompt" || true
echo
if [ -d console/web/src/pages/Prompts ]; then
rg -n "getPrompt|listPrompts|open\\(|existing:|readonly:|kind|source|delete|editable" console/web/src/pages/Prompts -S
fi
echo
if [ -f console/web/src/lib/prompts.ts ]; then
sed -n '1,120p' console/web/src/lib/prompts.ts
fi
echo
echo "== zod v4 optional/unknown/object behavior =="
node - <<'JS'
const schema = require('zod').z.object({
kind: require('zod').z.string().default('command'),
source: require('zod').z.string().default('worker'),
});
for (const input of [{}, {kind:'system'}, {kind:'system', source:'user'}]) {
const parsed = schema.safeParse(input);
console.log(JSON.stringify(input), parsed.success, parsed.success ? parsed.data : parsed.error.errors.map(e => ({path: e.path, message: e.message})));
}
JSRepository: iii-hq/workers
Length of output: 5997
🌐 Web query:
zod v4 object default undefined input field default only when key is undefined
💡 Result:
In Zod v4, the behavior of .default() is designed to handle the absence of a value by short-circuiting the parsing process and returning the specified default value [1][2]. Key behaviors in Zod v4: 1. Short-circuiting: When input is undefined, .default() returns the provided value immediately, skipping the inner schema's parsing [1][3]. Consequently, the output type of a schema with .default() loses optionality (e.g., it becomes string instead of string | undefined) because the parser guarantees a non-undefined result [4][5]. 2. Defining "Only When Undefined": Because .default() treats any undefined input as an absence to be filled, it naturally applies only when the key is undefined (or absent). If you pass an explicit value, Zod validates that value against the inner schema [6]. 3. Preserving Optionality: If your goal is for the field to remain optional (retaining | undefined in the output type) while providing a default value only at runtime, .default() is generally not the correct tool as it forces a non-optional output type [4][5]. Instead, you should use .prefault(), which allows you to substitute a value into the input before parsing while preserving the schema's optional nature [1][2][3]. If you explicitly need to define a default only when a key is undefined while maintaining an optional type, consider the following pattern using .prefault() [1][3]: const schema = z.string.prefault("your-default").optional; type Schema = z.infer; // string | undefined This approach ensures that undefined inputs receive the default value during parsing, but the type signature correctly reflects that the resulting field can still be undefined [3].
Citations:
- 1: https://zod.dev/v4/changelog
- 2: https://github.com/colinhacks/zod/blob/c7805073/packages/docs/content/v4/changelog.mdx
- 3: v4: defaults in zod object are no longer optional when defining a default for wrapped zod object colinhacks/zod#4140
- 4:
.default(undefined)removes optionality colinhacks/zod#4815 - 5: [v4] Prevent
.default(undefined)colinhacks/zod#5213 - 6: https://github.com/colinhacks/zod/blob/912f0f51/packages/zod/src/v4/classic/tests/default.test.ts
Return kind and source from directory::prompts::list/get.
PromptEntry and PromptGetOutput currently serialize only name, description, body, and modified_at, while promptRowSchema defaults missing fields to kind: 'command' and source: 'worker'. This makes system prompts appear as command in the list and forces every detail opened via getPrompt into readonly: true, hiding delete and disabling edits for user library prompts. Emit the actual kind/source in the Rust responses or store/detect it before applying these defaults.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/web/src/lib/prompts.ts` around lines 17 - 33, Update the Rust
implementations of directory::prompts::list and directory::prompts::get,
including PromptEntry and PromptGetOutput serialization, to emit each prompt’s
actual kind and source fields. Ensure the values are populated before
promptRowSchema or promptDetailSchema defaults are applied so system prompts
retain their true kind and user library prompts retain their true source and
remain editable/deletable.
What
A
promptspage in the console, following the same optional-worker pattern as the memory and github pages:use-prompts-statuspresence probe over the shareduseWorkerPresencehook); a direct#/promptshit while it is absent lands on the standard install notice.kind(systemhighlighted) andsourcebadges.directory::prompts::save/delete; fork opens an editable copy under<name>-fork. Worker-shipped templates render read-only, since they ship with their worker's bundle.lib/prompts.tsdata layer followsmemory.tsconventions: zod schemas with defaults for cross-version tolerance, dropped unparseable rows on reads, plain trigger on mutations.Why
The prompt library backend (#564) gives agents and operators named system prompts; this makes the store visible and editable the way the memory worker's banks are. The new-chat dropdown that applies a chosen prompt to a session is a separate follow-up on the injectable-view base being built independently; this page is the browse-and-edit surface it will pick from.
Validation
FunctionCallCard.test.ts, a jsdom userAgent issue in a diff-rendering dependency) and the two biome errors (Configurationtab,index.css) predate this change and touch files this PR does not.Dependencies
Read-only listing works against any directory worker. Save, fork, and delete need the library backend from #564; on an older worker those actions surface the worker's own error in the page's alert row.
Linear
Part of MOT-4173 and MOT-4169.
Second slice: apply prompts in chat (added after review of the call notes)
prompt: default(the harness identity chain) or anykind: systementry from the store. Selection fetches the body and every following send in that conversation carriesoptions.system_prompt+system_prompt_strategy: override; switching mid-conversation applies from the next turn.HarnessSendOptionsgains thesystem_prompt_strategyfield the harness already accepts.kind: commandprompt-store entries alongside builtins; selecting one injects the prompt BODY into the message (context injection). System prompts are deliberately excluded from the slash menu - they apply through the picker, not the message. The slash trigger pattern now allows kebab-case names.prompt: scraperin the dropdown, asked the chat what its job is, and it answered as the five-line scraper prompt (pipe the fetch, state::set, never spawn) instead of the general agent identity.Summary by CodeRabbit