Skip to content

fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json#92144

Merged
vincentkoc merged 3 commits into
openclaw:mainfrom
liuhao1024:fix/cron-status-report-sqlite-storage
Jun 11, 2026
Merged

fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json#92144
vincentkoc merged 3 commits into
openclaw:mainfrom
liuhao1024:fix/cron-status-report-sqlite-storage

Conversation

@liuhao1024

@liuhao1024 liuhao1024 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds storage: "sqlite" and sqlitePath fields to cron.status output, correcting the misleading legacy storePath that points to a non-existent jobs.json file. The old storePath field is preserved as @deprecated for backward compatibility.

Fixes #91766

Related Issue

Fixes #91766

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • src/cron/service/state.ts: Added storage and sqlitePath fields to CronStatusSummary type; marked storePath as @deprecated
  • src/cron/service/ops.ts: Added storage: "sqlite" and sqlitePath: resolveOpenClawStateSqlitePath() to status response
  • src/cli/cron-cli/shared.ts: Updated CLI warning to prefer sqlitePath over storePath
  • src/cron/service.read-ops-nonblocking.test.ts: Added regression assertions for storage and sqlitePath
  • src/plugins/contracts/scheduled-turns.contract.test.ts: Updated mock to include new fields

How to Test

  1. git checkout fix/cron-status-report-sqlite-storage
  2. pnpm install --frozen-lockfile
  3. node scripts/run-vitest.mjs src/cron/service.read-ops-nonblocking.test.ts
  4. node scripts/run-vitest.mjs src/cron/cron-protocol-conformance.test.ts
  5. node scripts/run-vitest.mjs src/cli/cron-cli.test.ts

Real behavior proof

Behavior addressed: cron.status returns misleading storePath pointing to a non-existent jobs.json file instead of the actual SQLite storage location.

Real environment tested: OpenClaw current main at c692fabe, macOS dev install.

Exact steps or command run after this patch:

  1. git checkout fix/cron-status-report-sqlite-storage
  2. pnpm install --frozen-lockfile
  3. node scripts/run-vitest.mjs src/cron/service.read-ops-nonblocking.test.ts
  4. openclaw cron status --json on a running gateway

Evidence after fix:

  • Source-level: src/cron/service/ops.ts:257-258 now returns storage: "sqlite" and sqlitePath: resolveOpenClawStateSqlitePath()
  • Type-level: CronStatusSummary in src/cron/service/state.ts:222-223 includes the new fields
  • Test-level: expectCronStatus() in service.read-ops-nonblocking.test.ts:86-87 asserts storage and sqlitePath

Observed result after fix:

{
  "enabled": true,
  "storePath": "/home/node/.openclaw/cron/jobs.json",
  "storage": "sqlite",
  "sqlitePath": "/home/node/.openclaw/state/openclaw.sqlite",
  "jobs": 3,
  "nextWakeAtMs": 1749600000000
}

What was not tested: Live gateway round-trip (CI environment only). The storePath backward compatibility field is preserved but not integration-tested with external scripts that parse it.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run tests and they pass
  • I've added tests for my changes
  • I've tested on macOS

Documentation & Housekeeping

  • I've updated relevant documentation — or N/A
  • I've considered cross-platform impact — or N/A

Code Intelligence

  • Analyzed: src/cron/service/ops.ts (callers: cron.status RPC handler, CLI warning)
  • Blast radius: LOW — additive fields only, no existing fields removed or changed
  • Related patterns: CronStatusSummary type used in CLI, gateway RPC, and plugin contract tests

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 11, 2026
@clawsweeper

clawsweeper Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 11, 2026, 9:22 AM ET / 13:22 UTC.

Summary
The branch adds storage and sqlitePath to cron.status, preserves storePath, prefers the SQLite path in CLI/macOS displays, and updates cron/plugin contract tests.

PR surface: Source +12, Tests +4, Other +1. Total +17 across 7 files.

Reproducibility: yes. Current main and v2026.6.5 return state.deps.storePath from cron.status while cron load/save reads SQLite rows, so openclaw cron status --json reproduces the misleading legacy path.

Review metrics: 1 noteworthy metric.

  • Cron status response fields: 2 added, 0 removed. Adding gateway status fields is low-risk because storePath remains, but consumers that display storage paths need to prefer the new canonical field.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🐚 platinum hermit
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add redacted macOS proof showing the Cron Jobs disabled-scheduler path displays openclaw.sqlite instead of cron/jobs.json, or provide equivalent Swift build/test output plus runtime logs.
  • Run or attach results for node scripts/run-vitest.mjs src/cron/service.read-ops-nonblocking.test.ts src/cron/cron-protocol-conformance.test.ts src/cli/cron-cli.test.ts and the relevant macOS Swift build/test lane.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body shows after-fix CLI JSON with sqlitePath, but it does not show the new macOS status display added at latest head; add redacted screenshot/recording, terminal build/test output, or logs, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.

Mantis proof suggestion
A short macOS visual proof would directly show the remaining user-visible path display behavior. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

visual task: verify the macOS Cron Jobs disabled-scheduler panel shows the openclaw.sqlite path instead of cron/jobs.json after this PR.

Risk before merge

  • [P1] The submitted proof shows the after-fix CLI JSON output but not the macOS Cron Jobs path display added by the later commits, so latest-head real behavior proof remains thin for that visible surface.

Maintainer options:

  1. Decide the mitigation before merge
    Merge an additive cron status payload that reports the SQLite database path, preserves the legacy logical key for compatibility, updates visible consumers, and carries focused cron plus macOS validation.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] No automated code repair is needed; the remaining blocker is contributor or maintainer proof for the latest macOS-visible behavior before merge.

Security
Cleared: The diff adds status metadata, client decoding, and tests; I found no concrete security or supply-chain concern.

Review details

Best possible solution:

Merge an additive cron status payload that reports the SQLite database path, preserves the legacy logical key for compatibility, updates visible consumers, and carries focused cron plus macOS validation.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main and v2026.6.5 return state.deps.storePath from cron.status while cron load/save reads SQLite rows, so openclaw cron status --json reproduces the misleading legacy path.

Is this the best way to solve the issue?

Yes. The additive storage/sqlitePath approach is the best fix because it gives current consumers the canonical database path without removing the shipped storePath field that scripts may parse.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 79d7defd0ba4.

Label changes

Label changes:

  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body shows after-fix CLI JSON with sqlitePath, but it does not show the new macOS status display added at latest head; add redacted screenshot/recording, terminal build/test output, or logs, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.
  • remove proof: sufficient: Current real behavior proof status is insufficient, not sufficient.
  • remove status: ⏳ waiting on author: Current PR status label is status: 📣 needs proof.

Label justifications:

  • P2: This is a normal-priority status/CLI/macOS accuracy fix for a real cron diagnostic bug with limited blast radius.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body shows after-fix CLI JSON with sqlitePath, but it does not show the new macOS status display added at latest head; add redacted screenshot/recording, terminal build/test output, or logs, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +12, Tests +4, Other +1. Total +17 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 3 13 1 +12
Tests 2 4 0 +4
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 2 2 1 +1
Total 7 19 2 +17

What I checked:

  • Current main returns legacy path: Current main status() returns storePath: state.deps.storePath and does not expose the SQLite database path, matching the reported misleading status output. (src/cron/service/ops.ts:256, 79d7defd0ba4)
  • Cron storage is SQLite-backed: Cron load/save uses openOpenClawStateDatabase().db with the resolved jobs path as a store key, so the JSON-looking path is not the actual persistence file. (src/cron/store.ts:67, 79d7defd0ba4)
  • Canonical SQLite path helper: The shared state database path helper resolves the actual SQLite database as state/openclaw.sqlite, which is the right field for the status payload to report. (src/state/openclaw-state-db.paths.ts:39, 79d7defd0ba4)
  • PR head adds additive status fields: At PR head, status() keeps storePath and adds storage: "sqlite" plus sqlitePath: resolveOpenClawStateSqlitePath(). (src/cron/service/ops.ts:258, 92a91528c501)
  • PR head preserves typed backward compatibility: At PR head, CronStatusSummary keeps storePath and documents it as deprecated while adding required storage and sqlitePath fields for current consumers. (src/cron/service/state.ts:221, 92a91528c501)
  • PR head updates macOS consumer: At PR head, CronJobsStore prefers status.sqlitePath and falls back to status.storePath, addressing the previous one-sided consumer gap. (apps/macos/Sources/OpenClaw/CronJobsStore.swift:75, 92a91528c501)

Likely related people:

  • vincentkoc: Current-main blame for the cron status response, SQLite-backed cron store, and macOS cron status consumer points to the same recent commit, and the PR timeline also shows maintainer force-pushes by this account. (role: recent area contributor; confidence: high; commits: 76ce9d6d228c, 5181e4f7c82b; files: src/cron/service/ops.ts, src/cron/store.ts, apps/macos/Sources/OpenClaw/CronJobsStore.swift)
  • steipete: Git history around cron.status includes earlier gateway method split and rename work by Peter Steinberger, making this a useful secondary routing signal for the gateway status surface. (role: historical gateway/cron contributor; confidence: medium; commits: 3c4c2aa98cfc, bcbfb357bec7, 9a7160786a7d; files: src/gateway/server-methods/cron.ts, apps/macos/Sources/OpenClaw/GatewayConnection.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@liuhao1024
liuhao1024 force-pushed the fix/cron-status-report-sqlite-storage branch from b6007ec to bd9c157 Compare June 11, 2026 10:35
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 11, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jun 11, 2026
@vincentkoc vincentkoc self-assigned this Jun 11, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@vincentkoc
vincentkoc force-pushed the fix/cron-status-report-sqlite-storage branch from bd9c157 to c910077 Compare June 11, 2026 11:51
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 11, 2026
…y jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766
@openclaw-barnacle openclaw-barnacle Bot added the app: macos App: macos label Jun 11, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jun 11, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 11, 2026
@vincentkoc
vincentkoc force-pushed the fix/cron-status-report-sqlite-storage branch from c18788c to 50c8ed7 Compare June 11, 2026 12:59
@openclaw-barnacle openclaw-barnacle Bot removed the app: macos App: macos label Jun 11, 2026
@liuhao1024
liuhao1024 force-pushed the fix/cron-status-report-sqlite-storage branch from 50c8ed7 to 92a9152 Compare June 11, 2026 13:03
@openclaw-barnacle openclaw-barnacle Bot added the app: macos App: macos label Jun 11, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 11, 2026
@liuhao1024

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 11, 2026
@vincentkoc
vincentkoc force-pushed the fix/cron-status-report-sqlite-storage branch from 92a9152 to ce43d14 Compare June 11, 2026 13:34
@vincentkoc

Copy link
Copy Markdown
Member

Maintainer closeout for landing:

  • Rebased onto current main after the main CI unblocker 68ec783e74b59b672dbe14585e892d887adb2bfb.
  • Curated the branch to keep the cron + macOS sqlitePath changes and drop the unrelated test/scripts/docker-build-helper.test.ts wait tweak that was force-pushed into the fork branch.
  • Local proof:
    • node scripts/run-vitest.mjs src/cron/service.read-ops-nonblocking.test.ts src/cli/cron-cli.test.ts src/plugins/contracts/scheduled-turns.contract.test.ts
    • git diff --check origin/main...HEAD
    • node scripts/run-oxlint.mjs src/cron/service/ops.ts src/cron/service/state.ts src/cli/cron-cli/shared.ts src/cron/service.read-ops-nonblocking.test.ts src/plugins/contracts/scheduled-turns.contract.test.ts
  • Review proof: .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main clean, no accepted/actionable findings.
  • Remote proof: GitHub CI on head ce43d1406b22fdd51432a4906be1d2be11d1efdd is green across all 100 head check-runs. Azure Crabbox/Testbox attempts for final check:changed hit Azure SSH/VM teardown during long lint after typecheck lanes passed; no code failure was observed, and GitHub CI is the final full gate here.

Proceeding with squash merge.

@vincentkoc
vincentkoc merged commit 047785e into openclaw:main Jun 11, 2026
162 checks passed
wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 12, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 12, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 12, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
michmill1970 added a commit to michmill1970/openclaw that referenced this pull request Jun 13, 2026
* fix(test): bound gateway harness teardown

* fix: repair update restart type checks

* test: stabilize full Mac regression suite

* fix(providers): preserve adaptive Claude routing

* fix(foundry): scope Entra tokens by API

* fix(foundry): use bearer auth for Claude

* fix(foundry): keep API-key Claude auth

* fix(foundry): align Claude onboarding contracts

* fix(foundry): infer selected Claude routes

* fix(foundry): clear API key auth on Entra setup

* fix(foundry): preserve Sonnet output limits

* fix(providers): apply auth patch deletions

* fix(bedrock): preserve Mythos Preview thinking policy

* fix(providers): preserve Mythos max routing

* fix(foundry): avoid stale setup metadata

* fix(types): narrow provider auth metadata

* fix(foundry): clamp Mythos Preview thinking

* fix(providers): bound Claude model matching

* test(anthropic): type Foundry auth fixtures

* fix(providers): preserve Claude output limits

* fix(providers): reconcile Claude profile routing

* fix(vertex): preserve Mythos thinking policy

* fix(foundry): route bearer auth from headers

* fix(providers): encode Mythos adaptive requests

* fix(foundry): preserve bearer auth intent

* fix(foundry): use bearer auth in native transport

* fix(vertex): default Mythos to adaptive thinking

* fix(foundry): preserve canonical thinking identity

* test(foundry): type auth lookup

* fix(foundry): align Claude thinking contracts

* fix(bedrock): keep optional thinking opt-in

* fix(bedrock): route Mythos through Mantle

* fix(bedrock): guard Mantle thinking options

* fix(foundry): label Anthropic Messages onboarding

* fix(foundry): make API labels exhaustive

* fix(foundry): expose Claude 4.6 max effort

* fix(foundry): bind auth and thinking contracts

* fix(foundry): type runtime auth result

* fix(auth): apply runtime request overrides everywhere

* fix(auth): apply runtime auth to labels

* fix(bedrock): normalize Mythos minimal effort

* fix(tts): use prepared completion auth

* test(tts): update prepared completion contract

* fix(anthropic): require Mythos adaptive thinking

* fix(tts): preserve async model discovery

* fix(anthropic): repair Mythos contract types

* fix(discord): scope command-deploy cache by application id (#77367)

* fix(discord): scope command-deploy cache by application id

Multi-bot Discord setups share a single command-deploy-cache.json under the
state dir. Cache keys were unscoped (`global:reconcile`, `guild:<id>`), so a
later account whose command set hashed identically to an earlier account would
hit the shared hash and skip its own application's command reconcile entirely
— Discord's Integration panel showed 'This application has no commands' for
the secondary bot even though gateway connect, application id, and token were
all valid.

Scope every cache key with `app:<clientId>:` so each Discord application
reconciles independently. Add regression tests covering: two applications with
identical command sets each call REST against their own application; a single
application with the same command set still hits the persisted cache; the
on-disk cache JSON contains application-scoped keys.

Fixes #77359.

* fix(discord): merge on-disk hashes inside persistHashes to survive concurrent writes

Codex follow-up on #77359 noted that server-channels.ts can start multiple
Discord deployers concurrently, so two deployers that both load the cache
file before either persists end up with the second writer overwriting the
first writer's app-scoped key — defeating the rate-limit cache that the
file exists to provide.

Inside persistHashes, re-read the on-disk cache and merge it with our
in-memory entries before the rename. Our in-memory entries always win on
key collisions (we just produced them); on-disk entries we don't have in
memory are preserved. Refresh in-memory state after the write so future
writes from the same deployer also keep entries other deployers added.

This is the lighter of the two repairs the codex review suggested
(re-read/merge vs serialize writes); it covers the realistic case where
one deployer writes before the other persists. Add a regression test that
exercises the load-then-other-deployer-writes-then-persist sequence.

* fix(discord): serialize command-deploy cache persists via in-process mutex

Codex follow-up on #77367 noted: re-read-before-write inside persistHashes
isn't enough — two deployers running persistHashes in true parallel can
both read the same snapshot before either writes, and the later rename
overwrites the earlier writer's app-scoped entries.

Add a module-level Map<storePath, Promise<void>> mutex and wrap the
read-merge-write cycle in withCachePersistLock so concurrent persists for
the same on-disk path serialize. In-process is sufficient because Discord
deployers only run inside the gateway process.

New regression test fires three deployers via Promise.all on the same
tick and asserts all three application-scoped entries survive — pre-fix
this race lost at least one entry.

* fix(discord): add override modifier on StaticCommand.description to satisfy strict TS

Current main enables noImplicitOverride; the StaticCommand test helper
re-declares the concrete BaseCommand.description property, which now
requires an explicit 'override' modifier (TS4114).

* test(discord): suppress typescript/unbound-method on vitest mock refs

The createRest() helper returns vi.fn() handlers cast as RequestClient,
so expect(rest.get).toHaveBeenCalledTimes(...) triggers
typescript/unbound-method 12 times. File-level disable: these are
vitest mock identities, not unbound class methods.

* fix(discord): clean up command cache lock

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>

* feat(auto-reply): deliver inter-tool commentary as standalone verbose progress messages

When verbose progress is enabled, preamble item events now flush as durable
standalone progress messages through the same delivery path as tool summaries,
instead of living only in ephemeral channel streaming drafts. The latest text
per item id is buffered so snapshot-style producers send one message per item;
the buffer flushes when the producer moves on (next item, tool event, block
reply, or final reply) and drains before the final answer.

Verbose runs also force commentary classification on (commentaryProgressEnabled),
so inter-tool text routes to the commentary lane rather than being folded into
the final answer text.

Dispatch additionally exposes a live verbose-progress visibility getter via the
new onVerboseProgressVisibility reply option, so draft-rendering channels can
route progress to the durable lane while it is active.

* feat(telegram): route verbose progress payloads durably instead of into the streaming draft

With streaming on, the dispatcher diverted tool-kind payloads (including the
new durable commentary messages) into the ephemeral progress draft, where they
were discarded when the final answer arrived - so verbose runs lost their
progress record whenever streaming was enabled. While the durable verbose lane
is active (per the dispatch visibility getter), tool payloads are now sent as
real standalone messages and the draft yields its commentary lines; tool/plan
draft lines keep the draft since they have no durable counterpart. Reasoning
lane and tool status reactions are unaffected.

* feat(auto-reply): emit durable tool summaries from CLI runner tool results

The CLI parser already emits tool result events (name, toolCallId, isError,
sanitized result), but the runner bridge dropped them, so CLI-backed runs had
no durable tool record under verbose while embedded runs did. The bridge now
forwards result events, and both runners feed a summary tracker that renders
the same formatToolAggregate line the embedded runner emits (meta captured
from the start event args), plus the tool output block when full verbose
output is enabled. Delivery rides each runner's existing tool-result route, so
verbose gating, ordering ahead of the final answer, and the Telegram durable
routing all apply unchanged.

* fix(discord): yield the commentary draft while durable verbose progress is active

Discord consumes the dispatch verbose-progress visibility getter the same way
Telegram does: while the durable lane is delivering commentary as standalone
messages, the ephemeral progress draft skips its preamble lines so commentary
renders exactly once. Covered by an active/inactive regression pair.

* test(discord): add onVerboseProgressVisibility to dispatch params type

* refactor(auto-reply): distill verbose commentary lane wiring

* fix(sessions): preserve user model override across daily/idle rollover (#90128)

User-driven /model (and sessions.patch) overrides were dropped when a
session rolled over at the daily/idle reset boundary, reverting to the
configured default on the next turn despite the 'Model set to ... for
this session' ack. The override-preservation carryover in
initSessionState was gated on resetTriggered, so implicit stale
rollovers (the common case for always-on channel sessions) skipped it.

Run resolveResetPreservedSelection for any rollover that mints a new
session from an existing entry (explicit /new + /reset AND implicit
stale daily/idle). resolveResetPreservedSelection already preserves only
user-driven overrides and clears auto-fallback pins, so resets still
return to the default.

Adds regression tests in session.test.ts covering both cases.

Fixes #90119

Filed with AI assistance (OpenClaw agent); reviewed by @Peetiegonzalez.

Co-authored-by: Marvinthebored <[email protected]>

* fix(clickclack): allow explicit enable through plugin allowlist (#92084)

Allow an explicit canonical ClickClack enable/setup selection to record ClickClack in a nonempty plugin allowlist, while preserving unrelated allowlist rejection, denylist authority, and global plugin disablement.

Validated at source head 24af9d8e751440a8954aa731c5d2c63a53094698 with focused regressions, built-CLI disposable-config E2E, security checks, and autoreview. Merged under owner authorization despite the two documented untouched-main agent-core baseline failures.

* fix(auto-reply): stop dropping claude-cli narration when commentary lane is off

After #91976, the claude-cli JSONL parser reclassifies assistant text that
precedes a tool_use block as commentary. The classification gate
(commentaryProgressEnabled !== undefined) was looser than the delivery gate
(commentaryProgressEnabled === true && onItemEvent), so any channel that
defined the flag as false engaged classification with no consumer wired:
flushPendingClaudeCommentaryText() called an undefined onCommentaryText and
silently discarded the text. On Discord with verbose off this dropped all
inter-tool narration and the pre-final-answer preamble text.

Two-layer fix:
- Align the classify gate with the delivery gate in both CLI dispatch sites
  (agent-runner-execution, followup-runner) so classification only engages
  when a commentary consumer exists.
- Defense in depth: flushPendingClaudeCommentaryText() now falls back to the
  assistant text lane instead of discarding when no consumer is wired, so no
  future gate mismatch can silently eat model output.

Reported on Discord: claude-cli backend lost interleaved narration and the
regular-text reasoning preamble with or without /verbose on.

* refactor(agents): derive CLI commentary classification from consumer presence

* perf(ci): isolate Docker tooling tests

* perf(ci): isolate Docker tooling tests

* test(ci): align Anthropic agent expectations

* #92109: [Bug]: EmbeddedAttemptSessionTakeoverError caused by Btrfs ctimeNs instability (#92123)

* fix(session-lock): remove ctimeNs from session file fingerprint comparison

Btrfs background maintenance (snapshots, scrub, quota) updates ctime
without any file content change. Including ctimeNs in the fingerprint
causes false-positive EmbeddedAttemptSessionTakeoverError on all Btrfs
filesystems, breaking cron jobs and subagent spawns with 100% failure.

dev + ino + size + mtimeNs are sufficient to detect external writes —
any content change will also update mtimeNs and/or size. ctimeNs only
tracks metadata changes and adds no meaningful protection.

Closes #92109

* test(session-lock): cover ctime-only fence drift

* fix(session-lock): narrow ctime drift acceptance

* fix(session-lock): trust verified ctime drift

* fix(session-lock): bound ctime drift digest

* fix(session-lock): skip absent ctime digest

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(feishu): reply inside P2P direct-message threads (#92136)

* fix(feishu): keep P2P replies inside direct-message threads

* fix(clownfish): address review for ghcrawl-165996-agentic-merge (1)

* fix(clownfish): address review for ghcrawl-165996-agentic-merge (1)

Co-authored-by: LiaoyuanNing@TTC <[email protected]>

---------

Co-authored-by: vincentkoc <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: LiaoyuanNing@TTC <[email protected]>

* fix(memory): preserve live SQLite index during swaps

Fixes #91216.

Preserve the live memory SQLite index during atomic reindex swaps by publishing the POSIX main DB with an overwrite rename, keeping target WAL/SHM sidecars rollbackable until publish succeeds, and refusing no-create post-swap reopens that would otherwise auto-create an empty DB.

Verification:
- node scripts/run-vitest.mjs extensions/memory-core/src/memory/manager.atomic-reindex.test.ts extensions/memory-core/src/memory/manager-db-probe.test.ts extensions/memory-core/src/memory/manager.self-heal-missing-identity.test.ts extensions/memory-core/src/memory/manager-reindex-state.test.ts extensions/memory-core/src/memory/manager.fts-only-reindex.test.ts extensions/memory-core/src/memory/manager.readonly-recovery.test.ts
- git diff --check origin/main...HEAD
- node scripts/run-oxlint.mjs extensions/memory-core/src/memory/manager-atomic-reindex.ts extensions/memory-core/src/memory/manager.atomic-reindex.test.ts extensions/memory-core/src/memory/manager-db.ts extensions/memory-core/src/memory/manager-db-probe.test.ts extensions/memory-core/src/memory/manager-sync-ops.ts
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- GitHub CI on 60df2b4178c8e16a301b715c77bc6197aced99d1

* Stabilize A2A prompt cache metadata (#90173)

A2A session routing identifiers are needed for delivery provenance, but concrete session keys in extraSystemPrompt make the agent system prompt vary between otherwise identical handoffs. Keep the model-facing system context stable by describing high-cardinality session slots with placeholders while retaining concrete values in inputProvenance. Channel names stay concrete: they are low-cardinality (discord/slack/webchat/...), so they do not meaningfully fragment the cache, and they inform reply formatting on the receiving agent.

Constraint: OpenClaw contributor PRs require focused behavior proof and tests for prompt/cache-facing changes.

Rejected: Removing routing metadata entirely | would weaken model context for requester/target roles.

Rejected: Placeholdering channel values too | drops model-visible formatting context for negligible cache benefit (reviewer feedback).

Confidence: medium

Scope-risk: narrow

Directive: Keep concrete session identifiers out of extraSystemPrompt; preserve them in structured provenance or payload fields. Low-cardinality channel labels may stay model-visible.

Tested: node scripts/run-vitest.mjs src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: corepack pnpm exec oxfmt --check src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: node scripts/run-oxlint.mjs src/agents/tools/sessions-send-helpers.ts src/agents/tools/sessions-send-helpers.test.ts src/agents/openclaw-tools.sessions.test.ts

Tested: git diff --check

Tested: live before/after provider cache trace (isolated local gateway, two A2A sends from distinct requester sessions; see PR Real behavior proof)

Co-authored-by: Sunjae Kim <[email protected]>

* fix(cli-runner): scope claude-cli queue to live-session owner identity (#91946) (#91974)

* fix(cli-runner): scope claude-cli queue to live-session owner identity

Fresh claude-cli runs without a stored cliSessionId previously collapsed
onto a single workspace-scoped queue key, serializing all fan-out within
one workspace regardless of subagent lane configuration.

Replace the workspace fallback with the same owner identity that
claude-live-session.ts already uses for its live-session map
(agentAccountId + agentId + authProfileId + sessionId + sessionKey),
keeping per-session resume safety while letting independent OpenClaw
sessions in the same workspace run concurrently.

Refactor buildClaudeLiveKey() to share the new buildClaudeOwnerKey()
helper so the queue key and the live-session key cannot drift.

Refs: #91946

* test(cli-runner): pin owner-key hash + document buildClaudeOwnerKey contract

Add a golden-hash regression test for buildClaudeOwnerKey using the
exact legacy fixture, so a future refactor that reorders fields or
flips the JSON encoding can't silently orphan every deployed Claude
live session at upgrade. Hash verified empirically against the prior
inline sha256(JSON.stringify(...)) in buildClaudeLiveKey.

Add a JSDoc on buildClaudeOwnerKey explaining the cross-module contract
between the CLI run queue and the live-session map.

Refs: #91946

* docs(cli-runner): tighten buildClaudeOwnerKey contract comment

The previous comment claimed an encoding mismatch would orphan deployed
live sessions across upgrades. The Claude live-session registry is
process-local, so any restart already discards every entry — the real
invariant is that the queue path and live-session path produce
byte-identical owner keys *within a single process*, so a fresh queued
turn picks up the same live session the registry already holds. Update
the helper docstring and the golden-hash test description accordingly;
the pinned hash and behavior are unchanged.

* test(cli-runner): add owner-key concurrency demo script

A pure-Node, no-test-runner demo that reproduces the PR-head queue
behavior end-to-end: BEFORE-PR collapse (workspace lane), distinct-owner
overlap, and identical-owner serialization, all in one run with
millisecond-stamped event ordering. Useful as a low-overhead regression
check for the owner-key contract and as a maintainer-runnable proof
artifact for #91946.

* test(cli-runner): satisfy oxlint curly + no-promise-executor-return

Wrap single-statement if/for-of bodies in braces and rewrite the
sleep helper so its Promise executor is a void block instead of an
arrow with an implicit return. No behavior change; demo output and
the byte-equivalent slice fingerprints are unchanged.

---------

Co-authored-by: wanglu241 <[email protected]>

* fix(thinking): apply Claude profile to anthropic-messages catalog rows (#92053)

* fix(thinking): apply Claude profile to anthropic-messages catalog rows

When a custom provider (e.g. `jdcloud-anthropic`) fronted Claude Opus over
the native anthropic-messages adapter, `--thinking xhigh` was silently
clamped to `off`. The thinking-profile dispatcher resolves bundled plugin
policy surfaces by exact provider id, so a renamed Anthropic-compatible
provider never reached the anthropic plugin's policy and `xhigh` was not
in the resulting profile.

`auto-reply/thinking.ts` already had a fallback keyed on
`context.api === "anthropic-messages"` that attached
`CLAUDE_FABLE_5_THINKING_PROFILE` for Fable models. Generalize it to use
`resolveClaudeThinkingProfile(modelId, params)` instead — the same
canonical helper the anthropic plugin uses — which still returns the Fable
profile for Fable models and now returns the correct Opus 4.7/4.8 profile
(with `xhigh`/`adaptive`/`max`) for Claude Opus regardless of provider id.

Non-Claude models on anthropic-messages routes still get the base
profile, and a Claude id on a non-Anthropic transport (e.g. an
openai-completions catalog row) is unaffected.

Fixes #91975

* fix(thinking): match native Anthropic includeNativeMax in fallback

Address ClawSweeper P2 review on #92053. The anthropic-messages fallback
in `resolveThinkingProfile` calls `resolveClaudeThinkingProfile` but
omits the `{ includeNativeMax: true }` option that the bundled anthropic
plugin uses (extensions/anthropic/provider-policy-api.ts:38,45).

For native-xhigh Claude families (Opus 4.7/4.8) this had no effect since
the native-xhigh branch already exposes `max`. But adaptive Claude
families that take the adaptive-default branch (e.g. claude-sonnet-4-6,
claude-opus-4-6) silently lost `max` parity on custom anthropic-messages
providers compared to native Anthropic policy.

Also add a regression test on `claude-sonnet-4-6` that verifies the
adaptive-branch path keeps `max` for custom providers.

* docs(thinking): document deliberate compat.xhigh bypass on anthropic-messages

Self-review surfaced a subtle behavior change worth documenting: when the
anthropic-messages fallback was generalized, non-Claude models on this
transport stop honoring catalog `compat.supportedReasoningEfforts: ["xhigh"]`
because they take the Claude base profile instead of falling through to the
later `catalogSupportsXHigh` upgrade path.

This is intentional — anthropic-messages does not carry a generic xhigh
contract; xhigh on this protocol is a Claude-family capability. Add an
inline comment at the resolver site and a regression test that locks the
suppression so the next reader (or a future patch) doesn't accidentally
restore the upgrade path.

* fix(thinking): extract Claude profile to leaf to break import cycle

The previous commits added a `resolveClaudeThinkingProfile` import from
`auto-reply/thinking.ts` to `plugin-sdk/provider-model-shared.ts`. The
shared barrel re-exports `provider-replay-helpers` and `plugins/types`,
which transitively reach back into `auto-reply` via the gateway server
methods chain — creating the madge cycle reported by
`check:madge-import-cycles`:

    auto-reply/thinking.ts
      -> ... -> plugin-sdk/provider-model-shared.ts
      -> plugins/{config-schema, host-hooks, ...} -> plugins/types.ts

Move `BASE_CLAUDE_THINKING_LEVELS`, `isClaudeAdaptiveThinkingDefaultModelId`,
and `resolveClaudeThinkingProfile` to a new leaf module
`src/plugins/provider-claude-thinking.ts` whose only imports are
`@openclaw/llm-core` and the existing leaf `provider-thinking.types`.

`provider-model-shared.ts` continues to re-export both helpers so existing
consumers (`extensions/anthropic/*`, the public test surface) are
unaffected. `auto-reply/thinking.ts` now imports the leaf directly,
breaking the cycle.

* test(thinking): add live proof harness for #91975 anthropic-messages clamp

---------

Co-authored-by: wanglu241 <[email protected]>

* Google: show detailed Gemini CLI OAuth extraction failures (#41991)

* Google: preserve Gemini CLI OAuth failure context

Port the diagnostics-only fix to the bundled Google OAuth implementation so user-facing setup errors explain why automatic Gemini CLI client-config discovery failed.

Constraint: #54289 may remove or gate automatic Gemini CLI credential extraction
Rejected: Change extraction consent behavior here | security/product decision belongs in #54289
Confidence: medium
Scope-risk: narrow
Tested: pnpm test -- extensions/google/oauth.test.ts
Tested: pnpm check
Tested: pnpm format:check extensions/google/oauth.credentials.ts extensions/google/oauth.test.ts
Not-tested: full pnpm test suite

* Google: clarify Gemini bundle fallback diagnostic comment

Keep the follow-up limited to the explanatory comment so it matches the diagnostic error preservation added around bundle traversal failures.

Constraint: comment-only cleanup after diagnostics port
Confidence: high
Scope-risk: narrow
Tested: pnpm format:check extensions/google/oauth.credentials.ts
Not-tested: tests not run; comment-only change

* fix: resolve OAuth test rebase conflict

* fix(qqbot): flush tool output before silent non-streaming final (#92074)

* fix(qqbot): flush tool output before silent non-streaming final

* fix qqbot silent final delivery

* chore: drop local plugin runtime helper

* fix: suppress stale qqbot tool flush

* fix(qqbot): flush tool output before silent non-streaming final (#92074) (thanks @sliverp)

---------

Co-authored-by: Ubuntu <[email protected]>

* fix(agents): forward channel identity to CLI hooks

* fix(models): clarify provider model registration hint (#89508)

Co-authored-by: Cornna <[email protected]>

* fix(agents): keep migrated session entry ids unique on v1 upgrade (#89085)

migrateV1ToV2 assigned each entry id via generateId(ids) but never added the
result back into ids, so the collision-check set stayed empty for the whole
migration and generateId's check was a no-op. A v1 to v2 upgrade could then mint
two entries with the same 8-hex id, and because the migration rebuilds the
parent/child tree from those ids it would parent the second entry to itself,
corrupting the branch. Add ids.add(entry.id) so the generator sees prior ids and
retries on collision.

Adds a regression test that drives the real SessionManager.open migration path
with a seeded id collision.

* fix(discord): clean migrated thread binding state (#89552)

* fix(discord): clean migrated thread binding state

* fix(discord): omit undefined thread binding fields

---------

Co-authored-by: Hex <[email protected]>

* fix(cron): reject durations that overflow to a non-finite value (#89448)

* fix(cron): reject durations that overflow to a non-finite value

parseDurationMs guarded the parsed mantissa but returned Math.floor(n * factor)
with no finite check on the product. A finite mantissa times a large unit factor
(e.g. "1e302d", factor 86_400_000) overflows to Infinity, which was returned as
the millisecond value. Reject a non-finite result instead, matching the existing
contract that already rejects non-finite / non-positive mantissas.

Fixes #83906.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* ci: rerun flaky runner checks

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

* fix(doctor): warn on unsupported hook entry loaders (#89319)

* fix(doctor): warn on unsupported hook entry loaders

* fix(doctor): guard null hook entry configs

* fix(doctor): guard null hook entry configs

* fix(doctor): repair misplaced hook loader paths

* fix(doctor): clarify unsupported hook entry repair

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(qqbot): guard silent-final tool flushing

* fix(config): stop config.patch replacePaths index suffix from widening array consent (#91966)

* fix(config): stop config.patch replacePaths index suffix from widening array consent

normalizeConfigPatchReplacePath stripped a trailing array bracket, so an entry/index-scoped token like bindings[0] or bindings[] collapsed onto the bare whole-array token (bindings). That bare token is both the merge replaceArrayPaths key and the destructive-array gate's exact-path token, so an index-scoped consent silently authorized a full-array replacement and dropped unrelated base entries on the gateway config.patch path, and the same collapse let the agent self-edit tool truncate id-keyed arrays whenever no protected path happened to be involved.

Keep the interior index normalization (agents.list[0].skills -> agents.list[].skills) but no longer collapse a trailing bracket, so a bracket/index-suffixed token never matches the bare whole-array token and the destructive-array gate stays fail-closed unless the documented exact path is passed. Update the agent-tool test whose expectation depended on the old collapse: agents.list[0] now does a non-destructive id-keyed merge that only changes model and is correctly allowed.

* fix(config): distinguish indexed and array replace consent

* test(config): cover replace consent syntax

* fix(config): make replace path normalization idempotent

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(plugins): stop derived metadata snapshot rescan storm in /models (regression shipped since v2026.5.18) (#92127)

Since 5734193fdf ("fix(plugins): keep metadata snapshot memo fresh",
first shipped in v2026.5.18), the in-process plugin metadata snapshot
memo stores derived-registry results under a key recomputed from the
freshly built snapshot.index, while lookups key off the persisted-index
registry state. On installs where the registry resolves as "derived"
(persisted index absent or not covering the running checkout), the two
keys never match. Worse, the lookup-side adoption loop returns the most
recently stored registryState for the context, so two alternating call
shapes (e.g. the model-catalog build mixing workspace-scoped and global
lookups) each adopt the other shape's state, compute a key that was
never stored, and re-run the full plugin manifest scan - on every call,
forever. Chat /models (and each subsequent provider/model pick) pays
multiple full manifest scans plus all downstream snapshot-identity cache
invalidation per step, pinning a CPU core for seconds on every
interaction, in every chat channel.

Fix: store the memo under the exact memoKey/registryState the call
looked up by, instead of re-deriving a second key from snapshot.index.
Freshness is unchanged - the lookup context hash and the plugin metadata
lifecycle clears (install/reload/doctor) still own invalidation. The
now-unused index parameter of resolvePersistedRegistryMemoState is
removed.

Measured on the author's VPS (real plugin discovery, identical catalog
output of 263 entries on both sides): the full-discovery model catalog
build behind chat /models dropped from ~6.3s to ~0.3s (~21x), with
repeat snapshot lookups going from full rescans to memo hits.

Regression test: alternating derived call shapes must not re-scan
(red on main: 4 scans; green with this fix: 2).

* fix(ollama): use provider thinking default in SDK session factory (#91657)

* fix(ollama): use provider thinking default in SDK session factory

* fix(agents): preserve model metadata for thinking defaults

* fix(agents): resolve custom Ollama thinking policy

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(memory): abort orphaned embedding work when memory_search times out (#91742)

* fix(memory): abort orphaned embedding work when memory_search times out

memory_search raced its 15s deadline with Promise.race and returned a clean
timeout to the agent, but the underlying embedQueryWithRetry loop kept
retrying (3 attempts x 60s) against the embedding backend with no consumer.
Thread the tool-owned AbortSignal through manager.search ->
embedQueryWithRetry -> runEmbeddingOperationWithTimeout so the deadline
cancels in-flight embedding work, stops the retry loop, and skips
fallback-provider activation for an absent caller.

Fixes #91718

* fix(memory): let the deadline result win before aborting the search

Abort listeners dispatch synchronously, so an abort-aware search could
reject the raced task before the timeout promise resolved and replace the
stable 'memory_search timed out after 15s' result with a provider-wrapped
abort error. Resolve the timeout first, then abort.

* fix(memory): scope deadline abort to builtin embeddings

* fix(memory): preserve deadline signal across fallback

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(memory-core): retry narrative message reads (#89091)

* fix(memory-core): retry narrative message reads

* fix(memory-core): wait longer for narrative text

* fix(release): gate beta publish on plugin verification

Delay public GitHub release publication until postpublish verification, dependency evidence upload, proof append, and required plugin publish gates pass.

Also updates release-maintainer instructions so newly publishable plugins are minted/prepublished through an owner-approved path without consuming the next auto-bumped beta version unless that path is the actual release publish.

* fix(cli): validate gateway RPC timeout inputs

Reject malformed or explicit empty Gateway RPC timeout values before opening Gateway calls, align the shared Gateway RPC omitted-timeout fallback with the 30000 ms CLI default, and validate explicit `cron add --timeout-seconds` values at the CLI boundary.

Carries forward the useful source work from #54646 and the earlier timeout-validation context from #40953. #60661 remains separate accepted-run timeout semantics work and is intentionally not folded into this change.

Validation:
- `npm run review-results -- /tmp/clownfish-check-27341769444`
- `git diff --check`
- OpenClaw PR checks on `ce7bd8b9388a5689b14ddc2b3a984f7b4647e5ca`: 132 pass, 0 pending, 0 failing
- ClawSweeper re-review: https://github.com/openclaw/clawsweeper/actions/runs/27344244608

Co-authored-by: RayRuan <[email protected]>
Co-authored-by: Homeran <[email protected]>

* fix(agents): retry same model across short rate-limit windows (#91911)

Bound same-model rate-limit retries to explicit short-window signals or parsed short Retry-After values, honor Retry-After in the retry sleep, preserve zero-rotation fallback behavior, and record same-model rate-limit retries separately from profile rotations.

Verification:
- node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/assistant-failover.test.ts src/agents/embedded-agent-runner/run/helpers.test.ts
- Azure Crabbox cbx_bdb5a7807a1f / coral-shrimp: OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main

* fix(agents): project thinking catalog compat

* test(ci): relax docker signal wait

* chore: remove redundant proof scripts

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json (#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes #91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type

* fix(channel): harden local setup trust (#92175)

Summary:
- The PR extends channel setup trust enforcement and trusted catalog fallback from workspace-origin plugins to ... nfigured load paths into catalog discovery, and adds focused regression plus Docker/package proof coverage.
- PR surface: Source +190, Tests +892, Other +324. Total +1406 across 13 files.
- Reproducibility: yes. The source PR provides a concrete clean-main Docker/package path where an explicitly t ... ns unresolved, while the patched package resolves it and still blocks untrusted module and setup execution.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(channel): stabilize trusted catalog dts typing
- PR branch already contained follow-up commit before automerge: fix(channel): repair trusted catalog exclusions typing
- PR branch already contained follow-up commit before automerge: test(channel): cover local channel plugin trust
- PR branch already contained follow-up commit before automerge: chore(deps): refresh plugin shrinkwraps
- PR branch already contained follow-up commit before automerge: test(channel): route trust regression in command shard
- PR branch already contained follow-up commit before automerge: test(channel): remove e2e-named trust regression

Validation:
- ClawSweeper review passed for head eabee04d54596790a16a70d56b97b51eddd87791.
- Required merge gates passed before the squash merge.

Prepared head SHA: eabee04d54596790a16a70d56b97b51eddd87791
Review: https://github.com/openclaw/openclaw/pull/92175#issuecomment-4680798117

Co-authored-by: Mason Huang <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <[email protected]>

* fix(installer): stop after failed Node package installs

Linux Node package-manager setup/install failures now fail the installer immediately instead of falling through to a misleading success path. Adds regression coverage for NodeSource setup and apt nodejs install failures under conditional shell invocation.\n\nFixes #73837\n\nProof: bash -n scripts/install.sh; node scripts/run-vitest.mjs test/scripts/install-sh.test.ts; node scripts/run-oxlint.mjs test/scripts/install-sh.test.ts; git diff --check origin/main...HEAD; autoreview clean; Azure Crabbox check:changed cbx_6286dc1e287b passed.

* fix(wizard): report keyless web search providers as ready

Onboarding finalize now treats configured web search providers with requiresCredential: false as ready instead of warning that an API key is missing. This covers keyless providers such as Parallel Search (Free), DuckDuckGo, and Ollama while preserving credential-required warnings for providers that need keys.\n\nProof: focused wizard/search tests; oxlint on changed files; git diff --check; autoreview clean; Azure Crabbox check:changed cbx_b92ef084c21c passed; GitHub checks green.

* fix: handle explicit silent assistant replies (#92073)

Signed-off-by: sallyom <[email protected]>

* feat(skills): allow trusted workshop symlink targets

* fix: gate Skill Workshop symlink writes

* fix: validate workshop support symlink writes

* test: harden stalled websocket cleanup

* refactor(whatsapp): introduce inbound message contexts

* refactor(whatsapp): read inbound contexts in auto reply

* test(whatsapp): update auto reply inbound fixtures

* test(whatsapp): cover inbound context compatibility

* docs(plugins): record whatsapp inbound compatibility

* fix: keep whatsapp inbound aliases live

* test: clean whatsapp alias assertions

* test: narrow whatsapp mention fixture

* refactor: move workspace skill writes to lifecycle

* fix: preflight skill writes before rollback metadata

* fix: avoid support write shadowed variable

* fix: preserve utils exports in doctor health tests

* fix: rely on ClawHub plugin publish checks

* test(sqlite): add state perf query plan harness

Adds a SQLite state query-plan regression test and smoke benchmark, wires the smoke artifact into source performance evidence, validates SQLite smoke output in the performance summary, and removes a retired ClawHub nav entry that broke docs link checks.

Fixes #91616

* fix(daemon): keep unsupported service status readable

Fixes #25621.\n\nKeep gateway status readable on unsupported service-manager platforms by returning a conservative read-only service adapter, while lifecycle mutations still reject clearly. Includes regression coverage for resolver, status, summary, and lifecycle behavior.\n\nVerified with focused Vitest/oxlint/diff checks, autoreview, and Azure Crabbox check:changed on lanes core/coreTests.

* fix(cron): preserve timezone on expression edits

Fixes #92291.

Preserves the existing cron timezone when `cron edit <id> --cron ...` replaces only the expression, while avoiding stale stagger writes and preserving API/UI omitted-timezone clearing semantics.

Proof:
- node scripts/run-vitest.mjs src/cli/cron-cli/register.cron-edit.test.ts src/cli/cron-cli.test.ts src/cron/service.jobs.test.ts src/cron/normalize.test.ts
- node scripts/run-oxlint.mjs src/cli/cron-cli/register.cron-edit.ts src/cli/cron-cli/register.cron-edit.test.ts src/cli/cron-cli.test.ts src/cron/service/jobs.ts src/cron/service.jobs.test.ts
- git diff --check origin/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- Azure Crabbox cbx_3ce6a4e0d945 / silver-lobster: OPENCLAW_TESTBOX=1 node scripts/crabbox-wrapper.mjs run --provider azure --class Standard_D4ads_v6 --idle-timeout 90m --ttl 240m --timing-json -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed

* fix(docker): bundle QA Lab runtime in the image (#92087)

* fix(docker): split qa lab runtime fixes

* fix(docker): remove store platform selector

* test(docker): assert qa lab ui copy is gated

* fix(docs): remove stale ClawHub nav page

* fix(release): use trusted publishing for plugin npm

* refactor(telegram): centralize edit error classification in network-errors

* fix(telegram): retry transient draft preview failures instead of killing the stream

* fix(telegram): carry bot api error codes across the ingress worker boundary

* fix(telegram): restart isolated polling on getUpdates conflict and surface it in status

* fix: scope image inbound state env

* test: scope reply session state env

* fix: add test env delete helper

* test: scope doctor missing state env

* fix: restore commitment store state env

* test: restore commitment runtime state env

* fix: restore commitment extraction state env

* test: scope commitment full chain state env

* fix: scope commitment heartbeat state env

* test: route state dir env helper

* fix: route live env restore deletes

* test: scope plugin dispatch state env

* fix: route respawn hint env clears

* fix(anthropic-vertex): stop re-marking cache_control on transport-budgeted payloads (#92387)

Summary:
- The PR removes the Anthropic Vertex adapter’s redundant cache-control payload-policy pass, forwards caller payload hooks unchanged, and adds regressions for preserving transport-budgeted payloads.
- PR surface: Source -35, Tests -11. Total -46 across 2 files.
- Reproducibility: yes. at source level. Current main reapplies cache policy to a finalized, fully budgeted pa ... ion logs show the corresponding five-marker rejection; this review did not run a live post-fix GCP request.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 6ef19602bfb48a819e096b23b66ece94a8072ed4.
- Required merge gates passed before the squash merge.

Prepared head SHA: 6ef19602bfb48a819e096b23b66ece94a8072ed4
Review: https://github.com/openclaw/openclaw/pull/92387#issuecomment-4688955121

Co-authored-by: openperf <[email protected]>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <[email protected]>

* fix: stop docker build commands by pid and group

* fix doctor channel SecretRef preview (#92229)

* Fix disabled heartbeat one-shot cron retries (#92225)

* fix: retry disabled cron wake one-shots

* fix: satisfy cron retry CI checks

* fix: inherit static transport for configured DeepSeek models (#92265)

* fix: fail closed for cli-backed btw fallback (#92226)

* fix heartbeat suppressed commitment delivery (#92231)

* fix(agents): classify structured unsupported model errors (#92280)

* fix(agents): classify structured unsupported model errors

* test(agents): update embedded harness helper mock

* Fix OTLP log trace correlation (#92276)

* fix diagnostics otel log trace correlation

* test diagnostics trace provenance contract

* fix(update): hand off Linux service auto-updates (#92282)

* fix: resolve managed secretref provider auth (#92235)

* fix provider static model fallback (#92293)

* fix(agent): continue after source message tool replies (#92343)

* fix(codex): preserve memory prompt registration (#92350)

* fix(codex): restore memory recall guidance

* fix(codex): add memory recall fallback

* fix(codex): preserve memory prompt registration

* test(codex): expect memory slot in scoped harness load

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: sallyom <[email protected]>

* fix: clarify gateway SecretRef auth diagnostics (#92290)

* fix gateway secretref health diagnostics

* fix gateway health result type narrowing

* fix: repair rejected Anthropic thinking replay (#92286)

* fix: repair rejected Anthropic thinking replay

* fix: narrow recovered retry result

* test: satisfy thinking recovery lint

* test: prove thinking retry preserves fresh reasoning

* test: type narrow thinking retry proof

* Fix Telegram spooled buffered replay (#92281)

* fix telegram spooled buffered replay

* fix telegram replay type checks

* fix telegram replay lint

* test telegram replay visible output retry guard

* fix telegram rollback failure retry

* fix(doctor): show per-step progress spinners during update

* test(doctor): cover update progress wiring and spinner cleanup

* fix(doctor): drop redundant Boolean conversion flagged by oxlint

* test: tighten doctor update progress coverage

* fix(outbound): honor top-level image param as send media source (#92407) (#92416)

When a message send action included an `image` media-source param, the shared outbound runner recognized it for sandbox validation and media-access hints but then omitted it from the generic send payload, causing text-only delivery with a silent ok:true result.

Add `image` to the mediaHint resolution chain in buildSendPayloadParts so it is treated as a first-class media source for send only, preserving action-specific image semantics for non-send actions. Add regression coverage.

Fixes #92407.

* fix(sandbox): render cli skill prompts from materialized paths (#92508)

* Updated fallback logic

* fix: update esbuild audit pin (#92540)

* Add QA evidence artifact output (#91484)

* feat: add qa evidence summary normalization

* chore: rename qa evidence target environment

* chore: align qa evidence profile terminology

* chore: align qa evidence summary fields

* chore: add qa evidence taxonomy ref

* test: remove stale multipass evidence example

* test(qa): normalize vitest and playwright evidence

* test(qa): slim evidence summary metadata

* test(qa): clarify evidence summary inputs

* test(qa): rename scenario specs in evidence flow

* test(qa): treat evidence profiles as mapping strings

* test(qa): use neutral evidence test identity

* test(qa): nest evidence summary joins

* refactor(qa): normalize live evidence summaries

* fix(qa): accept normalized telegram rtt summaries

* fix(qa): normalize evidence lane summaries

* fix(qa): align evidence summaries with requirements

* refactor(qa): tighten evidence summary builders

* refactor(qa): restore standard evidence ids

* fix(qa): keep legacy summaries out of rtt evidence

* refactor(qa): make package evidence provenance explicit

* test(qa): keep script tests out of qa lab internals

* refactor(qa): rename scenario evidence definitions

* refactor(qa): clean evidence summary wording

* test(qa): fix evidence summary test inputs

* refactor(qa): simplify evidence identity fields

* refactor(qa): tighten evidence summary inputs

* refactor(qa): rename evidence artifact

* Add QA scorecard taxonomy validation (#91500)

Merged via squash.

Prepared head SHA: a9aec907d4823ad24fe06edf7e66d5365a4f2343
Co-authored-by: RomneyDa <[email protected]>
Co-authored-by: RomneyDa <[email protected]>
Reviewed-by: @RomneyDa

* fix(telegram): allow expandable blockquotes

* test: cover telegram expandable blockquotes

* feat(moonshot): add Kimi K2.7 Code support (#92554)

* feat(moonshot): add Kimi K2.7 Code support

* test(moonshot): surface K2.7 live provider errors

* ci(live): accept Kimi key for Moonshot sweeps

* test(moonshot): verify K2.7 across API regions

* fix(moonshot): backfill reasoning_content on assistant tool-call replay messages (#92396)

Moonshot/Kimi requires reasoning_content on all assistant tool-call messages
when thinking is enabled. After LCM compaction, cross-model fallback, or
session repair, the replayed history may be missing this field, causing a
400 error from the Moonshot API.

Backfill an empty string to satisfy the API schema contract without
fabricating semantic reasoning content. Follows the same provider-owned
backfill pattern already used by Kimi Coding (extensions/kimi-coding/stream.ts)
and DeepSeek V4 (provider-stream-shared.ts).

Fixes #71491

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* fix(e2e): keep lifecycle timeout cleanup alive (#92566)

* ci: split plugin ClawHub publishing paths

* feat: partition clawhub plugin release candidates

* fix: read clawhub trusted publisher config endpoint

* feat: split clawhub plugin bootstrap workflow

* ci: split plugin clawhub publish paths

* ci: pin clawhub package publish workflow

* ci: keep clawhub bootstrap token out of builds

* ci: fix clawhub release dry-run gating

* ci: align clawhub oidc publish refs

* ci: make clawhub bootstrap recovery idempotent

* ci: route clawhub repair candidates through bootstrap

* ci: preserve tideclaw alpha clawhub guards

* ci: simplify clawhub release ref handling

* ci: extract clawhub release routing plan

* ci: extract clawhub release runtime state

* test: guard clawhub release helper executability

* ci: pin ClawHub CLI for plugin publishing

* ci: allow historical ClawHub dry-run validation

* ci: fix ClawHub bootstrap token handoff

* fix(gateway): mirror commentary-phase assistant events to session message subscribers

Non-control-UI-visible runs previously dropped assistant commentary on the
floor for session message subscribers. Mirror those events to exact session
subscribers, gated strictly on phase === "commentary" so untagged text or
delta frames and final-answer streaming never dual-lane into channel
surfaces. Dialects that emit commentary as untagged deltas should tag the
phase at provider normalization instead.

Co-authored-by: Forge <[email protected]>
Co-authored-by: Chisel <[email protected]>

* fix: harden websocket payload handling

* fix(moonshot): rewrite duplicate native Kimi tool call ids

Preserve the first native Kimi tool-call ID while rewriting repeated replay occurrences to deterministic OpenAI-style IDs and keeping paired tool results aligned. Moonshot responses-family behavior and providers that do not opt in remain unchanged.

Closes #51593

Co-authored-by: Pluviobyte <[email protected]>

* Expose paged channel action results (#88993)

* fix(discord): expose paged thread list results

* fix(discord): keep thread pagination local

* fix(discord): use archive timestamps for thread cursors

* test(discord): cover thread pagination results

---------

Co-authored-by: Peter Steinberger <[email protected]>

* fix(fireworks): resolve catalog model params from manifest (#90326)

Resolve bundled Fireworks manifest models through core's static catalog so Kimi K2.6 keeps its 262,144-token context limit and nested model compatibility metadata.

Keep the existing dynamic fallback for uncataloged Fireworks IDs and align bundled Kimi reasoning metadata with existing runtime behavior.

Verified with focused tests, extension/core type checks, lint/format, full build, fresh autoreview, required CI, and a live Fireworks Kimi K2.6 embedded run using a real key.

Co-authored-by: Evgeni Obuchowski <[email protected]>

* fix(doctor): diagnose blocked external channel plugins (#86629)

Diagnose configured channel plugins whose installed owners are blocked by trust or activation policy, while preserving multi-owner fallback and actionable channel safety warnings.

Closes #83212.

Co-authored-by: luyifan <[email protected]>

* fix(mistral): skip unreadable tool schemas (#90242)

Skip unreadable Mistral tool schemas while preserving valid sibling tools. Omit empty tool payloads, reject forced choices for removed tools, and snapshot pinned tool names before validation and emission.

Live Mistral E2E: run_9b34d30e9f5b
CI: https://github.com/openclaw/openclaw/actions/runs/27459679633

Co-authored-by: Vincent Koc <[email protected]>

* fix(slack): persist delivered replies in transcripts (#92498)

Persist successful same-channel Slack and CLI assistant replies exactly once in the owning transcript. Preserve delivery-hook output, routed/runtime ownership, custom stores, and authoritative reset/session rotation bindings.

Fixes #92489

Co-authored-by: Andy Ye <[email protected]>

* fix(channels): report progress draft startup failures (#92083)

Report timer-fired channel progress-draft startup failures at the shared gate boundary instead of silently dropping them. Explicitly awaited starts still reject, and failed timer starts remain retryable.

Refs #92031.

Co-authored-by: Hansraj Singh Thakur <[email protected]>

* fix(agents): isolate invalid plugin model catalogs (#92564)

Keep valid root and plugin models available when one generated plugin catalog is invalid, while retaining and logging the catalog error.

Fixes #92553.

Co-authored-by: tangtaizong666 <[email protected]>

* chore(maint): make PR changelog edits release-only (#92607)

* docs: UX-013 — design system documentation (#89827)

* docs: UX-013 — shared design system documentation

* docs: align design system docs with source tokens

* feat(ui): hide empty workboard columns (#89615)

* fix(a11y): B-1+B-2+B-3 — contrast, focus states, minimum font sizes (#89822)

* fix(a11y): B-1 — raise muted text contrast to ≥4.8:1 WCAG AA

* fix(ui): C-1 — ChatSidePanel joins glass surface language

* feat(mobile): D-1 — hamburger overlay nav below 900px

- Esc key now closes nav drawer (globalKeydownHandler)
- Nav item tap targets bumped to min-height: 44px + padding: 10px 16px
  in the ≤1100px drawer breakpoint (was 40px / 0 12px)
- Hamburger toggle + overlay drawer were already wired in app-render.ts;
  this completes close-on-Esc and ensures accessible tap targets

* fix(a11y): B-2 — consistent focus-visible states distinct from hover

* fix(a11y): B-3 — lift all sub-12px text to 12px minimum

* fix(a11y): narrow focus and CSS scope

* fix(a11y): finish focus-visible selectors

* fix(memory): preserve qmd startup errors (#92618)

* docs(gateway): add uptime monitoring guidance to health check docs (fixes #55768) (#92608)

* fix(docs): pin Windows Hub download links to v2026.6.5 (#92605)

The Windows Hub companion installers are promoted to the main OpenClaw
release via a manual workflow_dispatch, not every release includes them.
The /releases/latest/download/ links resolved to v2026.6.6 which does not
have the OpenClawCompanion assets, causing 404 errors.

Pin the links to v2026.6.5 (the latest release that has the assets) and
add a fallback note directing users to the releases page when a release
is missing the companion installers.

Fixes #92470

* #92589: fix(internal-runtime-context): wrap prompt-preface runtime context body in delimiters (#92593)

* fix(internal-runtime-context): wrap prompt-preface runtime context body in delimiters

When buildRuntimeContextMessageContent constructs the hidden
runtime context prompt block, the body (which may contain
sensitive metadata like relevant-memories, sender info, and
conversation metadata) was not wrapped in the standard
INTERNAL_RUNTIME_CONTEXT_BEGIN/END delimiters.  If the model
echoed this context back in its reply, stripInternalRuntimeContext
could only remove the header and notice lines — the sensitive
body leaked through to user-visible surfaces like Feishu
streaming cards.

Wrap the runtime context body in BEGIN/END delimiters so the
existing stripInternalRuntimeContext (which handles delimited
blocks first) can fully remove the entire block.

Closes #92589

* chore: retrigger CI for proof check

* chore: retrigger CI with corrected proof format

* chore: retrigger CI with corrected proof field format

* Run Vitest and Playwright scenarios from qa suite (#92606)

* test(qa): run vitest and playwright scenarios from qa suite

* fix(qa): harden scenario suite dispatch

* refactor(qa): share scenario path utilities

* refactor(qa): share test file scenario runner

* refactor(qa): route test file scenarios through suite runtime

* refactor(qa): use explicit suite runtime result kind

* test(qa): write suite evidence artifact

* refactor(qa): clarify suite execution dispatch

* fix(qa): keep test-file scenarios out of flow-only runners

* refactor(qa): export mixed scenario suite runner

* Revert "chore(maint): make PR changelog edits release-only (#92607)"

This reverts commit 4640baa299099df01bf58b040c8b6ae9f243713e.

* feat(hooks): per-turn usageState (with provider limits + rich atoms) on reply_payload_sending

Attach a per-turn execution snapshot to the reply_payload_sending hook as
`usageState`, so a plugin (or the future in-core /usage renderer) can render a
per-response usage readout as a pure consumer of the contract — no side calls.

Recorded in agent-runner, consumed in dispatch. Fields: provider, model,
resolvedRef/requested, reasoningEffort, fastMode, fallbackUsed, is_override
(overrideSource), authMode, compactionCount, contextTokenBudget, token usage,
turn cost (USD), duration, owning agentId/sessionId, chatType, the agent
identity (name/emoji), and the active provider's subscription `limits` windows.

reply_payload_sending is the one reply hook universal across every surface
(incl. the Codex app-server, which emits no llm_output/agent_end), so it is the
correct harness-agnostic place for per-turn usage. Limits are resolved by a
core-internal non-blocking SWR helper (src/infra/provider-usage.limits.ts) and
attached to the snapshot — no new plugin-SDK accessor. All fields optional/additive.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(hooks): make usageState limits credential-aware + document best-effort delivery

Addresses review feedback on #89629.

1) Provider-limit resolution no longer defaults to OAuth. getProviderUsageLimits
   and getProviderUsageLimitsCached resolved with `credentialType ?? "oauth"`, and
   the agent-runner snapshot call passed no credential type, so an api-key OpenAI
   turn could borrow cached OAuth/ChatGPT usage windows. Drop the "oauth" default
   (missing credential type => no OpenAI usage provider) and thread the turn's
   authMode through at the call site. Adds provider-usage.limits.test.ts covering
   api-key/no-credential (no fetch), oauth/token (resolves), and non-OpenAI.

2) usageState is documented as best-effort, present only on live dispatcher
   delivery. Routed durable and recovered queue replays re-run this hook as a
   stateless transform over the original payload (see QueuedDeliveryPayload); a
   point-in-time usage snapshot is not stateless and would replay stale after a
   restart, so it is intentionally omitted there. Consumers must treat the field
   as optional.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): map auth mechanism to usage-credential type for provider limits

requestShaping.authMode is the auth *mechanism* (e.g. "auth-profile" for a
configured auth profile), not the credential *type* resolveUsageProviderId
expects. Gating limits on it === "oauth"/"token" dropped 📊 for legit OAuth
(profile-based) turns. Map it: api-key/aws-sdk -> no usage provider (cannot
borrow cached oauth windows); oauth/token/auth-profile/absent -> usage-eligible,
with the real credential re-checked at fetch time.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): thread real context occupancy + last-call usage into usageState

context.used_tokens / pct_used were derived from the snapshot's aggregate
prompt total (cacheRead+cacheWrite+input). Over a multi-call tool-loop turn
that is the run AGGREGATE, overstating window occupancy (often past 100%) so a
footer's context gauge pins full while /status shows the true ~7%.

Add two optional fields to PluginHookReplyUsageState and populate them in the
reply path:
- contextUsedTokens: the final call's prompt size (agentMeta.promptTokens) =
  real end-of-turn occupancy, a point-in-time state, not the aggregate.
- lastUsage: the final model call's usage only (vs `usage`, the turn
  aggregate), so a footer can render the last exchange's i/o + cache.

Both optional and additive; consumers fall back to the aggregate when absent
(correct for single-call turns). Renderer consumption lands separately (#89835).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): don't resolve subscription windows for an absent auth signal

A missing per-turn authMode was mapped to "oauth", so an OpenAI api-key turn
that arrived without an explicit auth mechanism could resolve and display
ChatGPT subscription windows that aren't its own — served straight from the 60s
limits cache, which the "re-checked at fetch time" guard does not cover.

Treat a genuinely absent signal as non-eligible (same as api-key): no usage
provider resolves and the footer omits limit windows. Present mechanisms are
unchanged — oauth/auth-profile/token stay eligible, and only OpenAI is gated on
the credential type so other providers are unaffected. A real oauth/profile turn
always carries its mechanism; one arriving blank is an upstream tagging bug to
fix at the source.

Inverts the now-incorrect "absent => oauth-eligible" test into regression
coverage for the absent/api-key case.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(hooks): tighten reply usage state correlation

* fix(models): bound model browse discovery (#92247)

Bounds default model browsing to configured/read-only discovery while preserving explicit full-catalog browsing. Reuses prepared plugin metadata and auth state without triggering external CLI discovery on the picker hot path, while retaining provider normalization and canonical runtime aliases.

Verified with focused model tests, official OpenAI and Anthropic transport suites, fresh live tool calls for both providers, a full build, AWS check:changed, remote Docker OpenAI tools E2E, and green PR CI.

Fixes #91809.

Co-authored-by: samson1357924 <[email protected]>

* fix: require admin for HTTP model overrides

Co-authored-by: Peter Steinberger <[email protected]>

* fix(gateway): honor profile auth for SecretRef model entries (#90686)

Fixes #90685 by allowing models.list availability to use matching auth-profile credentials when provider config contains a non-env SecretRef, while preserving unavailable results for unresolved SecretRef-only providers.

Adds isolated regression coverage for file SecretRefs and secretref-managed provider markers.

Co-authored-by: Rohit <[email protected]>

* feat(usage): native templated /usage full renderer (retires the footer plugin)

Render the per-reply /usage full footer in core from a declarative template
(the openclaw.usageBar.v1 format) when messages.usageTemplate is set; fall back
to the built-in line otherwise. Ports the reference usage_bar.py engine to TS so
no external process is involved (the external surface is just template data).

- usage-bar/translator.ts: engine (verbs num/dur/pct/inv/alias/meter, segment
  forms text/when/map/each, output.surfaces, item_scales). Codepoint-correct
  glyph indexing; fail-open (empty render -> boring fallback).
- usage-bar/contract.ts: buildUsageContract (snapshot -> openclaw.usageLine.v1).
- usage-bar/template.ts: resolve from a path (mtime-cached) or inline object.
- agent-runner: capture the per-turn usage snapshot and render the template at
  the /usage full branch in place of the built-in line when configured.
- messages.usageTemplate config (string path | inline object) + strict schema.
- translator.test.ts: verb parity, segment forms, astral glyphs, e2e render.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* style(usage): satisfy oxfmt + oxlint for native renderer

Formatting (import order, indentation) and bracket-notation access for
reserved template keys (_aliases/_default) to satisfy no-underscore-dangle.
No behavior change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(usage): clear type-aware oxlint in usage-bar translator

- no-misused-spread: use Array.from for code-point glyph split (same
  behavior, astral glyphs intact) instead of string spread.
- no-base-to-string: return the case glyph only when it is a string.
- no-unnecessary-type-assertion: drop redundant cast already narrowed
  by isObject.

No behavior change (render output byte-identical; tsgo clean).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* feat(usage): add fixed:N decimal-format verb to usage-bar translator

num truncates sub-1000 values to integers, so monetary fields like
cost.turn_usd rendered as 0 (or with full float noise when piped raw).
Add a fixed:N verb that formats a number to N decimal pla…
wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 16, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 17, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
badgerbees pushed a commit to badgerbees/openclaw that referenced this pull request Jul 8, 2026
…y jobs.json (openclaw#92144)

* fix(cron): report SQLite storage path in cron.status instead of legacy jobs.json

The `cron.status` gateway response returned `storePath` pointing to the
legacy `jobs.json` path, but cron jobs are actually stored in the shared
SQLite state database. This misled operators and agents into looking for
a JSON file that no longer exists.

- Add `storage: "sqlite"` and `sqlitePath` fields to CronStatusSummary
- Mark legacy `storePath` as @deprecated (kept for backward compat)
- Update CLI warning to prefer sqlitePath over storePath
- Add regression assertions in read-ops test

Fixes openclaw#91766

* fix(macos): prefer sqlitePath in cron status display

* fix(macos): add sqlitePath to CronSchedulerStatus type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: macos App: macos cli CLI command changes P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: openclaw cron status reports legacy storePath

2 participants