Commit Graph

527 Commits

Author SHA1 Message Date
Peter Steinberger 75fcaba919 fix(voice-call): survive gateway in-process restart and stop CLI dead-ends (#125458)
* fix(voice-call): survive gateway in-process restart and stop CLI dead-ends

The gateway's in-process restart (SIGUSR1 config reload) reuses the cached
plugin registry, so service stop/start run on the same retained voice-call
registration. Generation fencing from #120289 treated that restart as a stale
actor: stop retired the generation forever, the next start silently bailed,
and every voicecall.* RPC answered UNAVAILABLE "runtime generation is
retired" while the webhook never rebound.

- Registrations now hold a replaceable generation: service start after stop
  mints a fresh generation, takes over a running slot owned by a retired
  predecessor, and reports start failures to service health instead of
  silently returning.
- The voicecall CLI classifies gateway failures with typed guards instead of
  message substrings: standalone/store fallback only when the gateway is
  genuinely absent; reachable-but-failed (request errors, auth, timeout)
  exits with actionable text; a standalone webhook port collision explains
  that a running Gateway probably owns the port instead of raw EADDRINUSE.
- Plugin SDK gateway-runtime exports structural isGatewayTransportError /
  isGatewayClientRequestError guards (+2 documented surface budget).
- Regression coverage: same-registration stop/start restart, retired-owner
  takeover, typed CLI fallback classification, and a real token-auth gateway
  server routing voicecall.status through callGatewayFromCli.

* refactor(voice-call): split CLI modules and dedupe gateway fallbacks

Collapse the four duplicated gateway-or-runtime command blocks (speak, dtmf,
end, continue fallback) into one generic runGatewayManagerCommand helper —
the continue command owns its legacy-method fallback and operation polling
via a gatewayCall closure, so the helper carries no per-command policy.
Smoke reuses the shared initiateVoiceCall path instead of a bespoke
fallback.

Split the 988-line cli.ts into concept modules (cli-gateway-call,
cli-call-log, cli-command-io) and drop its grandfathered max-lines
suppression plus the now-stale max-lines and assertion-safety baseline
entries (shrink-only ratchet maintenance).

Behavior-frozen: stdout/exit semantics unchanged; net -2 production LOC.

* fix(voice-call): redact gateway URLs in CLI operational errors

ClawSweeper P1: the operational-error formatter interpolated the raw
connectionDetails.url, so a configured gateway URL with userinfo or query
tokens would print credentials into terminal output. Redact the composed
message once with the canonical net-policy redactor (also covers
remote-controlled close-reason text), exported through the plugin SDK
gateway-runtime subpath (+1 documented surface budget). Regression test
covers a credential-bearing URL in both the URL and message fields.
2026-08-17 18:59:19 -07:00
Peter Steinberger b5b17c654b fix: keep Workboard visible in the sidebar (#125473)
* fix(ui): keep workboard in sidebar navigation

* test(ui): align workboard navigation ownership

* fix(ui): preserve saved workboard sidebar slots

* test(ui): shrink assertion safety baseline

* fix(plugins): bind native routes to bundled owners

* refactor(plugins): isolate native route policy
2026-08-17 18:44:39 -07:00
Peter Steinberger bb1ce58514 refactor(gateway): remove obsolete reverse worker tunnel (#125465)
* refactor(gateway): remove reverse worker tunnel

* test(gateway): align worker transport expectations

* docs(gateway): clarify cloud worker ingress
2026-08-17 18:19:52 -07:00
Peter Steinberger 720da745d9 fix(voice-call): keep realtime calls alive through brief stream reconnects (#125469)
* fix(voice-call): grace realtime stream disconnects

Share reconnect grace by CallSid and stream ID across classic and realtime streams while cleaning realtime bridge resources immediately.

Log terminal call reasons and document Twilio inbound voice and status callback setup.

* test(voice-call): align reconnect grace after rebase

Preserve the newly landed realtime generation and inactivity coverage while updating its terminal expectations for shared delayed finalization.

Remove the redundant replacement cross-product case so the lifecycle suite remains below the max-lines limit.
2026-08-17 17:51:46 -07:00
Peter Steinberger e3de722971 refactor(tooling): consolidate baseline ratchets (#125459) 2026-08-17 17:17:49 -07:00
Peter Steinberger e169520fef fix: surface swallowed failures on action paths (#125319)
* fix: surface swallowed failures on action paths

* fix(memory): propagate directory traversal failures
2026-08-17 17:14:05 -07:00
Peter Steinberger 3cc55589e3 fix(agents): stop silent compaction failures (#125302)
* fix(agents): stop silent compaction failures

* fix(agents): preserve aborted compaction outcomes

* fix(agents): preserve manual abort outcomes
2026-08-17 17:07:11 -07:00
Peter Steinberger aeee426180 feat(control-ui): persistent Ask OpenClaw companion with global toggle (#125107)
* feat(control-ui): persistent Ask OpenClaw companion with global toggle

The custodian surface now behaves like the persistent machine-wide agent it
already is on the Gateway: the session id persists in localStorage so a
reopened surface rebinds to the live engine (wizard and approval state
survive close/reopen), the durable transcript is refetched when a surface
opens or the gateway reconnects (idle-gated so active question/wizard cards
are never clobbered), and the panel toggles from anywhere via the shared
panel-toggle contract, a command-palette action, and an admin-gated lobster
chrome button.

One server-side line: the openclaw.chat owner-mismatch rejection now carries
the existing structured session-invalidated details so persisted clients
re-mint their id from a closed code instead of matching error prose.

No gateway events, no protocol schema changes, no polling. Splits
(session-identity/variant modules, session-lifecycle and panel-toggle test
files) keep the touched files under the max-lines ratchet.

* fix(control-ui): coerce custodian toggle detail without a type assertion

The assertion-safety ratchet holds custodian-panel.ts at zero uncommented
assertions; parse the toggle CustomEvent detail through the canonical
record-coerce guard and literal narrowing instead of casting.

* fix(control-ui): delete unused CustodianPanelToggleDetail export

The record-coerce toggle parsing left the exported type without a
production consumer; the deadcode gate rightly flags it. The palette test
keeps a local shape.

* test(control-ui): select the palette custodian item via keyboard

Async session-search results can reflow the palette list mid-click on slow
CI runners, silently dropping the positional click; keyboard selection of
the asserted-active item is atomic against reflow. Also stage the reopen
wait (panel section, then text) for sharper failure localization.

* fix(control-ui): project live wizard state on rejoin and scope-gate the toggles

Address both ClawSweeper P1 findings. The welcome-only rejoin of an
existing session now routes through engine.decorateRejoinReply (the
existing ChatWizardHost projection), so a reconnecting client re-renders
the live wizard/question controls the session still awaits; the stale
welcome question only fills in when no interaction is live. The chrome
button, palette action, and deferred panel loading now use the
scope-aware canCallGatewayMethod gate (operator.admin) that the session
store already used, so advertised-but-read-scoped clients see nothing.

* test(control-ui): fix the cloud-workers e2e flake at both roots

The mocked config.get stayed frozen at the empty initial config while
patch responses advanced, so a config-store reconciliation refetch could
flap the snapshot to empty and saveProfile silently dropped the next
save; the mock now stays consistent before each patch resolution. Also
give waitForRequest an opt-in after-cursor: it is satisfied by any prior
same-method request and returns the latest match, so a second wait could
assert against the stale earlier request; the cloud-workers waits pin it
(15x green locally, previously failing 1-in-3).

* fix(ci): cover rejoin projection in sibling engine mocks; bump startup baseline

The greeting-welcome and session-ownership suites' engine mocks now
export decorateRejoinReply like the handler requires. The Control UI
startup-JS baseline moves 337511 -> 338920 B via the documented update
command: the shell chrome toggle, palette action, and scope-aware gating
are genuine startup surface (~1.4 KiB gzip, within the committed
ceiling).

* fix(control-ui): settle interrupted structured replies and racing turns on rejoin

Address both ClawSweeper reconnect P1s. A submitted question/wizard reply
with an unknown outcome now triggers a full session rejoin on reconnect
instead of being blocked by its own uncertainty flag: the Gateway projects
whether the answer was consumed and which control is live. A restored
persisted id also arms a one-shot rejoin barrier: the welcome-only request
queues behind any in-flight turn on the Gateway's per-session queue, so a
post-response history refresh deterministically surfaces rows a racing
turn persisted after the initial fetch. The open-agent handoff moved to
custodian-navigation (its owner) to keep the store under the size cap.
Live-Gateway proof (isolated state dir, real gpt-5.6-luna turns): video
and screenshots on the PR.

* test(control-ui): reopen via the chrome toggle in the custodian e2e

The palette click-through composition proved timing-flaky on loaded CI
runners in three different ways while adding no coverage: the palette
action's dispatch is pinned by the palette unit test and the event-opens-
panel path by the chrome-toggle step. Keep the gated palette entry
assertion + screenshot; reopen through the chrome path.

* fix(control-ui): keep the agent-handoff path helper module-local

The store now routes through performCustodianAgentHandoff, leaving the
path builder without external callers; the deadcode gate rightly flags
the export.

* fix(control-ui): run the rejoin barrier even when a live control projects

The racing-history refresh happens before the reply/control message is
appended, so skipping it for projected wizard/question rejoins had no
purpose and lost rows a turn persisted while the page was closed mid-
wizard. Regression covers the live-step rejoin reconciling racing rows.
2026-08-17 16:28:43 -07:00
Peter Steinberger 7170a6231a feat(agents): unify agent status into a durable progress_card (#125125)
* feat(agents): unify agent status into a durable progress_card

Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.

Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.

Net -277 production LOC; -480 test LOC.

* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable

* chore(protocol): allowlist progressCard.changed for native apps pending card migration

* fix(ci): repair progress card integration checks

* fix(codex): canonicalize native progress cards

* test(gateway): reconcile progress card method order

* test(codex): stabilize native approval fixture
2026-08-17 09:44:04 -07:00
Peter Steinberger 1298adfca7 fix(cron): distinguish missing automations from empty run history (#125343) 2026-08-17 09:34:38 -07:00
Peter Steinberger 1a821db049 fix(sessions): batch doctor SQLite imports (#125300)
Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

Co-authored-by: Amp <amp@ampcode.com>
2026-08-17 09:33:30 -07:00
Peter Steinberger bc277fa095 fix(qa): reject inconsistent suite summaries (#125122)
Amp-Thread-ID: https://ampcode.com/threads/T-01a00b7c-b9f0-73d5-8fb7-e619e8e458e1

Co-authored-by: Amp <amp@ampcode.com>
2026-08-17 06:19:25 -07:00
Peter Steinberger 08622cd766 fix(ui): keep streamed replies ordered across steering (#125197)
* fix(ui): preserve steering stream order

Record exact steering provenance and keep cumulative assistant output on its causal side of persisted user turns across live streaming, terminal events, tool boundaries, and history reloads.

* chore(ci): refresh post-rebase gates

Tighten the inherited environment-variable budget and remove a stale test import exposed by the rebased lint gate.

* fix(ui): preserve terminal steer stream segment

Assert the causal stream rollover when a steer lands and move persisted split-layout normalization off the startup path to keep the Control UI bundle within budget.

* refactor(ui): extract split layout types

Keep the persisted split-layout normalizer off the interactive module cycle while preserving the Control UI startup bundle reduction.

* fix(gateway): resolve steering provenance at injection

* fix(gateway): confirm steering provenance after persistence

* refactor(ui): move live tool filtering to identity owner

* fix(ui): preserve queued user stream ceiling
2026-08-17 06:00:40 -07:00
Peter Steinberger 4b0d573437 feat: session permission modes with worktree-scoped defaults (#124909)
* feat(gateway): add session permission modes

* feat(agents): enforce session permission policies

* test: cover session permission modes

* docs: explain session permission modes

* fix(agents): preserve apply patch default root

* feat(codex): map session permission modes

* feat(ui): add session permission controls

* test: cover session permission surfaces

* fix(agents): enforce session permissions from entry

* fix(codex): pass params object to requirements reader

* fix(codex): honor session permission modes

* fix(codex): preserve exec approval floors

* chore: revert changelog edit (release-owned)

* test: align full-mode promotion with upstream semantics

* chore(protocol): regenerate session permission models

* docs: register session permission modes

* fix(ui): use shared permission dropdown

* fix(sessions): restore permission mode contracts

* fix(ci): record session permission startup JS growth in the budget baseline

* refactor(agents): prepare session exec mode at dispatch

Host dispatch owns permissionMode→exec override preparation; the codex
plugin consumes prepared facts and the SDK barrel export is removed,
keeping the plugin-sdk surface at its recorded budget.

* test: type exec mode fixtures as ExecMode

* fix(codex): require admin scope for yolo permissions

* test: isolate codex permission command state

* ci: refresh PR checks against current main

* ci: refresh checks after gateway test repair

* test: fix main-fallout CI suites

* chore: refresh CI merge ref

* fix(worker): fence permission-mode cloud placements

* fix(sessions): clear worktree policy on detach

* chore(protocol): generate session permission mode enum

* test(gateway): type dispatch fixtures from session entries

* fix(ui): allow clearing session permission mode

* chore: refresh CI merge ref

* fix(ci): record permission picker startup growth in the budget baseline

* fix(gateway): gate session root projections
2026-08-17 04:37:35 -07:00
Peter Steinberger 7efd0f0feb fix(ui): refresh startup budget baseline (#125200)
Amp-Thread-ID: https://ampcode.com/threads/T-01a00f1b-89ce-727e-8bab-bb3f12f9458f

Co-authored-by: Amp <amp@ampcode.com>
2026-08-17 03:01:50 -07:00
Peter Steinberger 55ce95fac8 feat(sessions): three-layer session ownership — agent attribution, assignable owner, participants (#125057)
* feat(sessions): stamp agent identity on spawned sessions and return spawn receipts

Agent-spawned sessions recorded the requesting session key as createdActor.id,
so the Control UI creator chip rendered an opaque key. Spawn producers now stamp
the canonical requester agent id; parent-authority validation moves to a new
trusted requesterSessionKey field. projectSessionActor enriches agent actors
with configured identity name/avatar at read time, and visible sessions_spawn
returns a sessionUrl + owner receipt with URL-first acknowledgement guidance.

* feat(sessions): assignable session ownership with owner facet and menus

GitHub-assignee-style ownership: sessions get a mutable owner (defaulting to
the immutable createdActor) stored in additive bare-nullable SQLite columns
with first-use lazy ensure. New operator.write sessions.assignOwner validates
targets, requires an identified caller, authorizes by session visibility, and
records assignedBy/assignedAt inside the write transaction. The sessions agent
tool gains assign_owner; the Control UI adds Assign-to-me/Assign-to menus in
sidebar rows and chat headers, renders the effective owner chip, and the
creator facet/filter now keys on effective owner. Sharing authority stays
anchored on createdActor.

* feat(sessions): record session participants and stack them in the owner chip

Records every distinct external prompter (human profile/channel sender, or a
requesting agent) per session in an additive session_participants table at the
turn-admission boundary — best-effort, deferred, never blocking the turn; the
session's own agent and viewers are never recorded, capped at 32 per session.
The session row projects a bounded participants list (owner excluded) plus a
total count with the same actor enrichment as owner/createdActor. The sidebar
chip becomes a pair-stack when others have prompted (owner front, one peeking
participant or +N behind), the chat header shows the full facepile, and an
authenticated involvingMe list filter adds an Involving-me sidebar predicate.
Participant projection is excluded from logical-session CAS equality so display
history never invalidates session writes.

* fix(sessions): identify built-in agent tool callers for owner assignment

The sessions tool's assign_owner dispatched through the in-process synthetic
client, which carries neither a signed agent-runtime identity nor a human
profile, so agent-initiated reassignment always failed with FORBIDDEN. The
tool now captures its trusted requester agent identity and carries it across
in-process dispatch as internal client state (never wire params); the handler
derives assignedBy as signed runtime identity, then trusted agent-tool caller,
then authenticated human. Live-verified end-to-end on a dev gateway.

* fix(ci): split oversized session modules and refresh prompt snapshots

Split the max-lines offenders at concept boundaries for session equality, tool overrides, and protocol owner schemas. Remove the redundant Number conversion from the node:sqlite participant count. Refresh prompt snapshots after drift from the sessions and sessions_spawn tool description updates.

* fix(ci): restore solo-mode chip suppression and conform new method descriptors

Solo-mode root cause: owner-assignment submenu options reused the permanent owner-chip custom element, so hidden menu avatars were counted as attribution chrome. Menus now use viewer avatars while gateway-gated owner chips remain exclusive to collaborative sessions.

Conform sessions.assignOwner to the 2026.8 descriptor and append-only advertised-method inventories, and regenerate the Swift and Kotlin protocol surfaces.

Keep historical v15/v14 fixtures frozen by stripping the new owner columns; the existing range already excludes the participant table. Replace the new raw SQLite schema probes with synchronous Kysely queries.

Clear max-lines by splitting the organizer host contract, pure agent-navigation projections, and ownership/filtering sidebar cases at their concept boundaries.

* fix(ci): integrate ownership series with latest main surfaces

Wire the sessions-page assign-owner action, merge capability imports, narrow the navigation export scope, and apply sessions-create formatting.

The owner-presence regression came from hidden assign-owner menu avatars emitting data-viewer-id, so owner and menu chrome now opt out of presence markers while real facepiles retain them.

* fix(sessions): scope the involving-me filter to profile-backed participants

Session participant history mixed channel-native sender ids with authenticated Gateway profile ids, so involving-me missed real sessions and could accept numeric collisions.

Record the actor_source namespace at each producer, carry it through the internal SQLite projection, and match authenticated viewers only against profile-backed human participants. Legacy NULL sources fail closed for filtering, while channel ids remain available for display.

* build(ui): raise startup budget baseline for session ownership surfaces

Ownership chips, assignment menus, and the participant stack add ~0.7 KiB
gzip to the startup path; CI compression landed just over the previous
baseline+tolerance. Hard cap (350 KiB) unchanged.

* refactor(sessions): drop raw NULL projection for the lazy actor_source column

The Kysely guardrail rejects typed raw sql snippets outside allowlisted
boundaries; select the lazily-ensured column only when present and let the
row projection treat its absence as unknown/legacy.

* build(ui): refresh combined startup baseline
2026-08-17 02:35:20 -07:00
Peter Steinberger 87a0493ae2 fix(ci): restore environment variable count budget (#125185) 2026-08-17 02:18:09 -07:00
Ayaan Zaidi 78b8f8cf66 fix(ai): preserve final phase across resumed reasoning (#125149)
Classify text interrupted by resumed reasoning at the OpenAI-completions producer boundary, so channels deliver only the confirmed final answer.

Defer phase-ambiguous replies until terminal classification while preserving live partial delivery for ordinary completions.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-17 14:47:58 +05:30
Peter Steinberger e3d553b734 fix(channels): surface Discord voice failures and preserve Telegram controls (#125152)
* fix(channels): surface degraded Discord and Telegram sends

* fix(telegram): preserve legacy slash callbacks

* fix(ci): correct env-var count after version define removal
2026-08-17 01:56:38 -07:00
Peter Steinberger ee86b24ac1 fix(node-host): recover MCP catalogs and sessions (#125092)
* fix(node-host): recover MCP catalogs and sessions

Share placement-neutral MCP lifecycle and result projection while keeping Gateway session ownership and node process ownership separate. Refresh node catalogs live, recover closed or expired transports without replay, and preserve MCP application errors across node.invoke.\n\nFixes #125044

* test: register node MCP CI inventory

* fix(node-host): stop MCP recovery after abort
2026-08-17 01:39:33 -07:00
Peter Steinberger faed554dd6 fix(ci): refresh Control UI startup gzip baseline (#125156) 2026-08-17 01:01:00 -07:00
Peter Steinberger 0e3a3aa203 refactor: finish canonicalization stragglers (#125056)
* refactor: finish canonicalization stragglers

* chore: remove release-owned changelog entry

* refactor: narrow SecretRef SDK import
2026-08-17 00:09:29 -07:00
Peter Steinberger 7b18510e6c fix(config): materialize fresh-install defaults for missing config (#125110)
* fix(config): materialize fresh-install defaults for missing config

A missing config file (fresh install) skipped runtime-defaults
materialization on both loadConfig and readConfigFileSnapshot, while an
existing empty {} config got the full defaults (compaction safeguard,
session/cron/model defaults). Out-of-box behavior silently diverged from
the documented defaults until the first config write created the file.
The drift dates to bc75968074, which dropped the missing-branch
materializeRuntimeConfig call during an import-trim.

Route the missing-file branches through the same load/snapshot
materialization as existing configs, and delete the now-unreferenced
per-mode defaults profile table in materialize.ts — load and snapshot
must materialize identically anyway (prepared-runtime exact-config
resolution depends on it), so the profile indirection only invited
drift.

* chore(ratchet): shrink io.load.ts assertion baseline to 2

The missing-config fix removed an 'as OpenClawConfig' cast; the
assertion-safety ratchet requires the baseline to shrink with it.
2026-08-16 23:48:46 -07:00
Peter Steinberger 4e1f26dc18 refactor(gateway,ui): one bounded display projection; delete marker strip sites (#124997)
* refactor(gateway,ui): one bounded display projection; delete marker strip sites

Persisted transcripts are marker-free since the write-boundary projection
(#124793), the historical migration (#124888), and TTS facts (#124913), so
display surfaces stop compensating. sessions.list.lastMessagePreview and its
siblings (sessions.preview/describe, TUI picker, sessions_list tool, MCP) now
share one bounded role-aware projection (240 chars, tool/system/thinking and
suppressed control replies excluded, directive-only rows fall through). The
web reply chip reads the typed openclawDelivery fact instead of parsing text;
chat.history preserves the field to the UI. Post-hoc display strips are
deleted across web/TUI/MCP/sessions-list; live streaming cleaners stay.
Stale gateway-protocol preview comments corrected; no schema change.
Assertion-safety baseline pruned for shrunk files (sanctioned direction).

Production net -173, tests net -137. Fixes the sidebar [[reply_to_current]]
preview leak and the empty-code-pill overstrip of quoted markers.

* fix(agents): preserve restart recovery transcript reads

* refactor(gateway): remove obsolete transcript exports

* fix(gateway): normalize injected delivery directives

* fix(ci): scope projection and recovery checks

* chore(ci): shrink plugin SDK surface budgets

* test: deflake loaded side question and worker checks

* test: align display projection CI fixtures

* style: format display projection fixture
2026-08-16 23:44:29 -07:00
Peter Steinberger 59c661bf66 fix(gateway): prevent large history from blocking chat startup (#124994)
* fix(gateway): bound startup history materialization

Apply SQLite byte limits before loading history payloads and share a yielded, fingerprinted Claude CLI snapshot across concurrent startup clients. Preserve marker, cursor, redaction, and external identity semantics, and clarify that shrink-only ratchet updates need no separate approval.

* test(browser): await all lazy command groups

* fix(gateway): bound history snapshot state

* fix(gateway): preserve oversized history responsiveness
2026-08-16 23:43:40 -07:00
Peter Steinberger c26eda0620 feat(approvals): add request kind discriminant (#125084)
* feat(approvals): add request kind discriminant

* test(approvals): expect normalized legacy request
2026-08-16 23:30:39 -07:00
Peter Steinberger 49b4775f30 refactor(security): consolidate plugin JSON body reading onto canonical bounded reader (#124944)
* fix(security): harden canonical bounded JSON body reading

* refactor(admin): use canonical bounded JSON body reader

* refactor(telegram): use canonical bounded JSON body reader

The assertion-safety baseline prune for extensions/telegram/src/miniapp/routes.ts (2 to 1) is explicitly approved.

* test(security): cover canonical JSON body migrations

* refactor(plugin-sdk): name response-first body profile

* fix(telegram): flush miniapp body-limit responses before close
2026-08-16 22:01:34 -07:00
Josh Avant f7a8638282 feat(audit): explain outbound message delivery (#123709)
* feat(audit): explain outbound message delivery

* fix(audit): record early message policy denials

* refactor(audit): split message delivery readers

* test(outbound): cover delivery audit lifecycle

* fix(audit): preserve message progress across downgrade

* fix(audit): keep progress out of activity protocol

* docs(audit): keep activity outcomes terminal-only

* fix(audit): bound merged delivery paging

* fix(audit): trust decision channel references

* fix(audit): keep retention constant private

* fix(audit): record broadcast target denials

* fix(audit): distinguish broadcast denial receipts

* test(qa): enforce message delivery receipt proof

* fix(audit): integrate C04 with schema v9 owners

* chore(audit): satisfy assertion safety gate

* refactor(agents): split explicit message target guard

* test(audit): materialize pinned reader in shallow CI

* test(qa): poll terminal audit persistence

* fix(delivery): defer audit terminal to recovery

* test(qa): isolate message delivery restart proof

* test(qa): keep gateway restart config canonical

* test(qa): exclude unrelated restart plugins

* test(qa): remove unused restart plugins

* test(qa): disable automatic memory plugin

* test(qa): converge replacement gateway startup

* fix(plugins): stabilize registry fingerprints

* fix(plugins): use public normalization export

* test(plugins): keep fingerprint fixture immutable

* fix(audit): bind delivery receipts to exact executions

* chore(audit): keep terminal binding helper private

* test(reply): cover unbound admission callbacks

* docs(agents): preserve durable delivery ownership
2026-08-16 21:26:27 -07:00
Pavan Kumar Gondhi 6e026c2fe3 fix(gateway): reject unattributable loopback proxy traffic [AI] (#119950)
* fix(gateway): bind auth limits to ingress attribution

* fix(gateway): close remaining ingress auth gaps

* fix(gateway): carry attribution into new ingress paths

* fix(gateway): close ingress ownership gaps

* fix(gateway): complete proxy ingress hardening

* fix(gateway): stabilize managed Tailscale ingress

* fix(gateway): make Tailscale cleanup ownership-safe

Refuse reset-on-exit publication until Tailscale exposes an atomic owner-bound cleanup operation, and migrate legacy configs with Doctor.

* fix(gateway): finish ingress ownership repair

* fix(gateway): own managed Tailscale route lifetime

Run managed Serve and Funnel routes as foreground claims tied to the Gateway lifecycle. Retire named Service config through Doctor because Tailscale Services cannot run in foreground mode.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* fix(gateway): align Tailscale consumers and build guards

Remove the retired named-service config from Telegram Mini App URL resolution and register the lifecycle worker as an explicit production entry.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* fix(gateway): preserve retired Tailscale inputs

Keep Funnel enabled when removing an ignored named-Service setting and accept the legacy positive reset flag as a no-op now that managed routes always follow Gateway lifetime.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* fix(gateway): preserve Tailscale route diagnostics

Prefer the actionable foreground CLI failure captured during timeout cleanup, and cover the original delayed-failure ordering.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* fix(gateway): reconcile Tailscale ingress with main

Preserve current ingress ownership contracts after the rebase, retire the obsolete device-auth migration check, validate route-owner IPC, and move Tailscale auth coverage onto the managed listener.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* fix(gateway): finish ingress rebase coverage

Unify the rebased net imports and let module-reset WebSocket tests prepare attribution through the same fresh module instance as the handler.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>

* test(gateway): align run-loop server fixture

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-08-16 21:01:20 -07:00
Peter Steinberger fdaaa1ba53 refactor(validation): consolidate boundary guards into schemas — wave 2 (#124961) 2026-08-16 20:13:55 -07:00
Peter Steinberger 7349177ce3 feat: main-session agent-wide visibility + session.groupScope routing (#124965)
* feat: add main session group routing

* docs: explain main session routing scopes

* fix: align memory session visibility

* test(qa): cover main-scoped group bindings

* fix(sessions): preserve binding-scoped outbound routes

* fix(routing): preserve explicit outbound owners

* fix(sessions): recognize global main visibility

* chore(ci): prune assertion safety baseline
2026-08-16 19:57:18 -07:00
PollyBot13 cef77d1aeb fix(codex): reject malformed native tool arguments (#124649)
* fix(codex): reject invalid native tool arguments

* fix(codex): preserve raw argument preparation

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-08-16 22:48:40 -04:00
Peter Steinberger 85baeecb57 refactor: consolidate record guards (#124970) 2026-08-16 19:34:57 -07:00
Peter Steinberger cfce94be52 refactor(types): discriminated unions for internal contracts — wave 2 (#124959) 2026-08-16 19:21:32 -07:00
Peter Steinberger 313cb134fe refactor(channels): align approval reaction bindings (#124942)
Split Signal approval routing into its concept-owned module and call the shared SDK binding helpers directly.

Reject persisted reaction targets when any allowed decision is invalid or duplicated, rather than retaining a valid subset from a corrupt transient record.
2026-08-16 18:48:39 -07:00
Peter Steinberger 78beccf053 feat: add cloud worker profiles and machine selection (#124864)
* feat(workers): add per-dispatch machine classes

* feat(ui): add cloud worker machine picker
2026-08-16 18:37:00 -07:00
Peter Steinberger ab5611f0be fix(security): prevent approved scripts from changing before execution (#124858)
* fix(security): bind exec approvals to mutable operands

* test: align approval fixtures with operand binding

* fix(security): close durable operand binding gaps

* fix(security): preserve independent exec policy grants
2026-08-16 18:26:42 -07:00
Josh Avant ffc94d0ff0 fix: stop forked commands after Gateway timeouts (#124081)
* fix: stop service child trees before timeout completion

* fix: preserve service child root results during cleanup

* test: isolate relay selection from linux spawn wrapping

* refactor: keep service anchor payload internal

* test: validate lifecycle probe pid output

* fix: close service child adversarial races

* fix: preserve service startup failures

* fix(process): flush service output before root result

* fix(process): bound cleanup on held output

* chore(process): satisfy promise executor lint

* fix(process): finish cleanup after lineage loss

* fix(process): retain relay output before subscription

* fix(ci): register service child runtime entries

* fix(ci): expose relay safety invariants

* fix(process): preserve service cancellation grace

* test(process): prove service grace on macOS
2026-08-16 16:02:34 -07:00
Peter Steinberger 800a0bb52a perf(webui): render cached model catalog while the chat pane refreshes (#124868)
* perf(webui): render cached model catalog while the chat pane refreshes

* test(webui): fix seed-test types and shrink assertion baseline
2026-08-16 15:52:26 -07:00
Peter Steinberger 080887df92 refactor(types): discriminated unions for internal contracts — pilot lane A (#124845) 2026-08-16 14:49:28 -07:00
Peter Steinberger 06101b1037 refactor(validation): consolidate boundary guards into schemas (#124820)
* refactor(validation): consolidate boundary guards into schemas — pilot lane B

* fix(meetings): reject non-object browser status
2026-08-16 14:41:14 -07:00
Peter Steinberger 9b17b93414 refactor(ui): derive cron types from gateway protocol (#124827)
* refactor(ui): derive cron types from gateway protocol

* fix(protocol): preserve cron session target type
2026-08-16 14:34:26 -07:00
Peter Steinberger ee1ec45eb3 improve(ui): one primary composer button, Enter-steer, Esc-stop, quiet mic, no transcript focus ring (#124799)
* fix(ui): drop the transcript keyboard-focus ring

The .chat-thread:focus-visible 2px inset outline (added in #120693) boxed
the entire main content whenever the transcript took keyboard focus
(Shift+Tab, closing transcript search) and read as a broken border that
stopped above the composer. The transcript stays keyboard-focusable for
scroll keys; it just paints no focus box. Accepted tradeoff: no visible
focus indicator on the scroll container.

* feat(ui): single primary composer button with Enter-steer and Esc-stop

The composer now renders exactly one primary action, always rightmost:
- draft or attachments present: one send-shaped button whose label follows
  the session follow-up mode (Send / Queue / Steer); the stop button no
  longer renders beside it
- empty while a run is active: Stop (tooltip "Stop (Esc)"); Escape in the
  composer aborts the run, deferring to reply-clear and menu precedence
- empty while idle: disabled send labeled "Write a message to send."

Enter on an empty composer while a run is active steers the oldest
steerable queued message — Enter queues, Enter again steers — using the
same eligibility predicate as the queue chip's Steer action (shared
steerableQueuedMessage helper). The mic/dictation control is now always a
compact secondary control left of the primary instead of morphing into
the primary when the composer is empty. Dead ChatRunControlsProps fields
(hasMessages, onExport, onNewSession, showPrimary, showSecondary) and the
composer exportMarkdown wrapper are removed; the assertion-safety
baseline entry for chat-composer.ts shrinks 8 -> 7.

* refactor(ui): quiet the idle composer mic

The idle mic was an accent-tinted two-segment pill (mic + permanently
visible device-picker chevron) that outweighed the primary action. It now
matches the attach button's ghost weight: transparent idle, subtle hover,
and the accent treatment is reserved for the recording and talk-active
states so the idle -> recording escalation reads stronger. The device
picker chevron collapses at idle and reveals on hover/focus-within (kept
always visible on coarse pointers, where dictation matters most; reveal
transition disabled under prefers-reduced-motion). The idle tooltip now
teaches the gesture split — "Tap to talk · Hold to dictate" — while the
aria-label stays "Start voice input".

* refactor(ui): split composer keydown and oversized composer test files

chat-composer.ts crossed the 700-line oxlint budget after the keyboard
additions; the keydown handling (menu routing, history keys, Escape-stop,
Enter steer/send) moves to chat-composer-keydown.ts behind a typed deps
factory. The composer unit tests split their primary-action/keyboard
coverage into chat-composer-actions.test.ts over a shared
chat-composer.test-support.ts, and the catalog/model-discovery e2e cases
move to chat-composer-catalog.e2e.test.ts. Pure moves, no behavior
change; no max-lines suppressions or baseline entries added.

* chore(ui): keep composer test-support ComposerProps internal

The split exported a type nothing imports; the dead-export scan is a
merge gate.

* fix(ui): narrow composer keydown target and adapt keyboard tab-order proof

The keydown split moved two bare textarea assertions into a zero-baseline
file; a single instanceof narrowing at the handler entry replaces both
(the handler only binds to the composer textarea). chat-composer.ts's own
assertion baseline ratchets 7 -> 5. The pointer-activation e2e now walks
the real post-redesign tab order (mic, focus-revealed picker trigger,
then primary send) instead of assuming one Tab.

* fix(ui): gate Enter-steer on the queue chip's visible availability

ClawSweeper P1: the Enter path only required an abortable run, so
offline Enter swallowed the keypress and invoked a steer lifecycle that
returns at !connected with no visible outcome, while the queue chip's
Steer button was correctly hidden behind connected && canCompose. The
keydown path now mirrors the chip's exact boundary (connected, canSend,
abortable, visible queue surface including sessionKey-less pending
rows).
2026-08-16 14:00:47 -07:00
Peter Steinberger 707d3f4adf refactor: split max-lines pilot files and retire suppressions (#124813) 2026-08-16 13:45:27 -07:00
Peter Steinberger 32f056f011 refactor(agents): persist directive facts at the assistant write boundary (#124793)
* refactor(agents): persist directive facts at the assistant write boundary

Assistant final text is now stripped of inline delivery directives once, at
the SessionManager append boundary, with the parsed facts persisted as a
typed openclawDelivery field on the assistant message (openclawDeliveryMirror
precedent, inside event_json — no schema change). Live session state is
replaced with the persisted canonical bytes so same-run continuations and
next-turn store rebuilds never diverge (prompt-cache invariant).

Delivery and restart recovery consume the stored facts instead of re-parsing
persisted text; pre-upgrade in-flight messages without facts intentionally get
no text-parse fallback (commented tradeoff at the read site). Directive
parsing is now code-region aware, so markers quoted in inline code or fences
neither parse nor strip — fixes the empty-code-pill display bug.

The undocumented reaction marker DSL ([[react:]], [[react_to_current:]]) is
deleted; structured message-tool reactions remain the canonical path. Stale
assertion-safety baseline entry removed with the parser.

Production net -10, tests net +38. Focused suites: 9,068 passed; pre-fix
regressions fail for the intended reasons. Codex autoreview clean.

* fix(agents): satisfy directive persistence gates

* fix(config): drop unused directive export

* fix(agents): preserve indented directive examples
2026-08-16 13:44:11 -07:00
Peter Steinberger 541f373bc0 refactor(validation): type producers and drop internal re-validation — pilot lane C (#124808) 2026-08-16 13:38:17 -07:00
Peter Steinberger d8ebe85c24 refactor(line): replace nine-marker prompt DSL with typed rich messages (#124755)
* refactor(line): replace nine-marker prompt DSL with typed rich messages

Delete the LINE plugin's double-bracket marker language (quick_replies,
location, confirm, buttons, media_player, event, agenda, device,
appletv_remote) and its parser. Portable interactions now flow through the
existing presentation-block seam (renderPresentation, matching Discord and
Feishu); LINE-specific cards ride closed channelData.line schemas mapped to
the existing Flex renderers. Prompt section shrinks to four capability
lines and explicitly de-fangs marker text. Removes the stale
assertion-safety baseline entry for the deleted parser.

Production LOC net -69, tests net -433. Suite: 510/510 green.

* fix(line): declare rich message schema dependency

* fix(line): satisfy rich message type checks

* docs(line): mark card fragments as partial
2026-08-16 12:45:39 -07:00
Peter Steinberger eb77ce5690 refactor(infra): split device pairing lifecycle modules (#124734) 2026-08-16 11:40:55 -07:00
Peter Steinberger edf4c74601 fix(ci): prevent QA maturity scorecard timeouts (#124612)
* fix(ci): prevent QA maturity scorecard timeouts

* fix(ci): register QA evidence shard entrypoint

* fix(ci): isolate QA evidence jobs from shared caches

* fix(ci): separate QA selected revision from trusted harness

* fix(ci): protect every QA selected-code job

* fix(ci): bind QA workflow callers before checkout

* fix(ci): authenticate QA evidence caller chain

* fix(ci): remove dynamic QA checkout actions
2026-08-16 11:17:21 -07:00
Peter Steinberger aeff737da9 fix(agents): prevent invalid names from targeting the default agent (#124670)
* fix(agents): reject unrepresentable agent ids

* refactor(system-agent): split model selection setup

* chore: shrink assertion safety baseline

* docs: record strict agent id validation proof

* style: format strict agent id report

* chore: drop stray unrelated report artifact

* chore: restore REPORT.md to main state
2026-08-16 10:37:06 -07:00