feat(coding-agent): enabled per-rule interrupt mode overrides via frontmatter
- Added per-rule `interruptMode` override capability to TTSR interrupt logic via optional frontmatter field. - Changed interrupt behavior to respect per-rule `interruptMode` settings with fallback to global `ttsr.interruptMode` configuration. - Extended `Rule` and `RuleConfig` interfaces with optional `interruptMode` property for granular control. - Updated rule discovery to extract and validate `interruptMode` from frontmatter with proper enum type checking.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added per-rule TTSR interrupt mode override via `interruptMode` field in rule frontmatter to allow fine-grained control over when TTSR interrupts stream processing
|
||||
- Added `task` model role to allow configuring a dedicated model for subtask execution via `modelRoles.task` setting
|
||||
- Added `moveCursorToMessageEnd` and `moveCursorToMessageStart` prompt actions to navigate to the beginning and end of the entire message
|
||||
- Added support for provider-level `compat` configuration to apply OpenAI compatibility settings across all models from a provider
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed TTSR interrupt logic to respect per-rule `interruptMode` settings, falling back to global `ttsr.interruptMode` when rule-level override is not specified
|
||||
- Reorganized settings tabs from 12 tabs (display, agent, input, tools, config, services, bash, lsp, ttsr, status) to 8 focused tabs (appearance, model, interaction, context, editing, tools, tasks, providers) for improved discoverability
|
||||
- Consolidated status line settings into the Appearance tab instead of a separate Status tab
|
||||
- Reorganized sampling parameters (temperature, topP, topK, minP, presencePenalty, repetitionPenalty) into the Model tab
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface RuleFrontmatter {
|
||||
condition?: string | string[];
|
||||
/** New key for TTSR stream scope. */
|
||||
scope?: string | string[];
|
||||
/** Per-rule TTSR interrupt mode override. */
|
||||
interruptMode?: "never" | "prose-only" | "tool-only" | "always";
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,8 @@ export interface Rule {
|
||||
condition?: string[];
|
||||
/** Optional stream scope tokens (for example: text, thinking, tool:edit(*.ts)). */
|
||||
scope?: string[];
|
||||
/** Per-rule TTSR interrupt mode override (falls back to global ttsr.interruptMode). */
|
||||
interruptMode?: "never" | "prose-only" | "tool-only" | "always";
|
||||
/** Source metadata */
|
||||
_source: SourceMeta;
|
||||
}
|
||||
|
||||
@@ -163,6 +163,11 @@ export function buildRuleFromMarkdown(
|
||||
}
|
||||
|
||||
const resolvedName = options?.ruleName ?? name.replace(options?.stripNamePattern ?? /\.(md|mdc)$/, "");
|
||||
const rawMode = frontmatter.interruptMode;
|
||||
const interruptMode: Rule["interruptMode"] =
|
||||
rawMode === "never" || rawMode === "prose-only" || rawMode === "tool-only" || rawMode === "always"
|
||||
? rawMode
|
||||
: undefined;
|
||||
return {
|
||||
name: resolvedName,
|
||||
path: filePath,
|
||||
@@ -172,6 +177,7 @@ export function buildRuleFromMarkdown(
|
||||
description: typeof frontmatter.description === "string" ? frontmatter.description : undefined,
|
||||
condition,
|
||||
scope,
|
||||
interruptMode,
|
||||
_source: source,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ export class AgentSession {
|
||||
|
||||
this.#addPendingTtsrInjections(matches);
|
||||
|
||||
if (this.#shouldInterruptForTtsrMatch(matchContext)) {
|
||||
if (this.#shouldInterruptForTtsrMatch(matches, matchContext)) {
|
||||
// Abort the stream immediately — do not gate on extension callbacks
|
||||
this.#ttsrAbortPending = true;
|
||||
this.#ensureTtsrResumePromise();
|
||||
@@ -1027,18 +1027,17 @@ export class AgentSession {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#shouldInterruptForTtsrMatch(matchContext: TtsrMatchContext): boolean {
|
||||
const mode = this.#ttsrManager?.getSettings().interruptMode ?? "always";
|
||||
if (mode === "never") {
|
||||
return false;
|
||||
#shouldInterruptForTtsrMatch(matches: Rule[], matchContext: TtsrMatchContext): boolean {
|
||||
const globalMode = this.#ttsrManager?.getSettings().interruptMode ?? "always";
|
||||
for (const rule of matches) {
|
||||
const mode = rule.interruptMode ?? globalMode;
|
||||
if (mode === "never") continue;
|
||||
if (mode === "prose-only" && (matchContext.source === "text" || matchContext.source === "thinking"))
|
||||
return true;
|
||||
if (mode === "tool-only" && matchContext.source === "tool") return true;
|
||||
if (mode === "always") return true;
|
||||
}
|
||||
if (mode === "prose-only") {
|
||||
return matchContext.source === "text" || matchContext.source === "thinking";
|
||||
}
|
||||
if (mode === "tool-only") {
|
||||
return matchContext.source === "tool";
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#queueDeferredTtsrInjectionIfNeeded(assistantMsg: AssistantMessage): void {
|
||||
|
||||
Reference in New Issue
Block a user