Commit Graph

38979 Commits

Author SHA1 Message Date
Peter Steinberger 92e5a10f1a fix(doctor): diagnose ignored web fetch proxy settings (#109778)
* fix(doctor): diagnose direct web fetch proxy routing

* test(doctor): cover proxy diagnostic through public API

* test(doctor): validate emitted proxy diagnostic type
2026-07-17 01:24:32 -07:00
Peter Steinberger edcf3131ad test(gateway): stop background plugin reloads from wiping test task runtimes (#109801)
Root cause of the flaky 120s timeout in "logs a swallowed finalize error
without blocking the background run" (CI run 29559328261, shard
agentic-gateway-methods): the preceding ACP plugin-subagent test registers a
subagent run whose completion keeps running in the background after the test
ends. That leaked chain (completeSubagentRunAttempt ->
emitSubagentEndedHookForRun -> ensureSubagentRegistryPluginRuntimeLoaded ->
ensureRuntimePluginsLoaded -> loadOpenClawPlugins) starts by calling
clearActivatedPluginRuntimeState(), which clears the process-wide detached
task lifecycle runtime registration. When it lands between the next test's
setDetachedTaskLifecycleRuntime(spy) install and that test's background
finalize, finalizeTaskRunByRunId re-reads the registration at call time,
falls back to the default executor, and finalizes the run successfully - so
the finalize spy is never called while the run itself completes with an ok
frame.

The prior event-driven rework (#109653, 883995f08d) could not fix this: it
only waits longer for a spy call that never comes. The older 2s
waitForAssertion flake ("expected 1 call, got 0") was the same mechanism.

Fix at the harness boundary: gateway agent handler tests now pin the
subagent-registry deps with a no-op ensureRuntimePluginsLoaded (matching
subagent-registry's own suites), applied at harness setup, in the shared
afterEach, and through the one wholesale deps override, so no leaked
ended-run hook can reload the plugin runtime and clear a later test's
runtime seam mid-test.

Proof:
- Deterministic repro (temporary env-gated interleaving patch in
  subagent-registry.ts, not committed): hold the ACP plugin-child run's
  ended-hook load until the next finalize-only runtime registration appears,
  then reload synchronously. Pre-fix this reproduces the exact CI failure
  (target times out at 120000ms; registration observed ABSENT right after
  the reload). Post-fix, with the identical forced interleaving, all 238
  tests pass and the registration stays present.
- Stress post-fix: 10x agent.test.ts and 5x src/gateway/server-methods with
  OPENCLAW_VITEST_MAX_WORKERS=6 - 0 failures (3765 tests per shard run).
- The same wipe-then-re-register window exists in prod plugin activation
  (loadOpenClawPlugins cache-miss path); flagged separately for an
  owner-reviewed atomic-swap follow-up rather than bundled here.
2026-07-17 01:16:34 -07:00
Peter Steinberger 4ecd85e0b7 fix: keep healthy channel accounts running when one credential fails (#109766)
* fix(secrets): isolate channel account credentials

* fix(secrets): normalize Feishu default ownership

* fix(feishu): preserve accountless secret ownership

* fix(matrix): keep accountless top-level refs active
2026-07-17 01:12:18 -07:00
Peter Steinberger e693d279b3 fix: Bedrock ARN region routing, Copilot device-flow pacing, TUI input fixes (#109740)
* build(deps): bump terminal UI library to 0.80.9

* fix(plugins): Bedrock ARN region routing, stop-reason detail, and Copilot device-flow pacing
2026-07-17 01:09:29 -07:00
Peter Steinberger 6ac15970a7 feat(onboarding): unified empty-state with provider icons, websites, and recommended installs (#109681)
* feat(onboarding): unify setup recommendations

* fix(control-ui): proxy model setup icons

* fix(onboarding): refresh landing artifacts

* chore(ui): sync translated i18n catalogs

* chore(ui): keep generated locale artifacts on the refresh workflow
2026-07-17 01:01:06 -07:00
Peter Steinberger 543244e7bb perf(ci): remove the anthropic-cli jiti stall and stripe the full-plan whales (#109769)
* perf(sdk): load Claude CLI identity from a narrow plugin artifact

src/plugin-sdk/anthropic-cli.ts snapshots CLAUDE_CLI_BACKEND_ID at module
scope through the sync facade loader, which jiti-evaluates the full
anthropic api.js barrel on source checkouts: 130.86s of self time per
cold worker on CI (Testbox import profile), silently stalling every job
whose graph reaches cli-runner/prepare.ts. A narrow cli-api.js artifact
carries the two static facts; Testbox proof: the reliability+helpers
agents-core pair drops from 157.6s to 14.3s.

* perf(ci): stripe unit-fast and tooling node tests on the full plan

core-fast ran the import-bound unit-fast graph as one job (247s vitest,
181s module evaluation) and core-tooling as one serial job (241s); both
now stripe on the full plan like the compact plan, so the compact-only
expansion is deleted and the docker helper config rides with the
isolated shard on both plans. Group hints refreshed from main run
29551077288; stale per-file commands hints dropped (the consolidation
landed); cache-writer selection and the warm workflow track the stripe
names.

* fix(state): tolerate vanished sqlite sidecars in agent-db permission sweep

existsSync+chmodSync raced SQLite's own WAL/SHM cleanup: a checkpoint or
close between the two calls throws ENOENT (observed from the transcript
reconcile worker in server-startup-web-fetch-bind on CI). chmod directly
and swallow only ENOENT, which removes the TOCTOU window.
2026-07-17 00:58:32 -07:00
Peter Steinberger 0e792b6de3 refactor(channels): centralize inbound orchestration and remove internal compat (#109716)
* refactor(channels): centralize inbound turn orchestration

* refactor(runtime): remove stale compatibility paths

* chore(guards): reject internal deprecated API use

* refactor(channels): simplify core turn planning

* chore(guards): keep deprecated checks boundary-focused

* refactor(memory): keep modern config off compat barrel

* fix(msteams): preserve feedback learning

* test(channels): align modern inbound fixtures

* refactor(channels): finish modern inbound migration

* refactor(channels): tighten core inbound kernel

* fix(channels): preserve turn assembly narrowing

* test(sdk): keep runtime mock binding immutable

* test(matrix): isolate read policy runtime

* test(msteams): mock canonical reply factory

* test(slack): mock core inbound turn dispatch

* test(telegram): inject core session recorder

* test(signal): inject core session recorder

* test(googlechat): assert canonical inbound routing

* test(synology-chat): align core turn fixture

* fix(sdk): preserve direct DM runtime compat

* refactor(channels): own inbound envelope compat in core

* refactor(channels): trim inbound dispatch seams

* refactor(channels): remove redundant async wrappers

* test(synology-chat): type canonical dispatcher mock

* refactor(channels): remove remaining dead compat seams

* chore(sdk): refresh API baseline after rebase

* fix(channels): preserve direct DM identity metadata
2026-07-17 00:56:46 -07:00
Peter Steinberger 765bb37364 feat(setup): stream provider prepare progress to every surface (#109764)
* feat: stream provider preparation progress

* fix(android): refresh gateway method constants
2026-07-17 00:55:04 -07:00
Trevor Malone b8d43ea49a fix(config): strip obsolete memorySearch keys before schema validation (#68664) (#68685)
* fix(config): strip obsolete memorySearch keys from defaults and list (#68664)

* fix(config): preserve canonical memory migration precedence

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-17 00:43:14 -07:00
Peter Steinberger f095549d1d perf(agents): cut CLI runner reliability import cost (#109772) 2026-07-17 00:41:00 -07:00
Peter Steinberger d26951ae9a fix(secrets): degrade stale auth profile refs on startup (#77213)
* fix(secrets): isolate unavailable auth profile refs

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* fix(secrets): retain current auth profile metadata

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* style(auth): use explicit materialized fields

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* fix(secrets): scrub skipped auth profile material

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* test(auth): separate setup fallback from secret refs

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* fix(secrets): reuse prepared auth alias metadata

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-07-17 00:38:35 -07:00
Peter Steinberger 2b18ef7426 feat(codex): surface native questions and goals (#109724)
* feat(codex): bridge native questions and goals

Co-authored-by: EVA <eva@100yen.org>

* fix(ui): preserve native question text encoding

* fix(codex): keep secret answers off action paths

* fix(codex): validate native action keys exactly

* fix(codex): isolate new goals and free-form answers

* style(codex): satisfy extension lint

* fix(codex): keep goal replacement non-destructive

* fix(codex): separate native goals from continuation

* fix(codex): preserve exact native answer labels

* test(codex): assert goal continuation stays disabled

* fix(codex): remove unused bridge exports

* test(codex): refresh goal feature snapshots

---------

Co-authored-by: EVA <eva@100yen.org>
2026-07-17 00:35:39 -07:00
ooiuuii 6651813bd0 fix(logging): redact auth-style HTTP headers (#107999)
* fix(logging): redact auth-style HTTP headers

* fix(logging): redact structured auth headers

* test(logging): keep auth fixtures synthetic

* test(logging): preserve existing gateway fixtures

* fix(logging): cover serialized auth credentials

* fix(logging): harden auth header redaction

* fix(logging): cover serialized auth whitespace

* chore(logging): refresh reviewed PR head

* test(logging): expect complete API key redaction

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-17 00:25:15 -07:00
tanshanshan 473dfc2e51 fix(compaction): add circuit breaker to stop token burn when summarizer unavailable (#86900)
* fix(compaction): stop repeated staged fallbacks

Co-authored-by: tanshanshan <tan.shanshan@xydigit.com>

* fix(compaction): preserve degraded summary state

* refactor(compaction): keep result state internal

* fix(compaction): cancel incomplete circuit-open runs

* chore: refresh PR checks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-17 00:18:33 -07:00
Yiğit ERDOĞAN e228a3ca3a fix: gateway.cmd launcher garbles non-ASCII profile paths on non-CJK Windows locales (#108967)
* fix(daemon): encode Windows cmd launcher to the console OEM code page

* fix(infra): unexport resolveWindowsSystemEncoding

The launcher encoder now resolves the boot-time OEM code page via
resolveWindowsOemEncoding, leaving resolveWindowsSystemEncoding with no
external importers; the dead-export ratchet (knip all-exports) fails CI
on exported-but-module-local functions.

* test(windows): cover OEM launcher encoding resolver

* fix(windows): declare launcher OEM code page

* fix(windows): fail closed on unsupported OEM pages

* fix(windows): reject CP1258 precomposition drift

* fix(windows): guard CP864 launcher syntax

* test(windows): pin OEM launcher code page

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-17 00:16:59 -07:00
Peter Steinberger b64242a4a9 fix(cron): isolate unavailable webhook secret (#109739) 2026-07-17 00:10:45 -07:00
Peter Steinberger a2ec3f6904 refactor: separate external conversations from local sessions (#109411)
* feat: separate external conversations from sessions

* test: cover conversation tool authorization

* chore: refresh session schema baseline

* fix: persist effective conversation delivery text

* fix: bind conversation identity to delivery target

* fix: preserve pending conversation deliveries

* fix: make conversation delivery outcomes synchronous

* refactor: make conversation delivery durable

* fix: harden durable conversation retries

* fix: close durable conversation pre-send gaps

* fix: terminally reject invalid conversation deliveries

* refactor: route external conversations through gateway

* fix: namespace conversation delivery queue intents

* refactor: remove obsolete transcript replacement path

* test: prove conversation delivery v10 migration

* test: keep conversation action mock typed

* fix: clear conversation refactor CI gates

* test: align Reef delivery coverage

* chore: refresh Android conversation strings

* fix: harden external conversation state

* fix: close conversation race edges

* fix: preserve conversation delivery identity

* fix: replay durable conversation operations

* fix: unify agent schema at version 11

* chore: move release note context to PR

* fix: make required queue persistence force core delivery

* chore: refresh plugin SDK baseline
2026-07-17 00:09:51 -07:00
Peter Steinberger b84199644d fix: silent credential-save failures and session/tool hygiene gaps (#109622)
* fix(agents): surface auth persistence failures and harden session, model, and shell tool hygiene

* refactor(agents): keep shell invocation types module-local

* test(agents): derive shell config type locally
2026-07-17 00:02:45 -07:00
Peter Steinberger 007d50f6ff test(commands): pin session freshness fixtures away from the daily-reset boundary (#109705) 2026-07-17 00:01:16 -07:00
Chris 35fa1f9613 fix(reply): resolve abort response prefix context (#45315)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-17 00:00:50 -07:00
Peter Steinberger 44569ffdda fix(agents): prevent stale replies after transcript rewrites (#109676)
* fix(agents): scope final replies to current run

Co-authored-by: ZengWen-DT <ceng.wen@xydigit.com>

* refactor(agents): internalize attempt helper

* fix(agents): preserve yielded turn classification

* fix(agents): preserve yielded run ownership

---------

Co-authored-by: ZengWen-DT <ceng.wen@xydigit.com>
2026-07-16 23:58:43 -07:00
Abner Shang a33ddc9dc5 fix(openai): close OAuth callback prompts cleanly (#89491)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 23:58:41 -07:00
Peter Steinberger c13925b387 fix(webhooks): keep routes cold when SecretRefs are unavailable (#109715)
* fix(webhooks): isolate unresolved route secrets

* chore(plugin-sdk): refresh API baseline

* docs(secrets): refresh credential surface
2026-07-16 23:56:59 -07:00
Peter Steinberger a838c68dbe feat(talk): add Gemini Live video (#109719)
Stream bounded browser camera frames directly to Gemini Live and keep camera media off the Gateway. Add lifecycle, function-calling, fake-camera E2E, and live-smoke coverage.

Co-authored-by: shushushu <1064076525@qq.com>
2026-07-16 23:55:06 -07:00
Peter Steinberger 21388da3aa perf(test): poll subagent lifecycle state faster (#109722) 2026-07-16 23:54:54 -07:00
Peter Steinberger 0097363c69 fix(secrets): keep gateway alive when web provider refs fail (#109687)
* fix(secrets): isolate unavailable web providers

* fix(secrets): keep web result type internal

* fix(secrets): hide unavailable web selections
2026-07-16 23:48:37 -07:00
Peter Steinberger 2030ac7438 fix(acp): preserve legacy replay state during doctor repair (#109685)
* fix(acp): move legacy replay migration to doctor

* refactor(acp): use Kysely for replay migration

* style(acp): remove redundant replay coercions

* style(acp): format replay migration
2026-07-16 23:29:29 -07:00
Peter Steinberger fb946267ae perf(test): poll gateway startup state faster (#109714) 2026-07-16 23:28:20 -07:00
Peter Steinberger f0aa2c892f refactor(tui): unify local queue fallback admission (#109706) 2026-07-16 23:16:11 -07:00
Peter Steinberger 03d287d111 fix: Responses streams mishandle interleaved output items and silent truncation (#109615)
* fix(ai): track Responses output items per index and require terminal stream events

* test(ai): add live Responses stream coverage

* test(infra): mirror packages live glob in live-config test
2026-07-16 23:15:35 -07:00
Masato Hoshino 45ab24dac4 fix: queued MEDIA attachments survive delivery retries (#108969)
* fix: queued MEDIA attachments survive delivery retries

Local media carried in a legacy MEDIA: text directive was invisible to
queue staging (which reads only structured mediaUrl(s)), so the raw
directive was persisted with no queue-owned copy and a retry read the
already-deleted producer path — burning the retry budget and dropping the
attachment plus its text.

Project each source payload's effective media through the canonical
createOutboundPayloadPlan before staging and fold directive-derived local
sources into the queue copy's structured fields, so the existing spool
takes custody. The custody copy anchors both mediaUrl and mediaUrls to the
effective set so the staged copy overrides the preserved in-text directive
on replay; the rendered-batch plan is recomputed from the deduplicated
effective media to avoid count inflation. Raw pre-hook text, the copy-free
live send, the same media capability, and sensitive-media fail-closed are
all unchanged. No schema/config/TTL change.

Extends the ownership invariant from #108501 / #108502 to the legacy
MEDIA: text-directive carrier.

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

* refactor(outbound): localize queue media custody

Trim the direct helper matrix in favor of end-to-end queue recovery proof.

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 23:15:08 -07:00
Yuval Dinodia 73cf9a8bed fix(plugins): plugins.deny is bypassed by a mixed-case manifest id (#109237)
* fix(plugins): canonicalize manifest plugin ids to lowercase

Plugin config policy lists (plugins.deny, plugins.allow, plugins.entries)
are lowercase-normalized through normalizePluginId, but a plugin's
self-declared manifest id was only trimmed, never lowercased. A plugin
publishing "id": "Malicious-Scraper" therefore never matched an operator
denylist entry of "malicious-scraper" and fell through to default
activation, loading its hooks, channels, secret integrations, and tools.
The same gap let a mixed-case spelling evade the core reserved-id check.

Canonicalize the id at the manifest parse boundary where
PluginManifestRecord.id is minted, so every downstream policy consumer
compares against the same canonical form.

* test(plugins): prove mixed-case deny at gateway startup

* test(plugins): assert denied gateway record is absent

* test(plugins): materialize allowed secret fixture

* fix(plugins): compare a derived policy key instead of rewriting manifest identity

Manifest ids stay exactly as declared. Deny, allow, and per-entry checks now
compare a lowercase policy key derived at each enforcement boundary, matching
the lowercase-normalized config lists.

The previous approach lowercased PluginManifestRecord.id at the parse boundary.
That id is also matched against the plugin runtime export id, and a mismatch is
a hard load failure, so an existing plugin declaring the same mixed-case id in
both its manifest and its runtime export would stop loading after upgrade.

The Gateway fixture now declares a mixed-case manifest id with a matching
mixed-case runtime export, covering that upgrade case, and drops the allowlist
that previously scoped discovery by a lowercase id and masked the denylist
behavior under test.

* fix(plugins): close mixed-case policy gaps

* test(plugins): use tracked policy fixture temp dir

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 23:13:13 -07:00
Peter Steinberger c63184ee9a fix(acp): persist failed bound turns so transcripts match the channel (#109683)
* fix(acp): persist failed bound turns so transcripts match the channel

A bound ACP turn that failed delivered an error reply to the channel but
wrote nothing to the session transcript: persistAcpDispatchTranscript ran
only on the success path, and turnPromptText was block-scoped inside the
try so the catch could not reach it. The bound transcript then silently
diverged from what the user saw, and the next turn resumed from history
that never mentioned the failure. Persist failures were also swallowed at
verbose level.

Persist exactly once, after the turn's outcome settles:
- success writes after finalization, so a finalizer failure is no longer
  recorded as a clean success;
- failures record the streamed output ahead of the error text, snapshotted
  before delivery so the accumulator cannot fold the error in twice;
- a dispatch flag (not prompt-text truthiness) gates the failure write, so
  attachment-only turns are covered and pre-dispatch setup failures do not
  mint blank-prompt records.

Found while live-testing the ACP connectors: codex and gemini bind lanes
pass 5/5, claude times out with turns that complete but never appear in the
bound transcript. This fixes the silent-divergence class and makes that
failure observable; the claude-specific trigger is tracked separately.

* fix(acp): narrow acp resolution before reading meta in the shared persist helper
2026-07-16 23:12:58 -07:00
Peter Steinberger c742caee4b perf(agents): prime code mode with compact tool ids (#109651) 2026-07-16 23:09:03 -07:00
Peter Steinberger c80b236a09 fix: local chat honors queue steering (#109689)
* fix(tui): honor local queue steering

* fix(tui): narrow pending collect queue

* test(tui): validate collected call safely

* test(tui): emit local steering evidence
2026-07-16 23:03:27 -07:00
Monkey-wusky a8fd558d62 fix(docs): cancel non-OK search response body before throwing (#109508)
When the Cloudflare docs search API returns a non-OK status,
fetchDocsSearch threw without cancelling the response body.
This could leave the underlying connection open until GC.

Cancel the body before throwing, matching the pattern established
in reef guard adapters (#109196) and fetchOpenRouterModels.
2026-07-16 22:58:13 -07:00
pick-cat f4f5150557 fix(video-generation): DashScope downloads hang when CDN body stalls after headers (#109394)
* fix(video-generation): bound stalled DashScope download body reads

* test(video-generation): narrow DashScope download asset for tsgo

* test(video-generation): narrow optional DashScope video buffer for tsgo

* test(video-generation): bind DashScope buffer before tsgo asserts

* fix(video-generation): fail closed on exhausted DashScope download budget

When the operation deadline passes a function that returns 0, the
previous resolver gate (resolved > 0) dropped the exhausted budget
and fell back to the 120s default. Also, readResponseWithLimit skips
chunk idle timeout when chunkTimeoutMs is 0/falsy, so a zero budget
would read the body unbounded instead of failing fast.

Now the resolver preserves zero/negative values and the body stage
throws immediately when the remaining budget is exhausted.

Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>

* fix(video-generation): reject exhausted download deadline before fetch

Check the resolved download timeout before fetchWithTimeoutGuarded so an
already-exhausted operation does not initiate network I/O. Previously the
deadline guard ran after headers, which still sent bytes over the wire.

Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>

* fix(video-generation): release failed DashScope downloads

---------

Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 22:54:41 -07:00
Leon-SK668 f977649065 fix(auto-reply): preserve emoji in bash session snippets (#109468) 2026-07-16 22:43:40 -07:00
Peter Steinberger 630b16805c fix(providers): refresh zai/kimi/moonshot/xai catalogs against July 2026 vendor docs (#109666)
* fix(providers): refresh vendor catalogs and setup hints

* test(providers): align registration metadata expectations
2026-07-16 22:41:03 -07:00
Peter Steinberger 399345e1bb feat: report Pi and OpenCode in guided setup (#109624)
* feat(onboarding): detect Pi and OpenCode CLIs

* fix(onboarding): clarify Pi and OpenCode readiness
2026-07-16 22:35:33 -07:00
Peter Steinberger a1dfd47edd refactor(sdk): collapse plan updates to typed steps (#109660)
* refactor(sdk): collapse plan updates to typed steps

Per owner decision, remove the shipped steps: string[] SDK field and its unreleased planSteps replacement early. Collapse onPlanUpdate to one typed steps field. Retain wire-level string normalization for external Codex plugin version skew.

* chore(sdk): tighten rebased surface budget
2026-07-16 22:35:29 -07:00
Peter Steinberger ec42788cef perf(ci): cache packaged dependency reify in the docker e2e functional image (#109665)
* perf(ci): cache packaged dependency reify in the docker e2e functional image

Split the shared functional E2E image into manifest -> deps -> app stages so
the 328-package npm reify is layer-keyed on package.json + npm-shrinkwrap.json
alone. Per-PR tarballs (changed dist bytes, unchanged deps) now hit the cached
dependency layer on warm builders (Blacksmith sticky docker layers, local
buildkit) instead of re-running npm install on every build; the per-PR work
drops to manifest extract + package extract + packaged postinstall.

The deps stage reify also matches the shipped shrinkwrap exactly; the previous
npm install -g <tarball> path silently drifted ~15 transitive packages to newer
registry versions and lost the node-domexception -> @nolyfill/domexception
alias. npm install (not npm ci) because the generated shrinkwrap does not
guarantee npm ci's strict manifest sync (p-retry pins @types/retry@0.12.0 while
the shrinkwrap resolves 0.12.5).

Local proof (Apple Silicon, docker-container builder): per-PR warm image build
59s -> 43s with the 29s npm layer CACHED; lane total 164s -> 139s warm,
229s -> 160s cold; system-agent-first-run lane passes end to end, and the
installed tree matches the old image structure except for the documented
version-drift fixes.

* test: update e2e image guard for the staged dependency layer

The guard pinned the retired npm install -g invocation; assert the new
invariants instead: manifest-keyed deps layer, no global-prefix install,
deps COPY into /app, and postinstall ordered before the self-link so its
prune walks cannot cycle.
2026-07-16 22:34:13 -07:00
Peter Steinberger 44b79d44ae test(agents): stabilize one-shot host exit wait (#109663) 2026-07-16 22:19:06 -07:00
Peter Steinberger 883995f08d test: stabilize flaky tests at root cause (batch 2) and fix the isolate:false cleanup hole (#109653)
* test: stabilize four flaky tests at root cause

- test/non-isolated-runner.ts: move shared-worker cleanup from onAfterRunSuite
  to onAfterRunFiles. Vitest early-returns runSuite for files that fail during
  collection, skipping onAfterRunSuite, so a crashed sibling left its evaluated
  real modules cached and the next file's vi.mock factories silently never
  applied — the "res.setHeader is not a function" failures in
  http-utils.authorize-request.test.ts. onAfterRunFiles fires per file
  regardless of collect/run outcome. Regression meta-test spawns a child
  vitest run (collect-crash sibling + mock-dependent file) and fails on the
  old runner with flavor:real vs flavor:mocked.
- src/gateway/http-utils.authorize-request.test.ts: export every http-common
  binding http-auth-utils imports so the factory stays isolate:false-safe.
- src/gateway/session-message-events.test.ts: drop the beforeAll 60s override;
  the suite harness cold-imports the full gateway server graph, which can
  legitimately exceed 60s under contention. Sibling suites use the shared
  project hookTimeout (120s/180s) for the same boot.
- src/gateway/server-methods/agent.sessions-and-models.test-utils.ts: the
  finalize-throw test polled a 2s waitForAssertion for an off-turn background
  run; signal finalize via a promise resolved from the spy (event-driven,
  bounded by the test timeout) and keep the bounded poll for the follow-up
  respond/warn observations.
- ui/src/pages/chat/chat-responsive.browser.test.ts: budget cold-app first
  renders at 30s (real-app cases boot a cold Vite dev server whose first
  transform can starve past 10s under 6-worker contention) and replace
  one-shot isVisible reads with bounded locator waits for the state-driven
  hover reveal.

Proof: focused runs green; 5x repeats of the three gateway files and 5x
repeats of chat-responsive with OPENCLAW_VITEST_MAX_WORKERS=6 all green;
runner regression test fails pre-fix, passes post-fix.

* test: isolate runner meta-test child env from GitHub Actions

The child vitest inherited GITHUB_ACTIONS/CI from the runner job, which
turned on ANSI colors (breaking the plain-substring assertions) and let
the child's github-actions reporter emit ::error annotations the parent
job rendered as its own failures. Drop GITHUB_ACTIONS/FORCE_COLOR and set
NO_COLOR, which overrides every tinyrainbow enable path.

* test: wait for parseable pid in tsdown-build pid-file polls

waitForFile existence polling can catch writeFileSync's open-truncate
0-byte window, yielding NaN pids and false isProcessAlive failures (the
same class as #109140). Poll until the file parses to a positive pid;
covers all three sibling pid-read sites in the suite.
2026-07-16 22:18:16 -07:00
Peter Steinberger fd3ee5c1eb refactor(proxy-capture): drop stale body reader gate (#109386)
* refactor(proxy-capture): drop stale body reader gate

* test(proxy-capture): prove clone-only body capture
2026-07-16 22:03:38 -07:00
Peter Steinberger e873a7f955 refactor(memory): move QMD coordination to SQLite (#109636)
* refactor(memory): move QMD coordination to SQLite

* chore: keep release notes in PR body

* chore: annotate lease SQLite primitive
2026-07-16 22:01:34 -07:00
Erick Kinnee 571f1dcef9 fix(dreaming): drop heartbeat assistant responses from dream corpus (#109403)
* fix(dreaming): drop heartbeat assistant responses from dream corpus

The dreaming ingestion pipeline filters heartbeat user messages
(containing [OpenClaw heartbeat poll]) via sanitizeSessionText, but the
paired assistant response is only dropped when it is the exact string
HEARTBEAT_OK. Local models frequently respond with natural-language
acknowledgments (e.g. "Heartbeat received. Main is active.") which pass
through the sanitizer unchanged and enter the dream corpus as
low-confidence (0.58) memory snippets.

Fix: track heartbeat user message drops in buildSessionEntry and skip
the immediately following assistant response. This cross-message coupling
is safe because the heartbeat prompt pattern is injected by the runtime
and cannot be spoofed by user input, unlike [cron:...] or
System (untrusted): ... patterns (see PR #70737).

Refs: #103720

* test(dreaming): add heartbeat assistant response filter test

* fix(dreaming): use provenance-based heartbeat detection instead of text matching

Replace text-based heartbeat detection (isGeneratedHeartbeatPromptMessage)
with provenance-based authentication. The heartbeat turn now carries
provenance: { kind: "heartbeat" } when persisted to the transcript,
and buildSessionEntry checks for this provenance instead of matching
user-spoofable text content.

Changes:
- src/sessions/input-provenance.ts: Add "heartbeat" to
  INPUT_PROVENANCE_KIND_VALUES
- src/auto-reply/reply/get-reply-run.ts: Attach heartbeat provenance
  to user turn input when isHeartbeat is true
- packages/memory-host-sdk/src/host/session-files.ts: Check
  message.provenance.kind === "heartbeat" instead of text matching
- packages/memory-host-sdk/src/host/session-files.test.ts: Update
  heartbeat test to include provenance; add lookalike regression test

This addresses the ClawSweeper review finding [P1] about user-spoofable
text matching. The cross-message coupling is now authenticated by
runtime provenance, not user-typed content.

Refs: #103720

* fix(dreaming): add targeted heartbeat-derived corpus repair

* fix(dreaming): fix TS return type for clearScopedLegacySessionIngestionJson catch

* fix(dreaming): match chunked SQLite seen-state keys by stored scope

* fix(dreaming): repair SQLite-backed session transcript lookup in doctor

The findHeartbeatContaminatedCorpusLines function could not read
SQLite-backed session transcripts. When a corpus ref had no .jsonl
extension (SQLite logical path format), it appended .jsonl, read an
empty file, and silently skipped — leaving pre-fix heartbeat
contamination intact for SQLite users.

Fix:
- Add loadTranscriptLinesFromSqlite helper that reads events via
  the canonical loadTranscriptEventsSync API
- Serialize all events to preserve original event positions for
  corpus #L<n> line-number references
- Fall back to SQLite reader when filesystem read fails on a
  non-.jsonl corpus ref path

Test: add SQLite-backed session regression test that seeds a
session, writes corpus refs without .jsonl extension, and
verifies both audit detection and targeted repair.

* fix(dreaming): address autoreview P1 and P2 for checkpoint clearing and early return

P1: Remove clearScopedSessionIngestionState call after heartbeat line
removal. Clearing the cursor causes the next ingestion to re-process the
transcript from the beginning, duplicating normal corpus lines that were
deliberately retained.

P2: Remove early return after heartbeat cleanup so the self-ingestion
narrative content check and archiveDiary request still run.

* fix(dreaming): resolve corpus ref session path without duplicate agent ID

The session path in corpus refs is already in the format
'sessions/main/abc.jsonl' (includes agent subdirectory). The
previous code prepended source.agentId again, creating a path
like 'agents/main/sessions/main/abc.jsonl' — which silently
returned empty in production workspaces.

Fix: use path.basename to extract just the filename from the
session path, then construct the transcript path correctly.

* refactor(dreaming): split repair utils into separate file to satisfy LOC ratchet

The dreaming-repair.ts file grew from 337 to 831 lines, exceeding the
500-line LOC ratchet limit. Split helper utilities into a new
dreaming-repair-utils.ts file:

- dreaming-repair.ts (365 lines): imports, types, public API functions
  (auditDreamingArtifacts, repairDreamingArtifacts)
- dreaming-repair-utils.ts (498 lines): all helper functions, constants,
  and internal types

* fix(ts): add missing type imports for return types in dreaming-repair.ts

* fix(deadcode): remove unused exports from dreaming-repair-utils.ts

* fix: resolve merge conflict marker in get-reply-run.ts

* fix: remove unnecessary export from INPUT_PROVENANCE_KIND_VALUES

* fix(dreaming): converge targeted repair path with wholesale archive+clear

When heartbeat contamination is found, archive the entire session-corpus directory and clear all SQLite checkpoints instead of doing targeted line-by-line rewrite. This ensures the cleaned corpus gets re-ingested under the new provenance-based forward filter.

* fix(dreaming): authenticate heartbeat transcript turns

Co-authored-by: Erick Kinnee <1707617+ekinnee@users.noreply.github.com>

---------

Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
Co-authored-by: Erick Kinnee <ekinnee@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 22:00:21 -07:00
Peter Steinberger e7c389b97a perf(ci): cache declarations and remove test import tail (#109593) 2026-07-16 21:51:06 -07:00
Peter Steinberger a77c0770ff refactor(speech-core): share TTS request pre-resolution across discord and voice-call (#109640) 2026-07-16 21:47:06 -07:00
Peter Steinberger 9b80f9a8b1 fix(cli): emit resolved config paths in machine output, not abbreviated home (#109361)
`config file`, `config patch --dry-run --json`, and `config unset --json` ran
the config path through shortenHomePath, emitting `~/…` or the literal
`$OPENCLAW_HOME/…` token in machine output — breaking `cat $(openclaw config
file)` and corrupting JSON path fields. `agents list --json` already resolves.
Emit the raw resolved snapshot path at these machine-output sites; human table
rendering keeps the friendly abbreviation.
2026-07-16 21:45:04 -07:00