Commit Graph

2282 Commits

Author SHA1 Message Date
y$un_ 4ae1ce4c28 docs: declared local browser profiles must set cdpPort or cdpUrl (#129923)
The schema already rejects a hand-declared openclaw or clawd profile
that sets neither. Stop telling operators those fields auto-assign.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 06:27:30 -07:00
Peter Steinberger a82042f204 feat(crabbox): opt-in profile warm images for cloud workers (#130087)
* docs(plan): add worker hibernation design note

Evaluates dormant leases, checkpoint-based warm start, and serverless
backends for a worker-environment hibernation tier; proposes a plugin-only
phase 1 (Crabbox checkpoint fork warm images) with SDK/store phases gated
on maintainer acceptance.

* feat(crabbox): add opt-in profile warm images

* fix(crabbox): preserve warm image machine class

* refactor(crabbox): tighten warm-image seams after review

* fix(crabbox): budget warm-image capture for real snapshot round trips

The 60s command bound starved the scrub and checkpoint-create phases under
coordinator latency, silently degrading every teardown to cold-only.
Live-measured on AWS.

* docs(plan): record worker-hibernation acceptance and live proof

Phase 1 live-proven: local-container full loop (cold 101s vs warm fork 40s,
scrub boundary verified) and AWS cold provisioning with graceful degrade at
the coordinator admin gate; named upstream follow-ups.

* fix(crabbox): make warm-image lifecycle recovery-safe

Delete least-recently-used provider snapshots before image index admission.

Recover stale empty capture reservations without breaking single-flight.

Persist effective machine classes across restarts and revert heartbeat coupling.

Align the hibernation plan with the exact-class warm-image key policy.
2026-08-26 05:58:00 -07:00
Peter Steinberger 093166c7d9 feat(gateway): add renewable cooperative suspension draining (#130003)
* feat(gateway): add renewable suspension draining

* fix(gateway): satisfy drain ownership checks

* fix(gateway): retain pending question drain owner
2026-08-26 03:46:16 -07:00
Peter Steinberger c69abc2327 fix(sandbox): restore reliable OpenShell execution and workspace integrity (#130031)
* fix(sandbox): harden OpenShell lifecycle and workspace integrity

Fixes #127441, #127438, #98446, #119270, and #89606.

Co-authored-by: harjoth <harjoth.khara@gmail.com>

Co-authored-by: qingminlong <qing.minlong@xydigit.com>

* fix(agents): reject unresolved memory flush path aliases

* docs: keep OpenShell release notes in pull request

---------

Co-authored-by: qingminlong <qing.minlong@xydigit.com>
2026-08-26 03:10:07 -07:00
Peter Steinberger 03f33346c0 fix(gateway): fan out batched gmail hook pushes and bound gmail hook bodies (#130002)
* fix(gateway): fan out batched gmail hook pushes and bound gmail hook bodies

The built-in gmail hook preset rendered only messages[0] of a Pub/Sub push
that batches up to 100 messages, silently dropping the rest (#120277), and
the shared 256KiB hook body cap 413-wedged gog's history-rewind retry loop
on large batches (#120278).

Hook mappings gain an explicit forEach fan-out contract: one action per
payload array element, per-item replay identity derived from dispatch scope
so producer redeliveries converge instead of duplicating, and batch
responses bounded under gog's 10s hook client timeout. gmail-path mappings
inherit a body bound derived from the provisioned producer contract
(hooks.gmail.maxBytes x gog's 100-message batch).

* fix(gateway): guard null transform action before fan-out merge

* fix(gateway): admit fan-out hook items in the background

Live proof against the real gog producer exposed a livelock: eight
concurrent cold isolated-run workspace preps take ~15s, exceeding the 15s
admission deadline, so every fan-out item was canceled, nothing reached the
replay cache, and each Pub/Sub redelivery repeated the same cold burst
forever. Fan-out items now admit without the start deadline; the producer's
redelivery plus per-item replay identity own retry semantics, and direct
/hooks/agent dispatches keep the bounded 15s admission contract.

* test(gateway): adapt reload-handler hook assertions to actions[] shape

* test(gateway): split hook fan-out mapping tests into their own file

hooks-mapping.test.ts crossed the max-lines lint cap after the fan-out
coverage landed; the fan-out describe moves to hooks-mapping.fanout.test.ts.

* fix(gateway): harden fan-out identity, mixed action kinds, and body ceiling

Addresses the ClawSweeper P1 findings on #130002: within-batch identical
rendered items now get occurrence-numbered replay identities (both run,
redeliveries replay both); per-item transforms emitting mixed wake/agent
kinds dispatch every action through one unified loop instead of silently
dropping the non-first kind; and the gmail-derived body allowance is
capped at 32 MiB so an operator-set hooks.gmail.maxBytes cannot amplify
into an unbounded in-memory request buffer.
2026-08-26 03:00:41 -07:00
Peter Steinberger 8bf8e80e1d feat(cron): link chat notifications to Control UI run inspection (#130049)
* feat(cron): link chat notifications to Control UI run inspection

When gateway.publicOrigin is configured and the Control UI is enabled,
channel-delivered automation and task notifications append an
'Inspect: <url>' line so operators can click straight to the run record:

- cron failure alerts and command/script completion announcements link to
  /automations?job=<id>&run=cron:<id>:<startedAt> (appended after summary
  redaction so the redactor cannot strip the link)
- isolated agent run completions link to the run's exact session route
  (appended only after silent/NO_REPLY suppression, immutably, so a
  suppressed run cannot become a visible announcement)
- direct-channel task terminal notifications link to the task's child
  session; session-queued events stay link-free
- the automations page adopts ?job=&run= deep links (loader search pass-
  through, one-shot adoption after jobs load, History tab + run highlight)

No new config surface: the URL derives from the existing
gateway.publicOrigin + gateway.controlUi owner in control-ui-link-base.

* fix(cron): match linked runs by execution id and dedupe delivery runtime type

Live verification caught that ledger run ids are public run ids (receipt
UUIDs, manual:<...>) while notifications embed the execution id
(cron:<jobId>:<startedAtMs>), so the linked-run highlight never matched:
the automations page now resolves the execution id against the entry's
recorded runAtMs, with regression tests for both id spaces.

Also: move the isolated-delivery inspection-link append into the payload
normalization owner (keeps delivery-dispatch under the max-lines limit),
and replace the duplicate TaskRegistryDeliveryRuntime declaration in
task-registry.test-support with a type-only import of the canonical type
(the duplicate hid the new optional resolver from the tsgo test shard).
2026-08-26 02:59:51 -07:00
Peter Steinberger 68f17b01cc fix(gateway): permission modes work on rootless sessions via workspace default root (#129994)
* fix(gateway): permission modes work on rootless sessions via workspace default root

Session permission modes hard-required a recorded sessionRoot, which only the
sessions.create RPC ever records. Channel sessions, pre-#124909 rows, and
exec-node sessions are rootless, so runs failed closed with 'session permission
mode requires a recorded session root' (rows poisoned before the #128149 guards
were permanently bricked) and operators could not set a mode on channel
sessions at all.

The mode semantics are root-free; every containment consumer already falls
back to the workspace, and the remote worker launcher already derives its
containment root from placement.remoteWorkspaceDir. Make that the canonical
rule: a recorded sessionRoot pins worktree/explicit-cwd boundaries, and a
rootless session's boundary is the agent's canonical workspace resolved where
the run is prepared.

- embedded runner + compaction: default policy root to realpath(workspace)
  instead of throwing/dropping the mode; bricked rows self-heal
- gateway: delete the patch/create/reset rejection guards and
  session-permission-policy.ts entirely
- codex plugin: helpers take a required defaultRoot (agent workspace fact,
  never a requested thread cwd); delete the four root throws; sandbox context
  for conversation bindings now derives from the agent workspace
- ui: drop the rootless-rejection surface from the permission picker
- docs: permission-modes boundary contract updated

* fix(agents): split workspace path identity out of the kysely state store

Heals main: #116650 made memory-host-sdk/dreaming.ts import
resolveWorkspaceStateIdentity from workspace-state-store, statically pulling
kysely into the memory-core doctor-contract closure and failing the
doctor-contract-closure-guard lane on origin/main. The identity helpers are
pure path/crypto code; move them to workspace-state-identity.ts and repoint
all importers (dreaming, event-store, onboarding recommendations, workspace
migrations, legacy state).

* fix(ui): drop the unused permission picker sessionRoot prop and dead i18n keys

check-prod-types caught the New Session dialog still passing the removed
sessionRoot picker prop; the prop and the permissionControls.rootLabel /
sessionRoot strings have had no consumers since #124301.

* fix(codex): resolve rootless permission boundaries from agent config, not bind cwd

ClawSweeper P1: /codex bind --cwd stores the requested path as the binding
workspaceDir, which the previous commit then trusted as defaultRoot — letting
a bound thread's requested cwd become (and widen) the rootless permission
boundary. resolveConversationAppServerRuntime now owns the fact: it resolves
the selected agent's canonical workspace from config (falling back to the
plugin's configured default workspace), and the caller-supplied
agentWorkspaceDir parameter chain is deleted so no call site can pass a
tainted value. Recovery of recorded roots is unchanged (pinned roots stay
pinned); the moved-history test now proves the bind-cwd clamp lands on the
agent workspace.
2026-08-26 02:25:57 -07:00
Peter Steinberger 4708c0b607 feat(secrets): opt-in traffic allowlist for the secret egress proxy (#129880)
* feat(secrets): add opt-in traffic allowlist to the secret egress proxy

Add secrets.egressProxy.allowedHosts: when present, the egress proxy
refuses non-sentinel requests and CONNECT tunnels to hosts outside the
effective allowed set (configured list, hosts bound to the run's
registered secrets, and bypassHosts) with a typed host-not-allowed
refusal naming the remediation. Empty array is lockdown; omitting the
key keeps the previous unrestricted behavior. Sentinel substitution,
per-secret destination binding, proxy auth, and bypass tunnels are
unchanged; the allowlist is defense in depth for cooperating traffic
since bypass-surviving sentinels remain the primary defense.

* fix(secrets): validate egress proxy allowlist hostnames at the config boundary

Extract the exact-host contract from the secret store into a canonical
normalizeExactAllowedHost helper and validate secrets.egressProxy
allowedHosts and bypassHosts entries through it. Schemes, ports,
wildcards, and malformed hostnames are now rejected when config is
accepted instead of throwing during egress-proxy startup, which the
runtime normalizer did for both keys before this change.

* fix(doctor): repair unusable secret egress proxy host entries

Tightening the egress-proxy host schema can invalidate an existing
config: a disabled proxy with a malformed bypassHosts entry loaded fine
before and now fails validation, which exits the Gateway with code 78.

Add the matching doctor --fix migration. Invalid entries never
functioned (the proxy's own hostname normalizer threw on them at
startup), so they are dropped rather than canonicalized; enabled and
valid entries are preserved untouched.
2026-08-26 01:36:09 -07:00
Peter Steinberger 29966f4f70 fix(sandbox): stop exposing environment values in process listings (#129781)
* fix(sandbox): keep environment values out of process arguments

Route Docker and Podman environment values through private env files, and stage SSH and OpenShell environments separately from command argv. Container env-file values are now explicitly single-line.

* fix(sandbox): always clean staged SSH environment

Remove staged SSH and OpenShell exec scripts during finalization even when the local SSH process exits normally with a non-shell error code.

* test(openshell): align remote seed and staged exec fixtures

* fix(sandbox): validate container environment transport early

Reject Docker and Podman environment entries that secure env files cannot represent during config validation, with manual remediation guidance while preserving SSH and OpenShell multiline values.

* fix(config): keep sandbox env validation cold

Share the dependency-light scope and environment merge contract between runtime resolution and Docker/Podman config validation so isolated config and Doctor flows do not load runtime state.
2026-08-26 00:45:50 -07:00
Peter Steinberger 6a6635abb3 feat(daemon): support Bun 1.4 managed services (#129593)
* feat(daemon): support Bun 1.4 managed runtimes

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

* fix(daemon): enforce Bun SQLite safety

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

* fix(daemon): preserve lightweight Bun startup

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

* docs: show Bun runtime opt-in commands

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 00:45:43 -07:00
Peter Steinberger 7f7448e96d feat(gateway): identity-conditional forced sandboxing for operator roles (#129926)
One shared agent could not give different people different execution
isolation: sandbox selection saw only the agent's mode and the session key,
never who created the session. Teams that share an agent had to choose
between sandboxing everyone or no one.

Add a closed, optional role axis sandbox?: "inherit" | "required"
(default "inherit", so existing deployments are unchanged). The Gateway
resolves it from the authenticated session creator and stamps it write-once
onto genuinely new session entries; the agents layer reads that immutable
fact and forces a sandbox even when the agent config says mode: "off".

Anchoring on the creator keeps a session's isolation stable for its lifetime:
a maintainer replying inside a guest session cannot un-sandbox it, and a
caller whose role requires a sandbox is refused inside an unstamped session
rather than silently inheriting host execution. Reset, rewind, rollover and
fast-path all carry the requirement forward so a session cannot be reset out
of its sandbox.

Because exec injects the agent's managed GitHub identity only for gateway-host
execution and blanks GH_TOKEN/GITHUB_TOKEN otherwise, a forced-sandbox session
also loses access to the shared agent's credentials — the property that makes
one shared agent safe for untrusted guests.

Session entries are JSON; no SQLite schema, schema-version, or protocol change.
Docs also correct the sandbox backend list, which omitted the first-class
podman backend.
2026-08-26 00:18:50 -07:00
Peter Steinberger 6d1c1c9f57 refactor(cron): unify heartbeat automations under cron ownership (#129862)
* refactor(cron): unify heartbeat automations under cron ownership

* test(cron): align heartbeat automation fixtures and snapshots

* test(heartbeat): preserve rebased manual-wake coverage

* test(ui): scope mobile session actions to active chat pane
2026-08-26 00:08:18 -07:00
Peter Steinberger 2f17d11e90 fix(backup): preserve configured agent state roots (#129773)
* fix(backup): honor configured agent state roots

Inventory configured agent directories as durable backup owners regardless of layout. Reuse the owner inventory for archive planning, SQLite snapshotting, verification, restore safety, and Git/SQLite backup selection.

Add manifest-first plugin backup resource declarations so regenerable runtime roots stay plugin-owned without provider IDs in core.

Closes #129730

* fix(backup): preserve canonical ownership checks
2026-08-25 21:13:37 -07:00
Peter Steinberger 9ab6df9f19 fix(openshell): remote workspace is never seeded when the gateway restarts between sandbox create and first exec (#129809)
The remote-mode seed obligation lived only in process memory
(remoteSeedPending), so a gateway restart between sandbox create and the
first exec adopted the sandbox via 'sandbox get' and silently skipped the
one-time seed, leaving an empty remote-canonical workspace with no visible
failure.

Adopted remote sandboxes now probe the managed roots once per process at
the ensure boundary and arm the seed only when every root is missing or
empty, so recovery can never wipe operator content in an already-seeded
workspace (the seed guard shipped in v2026.7.1, so legacy seeded sandboxes
without any marker exist). The probe also heals a create that timed out
client-side after the sandbox actually came up.

Regression test simulates create -> restart -> first exec and fails
pre-fix; a sibling test locks the never-reseed-non-empty-roots invariant.
2026-08-25 20:32:40 -07:00
Peter Steinberger 422d6818e4 refactor(workers): unify cloud bootstrap lifecycle (#129719)
* refactor(workers): unify cloud bootstrap lifecycle

* fix(workers): preserve cloud setup and repair qa fixtures
2026-08-25 20:11:33 -07:00
Peter Steinberger 3d0df74b0f fix(openshell): sandbox cleanup, workspace sync, and gateway setup failures (#129641)
* fix(openshell): harden sandbox lifecycle and workspace sync

* fix(openshell): preserve shipped workspace root layouts

* test(gateway): reset shared agent harness state between cases

* test(qa): measure channel health startup grace directly

* chore(release): leave changelog updates to release automation
2026-08-25 18:08:55 -07:00
Peter Steinberger 91cc37b788 fix(gateway): stop fabricating scope-upgrade requests on trusted-proxy reconnects (#129563)
* fix(gateway): stop fabricating scope-upgrade requests on trusted-proxy reconnects

Same-key trusted-proxy reconnects whose auto-approvable scopes are already
covered by the paired grant now narrow the session in place instead of
running the pairing request/approve machinery: no pending-store writes and
no false 'device access upgrade requested' / 'auto-approved' security-audit
warns on every Control UI page load. The upgrade audit warn moved into
requirePairing so it fires only when a pairing request is materialized.
Durable widening (broadened deviceAutoApprove.scopes) and key-mismatch
repairs keep the existing lanes.

* fix(gateway): revalidate the live paired grant in the reconnect fast path

Address ClawSweeper P1: the narrowing decision now rereads the
authoritative paired row as the last await before returning, so a
concurrent revoke, key replacement, or grant reduction fails closed into
the pairing lane instead of riding the pre-plan snapshot. Extract the
pairing approval-plan resolver into connect-pairing-approval-plan.ts to
keep the authorization owner under the 700-line lint boundary.
2026-08-25 17:59:51 -07:00
Josh Avant ede9775941 fix(gateway): isolate portal cookies by lifetime (#129503) 2026-08-25 16:11:30 -07:00
Peter Steinberger 6a1dedb00c refactor(agents): remove redundant heartbeat system prompts (#129642)
* refactor(agents): remove redundant heartbeat system prompts

* test(agents): align heartbeat cleanup checks and prompt snapshots
2026-08-25 16:10:22 -07:00
Peter Steinberger 3626a8e18e fix(agents): enforce session permissions for shared terminal input (#129604)
* fix(agents): enforce session permissions for shared terminal input

* chore: keep terminal release notes in pull request

* fix(agents): fence full terminal input to active run authority
2026-08-25 15:59:28 -07:00
Peter Steinberger 9b7c95a0f0 fix(anthropic): keep automated heartbeats on subscription usage (#129513)
* fix(anthropic): keep automated heartbeats on subscription usage

* test(agents): align retired heartbeat template expectations

* fix(anthropic): avoid heavyweight policy bootstrap imports

* chore(config): prune retired template assertion baseline

* test(vitest): cover Codex startup test family

* refactor(anthropic): remove obsolete thinking-profile re-export
2026-08-25 14:11:38 -07:00
Sasan e9620fba9d fix(gateway): usage.status no longer waits on provider HTTP (#121799)
* fix(gateway): refresh provider usage asynchronously

* fix(ui): report a stalled provider-usage refresh on Model Providers

The page observed the incomplete-usage marker but discarded the exhausted
outcome, so once the retry budget was spent it rendered ordinary provider
cards with no usage and no explanation — indistinguishable from providers
that report no usage at all. Keep the outcome and render the warning the
Usage page already owns, reusing usage.providerUsage.stalled rather than
minting a Model Providers key so no locale baseline churns.

A user-initiated refresh now restarts the retry budget. The notice tells the
operator to refresh, so the button has to hand back attempts to spend; only
the forced path resets it, or the budget could never exhaust.

Also fixes tsgo:core:test on the current head: createStore's inferred literal
had no usageStats, so the run-bookkeeping case could not stamp it, and
view.test.ts needed the new prop.

Closes the ClawSweeper P2 at model-providers-page.ts:169-175.

* fix(ui): keep the stalled usage notice when usage.status starts rejecting

loadModelProvidersData turned a rejected usage.status into providerUsage:
null, which the page read as a completed load. observe(false) then reset the
retry budget and cleared the stalled callout, so a permanently broken usage
endpoint rendered as ordinary cards with no usage and no explanation — the
same silent failure the callout was added to prevent. The reset also fired
mid-cycle: one incomplete response followed by one rejection restarted the
budget, so the notice could be deferred indefinitely.

Record the failure at its producer instead of inferring it downstream. A null
providerUsage also means "not loaded yet", and no caller can tell the two
apart, so load.ts now reports providerUsageFailed explicitly and the page
treats a failed read as unresolved rather than resolved-empty.

Found by a Codex review of 417d43b65d.

* revert(gateway): drop the opportunistic model-catalog fast path

It broke two chat.history tests on main — both assert the cold catalog loader
runs exactly once, and reading the prepared snapshot first means it never does.
checks-node-compact-small-10 was red for that reason.

The change was a separate-surface latency fix that this PR picked up in passing,
and the body already offered to split it. Dropping it is the honest resolution:
rewriting main's assertions to accommodate a drive-by optimization would trade
one concern's proof for another's convenience. optional-model-catalog.ts,
server-model-catalog-auth.ts and their test return to the merge-base.

This PR is now only the usage.status non-blocking contract and its clients.

* fix(usage): preserve incomplete retry state

* perf(ui): keep usage capability startup-neutral

* fix(ui): restore provider usage retry convergence

* fix(usage): restore retry and cache invariants

* fix(usage): stabilize provider convergence

* test(ui): exercise provider recovery path

* test(ui): remove stale usage route fixture field

* fix(macos): show provider usage errors

* fix(macos): bound usage retries per menu open

* fix(macos): end usage retries on menu close

---------

Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
2026-08-25 13:20:10 -07:00
Ayaan Zaidi 750a64e7cd fix(anthropic): keep Claude CLI authentication native (#129052)
Stop OpenClaw from copying or refreshing Claude CLI OAuth tokens.
Claude CLI now owns native login and refresh state; Doctor removes retired copies while preserving CLI routing.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-25 17:18:06 +05:30
Peter Steinberger fda20a6ed2 fix(systemd): honor effective drop-in configuration (#129170)
* fix(systemd): honor effective drop-in configuration

Read one coherent manager-loaded service snapshot while retaining the managed base definition for repairs. Track drop-in ownership, surface pending daemon reloads, and keep update and doctor rewrites from adopting operator-owned values.

Closes #128929

* fix(systemd): expose reload status in service snapshot

* test(systemd): cover Linux doctor ownership
2026-08-25 02:30:34 -07:00
Peter Steinberger 24898ba894 fix(workers): repair cloud session lifecycle and recovery (#121564)
* fix(workers): repair cloud session lifecycle and recovery

* test(workers): preserve dual-mode provider dispatch coverage

* refactor(workers): centralize inherited profile authorization
2026-08-25 01:48:57 -07:00
Peter Steinberger 6885eb69b0 fix(telemetry): suppress reporting from automated environments (#129155)
CI jobs are not installs; unchecked they outnumber operators by orders of
magnitude and make version and platform counts meaningless. A configured
telemetry endpoint still reports, so update-path E2E lanes keep working.
2026-08-25 01:37:48 -07:00
RoboClaw 0415c403a7 improve: use agent identity as the sole assistant identity (#129067)
* refactor(config): remove duplicate assistant identity

* test(config): isolate assistant migration coverage

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-25 00:21:13 -07:00
Peter Steinberger 8a1ff3966b feat(workers): run OpenClaw and Codex on the same cloud profile (#127752)
* feat(workers): run both harnesses on one cloud node profile

* docs(workers): explain shared OpenClaw and Codex cloud profiles

* fix(workers): preserve trusted Codex cloud enrollment

* docs(codex): clarify bundled cloud plugin eligibility

* fix(workers): honor Crabbox bootstrap retries

* fix(workers): allow cloud profile setup to finish

* fix(workers): reject unsupported cloud execution modes

* refactor(workers): split cloud placement modules

* fix(workers): preserve completed container results
2026-08-25 00:13:29 -07:00
Vincent Koc df83ff516f fix(agents): apply tool policy to Anthropic native calls (#128805)
* fix(agents): enforce native policy in Anthropic SDK

* fix(agents): enforce canonical native tool policies safely

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-24 23:58:23 -07:00
Peter Steinberger e9017714c2 fix(hooks): avoid false admission timeouts during runtime reload (#128975) 2026-08-24 22:12:54 -07:00
Peter Steinberger 2d7b1d21d3 fix: keep verbose health available when channel probes stall (#128915)
* fix(gateway): bound live health aggregation

Keep verbose health within a Gateway-owned deadline, preserve healthy sibling results when channel probes stall, and document that the CLI timeout controls the Gateway connection.

Fixes #128890

* fix(gateway): retain health probe permits

Keep process-wide health capacity occupied until timed-out plugin work actually settles, and prove repeated refreshes cannot replace five stalled probes.
2026-08-24 19:44:18 -07:00
Josh Avant 2e9c8f8d3f Audit exact-bound owner-native lifecycle receipts (#126082)
* feat(audit): add owner-native lifecycle receipts

* fix(audit): store lifecycle bindings additively

* test(cron): isolate execution binding fixtures

* fix(audit): reject stale lifecycle owners

* fix(acp): gate parent flow binding on retained task

* fix(audit): reject pruned owner cursors
2026-08-24 17:49:25 -07:00
Peter Steinberger 33b10fe4aa fix(macos): user accent from Control UI is clobbered by config snapshots and never live-updates the chat window (#128703)
* fix(macos): honor user accent precedence in config snapshot and live-update chat window

The Control UI user accent (ui.prefs.accent) landed in #128432/#128577 with
precedence user accent -> operator ui.seamColor -> theme default, and the
gateway's talk.config payload already applies it. The macOS app had two gaps:

- ChannelsStore.applyUIConfig read raw ui.seamColor from the config.get
  snapshot and clobbered the user accent set from talk.config depending on
  arrival order. It now resolves ui.prefs.accent ?? ui.seamColor via a
  testable helper mirroring the gateway precedence.
- The native chat window read AppStateStore.seamColorHex once at window
  construction, so accent changes never live-updated. MacChatSurface now
  reads the @Observable store in body, deleting the one-shot userAccent
  plumbing.

Docs: configuration-reference.md documents the precedence for native-app
chrome. Regression test fails pre-fix (snapshot returned the operator seam
color instead of the user accent).

* fix(macos): refresh config from gateway config.changed events

Addresses the review finding that no macOS consumer turned the gateway's
hash-only config.changed broadcast into refreshed shared state, so a
Control UI accent change never reached an open native chat window while
the app ran. ChannelsStore now subscribes to gateway pushes and re-fetches
config.get on config.changed, reconnect snapshots, and sequence gaps.

The refresh applies non-force so an in-progress local settings draft wins
(the gateway rejects stale-hash writes anyway). The in-flight reload queue
gains a closed pending level (none/refresh/force) so a refresh arriving
during a load is coalesced instead of dropped, and a requeued refresh
cannot clobber a dirty draft the way the old boolean force-pending did.
2026-08-24 05:37:59 -07:00
Peter Steinberger 4c48c13ab6 fix: surface cloud worker enrollment rejection evidence (#128632) 2026-08-24 02:32:24 -07:00
Peter Steinberger 12d0fd2ef8 refactor(anthropic): replace handwritten Claude sessions with Agent SDK (#128131)
* refactor(anthropic): explore official Claude Agent SDK runtime

* refactor(anthropic): replace handwritten Claude sessions with SDK

* refactor(anthropic): collapse SDK live-session ownership

* refactor(anthropic): simplify SDK ownership and preserve live skills

* fix(anthropic): fence cancelled SDK runs before process startup

* fix(anthropic): harden SDK approvals, lifecycle, and packaging

* refactor(anthropic): own SDK process trees and streamline runtime

* fix(anthropic): repair rebased packaging and legacy test fixtures
2026-08-24 01:59:16 -07:00
Peter Steinberger 53dcaaedec feat(gateway): generic operator roles for non-maintainer access (#128548)
* feat(gateway): generic operator roles for non-maintainer access

Adds gateway.roles: named role bundles over a closed capability set —
sessions.others (none/view/suggest/write), an agents allowlist, and an
operator-scope ceiling. Roles are person-level (additive user_profiles.role
column, SQLite stays at v9); users.setRole (admin-only) assigns them. With
no gateway.roles config, behavior is unchanged for solo deployments.

Enforcement is deny-by-default from a host-minted actor identity
(system vs operator+profileId on server-only client.internal, never
accepted from the wire) and covers every entry point: WS RPCs, OpenAI-compat
and Responses HTTP, tools invocation, cron, questions, usage, task
suggestions, session catalog/sharing/reads. The agents allowlist gates both
session creation and run-start on existing sessions. Subagent completion
announce and descendant wake mint explicit system authority so role
boundaries never silently drop parent notifications.

The enforcement surface is expressed through a narrow policy vocabulary
(operatorSessionCap, hasOperatorBoundary, authorizeSessionSharing) rather
than per-handler policy internals.

* fix(gateway): heal PR CI after rebase onto main

- Break import cycles: extract GatewayOperatorRoleActor leaf contract; merge
  session-group-mutation-targets into session-sharing-target-input.
- Split sessions-suggestions.test.ts (max-lines) into a visibility suite.
- Add users.setRole to the 2026.8 train registry test and regenerate the
  Kotlin protocol client.
- Startup UNAVAILABLE gating now precedes session authorization: session
  stores are not loaded during startup, so authorization reads would deny
  with a misleading non-retryable error.
- sessions.assignOwner keeps its documented visibility-authorized contract
  when no operator role caps the caller; view/suggest-capped roles still
  cannot reassign foreign session ownership.
- Test stubs updated for main's socket readyState guard (#128144) and the
  system-authority arg on channel-native resets.

* test(gateway): chat.send pending-profile dispatch carries its required session target

chat.send requires a non-empty sessionKey at the protocol level; the mutation
pipeline now rejects targetless frames before profile-dependent dispatch, so
the pending-profile test must send a realistic frame.
2026-08-24 01:20:03 -07:00
Peter Steinberger 4ac8cd3dad fix(telemetry): report only publicly known plugin identities (#128603) 2026-08-24 01:06:39 -07:00
Ayaan Gazali 834fc445cc docs(channels): scope implicitMentions overrides to the channels that read them (#119320)
* docs(channels): scope implicitMentions overrides to the channels that read them

Only Mattermost, Slack, and Tlon call resolveChannelImplicitMentions, and they
are the only channels whose schemas accept the key. The other documented
producers pass no policy, so allowedImplicitMentionKinds stays undefined and
every produced fact counts as a mention.

* docs(channels): correct implicitMentions override wording

QQBot is a listed reply-to-bot producer with a passthrough schema, so the key
is accepted and ignored there rather than rejected. Also drops 'yet', which
implied a planned rollout while #80234 is still an open product decision.

* docs: limit implicit mention claims to bundled channels

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-24 00:13:35 -07:00
Peter Steinberger 6417bcb7b1 docs(telemetry): lead with what is on by default and thank opt-in users (#128491) 2026-08-23 21:39:10 -07:00
Ayaan Gazali 4f8f050f5b docs(whatsapp): document the canonical ack reaction keys (#118161)
* docs(channels): record the WhatsApp ack-reaction exceptions

The shared reference promises channel and account ackReaction overrides plus an
identity fallback for every channel, but only Discord, Matrix, Slack, and
Telegram accept those keys, and WhatsApp sends no acknowledgment at all when
messages.ackReaction is unset. Also notes that group activation always bypasses
the group-mentions mention check.

* docs(whatsapp): qualify the ack-reaction exception

The eligibility path reads the account-aware channels.whatsapp.reactionLevel
before the message settings, so "only" applies to the emoji and scope rather
than to the whole decision.

* docs(whatsapp): describe supported ack reaction overrides precisely

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-23 21:31:00 -07:00
Peter Steinberger 4d617f44f3 feat(telemetry): opt-in anonymous usage stats on a daily update ping (#128476)
* feat(telemetry): add opt-in anonymous usage reporting

* docs(telemetry): document anonymous update and privacy controls

* feat(telemetry): report show state as JSON

Give the reporting command a machine-readable form and classify the
group and mutations against the CLI JSON-output policy.
2026-08-23 21:18:04 -07:00
Peter Steinberger 0978d55b05 feat(nodes): automatic device placement for sessions.dispatch (#128421)
* feat(nodes): automatic device placement for sessions.dispatch

sessions.dispatch gains autoDevice: true — the gateway selects the eligible
session-host node with the most available worker slots (deterministic
tie-break), retries up to three candidates when a node churns at the
pre-provisioning eligibility fence, and reports the chosen device in the
placement runner projection. Control UI offers Any available node with
actionable disabled reasons. No-eligible-host failures state why.

* fix(ui): break draft-place-state/draft-session-placement import cycle

resolveDraftSessionPlacement only needs four scalar fields; a structural
param type replaces the Pick<DraftPlaceState,...> import that created the
madge cycle.

* fix(ui): keep the devices section hidden when no devices are paired

The Any available node row lives inside the Your devices section; rendering
it with zero paired devices resurrected the section on gateway-only setups.
Gate it on device presence — Connect a machine remains the discoverability
path — and cover both the empty and non-hostable cases.

* fix(gateway): project dispatch runner state through the canonical reader

The dispatch reply no longer synthesizes an available device runner; it uses
the fenced workerPlacementRunnerAvailabilityReader (and disk-space reader)
exactly like session reads, so a node lost after durable provisioning
projects offline consistently. Documents placement.runner.deviceId in the
protocol reference.
2026-08-23 21:08:33 -07:00
Peter Steinberger 2aa5eee34e feat(channels): custom emoji discovery via emoji-list across Discord, Slack, Telegram (#128435)
* feat(channels): custom emoji discovery via emoji-list across Discord, Slack, Telegram

Make custom emojis discoverable by the agent. The message tool's emoji
param now documents custom-emoji syntax per channel (gate-aware, only
naming emoji-list when the action is actually advertised). Discord
emoji-list defaults guildId from the current conversation and returns
reaction-ready { name, identifier, animated? } entries; Slack returns
normalized shortcodes with aliasOf. Telegram gains emoji-list backed by
one canonical allowed-reactions owner (getChat available_reactions,
custom_emoji entries preserved), numeric custom-emoji reactions, and
replaces the dead 'reaction disallow list' error advice with a bounded
sample of the chat's allowed reactions.

* test(channels): expect telegram emoji-list provider-owned read gate in plugin shape contract

* test(telegram): prove emoji-list authority chain via mock-gateway e2e

Ephemeral gateway + mock Bot API + mock OpenAI provider: current-chat
emoji-list returns normalized standard and custom_emoji identifiers with
exactly one getChat call; a delegated cross-chat request is rejected with
the conversation-binding error and zero Bot API requests reference the
foreign chat.
2026-08-23 20:39:06 -07:00
Vincent Koc b6798cbb10 docs(slack): use canonical postAs key (#128448) 2026-08-23 18:37:49 -07:00
Mitra Kermanian d710cbd8be docs: fix four broken cross-page anchor links (#128278)
* docs: fix five broken cross-page anchor links

Five internal doc links pointed at anchors that do not exist on the target
page, so readers following them landed at the top of the page instead of the
referenced section.

- /gateway/cloud-workers#desktop -> #desktop-interactive
  (heading is "## Desktop (interactive)"), from experimental-features and
  the gateway protocol reference.
- /web/urls#special-documents-and-startup-modes ->
  #other-special-documents-and-startup-modes
  (heading is "## Other special documents and startup modes"), twice from
  the Control UI page.
- /plugins/sdk-runtime#api-runtime-agent -> #runtime-namespaces
  "api.runtime.agent" is an <Accordion> title, not a heading, so it emits no
  anchor; the enclosing "## Runtime namespaces" heading is the real target.

Sibling links on all three target pages already use heading-derived anchors
(#coordinator-backed-crabbox, #route-table, #reusable-runtime-utilities),
and this was the only link in docs/** aiming at an Accordion title out of
1342 such titles.

Docs-only change; no runtime or config surface is touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: restore the sdk-entrypoints deep link

Running the real Mintlify anchor audit (pnpm docs:check-links:anchors) showed
that /plugins/sdk-runtime#api-runtime-agent already resolves: Mintlify does
generate anchors from <Accordion title="..."> elements, so the original link
was correct.

The audit flags fragments it cannot resolve -- it reported both
cloud-workers#desktop and urls#special-documents-and-startup-modes on main --
and it never reported this one. Retargeting it to #runtime-namespaces would
have replaced a precise deep link with a coarser section link, so this restores
the original.

The remaining four link instances in this PR are confirmed broken by the same
audit: main reports 313 broken links across 45 files, this branch reports 309
across 42, with no newly broken links.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 16:31:01 -07:00
Peter Steinberger c48e973c66 fix(sessions): keep admin events out of model context (#128346) 2026-08-23 14:09:39 -07:00
Vincent Koc 9046ecea73 fix(talk): isolate cancellation ownership contract (#127186)
* fix(talk): isolate cancellation ownership contract

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): frame realtime relay output audio

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): confirm turn-bound provider cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): bind legacy iOS output cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): close cancellation ownership gaps

Drain dynamically arriving forced-consult results before terminal completion and validate legacy iOS cancellation responses against the active lifecycle.

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): require Android output identity for cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): bind Android cancellation to action turn

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* test(talk): align relay checks with current main

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): fence stale iOS output clear

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

---------

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>
2026-08-23 03:58:00 -07:00
Jason O'Neal 7afbb7072a fix(heartbeat): deliver exec completions with disabled cadence (#126895)
With agents.defaults.heartbeat.every set to "0m", a background exec completion queued its system event and requested a targeted exec-event wake, but the shared unscheduled-wake policy only admitted immediate wakes, so the follow-up agent turn silently never ran. Admit the exact exec-event/event producer shape (session or agent target required) in one per-source predicate, dispatch wake timers outside the requesting attempt's transcript-writer context, and document that "0m" disables recurring cadence only.

Closes #62505

Co-authored-by: Jason O'Neal <jason.allen.oneal@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-23 09:40:18 +05:30
Josh Avant 40a6b50cc1 feat(audit): explain plugin and remote actions (#126027)
* fix(audit): converge action receipts after U2

* docs(agents): clarify receipt authority fencing
2026-08-22 14:06:54 -07:00
Peter Steinberger 224d11f222 feat(ui): environment differentiation for the Control UI (gateway.controlUi.environment) (#127711)
* feat(ui): differentiate gateway environments in Control UI

* fix(ui): guard sidebar environment read for partial session-data hosts

* fix(ui): clear environment presentation when refreshed config is unset
2026-08-22 14:06:28 -07:00