* fix(workboard): resolve parent dependency status via targeted lookups, not a full-corpus scan
dependencyTargetStatus() called the fully unscoped store.list() (every card,
every board, fully materialized via entries()+readCard()) just to look up the
status of a card's own parent id(s) -- almost always exactly one. dispatch()
calls promoteDependencyReady() once per card in its board-scoped pass, so
every parented card in that pass re-paid for a full-corpus re-scan.
Confirmed via a live CDP CPU profile of a real "openclaw workboard dispatch"
call (--inspect attached to a running gateway, Profiler.start/stop scoped to
the repro, elapsed 161.8s matching a known 160s+ stall): 93.3% of the time
(142s + 8.7s of 162s) was inside native SQLite all()/prepare() bindings, all
funneling through dispatch -> promoteDependencyReady -> dependencyTargetStatus
-> list -> entries -> readCard (readCard issues 4 separate synchronous SQL
statements per card). On a corpus of ~5770 cards, this is O(parented_cards_in_pass
x corpus_size x 4_queries) -- a genuine algorithmic defect, not event-loop
starvation from a concurrent session as originally suspected for this symptom.
Replace the unscoped list()+Map lookup with targeted get(parentId) calls
(already a single indexed-row lookup used everywhere else in this file) --
O(parents.length), normally O(1), instead of O(corpus size). Behavior is
unchanged: parent?.status === "done" on the get() result is exactly what
cards.get(parentId)?.status === "done" computed from the unscoped list().
Adds a regression test asserting the underlying store's entries() is called
at most once while promoting 8 parented cards together in one dispatch pass
(would have been 9 calls before this fix -- 1 outer scoped list() + 1 inner
unscoped list() per parented card).
* fix(workboard): address review feedback on dependency-target-status fastpath
Two findings from the automated PR review, both confirmed real:
- Remove an incomplete, unrelated active-owner fast-path (listActiveOwnerIds()
in store.ts + its cross-board test in store.test.ts) that leaked into this
commit from separate, still-in-progress local work. It imported
WorkboardActiveOwnerQueryable, a type that isn't defined anywhere in this
PR's tree, breaking typecheck and the bundled-extension lint check. Not
part of the dependencyTargetStatus fix this PR is scoped to.
- Replace the comparator-free, mutating .sort() calls in the new dependency
regression test with .toSorted() plus an explicit localeCompare comparator,
per the repo's lint rules.
Re-ran locally against just these two files (the actual PR diff, review WIP
set aside): pnpm lint --threads=8, pnpm format:check, pnpm tsgo:prod, pnpm
check:test-types, pnpm run lint:extensions:bundled all pass; full workboard
extension suite 111/111 (two removed tests belonged to the unrelated
active-owner code, not this fix).
---------
Co-authored-by: ClawBox <clawbox@cayk.ca>
* feat(usage): surface plan windows and account email in the chat context popover
The context-window popover now shows which account a session runs on:
provider usage snapshots carry an accountEmail resolved from the auth
profile when stored, the ChatGPT access-token JWT claims (openai/codex),
or the Claude CLI config file (~/.claude.json oauthAccount) for
keychain-synced logins. models.authStatus embeds it, quota groups keep
distinct accounts separate, and the popover renders the email under the
plan header for local CLI sessions and OpenClaw-configured subscriptions
alike.
* test(openai): assemble the fake usage JWT from parts
* test(usage): keep fixture tokens and identity plumbing scanner-safe
* fix(usage): verify the Claude CLI login before labeling usage with its email
Review findings: the CLI-config email fallback now requires the usage token
to match the cached CLI credential (and honors CLAUDE_CONFIG_DIR), so an
ambient login for another account never labels a snapshot; token-type
credentials propagate their stored email alongside oauth ones.
* style(anthropic): scanner-safe local for the CLI login read
* fix(usage): capture the Claude CLI account email on the credential
Review findings: reading ambient CLI config at usage-fetch time could not
verify keychain-only logins and could go stale across account switches.
The credential reader now captures oauthAccount.emailAddress next to the
credential it belongs to, the synced claude-cli profile carries it, and
the anthropic fetcher uses only the credential-borne identity.
* style(usage): scanner-safe credential fixtures and helper naming
* fix(auth): backfill the CLI account email onto existing synced profiles
Profiles synced before identity capture stay usable and skip CLI reads,
so upgraded installs would never gain the email until token rotation.
While the stored token material matches the CLI login, merge the
non-secret email onto the stored profile.
* style(test): scanner-safe email assertion
* test(auth): pin the no-reread invariant to identity-complete profiles
* fix(transcripts): stop live capture when the agent run is canceled
* fix(transcripts): preserve startup cleanup ownership
* fix(transcripts): keep runtime session type internal
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(extensions): trim channel-private exports
* chore(deadcode): refresh Telegram export baseline
* fix(telegram): drop unused media import
* refactor(telegram): drop dead media re-export
* style(telegram): format media runtime imports
* fix(codex): do not promote missing_tool_result on abort/interrupt
Keep synthetic tool.result rows for transcript integrity, but skip
promoting the missing-tool invariant to promptError when the turn is
already aborted or interrupted so mid-exec cancel paths keep abort
classification and assistant text (#104898).
* fix(codex): preserve explicit abort outcome
---------
Co-authored-by: Altay <altay@hey.com>
Prevent LINE group allowlist policy from inheriting DM allowFrom entries, so open DMs do not broaden group access. Keep group-specific allowlists and per-group overrides as the only group admission sources, and document the scoped behavior.
Co-authored-by: pgondhi987 <pgondhi987@users.noreply.github.com>
Fix Discord inbound replies where Discord provides a message reference but omits the nested referenced message payload.
Refactor hydration so complete gateway replies fetch only the missing referenced message, while partial payloads still hydrate the current message first and reuse any referenced payload returned by Discord. Preserve non-blocking behavior for deleted references, forwarded messages, and failed reference fetches.
Closes#105862.
Supersedes #105901.
Co-authored-by: momothemage <niuzhengnan@163.com>
* fix(ui): make catalog sessions selectable in place and continuable from desktop-app sources
- sidebar: no phantom chat row for catalog keys, active highlight on catalog rows,
adopted sessions render as their live row inside the catalog group (deduped from
the regular list), host subtitle only for paired-node rows
- chat: view-only banner only after catalog metadata proves canContinue=false
- chat->sidebar continued event binds the adopted row before the next catalog poll
- anthropic: local claude-desktop sessions share the ~/.claude/projects store, so
list them as continuable and allow catalog continue to fork-resume them
* refactor(ui): extract catalog row rendering and lookup into shared modules
Keeps app-sidebar/chat-pane/anthropic session-catalog under the LOC ratchet by
moving cohesive blocks: sidebar catalog groups + adopted-row binding into
app-sidebar-session-catalogs.ts, the catalog metadata lookup into
catalog-key.ts, and the Claude node-host commands into
session-catalog-node-commands.ts; ratchet baselines down for all three.
* fix(ui): explain the disabled composer when catalog metadata cannot resolve a session
* style(ui): keep chat-pane within the LOC ratchet
* fix(anthropic): keep catalog node-command helpers module-local and repin SDK surface
The node-command module now owns its capability constant, projects-store
probe, and params parsing, so the catalog engine exports nothing new.
Repin the SDK surface budgets (+1 export/+1 callable landed on main
without a pin bump); unexport the UI helper types knip flagged.
* refactor(extensions): remove dead QA and utility exports
* refactor(extensions): trim Matrix QA internal exports
* chore(deadcode): refresh extension export baseline
* chore(ci): reconcile deadcode and LOC baselines
* chore(deadcode): refresh baseline after main advance
* chore(deadcode): refresh baseline after main advance
* refactor(plugins): keep channel snapshot type private
* fix(ci): align Linux deadcode baseline
* fix(memory): scope qmd search deadline
Preserve the phase-scoped QMD timeout semantics on current main.
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
* docs: refresh generated docs map
* test(memory): reconcile manager mock with current main
* test(memory): isolate deadline mock import
* style(memory): format search test mocks
* test(memory): decouple mock from deadline symbol
* chore(memory): refresh reviewed pull request head
---------
Co-authored-by: Benjamin Badejo <ben@benbadejo.com>
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* refactor: use semver package for version ordering
* refactor: centralize semver validation
* chore: keep release notes in PR body
* refactor: update semver LOC ratchet