* fix(sessions): complete tail-read windows despite short positional reads
Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.
- readRecentTranscriptTailLinesAsync: tail-window read for recent
session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
preview tail read now loops until the requested window fills
The same readFileWindowFully helper was introduced in #108253 and
expanded with a sync variant in #108127 (both by sunlit-deng).
* fix(sessions): complete sync title preview reads
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Ports the unique coverage from #108126 onto the parser landed via #106850:
wrapped 401/403/404 and quota-exhausted 429 stay non-retryable, an
envelope embedded in user text is ignored, and a short-window wrapped
429 retries. Credit @zhanxingxin1998.
* fix: normalize provider keys during model config merge
* fix: normalize source-managed provider keys when reapplying secret markers
Make enforceSourceManagedProviderSecrets canonicalize provider keys with
normalizeProviderId so mixed-case (e.g. "OpenAI") source config still matches
the canonical "openai" generated provider. Without this, the trim-only source
lookup misses, the source SecretRef marker is never reapplied, and resolved
runtime secret values leak into generated models.json.
Adds plan-level regressions in models-config.runtime-source-snapshot.test.ts
covering mixed-case apiKey and header source configs.
* fix: use Array#toSorted in mixed-case provider regression tests
oxlint(no-array-sort) flagged the new test assertions for using
.sort() on Object.keys(...). Switch to .toSorted() to keep the
check-lint shard green.
* style(agents): oxfmt models-config provider merge files
* fix(agents): define provider key collision precedence
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* feat(mac): playful hard-hat working mascot during setup and updates
The mascot gets a new .working mood: it dons a safety hard hat, hammers
away with impact sparks while setup work is in flight, and pauses every
few seconds to wipe its brow. Used on the onboarding "Getting things
ready" page and the post-update window instead of the generic thinking
mood. Lives in shared OpenClawKit so iOS can adopt it too.
* chore(i18n): refresh native inventory line for mascot view
channel.test.ts crossed the max-lines lint budget (1002 > 1000) after
#108584; move the mock-free bindings/security/groups describes into
channel.policies.test.ts instead of suppressing.
The canonical schema gate rejects non-STRICT tables, failing every
gateway-suite shard since #108543 added the tombstones table without
STRICT. Align it with every sibling table and regenerate the schema
mirror.
* fix(android): stop killing long chat runs with hardcoded timeouts
Two independent timeouts made any agent turn longer than ~2 minutes fail
when started from the Android app, while the same turn worked over
Telegram (#106863):
- chat.send hardcoded timeoutMs=30000, which the gateway turns into the
server-side run expiry; the maintenance sweep then aborts longer runs
("CLI run aborted" / "Embedded agent failed before reply"). Omit the
override so app runs get the configured default like other channels.
- armPendingRunTimeout declared a timeout after 120s even when the
refreshed history snapshot confirmed the run was still in flight,
showing a spurious error and dropping the optimistic bubble mid-run.
Re-arm the timer in that case; terminal events and the server-side
expiry remain the liveness backstop.
Fixes#106863
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(i18n): sync native inventory after ChatController line shifts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(android): run ChatController on backgroundScope in reconnect tests
The pending-run watchdog now re-arms while a run is confirmed in flight,
so reconnect scenarios that end with a restored in-flight run keep a
timer coroutine alive. Launch the controller on runTest's
backgroundScope so that timer is cancelled at test end instead of
failing the suite with UncompletedCoroutinesError.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(android): bound pending-run watchdog recovery
Distinguish applied, superseded, and failed history refreshes so recovered runs neither expire during a newer authoritative load nor rearm forever after refresh failure.
Co-authored-by: Léandre Chamberland-Dozois <lchamberland-dozois@hotmail.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): handle ENOTDIR gracefully in npm-project-roots
The listManagedPluginNpmProjectRootsSync and listManagedPluginNpmProjectRoots
functions only caught ENOENT errors when reading the projects directory,
but an ENOTDIR error (when a file path is mistakenly used as a directory)
would propagate unhandled and crash the CLI.
Replace the local isMissing helper with the shared isNotFoundPathError from
infra/path-guards.ts, which already covers both ENOENT and ENOTDIR.
Fixes#107467
* test(plugins): use managed temp directory fixture
* chore: keep release notes out of contributor PR
* fix(plugins): restore not-found path guard export
* fix(plugins): normalize Windows managed install paths
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* fix(envelope): accept timestamp 0 in formatEnvelopeTimestamp
formatEnvelopeTimestamp used if (!ts) to check for missing timestamps,
which incorrectly rejected the valid numeric timestamp 0 (Unix epoch).
Replace the falsy check with an explicit undefined/null check so 0 is
treated as a valid timestamp value.
* refactor(auto-reply): tighten epoch timestamp fix
Exercise the public envelope path with an exact epoch result and keep the missing-value guard aligned with its declared input type.
Co-authored-by: zenglingbiao <zeng.lingbiao@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
- Align OAuth model-discovery client_version with the pinned @openai/codex
package (contract test ties it to extensions/codex/package.json).
- Static OAuth fallback advertises only the proven GPT-5.6 subscription
route (Sol); Terra/Luna come from live discovery when entitled. (#105445)
- Honor the deprecated whole-agent agentRuntime openclaw opt-out for
implicit Codex plugin installation; explicit model policy still wins. (#95131)
- Half-open reprobe for long WHAM subscription blocks: bounded 45-minute
background probes with durable claims, generation checks, and process
dedupe so restored capacity unblocks profiles early. (#107262)
* fix(feishu): add chunk-idle timeout to inbound media download
* fix(feishu): extract media chunk-idle helper under LOC ratchet
* fix(feishu): keep only used media idle export for knip/lint
* test(feishu): add executable negative control for stalled media hang
* test(feishu): avoid dangling underscore in negative control
* test(feishu): add production-path idle proof with real saveMediaStream
* fix(feishu): drop await in media idle finally so timeout escapes stalled Readable
The finally block's `await iterator.return()` blocked the timeout error
from propagating when the source is a real Node.js Readable.
Readable async-iterator `return()` does not settle on stalled streams,
so awaiting it made the whole idle timeout a no-op for that boundary.
Add Lark SDK boundary tests with real Readable streams (stalled,
progressing, multi-chunk) and production-path proof.
* fix(feishu): thread canonical Feishu timeout through media download
Derive the per-chunk idle timeout from resolveConfiguredHttpTimeoutMs()
so the established Feishu HTTP-timeout policy (env var, account config,
default) is preserved instead of a hardcoded 30s fallback.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* fix(feishu): restore sanitizeFileNameForUpload export lost in cherry-pick
The extraction commit (extract media chunk-idle helper) removed the
export from media.ts and the let declaration from media.test.ts during
the cherry-pick because origin/main does not export this function.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* fix(feishu): destroy stalled source on chunk-idle timeout
Destroy the underlying Readable source when the idle timer fires so the
Lark SDK stream and its HTTP resource close instead of leaking. Previously
only iterator.return() was called without awaiting it, which dropped the
cleanup promise on stalled Node Readables without releasing resources.
Add assertions in tests and proof that the source Readable is destroyed
after timeout.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* fix(feishu): drop unused export and compact timeout calls to stay under LOC ratchet
Removes the `export` from sanitizeFileNameForUpload (never imported outside
media.ts and its test) and uses a local `save` wrapper in
saveFeishuResponseMedia to keep the chunk-idle timeout calls single-line so
media.ts stays under its baseline. The test no longer imports the
now-private helper.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* chore: trigger CI after rebase onto main
* chore: trigger CI after rebase onto main
* test(feishu): add HTTP loopback proof for media chunk-idle destroy
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(feishu): clear media idle proof oxlint
* fix(feishu): bound stalled inbound media streams
Co-authored-by: Pick-cat <huang.ting3@xydigit.com>
* test(feishu): avoid promise executor return
Co-authored-by: Pick-cat <huang.ting3@xydigit.com>
* fix(feishu): keep media timeout authoritative
Co-authored-by: Pick-cat <huang.ting3@xydigit.com>
---------
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>