refactor(prompts): decouple allow_shell from static system-prompt prefix
Move allow_shell from message[0] (static system prompt) to the per-turn <runtime_prompt> tag alongside mode and approval. This preserves the DeepSeek prefix cache across shell-access toggles and mode switches, which previously forced YOLO entry/exit to rebuild the entire prompt. Changes: - Delete remove_shell_tool_guidance and 3 other dead functions (~60 lines) - Remove allow_shell field from PromptSessionContext and StaticPromptCtx - Remove shell_tools_available dead parameter from compose functions - Add Shell Policy section to Runtime Policy Reference (static text) - Extend <runtime_prompt> tag with allow_shell="true|false" attribute - Update tests: omits→always_keeps, 83/83 prompts tests pass - Drop dead compose_mode_prompt_with_approval_and_model Net: message[0] bytes are now stable regardless of shell-access state. Mode/approval/shell flags all use the same per-turn tag pattern.
This commit is contained in:
@@ -681,7 +681,6 @@ impl Engine {
|
||||
goal_objective_for_prompt(config.goal_objective.as_deref(), &config.goal_state);
|
||||
let system_prompt =
|
||||
prompts::system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
&config.workspace,
|
||||
None,
|
||||
Some(&config.skills_dir),
|
||||
@@ -694,7 +693,6 @@ impl Engine {
|
||||
translation_enabled: config.translation_enabled,
|
||||
model_id: &config.model,
|
||||
show_thinking: config.show_thinking,
|
||||
allow_shell: config.allow_shell,
|
||||
},
|
||||
);
|
||||
let stable_prompt = Some(system_prompt);
|
||||
@@ -1456,7 +1454,7 @@ impl Engine {
|
||||
Message {
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentBlock::Text {
|
||||
text: runtime_prompt_text(mode, approval_mode),
|
||||
text: runtime_prompt_text(mode, approval_mode, self.session.allow_shell),
|
||||
cache_control: None,
|
||||
}],
|
||||
}
|
||||
@@ -2426,7 +2424,6 @@ impl Engine {
|
||||
&self.config.goal_state,
|
||||
);
|
||||
let base = prompts::system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
&self.config.workspace,
|
||||
None,
|
||||
Some(&self.config.skills_dir),
|
||||
@@ -2439,7 +2436,6 @@ impl Engine {
|
||||
translation_enabled: self.config.translation_enabled,
|
||||
model_id: &self.config.model,
|
||||
show_thinking: self.config.show_thinking,
|
||||
allow_shell: self.session.allow_shell,
|
||||
},
|
||||
);
|
||||
let mut stable_prompt =
|
||||
@@ -2649,11 +2645,16 @@ fn agent_approval_mode_for_turn(
|
||||
|
||||
/// Produce a minimal runtime-policy tag for the per-turn transient user message.
|
||||
///
|
||||
/// All mode and approval policy descriptions live in the frozen system-prompt
|
||||
/// prefix (`render_runtime_policy_reference()`). This tag is a pointer — the
|
||||
/// model looks up the corresponding rules from the system prompt. Reduces
|
||||
/// per-request overhead from ~500 tokens to ~12 tokens.
|
||||
fn runtime_prompt_text(mode: AppMode, approval_mode: crate::tui::approval::ApprovalMode) -> String {
|
||||
/// All mode / approval / shell policy descriptions live in the frozen
|
||||
/// system-prompt prefix (`render_runtime_policy_reference()`). This tag
|
||||
/// is a pointer — the model looks up the corresponding rules from the
|
||||
/// system prompt. Keeping these flags out of the static prefix preserves
|
||||
/// the DeepSeek prefix cache across mode-switches and config-toggles.
|
||||
fn runtime_prompt_text(
|
||||
mode: AppMode,
|
||||
approval_mode: crate::tui::approval::ApprovalMode,
|
||||
allow_shell: bool,
|
||||
) -> String {
|
||||
let mode_str = match mode {
|
||||
AppMode::Agent => "agent",
|
||||
AppMode::Plan => "plan",
|
||||
@@ -2665,7 +2666,7 @@ fn runtime_prompt_text(mode: AppMode, approval_mode: crate::tui::approval::Appro
|
||||
crate::tui::approval::ApprovalMode::Never => "never",
|
||||
};
|
||||
format!(
|
||||
"<runtime_prompt visibility=\"internal\" mode=\"{mode_str}\" approval=\"{approval_str}\"/>"
|
||||
"<runtime_prompt visibility=\"internal\" mode=\"{mode_str}\" approval=\"{approval_str}\" allow_shell=\"{allow_shell}\"/>"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+128
-312
@@ -11,7 +11,6 @@
|
||||
use crate::models::SystemPrompt;
|
||||
use crate::project_context::{ProjectContext, load_project_context_with_parents};
|
||||
use crate::tui::app::AppMode;
|
||||
use crate::tui::approval::ApprovalMode;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -39,10 +38,6 @@ pub struct PromptSessionContext<'a> {
|
||||
/// When false, the prompt should not spend localization pressure on
|
||||
/// `reasoning_content` the user will never see.
|
||||
pub show_thinking: bool,
|
||||
/// Whether shell tools are available in the runtime tool catalog for
|
||||
/// this session. The prompt must not advertise shell-only workflows
|
||||
/// when runtime gates have removed those tools.
|
||||
pub allow_shell: bool,
|
||||
}
|
||||
|
||||
impl Default for PromptSessionContext<'_> {
|
||||
@@ -55,7 +50,6 @@ impl Default for PromptSessionContext<'_> {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,8 +308,6 @@ pub struct StaticPromptCtx<'a> {
|
||||
pub model_id: &'a str,
|
||||
/// Personality overlay requested for the base static prompt.
|
||||
pub personality: Personality,
|
||||
/// Whether shell tools are present in the runtime tool catalog.
|
||||
pub shell_tools_available: bool,
|
||||
/// Default base/personality prompt layers that would be used without an
|
||||
/// override.
|
||||
pub default_layers: &'a str,
|
||||
@@ -643,6 +635,14 @@ pub const AUTO_APPROVAL: &str = include_str!("prompts/approvals/auto.md");
|
||||
pub const SUGGEST_APPROVAL: &str = include_str!("prompts/approvals/suggest.md");
|
||||
pub const NEVER_APPROVAL: &str = include_str!("prompts/approvals/never.md");
|
||||
|
||||
/// Shell policy guidance for `allow_shell=false`. Referenced from the
|
||||
/// Runtime Policy Reference so the model can adapt without mutating the
|
||||
/// static system-prompt prefix (preserves DeepSeek prefix cache across
|
||||
/// shell-access toggles).
|
||||
pub const SHELL_POLICY_DISABLED: &str = "Shell tools unavailable. For mandatory-use items referencing \
|
||||
`exec_shell`, use `code_execution` (Python sandbox). For GitHub triage, use \
|
||||
`github_issue_context` / `github_pr_context` as primary route.";
|
||||
|
||||
/// Compaction relay template — written into the system prompt so the
|
||||
/// model knows the format to use when writing `.codewhale/handoff.md`.
|
||||
pub const COMPACT_TEMPLATE: &str = include_str!("prompts/compact.md");
|
||||
@@ -758,6 +758,16 @@ pub(crate) fn render_runtime_policy_reference() -> String {
|
||||
|
||||
out.push_str("#### never\n\n");
|
||||
out.push_str(NEVER_APPROVAL.trim());
|
||||
out.push_str("\n\n");
|
||||
|
||||
// ── Shell policy reference ──────────────────────────────────────────
|
||||
out.push_str("### Shell Policy\n\n");
|
||||
|
||||
out.push_str("#### allow_shell=true\n\n");
|
||||
out.push_str("Shell tools available as described in the base prompt.\n\n");
|
||||
|
||||
out.push_str("#### allow_shell=false\n\n");
|
||||
out.push_str(SHELL_POLICY_DISABLED.trim());
|
||||
|
||||
out
|
||||
}
|
||||
@@ -843,52 +853,25 @@ directive within Constitutional bounds. Personality, memory, and handoff
|
||||
context are subordinate to the Constitution, the Statutes, and the user's
|
||||
current request. When in doubt, consult Article VII: The Hierarchy of Law.";
|
||||
|
||||
pub fn compose_prompt(mode: AppMode, personality: Personality) -> String {
|
||||
compose_prompt_with_approval(mode, personality)
|
||||
pub fn compose_prompt(personality: Personality) -> String {
|
||||
compose_prompt_with_approval_model_and_shell(personality, "codewhale")
|
||||
}
|
||||
|
||||
pub fn compose_prompt_with_approval(mode: AppMode, personality: Personality) -> String {
|
||||
compose_prompt_with_approval_and_model(mode, personality, "codewhale")
|
||||
}
|
||||
|
||||
/// Compose with explicit model ID for dynamic identity injection.
|
||||
/// The model_id replaces `{model_id}` in the Constitutional preamble.
|
||||
pub fn compose_prompt_with_approval_and_model(
|
||||
mode: AppMode,
|
||||
pub(crate) fn compose_prompt_with_approval_model_and_shell(
|
||||
personality: Personality,
|
||||
model_id: &str,
|
||||
) -> String {
|
||||
compose_prompt_with_approval_model_and_shell(mode, personality, model_id, true)
|
||||
}
|
||||
|
||||
fn compose_prompt_with_approval_model_and_shell(
|
||||
mode: AppMode,
|
||||
personality: Personality,
|
||||
model_id: &str,
|
||||
allow_shell: bool,
|
||||
) -> String {
|
||||
let shell_tools_available = allow_shell && mode != AppMode::Plan;
|
||||
let default_layers =
|
||||
compose_default_static_layers(personality, model_id, shell_tools_available);
|
||||
let default_layers = compose_default_static_layers(personality, model_id);
|
||||
apply_static_prompt_composer(
|
||||
effective_static_prompt_composer(),
|
||||
personality,
|
||||
model_id,
|
||||
shell_tools_available,
|
||||
&default_layers,
|
||||
)
|
||||
}
|
||||
|
||||
fn compose_default_static_layers(
|
||||
personality: Personality,
|
||||
model_id: &str,
|
||||
shell_tools_available: bool,
|
||||
) -> String {
|
||||
let base_prompt = render_base_prompt_for_tool_availability(
|
||||
effective_base_prompt().trim(),
|
||||
model_id,
|
||||
shell_tools_available,
|
||||
);
|
||||
fn compose_default_static_layers(personality: Personality, model_id: &str) -> String {
|
||||
let base_prompt = apply_model_template(effective_base_prompt().trim(), model_id);
|
||||
let parts: [&str; 2] = [base_prompt.as_str(), personality.prompt().trim()];
|
||||
|
||||
let mut out =
|
||||
@@ -907,126 +890,31 @@ fn apply_static_prompt_composer(
|
||||
composer: Option<&StaticPromptComposer>,
|
||||
personality: Personality,
|
||||
model_id: &str,
|
||||
shell_tools_available: bool,
|
||||
default_layers: &str,
|
||||
) -> String {
|
||||
match composer {
|
||||
Some(composer) => composer(&StaticPromptCtx {
|
||||
model_id,
|
||||
personality,
|
||||
shell_tools_available,
|
||||
default_layers,
|
||||
}),
|
||||
None => default_layers.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_base_prompt_for_tool_availability(
|
||||
prompt: &str,
|
||||
model_id: &str,
|
||||
shell_tools_available: bool,
|
||||
) -> String {
|
||||
let prompt = if shell_tools_available {
|
||||
prompt.to_string()
|
||||
} else {
|
||||
remove_shell_tool_guidance(prompt)
|
||||
};
|
||||
apply_model_template(&prompt, model_id)
|
||||
}
|
||||
|
||||
fn remove_shell_tool_guidance(prompt: &str) -> String {
|
||||
let prompt = prompt
|
||||
.lines()
|
||||
.filter(|line| !is_shell_disabled_prompt_line(line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let prompt = remove_markdown_section(&prompt, "### `exec_shell`");
|
||||
let prompt = prompt.replace(
|
||||
"; for GitHub issue/PR/release triage, prefer the native `gh ... --json` CLI through shell because it is authenticated, structured, and reproducible; `github_issue_context` / `github_pr_context` are read-only fallbacks when the CLI route is unavailable;",
|
||||
"; for GitHub issue/PR/release triage, use `github_issue_context` / `github_pr_context` as read-only routes when shell tools are unavailable;",
|
||||
);
|
||||
prompt.replace(
|
||||
"Use deterministic Python inside RLM for exact counts and structured aggregation; use `grep_files` or `exec_shell` directly when that is the clearest deterministic check.",
|
||||
"Use deterministic Python inside RLM for exact counts and structured aggregation; use `grep_files` directly when that is the clearest deterministic check.",
|
||||
)
|
||||
}
|
||||
|
||||
fn is_shell_disabled_prompt_line(line: &str) -> bool {
|
||||
line.starts_with("- Arithmetic, math, calculations → `exec_shell`")
|
||||
|| line.starts_with("- Hashes, encodings, checksums → `exec_shell`")
|
||||
|| line.starts_with("- Current time, date, timezone → `exec_shell`")
|
||||
|| line
|
||||
.starts_with("- System state: OS, CPU, memory, disk, ports, processes → `exec_shell`")
|
||||
|| line.starts_with("- **Shell**:")
|
||||
}
|
||||
|
||||
fn remove_markdown_section(prompt: &str, heading: &str) -> String {
|
||||
let Some(start) = prompt.find(heading) else {
|
||||
return prompt.to_string();
|
||||
};
|
||||
let after_heading = start + heading.len();
|
||||
let end = prompt[after_heading..]
|
||||
.find("\n### ")
|
||||
.map(|offset| after_heading + offset)
|
||||
.unwrap_or(prompt.len());
|
||||
|
||||
let before = prompt[..start].trim_end();
|
||||
let after = prompt[end..].trim_start_matches('\n');
|
||||
if before.is_empty() {
|
||||
after.to_string()
|
||||
} else if after.is_empty() {
|
||||
before.to_string()
|
||||
} else {
|
||||
format!("{before}\n\n{after}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Compose for the default personality (Calm).
|
||||
fn compose_mode_prompt(mode: AppMode) -> String {
|
||||
compose_prompt(mode, Personality::Calm)
|
||||
}
|
||||
|
||||
fn compose_mode_prompt_with_approval(mode: AppMode) -> String {
|
||||
compose_prompt_with_approval(mode, Personality::Calm)
|
||||
}
|
||||
|
||||
fn compose_mode_prompt_with_approval_and_model(
|
||||
mode: AppMode,
|
||||
_approval_mode: ApprovalMode,
|
||||
model_id: &str,
|
||||
) -> String {
|
||||
compose_prompt_with_approval_model_and_shell(mode, Personality::Calm, model_id, true)
|
||||
}
|
||||
// Shell tool guidance removal functions have been deleted.
|
||||
// The full base prompt is always used; the `allow_shell` flag is
|
||||
// conveyed via the per-turn <runtime_prompt> tag so the model can
|
||||
// adapt without mutating the static system-prompt prefix.
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────
|
||||
|
||||
/// Get the system prompt for a specific mode (default Calm personality).
|
||||
pub fn system_prompt_for_mode(mode: AppMode) -> SystemPrompt {
|
||||
SystemPrompt::Text(compose_mode_prompt(mode))
|
||||
}
|
||||
|
||||
/// Get the system prompt for a specific mode with explicit personality.
|
||||
pub fn system_prompt_for_mode_with_personality(
|
||||
mode: AppMode,
|
||||
personality: Personality,
|
||||
) -> SystemPrompt {
|
||||
SystemPrompt::Text(compose_prompt(mode, personality))
|
||||
}
|
||||
|
||||
/// Get the system prompt for a specific mode with project context.
|
||||
pub fn system_prompt_for_mode_with_context(
|
||||
mode: AppMode,
|
||||
workspace: &Path,
|
||||
working_set_summary: Option<&str>,
|
||||
) -> SystemPrompt {
|
||||
system_prompt_for_mode_with_context_and_skills(
|
||||
mode,
|
||||
workspace,
|
||||
working_set_summary,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
system_prompt_for_mode_with_context_and_skills(workspace, working_set_summary, None, None, None)
|
||||
}
|
||||
|
||||
/// Get the system prompt for a specific mode with project and skills context.
|
||||
@@ -1047,7 +935,6 @@ pub fn system_prompt_for_mode_with_context(
|
||||
/// themselves are turn-volatile. Working-set metadata is now injected into the
|
||||
/// latest user message as per-turn metadata instead of this system prompt.
|
||||
pub fn system_prompt_for_mode_with_context_and_skills(
|
||||
mode: AppMode,
|
||||
workspace: &Path,
|
||||
working_set_summary: Option<&str>,
|
||||
skills_dir: Option<&Path>,
|
||||
@@ -1055,7 +942,6 @@ pub fn system_prompt_for_mode_with_context_and_skills(
|
||||
user_memory_block: Option<&str>,
|
||||
) -> SystemPrompt {
|
||||
system_prompt_for_mode_with_context_skills_and_session(
|
||||
mode,
|
||||
workspace,
|
||||
working_set_summary,
|
||||
skills_dir,
|
||||
@@ -1068,13 +954,11 @@ pub fn system_prompt_for_mode_with_context_and_skills(
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn system_prompt_for_mode_with_context_skills_and_session(
|
||||
mode: AppMode,
|
||||
workspace: &Path,
|
||||
_working_set_summary: Option<&str>,
|
||||
skills_dir: Option<&Path>,
|
||||
@@ -1082,7 +966,6 @@ pub fn system_prompt_for_mode_with_context_skills_and_session(
|
||||
session_context: PromptSessionContext<'_>,
|
||||
) -> SystemPrompt {
|
||||
system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
mode,
|
||||
workspace,
|
||||
_working_set_summary,
|
||||
skills_dir,
|
||||
@@ -1092,19 +975,14 @@ pub fn system_prompt_for_mode_with_context_skills_and_session(
|
||||
}
|
||||
|
||||
pub fn system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
mode: AppMode,
|
||||
workspace: &Path,
|
||||
_working_set_summary: Option<&str>,
|
||||
skills_dir: Option<&Path>,
|
||||
instructions: Option<&[InstructionSource]>,
|
||||
session_context: PromptSessionContext<'_>,
|
||||
) -> SystemPrompt {
|
||||
let mode_prompt = compose_prompt_with_approval_model_and_shell(
|
||||
mode,
|
||||
Personality::Calm,
|
||||
session_context.model_id,
|
||||
session_context.allow_shell,
|
||||
);
|
||||
let mode_prompt =
|
||||
compose_prompt_with_approval_model_and_shell(Personality::Calm, session_context.model_id);
|
||||
|
||||
// Load project context from workspace
|
||||
let project_context = load_project_context_with_parents(workspace);
|
||||
@@ -1176,8 +1054,8 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
full_prompt = format!("{full_prompt}\n\n{block}");
|
||||
}
|
||||
|
||||
// 4. Context Management (Agent / Yolo only).
|
||||
if matches!(mode, AppMode::Agent | AppMode::Yolo) {
|
||||
// 4. Context Management — included in all modes.
|
||||
{
|
||||
full_prompt.push_str(
|
||||
"\n\n## Context Management\n\n\
|
||||
When the conversation gets long (you'll see a context usage indicator), you can:\n\
|
||||
@@ -1353,7 +1231,6 @@ mod tests {
|
||||
let ctx = StaticPromptCtx {
|
||||
model_id: "deepseek-v4-pro",
|
||||
personality: Personality::Calm,
|
||||
shell_tools_available: true,
|
||||
default_layers: "fallback",
|
||||
};
|
||||
|
||||
@@ -1367,33 +1244,24 @@ mod tests {
|
||||
#[test]
|
||||
fn static_prompt_composer_unset_keeps_default_layers_byte_identical() {
|
||||
for personality in [Personality::Calm, Personality::Playful] {
|
||||
for shell_tools_available in [true, false] {
|
||||
let default_layers = compose_default_static_layers(
|
||||
personality,
|
||||
"deepseek-v4-flash",
|
||||
shell_tools_available,
|
||||
);
|
||||
let composed = apply_static_prompt_composer(
|
||||
None,
|
||||
personality,
|
||||
"deepseek-v4-flash",
|
||||
shell_tools_available,
|
||||
&default_layers,
|
||||
);
|
||||
let default_layers = compose_default_static_layers(personality, "deepseek-v4-flash");
|
||||
let composed = apply_static_prompt_composer(
|
||||
None,
|
||||
personality,
|
||||
"deepseek-v4-flash",
|
||||
&default_layers,
|
||||
);
|
||||
|
||||
assert_byte_identical("unset static prompt composer", &default_layers, &composed);
|
||||
}
|
||||
assert_byte_identical("unset static prompt composer", &default_layers, &composed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_prompt_composer_receives_context_and_replaces_layers() {
|
||||
let default_layers =
|
||||
compose_default_static_layers(Personality::Calm, "deepseek-v4-pro", false);
|
||||
let default_layers = compose_default_static_layers(Personality::Calm, "deepseek-v4-pro");
|
||||
let composer: Box<StaticPromptComposer> = Box::new(|ctx| {
|
||||
assert_eq!(ctx.model_id, "deepseek-v4-pro");
|
||||
assert_eq!(ctx.personality, Personality::Calm);
|
||||
assert!(!ctx.shell_tools_available);
|
||||
assert!(ctx.default_layers.contains("You are deepseek-v4-pro"));
|
||||
assert!(ctx.default_layers.contains("Personality: Calm"));
|
||||
assert!(!ctx.default_layers.contains("## Core Tool Taxonomy"));
|
||||
@@ -1405,7 +1273,6 @@ mod tests {
|
||||
Some(composer.as_ref()),
|
||||
Personality::Calm,
|
||||
"deepseek-v4-pro",
|
||||
false,
|
||||
&default_layers,
|
||||
);
|
||||
|
||||
@@ -1506,11 +1373,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn compose_prompt_injects_model_id() {
|
||||
let prompt = compose_prompt_with_approval_and_model(
|
||||
AppMode::Agent,
|
||||
Personality::Calm,
|
||||
"deepseek-v4-flash",
|
||||
);
|
||||
let prompt =
|
||||
compose_prompt_with_approval_model_and_shell(Personality::Calm, "deepseek-v4-flash");
|
||||
assert!(
|
||||
prompt.contains("You are deepseek-v4-flash"),
|
||||
"composed prompt must contain the injected model id"
|
||||
@@ -1522,13 +1386,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_prompt_keeps_shell_guidance_when_shell_tools_are_available() {
|
||||
let prompt = compose_prompt_with_approval_model_and_shell(
|
||||
AppMode::Agent,
|
||||
Personality::Calm,
|
||||
"deepseek-v4-pro",
|
||||
true,
|
||||
);
|
||||
fn base_prompt_includes_full_shell_tool_guidance() {
|
||||
let prompt =
|
||||
compose_prompt_with_approval_model_and_shell(Personality::Calm, "deepseek-v4-pro");
|
||||
|
||||
assert!(prompt.contains("- **Shell**:"));
|
||||
assert!(prompt.contains("### `exec_shell`"));
|
||||
@@ -1537,15 +1397,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_prompt_omits_shell_guidance_when_shell_tools_are_unavailable() {
|
||||
let prompt = compose_prompt_with_approval_model_and_shell(
|
||||
AppMode::Agent,
|
||||
Personality::Calm,
|
||||
"deepseek-v4-pro",
|
||||
false,
|
||||
);
|
||||
fn composed_prompt_always_keeps_shell_guidance() {
|
||||
// After decoupling `allow_shell` from the static system-prompt prefix,
|
||||
// the base prompt always includes full shell tool guidance. Whether
|
||||
// shell tools are actually available is conveyed by the per-turn
|
||||
// <runtime_prompt allow_shell="..."> tag, not by mutating message[0].
|
||||
let prompt =
|
||||
compose_prompt_with_approval_model_and_shell(Personality::Calm, "deepseek-v4-pro");
|
||||
|
||||
for shell_only in [
|
||||
for required in [
|
||||
"- **Shell**:",
|
||||
"### `exec_shell`",
|
||||
"`task_shell_start`",
|
||||
@@ -1555,31 +1415,26 @@ mod tests {
|
||||
"Hashes, encodings, checksums → `exec_shell`",
|
||||
"Current time, date, timezone → `exec_shell`",
|
||||
"System state: OS, CPU, memory, disk, ports, processes → `exec_shell`",
|
||||
"CLI through shell",
|
||||
"or `exec_shell` directly",
|
||||
] {
|
||||
assert!(
|
||||
!prompt.contains(shell_only),
|
||||
"shell-disabled prompt must not advertise {shell_only:?}"
|
||||
prompt.contains(required),
|
||||
"static prompt must always include shell guidance: {required:?}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
prompt.contains("actual runtime gates still determine what tools can execute"),
|
||||
"shell-disabled prompt should keep the runtime-gates hierarchy clause"
|
||||
"static prompt must include the runtime-gates hierarchy clause"
|
||||
);
|
||||
assert!(
|
||||
prompt.contains("`task_gate_run`") && prompt.contains("`github_issue_context`"),
|
||||
"shell-disabled prompt should keep non-shell task evidence tools"
|
||||
"static prompt must include non-shell task evidence tools"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_prompt_no_longer_inlines_tool_taxonomy() {
|
||||
let prompt = compose_prompt_with_approval_and_model(
|
||||
AppMode::Agent,
|
||||
Personality::Calm,
|
||||
"deepseek-v4-pro",
|
||||
);
|
||||
let prompt =
|
||||
compose_prompt_with_approval_model_and_shell(Personality::Calm, "deepseek-v4-pro");
|
||||
// The core tool taxonomy (grep_files / git_status / run_tests hints)
|
||||
// is no longer prepended as a standalone "## Core Tool Taxonomy" block.
|
||||
// It now lives inside the "## Runtime Policy Reference" section of the
|
||||
@@ -1628,7 +1483,6 @@ mod tests {
|
||||
fn authority_recap_appears_in_full_prompt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -1652,7 +1506,6 @@ mod tests {
|
||||
fn runtime_policy_reference_is_included_in_full_prompt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -1722,7 +1575,6 @@ mod tests {
|
||||
write_test_skill(&configured_dir, "configured-skill", "configured skill");
|
||||
|
||||
let text = match system_prompt_for_mode_with_context_and_skills(
|
||||
AppMode::Plan,
|
||||
&workspace,
|
||||
None,
|
||||
Some(&configured_dir),
|
||||
@@ -1883,7 +1735,6 @@ mod tests {
|
||||
// both depend on this ordering.
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -1896,7 +1747,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -1954,7 +1804,6 @@ mod tests {
|
||||
// motivated the closer.
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -1967,7 +1816,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -1998,7 +1846,6 @@ mod tests {
|
||||
fn hidden_thinking_uses_english_reasoning_without_locale_bookends() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2011,7 +1858,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: false,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2052,7 +1898,6 @@ mod tests {
|
||||
// "preamble is opt-in for non-English" invariant.
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let text = match system_prompt_for_mode_with_context_skills_session_and_approval(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2065,7 +1910,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2157,7 +2001,6 @@ mod tests {
|
||||
fn environment_block_is_inserted_into_system_prompt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2170,7 +2013,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2195,7 +2037,6 @@ mod tests {
|
||||
fn memory_guidance_absent_when_no_memory_block() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2208,7 +2049,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2225,7 +2065,6 @@ mod tests {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let block = "## User Memory\n\n- prefers Rust\n";
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2238,7 +2077,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2284,7 +2122,6 @@ mod tests {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
std::fs::write(tmp.path().join("README.md"), "# Pack test").expect("write readme");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2297,7 +2134,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2314,7 +2150,6 @@ mod tests {
|
||||
std::fs::write(tmp.path().join(".deepseek").join("handoff.md"), "handoff")
|
||||
.expect("handoff");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2327,7 +2162,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2352,7 +2186,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let prompt = match system_prompt_for_mode_with_context(AppMode::Agent, workspace, None) {
|
||||
let prompt = match system_prompt_for_mode_with_context(workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2365,7 +2199,7 @@ mod tests {
|
||||
#[test]
|
||||
fn missing_handoff_does_not_inject_block() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context(AppMode::Agent, tmp.path(), None) {
|
||||
let prompt = match system_prompt_for_mode_with_context(tmp.path(), None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2378,7 +2212,7 @@ mod tests {
|
||||
let dir = tmp.path().join(".deepseek");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("handoff.md"), " \n\n ").unwrap();
|
||||
let prompt = match system_prompt_for_mode_with_context(AppMode::Agent, tmp.path(), None) {
|
||||
let prompt = match system_prompt_for_mode_with_context(tmp.path(), None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2387,7 +2221,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn compose_prompt_includes_all_layers() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
// Base layer
|
||||
assert!(prompt.contains("You are codewhale"));
|
||||
// Personality layer
|
||||
@@ -2447,7 +2281,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn compose_prompt_deterministic_order() {
|
||||
let prompt = compose_prompt(AppMode::Yolo, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
let base_pos = prompt.find("You are codewhale").unwrap();
|
||||
let personality_pos = prompt.find("Personality: Calm").unwrap();
|
||||
|
||||
@@ -2460,23 +2294,19 @@ mod tests {
|
||||
fn base_prompt_is_mode_agnostic() {
|
||||
// Mode and approval text are no longer inlined into compose_prompt —
|
||||
// they travel as request-time runtime metadata.
|
||||
let agent_prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let yolo_prompt = compose_prompt(AppMode::Yolo, Personality::Calm);
|
||||
let plan_prompt = compose_prompt(AppMode::Plan, Personality::Calm);
|
||||
assert!(!agent_prompt.contains("Mode: Agent"));
|
||||
assert!(!yolo_prompt.contains("Mode: YOLO"));
|
||||
assert!(!plan_prompt.contains("Mode: Plan"));
|
||||
assert!(!agent_prompt.contains("Approval Policy:"));
|
||||
assert!(!yolo_prompt.contains("Approval Policy:"));
|
||||
assert!(!plan_prompt.contains("Approval Policy:"));
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(!prompt.contains("Mode: Agent"));
|
||||
assert!(!prompt.contains("Mode: YOLO"));
|
||||
assert!(!prompt.contains("Mode: Plan"));
|
||||
assert!(!prompt.contains("Approval Policy:"));
|
||||
// Base prompt still contains Constitutional preamble and personality
|
||||
assert!(agent_prompt.contains("You are codewhale"));
|
||||
assert!(agent_prompt.contains("Personality: Calm"));
|
||||
assert!(prompt.contains("You are codewhale"));
|
||||
assert!(prompt.contains("Personality: Calm"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_policy_no_longer_inlined_in_base_prompt() {
|
||||
let prompt = compose_prompt_with_approval(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(!prompt.contains("Mode: Agent"));
|
||||
assert!(!prompt.contains("Approval Policy:"));
|
||||
// Constitutional preamble is still present
|
||||
@@ -2485,8 +2315,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn personality_switches_correctly() {
|
||||
let calm = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let playful = compose_prompt(AppMode::Agent, Personality::Playful);
|
||||
let calm = compose_prompt(Personality::Calm);
|
||||
let playful = compose_prompt(Personality::Playful);
|
||||
assert!(calm.contains("Personality: Calm"));
|
||||
assert!(playful.contains("Personality: Playful"));
|
||||
assert!(!calm.contains("Personality: Playful"));
|
||||
@@ -2495,7 +2325,7 @@ mod tests {
|
||||
#[test]
|
||||
fn compact_template_is_included_in_full_prompt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context(AppMode::Agent, tmp.path(), None) {
|
||||
let prompt = match system_prompt_for_mode_with_context(tmp.path(), None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2516,7 +2346,6 @@ mod tests {
|
||||
fn session_goal_is_injected_below_compact_template() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
Some("## Repo Working Set\nsrc/lib.rs"),
|
||||
None,
|
||||
@@ -2529,7 +2358,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2552,7 +2380,6 @@ mod tests {
|
||||
fn empty_session_goal_is_not_injected() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let prompt = match system_prompt_for_mode_with_context_skills_and_session(
|
||||
AppMode::Agent,
|
||||
tmp.path(),
|
||||
None,
|
||||
None,
|
||||
@@ -2565,7 +2392,6 @@ mod tests {
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
show_thinking: true,
|
||||
allow_shell: true,
|
||||
},
|
||||
) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
@@ -2578,7 +2404,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tool_selection_guide_avoids_defensive_tool_suppression() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(prompt.contains("Tool Selection Guide"));
|
||||
assert!(prompt.contains("Use `agent_eval`"));
|
||||
assert!(
|
||||
@@ -2600,25 +2426,23 @@ mod tests {
|
||||
/// can't silently weaken the section to a generic "respond in the
|
||||
/// user's language" directive while keeping the heading.
|
||||
#[test]
|
||||
fn language_mirroring_section_present_in_all_modes() {
|
||||
for mode in [AppMode::Agent, AppMode::Yolo, AppMode::Plan] {
|
||||
let prompt = compose_prompt(mode, Personality::Calm);
|
||||
assert!(
|
||||
prompt.contains("## Language"),
|
||||
"## Language section missing from mode {mode:?}"
|
||||
);
|
||||
assert!(
|
||||
prompt.contains("reasoning_content"),
|
||||
"## Language section in {mode:?} must mention `reasoning_content` — \
|
||||
that field name is the structural anchor for the #588 commitment that \
|
||||
internal reasoning, not just the visible reply, follows the user's language"
|
||||
);
|
||||
}
|
||||
fn language_mirroring_section_present() {
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(
|
||||
prompt.contains("## Language"),
|
||||
"## Language section missing from base prompt"
|
||||
);
|
||||
assert!(
|
||||
prompt.contains("reasoning_content"),
|
||||
"## Language section must mention `reasoning_content` — \
|
||||
that field name is the structural anchor for the #588 commitment that \
|
||||
internal reasoning, not just the visible reply, follows the user's language"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_mirroring_prioritizes_latest_user_message_over_locale_default() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(
|
||||
prompt.contains("latest user message first"),
|
||||
"the language directive must choose the turn language from the user message before \
|
||||
@@ -2644,7 +2468,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn english_base_prompt_avoids_native_script_language_priming() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(
|
||||
!contains_cjk(&prompt),
|
||||
"English base prompt should keep native-script reinforcement in locale bookends only"
|
||||
@@ -2663,7 +2487,7 @@ mod tests {
|
||||
/// changing the wording, don't fail a test for it.
|
||||
#[test]
|
||||
fn rlm_specialty_tool_guidance_present() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
// Structural: the RLM heading must exist as a section anchor.
|
||||
assert!(prompt.contains("RLM — How to Use It"));
|
||||
// Structural: the word "rlm" must appear multiple times (tool
|
||||
@@ -2690,7 +2514,7 @@ mod tests {
|
||||
/// overridable by a single user sentence.
|
||||
#[test]
|
||||
fn local_law_tier_covers_engine_config_instructions() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(
|
||||
prompt.contains("any file configured via `EngineConfig.instructions`"),
|
||||
"Tier 5 must explicitly cover EngineConfig.instructions so \
|
||||
@@ -2700,7 +2524,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn workspace_orientation_guidance_present() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(prompt.contains("AGENTS.md"));
|
||||
assert!(prompt.contains("Local Law"));
|
||||
assert!(
|
||||
@@ -2711,7 +2535,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prompt_uses_persistent_agent_and_rlm_surface() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
for tool in [
|
||||
"agent_open",
|
||||
"agent_eval",
|
||||
@@ -2749,7 +2573,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prompt_documents_fork_context_prefix_cache_contract() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(prompt.contains("fork_context: true"));
|
||||
assert!(prompt.contains("byte-identical"));
|
||||
assert!(prompt.contains("DeepSeek prefix-cache reuse"));
|
||||
@@ -2758,7 +2582,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn subagent_done_sentinel_section_present() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
assert!(prompt.contains("Internal Sub-agent Completion Events"));
|
||||
assert!(prompt.contains("<codewhale:subagent.done>"));
|
||||
assert!(prompt.contains("not user input"));
|
||||
@@ -2768,7 +2592,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn preamble_rhythm_section_present() {
|
||||
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
|
||||
let prompt = compose_prompt(Personality::Calm);
|
||||
// Preamble rhythm is now part of the Calm personality overlay.
|
||||
// Verify the load-bearing guidance is still present.
|
||||
assert!(prompt.contains("In preambles, name the action"));
|
||||
@@ -2795,16 +2619,14 @@ mod tests {
|
||||
// Suspect #4 from #263: mode prompt churn within a single mode.
|
||||
// Two calls with identical (mode, personality) inputs must produce
|
||||
// identical bytes — anything else is a cache buster.
|
||||
for mode in [AppMode::Agent, AppMode::Yolo, AppMode::Plan] {
|
||||
for personality in [Personality::Calm, Personality::Playful] {
|
||||
let a = compose_prompt(mode, personality);
|
||||
let b = compose_prompt(mode, personality);
|
||||
assert_byte_identical(
|
||||
&format!("compose_prompt(mode={mode:?}, personality={personality:?})"),
|
||||
&a,
|
||||
&b,
|
||||
);
|
||||
}
|
||||
for personality in [Personality::Calm, Personality::Playful] {
|
||||
let a = compose_prompt(personality);
|
||||
let b = compose_prompt(personality);
|
||||
assert_byte_identical(
|
||||
&format!("compose_prompt(personality={personality:?})"),
|
||||
&a,
|
||||
&b,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2822,21 +2644,19 @@ mod tests {
|
||||
let _skills_dir = EnvVarGuard::remove("DEEPSEEK_SKILLS_DIR");
|
||||
let workspace = workspace_tmp.path();
|
||||
|
||||
for mode in [AppMode::Agent, AppMode::Yolo, AppMode::Plan] {
|
||||
let a = match system_prompt_for_mode_with_context(mode, workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
let b = match system_prompt_for_mode_with_context(mode, workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
assert_byte_identical(
|
||||
&format!("system_prompt_for_mode_with_context(mode={mode:?}) on empty workspace"),
|
||||
&a,
|
||||
&b,
|
||||
);
|
||||
}
|
||||
let a = match system_prompt_for_mode_with_context(workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
let b = match system_prompt_for_mode_with_context(workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
assert_byte_identical(
|
||||
"system_prompt_for_mode_with_context() on empty workspace",
|
||||
&a,
|
||||
&b,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2853,13 +2673,11 @@ mod tests {
|
||||
let workspace = tmp.path();
|
||||
let summary = "## Repo Working Set\nWorkspace: /tmp/x\n";
|
||||
|
||||
let a = match system_prompt_for_mode_with_context(AppMode::Agent, workspace, Some(summary))
|
||||
{
|
||||
let a = match system_prompt_for_mode_with_context(workspace, Some(summary)) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
let b = match system_prompt_for_mode_with_context(AppMode::Agent, workspace, Some(summary))
|
||||
{
|
||||
let b = match system_prompt_for_mode_with_context(workspace, Some(summary)) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2895,11 +2713,11 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let a = match system_prompt_for_mode_with_context(AppMode::Agent, workspace, None) {
|
||||
let a = match system_prompt_for_mode_with_context(workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
let b = match system_prompt_for_mode_with_context(AppMode::Agent, workspace, None) {
|
||||
let b = match system_prompt_for_mode_with_context(workspace, None) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
@@ -2925,11 +2743,10 @@ mod tests {
|
||||
std::fs::write(handoff_dir.join("handoff.md"), "# handoff body\n").unwrap();
|
||||
|
||||
let summary = "## Repo Working Set\nWorkspace: /tmp/x\n";
|
||||
let prompt =
|
||||
match system_prompt_for_mode_with_context(AppMode::Agent, workspace, Some(summary)) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
let prompt = match system_prompt_for_mode_with_context(workspace, Some(summary)) {
|
||||
SystemPrompt::Text(text) => text,
|
||||
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
|
||||
};
|
||||
|
||||
let context_pos = prompt
|
||||
.find("## Context Management")
|
||||
@@ -3078,7 +2895,6 @@ mod tests {
|
||||
|
||||
let extra_source: super::InstructionSource = extra.clone().into();
|
||||
let prompt = match super::system_prompt_for_mode_with_context_and_skills(
|
||||
AppMode::Agent,
|
||||
workspace,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -5352,7 +5352,6 @@ async fn dispatch_user_message(
|
||||
let message_index = app.api_messages.len();
|
||||
app.system_prompt = Some(
|
||||
prompts::system_prompt_for_mode_with_context_skills_and_session(
|
||||
app.mode,
|
||||
&app.workspace,
|
||||
None,
|
||||
None,
|
||||
@@ -5365,7 +5364,6 @@ async fn dispatch_user_message(
|
||||
translation_enabled: app.translation_enabled,
|
||||
model_id: &app.model,
|
||||
show_thinking: app.show_thinking,
|
||||
allow_shell: app.allow_shell,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user