* test: consolidate OpenClaw test state fixtures
* test(plugin-sdk): expose isolated test state
Promote the isolated OpenClaw test-state lifecycle through a narrow published Plugin SDK subpath so extension tests no longer import private core helpers. This intentional SDK surface addition is maintainer-approved.
* test: use SDK test-state seam in extensions
Route bundled extension suites through the focused repo-local Plugin SDK test-state entrypoint and remove the Codex projector harness exports made stale by fixture consolidation. Keep the seam out of production builds and published package artifacts while auditing its real consumers in the full-tree deadcode scan.
* test(plugins): map test-state in package boundaries
* test(telegram): consolidate bot test harness
* test(plugin-sdk): expose isolated test state
Promote the isolated OpenClaw test-state lifecycle through a narrow published Plugin SDK subpath so extension tests no longer import private core helpers. This intentional SDK surface addition is maintainer-approved.
* test(telegram): use public test-state seam
* test(plugin-sdk): keep test state local-only
Match the existing channel test-helper boundary: bundled extension tests can import the focused SDK source entrypoint, while ordinary builds and the published npm package exclude it. The earlier public classification existed only on this unmerged PR branch and was never a shipped contract.
* test(plugins): map test-state in package boundaries
* fix(agents): honor run abort signal in image and pdf tools
The image and pdf agent tools declared `execute: async (_toolCallId, args)`
and dropped the run abort signal that `wrapToolWithAbortSignal` supplies as
the third execute argument. The wrapper only races the execute promise, so an
aborted run kept sequentially downloading images/PDFs (up to the per-tool cap,
each up to the byte cap) and still issued a paid vision/PDF-model call for a
dead run.
Thread the signal into the existing `requestInit: { signal }` seam (which the
media fetch layer already merges into the download fetch) and add
`signal.throwIfAborted()` between sequential loop items and before the paid
model call. No new media-options signal field; non-abort behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agents): keep the pdf test suite under the lint ceiling; guard model dispatch
Follow-up on the abort-signal change, resolving the check-lint failure and a
review finding on the same seam.
max-lines
---------
Adding the two pdf abort tests pushed src/agents/tools/pdf-tool.test.ts to 1018
effective lines against a ceiling of 1000, failing check-lint. Resolved without
touching config/max-lines-baseline.txt — a suppression there would also have
tripped the max-lines ratchet.
- The new tests declared their own `describe` with `beforeEach`/`afterEach`
hooks identical to the existing `describe("createPdfTool")`. They exercise
that same tool, so they now live in it and the duplicated scaffolding is gone.
- `stubPdfToolInfra`, `createPdfModelRegistry` and `FAKE_PDF_MEDIA` moved to
pdf-tool.test-support.ts, which already exists for exactly this. They go
through `createPdfToolInfraStub(completeMock)` rather than being exported
directly, because the stub wires the suite's own `complete` mock into the
model registry and vi.mock handles are file-scoped. All 21 existing call
sites are unchanged.
Abort propagation into model dispatch
-------------------------------------
The previous revision stopped cancellation at download boundaries and before
the first model call, but `runImagePrompt`/`runPdfPrompt` never saw the signal.
A run cancelled while the first provider request was in flight could still
issue the remaining ones — the image path dispatches `describeImage` once per
image in a sequential loop, so a dead run kept paying for every later image.
Both now take an optional `signal` and check it immediately before each
provider dispatch (3 sites in image-tool, 4 in pdf-tool).
Forwarding the signal further, into the provider transports themselves, is a
different seam and is deliberately left out of this PR.
Also declares `requestInit` on `ImageToolLoadWebMediaOptions`. The local facade
omitted it while the underlying loader accepts it (web-media.ts declares it and
forwards it to readRemoteMediaBuffer), so the option worked at runtime and only
compiled because spread properties skip excess-property checking. A non-spread
call site would not have.
tsgo core + core-test, oxlint, oxfmt clean; pdf-tool and image-tool suites 266
tests pass.
* test(agents): prove in-flight media aborts
* test(agents): narrow PDF loader options
* test(agents): reject abort mocks with errors
* fix(agents): propagate media cancellation
* test(agents): type PDF abort fixture
* test(agents): type prepared runtime snapshot
* fix(agents): normalize abort rejection errors
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Locked script/command automations rendered their source in a plain textarea. They now render as a resizable, highlighted read-only code block reusing the existing highlight.js path.
* perf(ui): refresh the session list once per batch action
Batch sidebar actions patched each row and then forced a full sessions.list
replacement per row, so archiving a nine-row multi-select paid nine list
rebuilds on top of nine patches. Measured against the gateway handlers on a
60-session store, sessions.list costs ~2.9s versus ~176ms for sessions.patch.
Batch rows now pass deferListRefresh and each batch helper issues one refresh
per owning agent after its last row; pushed sessions.changed events keep rows
reconciled while the batch runs. Single-row actions are unchanged.
* test(ui): prove batch archive costs one list refresh in the browser
Adds a Control UI e2e case that cmd-clicks three sidebar rows, archives the
selection from the batch menu, and asserts the Gateway saw three sessions.patch
calls in row order plus exactly one sessions.list. It holds past the batch so a
late per-row refresh would still be caught.
Before the batch-refresh change this flow issued one full sessions.list per
archived row.
* fix: SQLite WAL file can stay inflated on a running gateway until restart
Since #82366 switched the periodic 30-minute checkpoint to PASSIVE (to keep
WAL maintenance off the event loop), no checkpoint on a running process
truncates the WAL *file* any more -- only close() does, i.e. a restart.
wal_autocheckpoint recycles WAL space in place but never shrinks the file,
and is itself a PASSIVE checkpoint a reader can transiently block. So when a
reader briefly pins frames (e.g. a memory reindex, a backup, a slow query),
the WAL grows past the autocheckpoint size and then stays parked at that
high-water mark for the whole life of the process. Observed in production: a
1.6 GB agent DB left a 1.6 GB -wal that only manual TRUNCATE checkpoints
could reclaim. This affects every SQLite-backed store (task registry, plugin
state, proxy capture, memory host, ...), not just memory.
Set PRAGMA journal_size_limit (default 64 MiB, overridable via
journalSizeLimitBytes) right after wal_autocheckpoint so any completing
checkpoint -- including the PASSIVE periodic/auto ones #82366 now relies on
-- truncates the WAL file back to the ceiling. This restores the bounded
on-disk WAL that TRUNCATE used to give, without reintroducing the blocking
checkpoint #82366 removed: journal_size_limit only changes how far a
completing checkpoint truncates, never checkpoint timing. The 64 MiB ceiling
sits ~16x above the autocheckpoint steady state (~4 MB at 1000 pages), so it
is inert in normal operation and engages only on pathological growth.
Related: #82366, #81715
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: tighten SQLite WAL ceiling proof
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>