Two Code-section bullets: correctness on real inputs is mandatory but
over-engineering for imagined ones is a defect; extremely unlikely edge
cases are tradable for real simplification when the tradeoff is named.
The watch inbox's empty and waiting surface gets a 72pt Clawd: sleepy
(nightcap and all) when nothing is waiting, thinking while the watch
waits for iPhone sync, attentive when an approval needs a decision.
The Always-On display renders a static pose so the animation loop
never runs dimmed, and the active loop is throttled to 15fps for
battery. The shared mascot gains a defaulted minimum frame interval;
mascot sources compile directly into the watch target following the
TalkWaveformView pattern.
* refactor(google-meet): merge host and node realtime engines behind one audio transport
* refactor(google-meet): internalize engine helpers left test-only after realtime-node removal
* fix(agents): wake top-level requester when its last parallel child settles [AI]
A top-level session (normal chat/dashboard, not itself a subagent) that
spawns parallel subagents and waits via sessions_yield never receives the
"all descendants settled -> synthesize" wake: wakeSubagentRunAfterDescendants
only targets orchestrators with a subagent-registry run record. The parent
only sees passive per-child announces, commonly mis-tracks the outstanding
set on the final completion turn (or never hears results whose announce gave
up), and then parks until a human sends a message.
Fix: when a child reaches a terminal settle (announce delivered, give-up, or
delivery suspended) and its requester has no more descendants awaiting
settle, deliver a one-shot "all spawned subagents settled - synthesize and
deliver now" wake to the parked top-level requester through the existing
announce delivery pipeline (active requesters get it steered into the live
turn; parked ones get a new origin-routed turn).
- hasDescendantRunAwaitingSettle: early-exit drain check where a suspended final
delivery counts as settled (suspension is terminal for automatic retries).
- maybeWakeRequesterAfterAllChildrenSettled (subagent-announce.ts): scopes
the batch to the settling child's parallel wave (the connected component of overlapping run
lifetimes), skips nested/cron requesters (owned by the descendant-settle
wake), skips single delivered completions and fire-and-forget children,
and dedupes concurrent last-sibling settles via a batch-stable
announce idempotency key (requester-settle:<requester>:<sorted runIds>).
- Trigger fires from completeCleanupBookkeeping (all cleanup-terminal paths)
and suspendPendingFinalDelivery, and is skipped by the suspended-delivery
discard sweep, so the wake is outcome-independent: a child whose announce
gave up with NO_REPLY still counts toward - and can trigger - the drain.
Companion to the nested-orchestrator wake; covers the top-level case it
left open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rukuut98qvDwzSwMp3u4kN
* fix(agents): retry the requester settle wake on transient turn failures [AI]
The settle wake is the only event that ever fires after a fan-out drains, so
a wake turn lost to a transient infra failure re-parked the requester
permanently. Observed live: the wake turn survived dispatch, reconciled all
investigators, and then died to a provider stream stall (LLM idle timeout)
mid-synthesis — with nothing left to retry it.
Bounded recovery: up to 3 attempts with 30s/120s backoff. Each retry uses a
fresh `:retry-N` idempotency suffix because the gateway dedupe caches
terminal run outcomes per key, so re-dispatching the same key would no-op. A
legitimately silent wake reply already classifies as delivered and never
retries; terminal failures and an abandoned requester stop immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rukuut98qvDwzSwMp3u4kN
* fix(agents): ledger retired delete-cleanup rows for the requester settle wake [AI]
cleanup="delete" retires a child's registry row in the same funnel pass
that schedules the settle wake, so a pure delete-mode fan-out could never
rebuild its drained wave from live rows: empty batch, no wake, requester
parked — the exact incident class this wake exists to close.
Retiring cleanup paths (delete branch, reconciled-killed tombstone) now
pass settledRowRetired, and the wake ledgers the in-hand record before
its first await: concurrent last-sibling settles all see the same
ledgered rows by the time any batch computation runs, keeping batch
membership and the idempotency key stable — the guarantee keep-mode gets
from rows persisting in the registry. Ledgered rows merge into the batch
candidates (a live registry row wins by runId); entries clear on
delivered/terminal wakes and the nested/requester-gone exits, with a 24h
TTL prune as the memory backstop. In-memory only: a restart degrades a
mid-wave delete fan-out to waking with the surviving rows.
* fix(agents): snapshot delete-mode child results before cleanup clears them [AI]
The delivered-finalize path clears completion.resultText (delete mode) and
the frozen delivery payload before cleanup bookkeeping runs, so the
requester-settle wake ledgered a row whose findings had already been
emptied: a cleanup="delete" fan-out woke its requester with "(no output)"
for successfully delivered children.
Capture an immutable snapshot of the entry (completion + delivery payload)
at the top of finalizeSubagentCleanup and in the resumed-announce give-up
path — before any clearing — and pass it through completeCleanupBookkeeping
to the settle wake for the retired-row branches. Keep-mode is unchanged:
its registry rows keep their result text and win over ledgered copies.
Regression: a lifecycle-driven test drives the real delivered delete-mode
cleanup (completeSubagentRun -> announce -> finalize -> funnel) and asserts
the wake's settledEntry still carries the child's result text after the
live entry was cleared and the row retired.
* fix(agents): release drained no-wake batches from the settle ledger [AI]
The zero-required and single-delivered guard exits decline to wake a wave
that has already fully drained. Ledgered delete-mode rows in such a batch
can never join a later wake (a child spawned after the drain cannot
lifetime-overlap rows that already ended, and any still-running overlap
would have failed the drain gate before these exits), so holding their
child-result snapshots for the 24h TTL was pure retention: high-volume
delete-mode fan-outs could pin large result texts in process memory.
Release the batch at both exits. No behavior change: waves with pending
overlapping children return earlier at the drain gate, which still keeps
their rows.
* test(agents): route e2e session fixture through the sqlite accessor [AI]
Upstream flipped session reads to the sqlite-backed session accessor
(#98236), which bypasses the loadSessionStore mock this e2e used for its
in-memory session fixture — requester entry lookups came back empty and
the settle wake exited at the usable-session guard. Serve loadSessionEntry
from the same fixture; everything else in the accessor stays real.
* refactor(agents): move the requester settle wake into its own module [AI]
Two pieces of fallout from rebasing across upstream tooling changes:
- The max-lines lint budget (#107315) has no grandfathered suppression for
subagent-announce.ts, and the settle wake + retired-row ledger pushed it
past the cap. The wake is a coherent unit, so it moves to
subagent-announce.requester-settle-wake.ts (matching its test file) with
its own registry-runtime seam; the registry's lazy dep and the lifecycle
type alias point at the new module. announce.ts drops back well under
the budget with no suppression needed.
- The announce read-path refactor rewired the runtime barrels onto
subagent-registry-announce-read.js, orphaning the
hasDescendantRunAwaitingSettle re-export on subagent-registry.ts, which
the deadcode-exports gate now flags. Removed; the retry-grace e2e reads
the announce-read implementation directly.
* fix(agents): admit the requester settle wake as tracked gateway root work [AI]
The settle wake was launched as a detached promise from cleanup
bookkeeping, so registry cleanup or shutdown could reach quiescence
before the wake admitted its gateway turn and the last-child completion
could still be lost during restart or teardown. Route the wake through
runWithGatewayIndependentRootWorkContinuation: a live cleanup parent
reserves the root synchronously, and restart drain now waits for the
in-flight wake. Adds a deterministic quiescence-race regression.
* fix(agents): persist requester settle recovery
Store requester-settle wake obligations on subagent run rows, replay them after restart, and retire cleanup rows only after a durable outcome. Persist admitted attempts and retry deadlines, coalesce live restores, drain disconnected waves, and let the sweeper recover stranded processors.
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* test(agents): allow durable settle persistence
Keep the bulk-cancellation regression focused on recovery after the injected persistence failure instead of assuming an exact write count; requester-settle bookkeeping now adds a legitimate durable write.
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* style(agents): declare settle wake scheduler
Use a hoisted function declaration for the lifecycle scheduler so lint accepts the intentional callback cycle without a mutable binding.
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* fix(agents): harden requester settle recovery
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* fix(agents): release settle wake during retry backoff
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* test(agents): type settle wake fixtures
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* style(agents): bind settle wake transition callbacks
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
* test(agents): restore past-due settle wake
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
---------
Co-authored-by: smthfoxy <263563487+smthfoxy@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(install): reject runtimes with broken npm
* test(installer): use real Node for npm selection
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(mattermost): add thread.requireExplicitMention to opt out of thread auto-follow
Mattermost treats any reply in a thread the bot has participated in as an
implicit mention, so requireMention only gates the first message and the bot
then answers follow-ups addressed to other people for the participation TTL.
Slack exposes channels.slack.thread.requireExplicitMention for exactly this;
Mattermost had no equivalent and its strict schema rejected the key.
Add channels.mattermost.thread.requireExplicitMention (channel + per-account),
mirroring Slack. When set, thread participation no longer counts as a mention.
Default (unset/false) keeps today's auto-follow behavior unchanged.
Related: #108269
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(channels): add implicit mention policy foundation
* feat(channels): unify implicit mention policy
Co-authored-by: leon <dodoma0919@gmail.com>
* refactor(channels): keep implicit policy inside evaluator
* fix(channels): use exported implicit mention type
* chore(channels): satisfy extension lint
* fix(config): break implicit mention type cycle
* fix(plugin-sdk): account for implicit mention config export
* refactor(config): isolate implicit mention schema
* chore(plugin-sdk): align implicit mention surface budget
* fix(config): remove unused schema re-export
---------
Co-authored-by: leon <dodoma0919@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ci): address reviewer findings across the perf-lever batch
Seven follow-ups from Codex review feedback on the landed CI perf PRs:
docker packaging switches to the full build profile (the ciArtifacts
step env clobbered its canonical-dts requirement); the boundary sticky
restore gates on generative source tree OIDs in both lanes, making
restored artifacts valid by construction (kills the clock-skew mtime
false-skip and ghost declarations from deleted sources); the node-test
worker budget derives from nproc instead of the runner label (labels
over-promise under load) and keeps the 2-worker cap when plans can
overlap; the deadcode task falls back to serial Knip below 8 cores;
the lint-lane i18n verify also runs for any ui-touching diff (keys are
extracted from all ui sources, not just ui/src/i18n); the oxlint
concurrency diagnostic moves to stderr; and workspace packages join
the prompt-snapshot always-run surface.
* fix(ci): fingerprint every generative source the boundary artifacts consume
Autoreview flagged inputs outside the tree-OID gate; toolchain, config,
and script inputs were already covered by the sticky key hash, but the
old actions/cache key also fingerprinted src/plugins/types.ts and
src/video-generation sources the OID list missed. Add those plus
src/channels (the plugin-sdk declaration graph reaches its public
types) to the fingerprint in all three sites.
* fix(ci): fold runner toolchain identity into the boundary sticky fingerprint
Autoreview: the sticky key hashes config/scripts/package.json/lockfile,
but a node/pnpm toolchain bump (pinned in the workflow, not the key)
could leave the key unchanged and bless artifacts built with the prior
toolchain. Record node/pnpm versions alongside the source tree OIDs in
all three fingerprint sites so a toolchain-only change forces a cold
build.
* refactor(channels): move reef, msteams, matrix, zalouser file state to SQLite with doctor imports
* fix(channels): harden SQLite state migrations
* test(reef): use SQLite flow stores in receipt suites
* fix(reef): validate restored request policy
* fix(reef): retain replay state through relay window
* docs(reef): describe durable state import
* fix(reef): honor configured legacy state path
* chore: leave changelog to release flow
* fix(reef): use managed temp root in flow tests
* fix(matrix): omit undefined device id from state
* fix(process): clarify command lane timeout cause
* fix(process): complete timeout cause formatting
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* Fix Feishu local media fallback leaks
* refactor(feishu): clarify media fallback link style
* fix(feishu): reject credentialed media fallbacks
* fix(feishu): reject ambiguous media fallback URLs
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The child published its pid with a non-atomic writeFileSync while the test
polled file existence only, so a loaded runner could read an empty pid file
(NaN -> isProcessAlive false). Publish the pid via rename and widen the
spawn-chain poll deadlines; assertion strength is unchanged.
The generated hand-edited d.mts declared pruneOrphanLocaleDocs twice
with resolveClawHubRepoPath between them, tripping the
adjacent-overload-signatures lint on every PR. Keep the documented
declaration.
Recent setup commits left the hard-zero gates red on every PR:
parseClickClackSetupCodeInput was exported but only used internally,
ClickClackSetupCodeClaimError was exported yet consumed only by its
test (production catches it structurally through a status field), a
string spread tripped no-misused-spread, and a computed "token" key
tripped no-useless-computed-key. Un-export both symbols, switch the
test to a structural claim-error stand-in matching what the formatter
reads, and apply the two mechanical lint fixes.