supercode-harness 0.5.45

The optional native Volter Harness agent and tool harness
Documentation
//! BP-4 (catalog:91 "Synthetic context-injection blocks", design §1.4:
//! "harness-spliced reminders/nudges … the ambient nudge class is core"):
//! the injection REGISTRY behind `core.context_injections`.
//!
//! Before BP-4 the key gated exactly one thing — a static, caller-populated
//! [`ContextInjectionBlock`] list appended once at construction — so a
//! preset turning it on got nothing, because no preset (and nothing in the
//! product) ever populated the list. Neither parity preset set the key at
//! all, which made the gap invisible.
//!
//! The registry has three sources, spliced in this order:
//!
//! 1. **Built-in blocks** ([`builtin_blocks`]) — derived from the RESOLVED
//!    config, so a block only appears when the capability it talks about is
//!    actually armed. This is the "~25 block types" class cx's own
//!    `context/` library and cc's `<system-reminder>` blocks occupy: ambient
//!    statements about the harness the model is running inside, which no
//!    instruction file can know.
//! 2. **User blocks** — [`crate::Config::context_injection_blocks`], the
//!    pre-existing embedder-populated list, unchanged.
//! 3. **Spliced blocks** — [`crate::Agent::inject_context_block`], added
//!    mid-session (a hook's `additionalContext`, a frontend's nudge, an
//!    orchestrator's brief). This is the half that makes the mechanism a
//!    SEAM rather than a startup constant.
//!
//! Everything here is a no-op when `core.context_injections` is false (the
//! default): [`assemble`] returns an empty string and nothing is read.

use crate::config::{Config, ContextInjectionBlock};
use crate::modules::ModuleId;

/// The built-in ambient blocks armed by `config`, in a stable order.
///
/// Each block states something true about THIS resolved configuration that
/// the model cannot otherwise know, and each is gated on the capability it
/// describes — a config with none of them armed contributes no blocks at
/// all, so this is never boilerplate the model has to ignore.
pub fn builtin_blocks(config: &Config) -> Vec<ContextInjectionBlock> {
    let mut blocks = Vec::new();
    let active = |id: ModuleId| config.module_registry && config.module_activation.is_active(id);

    if active(ModuleId::Todos) {
        blocks.push(ContextInjectionBlock::new(
            "Task list",
            "A persistent task list is available through the plan/todo tool. Keep it current: \
             write the plan out before starting multi-step work, mark each step completed as \
             you finish it, and add work you discover along the way. The list survives \
             compaction, so it is the durable record of where this session is.",
        ));
    }
    if active(ModuleId::PlanMode) {
        blocks.push(ContextInjectionBlock::new(
            "Plan mode",
            "This session can enter a read-only planning mode. While it is active, do not edit \
             files, write files, or run state-changing commands — investigate, then present the \
             plan and wait for it to be accepted.",
        ));
    }
    if !config.permissions_protected_paths.is_empty() {
        blocks.push(ContextInjectionBlock::new(
            "Protected paths",
            format!(
                "Writes to these paths are never auto-approved and will stop for the user's \
                 decision: {}. Prefer a route that doesn't touch them.",
                config.permissions_protected_paths.join(", ")
            ),
        ));
    }
    if active(ModuleId::ToolsBackground) {
        blocks.push(ContextInjectionBlock::new(
            "Background work",
            "Long-running commands can be started in the background instead of blocking the \
             turn. Start them detached, keep working, and read their output when it matters — \
             never sit on a foreground command waiting for it to finish.",
        ));
    }
    blocks
}

/// Every block a system prompt should carry, in splice order: built-ins,
/// then the config's own list, then anything spliced in at runtime.
/// Empty (and free of any work) when `core.context_injections` is off.
pub fn blocks(config: &Config, spliced: &[ContextInjectionBlock]) -> Vec<ContextInjectionBlock> {
    if !config.context_injections {
        return Vec::new();
    }
    let mut out = builtin_blocks(config);
    out.extend(config.context_injection_blocks.iter().cloned());
    out.extend(spliced.iter().cloned());
    out
}

/// Render blocks as the `\n\n# {name}\n{content}` sections the assembly site
/// appends to the system prompt — the exact shape P4e's static list used, so
/// a config that only set `context_injection_blocks` renders identically.
pub fn render(blocks: &[ContextInjectionBlock]) -> String {
    let mut out = String::new();
    for block in blocks {
        out.push_str(&format!("\n\n# {}\n{}", block.name, block.content));
    }
    out
}

/// [`blocks`] + [`render`] — the whole injection contribution to a system
/// prompt.
pub fn assemble(config: &Config, spliced: &[ContextInjectionBlock]) -> String {
    render(&blocks(config, spliced))
}