* fix(process): skip kill-tree group kill when child wasn't detached (#71662)
When the supervisor spawns a child with detached:false (service-managed
runtime under launchd/systemd), the child shares the gateway's process
group. On session abort or SIGKILL, killProcessTree was unconditionally
issuing process.kill(-pid, 'SIGTERM') — which targets the entire process
GROUP (negative pid is POSIX group-kill semantics) and therefore
SIGTERMs the gateway parent along with the child.
Reporter saw this on macOS (LaunchAgent + KeepAlive=true): aborting a
claude-cli/claude-opus-4-7 session caused the gateway to receive
SIGTERM, then auto-restart, dropping all in-flight sessions. Switching
the primary model to a non-cli provider eliminated it because the
non-cli paths don't go through this kill-tree call. Did not occur on
Linux VPS where the gateway runs detached, because there
useDetached === true and the child got its own process group.
Fix:
- killProcessTree now accepts opts.detached?: boolean. When detached:false,
killProcessTreeUnix skips the `-pid` group-kill and goes straight to
direct-pid SIGTERM/SIGKILL. Group-kill default (detached:true) is
preserved so all existing callers behave exactly as before.
- supervisor/adapters/child.ts:286 now threads the spawn-time `useDetached`
flag into killProcessTree, so the kill-tree path matches the spawn-time
detachment decision (line 45 of the same file already computes
useDetached = process.platform !== 'win32' && !isServiceManagedRuntime()).
Tests:
- new: detached:false skips group kill and uses direct pid SIGTERM only.
- new: default behaviour (detached:true) still uses group kill (regression
guard so the existing test case isn't accidentally weakened).
Existing tests still pass (6/6 in kill-tree.test.ts). Lint clean.
Out of scope: other killProcessTree callers (mcp-stdio-transport,
bash-tools.process, etc.) keep the default group-kill behaviour because
those processes are typically detached from the gateway. Only the
supervisor/adapters/child.ts path threads `detached` through, since it's
the path that knows whether the child was actually spawned detached.
* fixup(process): also gate kill-tree group-kill on the no-detach spawn fallback (#71662)
Greptile review on the original PR caught a P1 gap: when
spawnWithFallback's initial detached spawn fails and it retries with the
no-detach fallback (label: "no-detach", options.detached: false), the
child runs detached:false but my variable useDetached was still true.
The kill closure then passed `detached: useDetached` = true to
killProcessTree, which still group-killed the gateway — same bug, just
on the fallback path.
Compute the actual detachment as
`useDetached && !spawned.usedFallback` after spawn returns, and pass
that through. This closes the gap: the kill path now correctly skips
group-kill in BOTH:
1. Service-managed runtime (useDetached=false from the start, original case)
2. Detached-spawn fallback to no-detach (useDetached=true at intent
time but spawned.usedFallback=true)
Tests:
- existing 'uses process-tree kill for default SIGKILL' updated to
assert the new {detached} option is forwarded.
- new: passes detached:false to killProcessTree when spawn fell back.
- new: passes detached:false in service-managed mode (regression guard
for the original fix).
11/11 tests pass in child.test.ts. 6/6 in kill-tree.test.ts.
When a subagent finishes while its parent main session is still running
(executing tools or awaiting model output), the announce queue would
follow the configured debounce and immediately attempt to deliver the
completion event back into the parent session via callGateway. The
gateway treats the parent as busy and the announce can either get
buffered until the next external user message or surface only as a
delayed echo, breaking the natural sessions_spawn -> sessions_yield
workflow where the parent expects the result to arrive as the next
turn.
This change adds an optional shouldDefer hook on the announce queue
state. The delivery layer wires it to the existing requester session
activity probe (resolveRequesterSessionActivity), so while the parent
session is still active the drain loop sleeps for max(250ms,
debounceMs) and re-checks instead of pushing the announce. As soon as
the parent goes idle, the queue drains normally.
- Plumbs shouldDefer through getAnnounceQueue / enqueueAnnounce.
- Skips drain step in scheduleAnnounceDrain when shouldDefer says the
target is still busy, with a bounded re-check sleep.
- Updates maybeQueueSubagentAnnounce to pass the activity probe.
- Adds a unit test that holds drain while parent is busy and resumes
when it goes idle.
No behavior change for callers that do not pass shouldDefer.
* fix(memory-core): add runtime cron service fallback for dreaming reconciliation
When the cron service is unavailable during gateway_start (e.g., due to
a startup timing race or deferred initialization), the startupCronSource
is captured as null and never refreshed. All subsequent runtime
reconciliation attempts fail with 'cron service unavailable', even when
the cron service is fully operational.
This adds a fallback path in the runtime reconciliation that attempts to
obtain the cron service from the plugin API runtime when the startup
capture was null. This handles the case where the cron service becomes
available after the initial startup event.
Fixes#67362
* fix(memory-core): hold gateway context for runtime cron resolution
The previous attempt tried to access api.runtime.cron which doesn't exist
on the PluginRuntime type. The cron service is only accessible through
PluginHookGatewayContext.getCron().
This fix stores the gateway context from the gateway_start event and uses
it to retry cron resolution at runtime when the initial capture was null.
This handles the race condition where the cron service isn't available
during gateway_start (250ms deferred init) but is ready later.
Also refreshes the startupCron capture when the runtime retry succeeds,
so subsequent reconciliation calls resolve immediately.
Addresses review feedback on #71694
Reporter measured `agents list --json` at ~7-9s on a fast host (~11s in
container) on 2026.4.23, while peer `--json` commands like
`channels list`, `cron list --all`, and `sessions ... --all-agents`
stay sub-second. Their cold-call dashboard endpoint dropped from 27s to
~2s after a local dist patch — they could even retire the 5-min cache
TTL workaround they had shipped to dodge it.
Root cause: `agents list` inherits `loadPlugins: 'always'` from the
parent `agents` policy in command-catalog, then `agentsListCommand`
calls `buildProviderStatusIndex(cfg)` unconditionally — both paths
trigger the bundled-extension import waterfall (~60+ extension index.js
modules).
`channels list` already uses `loadPlugins: 'never'` and proves the
shape is right; this PR matches that shape with the safer `text-only`
variant so human invocations are unchanged.
Two-line fix per reporter:
1. `src/cli/command-catalog.ts` — opt agents list into `text-only`,
the same plugin-preload policy bucket that already exists. Plugin
preload runs for human text output, skips for `--json`.
2. `src/commands/agents.commands.list.ts` — skip
`buildProviderStatusIndex` (and the per-summary provider
enrichment loop) when `opts.json`. Provider info is only rendered
in human text output via `formatSummary`, so dropping it from JSON
has no observable effect on existing callers that consume `id`,
`name`, `model`, `bindings`, `isDefault`, `identity*`, `workspace`,
or `agentDir`. `routes` is config-derived and continues to be set
in both modes.
Tests:
- new assertion in command-startup-policy.test.ts: `agents list` with
jsonOutputMode:true now resolves to `loadPlugins: false` (was
effectively `true` via the parent `agents` 'always' policy).
- existing assertion that human (jsonOutputMode:false) still triggers
plugin load is preserved verbatim.
6/6 tests pass. Lint clean.
Out of scope:
- `--bindings` flag opt-in for restoring providers in JSON output:
worth adding later if any consumer needs it; reporter said dashboard
consumers don't.
- Broader plugin-discovery cache work (#67040, #71690) which addresses
the same family of cold-start cost.
* feat(talk): add distinct system sounds for each Talk Mode phase
Play a short system sound on phase transitions to give the user
audible feedback:
- thinking: Tink
- speaking: Pop
- listening (after speech interrupted): Bottle
- listening (after thinking): Submarine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(talk): add right Shift key to interrupt Talk Mode speech
Add TalkSpeechInterruptMonitor — a dedicated global key monitor that
listens for right Shift (keyCode 60) to interrupt Talk Mode speech.
Independent of Push-to-Talk, so it works even when PTT is disabled.
Stops only the current response; the next conversation cycle
continues normally via sendAndSpeak's resumeListeningIfNeeded flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(talk): increase silence detection timeout for CJK locales
Korean, Japanese, and Chinese speakers need longer pauses between
phrases. When the app locale is CJK, enforce a minimum 2000ms
silence window (vs the default 1500ms) to avoid premature
transcript submission.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(talk): remove force-unwraps and log CJK silence clamp in reloadConfig
Replace non-idiomatic force-unwraps (cfg.voiceId!, cfg.modelId!) with
safe flatMap unwrapping, and add an info log when CJK locale clamps the
silence timeout so the override is observable in diagnostics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(talk): add settings toggle to mute phase-transition sounds
Add a "Play phase-transition sounds" checkbox to Voice Wake settings.
When disabled, Talk Mode phase transitions (Tink/Pop/Bottle/Submarine)
are silent. Defaults to enabled to preserve existing behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(talk): add toggle for Right Option speech interrupt
Add a "Press Right Option to stop speech" checkbox to Voice Wake
settings. Also change the interrupt key from right Shift to right
Option (keyCode 61) to avoid conflicts with typing.
Defaults to enabled to preserve existing behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(talk): disable Push-to-Talk while Talk Mode is active
Talk Mode and Push-to-Talk both use the right Option key (keyCode 61).
Disable PTT when Talk Mode is enabled to prevent conflicting handlers,
and restore PTT when Talk Mode is disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(talk): show info when PTT is paused during Talk Mode
Display a footnote under the Push-to-Talk toggle when both PTT and
Talk Mode are enabled, explaining that PTT is paused while Talk Mode
is active and resumes when Talk Mode is turned off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fixup: SwiftFormat lint on TalkModeController phase sound switch
Resolves macos-swift CI lint failures introduced by Korean
comment formatting in 'feat(talk): add distinct system sounds for each Talk Mode phase'.
- Collapse consecutive spaces between sound name and comment
- Move floating comments above the listening case expression so
they're at the correct indent level
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: hongsw <hongsw@hongswui-Macmini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Fabian Williams <fabian@adotob.com>
Pre-existing lint errors blocking the macos-swift CI check on every
PR that touches Swift code. Apply the wrapMultilineStatementBraces
rule by moving the opening brace of the multi-line if/else if to its
own line.
- apps/macos/Sources/OpenClaw/ExecAllowlistMatcher.swift:17
- apps/macos/Sources/OpenClaw/ExecApprovals.swift:621
Pure formatting change; no behavioral effect.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translate OpenClaw `mcp.servers.*.transport` entries into the downstream Claude/Gemini CLI `type` field before writing bundle MCP config.
Also keeps the plugin-sdk bundled-entry fast-path fixture unambiguously CommonJS on Node 24 after runtime-deps mirroring adds a `type: "module"` boundary.
Co-authored-by: Blockchain-Oracle <ajweb3dev@gmail.com>
Image generation and media understanding both thread the
sanitized models.providers.google.request config (including
allowPrivateNetwork) into resolveGoogleGenerativeAiHttpRequestConfig.
Speech synthesis omitted that arg, so TTS always saw
allowPrivateNetwork: false regardless of config — silently falling
back to a different speech provider when the configured Google TTS
endpoint resolved to a private/internal IP (proxies, custom backends,
test mocks).
Mirror the image-generation-provider pattern: thread request through
synthesizeGoogleTtsPcm at both call sites (synthesize and
synthesizeTelephony).
Follow-up to #67216.
* fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support
The existing guard (8fd15ed0e5) only skipped rewriting reasoning item IDs
when encrypted_content was a non-null string. When gpt-5.3-codex is used
via GitHub Copilot, the model falls through to the forward-compat catch-all
with reasoning:false, so encrypted_content is never requested and arrives
as null — bypassing the guard and causing a rewrite. Copilot validates
reasoning item IDs server-side regardless of whether the client includes
encrypted_content, so the rewritten id triggers the 400 error.
Two changes:
1. connection-bound-ids.ts: skip ALL reasoning items unconditionally.
Reasoning items always reference server-side state bound to their
original ID; rewriting any of them breaks Copilot's lookup.
2. models.ts + index.ts: extend the forward-compat cloning logic to
cover gpt-5.3-codex (adds it to the template-target set and to
CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source
for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for
the thinking profile.
Thanks @InvalidPandaa.
* docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself
https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm
* fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId
https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm
* fix(github-copilot): align reasoning id replay tests
* test(plugin-sdk): use cjs sidecar for require fast path
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
mushuiyu_xydt's commit 0e1ef93e84 (#61155) routes MiniMax image
generation requests to the dedicated image endpoint
(api.minimax.io/v1/image_generation), ignoring models.providers.minimax.baseUrl
(which targets the chat/Anthropic-compatible API), and adds
MINIMAX_API_HOST support for the CN api.minimaxi.com endpoint. The
CHANGELOG entry covered it but docs/providers/minimax.md image-generation
section did not. Add a paragraph naming both endpoints and the
MINIMAX_API_HOST override.