mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 17:09:13 -10:00
fix(tasks): bound the human-wait pause and gate it on the HTTP listener
Maintainer follow-up on @asto18089's #7230, landing it as their PR. - Apply the maintainer patch from the review thread: `[tools] task_human_wait_cap_seconds` (unset or 0 -> 1800 s, clamped to 1..=86400) feeds `TaskExecutionLimits::human_wait_cap`, the default cap drops from 24 h to 30 min, docs/CONFIGURATION.md and config.example.toml document it, and the blank line after the `drive_engine_turn` doc comment is gone (clippy empty_line_after_doc_comments). - A wall deadline that has already passed is no longer skipped: `begin_human_wait` declines to open a window once the wall deadline (moved only by closed windows) is behind it, so the guard ends the run with WallTimeout instead of letting the human answer and then failing the task right after. The deadline arithmetic lives in one helper shared with `evaluate`, which also drops an always-zero open-window term from the no-window branch. Idle crossings are left alone on purpose: #6118 suspends idle while an approval is pending, and the worker supervisor lets queued executor progress win an idle race, so refusing a window on an idle crossing would turn a starved event channel into a spurious IdleTimeout. - Answerable waits require a Runtime that binds its HTTP listener: `server_task_config` takes `http_decisions` and `run_http_server` passes `!local_control`. Local Stdio, Socket and ACP control frontends bind no listener, and the control transport refuses user-input replies, so their tasks keep the unanswerable default. - The two existing server_task pins pass the flag; the config pin also asserts the local-control case stays unanswerable. - Merge current main first so the #5898 release-note receipt (dropped from main's changelog after the earlier branch update) is re-added in the 0.10.3 Added section, with the contributor credit extended. Evidence: all 13 existing ExecutionGuard unit tests, extracted verbatim with the guard code into a scratch harness, pass on both the previous head and this one; the same harness shows the reported ordering (wall 400 ms, prompt at 500 ms) returning Run before and WallTimeout after. rustfmt clean on the touched files. Full crate builds and the async task tests are left to this PR's CI. Refs #5898 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ac9a30319a
commit
bf73bf8778
9 files changed
+104
-46
No files matched your search
+2
-1
@@ -61,6 +61,7 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
|
||||
### Added
|
||||
|
||||
- **Task wall clock pauses for answerable human waits (#5898):** a durable task run by the Runtime API server no longer spends its wall-time budget while a prompt on that run waits for an approval or user-input answer. `[tools] task_human_wait_cap_seconds` (default 1800, maximum 86400) bounds the total wait and fails an unanswered task with `human_wait_timeout`, releasing its worker. Only a Runtime serving its HTTP API pauses the clock; local Stdio, Socket and ACP control frontends keep the wall deadline, and a wall deadline crossed before the prompt still ends the task. The TUI's own tasks are unchanged. Thanks [@asto18089](https://github.com/asto18089).
|
||||
- **Inspect and repair plugin snapshots:** `codew plugins status [name]
|
||||
[--json]` reports bundle state and diagnostics without changing plugin
|
||||
stores. Runtime loading restores a missing snapshot only from bytes matching
|
||||
@@ -597,7 +598,7 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
- **[Boyce / @Aboysky](https://github.com/Aboysky)** — refreshed the README translation source stamps ([#7104](https://github.com/codewhale-hq/Codewhale/pull/7104)) and localized `/undo` feedback ([#7121](https://github.com/codewhale-hq/Codewhale/pull/7121)).
|
||||
- **[@Ricardo-M-L](https://github.com/Ricardo-M-L)** — opened `.tsx` documents with the `typescriptreact` language ID ([#7140](https://github.com/codewhale-hq/Codewhale/pull/7140)).
|
||||
- **[@M-Maciej](https://github.com/M-Maciej)** — reported answerless output-limit stops in long Fleet proofs ([#6990](https://github.com/codewhale-hq/Codewhale/issues/6990)).
|
||||
- **[@asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)). Dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept.
|
||||
- **[@asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)). Dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept. Paused the durable task wall clock while an answerable prompt waits ([#7230](https://github.com/codewhale-hq/Codewhale/pull/7230)).
|
||||
- **[@Statter](https://github.com/Statter)** — reported black-on-black text on terminals that do not report their background ([#7043](https://github.com/codewhale-hq/Codewhale/issues/7043)).
|
||||
- **[@eugenicum](https://github.com/eugenicum)** — asked for a visible Full Access flag for `serve --acp` ([#6992](https://github.com/codewhale-hq/Codewhale/issues/6992)).
|
||||
- **[@luestr](https://github.com/luestr)** — reported file and conversation rollback diverging ([#6970](https://github.com/codewhale-hq/Codewhale/issues/6970)), the Linux multiline send key ([#7093](https://github.com/codewhale-hq/Codewhale/issues/7093)) and session export guidance ([#6996](https://github.com/codewhale-hq/Codewhale/issues/6996)).
|
||||
|
||||
@@ -525,6 +525,11 @@ show_whats_new = true
|
||||
# 0 disables the timeout entirely; values above 86400 (24h) are clamped.
|
||||
# user_input_timeout_seconds = 300
|
||||
#
|
||||
# Fail-safe ceiling, in seconds, for how long a durable task (runtime API
|
||||
# server) may sit on an unanswered approval or question before it fails with
|
||||
# human_wait_timeout. 0 or omitted uses 1800; values are clamped to 1..=86400.
|
||||
# task_human_wait_cap_seconds = 1800
|
||||
#
|
||||
# Bound an interactive approval card's wait (#6101). When the window elapses
|
||||
# the card resolves to deny (fail-closed) and the transcript says the bound
|
||||
# denied the call; omitted or 0 waits indefinitely.
|
||||
|
||||
@@ -2073,21 +2073,25 @@ pub const RUNTIME_LISTENING_PREFIX: &str = "Runtime API listening on http://";
|
||||
|
||||
/// Build the task manager config for the runtime API server.
|
||||
///
|
||||
/// This is where the server opts its tasks into answerable human waits: the
|
||||
/// tasks it runs are served by this server's HTTP decision endpoints
|
||||
/// (`POST /v1/approvals/{id}` and the user-input endpoints), so a pending
|
||||
/// prompt on them is a human-paced wait the wall clock may pause for — see
|
||||
/// [`TaskManagerConfig::human_waits_answerable`].
|
||||
/// This is where the server opts its tasks into answerable human waits, and
|
||||
/// only when it binds its HTTP listener: the tasks it runs are then served by
|
||||
/// this server's HTTP decision endpoints (`POST /v1/approvals/{id}` and the
|
||||
/// user-input endpoints), so a pending prompt on them is a human-paced wait
|
||||
/// the wall clock may pause for — see
|
||||
/// [`TaskManagerConfig::human_waits_answerable`]. A local Stdio, Socket or
|
||||
/// ACP control frontend binds no listener and cannot deliver a task-owned
|
||||
/// answer, so its tasks keep the unanswerable default.
|
||||
#[must_use]
|
||||
pub(crate) fn server_task_config(
|
||||
config: &Config,
|
||||
workspace: PathBuf,
|
||||
workers: usize,
|
||||
http_decisions: bool,
|
||||
) -> TaskManagerConfig {
|
||||
let default_model = runtime_request_model(config, None).unwrap_or_else(|_| "auto".into());
|
||||
let mut task_cfg =
|
||||
TaskManagerConfig::from_runtime(config, workspace, Some(default_model), Some(workers));
|
||||
task_cfg.human_waits_answerable = true;
|
||||
task_cfg.human_waits_answerable = http_decisions;
|
||||
task_cfg
|
||||
}
|
||||
|
||||
@@ -2120,7 +2124,7 @@ pub async fn run_http_server(
|
||||
);
|
||||
|
||||
let task_default_model = runtime_request_model(&config, None).unwrap_or_else(|_| "auto".into());
|
||||
let task_cfg = server_task_config(&config, workspace.clone(), options.workers);
|
||||
let task_cfg = server_task_config(&config, workspace.clone(), options.workers, !local_control);
|
||||
#[cfg(any(unix, windows))]
|
||||
let selected_store =
|
||||
RuntimeThreadManagerConfig::from_task_data_dir(task_cfg.data_dir.clone()).data_dir;
|
||||
|
||||
@@ -69,11 +69,16 @@ fn server_task_config_opts_tasks_into_answerable_human_waits() {
|
||||
// tasks (`POST /v1/approvals/{id}`, the user-input endpoints), so its
|
||||
// config must opt in explicitly; everywhere else (`from_runtime`, the
|
||||
// TUI's private runtime) the safe default is unanswerable.
|
||||
let cfg = server_task_config(&Config::default(), PathBuf::from("."), 2);
|
||||
let cfg = server_task_config(&Config::default(), PathBuf::from("."), 2, true);
|
||||
assert!(
|
||||
cfg.human_waits_answerable,
|
||||
"server task config must arm answerable human waits"
|
||||
);
|
||||
let local = server_task_config(&Config::default(), PathBuf::from("."), 2, false);
|
||||
assert!(
|
||||
!local.human_waits_answerable,
|
||||
"a local control frontend binds no HTTP listener, so its tasks stay unanswerable"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -101,7 +106,7 @@ async fn server_task_runtime_wires_answerable_human_waits_into_the_executor() ->
|
||||
RuntimeThreadManagerConfig::from_task_data_dir(root.path().join("runtime")),
|
||||
)?);
|
||||
let task_manager = TaskManager::start_with_runtime_manager(
|
||||
server_task_config(&config, workspace, 1),
|
||||
server_task_config(&config, workspace, 1, true),
|
||||
config,
|
||||
runtime_threads.clone(),
|
||||
)
|
||||
|
||||
@@ -582,7 +582,7 @@ impl Default for TaskExecutionLimits {
|
||||
idle_progress: Duration::from_secs(2 * 60),
|
||||
cancel_grace: Duration::from_secs(5),
|
||||
persist_debounce: Duration::from_millis(250),
|
||||
human_wait_cap: Duration::from_secs(24 * 60 * 60),
|
||||
human_wait_cap: Duration::from_secs(30 * 60),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,7 +639,9 @@ impl ExecutionGuard {
|
||||
}
|
||||
|
||||
/// Mark the start of a human-paced wait (idempotent). Only the first
|
||||
/// begin of an unmatched begin/end pair takes effect.
|
||||
/// begin of an unmatched begin/end pair takes effect, and none does once
|
||||
/// the wall deadline has already passed: that crossing still ends the
|
||||
/// task instead of being moved past the prompt.
|
||||
///
|
||||
/// Windows merge rather than stack: the first begin sets the origin and
|
||||
/// any end closes the whole window. That is sound because the engine
|
||||
@@ -649,7 +651,7 @@ impl ExecutionGuard {
|
||||
/// today. If a future change lets nested prompts surface concurrently,
|
||||
/// switch to paired (counted) windows first.
|
||||
fn begin_human_wait(&mut self, now: Instant) {
|
||||
if self.human_wait_since.is_none() {
|
||||
if self.human_wait_since.is_none() && self.wall_deadline().is_none_or(|wall| now < wall) {
|
||||
self.human_wait_since = Some(now);
|
||||
}
|
||||
}
|
||||
@@ -676,6 +678,32 @@ impl ExecutionGuard {
|
||||
.saturating_sub(open)
|
||||
}
|
||||
|
||||
fn wall_deadline(&self) -> Option<Instant> {
|
||||
self.started_at
|
||||
.checked_add(self.limits.wall_time)
|
||||
.and_then(|deadline| deadline.checked_add(self.human_wait_total))
|
||||
}
|
||||
|
||||
/// Attribute the timeout to the limit that was crossed first, not to the
|
||||
/// one this tick happens to check first. When the watchdog is starved
|
||||
/// past both deadlines (a >=250 ms scheduler stall on a loaded CI runner
|
||||
/// is enough with the test budgets), the idle limit that expired earlier
|
||||
/// is still the truthful reason; a tie keeps the wall limit's precedence
|
||||
/// (issue #5898). The wall deadline moves with every closed human-wait
|
||||
/// window, so it stays `wall_time` away from the engine-owned elapsed
|
||||
/// time rather than from the raw wall clock.
|
||||
fn crossed_deadline(&self, now: Instant) -> Option<TaskTerminalReason> {
|
||||
let idle_deadline = self.last_progress_at.checked_add(self.limits.idle_progress);
|
||||
match (self.wall_deadline(), idle_deadline) {
|
||||
(Some(wall), Some(idle)) if now >= wall && wall <= idle => {
|
||||
Some(TaskTerminalReason::WallTimeout)
|
||||
}
|
||||
(_, Some(idle)) if now >= idle => Some(TaskTerminalReason::IdleTimeout),
|
||||
(Some(wall), _) if now >= wall => Some(TaskTerminalReason::WallTimeout),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn note_progress(&mut self, now: Instant) {
|
||||
self.last_progress_at = now;
|
||||
}
|
||||
@@ -729,34 +757,7 @@ impl ExecutionGuard {
|
||||
// task even between windows.
|
||||
Some(TaskTerminalReason::HumanWaitTimeout)
|
||||
} else {
|
||||
// Attribute the timeout to the limit that was crossed first, not
|
||||
// to the one this tick happens to check first. When the watchdog
|
||||
// is starved past both deadlines (a >=250 ms scheduler stall on a
|
||||
// loaded CI runner is enough with the test budgets), the idle
|
||||
// limit that expired earlier is still the truthful reason; a tie
|
||||
// keeps the wall limit's precedence (issue #5898).
|
||||
// The wall deadline moves with every excluded human-wait window,
|
||||
// so it stays `wall_time` away from the engine-owned elapsed
|
||||
// time rather than from the raw wall clock.
|
||||
let open = self
|
||||
.human_wait_since
|
||||
.map(|since| now.saturating_duration_since(since))
|
||||
.unwrap_or_default();
|
||||
let wall_deadline =
|
||||
self.started_at
|
||||
.checked_add(self.limits.wall_time)
|
||||
.and_then(|deadline| {
|
||||
deadline.checked_add(self.human_wait_total.saturating_add(open))
|
||||
});
|
||||
let idle_deadline = self.last_progress_at.checked_add(self.limits.idle_progress);
|
||||
match (wall_deadline, idle_deadline) {
|
||||
(Some(wall), Some(idle)) if now >= wall && wall <= idle => {
|
||||
Some(TaskTerminalReason::WallTimeout)
|
||||
}
|
||||
(_, Some(idle)) if now >= idle => Some(TaskTerminalReason::IdleTimeout),
|
||||
(Some(wall), _) if now >= wall => Some(TaskTerminalReason::WallTimeout),
|
||||
_ => None,
|
||||
}
|
||||
self.crossed_deadline(now)
|
||||
};
|
||||
if let Some(reason) = pending {
|
||||
return GuardAction::Interrupt { reason };
|
||||
@@ -829,7 +830,10 @@ impl TaskManagerConfig {
|
||||
default_mode: "agent".to_string(),
|
||||
allow_shell: config.allow_shell(),
|
||||
trust_mode: false,
|
||||
execution_limits: TaskExecutionLimits::default(),
|
||||
execution_limits: TaskExecutionLimits {
|
||||
human_wait_cap: config.task_human_wait_cap(),
|
||||
..TaskExecutionLimits::default()
|
||||
},
|
||||
human_waits_answerable: false,
|
||||
}
|
||||
}
|
||||
@@ -1164,7 +1168,6 @@ impl TaskExecutor for EngineTaskExecutor {
|
||||
/// decision: with no host able to deliver a decision, a pending prompt is
|
||||
/// not a human-paced wait but a prompt nobody will answer, so the wall
|
||||
/// clock keeps running and releases the worker at its own deadline.
|
||||
|
||||
async fn drive_engine_turn(
|
||||
runtime_threads: &RuntimeThreadManager,
|
||||
thread_id: &str,
|
||||
|
||||
@@ -1112,6 +1112,13 @@ pub struct ToolsConfig {
|
||||
/// with a warning.
|
||||
#[serde(default)]
|
||||
pub user_input_timeout_seconds: Option<u64>,
|
||||
|
||||
/// Seconds a durable task may stay parked on an unanswered approval or
|
||||
/// user-input prompt before it fails with `human_wait_timeout`. Absent,
|
||||
/// or an explicit `0`, uses the 1,800-second (30 minute) default. Values
|
||||
/// are clamped to `1..=86_400` (24h) with a warning.
|
||||
#[serde(default)]
|
||||
pub task_human_wait_cap_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
/// Persistent-goal loop controls (`[goal]` table in config.toml, #5052).
|
||||
@@ -3848,6 +3855,27 @@ impl Config {
|
||||
Some(std::time::Duration::from_secs(seconds.min(MAX_SECONDS)))
|
||||
}
|
||||
|
||||
/// Effective ceiling for how long one durable task may stay parked on a
|
||||
/// human answer. Absent or `0` is the 30 minute default; other values
|
||||
/// clamp to `1..=86_400` seconds.
|
||||
#[must_use]
|
||||
pub fn task_human_wait_cap(&self) -> std::time::Duration {
|
||||
const DEFAULT_SECONDS: u64 = 30 * 60;
|
||||
const MAX_SECONDS: u64 = 86_400;
|
||||
let seconds = self
|
||||
.tools
|
||||
.as_ref()
|
||||
.and_then(|tools| tools.task_human_wait_cap_seconds)
|
||||
.filter(|seconds| *seconds > 0)
|
||||
.unwrap_or(DEFAULT_SECONDS);
|
||||
if seconds > MAX_SECONDS {
|
||||
tracing::warn!(
|
||||
"[tools] task_human_wait_cap_seconds={seconds} exceeds 24h; clamping to {MAX_SECONDS}"
|
||||
);
|
||||
}
|
||||
std::time::Duration::from_secs(seconds.min(MAX_SECONDS))
|
||||
}
|
||||
|
||||
/// Load configuration from disk and merge with environment overrides.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -61,6 +61,7 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
|
||||
### Added
|
||||
|
||||
- **Task wall clock pauses for answerable human waits (#5898):** a durable task run by the Runtime API server no longer spends its wall-time budget while a prompt on that run waits for an approval or user-input answer. `[tools] task_human_wait_cap_seconds` (default 1800, maximum 86400) bounds the total wait and fails an unanswered task with `human_wait_timeout`, releasing its worker. Only a Runtime serving its HTTP API pauses the clock; local Stdio, Socket and ACP control frontends keep the wall deadline, and a wall deadline crossed before the prompt still ends the task. The TUI's own tasks are unchanged. Thanks [@asto18089](https://github.com/asto18089).
|
||||
- **Inspect and repair plugin snapshots:** `codew plugins status [name]
|
||||
[--json]` reports bundle state and diagnostics without changing plugin
|
||||
stores. Runtime loading restores a missing snapshot only from bytes matching
|
||||
@@ -597,7 +598,7 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
- **[Boyce / @Aboysky](https://github.com/Aboysky)** — refreshed the README translation source stamps ([#7104](https://github.com/codewhale-hq/Codewhale/pull/7104)) and localized `/undo` feedback ([#7121](https://github.com/codewhale-hq/Codewhale/pull/7121)).
|
||||
- **[@Ricardo-M-L](https://github.com/Ricardo-M-L)** — opened `.tsx` documents with the `typescriptreact` language ID ([#7140](https://github.com/codewhale-hq/Codewhale/pull/7140)).
|
||||
- **[@M-Maciej](https://github.com/M-Maciej)** — reported answerless output-limit stops in long Fleet proofs ([#6990](https://github.com/codewhale-hq/Codewhale/issues/6990)).
|
||||
- **[@asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)). Dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept.
|
||||
- **[@asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)). Dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept. Paused the durable task wall clock while an answerable prompt waits ([#7230](https://github.com/codewhale-hq/Codewhale/pull/7230)).
|
||||
- **[@Statter](https://github.com/Statter)** — reported black-on-black text on terminals that do not report their background ([#7043](https://github.com/codewhale-hq/Codewhale/issues/7043)).
|
||||
- **[@eugenicum](https://github.com/eugenicum)** — asked for a visible Full Access flag for `serve --acp` ([#6992](https://github.com/codewhale-hq/Codewhale/issues/6992)).
|
||||
- **[@luestr](https://github.com/luestr)** — reported file and conversation rollback diverging ([#6970](https://github.com/codewhale-hq/Codewhale/issues/6970)), the Linux multiline send key ([#7093](https://github.com/codewhale-hq/Codewhale/issues/7093)) and session export guidance ([#6996](https://github.com/codewhale-hq/Codewhale/issues/6996)).
|
||||
|
||||
+12
-1
@@ -3556,7 +3556,18 @@ the model reports the tool absent and finishes instead of stalling.
|
||||
user_input_timeout_seconds = 300 # opt into 5 minutes; omitted or 0 waits indefinitely; maximum 86400
|
||||
```
|
||||
|
||||
This key governs question waits only. Approvals have their own clock,
|
||||
`task_human_wait_cap_seconds` is a separate, fail-safe ceiling for durable
|
||||
tasks run by the runtime API server. While a task waits on an approval or
|
||||
user-input answer its wall clock is paused; if nobody answers within this many
|
||||
seconds the task fails with `human_wait_timeout` and releases its worker.
|
||||
Omitted or `0` uses 1800 (30 minutes); values are clamped to `1..=86400`.
|
||||
|
||||
```toml
|
||||
[tools]
|
||||
task_human_wait_cap_seconds = 3600 # optional; default 1800
|
||||
```
|
||||
|
||||
`user_input_timeout_seconds` governs question waits only. Approvals have their own clock,
|
||||
`[approval] timeout_seconds`, which waits indefinitely unless you set it.
|
||||
Wall-clock and stream protections elsewhere are unaffected.
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ changelog or release notes, and this contributor record.
|
||||
- **[luochen211](https://github.com/luochen211)** — one form per term in the zh-Hans pack ([#7150](https://github.com/codewhale-hq/Codewhale/pull/7150)) and blanket dead-code allows dropped in fleet alerts and the pet worker ([#7149](https://github.com/codewhale-hq/Codewhale/pull/7149)).
|
||||
- **[gannp](https://github.com/gannp)** — vLLM effort values that follow the local model template ([#7124](https://github.com/codewhale-hq/Codewhale/issues/7124), [#7125](https://github.com/codewhale-hq/Codewhale/pull/7125)).
|
||||
- **[Sh1Zuku / SparkofSpike](https://github.com/SparkofSpike)** — relocated state root before session-side state ([#7097](https://github.com/codewhale-hq/Codewhale/pull/7097)) goal continuation that pauses to ask ([#7175](https://github.com/codewhale-hq/Codewhale/pull/7175)) and recall of the transcript compaction replaced ([#7173](https://github.com/codewhale-hq/Codewhale/pull/7173)). Also clarified goal-tool guidance while awaiting an answer ([#7177](https://github.com/codewhale-hq/Codewhale/pull/7177)) and kept star keycap graphemes intact through Markdown parsing ([#7176](https://github.com/codewhale-hq/Codewhale/pull/7176)). Their journal fix keeps a provisional input on the same session branch when the Engine stamps it ([#7180](https://github.com/codewhale-hq/Codewhale/pull/7180)). Added the bare DeepSeek V4.1 Flash catalog entry ([#7183](https://github.com/codewhale-hq/Codewhale/pull/7183)). Stopped a home-directory workspace from refusing its own state ([#7207](https://github.com/codewhale-hq/Codewhale/pull/7207)).
|
||||
- **[asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)), dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept.
|
||||
- **[asto18089](https://github.com/asto18089)** — diagnosed the NoNewPrivs child-matrix failure on hardened containers ([#7021](https://github.com/codewhale-hq/Codewhale/issues/7021)), dropped the false sandbox claim from the `fetch_url` description ([#7222](https://github.com/codewhale-hq/Codewhale/pull/7222)) and limited nested-repository discovery to the sub-agent worktree anchor ([#7224](https://github.com/codewhale-hq/Codewhale/pull/7224)). Accepted absolute in-workspace paths in sub-agent write claims ([#7227](https://github.com/codewhale-hq/Codewhale/pull/7227)). Repaired pre-0.10.0 topology checkpoint placement on session restore ([#7229](https://github.com/codewhale-hq/Codewhale/pull/7229)). Pointed the `verify` description at the Run tool instead of a retired name ([#7223](https://github.com/codewhale-hq/Codewhale/pull/7223)). Swept stale mode-jump and rail copy from the non-English packs ([#7225](https://github.com/codewhale-hq/Codewhale/pull/7225)), accepted absolute image paths that resolve inside the workspace ([#7226](https://github.com/codewhale-hq/Codewhale/pull/7226)) and reported follow-up delivery truthfully ([#7228](https://github.com/codewhale-hq/Codewhale/pull/7228)); all three were harvested with the original commits kept. Paused the durable task wall clock while an answerable prompt waits ([#7230](https://github.com/codewhale-hq/Codewhale/pull/7230)).
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Reference in new issue
Block a user