The parity test loaded the test-only bundled-plugin public-surface
helper from src/plugins, which the extension-test boundary forbids for
core source files; every PR touching the build-artifacts shard now
fails. Moving it to test/plugins keeps the dynamic-import lane
protection and passes both the boundary and parity suites.
The provider auth overview passed the caller's synthetic-auth object
through to the JSON payload. Status hands it the full runtime shape,
which also carries the raw credential, so structural typing let live
API keys/tokens ship in a diagnostic surface (#104713). Re-project to
the declared value/source fields at the overview boundary.
Fixes#104713
* feat(gateway): flag sessions with attached automations and disable bound cron jobs on archive
Session rows now carry hasAutomation, derived from a lifecycle-owned index
over the cron service's in-memory jobs; cron events push refreshed rows so
badges stay live. sessions.patch { archived: true } disables enabled cron
jobs bound to the session (locked binding re-check, internal/operator-admin
callers only); restore intentionally does not re-enable them.
Refs #104700
* feat(ui): sidebar session state slot and worktree/automation badges
The run spinner moves into a leading state slot shared with the unread dot,
keeping the timestamp visible during runs; muted fork/clock badges sit after
the title (outside the trail/action overlap so pinned rows and touch devices
keep them). New strings localized via ui:i18n:sync (English fallback pending
a keyed translation run).
Refs #104700
* test(gateway): pin cron binding broadcast test to the event mechanism
The shard shares one process; session-store state from earlier tests can make
the row load return null, which legitimately produces a keyless refresh
payload. Row-field projection stays covered by session-utils and
session-automation-index tests.
* feat(ui): fold Skills and Skill Workshop into a Plugins hub
One sidebar entry (Plugins) now covers plugins, skills, and skill-workshop
routes via a shared hub tab strip (Installed / Discover / Skills / Workshop).
Routes and URLs are unchanged; /settings/plugins gains ?tab= deep links and
the sidebar highlights Plugins for all hub routes.
Also latches Skill Workshop proposal loading so updated() no longer re-kicks
loads while one is pending or after one fails - the early-return finally
used to spin the page at 100% CPU whenever skills.proposals.list stalled.
* feat(mock): plugin catalog and skill-workshop fixtures for the mock harness
* docs(web): describe the Plugins hub tabs in the Control UI guide
* test(ui): backfill initialTab in plugins route-data fixtures
* fix(ui): review fixes for the plugins hub
- plugins route declares loaderDeps on the ?tab= param so query-only
navigation and back/forward re-run the loader instead of reusing the
cached match with the previous tab
- catalog tab clicks navigate so the URL and history stay in sync with
the documented ?tab=discover deep link (local switch stays instant)
- skill-workshop error banner gains a Try again button so a transient
proposals-list failure is recoverable without remounting the route
* fix(ui): restore the Installed tab on bare plugins URLs in history navigation
* fix(ui): manual-activation hub tablist with cross-route focus hand-off
Arrow keys only move focus now; activation stays on click/Enter so arrowing
can never unmount the strip under the user's focus. Keyboard activation of a
cross-route tab hands focus to the destination strip's active tab, and the
hub-route set uses a Set per lint.
* fix(ui): defer hub-tab focus reclaim until the strip is connected
* fix(ui): skip focus recovery for same-tab hub activation
* docs(css): note the hub-panel flex ownership for the workshop board
* fix(ui): adapt hub test to retired Overview route
* chore(i18n): sync locale bundles for the plugins hub keys
* docs(map): regenerate for the plugins hub section
An unprobeable api-key choice left its flag/env literals unchecked while CI
stayed green (ClawSweeper finding). The probe now supplies a real agent dir
and placeholder preflight opts (sentinel still bound to the declared
optionKey only, preserving the key-correctness proof), so every api-key
method must reach resolveApiKey — this immediately exercised
cloudflare-ai-gateway's account/gateway preflight path.
Loading built plugin dists pulls large module graphs into the shared vitest
worker cache and broke co-resident vi.mock unit tests (memory-host-sdk
embeddings, checks-node-compact-large-2). An explicit dynamic import of the
surface loader trips the lane classifier's dynamic-import rule, moving the
file to the plugins project where dist loading is the norm.
Silent skip on missing runtime registration recreated the drift class the
test guards (ClawSweeper finding); capability-only plugins now must register
zero text providers to be exempt.
context_overflow and transient (timeout/server_error/overloaded) FailoverError
reasons now classify structurally like billing/rate_limit already do, instead
of relying on message text that may lack overflow or HTTP-status tokens. A
typed transient failure without a leading status token now takes the single
transient retry; typed context overflow suppresses the generic failure reply.
Part of #104219 (row 12).
gateway-startup-plugin-ids duplicated the built-in model-API set byte-for-byte
with nothing enforcing sync; provider-config-owner now owns it. New parity
test proves bundled provider manifests and runtime provider.auth declare the
same optionKey/cliFlag/envVar/method literals, guarding against silent
wizard-vs-runtime auth drift. Part of #104219 (row 12).
Drops the last non-doctor whole-store resolveSessionStoreEntry read in the
reply pipeline; exact working-set key plus accessor disk read match the
sibling refresh sites in runPreparedReply. Part of #104219 (seam 8).
* fix(core): make indexed access explicit in auto-reply, infra, and config
Part 1/3 of the src NUIA phase-3b burn-down (#104600): iteration and
destructuring over index reads, boundary guards on parsed input, and
named invariants. Config path walkers bind the path head once; SQLite
migration key handling is hoisted without query-shape changes.
* fix(core): make indexed access explicit in cli, gateway, commands, security, shared
Part 2/3: argv/token selection restructured, gateway event/attachment
invariants named, security parsers stay fail-closed (invariant
violations throw), edit-distance matrices access checked entries.
* fix(core): make indexed access explicit across remaining src surfaces
Part 3/3: channels, plugins, process, cron, plugin-sdk, media, logging,
tui, hooks, daemon, and small directories. Latent bug fixed: a tailnet
resolver could leak undefined through a string|null contract and now
fails with a descriptive local error.
* fix(core): keep optional boundaries optional after per-commit review
Review findings: expectDefined misused where absence is a legitimate
state. CLI --profile/route-args missing next tokens take their existing
miss paths; help normalization compares --help against the last
positional again; first-time plugin install spreads absent cfg.plugins;
denylist scan iterates manifest dependency entries instead of throwing
on omitted sections; tailnet resolver returns a guaranteed string at
the source instead of a caller-side undefined throw.
* refactor(core): closed-key provider labels and honest optional passthroughs
PROVIDER_LABELS becomes a satisfies-typed closed record (static reads
provably defined; dynamic lookups go through providerUsageLabel with
honest string|undefined). Status-scan overview passes its optional
params through unchanged instead of asserting them.
* fix(channels): make getChatChannelMeta honestly optional
The original signature claimed ChatChannelMeta while leaking undefined
on bundled channel id metadata drift; three of four callers already
handled absence. The return type now says so, and the one assuming
caller falls back to the raw channel label.
* fix(core): index-safety for post-rebase main drift
Covers the sqlite-sessions flip and auth-source-plan code that landed
mid-phase, plus the channel-validation test consuming the now honestly
optional getChatChannelMeta.
* refactor(channels): split chat-meta accessors along the SDK contract
getChatChannelMeta keeps its shipped plugin-SDK signature (defined for
bundled ids, fail-loud on impossible misses); new findChatChannelMeta
carries the drift-tolerant optional contract for core auto-enable and
formatting paths.
* fix(qa-channel): own channel metadata instead of a guaranteed-undefined catalog lookup
qa-channel spread getChatChannelMeta over an id that is never in the
bundled catalog, shipping an empty setup meta by accident; the fail-loud
SDK accessor exposed it. The channel now declares its metadata once.
* fix(gateway): heartbeat projection lookahead is optional at the transcript tail
expectDefined wrapped messages[i + 1] whose absence on the final message
is the normal case; the adjacent ternary already handled it. Restores
the plain optional read with an explicit guard in the pair condition.
* fix(plugin-sdk): channel plugin factory tolerates non-bundled channel ids again
createChannelPluginBase spreads bundled catalog meta for ANY channel id,
where absence is the normal case for external plugins; the resolver is
honestly optional again while the exported bundled-id accessor keeps the
fail-loud contract.
* fix(core): spreads of optional config sections stay optional
Fresh-setup and first-install paths (crestodian setup inference, hook
installs, agent config base, target agent models) legitimately lack the
section being rebuilt; spreading undefined is the shipped {} semantics.
Removes the remaining gratuitous assertion wraps found by tree audit.