* fix(acp): scope /acp sessions listing for non-owner senders
/acp sessions listed every ACP session on the gateway for any sender
allowlisted via commands.allowFrom, exposing other senders' session
labels, agent ids, runtime state, and thread bindings. The handler now
returns only the current bound or requester session for non-owner
senders, while owner identity and operator.admin clients keep the full
gateway-wide listing, matching the documented contract.
Fixes#103055
* test(acp): cover empty and missing-session cases for /acp sessions scoping
* fix(acp): avoid non-owner session scans
* docs(acp): remove duplicated session scope text
* test(acp): cover internal session visibility
* fix(acp): require current ACP metadata
* test(acp): reject sessions target tokens
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(android): copy or save rendered widgets as images from the chat transcript
* chore(i18n): sync native app i18n inventory for widget export strings
* chore(i18n): regenerate native locale artifacts for widget export strings
* feat(ui): add composer microphone picker
* fix(ui): single check indicator in mic picker, matching house radio-menu pattern
* fix(ui): keep System default focusable during mic discovery; update composer contract test for picker caret
* fix(acp): persist cancelled partial replies
* fix(acp): persist delivered output for cancelled turns
Normalize terminal status at the ACP manager boundary and settle routed/direct delivery outcomes before persisting cancelled bound turns.
Co-authored-by: shaoohh <150606856+shaoohh@users.noreply.github.com>
* fix(acp): require confirmed cancelled-turn delivery
Persist cancelled-turn output only when the core dispatcher reports successful delivery. Keep canonical ACP history independent of outbound-only hook rewrites and prove backend cancellation with the real dispatcher.
Co-authored-by: shaoohh <150606856+shaoohh@users.noreply.github.com>
* test(acp): satisfy cancellation proof lint
Keep the pending-delivery race assertion behavior while avoiding a return value from the Promise executor.
Co-authored-by: shaoohh <150606856+shaoohh@users.noreply.github.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(macos): add Quick Chat power features
* docs(macos): document Quick Chat power features
* chore(i18n): refresh native locale artifacts for Quick Chat power features
* fix(macos): update DEBUG test helper sendProvider to the reasoning-threaded arity
Run-mode subagent cleanup retired the session bundle MCP runtime even when an
approved exec follow-up turn still held an active materialization lease, so the
follow-up turn failed with "bundle-mcp runtime disposed for session ...".
Retire run-mode subagent runtimes with preserveActiveLeases so retirement defers
while a lease is held, and complete the deferred retirement when the run's
materialized tool runtime releases its lease.
* fix(usage-bar): clear dead watcher reference after transient error
When a usage bar template file watcher hits a transient error, the error
handler closes the FSWatcher but leaves entry.watcher pointing to the
closed instance. loadUsageBarTemplate treats a truthy cached.watcher
as "still watching" and never re-reads the file, so a template that
became invalid stays stuck at the default forever.
Clear entry.watcher after closing so the next access that needs a
re-read can create a fresh watcher.
This matches the pattern from #109682 (config hot-reload watcher
recovery).
* fix(usage-bar): invalidate cached template on watcher error
Clear both entry.watcher and entry.template when the FSWatcher errors
so the next loadUsageBarTemplate call re-reads from disk and creates a
fresh watcher. Previously only the watcher reference was cleared,
leaving a valid cached template that never observed future file edits.
This addresses the remaining valid-cache recovery gap from #109682.
* fix(usage-bar): fix no-promise-executor-return lint in test
* fix(config): bound state-directory .env file reads with size limit
Replace unbounded fs.readFileSync with readRegularFileSync capped at
1 MiB (MAX_STATE_DIR_DOTENV_BYTES) so an oversized .env file is
rejected before loading the entire file into memory.
Resolve symlinks via fs.realpathSync before the bounded read so
symlinked .env files keep working — matching the marketplace.ts
pattern for bounded manifest reads.
* fix(config): add diagnostic when oversized state .env is skipped
Log a warning when the state-directory .env file exceeds the 1 MiB
limit so operators know a configured file was skipped — matching the
pattern from #108200 (plugin catalog bounded read diagnostic).
* fix(config): bound global runtime dotenv file reads with size limit
Replace unbounded fs.readFileSync in readDotEnvFile with readRegularFileSync
capped at 1 MiB so CLI/Gateway startup dotenv loading also gets the bounded
read protection — not just the state-dir service-env path.
This addresses the P1 review finding that the original fix only capped the
helper-level reader while the shared readDotEnvFile used by
loadGlobalRuntimeDotEnvFiles was still unbounded.
* fix(config): bound external catalog file reads with size limit
Replace unbounded fs.readFileSync with readRegularFileSync capped at
16 MiB (MAX_EXTERNAL_CATALOG_BYTES) to prevent memory exhaustion from
oversized or malicious plugin catalog files.
Resolve symlinks via fs.realpathSync before the bounded read so
symlinked catalog files keep working — matching the marketplace.ts
pattern for bounded manifest reads.
* fix(config): add diagnostic when oversized catalog is skipped
Log a warning when an external catalog file exceeds the 16 MiB limit
so operators know a configured file was skipped. Add regression test
verifying the oversized catalog is skipped and selection continues.
* fix(config): fix false-positive oversized catalog test
Replace the broken spy-on-object-literal mock with a real sparse file
that genuinely triggers readRegularFileSync rejection via stat.size.
The previous vi.spyOn({ readRegularFileSync }, ...) intercepted a
throwaway object, never the actual module import, so the test passed
regardless of whether the fix was applied or not.
Also fix the no-unused-expressions lint error on the env-primary
assertion that used a discarded ternary (? undefined : undefined)
instead of a proper expect assertion.
Reload the current on-disk config once replacement native or polling watchers finish their initial scan, so edits made during recovery backoff are not lost.
* fix(litellm): guard loopback hostname auto-allow with isIP to prevent DNS bypass
The isAutoAllowedLitellmHostname helper auto-enables private-network access
for loopback-style hosts. Before this fix, lowered.startsWith("127.")
matched DNS hostnames like 127.evil.com, letting remote endpoints bypass
the explicit allowPrivateNetwork opt-in — a SSRF risk.
Add isIP(host)===4 guard so only literal IPv4 loopback addresses qualify.
Same canonical pattern as extensions/slack/src/monitor/relay-source.ts:271
and the codex loopback fix.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(litellm): cover loopback endpoint policy
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(cron): support current and session: targets in initial delivery resolution
resolveInitialCronDelivery only checked sessionTarget === 'isolated' for
default announce delivery. All other cron paths (delivery-plan.ts:106-114,
normalize.ts:680-684, jobs.ts:288-298) already treat 'isolated', 'current',
and 'session:' as equivalent. This gap meant cron jobs created with
--session-target current or --session-target session:... would not get
default announce delivery at creation time.
Real behavior proof:
Input: sessionTarget='current', payload.kind='agentTurn'
OLD: undefined <- missing default announce delivery
NEW: {mode:'announce'} <- consistent with other cron code paths
Co-Authored-By: Claude <noreply@anthropic.com>
* test(cron): add typed initial delivery resolution tests for all session targets
* test(cron): document direct-service delivery contract and cover service callers
* refactor(cron): centralize detached-delivery-target predicate into shared helper
Extract isDetachedDeliveryTarget(sessionTarget, payloadKind) in normalize.ts
and use it from all three callers:
- normalizeCronJobInput (write-time default, normalize.ts)
- resolveCronDeliveryPlan (read-time default, delivery-plan.ts)
- resolveInitialCronDelivery (service-bypass default, initial-delivery.ts)
This removes the third manually synchronized copy that the original PR
added, addressing ClawSweeper P1 finding.
* fix(cron): align initial delivery targets
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* test(cron): consolidate initial delivery coverage
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* test(cron): keep delivery matrix type-safe
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* chore: keep release note in PR metadata
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(imessage): keep fast progress out of chat
* test(imessage): require fast progress callback
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
* fix(whatsapp): evaluate mention patterns when other members are @-mentioned
isBotMentionedFromTargets returned false as soon as a group message
carried native @-mentions that did not include the bot, so the
configured mentionPatterns were never evaluated and a message like
'marlow, look at @SomeoneElse's message' was silently dropped by the
requireMention gate (#109488).
Fall through to the configured text mentionPatterns when the native
@-mention JIDs do not include the bot. The loose self-number digit
fallback stays suppressed in that shape: an @-tag of another member
injects that member's number into the body, so substring digit
matching is unreliable there.
Fixes#109488
* refactor(whatsapp): unify mention pattern gating
---------
Co-authored-by: Marcus Castro <mcaxtr@openclaw.ai>