* fix(agents): wake owning session after generated-media direct delivery
When a background media generation (image/music/video) completes but the
requester's agent turn cannot be woken, delivery falls back to sending the
media straight to the channel. The owning session never learned about it,
leaving the attachment orphaned with no agent follow-up.
Queue a system event plus a scoped heartbeat wake after both direct
fallback paths (announce delivery and the shared background lifecycle) so
the agent continues the conversation in its own voice. Subagent requester
sessions skip the heartbeat since their completions route via announce.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): persist generated media session handoffs
Co-authored-by: Molty <steipete@gmail.com>
* chore: prune max-lines baseline
* fix(agents): harden durable media handoffs
* fix(agents): charge media retries once
* chore: refresh max-lines baseline
* fix(agents): persist delivery settlement cleanup
* fix(agents): fence interrupted media delivery
* fix(agents): fence durable handoff admission
* fix(agents): reconcile terminal handoffs at retry limit
* fix(agents): align durable handoff types
* test(agents): type durable handoff fixtures
* fix: satisfy session delivery lint
* fix: avoid payload map spreads
* fix: satisfy durable handoff gates
* test: align generated media handoff expectations
* fix: avoid serializing session recovery state during load
---------
Co-authored-by: Molty <steipete@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(gateway-protocol): add session placement schema
Closed state discriminator for session execution placement (local/requested/provisioning/syncing/starting/active/draining/reconciling/reclaimed/failed), sessions.dispatch params, and worker-admission transcript/live cursor extensions. Swift protocol models mirror the schema.
* feat(state): add worker session placement table
worker_session_placements rows carry placement state, transition generation, worker ownership metadata, ACK cursors, and the turn claim columns used for atomic admission.
* feat(cloud-workers): add durable placement state machine store
SQLite-backed placement store split by concern: state table (placement-state), discriminated record types + shape invariants (placement-record), row codec + CAS transition values (placement-row-codec), atomic turn-claim admission/release/waiters (placement-turn-claims), and lifecycle CAS transitions (placement-store).
* feat(cloud-workers): sync workspaces and attach sessions to worker environments
Environment service session attachment + turn credentials, tunnel workspace commands over a dedicated SSH runner, and git/plain workspace sync into $HOME/.openclaw-worker/workspaces with an immutable manifest. Symlink escapes are rejected locally before transfer (macOS openrsync stat-fails them opaquely) and again by the remote manifest guard.
* feat(worker): run one-shot embedded turns from launch descriptors
Worker runtime executes a single embedded turn from a stdin launch descriptor and reports completed/failed/fenced on stdout for the gateway launcher. Terminal lifecycle live events are deferred past the final transcript flush; transcript projection helpers are shared via transcript-message instead of duplicated in the runtime.
* feat(cloud-workers): dispatch placements and route worker turns
Dispatch service drives local->requested->provisioning->syncing->starting->active with failure teardown (placement-dispatch-failure) and restart/runtime recovery incl. lost-worker reclaim (placement-dispatch-recovery). Worker turn launcher claims the placement turn atomically, builds a windowed launch descriptor (worker-turn-payload), runs the remote one-shot worker, and reconciles the committed transcript; agent runners route turns through the session placement admission provider.
* feat(gateway): expose session placement RPCs and startup reconciliation
sessions.dispatch RPC with lifecycle admission barriers, operator-facing placement projection on session listings, placement-aware session reset guard, and startup/interval reconciliation wiring for worker placements.
* refactor(sessions): migrate runtime storage to sqlite
* test(sessions): fix sqlite CI regressions
* test(sessions): align remaining sqlite fixtures
* fix(codex): require sqlite trajectory recorder
* test(sessions): align orphan recovery sqlite fixture
* test(sessions): align sqlite rebase fixtures
* fix(sessions): finish current-main integration of the sqlite flip
Resolve the whole-store SDK removal across its owner boundary: drop the
loadSessionStore re-export and the registry whole-store wrappers, wire
hasTrackedActiveSessionRun into gateway chat, complete the
preserveLockedHarnessIds cleanup contract, flip the codex thread-history
import to storePath targets, and port remaining main-side tests from
file-store helpers to session accessor reads.
* chore: drop committed pebbles log, revert plugin-inspector bump, refresh generated docs
Remove the 1.8k-line .pebbles/events.jsonl work log from the branch, restore
the plugin-inspector advisory lane to main's pinned 0.3.10 so the supply-chain
bump gets its own review, and regenerate docs_map, the plugin SDK API baseline,
and the export-surface ratchet for the merged tree.
* feat(sessions): keep archived transcripts by default with zstd cold storage
Codex-style retention: deleting or resetting a session archives its
transcript as a zstd-compressed JSONL artifact (plain when the runtime
lacks node:zlib zstd) and keeps it until the disk budget evicts oldest
first. resetArchiveRetention now governs both deleted and reset archives
and defaults to keep; maxDiskBytes defaults to 2gb so retention stays
bounded, with archives evicted before live sessions. The cron reaper
follows the same knob instead of deleting archives on its own timer.
* fix(state): converge agent DB migration lineages and bound database growth
Merge coherence: run both structure-gated legacy memory-schema repairs
(flip-lineage drop, main-lineage identity rebuild) before the flip
migration so pre-flip v1/v2 and pre-merge flip v1/v4 databases all
converge, and hoist foreign_keys=OFF outside the schema transaction
where the pragma was silently ignored and the v1 sessions rebuild
cascade-deleted session_entries.
Growth guards: fresh agent DBs enable auto_vacuum=INCREMENTAL, WAL
maintenance releases freed pages in bounded passes (never a blocking
full VACUUM), and doctor reports state/agent DB bloat from freelist
stats.
* fix(codex): resolve the store path for thread-history import via the SDK
The supervision catalog passed the legacy sessionFile locator to the
storePath-targeted transcript mirror; resolve the agent store path with
the session-store SDK helper instead of a runtime-object seam so test
fakes and headless callers need no extra surface. Drop the obsolete
missing-session-id preprocessing case: sessions rows are NOT NULL on
session_id and upsert repairs id-less patches at write time.
* fix(sessions): fail safe on malformed disk-budget config and doctor stat errors
A malformed explicit maxDiskBytes disables the budget instead of
falling back to the destructive 2gb default the user never chose, and
the doctor bloat check skips databases whose paths stat-fail instead of
aborting doctor.
* fix(sessions): complete sqlite conflict translations
* test(sqlite): align hardening checks with maintenance
* test(sessions): inspect compressed transcript archives
* fix(tests): await session seeds and drop unused helpers flagged by CI lint
The five unawaited writeSessionStoreSeed calls raced their SQLite seeds
against the assertions, failing compact shards; the bloat probe drops a
useless initializer and the merged tests drop now-unused helpers.
* test(sessions): type legacy proof events directly
* test(sessions): align hardening contracts
* perf(sessions): read usage transcript sizes from SQL aggregates
Usage/cost scans walked every session and materialized every transcript
event just to re-stringify it for a byte estimate — the #86718 stall
class reborn on the DB. readTranscriptStatsSync sums stored JSON bytes
in SQLite without loading a single row.
* fix(sessions): re-root foreign-root transcript paths onto the current sessions dir
Restored backups, moved OPENCLAW_STATE_DIR, and rehearsal copies carry
absolute sessionFile paths from the old root; the containment fallback
kept those foreign paths, so migration read (and would archive) files in
the original root and reported local copies missing. Re-root the
canonical agents/<id>/sessions suffix onto the current dir when the file
exists there; genuine cross-root layouts still fall through unchanged.
* test(agents): seed harness admission through sqlite
* fix(sqlite): close agent db on pragma setup failure
* fix(doctor): compact and retrofit incremental auto-vacuum after session import
The migration is the sanctioned offline window: post-import compact
reclaims import churn and applies auto_vacuum=INCREMENTAL to databases
created before the fresh-DB pragma existed, so runtime maintenance can
release pages in bounded passes on every install.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): recover claude-cli warm-stdin continuity when no native transcript is written
The headless warm-stdin claude-cli backend (liveSession: "claude-stdio")
never writes a native transcript, so the post-turn flush probe always
fails and the missing-transcript reuse path drops the bound session id.
Part 1 (cli-runner.ts): scope the non-destructive binding behavior to
warm-stdin sessions so they keep their binding instead of clearing it
every turn.
Part 2 (attempt-execution.ts): on a missing transcript, clear the stored
binding (no stale --resume) but still return the bound id as the reuse
candidate so prepare can re-detect the missing transcript and arm
raw-transcript reseed. Returning undefined starved reseed and lost
warm-stdin continuity.
Adds/updates regression coverage in attempt-execution.cli.test.ts and a
complementary reseed test in prepare.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(agents): align warm-session continuity coverage
* test(agents): keep cli live-session mock complete
* fix(agents): respect stateless CLI session mode
* style(agents): keep session candidate guard focused
* test(agents): preserve minimal CLI runner fixtures
* fix(agents): preserve exact Claude warm sessions
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(cron): preserve cron context in session entry for async completion wakes (#99919)
Persist bootstrapContextRunKind on the session entry after cron agent
runs, and restore provider/model/thinking/runKind from the session
entry into directAgentParams when an async completion wake (e.g. media
generation) resumes a cron session.
Before this fix, async completion wakes lost the original cron run
context and fell back to account defaults, causing multi-step cron tasks
to silently dead-end after the first async media generation call.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cron): authorize model override on trusted in-process dispatch for cron context restoration
* fix(cron): persist bootstrapContextRunKind on base session entry for async completion wakes
* fix(cron): route async completion wake to base cron session key instead of ephemeral run key
When the cron run-key fallback fires, record the base cron key as
effectiveSessionKey so deliverDirectSubagentAnnounce can route the
agent call to the persisted session row with its transcript and task
context instead of starting a fresh turn (#99919).
* fix(cron): add bootstrapContextRunKind to session entry slot keys and declare loadRequesterSessionEntry return type
* fix(cron): rebase onto upstream main, restore upstream agent-command behavior while keeping bootstrapContextRunKind persistence
* fix(cron): persist bootstrapContextRunKind from a pre-mutation snapshot to produce a real delta
* fix(cron): move pre-mutation snapshot inside guard so initialEntry is non-optional
* fix(cron): preserve async media continuation context
Co-authored-by: Peter Lee <li.xialong@xydigit.com>
* fix(cron): retain continuation after base cleanup
* test(gateway): complete agent scope mock
* test(gateway): preserve agent scope exports
* test(cron): persist mocked session mutations
* test(cron): model persisted session fixtures
* fix(agents): snapshot cron continuation retries
* fix(cron): harden async continuation settlement
* test(cron): control continuation recovery clock
* fix(gateway): narrow continuation model row
* chore(changelog): defer cron continuation note
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): persist agent request transcript media
Route inline and offloaded agent.request images through the canonical user-turn transcript recorder across embedded, CLI, and ACP runtimes. Share ordered media persistence with chat.send and cover media-only empty-reply turns.
Co-authored-by: Petros Dhespollari <info@peterdsp.dev>
* fix(gateway): avoid transcript media shadowing
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).
Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.
iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.
Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.
Closes#100739
* feat: correlate native search outcomes in audit history
Metadata-only audit ledger for agent runs and tool actions in the shared
state DB: stable event identity, closed action/status/error vocabularies,
one-way-hashed tool-call ids, never-inferred terminal outcomes for native
web-search (explicit completed/failed only; otherwise unknown), bounded
retention, audit.list gateway RPC and openclaw audit CLI. Squashed from
the 82-commit audit stack for replay onto current main.
* feat(audit): add audit.enabled config gate (default on)
The metadata-only audit ledger records by default: an audit trail enabled
only after an incident cannot explain the incident, and the rows are
strictly less sensitive than the transcripts every install already
stores. audit.enabled=false stops new writes at the gateway subscription
seam; audit.list and openclaw audit keep serving existing records until
they expire. Documented in the configuration reference, protocol page,
and CLI reference.
* fix: repair full-matrix CI findings after rebase
- break the dynamic-tools/dynamic-tool-execution import cycle by
extracting resolveCodexToolAbortTerminalReason into a leaf module
- restore main's session-worktree protocol exports lost in the
index.ts auto-merge
- register the audit event writer worker as a knip entry point
- docs table formatting; subagent wait-cancellation test scoped to its
audit intent (outcome + timing) and advanced past main's new
lifecycle-timeout retry grace
* fix(outbound): report delivery status in best-effort message send results
Best-effort sends (forced on for implicit message_tool_only source
replies) previously collapsed failed and partial_failed durable send
results into a success-shaped MessageSendResult, so agents saw a
normal-looking envelope while delivery had actually failed. Surface
deliveryStatus, a formatted error, sentBeforeError, and per-payload
outcomes without changing throw semantics.
* fix(outbound): centralize message delivery outcomes
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
* docs(changelog): split aggregate entries from code landing
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(discord): reuse stored sessionId across voice (stt-tts) turns
Discord voice turns previously called agentCommandFromIngress without a
sessionId, so the core session resolver minted a fresh UUID per turn.
Because the codex app-server thread is bound beside the per-session
file, every voice turn produced a new codex thread, a brand-new
rollout-*.jsonl, and lost all prior context.
The fix reads the persisted session entry for the route's sessionKey
(the same supervisor entry already used for the route) and threads that
sessionId into agentCommandFromIngress, mirroring how voice-call's
response-generator and the gateway 'voice.transcript' dispatcher already
keep voice conversations stable.
Adds extensions/discord/src/voice/ingress.test.ts covering both
directions: reuses the stored id when present, and lets core mint a
fresh one when the store is empty.
Fixes#97688
* fix(discord): preserve voice session after rollover
* refactor(discord): keep voice session ownership in core
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat: add session thread management
Squash of codex/thread-management (025aefc3ad1) onto origin/main:
pin/archive/rename sessions via sessions.patch, archived-aware
sessions.list, lifecycle fencing, read-only archived chat, SDK +
Swift protocol support, Control UI session management.
* refactor(ui): minimal session rows with hover-revealed management
Chat picker and sidebar recents share session-row primitives: single-line
rows, relative timestamps, rename/archive/pin revealed on hover or focus,
accent pin badge for pinned rows, and an active-run spinner in the trail
slot. Sidebar floats pinned sessions above recency via the shared
comparator and gains archive/pin actions through the unified sessions-view
patch fallback. Archive eligibility is one shared policy
(canArchiveSessionRow); the sidebar/picker active-run tooltip now uses the
real sessionsView.activeRun locale key.
* fix: align session admission with mailbox-era main
Integration fixes after rebasing onto current main: sessions_list mailbox
test expectations learn the archived/pinned row fields and archived:false
list param; gateway agent admission treats a session as deleted only when
both the requested and canonical alias sets miss it (legacy bare-main
stores and exec-approval followups read under different spellings); cron
persist tests keep a consistent store across claim-guarded persist calls;
the ACP abort hook test asserts abort propagation instead of signal
identity; drop dead lifecycle writes flagged by no-useless-assignment and
fix the promise-executor return in the codex compact test.
* fix(qa): align UI e2e and shard fixtures with redesigned session rows
Sidebar session rows are wrapper divs with an inner link now: update the
navigation browser tests and chat-flow Playwright selectors. Seed a real
per-test session store for the auto-fallback admission guard instead of
depending on leftover host files at /tmp/sessions.json. Teach the
test-projects routing fixture about the suites that newly import the
shared temp-dir helper. Document the Codex thread-format contract for
archivedAt/pinnedAt (flag derived from server-stamped timestamp, epoch ms
here vs Codex epoch seconds) at the type and in the session docs.
* test: route auto-fallback suite through temp-dir helper plans
The auto-fallback suite now imports the shared temp-dir helper for its
seeded session store, so the top-level helper routing fixture must list
it in the auto-reply plan.
The provider-owned CLI session exemption added for the gateway agent.run path
in #97931 was not applied to the non-gateway session resolvers. Scheduled
isolated-agent cron jobs run through runCronIsolatedAgentTurn ->
resolveCronSession, and the local openclaw command runs through resolveSession;
both called evaluateSessionFreshness directly with no provider-owned guard.
Under the default reset config a persistent-target cron job on a CLI runtime
(claude-cli, codex, gemini-cli) therefore rotated its session after the daily
boundary, minting a new sessionId and dropping the cliSessionBindings, so the
agent silently lost its underlying CLI conversation every morning and the
transcript was split. Because resolveCronSession also backs the heartbeat
runner, that surface was affected too.
Route both resolvers through the same
resetPolicy.configured !== true && hasProviderOwnedSession(entry) skip the
gateway and inbound paths already use. Explicit session.reset and configured
resets still rotate these sessions, and the command path still rotates when the
terminal main transcript is newer than the registry.
Subagent runs do not have a live stream consumer; their result is delivered from
the terminal message path after the child run finishes. The intermediate
message_update stream work only feeds live preview output.
Thread suppressLiveStreamOutput from the subagent lane into the embedded runner
subscription and return from handleMessageUpdate after accumulating the raw
chunk. This keeps final delivery unchanged while skipping per-chunk visible text
and reasoning stream parsing for subagents, which reduces event-loop pressure
when multiple child agents stream long answers in parallel.
Interactive and Control UI runs keep the existing live preview path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit e0382c2c58c3eabdf64638777ec82cb1e68514e9)