Commit Graph

6734 Commits

Author SHA1 Message Date
Peter Steinberger 4b2d78a2ef fix(gateway): keep streaming roots until delivery and cleanup finish (#117505)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-08-01 13:57:55 -07:00
Peter Steinberger c6bad23f2d test(gateway): dedupe chat server fixtures (#117588) 2026-08-01 13:12:45 -07:00
Vincent Koc 04ac671de6 perf(gateway): separate startup maintenance timing (#117577) 2026-08-02 03:56:51 +08:00
Vincent Koc 27b1d04178 perf(gateway): skip empty session reconciliation (#117589)
* perf(gateway): skip empty session reconciliation

* chore(release): remove branch-local changelog entry
2026-08-02 03:47:49 +08:00
Vincent Koc ebf121af6a perf(gateway): mark recovery before model preparation (#117544)
* perf(gateway): keep recovery runtime off startup path

* perf(gateway): mark recovery before model preparation

* test(gateway): lock recovery marking failure order
2026-08-02 03:06:19 +08:00
Omar Shahine a624ba7b96 feat(gateway): advertise chat attachment limits on hello-ok (#116188)
* feat(gateway): advertise chat attachment limits on hello-ok

Clients had no way to learn the gateway attachment ceilings, so external
clients hardcoded guesses that drifted from server enforcement. Publish the
two unconditional decoded-size ceilings on hello-ok policy.attachments from
one shared resolver so advertised values cannot drift from the parser.

MIME acceptance and per-message counts stay server-side: they depend on the
entrypoint, the resolved model, and payload sniffing, so they cannot be stated
once per connection.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 17d6c355-8948-4b48-a936-e08b1c8806ef

* feat(gateway): advertise chat attachment limits on hello-ok

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Copilot-Session: 17d6c355-8948-4b48-a936-e08b1c8806ef
2026-08-01 11:31:29 -07:00
Peter Steinberger b7f0df0ac2 refactor(agents): consolidate main-session recovery ownership (#117383)
* refactor(agents): consolidate main-session recovery ownership

* test(agents): cover recovery owner boundaries
2026-08-01 10:40:56 -07:00
Peter Steinberger fb6f60a704 fix(gateway): preserve Responses usage details (#117533)
* fix(gateway): preserve Responses usage details

* fix(gateway): keep Responses usage type internal
2026-08-01 10:37:02 -07:00
Vincent Koc 263741b1f0 fix(ci): align release validation fixtures (#117494)
* fix(release): align beta 6 e2e fixtures

* fix(doctor): preserve missing state startup signal

* test(release): align beta e2e contracts

* test(agents): use canonical empty auth store

* test(agents): isolate prompt cooldown scheduling

* test(release): align main validation fixtures

* test(release): accept Code Mode plugin tool evidence

* fix(test): repair release validation checks

* test(cron): assert structured session targets
2026-08-02 00:39:36 +08:00
Vincent Koc 5ae401d908 fix(models): reduce default model-list memory use (#117323)
* fix(models): scope default catalog discovery

* fix(models): split scoped catalog preparation

* perf(models): narrow model-list imports

* perf(auth): narrow provider id imports

* perf(plugins): narrow provider discovery imports

* perf(models): bypass runtime catalog facade for scoped lists

* perf(models): avoid runtime imports in default list

* fix(models): preserve scoped catalog coverage

* chore(models): remove obsolete auth exports

* perf(models): skip redundant configured-provider discovery

* perf(models): skip canonical row runtime loading

* fix(models): normalize configured fallback rows

* test(models): update registry list fixtures

* fix(models): separate catalog and runtime discovery scopes

* fix(models): break row projection import cycle

* fix(models): satisfy type and export checks

* fix(models): keep row projection bundled-only

* fix(models): preserve partial catalog visibility

* fix(models): avoid live discovery in default lists

* test(models): isolate auth-backed catalog coverage

* fix(models): retain OpenAI runtime normalization

* fix(models): skip canonical OpenAI row runtime loading

* test(models): keep provider policy fixtures typed

* fix(models): preserve configured OpenAI routes
2026-08-02 00:01:37 +08:00
Vincent Koc 7cc2e6d830 fix(ci): repair full release validation regressions (#117447)
* fix(worktrees): reject repositories without commits

* test(memory-wiki): allow agent isolation runtime

* test(ios): await queued approval retry
2026-08-01 14:55:13 +00:00
nocodet888-arch 5ad7c969b8 fix(gateway): trim audit.list exact-match filter ids (#110847) 2026-08-01 07:50:24 -07:00
Peter Steinberger 3146736f12 fix(gateway): keep service routes out of Control UI fallback (#117436) 2026-08-01 07:22:12 -07:00
Peter Steinberger e4d1b7e0d3 refactor(plugins): move plugin contributions into the registry bundle (#117372)
* refactor(plugins): move plugin contributions into the registry bundle

* fix(plugins): guard embedding owner union and drop unused test-util imports

* fix(plugins): break registry facade import cycles

* fix(plugins): remove obsolete registry snapshot seams

* test(gateway): preserve plugin runtime mock exports

* test(auto-reply): install builder registry in diagnostics fixture

* test(plugins): activate registry-backed capability fixtures
2026-08-01 06:57:01 -07:00
Peter Steinberger 9214183f45 refactor(sessions): retire runtime transcript sidecars (#117380) 2026-08-01 06:21:54 -07:00
Peter Steinberger 4e47c4cabf refactor(gateway): consolidate archived transcript reads (#117343) 2026-08-01 03:52:35 -07:00
Peter Steinberger aa2a5c96f6 feat(channels): record account lifecycle facts and retire inferred health vocabularies (#117300)
* fix(channels): record account lifecycle facts

* fix(channels): bound recorded lifecycle grace

* fix(channels): preserve authored health state

* test: dedupe health snapshot type literal and fix spread order
2026-08-01 02:51:47 -07:00
Peter Steinberger fde5420dba refactor(agents): canonicalize subagent execution state (#117267) 2026-08-01 02:21:50 -07:00
Vincent Koc f5a8cb02ea test(release): repair full validation regressions (#116931)
* test(discord): mock thread delete listener

* test(qa): expect blocked update evidence

* test(telegram): preserve recovered context body

* fix(test): configure kitchen-sink personality

* test(browser): expect canonical staged upload paths

* test(browser): canonicalize macOS download roots

* test(feishu): seed legacy session rows offline

* test(telegram): isolate message context session stores

* test(qqbot): expect canonical media paths

* test(anthropic): match canonical transcript paths

* test(qa): expect canonical session store keys

* test(gateway): isolate rewind media reads

* test(release): review plugin child spawns

* test(plugins): expect process-stable manifest metadata

* test(google): retire usage telemetry contracts
2026-08-01 15:42:50 +08:00
Vincent Koc 754dcb8b2f merge: preserve verified completion handoff series (#117148)
* commit '619682ed010f4e6c56c73feb4948fd7265083e10':
  fix(agents): bind completion messages to source
  fix(agents): mint completion delivery grant
  fix(agents): retain tools for direct completion delivery
  fix(agents): integrate verified completion delivery
  test(codex): use typed completion handoff fixture
  fix(agents): verify completion tool handoffs
2026-08-01 15:35:41 +08:00
Harjoth Khara b97a214604 fix(canvas): preserve widgets across derived-name collisions (#117132)
Fixes #116945
2026-08-01 15:33:56 +08:00
zilokki-bot 0991fad3a3 fix(gateway): bound session catalog refresh pressure (#113171)
* fix(gateway): bound session catalog refresh pressure

* fix(gateway): keep session catalog coordinator types internal

* chore: rerun ci

* test(secrets): allow migration isolation shard headroom

* test(secrets): lock migration isolation profile

* chore: keep session catalog note out of changelog

* test(gateway): cover saturated session catalog admission

* fix(gateway): queue saturated session catalog loads

* test(gateway): cover session catalog pressure over rpc

* test(gateway): cover session catalog pressure over rpc

* test(gateway): keep catalog pressure proof isolated

* test(gateway): satisfy catalog pressure proof types

* fix(gateway): keep catalog admission internals private

* fix(gateway): preserve raw catalog host filters

* test(gateway): preserve session catalog host filter order

---------

Co-authored-by: probe <probe@example.invalid>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-08-01 01:04:16 -06:00
Vincent Koc 4c2d2d3a8f Merge pull request #117215 from openclaw/fix/talk-transcript-relay-bound 2026-08-01 14:20:00 +08:00
Peter Steinberger 1b8411958b fix(gateway): stop post-ready sidecar timers on close (#117242)
* fix(gateway): stop post-ready sidecar timers on close

* fix(gateway): return promise from generation timer admission callback
2026-07-31 23:03:10 -07:00
Harjoth Khara 6fb334fe84 test(gateway): pin MCP loopback drain proof to a server-side barrier (#117181)
The drain test asserted "close has not settled" after a fixed 20ms sleep.
Node only exempts a connection from close()'s idle sweep once its parser has
begun a message, so a client-side connect barrier let a loaded machine reach
close() before the server parsed the request: the socket was swept as idle,
the client saw ECONNRESET, and the drain settled inside the window.

Wait on capture admission (onRequestStart) instead, which fires server-side
after the headers are accepted, and move the assertion to just before the
successor is used so it proves the successor was minted mid-drain.
2026-08-01 14:03:02 +08:00
Vincent Koc d6f6bcf291 fix(talk): retain bounded relay close owners 2026-08-01 14:01:55 +08:00
xingzhou 1f40646f7e fix(board): prevent emoji corruption in event notices (#111702)
* fix(board): preserve Unicode notice boundaries

* test(board): document surrogate boundary

* test(pr): isolate wrapper command inventory
2026-07-31 22:57:11 -07:00
Vincent Koc cdf683f004 fix(talk): bound relay transcript persistence 2026-08-01 13:52:45 +08:00
xingzhou 4fb19fb8c5 fix(gateway): clamp discovery advertise timeout (#117204) 2026-07-31 22:08:51 -07:00
Vincent Koc e6a9660241 fix(agents): verify completion tool handoffs 2026-08-01 12:47:05 +08:00
Peter Steinberger cb16d739cf fix(inference): harden Codex streaming, cancellation, and live QA (#116012)
* fix(inference): harden Codex turns, streams, and live QA

* fix(inference): dispatch open CR-delimited response frames

* fix(inference): finish live goal and split streaming regressions

* fix(codex): wait for interrupted turn completion

* fix(codex): narrow terminal collector notification ownership

* fix(inference): harden completion authority and lifecycle

* fix(inference): use canonical source delivery contract

* test(inference): align lifecycle probes with current main

* style(inference): format source delivery verification

* fix(goals): prevent premature completion across staged work

* fix(qa): settle streamed goal turns before continuation

* fix(goals): require visible replies after goal completion

* test(qa): keep goal flow regressions within lint limits
2026-07-31 21:29:33 -07:00
Vincent Koc 602b5759f2 fix(exec): preserve approved command output when sessions resume (#117177)
* fix(exec): preserve approved exec continuation output

Approved async exec continuations reused the compact background notification
formatter, so the agent resumed from the last 400 characters of output with all
whitespace collapsed and the head silently dropped.

Both hosts now render the continuation through a shared whitespace-preserving
formatter bounded at 16,000 UTF-16 units. Compact `notifyOnExit` notifications,
poll/retained output, and the gateway diagnostics branch are unchanged.

The truncation marker deliberately reports no exact omission count: output can
already be capped at capture time without leaving a marker, so an exact number
would describe only this cut while reading as though nothing else was lost.

Closes #41152

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2d9762e2-f967-4cdd-9895-299512a20114

* fix(exec): secure approved continuation handoff

* test(exec): cover authenticated approval handoff

* fix(exec): retain approved followup delivery ownership

* fix(exec): retain compact continuation fallback

* fix(exec): bound accepted followup observation

* fix(exec): keep observer diagnostics internal

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2d9762e2-f967-4cdd-9895-299512a20114
2026-08-01 11:59:51 +08:00
Spencer Fuller 3374f78ad8 fix(queue): prevent cron saturation from starving hook dispatch (#116666)
* feat(hooks): dispatch hook agent runs into a dedicated command lane

Hook agent runs passed lane:"cron", which resolveCronAgentLane remaps to
cron-nested — the same lane cron's own inner agent work uses, capped at the
hardcoded cron budget of 8. Eight busy cron turns therefore starved every hook.

Adds CommandLane.HookDispatch and dispatches hook runs into it. Neither lane
resolver needs changing: resolveCronAgentLane (agents/lanes.ts:15-22) and
resolveGlobalLane (embedded-agent-runner/lanes.ts:11-18) special-case only
"cron" and pass every other lane through.

This is lane identity only. It does NOT yet bound aggregate capacity — that is
the capacity group in the following commits. On its own this widens total
command-lane concurrency by the hook lane's width (1).

Consumers that inferred cron-ness from the lane, both preserved rather than
silently changed:
- heartbeat-runner-execution: HookDispatch added to the busy-lane check so hook
  work still suppresses heartbeats; only the lane it occupies changed.
- session-suspension: explicit resume concurrency and gateway-managed-lane
  membership, instead of falling through to the custom-lane default.

server-lanes publishes the lane at width 1: the guarantee is that a hook can
always start under cron saturation, not that hooks run concurrently.

Test: server.hooks-lane.test.ts asserts the dispatched lane and that it survives
cron lane resolution unremapped, with a positive control on the inputs that DO
remap. Mutation-verified — reverting the call site to "cron" fails it with
'expected cron to be hook-dispatch'. Nothing else in the suite reads the
dispatched lane, so without this assertion the change regresses silently.

Refs: openclaw#98813, openclaw#43235

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(queue): capacity groups with hard per-member reservations

Adds optional capacity groups to the command queue: lanes in a group share one
hard aggregate budget, and a member may hold a non-borrowable reservation
within it. This is what makes a separate hook lane safe — it bounds hook and
cron-nested work together at the existing cron cap instead of adding a slot
outside it (openclaw#98813 maintainer audit measured cron-nested=8 + hook-a=1 +
hook-b=1 = 10).

Group capacity is always DERIVED from members' activeTaskIds, never a separate
counter. Timeout, abort, clear, reset and stale-generation completion therefore
release capacity for free, because they all remove the task id; the only
remaining obligation is that those paths re-drain the group.

- setCommandLaneGroup / clearCommandLaneGroup / drainCommandLaneGroup
- admission: lane max, then group budget, then sibling reservations. A member
  may burst above its own reservation only into unreserved capacity.
- both completion paths (success AND error) wake group siblings; freed capacity
  belongs to the group, so a lane-local pump would strand a queued sibling
  behind capacity that is already free. resetCommandLane likewise.
- membership lives in the queue singleton keyed by lane name, NOT in LaneState,
  so setCommandLaneConcurrency cannot detach a member from its group.
- deadlock guard: rejects cron/main/subagent/nested and session:*/nested:*/
  context-engine-turn-maintenance:* — lanes that can be synchronously awaited,
  where a group wait would become a deadlock.
- rejects sum(reservations) > budget rather than starving silently.
- snapshot exposes group/groupActive/groupBudget/reservedForLane/blockedBy.

Tests: 9 new, all mutation-verified — dropping group admission fails 5,
removing the sibling wake fails 2, making reservations borrowable fails 2
("expected 4 to be 3": an idle sibling's reserve being borrowed). 57 pass
across all command-queue suites.

Not yet wired: no group is configured by default. That, plus group-wait
visibility and atomic publish, are the following commits.

Refs: openclaw#98813, openclaw#43235

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(queue): blockedBy answers hypothetical immediate admission, not queue state

Round-4 review (costaff-lapclaw-001) named this as the precision requirement
that decides whether the wait-visibility fix is vacuous:

  noteLaneWaitIfBusy runs BEFORE enqueue, so it snapshots the lane with
  queuedCount === 0. If blockedBy were populated only for an already-queued
  head entry, that snapshot would read "not blocked", no
  onLaneWait(waiting:true) would fire, and agent-watchdog's setup-timeout
  suppression would never engage — a run merely waiting on group capacity
  would take a false setup timeout.

resolveLaneBlockReason already answers "could this lane start work right now?"
independent of queue contents; these tests pin that contract:

- 7 cron active, hook holding the group's reserved slot: cron reports
  sibling-reservation with queuedCount 0 and activeCount < maxConcurrent, while
  the hook reports null (so the assertion discriminates rather than being
  always-truthy).
- a member lane that was never enqueued or was retired while idle still reports
  its group block state, instead of the not-found path returning a bare default
  that reads as free.

Mutation-verified: gating blockedBy on queue.length > 0 fails 3 tests with
'expected null to be sibling-reservation' — the exact symptom predicted.

Refs: openclaw#98813

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(queue): atomic lane publication, group-aware wait visibility, opt-in group

Closes the remaining two round-2 blockers and wires the default group.

publishLaneConfiguration({lanes, groups, clearGroups}) applies lane maxima and
group definitions as ONE transaction: install with dispatch suppressed, then a
single commit-time drain. The per-lane setter drains the instant a lane goes
positive and gateway publication was sequential, so a member could be widened
and dispatch BEFORE its group existed — admitting work above the budget the
group was meant to enforce. Validation throws before any drain, so a rejected
configuration cannot strand lanes widened and ungoverned.

lane-controller's noteLaneWaitIfBusy now also emits a wait when
snapshot.blockedBy != null. A group-blocked member has
activeCount < maxConcurrent and can have queuedCount === 0, so both lane-local
terms were false while the task genuinely could not start. This is not just
observability: agent-watchdog.ts suppresses the cron setup timeout only while
waitingForLane is true, so an invisible group wait produced a FALSE setup
timeout for cron-shaped runs.

The group is opt-in on hooks.enabled. The reservation is a real cost — it
withholds a slot from cron inner work even while the hook lane is idle — so it
is only paid where it buys something. This surfaced as a genuine regression:
server-lanes.test.ts asserts cron-nested alone reaches all 8, which an
unconditional group breaks. With hooks off, no group is installed, cron keeps
the entire budget, and such a deployment sees no behaviour change at all.
Turning hooks off on reload tears the group down (clearGroups).

With hooks ON, cron inner work trades one slot for the guarantee that hooks
cannot be starved. Aggregate stays exactly the pre-existing cron cap — no slot
added outside it, which is what openclaw#98813 was held for.

Tests: 6 new (3 publication, 3 opt-in), 135 passing across all affected suites.
Mutation-verified:
- sequential per-lane publication: 'expected 12 to be less than or equal to 8',
  the exact 8+4 additive leak, caught at PEAK not post-state as review required
- unconditional group: 'expected cron-hooks to be undefined'

Refs: openclaw#98813, openclaw#43235

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(queue): pin group-blocked lane waits to the setup-timeout suppression chain

Round-4 review (fiducian-spencer-001) asked for the 8-cron / hook-holding-
reserve / 8th-cron-waiting regression asserting no false setup timeout.

The chain spans three files:
  lane-controller.noteLaneWaitIfBusy -> onLaneWait({waiting:true})
  -> timer-job-runner.noteLaneState  -> watchdog.noteLaneWait()
  -> agent-watchdog:159-164          -> waitingForLane = true, clear timeout
  -> agent-watchdog:98               -> setup timeout suppressed

The watchdog end is already covered by agent-watchdog.test.ts. The link this
change introduced is the FIRST one, and it is the one that fails silently: a
group-blocked lane looks idle to a lane-local view, so no wait is reported and
a healthy run queued behind group capacity takes a false setup timeout.

The predicate was an inline closure, so it was untestable without the full
runner harness — and asserting a copy of it in a test would prove nothing.
Extracted as shouldNoteLaneWait(snapshot) and driven with real snapshots from a
real group:

- 7 cron active, hook holding the reserve: the test asserts explicitly that
  BOTH lane-local terms are false (activeCount 7 < maxConcurrent 8,
  queuedCount 0) and that the predicate still reports a wait.
- a hook blocked by a full group budget reports a wait.
- negative control: lanes that can start immediately report no wait, so a
  predicate hardcoded to true would fail.
- ordinary lane-local saturation still reports a wait (pre-existing behaviour).

Mutation-verified: reverting to the lane-local predicate fails 3 tests with
'expected false to be true'. 182 pass across all affected suites.

Refs: openclaw#98813

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(queue): make rejected publication a no-op; always tear down the group on hooks-off

Round-5 implementation review (fiducian-spencer-001, CHANGES REQUESTED) found
one real atomicity bug and two test gaps. Both bugs are fixed and both are now
mutation-guarded.

BLOCKER — rejected publishLaneConfiguration left lane maxima mutated.
Phase 1 widened lanes, then setCommandLaneGroup could throw (e.g.
sum(reservations) > budget) with no rollback. No drain ran, so the old test's
activeCount === 0 assertion passed — but the lane sat at the new width governed
by NO group, and the next unrelated drain trigger would dispatch its preserved
queue ungoverned. The function comment promised exactly what the code did not
do. Validation is now a distinct phase 0 over every group spec before anything
is mutated; validateCommandLaneGroupSpec/installCommandLaneGroup split out so
setCommandLaneGroup and the transaction share one validation path.

BUG — hooks-off skipped group teardown when the grouped lane was suspended.
applyGatewayLaneConcurrency published only when the lane map was non-empty. With
hooks off, cron-nested is the only lane that can enter it, so a suspended
cron-nested left the map empty and clearGroups was never published. A previously
installed cron-hooks group survived, and the member resumed still paying a
reservation for a hook lane receiving no work. Now publishes whenever hooks are
disabled, regardless of the lane map.

Also (review item 4): a lane may now belong to at most one group.
installCommandLaneGroup removes it from any prior owner's members, which
otherwise kept counting its active tasks toward a budget it had left. Not
reachable with the single default group, but this is a public API.

Tests: 3 new. Mutation-verified —
- validating during install instead of before: 'expected 8 to be +0'
- restoring the non-empty-lane-map guard: 'expected cron-hooks to be undefined'
  (this one initially SURVIVED; the first version of the teardown test never
  simulated suspension, so it could not see the bug. Now seeds a cleared lane
  resume and publishes via the gatewayStart path.)
- hooks-off now proves it DRAINS the work its teardown releases, not just that
  membership was deleted.

Test teardown fixed: resetAllLanes preserves queued entries by design, so work
on a lane that never opened never settles. clearCommandLane rejects it instead.

Typecheck clean (tsgo core + core test, exit 0).

Refs: openclaw#98813

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(queue): make setCommandLaneGroup self-waking; guard the clearGroups+invalid case

Round-5 implementation review from costaff-lapclaw-001 independently found the
same two bugs fiducian did (rejected-publish partial mutation, and hooks-off
teardown skipped when the grouped lane is suspended) — both already fixed in
0eb96a7. It raised two things fiducian did not:

1. The exported setCommandLaneGroup primitive was not self-waking. Replacing a
   group can FREE capacity — wider budget, dropped reservation, removed member —
   and queued members must not sit behind capacity that is already available.
   publishLaneConfiguration drains at commit, but the bare primitive is exported
   and its "replace" semantics silently stranded members until an unrelated
   poke. Now drains the union of previous and next members.

2. clearGroups combined with an invalid replacement was the worst case: the old
   group could be removed before the new one threw, leaving BOTH lane width and
   group membership partially committed. Phase 0 validation already ran before
   the clear after 0eb96a7, but nothing pinned it.

Also documents a limitation costaff noted: reservations are not validated
against the member's own maxConcurrent, because lane widths and group
definitions are published together and the width may not be applied yet at
validation time. A too-large reservation is accepted but partly unusable.

Tests: 2 new. Mutation-verified —
- removing the self-wake: 'expected 2 to be 5'
- validating during install instead of before the clear:
  'expected undefined to be cron-hooks' (the existing group torn down by a
  rejected replacement — costaff's exact worst case)

Reviewer agreement on the rest: admission arithmetic sound for the concrete
config, clearCommandLane correctly not wired (frees no active capacity), the
peak-occupancy publication test is the right shape, the static deadlock deny set
matches the known synchronous-wait lanes, and shouldNoteLaneWait's export is the
right trade over asserting a copied closure.

Typecheck clean (tsgo core + core test, exit 0).

Refs: openclaw#98813

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* style(queue): satisfy oxlint in the capacity-group tests

`check-lint` was failing on 21 errors across the four new suites:

- 17 `curly`: single-statement `for`/`for-of` bodies without braces.
- 4 `no-promise-executor-return`: `new Promise((resolve) => setTimeout(resolve, 0))`
  implicitly returns the Timeout handle from the executor. Rewritten to the
  braced form already used ~85 times elsewhere in the repo, e.g.
  `src/plugins/install-paths.test.ts:41`.

No behaviour change; the suites pass unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(queue): split capacity groups and shared state out of command-queue.ts

`check-lint` was failing `max-lines` on src/process/command-queue.ts: 918
counted lines against the repo cap of 700. The file was already only 51 lines
under the cap before this branch, so the new capacity-group code could not fit
in it.

Two pure moves, no logic change:

- `command-queue.state.ts` — the globalThis-backed queue singleton
  (`getQueueState`, same `Symbol.for` key), `normalizeLane`, and the
  `QueueEntry` / `LaneState` / `ActiveTaskWaiter` / `CommandLaneTaskMarker`
  types. Lets the group policy read lane state without importing the queue.
- `command-queue.capacity-groups.ts` — the group registry, eligibility policy,
  spec validation, install, and the block-reason computation.

The four near-identical "drain these member lanes" loops collapse into one
`drainMembers` helper. It keeps the load-bearing part of each original: the
lane is looked up rather than created, because `drainLane` goes through
`getLaneState` and would resurrect a scoped lane that
`retireIdleScopedCommandLane` had just removed. The one loop that additionally
tested `maxConcurrent > 0` loses that check, which was an optimisation only —
a zero-width lane's pump admits nothing.

The dependency on `drainLane` is passed in as a parameter rather than imported,
so the new modules stay acyclic; `setCommandLaneGroup`, `clearCommandLaneGroup`
and `drainCommandLaneGroup` remain exported from command-queue.ts as thin
wrappers, and every previously exported symbol is still exported from there.

command-queue.ts is now 676 counted lines; all three modules are under the cap.

Verified: `tsgo:core`, `tsgo:core:test`, `oxfmt --check`, `oxlint` all clean;
1007 tests across the 43 suites that touch command-queue pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(queue): allow concurrent hook dispatch within cron budget

* test(gateway): prove hook burst concurrency stays bounded

* refactor(queue): keep capacity groups internal

* test(gateway): isolate steady-state hook admission

* fix(gateway): close hook lane on disable

* fix(gateway): retarget suspended hook resumes

* fix(gateway): restore hook group before lane resume

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-01 11:49:16 +08:00
Masato Hoshino 53326b9e9a fix(gateway): sessions.steer retry no longer interrupts an active run when replaying a cached idempotency key (#116969)
* fix(gateway): stop sessions.steer from interrupting an idempotent replay

* fix(gateway): gate steer interruption on new chat admission

* fix(gateway): serialize steer admission retries

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-01 11:48:20 +08:00
Peter Steinberger c50237e37d perf: count large histories before Gateway prewarm (#117118)
* perf(gateway): count before sidebar prewarm

* fix(sessions): narrow count row before normalization

* fix(gateway): align sidebar prewarm admission targets

* test(gateway): prove large prewarm stays optional

* fix(gateway): budget repeated shared-store prewarm
2026-07-31 20:36:00 -07:00
Jason (Json) 65f3e42f24 fix(gateway): yield before post-ready background work (#117083)
* fix(gateway): yield before post-ready background work

* fix(gateway): gate restart recovery after ready
2026-07-31 20:56:03 -06:00
Masato Hoshino a7b4a47356 fix(cron): reject disabled delivery accounts when scheduling (#116899)
Scheduling an automation against a channel account the operator has turned
off succeeded. The job was stored and scheduled against a route its owner had
already disabled, and nothing reported that until it fired.

`assertValidCronFailureAlert` in this file already applies the rule to the
channel on the same route: an explicit unknown channel must not be "stored and
only fails later as `channel_not_found`". The account had no equivalent check.

Reject a `delivery.accountId` whose `accounts.<id>.enabled` is `false`, beside
the existing channel checks.

The check is scoped to that account entry and nothing else, because the wider
signals all have false positives here:

- Cron delivery ids are not always operator-typed - `delivery-context.ts`
  copies the current context account into inferred jobs.
- Channel `isEnabled` adapters report unlisted or credential-suppressed
  accounts as not enabled (twitch resolves any unlisted id to `enabled: false`).
- A top-level `channels.<id>.enabled: false` is not uniformly channel-wide -
  twitch resolves named accounts from `accounts` alone.

Only the account entry itself is an unambiguous statement about this route, so
only that blocks a mutation. No channel plugin is resolved on this path.

Both cron mutation paths share this validator and the update caller is already
gated on `"delivery" in patch`, so edits that do not touch routing stay
unblocked by an id stored before this validation existed.
`cron-error-classification.ts` learns the field name so the precondition
recheck inside `updateWithPrecondition` still classifies it as
`INVALID_REQUEST`.

`delivery.failureDestination.accountId` is intentionally out of scope: its
effective route layers over the global failure-destination config, so it needs
route resolution before validation rather than a raw check on the partial
override.
2026-08-01 08:57:35 +08:00
Peter Steinberger a5d89e169e fix(gateway): honor HTTP If-Modified-Since media validators (#117133)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 17:56:44 -07:00
SunnyShu cbdbf6802b fix(models): expose runtime provider catalogs in browse views (#116857)
Use the resolved runtime config for discovery auth while preserving source SecretRef markers, keep full-catalog discovery unscoped, honor per-agent wildcards, and restrict discovered inventory to manifest-declared dynamic providers.

Fixes #115953
2026-08-01 08:37:43 +08:00
Peter Steinberger afa8fffab8 fix(gateway): retain avatar content length for HEAD requests (#117124)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 17:36:28 -07:00
Peter Steinberger 4f7fbcb34d fix(gateway): preserve icon metadata for HEAD requests (#117079)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 17:23:57 -07:00
Peter Steinberger f455eb76d8 fix(gateway): reject sends on closing node sockets (#117066)
* fix(gateway): reject sends on closing node sockets

* style(gateway): format node socket fixtures

* test(gateway): fix node socket state typing

* test(gateway): prove closing socket admission

* test(gateway): align WebSocket proof contracts
2026-07-31 17:21:30 -07:00
Peter Steinberger e67df99edc refactor(gateway): consolidate operator approval resolution (#117095) 2026-07-31 16:45:01 -07:00
Peter Steinberger 2f875c5ad7 perf(gateway): skip large catalog prewarm (#117108) 2026-07-31 16:44:22 -07:00
Peter Steinberger ea1d6cc35e refactor(memory): unify embedding provider adaptation (#117094) 2026-07-31 16:30:22 -07:00
Peter Steinberger 727216b014 fix(gateway): honor authoritative HTTP If-Range date validators (#117058)
* fix(gateway): honor exact HTTP-date range validators

* fix(gateway): bound future media validators to response origin

---------

Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 15:43:09 -07:00
Peter Steinberger 4bbcee0b8e fix(gateway): preserve static asset HEAD representation metadata (#117049)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 14:57:42 -07:00
xingzhou 2614251533 fix(gateway): abort embeddings when clients disconnect (#116146)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-07-31 16:55:44 -05:00
Peter Steinberger 383363e362 fix(cron): preserve lazy ownership and bounded notification lifetimes (#117018)
* fix(cron): preserve lazy ownership and bound notification lifetimes

* fix(cron): respect fs-safe policy boundary

---------

Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 14:38:21 -07:00
Peter Steinberger f47542c592 fix(gateway): centralize streamed media descriptor ownership (#117000)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 14:17:38 -07:00