* fix(doctor): honor per-agent bootstrap profile in size check
* fix(doctor): thread defaultAgentId through structured bootstrap-size health check
The noteBootstrapFileSize note path was fixed in the previous commit.
This commit applies the same defaultAgentId threading to the registered
core/doctor/bootstrap-size health check in doctor-core-checks.ts, which
is used by doctor --lint and repair flows. Adds an integration regression
test that places a 15 000-char AGENTS.md between the per-agent budget
(10 000) and the defaults budget (20 000) and asserts that a truncation
warning is emitted -- the test fails on main (defaults budget wins, no
warning) and passes after this patch (per-agent budget wins, warning).
Also updates three fixHint strings in the health check to name the
per-agent knob before the defaults fallback, matching the note-path tip
text from the previous commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(doctor): preserve total-budget remediation guidance
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(cron): add headless code-mode driver and trigger-script evaluator
Part A of cron event triggers: runCodeModeScriptHeadless runs a script
to completion (exec/settle/resume, no snapshots, no session), plus the
cron-facing evaluator with per-job tool catalog cache, semaphore, 16KB
state cap, and closed failure taxonomy.
* fix(cron): correct trigger-script bootstrap flags and cache narrowing
* feat(cron): add event triggers (polled condition-watcher scripts)
Part B: trigger field on cron jobs gated by cron.triggers.enabled;
timer evaluates the script each due tick, quiet ticks leave no run
history, fired runs append the script message to the payload; once
semantics, min-interval floor, SQLite columns, RPC/CLI/agent-tool
surfaces, and docs.
* fix(cron): propagate triggerEval through startup catch-up outcomes
* fix(cron): honor cron staggering on quiet trigger ticks; fix trigger test types
* fix(cron): reject with Error reason in trigger-script abort test
* fix(cron): regenerate protocol/docs/snapshot artifacts and break madge cycle for trigger types
CI fixes for #101195: trigger evaluator result types move to the cron
types leaf (madge cycle), cron tool schema inventory gains trigger,
Swift protocol bindings + docs map + Linux prompt snapshots regenerated.
* fix(cron): drop underscore-dangle names in trigger code sync assert
* fix(cron): adopt registerHeadlessToolSearchCatalog after tool-search symbol localization
Upstream #101831 made registerToolSearchCatalog module-private; fold the
headless ref-only catalog registration into one public seam.
* fix(gateway): bound all-agent usage.cost concurrency to prevent SQLite starvation
Replace raw Promise.all in loadAllAgentCostUsageSummary with
runTasksWithConcurrency (limit=12), matching the sibling sessions.usage
pattern. Unbounded concurrent usage-cost cache reads saturated the SQLite
connection pool for deployments with 100+ agents, causing SQLITE_BUSY
timeouts or gateway memory crashes.
Fixes#101552
* fix(gateway): share usage cache concurrency cap
---------
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* fix(claude-cli): surface re-auth hint when subprocess OAuth token expires
When the claude-cli subprocess emits a 401 "Failed to authenticate. API
Error: 401 Invalid authentication credentials" because its stored OAuth
token has expired, the error was surfaced as the generic provider-auth
copy instead of the targeted re-auth hint.
Two fixes:
1. Extend isOAuthRefreshFailureMessage to recognise the claude-cli
subprocess 401 error shape (requires "claude-cli" prefix + 401 +
auth failure text). extractOAuthRefreshFailureProvider returns
"claude-cli" for this pattern; classifyOAuthRefreshFailureReason
maps it to "revoked" so the "expired" copy is shown.
2. Reorder both classification sites in buildExternalRunFailureReply and
the runAgentTurnWithFallback catch block so the OAuth-refresh check
runs before classifyProviderRequestError. The typed 401 branch in
classifyProviderRequestError would otherwise intercept the FailoverError
before the OAuth hint path is ever reached.
Fixes#97553
* fix(claude-cli): use correct auth login command format in re-auth hint
--provider claude-cli is not a registered provider id; the correct
command to re-authenticate the claude subprocess is:
openclaw models auth login --provider anthropic --method cli
Add a special-case branch in buildOAuthRefreshFailureLoginCommand so
that a 'claude-cli' sanitized provider emits the two-flag form instead
of the generic --provider <id> template. Update the regression test
expectation to match.
* test(claude-cli): expect CLI auth re-login command
* fix(claude-cli): classify structured OAuth auth failures
* test(claude-cli): add real http oauth expiry proof
* test(claude-cli): add proof output to real HTTP server OAuth test
* test(claude-cli): add proof console.log to real HTTP server test
* test(claude-cli): keep real HTTP OAuth proof output explicit
* fix(agents): include Claude CLI reauth step
* test(claude-cli): expect full cli reauth command
* test(claude-cli): align reauth hint with terminal wording
* chore: retrigger claude-cli CI after opengrep fetch failure
* fix(sessions): bound trajectory snapshot and pointer reads
* test(sessions): align oversized snapshot assertion with bounded reader error message
* fix(sessions): cap follow-mode trajectory deltas
ClawSweeper review noted that the trajectory snapshot read was bounded
but the follow-mode delta read (Buffer.alloc(fileState.size - offset))
was not. A runaway trajectory append could still OOM the tail loop.
Reject deltas above TRAJECTORY_RUNTIME_FILE_MAX_BYTES and add regression
coverage for the oversized-delta path.
* test(sessions): exercise trajectory limits through command path
---------
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* fix(http-error-body): keep emoji / surrogate pairs intact during error body truncation
Replace .slice(0, limits.maxChars) with truncateUtf16Safe() so that
emoji straddling the maxChars boundary don't produce lone surrogates
that render as � in error diagnostics. Matches the sibling http-body.ts
which already uses truncateUtf16Safe.
Co-Authored-By: Claude <noreply@anthropic.com>
* test: cover UTF-16 HTTP body branches
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): support native Windows exec approvals
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* chore: defer changelog entry to release
* test: use tracked approvals temp directories
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(tasks): keep emoji / surrogate pairs intact during terminal output truncation
.slice() counts UTF-16 code units; use Array.from() to count full
code points instead, preventing lone surrogates in task list output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tasks): preserve terminal column width
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(control-ui-assets): keep emoji / surrogate pairs intact during last-line truncation
.slice(0, 239) counts UTF-16 code units; an emoji straddling the
cut point produces a lone surrogate rendering as in WebUI assets.
Use [...last] spread to count full code points instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(control-ui-assets): preserve diagnostic length cap
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(auto-reply): keep suppressed reply text preview truncation UTF-16 safe
String.prototype.slice at offset 160 can split surrogate pairs in
reply text previews logged for suppressed auto-replies. Replace raw
slice(0, 160) with truncateUtf16Safe — already imported in this file.
* test(auto-reply): exercise suppressed preview logging
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(acp): use truncateUtf16Safe for event mapper error text truncation
.slice(0, 100) on ACP event argument values can split surrogate
pairs from emoji or CJK text, producing U+FFFD in error/warning
notices shown to operators.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(acp): cover UTF-16-safe tool titles
* refactor(acp): keep event mapper import leaf-only
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): keep plugin approval title and description truncation UTF-16 safe
Node.js String.prototype.slice cuts at UTF-16 code unit boundaries,
which can split surrogate pairs (emoji, CJK supplementary characters)
into lone surrogates that render as U+FFFD in approval UI payloads.
Use truncateUtf16Safe for both title and description fields in
createPluginApprovalsAttachment so approval requests never deliver
broken characters to the gateway approval UI.
Fixes #???
* test(gateway): exercise UTF-16 approval payloads
---------
Co-authored-by: wm0018 <wu.min5@xydigit.com>
* fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation
.slice(0, maxLen) counts UTF-16 code units, so an emoji straddling
the cut point produces a lone surrogate that renders as � in
usage cost CLI output. Use [...str] to count full code points instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(usage): cover UTF-16-safe log truncation
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(status): keep issue message truncation UTF-16 safe
String.prototype.slice at offset 90 can split surrogate pairs in issue
titles displayed by `openclaw status --all`, rendering broken U+FFFD in
the channel issues column.
Replace raw slice(0, 90) with truncateUtf16Safe to match the existing
UTF-16-safe truncation hardening pattern.
Fixes #???
* test(status): cover UTF-16-safe issue rendering
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>