Commit Graph

81255 Commits

Author SHA1 Message Date
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 468d621f9a fix(update): complete high-volume stable upgrades without stalls (#124651)
* fix(testing): restore published upgrade validation

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* perf(sessions): batch transcript archive workers

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* fix(cli): exit after legacy agent turns

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* fix(testing): reset upgrade fixture ports

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* chore(lint): shrink assertion safety baseline

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-16 08:14:56 -07:00
Peter Steinberger 3e0634f7ed fix(gateway): join concurrent node worker tunnel starts (#124638)
* fix(gateway): join concurrent node worker tunnel starts

* fix(gateway): keep node tunnel shutdown cancellable
2026-08-16 08:12:22 -07:00
Peter Steinberger d61202b128 fix(ui): redact errors across Control UI surfaces (#124552)
* fix(ui): redact errors through canonical formatter

* fix(ui): route remaining error paths through canonical redacting formatter

* fix(ui): route worktrees-page and remaining raw error renders through redacting formatter

* style(ui): normalize labs import order

* fix(ui): preserve structured error messages through formatter

* test(ui): align memory refresh warning with canonical formatter

* fix(ui): redact chat background-task rail failures

* fix(ui): redact all externally-sourced text at render boundaries

* fix(ui): keep external redaction paths type-safe
2026-08-16 08:09:50 -07:00
Peter Steinberger 688068101d fix(discord): warn when webhook persona send falls back to bot send (#124650)
The bound-thread webhook persona path swallowed every pre-dispatch
failure (deleted webhook, revoked token, rate limit) and silently fell
back to the plain bot send. The fallback is intended — persona delivery
is best-effort — but the failure was invisible, so a broken webhook
binding degraded every reply in the thread with no operator signal.

Warn with the error before falling back. Post-dispatch failures still
rethrow (webhookSelected) to avoid duplicate sends.
2026-08-16 08:06:17 -07:00
Peter Steinberger b90d7efddb fix(ui): surface command-palette chat search failures (#124648)
The palette's session search swallowed every gateway error and rendered
the plain "No results" empty state — a failed search (e.g. sessions.list
rejecting with a store-needs-doctor migration error) was
indistinguishable from a successful search with zero matches. Silent
failure on a default path is the worst bug class in this repo.

Track a sessionSearchFailed flag (set only for the current request id
while the palette is open, cleared on every new query/clear), and render
"Chat search failed — check the gateway logs and retry" in the empty
state instead. Navigation commands stay usable throughout.
2026-08-16 08:00:05 -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 26f980d9f5 perf(agents): skip dead restart session lookups (#124616) 2026-08-16 07:53:55 -07:00
Peter Steinberger 1113d1d245 refactor(sessions): delete dead maintenanceReport and evict-budget surfaces (#124641)
Both SQLite lifecycle producers have returned maintenanceReport: null and
unreferencedArtifacts: null since the legacy JSON store collapsed
(c440ae3e86), leaving cleanup-service carrying a 60-line dead ternary
branch, dead applied-count computations, and dead fallback reads. The
evict-budget cleanup action was equally dead: budgetEvictedKeys has been
an always-empty set since the SQLite flip (0a8e3604ba), so the action
could never be rendered.

Delete both fields from SessionEntryLifecycleMutationResult, collapse the
apply-summary to the single live branch, and drop the evict-budget action
and its always-empty plumbing through cleanup previews and the CLI
renderer. Disk-budget eviction reporting lives in the diskBudget summary
field, which remains.
2026-08-16 07:48:57 -07:00
Peter Steinberger e38a645a75 fix(cron): honor explicit delivery agent ownership (#124631)
* fix(cron): honor explicit delivery agent ownership

* test(gateway): honor explicit ownership in send mock
2026-08-16 07:45:26 -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 63bae0d69f fix(pr): release clean-exit locks despite escaped notify-pipe holders (#124614)
* fix(pr): release clean-exit locks despite escaped notify-pipe holders

Fixes #124583

* fix(pr): require leader completion before lock release

Addresses the ClawSweeper P1 on #124614.
2026-08-16 07:34:03 -07:00
Peter Steinberger 6a82431494 refactor: replace runtime mock seams with injection (#124634) 2026-08-16 07:33:13 -07:00
Peter Steinberger 5ce0126194 fix(acpx): require the complete runtime contract behind the lazy proxy (#124628)
The ACPX lazy runtime proxy optional-chained every forwarded hook and
fabricated success when the resolved runtime lacked one: doctor() returned
{ ok: true }, getStatus() returned {}, and setMode/setConfigOption/
prepareFreshSession silently no-opped. Combined with the session-reset
service calling prepareFreshSession through two raw optional chains, an
unregistered or incomplete backend made reset preparation a silent no-op:
the reset appeared to succeed while the backend kept resuming the old
conversation.

Contract decision: the SDK-level AcpRuntime type keeps optional hooks
(third-party backends legitimately omit capabilities, and core consumers
gate on presence), but every runtime the ACPX extension resolves
implements the full surface. A new CompleteAcpRuntime type in the
extension makes those hooks required for proxy-resolved runtimes, so an
absent hook is a compile error instead of a fabricated runtime success.
The now-dead legacy runTurn->startTurn adapter (~190 LOC) is deleted with
its tests; the proxy forwards startTurn directly.

The session-reset-service call sites consolidate onto the control-plane
owner helper tryPrepareFreshManagerRuntimeSession, which now records a
visible outcome for every skipped path (backend not registered, hook not
supported) instead of returning silently; that also covers the same
silent skip in manager.close-session.

Regression tests fail pre-fix: proxy hooks reject on contract-violating
runtimes instead of fabricating success, and skipped fresh-session
preparation is recorded.
2026-08-16 07:29:50 -07:00
Peter Steinberger 29e8bebcbe fix(gateway): warn on placement session evidence pipeline failure (#124635)
The worker placement session evidence resolver swallowed every pipeline
failure into a bare catch that returned "unknown" for all placements.
Fail-open is correct for retirement safety, but the silent catch hid a
broken evidence pipeline (bad config, store corruption) behind indefinite
placement retention with no operator-visible signal. Record the fact at
the boundary that owns it: warn with the error before returning unknown.

Also collapse the byte-identical publish branches in
acquirePreparedModelRuntimeLeaseFromOwners: staleDynamicOwner and
missing-owner both published the exact same snapshot call; one branch
now owns publication with the invariant comment preserved.
2026-08-16 07:29:13 -07:00
Peter Steinberger a11e9d673b fix(plugins): avoid lifecycle lease startup flake (#124607)
* test(plugins): wait for lease child readiness

* fix(plugins): type lease child stdio

* test(plugins): scope lease child cleanup
2026-08-16 07:29:01 -07:00
Peter Steinberger 82ffcdd0d8 feat(lint): enable verified-clean rules and import checks (#124610)
* fix: add symbol descriptions, enum initializers, and Error rejections

* feat(lint): enable verified-clean rule tranche and activate import plugin

* test(lint): cover deferred import rules

* fix(lint): preserve Error rejection identity
2026-08-16 07:28:21 -07:00
Peter Steinberger 8bf55bfe19 fix(update): remove false dependency staleness (#124627) 2026-08-16 07:28:04 -07:00
Peter Steinberger a3099a572d fix(cli): stop devices approve from misdiagnosing a superseded pairing request (#124637)
When the local pairing fallback could not find the gateway's requestId, the
error always blamed a profile/state-dir mismatch and prescribed
--token/--password — even on gateways with no shared auth, and even though a
populated local pending list proves the CLI and gateway share one store and
the held id was merely superseded by a re-minted request. The supersession
case now names the current pending requestId with the exact approve command;
the mismatch hypothesis and shared-auth flags remain only for an empty local
pending list, phrased as possibilities. The fail-closed replacement-validation
paths keep the mismatch wording so they never point at an incompatible pending
request as a recovery step.
2026-08-16 07:27:45 -07:00
Peter Steinberger a5f9adf483 fix(ui): surface the blocking reason when Enter cannot start a new session (#124613)
* fix(ui): surface the blocking reason when Enter cannot start a new session

On the New Session page, pressing Enter while any submit gate was active
silently did nothing: handleComposerKeydown returned when canSubmit was
false, and submitDisabledReason() only knew 3 of the ~14 gates canSubmit()
checked, so during transient states (async worktree/model preference
restore, WS reconnect, roster hydration) neither the tooltip nor the page
explained why nothing happened. Clicking Start seconds later worked.

Fix at the owner: replace the divergent canSubmit()/submitDisabledReason()
pair with one ordered gate table (submit-gates.ts). Every block is a typed
{gate, reason} entry; only the closed silent list (busy button, empty
draft) may omit a reason, enforced at the type level so a new gate cannot
silently eat Enter again. canSubmit, the Start tooltip, and the terminal
split-button all derive from the same walk.

Enter during a reasoned gate is now recorded as a blocked submit attempt:
the flow captures the gate, the composer renders the reason as a role=status
notice, and the notice retires itself as soon as that gate lifts (no queue,
no timers). Gates that already render a page callout (outcome-unknown,
invalid worktree name) are excluded to avoid duplicate text.

Regression tests: a gate-table completeness sweep asserting every blocking
scenario yields canSubmit=false plus a visible reason (fails if a gate is
added without one), Enter-while-restoring surfaces then clears the notice,
and composer-level coverage that reasoned gates consume Enter while silent
gates keep it native.

* test(ui): scope outcome-unknown e2e assertion to the page callout

The consolidated gate table now also surfaces the outcome-unknown reason
in the Start tooltip, so the bare getByText match resolved to 3 elements
under Playwright strict mode. Scope the wait to the role=alert callout.
2026-08-16 07:24:01 -07:00
Peter Steinberger 19bcb23ba1 fix: preserve reply order across overlapping turns (#124623)
Deliver same-target channel replies in inbound order while keeping steer admission concurrent. Record accepted steer/followup ownership so intentional deferred turns do not look silently empty.

Fixes #124618
2026-08-16 07:17:53 -07:00
Peter Steinberger 8dc3230b0a fix(update): bound automatic dev update attempts (#124626) 2026-08-16 07:17:33 -07:00
Peter Steinberger 4caa06b976 refactor(compaction): delete dead generic-fallback variant and prod-dead wrappers (#124603)
* refactor(compaction): delete dead generic-fallback variant and prod-dead wrappers

The CompactionSummaryResult union's generic-fallback variant lost its
only producer in b942db4d56 (summarization failures now throw
CompactionError), leaving summarizeInStages returning kind:'summary'
unconditionally, a dead consumer branch in the safeguard claiming a
degradation path that cannot execute, and a test asserting the
impossible shape. Collapse the return type to string and delete the
branch + obsolete test.

Also delete three prod-dead helpers that survived only through the
safeguard's testing export (invisible to the dead-export lint):
isOversizedForSummary (planning uses its own inline threshold since the
oversized-plan refactor), capCompactionSummaryPreservingSuffix, and
formatPreservedTurnsSection (thin wrappers over budgetCompactionSummary
/ buildPreservedTurnsSection with no prod callers). Tests point at the
surviving primitives; the details-exclusion test retargets the real
owner (estimateMessagesTokens sanitization).

* test(compaction): update staged-summary assertion to string return
2026-08-16 07:16:42 -07:00
Peter Steinberger ea77e21646 perf(test): remove ingress capacity waits (#124630) 2026-08-16 07:15:33 -07:00
Peter Steinberger fb32d5df47 fix(macos): stop Talk MLX helpers after shutdown or replacement (#124604)
* fix(macos): reap replaced MLX speech helpers

* fix(macos): scope stale MLX cleanup to requests
2026-08-16 07:13:59 -07:00
Peter Steinberger 45572dbf36 fix(gateway): keep shutdown reliable during updates (#124582)
* fix(gateway): resolve shutdown dependencies before stopping

* fix(gateway): isolate prepared shutdown runtimes
2026-08-16 07:13:36 -07:00
Peter Steinberger cb65a14c46 fix(sessions): missing configured store must not poison readable siblings (#124617)
resolveExistingAgentSessionStoreTargetsReadOnlyResult added the
configured per-agent template target unconditionally and early-returned
on the first unavailable candidate. A configured store path that has not
been created yet (fresh config, store-migration window) therefore
reported database-missing for the whole agent even when the agent's
real sessions live in a readable discovered store.

Worker placement session evidence maps database-missing to 'absent',
and reconcile destroys environments for absent sessions — so live
placements referencing sessions in the discovered store were retired
and force-destroyed on restart (partial visibility proving absence,
which the evidence doctrine forbids).

Skip missing candidates and return database-missing only when no
candidate store exists; broken-but-present stores (schema/table/read
failures) still fail the whole agent so degraded state keeps mapping to
'unknown', never 'absent'.
2026-08-16 07:13:02 -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 1b34939f82 fix(gateway): retire ownerless requested placements (#124609)
* fix(gateway): retire ownerless requested placements

* fix(gateway): bind placement retirement test methods
2026-08-16 07:09:38 -07:00
Peter Steinberger 99bda3d8cd refactor: make transport stream writer types honest (#124587)
* refactor(ai): give transport streams an honest writer type

* test(ai): use canonical transport stream fixtures

* fix(ai): preserve partial-less stream deltas
2026-08-16 07:03:36 -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
SunnyShu 16cde04136 fix(agents): allow required-preflight native Codex compaction (#120197)
* fix(agents): allow required-preflight native Codex compaction

Required reply-preflight compaction on a Codex app-server-backed session
returns the intentional `ok: true, compacted: false` "codex app-server owns
automatic compaction" no-op because the preflight caller never passes
`allowNonManualNativeRequest`. The reply/preflight path then misclassifies
that successful skip as a failure and throws, dropping the user's turn with
"Context is too large and auto-compaction could not recover this turn." The
equivalent CLI path was fixed by #88207; this is the second, unpatched caller.

Route required-preflight through the existing private
`compactAfterContextEngine` harness capability (which already passes
`allowNonManualNativeRequest: true`) by adding a typed
`nativeCompactionRequest: "required_preflight" | "after_context_engine"`
origin on `maybeCompactAgentHarnessSession` and the Codex compact bridge.
The non-manual skip guard is bypassed for preflight, so Codex actually
compacts the thread.

A binding change between the initial read and the native request is a
stale-binding race, not a benign skip. For `required_preflight` (and the
non-manual CLI path) it now surfaces as the canonical recoverable
`stale_thread_binding` failure so the queued harness falls back to the
context engine instead of treating an uncompacted `ok: true` result as a
completed turn. A genuine post-context-engine request may still skip,
because the context engine has already compacted. Required-preflight is also
the one scoped exception to the model-locked terminal rule: missing or stale
Codex thread bindings recover via the shared context-engine fallback while
the persisted harness lock stays intact; other locked failures remain
terminal.

Rebased onto main after #120740 restructured the guarded native compaction
block; the recoverable-binding semantics are reintroduced on the new
structure and scoped by `nativeCompactionRequest` so #120740's
post-context-engine skip behavior is preserved.

Closes #119971.

* test(evidence): commit inspectable required-preflight live proof scripts for #119971

Adds the two live codex app-server proof scripts (binding-race +
locked-preflight) so the redacted terminal traces in the PR body are
inspectable on the exact head. Both drive the real codex binary and real
maybeCompactCodexAppServerSession with nativeCompactionRequest:
"required_preflight"; neither runs in CI (no codex binary).

* fix(agents): scope locked-preflight compaction fallback to Codex

Restrict the required-preflight model-lock exception to the Codex harness
so missing/stale thread bindings in other locked native harnesses (e.g.
Copilot) stay terminal instead of escaping the persisted model-lock
boundary via context-engine fallback. Add a model-locked Copilot
required-preflight regression covering both missing and stale thread
bindings.

* fix(codex): require native preflight compaction

* chore(plugin-sdk): account for native compaction exports

* test(codex): use complete cron authority fixtures

* chore(lint): shrink compaction assertion baseline

* fix(lint): honor root boundary timeout

* fix(lint): extend package boundary timeout

* fix(plugins): verify native compaction owner

---------

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-08-16 08:00:38 -06:00
Peter Steinberger 7f842da4bd fix(plugins): restrict npm project cleanup to owned roots (#124608)
* fix(plugins): validate npm project root ownership

Restrict recursive managed npm project deletion to canonical ordinary or exact generation roots. Preserve noncanonical sibling roots while retaining package-leaf cleanup and valid generation lifecycle behavior.\n\nFixes #123972

* fix(plugins): reject substituted npm project roots

* fix(plugins): pin managed npm root identity

* fix(plugins): revalidate npm package removals

* fix(plugins): validate npm cleanup descendants

* fix(plugins): reject npm cleanup symlink aliases

* fix(plugins): bind npm cleanup to planned root kind
2026-08-16 06:56:29 -07:00
Peter Steinberger 51964c1eee fix(line): bound inbound media response bodies (#124606) 2026-08-16 06:52:56 -07:00
Peter Steinberger 1f420c2629 fix(telegram): keep canceled requests health-neutral (#124588)
* fix(telegram): keep canceled fetches health-neutral

* refactor(telegram): use native abort fences

* fix(telegram): honor Request-carried cancellation
2026-08-16 06:52:03 -07:00
Peter Steinberger d8e7ec5345 fix(ui): show snapshot warning after forced worktree removal (#124605)
* fix(ui): surface forced worktree snapshot failures

* chore: remove normal-PR changelog entry
2026-08-16 06:50:43 -07:00
Peter Steinberger 2ceb18118c fix(plugins): resolve package module specifiers (#124600) 2026-08-16 06:43:38 -07:00
Peter Steinberger 605dece171 perf(test): shorten Google Meet reconnect proof (#124602) 2026-08-16 06:41:17 -07:00
Peter Steinberger c82dfdb43a fix(macos): stop idle node setup CPU loop (#124599)
Make validated CLI cache writes idempotent and reuse the startup-scoped node worker launch across route retries, keeping the signed menu-bar app near-zero CPU while idle.\n\nCloses #124592
2026-08-16 06:40:56 -07:00
Peter Steinberger 147fa7e4c3 fix(auto-reply): fully read startup context files (#124597) 2026-08-16 06:40:36 -07:00
Peter Steinberger 85ac519900 fix(zalo): reject failed probe responses (#124593) 2026-08-16 06:38:50 -07:00
Peter Steinberger 81d44365ae chore(ci): drop the artifact-check step outputs left behind by the job outputs
Removing build-artifacts' unread job outputs left the step still writing
four `*-result` step outputs and carrying an `id` that nothing
references. The wave already reports failures through ::error
annotations and its exit code, so these were pure leftovers.

Re-simulated the step body with stubbed pnpm/node: normal run exits 0, a
failing verifier still exits 1.
2026-08-16 06:37:01 -07:00
Peter Steinberger b5491a08bf fix(cli): keep approvals JSON failures parseable (#124585) 2026-08-16 06:35:35 -07:00
Peter Steinberger eafeeec537 perf(cli): skip plugin preflight for audit (#124595) 2026-08-16 06:32:10 -07:00
Peter Steinberger 9a555b6a9d fix(ai): preserve WebSocket response failure semantics (#124591) 2026-08-16 06:32:05 -07:00
Peter Steinberger 6602b4eca5 fix: reject invalid targeted channel setup (#124584)
* fix(cli): reject invalid guided channel targets

* fix(gateway): reject invalid channel wizard targets

* test(gateway): keep wizard coverage within lint budget
2026-08-16 06:31:41 -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 7173aeb663 fix: surface hidden-pane steer failures and demote per-turn gateway log noise (#124560)
* fix(ui): surface hidden-pane steer terminal failures globally

Three terminal branches in steer-lifecycle.ts (transport null result,
failed queue-row restore, failed queue-row removal) still gated their
error on itemStillVisible, so a steer that failed after the operator
navigated away parked the error on the queue row with no visible
outcome — the exact invariant #124473 introduced
surfaceChatDeliveryFailure() to protect.

Route all three through the canonical helper and delete the divergent
visibility-only branches. Regression test fails pre-fix
(stash-verified): steer transport failure with the pane hidden now
surfaces the session-named global toast.

* fix(logging): demote per-turn gateway log noise to debug

Live campaign evidence showed three lines dominating operator logs at
info level with no per-turn diagnostic value:

- 'tool policy removed N tool(s)': the policy pipeline runs on every
  turn, so this repeated 42x in one session. Demote to debug and delete
  the now-dead toolPolicyAuditLogLevel/auditLogLevel plumbing that only
  existed to lower diagnostic probes to the level that is now the
  default (net -13 production LOC).
- 'codex app-server one-shot cleanup checked shared client retirement':
  routine per-attempt teardown detail; demote to debug.
- 'codex trajectory capture requires the SQLite host recorder': static
  config condition warned per attempt; warn once per process.

Skipped: the [model-fetch] info carve-out in model-transport-debug.ts is
a named contract (docs/logging.md, #89648) — always-info by design.

* fix(codex): drop test-only trajectory warn-once reset export

Knip's production unused-export gate rejects
resetCodexTrajectoryRecorderWarningForTest — it was a test-only seam in
production code. Reset the process-wide warn-once flag via
vi.resetModules() + fresh dynamic import in the test instead.

* test(cron): wait for backoff re-arm instead of fixed sleep

The 0ms retry timer arms only after async watcher-state persistence, so
'await delay(5)' races it on loaded CI workers (flaked on
checks-node-compact-large-2: spawn called 1 time, expected 2). Replace
both fixed-sleep re-arm waits with vi.waitFor on the spawn count. The
remaining delay(5) guards a negative no-further-spawn assertion after
cancel, where a bounded sleep is the correct shape.

* fix(codex): scope trajectory recorder warn dedupe to session

ClawSweeper P2: the host recorder factory returns null for per-session
target-mapping conflicts, not only static config, so a process-wide
warn-once flag silenced a later distinct session's recorder loss. Warn
once per session (bounded set, cleared past 64 entries) so retries stay
quiet but each newly affected session records its loss. Regression
covers a later distinct session still warning.
2026-08-16 06:23:44 -07:00
Peter Steinberger 243f51d314 refactor(macos): name computer-use executors for their scope (#124586)
The computer.act v1 wire contract is gone, but the naming that survived it
still described a version split instead of the real one: screen-coordinate
execution versus window/element-scoped execution. Both are live rungs of the
same ladder.

- Extract the screen-coordinate half of the 1334-line ComputerActionService
  into ComputerScreenActionExecutor (dispatch, typing, scroll, coordinate
  mapping, button-hold watchdog, raw CoreGraphics primitives). Moved code is
  unchanged apart from threading the queue authority check as a parameter
  instead of reaching back into the queue.
- ComputerActionService keeps its name and becomes the coordinator that owns
  the execution queue, the permission probe, and the shared error vocabulary.
- Rename ComputerActionServiceV2 to ComputerWindowActionExecutor, isV2Request
  to isWindowScopedRequest, isComputerActV2Only to isWindowScopedOnly, and
  ComputerActionError.invalidV2Request to .invalidRequest. The emitted
  COMPUTER_INVALID_REQUEST: prefix is unchanged.
- cua-computer: v2-actions.ts becomes window-actions.ts, handleV2Act becomes
  handleWindowAct, and the stale v1Params local in handleDesktopAct becomes
  desktopParams.
- Note at the computer.act idempotency key that its v1 prefix versions the key
  composition, not the wire contract.

Behavior-neutral: no logic edits, no new branches, no changed error strings.
2026-08-16 06:17:31 -07:00