* feat(logbook): automatic work journal plugin with a plugin-contributed Control UI tab
Squash of PR #99930 work for rebase onto the Control UI route refactor:
- extensions/logbook: Dayflow-style capture -> observations -> timeline cards
pipeline with SQLite store, node capture commands, standup/ask, retention
- plugin SDK/gateway seam: surface "tab" Control UI descriptors projected
into hello-ok controlUiTabs (scope-filtered, deterministic order)
- Control UI: dynamic plugin tabs with bundled Logbook view
- docs, tests, labeler wiring
* feat(ui): port plugin tabs and Logbook to the route-owned Control UI architecture
- shared /plugin route carries the tab id in the query (?id=<tab>), matching
the router's exact-path contract
- openclaw-plugin-page renders bundled views (Logbook), sandboxed plugin
frames (descriptor path), or the unavailable card
- sidebar renders hello controlUiTabs after each group's static routes
- Logbook view/controller live under ui/src/pages/plugin/
* fix(ui): namespace plugin tabs by pluginId to prevent cross-plugin tab id collisions
* fix(logbook): prefer app capture nodes and rotate off failing nodes
* fix(plugins): reject protocol-relative Control UI tab paths
* fix(logbook): harden automatic journal
* docs(changelog): remove maintainer self-credit
* chore(ui): refresh locale metadata after rebase
* fix(logbook): preserve analysis window boundaries
* fix(logbook): align status privacy and timezone
* fix(ui): stop hidden plugin tab polling
* feat(control-ui): session grouping with drag & drop and channel categorization
* fix(control-ui): restrict session drag&drop to private session-key payloads
* chore(i18n): translate session grouping strings across control ui locales
* chore(protocol): regenerate swift gateway models for session category
* chore(protocol): regenerate swift gateway models for session category
* fix(tui): show last tool-error summary on aborted runs
When a run ends while looping on tool-call validation errors the TUI showed
only "run aborted". Carry the last tool failure on the terminal lifecycle
metadata and render a sanitized one-line summary on the abort line, e.g.
"run aborted: edit tool validation failed: edits: must have required properties edits".
Argument dumps are stripped; with no summary it stays "run aborted".
Refs #90982
* fix(tui): secure abort validation diagnostics
* docs(changelog): move TUI fix to unreleased
* docs(changelog): aggregate TUI fixes
* fix(tui): scope abort validation diagnostics
* docs(changelog): restore unreleased spacing
* fix(tui): scope abort validation diagnostics
* fix(tui): scope abort validation diagnostics
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The plugin-approvals schema gained approvalReviewerDeviceIds (#100251)
without refreshing the generated Swift model, so the bundled-protocol
drift gate fails on every PR. Mechanical pnpm protocol:gen:swift output.
* feat(crestodian): AI-first conversational onboarding with typed-op guardrails
Interactive `openclaw onboard` (and bare `openclaw` on a fresh install) now
opens the Crestodian conversation: detection-backed first-run proposal
(Claude Code/Codex logins, API keys), persona AI turns for every free-form
message (configless local-runtime fallback, 60s deadline, deterministic
degradation), approval-gated typed operations, chat-hosted channel setup
(`connect <channel>`), config get/schema read ops with secret redaction, and
a post-write validation hook that feeds schema errors back for a self-fix
turn. Adds the additive gateway `crestodian.chat` method so app clients run
the same conversation. Classic wizard stays behind --classic/explicit flags;
non-interactive automation unchanged; `--modern` becomes a deprecated alias
for `openclaw crestodian`.
* feat(macos): Crestodian chat onboarding and importance-ordered permissions
Replace the gateway step-wizard page with a Crestodian chat over the new
crestodian.chat method (works before any model auth exists), sort the
permissions page by importance with no scrolling, drop the redundant manual
refresh, and bump the onboarding version.
* feat(crestodian): run the custodian on the real agent loop with a ring-zero tool
Crestodian conversations now execute through the same embedded agent runner
as regular agents: a persistent agent session with a single construction-gated
`crestodian` tool wrapping the typed operations (read actions free; mutations
require approved=true asserted from explicit user consent, audited, with
post-write config validation fed back into the loop). The engine prefers the
loop (configured models or the Codex app-server fallback) and degrades to the
single-turn planner, then to deterministic commands. Setup approval seeds the
crestodian exec approval so local model harnesses can run; the configless
Codex backend config now enables exec and direct tool loading (it was
dead-on-arrival behind tools.exec.mode=deny and the tool-search index).
* test(crestodian): type the engine mock signatures for the core test lane
* fix(crestodian): map the advertised create_agent tool action
* fix(crestodian): host-verified approval arming and a macOS setup completion gate
Review findings: the model-supplied approved flag alone could authorize
ring-zero mutations (prompt injection / model error), and removing the macOS
wizard gate let users Next past the Crestodian page with nothing configured.
Mutating tool actions now also require host-verified consent (the engine arms
approval only when the user's actual message is an explicit yes), and local
macOS onboarding blocks advancing until setup authored the config, using the
same signal the old step wizard checked.
* fix(crestodian): bind approval to the exact proposed operation and gate dot navigation
A generic yes no longer authorizes arbitrary mutations: denied mutating tool
calls register a canonical operation fingerprint (host-owned, per session),
and an armed turn executes only the identical call, once. The denial message
is arming-aware so the approved turn self-heals in one roundtrip, and the
agent protocol pre-registers proposals. macOS onboarding page dots now honor
the same setup-completion gate as the Next button.
* fix(crestodian): redact sensitive wizard answers, skip logged-out CLIs, gate programmatic advance
Sensitive channel-wizard answers (tokens, passwords) are redacted from the
AI-visible conversation history; setup and the onboarding welcome never pick
or advertise a definitively logged-out CLI as the model; and macOS
handleNext() honors the page gates for programmatic callers (chat handoff)
just like the Next button.
* fix(crestodian): align the onboarding welcome's configured predicate with the app gate
A valid config carrying only a default model (partial/hand-written) now still
gets the first-run proposal instead of the ready guide, so the macOS setup
gate can always be satisfied from the conversation.
* fix(crestodian): armed turns can never mint their own executable proposal
An approval-mismatched call inside an armed turn no longer re-registers and
invites a retry (which let the model swap the approved operation for another
in the same turn); it voids the approval entirely and requires a fresh yes.
Proposals register only in unarmed turns, which the agent protocol already
does when proposing.
* fix(onboard): route any explicit setup flag to the classic wizard
* fix(ci): satisfy new lint rules, tool-display guard, and generated artifacts for crestodian
* chore(i18n): refresh native inventory after permissions copy wrap
* fix(crestodian): harden conversational onboarding
* docs(crestodian): document conversational onboarding
* test(crestodian): type embedded runner mock
* fix(crestodian): close onboarding security gaps
* chore: retrigger ci
Allow busy TUI sessions to forward prompts into the configured queue while
keeping queued-turn admission, cancellation, restart, and transcript ownership
consistent across the TUI, Gateway, and followup queue.
Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
* 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.
* feat(gateway): add device.pair.setupCode RPC for connect QR
The OpenClaw mobile/companion app scans a pairing setup code to connect to the gateway, but that code + QR could only be produced by the openclaw qr CLI (ASCII to stdout). Non-terminal clients driving onboarding had no way to display the connect QR.
Add a device.pair.setupCode gateway method that reuses resolvePairingSetupFromConfig + encodePairingSetupCode + renderQrPngDataUrl to return { setupCode, qrDataUrl?, gatewayUrl, auth, urlSource }. The embedded setup code mints a short-lived bootstrap token that hands off broad operator scopes (read/write/approvals/talk.secrets), so the method requires operator.admin (matching the wizard methods a companion already uses) and is not advertised. auth is a label only; the gateway credential is never returned, and an oversized QR is omitted so the response always satisfies the result schema.
Refs #94661.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): pair mobile devices from Control UI
* docs: refresh generated docs map
* docs: clarify mobile QR auto-connect
* docs: clarify mobile QR auto-connect
* docs: clarify mobile QR auto-connect
---------
Co-authored-by: Barbara Kudiess <76582160+bkudiess@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): resolve "current" session alias locally without gateway round-trip
The system prompt tells agents to use sessionKey="current" to refer to
their own session. Previously, resolveSessionReference sent the literal
string "current" to the gateway sessions.resolve action, which rejected
it with INVALID_REQUEST and logged a noisy error line on every tool call.
The wrapper fell back to requesterInternalKey and succeeded, so the tool
worked — but the gateway error was spurious.
Add "current" to the well-known client alias check in
resolveCurrentSessionClientAlias so it is resolved locally to the
requester's session key, matching how TUI/CLI/WebChat client labels are
handled. This eliminates the unnecessary gateway round-trip and the
error log line.
Fixes#78424
* test: update session_status tests for local current-key resolution
* test: update session_status tests for local current-key resolution
* Revert "test: update session_status tests for local current-key resolution"
This reverts commit d9f6c8b5248921c99f43dc222667ffa429b34401.
* Revert "test: update session_status tests for local current-key resolution"
This reverts commit 40bf77d06711833c1beaeedf562b60a765a559d6.
* Revert "fix(agents): resolve "current" session alias locally without gateway round-trip"
This reverts commit d92bc9b91e0840ea5823cd44223c139e434c5ec4.
* fix(agents): preserve literal current session resolution
---------
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
Capture the originating sessionKey and agentId for cron wake tool calls so non-main session and multi-agent wakes return to the conversation lane that requested them.
Carry stored delivery context through queued wake events so topic/thread replies route correctly, while preserving the default no-origin wake behavior and explicit target:none opt-out.
Refs #46886.
Refs #64556.
Thanks @anagnorisis2peripeteia.
Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>
Exec approval followups were dispatched by sessionKey only. When /new or
/reset rotates the sessionId under that key while an approval is pending,
the resolved followup landed in the new session, surfacing stale approval
output (or 'Exec denied' / continuation text) in a fresh conversation.
Capture the session UUID active when the approval is requested and drop the
followup once the key has been rebound to a different sessionId:
- agent-run followups: carry the expected id on the agent request and drop it
at the gateway as an early preflight, before the handler touches the rebound
session (session-store write, chat/agent run + active-run registration,
dedupe, accepted ack) — not just before model dispatch. Covers elevated and
non-elevated.
- denied / direct fallback followups: resolve the key's current sessionId from
the session store and drop before the channel send.
Fixes#59349.
* fix(infra/agents): session-routing guard for coalesced gateway restart continuations (#86742)
When two sessions issue gateway.restart with continuationMessage close
together, the scheduler Path B updatePendingRestartEmitHooks
unconditionally overwrote the existing pending hooks, silently dropping
the first sessions continuation and potentially routing the second
sessions continuation back to the first session (CWE-200 finding
flagged by aisle-research-bot on prior attempt #74443).
Add a session-routing guard: scheduleGatewaySigusr1Restart now accepts
an optional sessionKey and tracks the pending restarts owning session.
Coalesced callers from a different session are rejected at the hook-
update step and the new ScheduledRestart.emitHooksQueued: false field
surfaces the drop to the caller. The gateway tool propagates this as
continuationQueued: false in the tool response, matching #83370 narrow
report-only surface.
Same-session debounce/replace and legacy hookless callers behave the
same as before.
Refs #86742
* fix(infra): preserve queued restart continuation on forced bypass
* fix(infra): make forced restart hook preservation explicit
* fix(infra): guard restart continuation ownership before reschedule
* fix(infra): report hookless coalesced restarts accurately
* fix(infra): trust runtime session for restart sentinel routing
* fix(infra): preserve earlier restart reschedule semantics
* fix(agents): trust runtime session for update continuations
* fix(infra): preserve hookless forced restart continuations
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Add a coalesced chat.metadata Gateway method so the Control UI can fetch model and command metadata without blocking a clean first message path. Reuses existing models/commands builders, keeps compatibility fallback for older gateways, updates protocol artifacts, and adds focused gateway/UI/e2e coverage.
Add a bounded `chat.message.get` gateway method so Control UI can fetch one display-normalized transcript message by id when an assistant history preview was truncated. Keep `chat.history` lightweight, reject oversized/hidden/missing rows with explicit unavailable reasons, and wire the WebChat side reader to request full content only for visible truncated assistant messages.
Also refresh the generated Swift gateway protocol models and document the new assistant-message side-reader behavior.
Closes#84651.
Related #53242.
Co-authored-by: NianJiuZst <3235467914@qq.com>
Fixes#87132.
Default Usage now requests all configured agents with `agentScope: "all"`, while selecting a specific agent sends `agentId` consistently to both session usage and cost usage calls. The gateway now supports explicit all-agent session usage, aggregates all-agent cost summaries across configured agents, and keeps scoped cache entries separate. Legacy gateway fallbacks remain for older `agentId` / `agentScope` support, with protocol docs/schema and Swift generated models updated.
Verification:
- `node scripts/run-vitest.mjs ui/src/ui/controllers/usage.node.test.ts ui/src/ui/app-render-usage-tab.test.ts ui/src/ui/views/usage.test.ts --reporter=dot`
- `node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway-methods.config.ts src/gateway/server-methods/usage.test.ts src/gateway/server-methods/usage.cost-usage-cache.test.ts src/gateway/server-methods/usage.sessions-usage.test.ts --reporter=dot`
- `pnpm check:test-types`
- `pnpm protocol:check`
- targeted `node scripts/run-oxlint.mjs ...`
- `git diff --check`
- autoreview clean after Swift compatibility fix
- PR CI green at head `d67156a3c552c4f9c8b6edf8516b6242bf5cdd26`
Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
Server-side cron job list filtering now applies schedule-kind and last-run-status filters before pagination, and the UI only sends table filters for the cron table view.
Fixes#9455.
Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
Summary:
- Replace the legacy iOS shell with Pro Command, Chat, Agents, and Settings tabs.
- Wire iOS chat/session/settings/diagnostics and realtime Talk flows through gateway-backed APIs.
- Add gateway/session and shared chat coverage for the new iOS flow.
Verification:
- git diff --check
- node scripts/run-vitest.mjs src/gateway/server.sessions.create.test.ts src/gateway/talk-realtime-relay.test.ts
- swift test --filter ChatViewModelTests (apps/shared/OpenClawKit)
- xcodebuild build for Nimrod's iPhone succeeded; install succeeded; launch was blocked because the phone was locked
Known follow-up:
- Preserve traceLevel in sessions.create parent runtime inheritance and keep the changelog credit in the follow-up patch.
* fix(session-lock): enforce maxHoldMs in shouldReclaim during lock acquisition
- Adds optional maxHoldMs parameter to inspectLockPayload
- Inspect now marks locks as stale when held longer than maxHoldMs
- Passes maxHoldMs through inspectLockPayloadForSession
- acquireSessionWriteLock's shouldReclaim callback now passes maxHoldMs
This ensures that when a live process holds a lock for longer than
maxHoldMs (default 5min), other processes can reclaim it during
acquisition — matching the watchdog's existing enforcement.
Previously shouldReclaim only used staleMs (30min default), meaning
a lock held for 10+ minutes by a live PID would never be reclaimable,
causing 60s timeout failures and gateway freezes.
Closes#85762
* fix(session-lock): add dead-PID fast-path before retry loop
Adds a fast-path check at the top of acquireSessionWriteLock:
if the lock file's owner PID is dead, remove it immediately
before entering the retry loop. This saves up to timeoutMs (60s)
of futile waiting when the previous lock holder has died.
The shouldReclaim callback already handles this case, but only
iteratively through the retry loop. The fast-path eliminates
that unnecessary delay.
* fix(session-lock): enforce max hold during acquisition
* fix(session-lock): revalidate max hold safely
* fix(session-lock): honor holder max-hold policy
* fix(session-lock): keep cleanup from reclaiming live holders
* fix(session-lock): remove stale locks only when unchanged
* fix(session-lock): skip self-held max-hold reclaim
* fix(ci): refresh gateway protocol checks
---------
Co-authored-by: njuboy11 <njuboy11@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>