Commit Graph

11156 Commits

Author SHA1 Message Date
Peter Steinberger 6205cf3bb3 fix(cron): stop advertising inactive JSON defaults (#124903)
* fix(cron): make --json help match behavior

* fix(cron): preserve scratch write JSON output

* test(cli): classify cron JSON result commands
2026-08-16 17:32:49 -07:00
Peter Steinberger bda2d54dcf docs(codex): clarify loop relay behavior (#124918) 2026-08-16 16:57:43 -07:00
Peter Steinberger 22ab8e3c2c fix(gateway): stop a plugin dangerous flag from revoking desktop computer.act (#124863)
* fix(gateway): stop a plugin dangerous flag from revoking desktop computer.act

`resolveNodeCommandAllowlistInternal` subtracted every plugin-declared
dangerous command from the composed allowlist, including core's own
`PLATFORM_DEFAULTS` entries and including the pairing allowlist, where core's
dangerous defaults are exempted. `computer.act` is both a desktop platform
default (grant = node-local enablement + pairing approval) and a command that
`registerComputerUseProvider` marks dangerous. Since `cua-computer` became
enabled-by-default on darwin, every macOS-hosted Gateway stripped `computer.act`
from both allowlists, so `normalizeDeclaredNodeCommands` dropped it from the
node's declaration with no pairing upgrade, no prompt, and no record. The
`computer` capability survived because caps were never allowlist-filtered, and
the `computerUse` descriptor was then dropped for the missing command.

Scope the plugin-dangerous subtraction to commands outside the platform-default
base: the flag still keeps a plugin's own surface behind an explicit allow and
still forces a registered invoke policy, but it no longer revokes a command core
declares itself.

Also make cap-without-command unrepresentable. `retainFulfilledNodeCapabilities`
drops a capability when policy withheld commands from its family and admitted
none, and the reconciler records the withheld commands so a refused declaration
is never silent.

* test(gateway): type the computer-use fixture against its contract
2026-08-16 16:27:45 -07:00
Peter Steinberger 0e280f2d33 feat(agents): record agent creation provenance and add roster tree listing (#124828)
Adds an additive agent_provenance table (shared state DB, schema v8) owned
by src/state/agent-provenance.ts. createAgent() records operator/agent
provenance after commit, the system-agent create-agent operation passes its
own id as creator, and Claw installs record created-via claw at their roster
commit point. Agent deletion removes the agent's own row inside the deletion
journal transaction; children keep dangling creator ids as historical fact.
openclaw agents list gains --tree (provenance hierarchy) and JSON provenance
fields.
2026-08-16 16:25:44 -07:00
Peter Steinberger 572e9f907a fix(skills): expand explicit references on agent turns (#124784)
* fix(skills): expand explicit references on agent turns

Route generic Gateway, CLI, webhook, and local agent turns through the same explicit skill-reference renderer as channel auto-replies. Keep original transcript text, preserve unknown slash behavior, and fail visibly for allowlist-hidden skills.

Maintainer review: scoped Option 1 — generic agent turns expand both $skill-name and leading /skill-name args through shared skill rendering; they do not run the channel command dispatcher, and all other slash commands retain their existing behavior.

* fix(skills): bound explicit reference prompts

* fix(skills): prefer allowed reference collisions

* fix(skills): preserve command invocation boundaries

* fix(skills): reject hidden channel slash commands

* perf(skills): skip literal dollar discovery
2026-08-16 16:09:21 -07:00
ClawSweeper 6bddfed530 fix: prevent clipped session selections and Code Mode timer crashes (#124879)
* fix(agents): support timers in code mode

* fix(ui): keep selected sessions clear of scrollbars

* fix(agents): propagate timer cancellation after resume

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-16 16:03:54 -07:00
Peter Steinberger 1f1aa88f14 perf(ci): split Windows CI two ways instead of one serial lane
`checks-windows-node-test` ran the whole 222-266s Windows body in a single
Blacksmith lane, so every run that scheduled it finished at 287-334s against a
~180s plateau. Measured across 45 canonical main runs it was the wall in all
seven runs that triggered it (324-519s vs a 210s median elsewhere).

Blacksmith's Windows class admits exactly 2 concurrent jobs (run 31865243804),
which is why the earlier 3-part split failed: its tail queued 211s behind a
finished part. Pin the split width to that measured capacity instead. The two
parts are balanced by measured per-project wall time -- part 1 keeps the old
list 1 plus list 2's non-infra projects (~108s), part 2 takes list 2's infra
project plus the old list 3 (~112s) -- so both land near 113s and the lane
stops setting the wall.

One canonical partition now serves every backend; the github-mode/dispatch
3-part variant and its separate file inventory are gone, along with the
single-lane `task: test` case and the per-row `matrix.runner` the collapsed
lane needed. The complete 65-file Windows inventory is preserved exactly, and
the guards pin the width on both sides (mutation-checked: restoring 3 parts
fails the matrix guard).
2026-08-16 16:02:24 -07:00
Peter Steinberger eadde4a9d0 fix(ui): show recovery when Control UI does not render (#124861)
* fix(ui): retire bootstrap fallback after render

* test(ui): avoid returning from Promise executor
2026-08-16 15:46:15 -07:00
Peter Steinberger 6c66f48a7c fix(cli): emit one JSON failure contract for --json invocations (#124849)
* fix(cli): unify JSON failure output

* test(cli): update skills verify failure envelope
2026-08-16 15:09:58 -07:00
Peter Steinberger 2d887acba3 fix(slack): drop the tool-call receipt from finished progress cards (#123851)
The Block Kit session card terminalized into a '🛠️ N tool calls · ⏱️ Ns'
footer, leaving a per-turn receipt in the Slack transcript on the default
progress path. PR #122976 deleted the same receipt from the native progress
surface but left this sibling rendering it.

Finished cards now keep only the durable diff stat and the session link;
tool-call and elapsed counters stay live working state. Also corrects the
documented channels.slack.streaming.mode default, stale since #122552.
2026-08-16 14:58:23 -07:00
Peter Steinberger d23246770a fix(memory): report indexed SQLite sessions (#124834)
* fix(memory): report indexed SQLite sessions

* refactor(memory): remove unused state path export
2026-08-16 14:38:15 -07:00
Peter Steinberger 33f3b72a19 refactor(plugin-sdk): extract stream and SecretRef primitives (#124835) 2026-08-16 14:30:11 -07:00
Peter Steinberger 63a3a958f4 fix(browser): support Chrome Web Store native bootstrap (#124775)
* fix(browser): support Chrome Web Store native bootstrap

* chore: keep browser release note in PR body

* docs(browser): document Store identity trust boundary

* docs(browser): correct Store recovery guidance
2026-08-16 14:20:47 -07:00
Peter Steinberger 046a9ffdf4 fix(mcp): make Codex approval dead ends actionable (#124766)
* fix(mcp): make Codex approval dead ends actionable

* fix(mcp): preserve native approval fallback

* fix(mcp): configure saved approval modes

* fix(mcp): preserve saved Codex metadata

* style(mcp): simplify saved metadata spread
2026-08-16 14:00:08 -07:00
Peter Steinberger 0c6040eb14 fix(runners): align reclaim results with runtime (#124791)
* refactor(runners): prepare placement move targets

* fix(runners): preserve normalized dispatch targets

* style(protocol): format reclaim result type
2026-08-16 13:46:00 -07:00
Peter Steinberger 4bc37cd597 fix(state): copy-safe agent database registry via state-relative paths (schema v9) (#124728)
* fix(state): store in-root agent database registry paths relative to the state dir (schema v9)

Copied state directories retained stale absolute agent database registry rows. The combined gateway store then merged old and new copies, causing every sessions.list request to fail with SessionCanonicalKeyMigrationRequiredError.

Schema v9 stores in-root registry paths relative to the state directory; migration rewrites eligible rows, deletes stale duplicates, and preserves external paths.

* fix(voice-call): describe state-relative registry migration

* fix(state): preserve registry locator traversal

* test(macos): move PortGuardian schema-version boundary to v9

The store opens any state database up to maximumSupportedSchemaVersion,
which the schema v9 bump moved from 8 to 9. Shift the supported/newer
partition in the boundary test accordingly.

* fix(state): re-anchor copied default-layout registry rows instead of deleting them

Resolve the ClawSweeper P1 by preserving genuine external default-layout registrations, re-anchoring copied rows when an in-root counterpart exists, and deleting only dual default-layout conflicts.
2026-08-16 13:31:32 -07:00
Peter Steinberger 2523943ef1 feat(codex): name additional session-catalog homes (#124807) 2026-08-16 13:30:49 -07:00
Peter Steinberger 4d4ee7bc86 refactor(plugin-sdk): consolidate session catalog families (#124801) 2026-08-16 13:25:11 -07:00
Peter Steinberger bc24508f5e docs: correct code mode runtime contract (#124792)
Amp-Thread-ID: https://ampcode.com/threads/T-01a00b7c-b9f0-73d5-8fb7-e619e8e458e1

Co-authored-by: Amp <amp@ampcode.com>
2026-08-16 13:16:10 -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 4667d188f2 fix(codex): honor session node exec host (#124777) 2026-08-16 12:41:48 -07:00
Peter Steinberger d5db5854fe fix(cli): explain empty directory lookups (#124753)
* fix(cli): explain empty directory lookups instead of printing nothing

* fix(cli): distinguish unsupported self lookups
2026-08-16 12:39:36 -07:00
Peter Steinberger 6cc72f9b3d fix: honor context caps on Codex-routed models (#124735)
* fix(codex): forward authored context caps

* test(codex): keep binding fixtures uncapped

* fix(agent-harness): separate authored context cap
2026-08-16 12:31:20 -07:00
Peter Steinberger 75bcc5cebe fix(ui): keep Control UI device identity working on plain-HTTP origins (#124724)
* fix(ui): keep Control UI device identity working on plain-HTTP origins

@noble/ed25519 defaults its SHA-512 provider to crypto.subtle, which
browsers gate to secure contexts, so device identity silently vanished
on http:// LAN dashboards and connects fell back to shared-credential
auth with no pairing. Wire a lazy pure-JS @noble/hashes fallback for
SHA-512 and the fingerprint SHA-256, and drop the isSecureContext gate
in the connect path. Secure contexts keep the platform digests and pay
no startup bytes: the fallback loads as its own lazy chunk, kept out of
the gateway-runtime startup chunk on purpose.

* test(ui): cover device identity minting and signing without crypto.subtle

New jsdom regression suite fails on pre-fix code (subtle-less crypto stub
with getRandomValues, which real insecure contexts keep). Rewrites the
gateway connect tests that previously asserted the device-less insecure
fallback: an insecure context now attaches a device identity.

* docs(web): plain-HTTP dashboards now pair with a device identity

The signing key never crosses the wire, so HTTP+pairing is strictly
stronger than the old HTTP token-only fallback; HTTPS (Tailscale Serve)
stays the recommendation for transport privacy.

* fix(ui): drop unnecessary boolean literal compare in secure-context timing meta

* test(ui): declare device.id on the connect-frame test shape

* test(ui): split the subtle-less scope-upgrade e2e into the two real invariants

Without crypto.subtle the browser can now sign, so the banner offers the
explicit admin upgrade; manual-only guidance is reserved for browsers that
cannot mint an identity at all (no WebCrypto RNG). Also corrects the
connect-path comment: blocked storage yields an ephemeral identity, only a
failed mint degrades device-less.

* fix(ui): address review findings on the HTTP device-identity path

- Storage-blocked pages keep one stable in-memory identity per page
  lifetime instead of minting a fresh unpaired key on every reconnect,
  and a write-rejecting store no longer fails the mint (regression tests
  bite pre-fix).
- Connect timing now reports the real browser secure-context fact via a
  shared browserSecureContext() helper instead of inferring it from
  device-identity presence.
- Docs state the accepted trusted-proxy contract: browsers attach a
  device identity on every origin, so first connects follow the standard
  pairing flow (deviceAutoApprove or a one-time approval); device-less
  admission remains only for browsers that cannot mint an identity.

* refactor(ui): trim the connect-path additions under the max-lines cap
2026-08-16 12:20:00 -07:00
WhatsSkiLL 08786d32b2 feat(codex): show sessions from additional homes (#124660)
* feat(codex): register additional session homes

* fix(codex): preserve automatic session homes

* docs(codex): explain automatic home discovery around sessionCatalog.homes

---------

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-16 12:14:10 -07:00
Peter Steinberger ebe7af218c fix(cli): allow hook toggles to select an agent (#124761) 2026-08-16 12:11:44 -07:00
Peter Steinberger 5168b5d4ab feat(voice-call): sessionScope "main" routes calls into the agent main session (#124708)
* feat(voice-call): add sessionScope "main" for main-session call routing

Inbound and outbound calls can now share the configured agent's main
session instead of a dedicated voice session. The new scope resolves
through the existing explicit-key canonicalization path, honoring core
session.mainKey and global-scope aliasing. The inbound webhook path now
forwards coreSession like every other resolver call site.

* style: format events.test.ts

* test(voice-call): split events.test.ts under the max-lines cap
2026-08-16 11:47:47 -07:00
Peter Steinberger 57ebd20566 fix(cli): fail absent service start and restart (#124711)
* fix(cli): fail uninstalled service mutations

Treat Gateway and Node start/restart as failures when no managed service is installed, while preserving absent-service stop as an idempotent success.

* docs(cli): clarify gateway restart recovery
2026-08-16 11:38:45 -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 4fb27369ee perf(ci): keep the real-Gateway UI E2E lane on Blacksmith in hybrid mode
`checks-ui-e2e-real-gateway` folded `hybrid` into the `github` breaker clause
instead of taking the attempt-gated route its sibling `checks-ui-e2e` uses, so
it was the only Chromium lane pinned to hosted capacity on attempt 1. Measured
across 46 canonical main runs it averaged 205s (max 225s) against a 150-190s
plateau, making it the run's slowest job whenever the compact Node matrix
behaved.

Route it like `checks-ui-e2e`: Blacksmith 16-vCPU on attempt 1, hosted on rerun,
manual dispatch, fork, and same-repo PR retry. Cache inputs stay on the shared
pnpm store for both backends, matching the sibling lane.

The guard now derives both Chromium lanes' `runs-on` from one pinned template so
a hosted-only divergence cannot return unnoticed; mutation-checked by reverting
the workflow clause (2 failures).
2026-08-16 11:15:47 -07:00
Peter Steinberger 569835e8b0 fix(ci): align baseline ratchets across local and CI (#124720) 2026-08-16 11:07:31 -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
Peter Steinberger 63401b730b fix: keep doctor security conditions as single findings (#124666)
* fix(doctor): record security finding severity

* refactor(security): keep audit severity internal

* fix(security): preserve channel finding severity
2026-08-16 10:24:40 -07:00
Peter Steinberger 715c379fd9 refactor(config): consolidate context budget to one per-model knob (#124665)
* refactor(config): consolidate context budget settings

* test(config): type legacy context fixtures

* test(config): align context budget fixtures

* fix(status): honor runtime context discovery

* docs(config): clarify context budget fallbacks

* fix(ci): resolve context budget lint failures

* test(ci): align context budget shard fixtures

* fix(models): preserve catalog context metadata

* fix(config): surface context migration diagnostics

* test(plugin-sdk): keep live catalog coverage focused
2026-08-16 10:05:01 -07:00
Peter Steinberger 4d872fbfbc feat(nodes): expose installed worker bundle status (#124640)
* feat(nodes): expose installed worker bundle status

* perf(nodes): defer bundle status validation

* fix(nodes): tighten bundle status type contracts

* docs(gateway): document node worker bundle status

* refactor(gateway): split runner inventory runtime

* test(ui): keep healthy device status quiet

* fix(ui): preserve steer target ordering

* test(ui): isolate catalog handoff lifecycle
2026-08-16 10:04:46 -07:00
Peter Steinberger bdcc6836e0 refactor(gateway): retire the Control UI device-auth migration window (#124667)
* refactor(gateway): delete the retired Control UI device-auth migration window

The one-time remediation window for the retired
gateway.controlUi.dangerouslyDisableDeviceAuth break-glass threaded a pending
state machine through the connect pipeline, startup bootstrap, lifecycle,
request context, device management, security audit, the hello frame, and a
Control UI banner flow — steady-state runtime carrying a retired-shape shim
the architecture reserves for doctor. The window is closed: the retired key
is now fully inert, doctor still detects and removes it, and a browser that
never completed the migration pairs through the normal device flow (one
approval, no data loss).

Deleted with it: the config-machine-state import of the legacy flag, the
hello deviceAuthMigration field (optional; clients validate envelopes only,
so older gateways emitting it stay compatible), the migration-bound device
management authz states, the requireNoPairingCapableOperator approval mode,
the effective-operator pairing event emitter whose only subscriber was the
migration completion, and the Control UI banner, loader, overlay wiring,
i18n strings, and e2e scenario support. Swift and Kotlin protocol models
regenerated.

Live-verified on an isolated gateway with the retired key present in config:
clean boot with no migration warning, silent CLI pairing and silent local
scope widening unaffected, and doctor reporting the key as retired-and-inert.

Net -1111 production LOC.

* fix(ci): finish migration-window cleanup surfaced by the gates

The doctor migration for the retired key kept its "Preserved for remediation"
change text and describe; both now state plain removal, with the retired
tests updated. peekStoredDeviceIdentityId lost its only production consumer
with the deleted banner loader and is removed with its test mock. Also fixes
the unrelated no-unnecessary-boolean-literal-compare lint break that #124636
landed on main in scripts/check-changed.mts (truthiness is equivalent for the
boolean-or-undefined TTY probe).

* fix(ui): drop the retired-key device-auth reader from the security summary

Review findings on the migration-window removal: the Control UI security
summary still derived its "Device auth" row from the retired
dangerouslyDisableDeviceAuth key, rendering device auth as disabled on
configurations that merely retain the inert key. Device auth is now
unconditionally enforced, so the row and its derivation are removed rather
than pinned to a constant. The build-admission test also tracks and removes
its temporary device-identity databases after each run.
2026-08-16 09:39:15 -07:00
Marvinthebored 2404d41de2 fix(codex): preserve delegation when image generation is denied (#124598)
Forward exact audited tool denies into agent harnesses so Codex can disable native image generation without dropping delegation. Fail closed when managed policy forces image generation and rotate retained threads when effective native config changes.

Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
2026-08-16 21:42:38 +05:30
Jason (Json) eb174bcaff fix(codex): candidate checks reject a mismatched app-server (#124137)
* fix(codex): validate managed app-server candidates

* docs(codex): document managed binary preflight

* test(codex): review managed preflight execution

* docs(codex): document managed app-server check

* fix(pr): support macOS system Bash operation locks

* fix(codex): scope managed preflight to Codex agents
2026-08-16 09:27:12 -06:00
Peter Steinberger eeffa53b20 improve(plugins): compile externalized plugins in source builds (#124639)
* build(plugins): compile externalized plugins into local dist

* test(plugins): assert native external plugin loading

* chore(plugins): keep source runner asset scan unchanged

* refactor(plugins): isolate external local dist builds

* test(plugins): codify external artifact precedence

* test(plugins): preserve contract path boundary
2026-08-16 08:21:17 -07:00
Peter Steinberger 5a0fe2232b fix(crabbox): keep active cloud worker leases alive (#124615)
* fix(crabbox): heartbeat active worker leases

* fix(crabbox): clarify heartbeat upgrade guidance

* fix(crabbox): bound heartbeat before idle expiry
2026-08-16 08:16:29 -07:00
Peter Steinberger 9873b0f6ad feat(ui): named glyphs and custom emoji entry for session icons (#124629)
* feat(protocol): add named session icon glyphs

* feat(ui): add session glyph and custom emoji picker

* docs: describe named and custom session icons

* fix(ui): give the custom emoji input an accessible name

ClawSweeper P2: the input had no label relationship; assistive tech announced an unnamed edit control.

* fix(protocol): guard the v-flag icon regex for browser module loads

ClawSweeper P1: the picker's static import evaluates this module in the browser; pre-Unicode-Sets engines threw at module scope and took down the session menu. Lazy capability-guarded construction; such engines fall back to the grapheme heuristic as client pre-validation while the Gateway keeps exact RGI validation.
2026-08-16 07:55:31 -07:00
Peter Steinberger 8d86fe79a6 perf(ci): route runners by contributor trust instead of fork status
Fork pull requests were pinned to GitHub-hosted runners while maintainer
PRs rode Blacksmith. That split arrived with ci.yml's first commit
(f4f990a) and was never a reasoned posture -- no rationale in docs,
commit bodies, or the ci-limits skill -- and this week's Blacksmith
tranches widened the gap: maintainer walls are ~3:36 while the last fork
PR I measured (#124633) took 13m0s. We already paid for it once in
#118530, which raised the cross-repo artifact budget to 35 minutes
because contributor PR #117992 timed out twice on hosted capacity.

Runner choice now follows author_association: OWNER, MEMBER, COLLABORATOR,
and CONTRIBUTOR get Blacksmith; FIRST_TIME_CONTRIBUTOR, FIRST_TIMER, NONE,
and MANNEQUIN stay on hosted runners, which are free for public repos, so
an unreviewed author cannot spend Blacksmith capacity. Earning the fast
path requires a landed commit, which requires a maintainer merge.

Note for anyone tempted to trim that list: maintainers report CONTRIBUTOR
here, not OWNER/MEMBER, because org membership is concealed -- steipete's
74 sampled PRs are all CONTRIBUTOR. Dropping CONTRIBUTOR would move
maintainer PRs to hosted.

Scope is deliberately runner-only: 27 runs-on clauses. The 34
dependency-cache/use-actions-cache conditions and 6 job ifs stay
fork-gated, because cache poisoning is a different risk from runner
choice -- a fork run still never writes an archive a trusted run restores.

Verified by evaluating all 25 configurable runs-on expressions: maintainer
PR 12/25 Blacksmith, returning-contributor fork 12/25 (identical), unknown
author 0/25, FIRST_TIME_CONTRIBUTOR 0/25, push to main 12/25 unchanged.
Guard tests gained trusted/untrusted fork cases; 119 pass.
2026-08-16 07:43:07 -07:00
Peter Steinberger 3e2edc7f22 improve(ci): cut hybrid compact runner overhead (#124622)
* perf(ci): refit compact planner hints for Blacksmith

* test(ci): type default runner backend fixture
2026-08-16 07:12:32 -07:00
Peter Steinberger 086f5916fa fix(gateway): local devices stall on scope upgrade pending approval despite autoApproveLocal (#124589)
* fix(cli): announce when nodes list degrades to paired-only data

tryReadNodeList swallowed every enrichment failure, so the unfiltered nodes list silently rendered a table without connected/commands state. The fallback is now announced on stderr, keeping --json stdout parseable.

* fix(gateway): silently widen local pairing scopes as documented

`autoApproveLocal` has documented "silently approves pairing, role upgrades,
and scope upgrades from trusted local connections" since the loopback
auto-approval landed, but a later hardening pass forced every scope-upgrade
pairing request non-silent. That block protected nothing locally — silent
initial pairing grants a fresh identity arbitrary requested scopes, so any
local process could mint a new keypair instead of upgrading — while it
stranded every row-authorized client (CLI, native apps, node hosts) on a
manual approval no local surface could perform.

Scope upgrades now ride the same silent-local rule as initial pairing, with
one new restriction that encodes the real boundary: the connect must itself
prove local-grade credentials (auth mode none, or the shared token/password).
Identity-proxy connects (tailscale, trusted-proxy) and bearer device tokens
never did, so their pairing rows remain a durable scope cap, and
`autoApproveLocal: false` still forces manual approval for everything.

The silent self-grant also approves the union of requested plus already-held
scopes: approval merges the existing row back in, so a client requesting only
its missing scope no longer fails the caller-authority check.

The decision surface shrinks with the behavior change: the scope-upgrade veto
contradicted shouldAllowSilentLocalPairing's answer, the CLI-container
locality was a duplicate of the shared-secret-loopback predicate, and three
classifiers re-derived the same shared-secret auth check.

Live-verified on an isolated auth-none loopback gateway: a CLI identity
paired at operator.pairing silently widens to operator.read on the next wider
command, with the scope-upgrade security audit line still emitted.

* test(gateway): rewrite veto-era pairing locks for silent local widening

Five control-ui pairing suite cases and the silent-scope-upgrade poc locked
the removed non-silent veto. The suite cases now assert the new invariant
(local shared-auth upgrades widen silently, malformed and legacy-shaped rows
are repaired by the fresh approval, node-then-operator grants complete
without a stranded prompt), and the poc case now exercises the surviving
manual-approval gate by disabling autoApproveLocal after its watcher
connects, keeping the pairing-request broadcast and remediation-hint
assertions alive on a real remaining path. The voice-node bootstrap failure
was leakage from the aborted sibling tests, and passes again once they
complete their flows.
2026-08-16 07:02:24 -07:00
Peter Steinberger d8697d5956 chore(ci): drop the disabled timing-summary job and unread build outputs
Two pieces of dead workflow surface, found auditing where CI time goes:

- ci-timings-summary was hard-disabled (`if: ${{ false && ... }}`) with a
  TODO to re-enable or delete it after the next timing-optimization
  review. That review happened; the local `pnpm ci:timings` helper is what
  we actually use, and docs already pointed there. The job carried a
  25-entry needs list that had to be kept in sync to stay lintable.
- build-artifacts exported four `*-result` outputs that no job or workflow
  reads.

Removing the job lets the gate guard assert the stronger invariant it
wanted all along: ci-gate needs *every* job in the file, so a new lane
cannot slip in ungated (28 jobs, 27 gated, zero exceptions).

No runtime behavior changes: the job could never run and the outputs had
no consumers. Also audited every `pnpm <script>` and `node scripts/...`
reference in ci.yml for rot -- all resolve.
2026-08-16 06:27:11 -07:00
Peter Steinberger 18d130fc1b fix(workers): bound node bundle retention (#124590)
* fix(workers): bound node bundle retention

* docs(runners): track bundle retention
2026-08-16 06:25:05 -07:00
Peter Steinberger eab287d224 docs(gateway): correct node pairing storage (#124571) 2026-08-16 05:46:44 -07:00
Peter Steinberger ebd23631ae perf(cli): build root help from plugin metadata (#124562) 2026-08-16 05:43:35 -07:00
Peter Steinberger bba57301d9 perf(ci): fold built-runtime verifiers into the artifact-check wave
build-artifacts is the wall's pole in 4 of the last 5 main runs (171-186s,
~15-20s ahead of the next lane), so its serial steps are the wall. The
Doctor plugin-index proof, singleton smoke, and startup-memory check ran
as their own 13s step even though they are independent dist readers that
the 47s artifact-check wave could absorb.

They now run inside that wave: on Blacksmith all seven start together, so
the verifiers cost the wave's max instead of 13s of serial time; hosted
runners still serialize the three through run_verifier so the RSS ceiling
measures an unloaded process. The step drops its selection gate because
the verifiers always run -- each artifact check already self-gates on its
own RUN_* flag, so a run with no checks selected still verifies.

Proof: extracted the step body and ran it with stubbed pnpm/node. Both
modes behave (Blacksmith 7 checks started, hosted-with-nothing-selected
still runs the 3 verifiers), and a failing verifier exits 1 with its
::error annotation in both -- the wave cannot swallow it.
2026-08-16 04:52:56 -07:00
Peter Steinberger dbad5e385d refactor(workers): separate runner consent from capacity (#124356)
* refactor(workers): separate runner inventory state

* docs(runners): track inventory cleanup

* test(workers): use bundle-only launch wire

* test(workers): update optional inventory fixture

* test(workers): validate prewarm inventory capability

* fix(workers): version runner inventory cutover
2026-08-16 04:46:38 -07:00