Skip to content

(MOT-4173) feat(console): composer prompt picker + slash-command prompts - #565

Open
rohitg00 wants to merge 1 commit into
mainfrom
console-prompts-view
Open

(MOT-4173) feat(console): composer prompt picker + slash-command prompts#565
rohitg00 wants to merge 1 commit into
mainfrom
console-prompts-view

Conversation

@rohitg00

@rohitg00 rohitg00 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

In-chat prompt selection surfaces that read the directory prompt store, replacing the earlier standalone console page (that browse/edit surface now lives in the injectable directory tab, #564).

  • System-prompt picker: a dropdown beside the composer model and memory controls. Pick a kind: system entry to override the system prompt for the conversation, applied per send (system_prompt + system_prompt_strategy: override). default defers to the harness identity chain. Mid-conversation switches apply from the next turn.
  • Slash commands: the / menu surfaces kind: command prompts, injecting the body into the message on select.

Depends on

The directory prompt-library backend (directory::prompts::save / delete, kind, source) in #564. Read-only listing works against any directory worker; save, fork, and delete need the backend.

Verification

  • console typecheck (tsc -b) and biome clean on all touched files.
  • Live against a running rig via the dev console (vite, wired to the engine): the prompt: dropdown lists the store's kind: system entries; selecting one drives the turn under that prompt.
  • Override confirmed end to end on non-claude-code providers. The prompt reaches the model verbatim: console to harness::send options.system_prompt, to assemble_context, to the context-manager render_system_prompt (verbatim), to the provider.
    • codex/gpt-5.6-sol: a blog-writer prompt makes the agent identify as a blog writer (web + state only); a Hindi-instructed prompt returns Devanagari output.
    • claude-sonnet-5 (anthropic API): same, adopts the blog-writer identity.

Known limitation

On claude-code/* (Max subscription) models the override is effectively lost. provider-claude-code must send the Claude Code identity as the first system block (a subscription wire requirement, see provider-claude-code/src/request.rs), and the caller prompt lands as a second block after the full agent identity, which dominates. This is a provider constraint, not a picker bug. A follow-up can surface a note in the picker when a claude-code/* model is active.

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 27, 2026 1:42pm
workers-tech-spec Ready Ready Preview, Comment Jul 27, 2026 1:42pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Console prompt functionality

Layer / File(s) Summary
Prompt availability and routing
console/web/src/hooks/use-prompts-status.ts, console/web/src/lib/conversations-context.tsx, console/web/src/lib/nav-options.ts, console/web/src/lib/nav-options.test.ts, console/web/src/hooks/use-hash-route.ts, console/web/src/App.tsx
Prompt worker presence gates navigation and chat capabilities, and the prompts view is routable and rendered.
Prompt store and editor
console/web/src/lib/prompts.ts, console/web/src/pages/Prompts/index.tsx
Typed worker wrappers and a split prompt editor support listing, loading, saving, forking, and deleting prompts.
Session prompt selection and sending
console/web/src/components/chat/PromptPicker.tsx, console/web/src/components/chat/Composer.tsx, console/web/src/components/chat/ChatView.tsx, console/web/src/lib/backend/types.ts, console/web/src/lib/backend/real.ts, console/web/src/lib/backend/harness-send.ts
The composer selects system prompts, and real-backend stream or queued-message requests forward the selected body as an override.
Prompt-backed slash commands
console/web/src/lib/slash-commands.ts, console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx
Command prompts are loaded into slash-command suggestions and asynchronously expanded into editor content, including kebab-case names.

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
Loading

Possibly related PRs

  • iii-hq/workers#564: Adds the directory-worker prompt metadata and CRUD backend used by this console UI.
  • iii-hq/workers#540: Adds backend harness::send prompt override and enrichment behavior connected to the frontend request changes.
  • iii-hq/workers#496: Uses similar worker-availability gating and console picker wiring patterns.

Poem

I’m a rabbit with prompts in my pack,
Editing words and sending them back.
Slash commands hop through the air,
System prompts bloom everywhere.
The console now follows their track!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and describes real parts of the change, especially the chat prompt picker and slash-command prompt support.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch console-prompts-view

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 49 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff0617 and 8c171c7.

📒 Files selected for processing (16)
  • console/web/src/App.tsx
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/Composer.tsx
  • console/web/src/components/chat/PromptPicker.tsx
  • console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx
  • console/web/src/hooks/use-hash-route.ts
  • console/web/src/hooks/use-prompts-status.ts
  • console/web/src/lib/backend/harness-send.ts
  • console/web/src/lib/backend/real.ts
  • console/web/src/lib/backend/types.ts
  • console/web/src/lib/conversations-context.tsx
  • console/web/src/lib/nav-options.test.ts
  • console/web/src/lib/nav-options.ts
  • console/web/src/lib/prompts.ts
  • console/web/src/lib/slash-commands.ts
  • console/web/src/pages/Prompts/index.tsx

Comment on lines +161 to +163
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment thread console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx
Comment thread console/web/src/components/chat/PromptPicker.tsx Outdated
Comment on lines +17 to +33
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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.rs

Repository: 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})));
}
JS

Repository: 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:


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.

Comment thread console/web/src/pages/Prompts/index.tsx Outdated
Comment thread console/web/src/pages/Prompts/index.tsx Outdated
Adds in-chat prompt selection surfaces that read the directory prompt store
(directory::prompts::*, kind system|command):
- a system-prompt picker beside the composer model/memory controls: pick a
  kind:system entry to override the system prompt for the conversation.
- the slash-command menu surfaces kind:command prompts, injecting the body.

Depends on the directory prompt-library backend (save/delete, kind, source).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant