* enhance(#4570): allow disabling planner stall detection * docs(#4570): add changelog fragment Emitted-Drift-Ack-Growth: plan-phase.md — the explicit opt-out gate covers all five planner and checker spawn classes Emitted-Drift-Ack-Growth: settings-advanced.md — the toggle prompt and bounded-recovery warning expose the new setting * chore(#4570): refresh compact-content baseline * fix(#4570): preserve default-on watchdog fallback * docs(#4570): qualify the chunked-mode orchestrator rules with the toggle The two chunked-planning-mode stall-watch imperatives read as unconditional, with the opt-out stated only in a following bullet. State the PLANNER_STALL_DETECTION_ENABLED condition inline, matching the three sites already qualified in plan-phase.md. * chore(#4570): refresh compact-content baselines against rebased next * fix(#4570): sync planner stall launcher Keep the stall-detection helper aligned with the canonical runtime launcher. * chore(#4570): refresh compact baseline after rebase --------- Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -162,9 +162,22 @@ const CONFIG_DEFAULTS = {
|
||||
research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
|
||||
smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
|
||||
inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
|
||||
planner_stall_detection_enabled: _getNestedConfigDefault('planner', 'stall_detection_enabled'),
|
||||
max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the planner watchdog policy from hand-edited configuration.
|
||||
* Only a real JSON boolean may override the default; strings/numbers/null
|
||||
* fail safe to the manifest-owned default-on behavior (#4570).
|
||||
*/
|
||||
function resolvePlannerStallDetectionEnabled(value: unknown): boolean {
|
||||
if (typeof value === 'boolean') return value;
|
||||
// A missing or skewed manifest must never convert an absent override into
|
||||
// permission to disable the watchdog. The documented contract is default-on.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep-merge two plain config objects. `overlay` wins on key conflict.
|
||||
* Explicit `null` in overlay overrides base (null means "unset this key").
|
||||
@@ -932,6 +945,9 @@ function loadConfigResolvedInternal(cwd: string, options: Record<string, unknown
|
||||
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
||||
project_code: get('project_code') ?? defaults.project_code,
|
||||
subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
|
||||
planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(
|
||||
getNested('planner', 'stall_detection_enabled'),
|
||||
),
|
||||
model_overrides: (parsed['model_overrides']) || null,
|
||||
agent_tools: (parsed['agent_tools']) || null,
|
||||
models: (parsed['models']) || null,
|
||||
@@ -1091,6 +1107,9 @@ function loadConfigResolvedInternal(cwd: string, options: Record<string, unknown
|
||||
resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
|
||||
context_window: (globalDefaults['context_window']) ?? defaults.context_window,
|
||||
subagent_timeout: (globalDefaults['subagent_timeout']) ?? defaults.subagent_timeout,
|
||||
planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(
|
||||
(globalDefaults['planner'] as Record<string, unknown> | undefined)?.['stall_detection_enabled'],
|
||||
),
|
||||
model_overrides: (globalDefaults['model_overrides']) || null,
|
||||
models: (globalDefaults['models']) || null,
|
||||
granularity: (globalDefaults['granularity']) !== undefined ? globalDefaults['granularity'] : null,
|
||||
@@ -1191,6 +1210,7 @@ export = {
|
||||
_getNestedConfigDefault,
|
||||
_getConfigValue,
|
||||
_getConfigNested,
|
||||
resolvePlannerStallDetectionEnabled,
|
||||
_deepMergeConfig,
|
||||
_warnedUnknownConfigKeys,
|
||||
_warnedShadowedGlobalKeys,
|
||||
|
||||
@@ -17,7 +17,7 @@ import cliExitMod = require('./cli-exit.cjs');
|
||||
const { ExitError } = cliExitMod;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import configLoader = require('./config-loader.cjs');
|
||||
const { CONFIG_DEFAULTS } = configLoader;
|
||||
const { CONFIG_DEFAULTS, resolvePlannerStallDetectionEnabled } = configLoader;
|
||||
import { platformWriteSync, platformEnsureDir } from './shell-command-projection.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import planningWorkspace = require('./planning-workspace.cjs');
|
||||
@@ -107,6 +107,7 @@ const SCHEMA_DEFAULTS: Record<string, unknown> = {
|
||||
'context_window': 200000,
|
||||
'executor.stall_detect_interval_minutes': 5,
|
||||
'executor.stall_threshold_minutes': 10,
|
||||
'planner.stall_detection_enabled': CONFIG_DEFAULTS.planner_stall_detection_enabled,
|
||||
'planner.stall_detect_interval_minutes': 5,
|
||||
'planner.stall_threshold_minutes': 10,
|
||||
'git.create_tag': true,
|
||||
@@ -185,12 +186,15 @@ function resolveSchemaDefault(cwd: string, kp: string): { found: boolean; value:
|
||||
* Centralizing emission here means masking can't be missed at a call site.
|
||||
*/
|
||||
function emitResolvedDefault(kp: string, value: unknown, raw: boolean): void {
|
||||
const resolvedValue = kp === 'planner.stall_detection_enabled'
|
||||
? resolvePlannerStallDetectionEnabled(value)
|
||||
: value;
|
||||
if (isSecretKey(kp)) {
|
||||
const masked = maskSecret(value as Parameters<typeof maskSecret>[0]);
|
||||
const masked = maskSecret(resolvedValue as Parameters<typeof maskSecret>[0]);
|
||||
output(masked, raw, masked);
|
||||
return;
|
||||
}
|
||||
output(value, raw, String(value));
|
||||
output(resolvedValue, raw, String(resolvedValue));
|
||||
}
|
||||
|
||||
// ─── Validation helpers ───────────────────────────────────────────────────────
|
||||
@@ -955,6 +959,14 @@ function cmdConfigSet(cwd: string, keyPath: string | undefined, value: string |
|
||||
}
|
||||
}
|
||||
|
||||
// Planner watchdog opt-out (#4570) — only a real boolean may change the
|
||||
// default-on policy. In particular, string "false" must not disable it.
|
||||
if (kp === 'planner.stall_detection_enabled') {
|
||||
if (typeof parsedValue !== 'boolean') {
|
||||
error(`Invalid planner.stall_detection_enabled '${val}'. Must be a boolean (true or false).`);
|
||||
}
|
||||
}
|
||||
|
||||
// #3086 — git.create_tag: boolean only
|
||||
if (kp === 'git.create_tag') {
|
||||
if (typeof parsedValue !== 'boolean') {
|
||||
@@ -1251,6 +1263,10 @@ function cmdConfigGet(cwd: string, keyPath: string | undefined, raw: boolean, de
|
||||
error(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (kp === 'planner.stall_detection_enabled') {
|
||||
current = resolvePlannerStallDetectionEnabled(current);
|
||||
}
|
||||
|
||||
// Never echo plaintext for sensitive keys via config-get. Plaintext lives
|
||||
// in config.json on disk; the CLI surface always shows the masked form.
|
||||
if (isSecretKey(kp)) {
|
||||
|
||||
Reference in New Issue
Block a user