Commit Graph

72243 Commits

Author SHA1 Message Date
Jesse Merhi d7627d6f4c refactor(prompt): use plain inbound context labels and drop system-tag sanitizer (#112000)
* refactor(prompt): plain inbound context labels with a provenance marker

Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.

Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.

Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.

Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.

* fix(ci): resolve gate failures for plain inbound context labels

- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
  so the Kysely connection-boundary guardrail holds; unexport the now-internal
  transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
  plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
  the 700-line oxlint cap; suppressions are disallowed, so follow the existing
  sibling record-module pattern. Public exports and PluginCompatCode literals
  unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
  only). The bracket de-fang and System: rewrite it expected were removed with
  sanitizeInboundSystemTags; forged system lines are neutralized at the
  system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.

* fix(prompt): harden inbound context label migration and drop in-band sanitizer

Review follow-ups on the plain-label + provenance-marker change:

- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
  look-alike `System:`/`[System]` markers corrupted legitimate user text and is
  not a real injection boundary; role separation plus external-content wrapping
  is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
  now requires a complete known legacy sentinel line, a legacy label followed by
  a fenced JSON body, or the complete legacy external-content header. The prior
  predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
  deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
  alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
  header's line break, migrated assistant rows skip newline normalization, and
  without it the marked-header replace wins and the body strips to empty. Added
  a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
  prose-block strip path.

Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
2026-07-27 11:27:53 +10:00
Vito Cappello 5e841bc2e1 fix(agents): preserve native runtime controls on Codex routes (#107588)
* fix(agents): preserve Codex runtime controls

* fix(agents): cover shipped fast cutoff aliases

* fix(agents): validate native runtime control values

* refactor(agents): simplify fast mode key matching

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 21:25:44 -04:00
Peter Steinberger b6dcb8c323 test(auth): avoid broad profile discovery in migration isolation (#114217) 2026-07-26 21:12:33 -04:00
Peter Steinberger 472f8e7c72 fix(ui): keep the live token counter current in memoized chat rows (#114213) 2026-07-26 21:09:18 -04:00
Peter Steinberger c91a37aeed feat(ui): add a Memory settings page (#114037)
* feat(ui): add a Memory settings page with Dreaming as a tab

Memory config was scattered across five surfaces: the memory.* schema section
lived on AI & Agents with 43 of 51 keys behind the Advanced tier, the memory
slot owner was only visible on Plugins, dreaming's knobs were JSON-only, its
status UI sat under Agents, and Memory Import was a separate route.

/settings/memory now owns that surface, following the MCP page shape (curated
rows above an embedded schema editor):

- Overview: the exclusive memory slot rendered as a segmented control over
  installed memory-kind plugins, memory.backend promoted out of Advanced with
  the qmd sub-config revealed only when qmd is selected, additive add-on rows,
  and a Memory Import link.
- Search: the memory.search surface via the embedded editor.
- Dreaming: the global frequency/model/timezone/storage/phase knobs, which
  previously required hand-editing openclaw.json, plus an agent picker feeding
  the existing dream scene/diary/advanced panel for the agent-scoped reads.

Engine selection calls plugins.setEnabled so the gateway's exclusive slot
policy stays the single owner instead of being duplicated in the UI.

* fix(ui): redirect stale ai-agents memory deep links to the memory page

* fix(ui): report memory runtime defaults on the Memory page

The Dreaming tab rendered its own defaults instead of the ones
resolveMemoryDreamingConfig applies, so a config carrying only
dreaming.enabled showed all three phases off while they were running, and
an unset storage mode read as inline instead of separate. Toggle specs now
carry the runtime fallback and the storage default is stated once, both
pointing at src/memory-host-sdk/dreaming.ts.

Three more surfaces asserted things the runtime does not do:

- plugins.slots.memory "none" is the explicit-off sentinel, not an engine
  id, so the segmented control selected nothing. The slot now resolves to a
  closed auto/off/pinned selection with its own hint.
- memory.backend is resolved by the memory runtime the slot owner
  registers, which only memory-core ships, so the row is hidden for any
  other engine instead of saving a value nothing reads.
- The Dreaming tab wrote config.dreaming for whichever plugin owns the
  slot even when that plugin's schema cannot hold it. It now reuses the
  enablement flow's schema check (resolveDreamingConfigPathSupport, shared
  with updateDreamingEnabled) and renders an unsupported state instead.

Also key the plugin-catalog sync on the connected phase: the connecting ->
connected transition keeps the same client object, so a page mounted
during the handshake never loaded the catalog and never showed the engine
picker.

The tab keeps the autosave status line and restart banner the embedded
editor renders on the other tabs; these knobs autosave, but nothing
reported it. The pure view moved to memory-dreaming.ts with the element in
memory-dreaming-page.ts, matching memory.ts/memory-page.ts.

* fix(ui): resolve the memory slot through the canonical policy

The Memory page re-derived plugins.slots.memory instead of using the rule the
runtime applies, which broke both directions of the engine control:

- An unset slot was reported as "the first enabled memory-kind plugin in the
  catalog". The runtime resolves it to the slot's default owner
  (DEFAULT_SLOT_BY_KEY.memory), so the page could show one engine as active
  while another was loaded, reveal or hide the backend row for the wrong
  plugin, and target the wrong plugin when switching memory off.
- Off called plugins.setEnabled(false), which writes enablement only. The slot
  stayed pinned, so the choice did not survive a refresh and re-enabling that
  plugin from the Plugins page silently switched memory back on.

resolveSlotSelection now lives next to defaultSlotIdForKey in
src/plugins/slots.ts and owns the rule once; config normalization consumes it
and the page imports it instead of restating it. Off writes the explicit "none"
sentinel through the config form, so it round-trips; picking an engine still
goes through plugins.setEnabled, which is where the exclusive slot policy
lives. The dreaming controller's own copy of the rule is gone too.

Four smaller fixes on the same surface:

- A failed engine change is reported next to the control instead of being
  swallowed, so the selector no longer just snaps back.
- Dreaming's numeric inputs carry the memory-core manifest's integer/min/max
  bounds and refuse out-of-range edits at the field, rather than patching a
  value autosave then fails to write.
- Settings search destinations carry the Memory tab that renders the matched
  child, so a memory.search hit no longer lands on Overview, whose narrowed
  editor omits it.
- The Dreaming tab caches only a definitive schema-capability answer. An
  offline or failed lookup now reports "unknown" and is retried on reconnect
  instead of permanently suppressing the recheck.

* fix(ui): model unknown memory state instead of collapsing it

The Memory page reported unknowns as decided values. An empty catalog meant
loading, disconnected, or a failed plugins.list, yet add-on rows rendered
"Disabled"; catalog completions were keyed on client identity, which survives a
phase flip, so a stale load could repopulate a disconnected page or overwrite a
newer read; and `?tab=` was adopted once per distinct value, so a repeat
navigation to a tab the user had left was ignored.

Replace the ad-hoc nullable fields with closed shapes. MemoryCatalog is a
loading/unavailable/ready union, so absence of an entry only decides anything
inside `ready`, and MemoryAddonRow carries a four-state enablement the view
renders without ever inventing an "off". CatalogConnection is one object per
(client, connected) transition and doubles as the request generation an
in-flight load carries, so obsolete completions are dropped by identity. The tab
is no longer page state at all: the URL owns it, tab clicks navigate, and every
arrival is honored.

Settings search now resolves the engine/backend through the same
resolveMemoryBackend the page uses and matches only the `memory.*` children the
page can surface, so a `memory.qmd` hit under the built-in backend no longer
routes to an Overview whose editor omits it.

* fix(ui): surface a disabled memory owner and anchor curated backend search

The slot and plugin enablement are independent config surfaces, so
`plugins.slots.memory` can name a plugin the catalog reports as disabled.
The engine control showed that plugin as selected, and because re-picking an
already-selected radio fires no change event, there was no way back on. Add an
explicit enable row for that state and let the same-id write through when the
owner is not running; picking Off stays a no-op.

`memory.backend` is curated out of the schema editor, so the generic
`#config-section-memory` anchor scrolled past it. Fold the memory tab and hash
choice into one `memoryDestination` owner that routes a curated-only match to
the new anchor above the editor.

* fix(ui): scope the dreaming capability probe to its connection

The probe was deduplicated by plugin id alone, which cannot tell a current
answer from a stale one. A disconnect and reconnect on the same slot owner left
the token armed, so the reconnect read as "already in flight" and swallowed the
retry that an `unknown` answer requires — leaving an unsupported engine's knobs
editable until some unrelated config notification arrived. An A -> B -> A switch
had the mirror problem: the old A response was accepted for the new A probe.

Make the in-flight probe an object whose identity is the generation, drop it
whenever the owner or the connection changes, and accept only the completion
that still owns the slot. Same shape as the catalog guard on the Memory page.

* fix(ui): satisfy the lint and dead-export gates on the memory page

Exhaustive switches need a terminal `default:` to satisfy
typescript/consistent-return, matching the existing view-status.ts shape.

Seven symbols were exported with no production consumer outside their own
module, which the hard-zero Knip production scan rejects. Tests alone do not
make internals contracts, so drop the exports and reach the behavior through
each module's public surface instead: the view props type comes from
`Parameters<typeof renderMemory>`, the tab panel is found by its ARIA role, and
the dreaming number/storage helpers are proven through `renderDreamingSettings`.

Folding those helper unit tests into the render path also corrected one of them:
a `type="number"` input coerces unparseable text to empty, so the "reject
garbage" case was unreachable through the real control. Replaced with the
inclusive-bound and clear-the-field cases, which are reachable.

* refactor(ui): keep the memory schema facts out of the startup bundle

Settings pages are already lazy — the config route is `import("./config-page.ts")`
— but settings search runs from app-host at startup, and it needed the same
answers about which `memory.*` children are reachable and where a match lives.
Importing those from the view module dragged lit, hub-tabs, and settings-ui into
the startup chunk with it, blowing the Control UI startup budget.

Move the rendering-free facts (slot/backend resolution, tab and curated key
lists, schema narrowing, the anchor id) into memory-schema.ts, which imports
only record-coerce and the shared slot policy. The view keeps the templates and
now consumes the same module, so there is still one owner per fact.

* chore(ui): record the memory settings surface in the startup budget baseline

Routing settings search through memory-schema.ts instead of the view module
recovered 10,872 B of the startup chunk (334,992 -> 324,120 B), which is back
under the 324,608 B ceiling. The remaining 2,795 B over the old baseline is the
honest cost of the new surface: its i18n strings, plus the slot/backend facts
the startup search index has to read.

Measured by hosted CI (run 30189972795); this worktree cannot build locally
because pnpm wants to purge a node_modules shared with other running agents.
2026-07-26 21:05:03 -04:00
Peter Steinberger abd9eededf test(ui): assert the Home run ring in chat run lifecycle e2e (#114212) 2026-07-26 21:03:21 -04:00
Vito Cappello c15ab158b0 fix(state): preserve live SQLite WAL files during verification (#114016)
* fix(state): isolate database verifier snapshots

* test(state): assert verifier retains POSIX lock

* fix(state): scope verifier child IPC

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 20:57:17 -04:00
Jason (Json) 0f6a693acd fix(models): shape unreleased Claude ids with the newest known contract (#114205)
Request shaping is selected by version predicates in llm-core, so a Claude id
from a generation newer than anything the plugin encodes fell through to pre-4.6
shaping: manual budget_tokens thinking plus caller sampling parameters, both of
which current models reject.

Resolve such an id through the plugin's existing forward-compat path and stamp
params.canonicalModelId, the same seam Bedrock and Mantle use to map a
provider-native id onto a canonical Claude contract. Shared contracts are
untouched, so Claude served through third-party providers keeps its own
shaping.
2026-07-26 18:46:34 -06:00
Peter Steinberger a53795ec35 fix(worktrees): reclaim git worktree locks left by dead processes (#114130)
* fix(worktrees): reclaim git worktree locks left by dead processes

A gateway that dies without releasing its worktrees leaves
"openclaw pid=<dead>" locks behind. lockState() already classifies those as
{ kind: "dead" }, and release()/remove() reclaim them, but
lockWorktreeForProcess() threw instead. Every later acquire then failed with
"fatal: ... is already locked", and retainGitLock() swallowed the error and ran
the agent with no lock at all -- so the stale lock permanently disabled the
protection it was meant to provide.

Reclaim a dead-owner lock on acquire, the way the sibling call sites do.

* docs(worktrees): enumerate every reclaim path in the scope note
2026-07-26 20:45:45 -04:00
keshavbotagent 46f4018ff9 fix(codex): recover in-place session resets (#114056)
* fix(codex): recover in-place session resets

* fix(codex): preserve retired generation fences

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-07-26 16:49:57 -07:00
Patrick Erichsen c66ca2fbb2 feat: show ClawHub skill icons in Control UI (#114152) 2026-07-26 14:44:36 -05:00
Peter Steinberger f9721b9809 fix(e2e): use the newest Windows smoke snapshot (#114157)
* fix(e2e): select newest Windows smoke snapshot

* test(e2e): preserve undated snapshot ordering
2026-07-26 15:34:04 -04:00
Patrick Erichsen 898e10c0f4 docs(buzz): shorten plugin readme (#114156) 2026-07-26 14:21:36 -05:00
Peter Steinberger 96aa6be670 fix(e2e): harden Windows prerequisite downloads (#114153) 2026-07-26 15:01:59 -04:00
Jason (Json) 4bb8df63ca fix(models): authenticate Anthropic discovery with OAuth tokens (#113906)
* fix(models): authenticate Anthropic discovery with OAuth tokens

Live model discovery sent every credential as x-api-key. Claude subscription
credentials are OAuth access tokens, which Anthropic rejects in that header, so
discovery 401d and silently fell back to the shipped catalog.

Select the auth header from the detected credential shape: API keys keep
x-api-key, OAuth tokens use Authorization: Bearer. The two are mutually
exclusive because Anthropic rejects requests carrying both.

* fix(models): keep shipped Anthropic models when discovery succeeds

Live discovery replaces the seed catalog with the /v1/models response. Anthropic
does not publish every model it serves, so a shipped entry with no live row was
dropped from the provider listing once discovery started working.

Re-add manifest models the live response omitted; discovered rows still win on
shared ids.
2026-07-26 12:19:57 -06:00
Peter Steinberger c16c0dbef4 fix(release): allow macOS snapshot selection in candidate smoke (#114144) 2026-07-26 14:08:31 -04:00
Jesse Merhi 6bbf1dd917 fix: open selected channel setup immediately (#113197)
* CLI: enter selected channel setup directly

* fix: keep channel direct entry resolution scoped

* fix: keep exact channel ids ahead of aliases

* test: clarify channel-only guided routing

* fix: align channel direct entry with current main
2026-07-27 03:30:15 +10:00
Peter Steinberger cca5b14785 feat(ui): path-based session and dashboard URLs (#113883)
* feat(ui): path-based session and dashboard URLs

* docs(ui): document control UI URLs

* fix(ui): finalize session path routing

* feat(ui): anchor session URLs on stable keys

* docs(ui): clarify stable session URL identity

* fix(ui): resolve session prefixes with full prefix and pagination

Query sessions.list with the full supplied prefix instead of an eight-char
truncation, and paginate up to a bounded page count so longer disambiguation
links resolve instead of being reported ambiguous. Zero strict-prefix matches
now fall through to literal-key resolution rather than rendering an empty
ambiguity view.

Also document the ~dot/~dotdot segment escape: peer ids reach session keys
trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or
'..' segment is reachable and browsers would normalize it away.

* fix(ui): synchronize committed session routes

* test(ui): split native shell host coverage

* fix(ui): thread configured mainKey through session URL builders

Reserved-set disambiguation needs the operator-configured mainKey at runtime, so
thread it from agentsList through every session path builder and the ClickClack
control URL. Unambiguous non-hex single-segment rests now resolve literally while
short-id-shaped rests still fail closed, which restores ClickClack channel
compatibility detection and control-link reconciliation.

Also bound prefix-resolution retries, preserve catalog thread identity, and keep
draft state on ambiguous candidate links.

* fix(ui): repair session URL CI integration

* perf(ui): lazy-load session route resolution

* perf(ui): isolate session prefix resolution

* perf(ui): defer session path parsing

* perf(ui): defer session navigation startup

* fix(ui): preserve first-run and literal session navigation

* fix(ui): satisfy session routing type and export gates

* fix(clickclack): preserve unscoped control link agent

* style(ui): satisfy chat page line limit

* refactor(ui): move chat page helpers to owning modules

* fix(ui): preserve destination session route identity

* fix(ui): preserve agent identity in session routes

* fix(ui): escape dots in literal session path segments

encodeURIComponent leaves periods intact, so a literal key segment like
channel:release.js produced /chat/main/channel/release.js. In-app navigation is
intercepted by the SPA, but a refresh, an external link, or a ClickClack link
would be served as a static asset request and never reach the app.

pathForWorkboardBoard already escapes dots for this reason; mirror it in both the
session URL contract and the ClickClack encoder, route the agent id through the
same segment encoder, and pin the case in both shared vector tables.

* fix(ui): stop bootstrap after teardown race

* refactor(ui): centralize session navigation targets

* fix(ui): consume bootstrap teardown abort

* fix(ui): canonicalize configured main session routes

* fix(ui): preserve distinct session references

* fix(ui): redirect released session query links

* fix(ui): make bootstrap teardown abort-safe
2026-07-26 12:46:04 -04:00
Peter Steinberger bba7e30fcc fix(browser): preserve nested timeouts and cancellation across node actions (#114131) 2026-07-26 12:04:52 -04:00
Peter Steinberger 01f8bd9d12 fix(agents): resume interrupted turns without a recognized abort string (#114121) 2026-07-26 11:10:21 -04:00
Peter Steinberger 2880c82fcf fix(release): handle conflicting npm lock forks (#114122) 2026-07-26 11:08:50 -04:00
Peter Steinberger 08a2f51e4e fix(ui): show the correct main-session title for every agent (#114120) 2026-07-26 10:59:14 -04:00
Peter Steinberger 2b7b0f2440 fix(release): preserve npm lock version forks (#114115) 2026-07-26 10:12:35 -04:00
Dallin Romney 354c6db731 fix(release): retain workflow ref after failed validation (#114104) 2026-07-26 06:13:26 -07:00
Shakker 8f524a23b2 fix: satisfy Buzz CI boundaries (#113419) 2026-07-26 14:09:23 +01:00
Shakker 04cafe601b fix: make Buzz rooms usable by default (#113419) 2026-07-26 14:09:23 +01:00
Shakker fa2ca9dfbb fix: clear recovered Buzz status errors 2026-07-26 14:09:23 +01:00
Shakker ffb00ca624 fix: register Buzz agent identities 2026-07-26 14:09:23 +01:00
Shakker 480a384ad2 fix: isolate Buzz lifecycle failures 2026-07-26 14:09:23 +01:00
Shakker 3e858a2a6f fix: streamline Buzz guided setup 2026-07-26 14:09:23 +01:00
Shakker d52a4e8ca9 fix: preserve Buzz profile identity 2026-07-26 14:09:23 +01:00
Shakker fa51a97b02 fix: enforce Buzz room membership 2026-07-26 14:09:23 +01:00
Shakker 2478134fa6 fix: publish Buzz bot presence 2026-07-26 14:09:23 +01:00
Shakker 08eac200b3 fix: publish Buzz bot profile names 2026-07-26 14:09:23 +01:00
Shakker 0e0698b6bc fix: correct Buzz room authorization guidance 2026-07-26 14:09:23 +01:00
Shakker 439de5577e fix: synchronize Buzz verification with gateway config 2026-07-26 14:09:23 +01:00
Shakker 3cba004098 fix: wait for Buzz gateway reload after setup 2026-07-26 14:09:23 +01:00
Shakker c6bee43a3a fix: preserve Buzz identity during setup navigation 2026-07-26 14:09:23 +01:00
Shakker abe73322a2 test: clean up Buzz lifecycle temp directories 2026-07-26 14:09:23 +01:00
Shakker 2746e35f69 fix: complete Buzz channel contracts 2026-07-26 14:09:23 +01:00
Shakker 40cb79edc9 fix: support standalone Buzz sends 2026-07-26 14:09:23 +01:00
Shakker b6cb3b6d7d fix: verify Buzz room access during setup 2026-07-26 14:09:23 +01:00
Shakker 089931d95d fix: make paused channel setup resumable 2026-07-26 14:09:23 +01:00
Patrick Erichsen aa0d3192d2 feat(channels): add Buzz channel plugin 2026-07-26 14:09:23 +01:00
Peter Steinberger 6a11e44497 fix(config): forward authorized roster removals (#114099) 2026-07-26 08:31:35 -04:00
Peter Steinberger 201e02f226 fix(ui): preserve concurrent background task progress (#114085)
* fix(ui): preserve concurrent background task progress

* fix(ui): retain concurrent task events across stale snapshots

* fix(ui): preserve chronological task snapshot progress

* fix(ui): type buffered task event replay
2026-07-26 08:03:18 -04:00
Peter Steinberger 43a695396d test(memory): mock admitted search sync (#114096) 2026-07-26 07:33:24 -04:00
Peter Steinberger 19a98c873c refactor(auth): finish SQLite-only auth profile cutover (#114033)
* refactor(auth): finish SQLite-only profile cutover

* test(auth): isolate SQLite shadow fixtures

* test(auth): satisfy OAuth refresh lint

* test(auth): infer legacy sidecar fixtures

* fix(auth): fail closed on unreadable stores

* fix(auth): recheck migration readiness on lookup

* fix(auth): preserve compatibility owner checks

* fix(auth): align SQLite cutover proof

* style(qa): format SQLite auth helpers

* fix(auth): recover interrupted SQLite migrations

* fix(auth): bind materialized SQLite refs

* style(auth): clarify receipt hash value

* fix(auth): preserve state-only OAuth routing

* fix(auth): snapshot receipted migration sources

* fix(auth): fail closed across recovery races

* fix(auth): close legacy recovery gaps

* fix(auth): serialize SQLite refresh recovery
2026-07-26 07:27:54 -04:00
Peter Steinberger 53e3f296b1 fix: release ClawHub lifecycle state on CLI exit (#114090) 2026-07-26 06:39:45 -04:00
Peter Steinberger 9eae43bd37 refactor(infra): move exec approvals into the shared SQLite state DB (#114063)
* refactor(infra): move exec approvals into the shared SQLite state DB

Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.

* fix(infra): green CI gates and retire file-era exec approvals tests

Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.

* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
2026-07-26 06:39:23 -04:00