* feat(secrets): agent-requested credentials the model never sees
The new main-session secrets tool lets the agent request a credential by
name: the human enters the value in a masked question card (Control UI,
/ask/<id> deep link, iOS/macOS/Android), and the gateway diverts the
answer straight into the shared secret store at question.resolve. The
record, broadcast, waitAnswer, tool result, transcript, and model context
only ever carry a synthetic stored marker.
- protocol: additive secretStore binding, secretStoreExisting replacement
metadata, and resolve-time secretStoreAllowedHosts (since 2026.8)
- gateway: store-bound question validation, admin-gated minting (blocks
questions-scope self-answer escalation past secrets.store.set), shared
redaction-first store write service reused by secrets.store.set
- tool: secrets request/list/delete; write-only by design, delete carries
verified agent runtime identity; channel delivery is link-only so chat
text is never captured as a secret
- Control UI: masked composer card with requester identity, store banner,
editable allowed hosts, replacement warning, retry-on-validation-error,
a standalone /ask/<id> page, and a named startup-JS baseline bump
- mobile: SecureField / password transformation for isSecret questions,
no answer echo in terminal summaries; new native string registered in
the locale-refresh inventory (generated artifacts stay workflow-owned)
- regression: claimed harness secret input stays out of session transcripts
Live-proven on an isolated dev gateway: real model turn, masked entry via
Playwright, value present only in secret_store_entries, absent from every
transcript, log, and the DOM.
* chore(protocol): regenerate protocol models and tool display
* fix(cli): read image string options through a typed helper
PR #129463 added four commander option narrowings in image.ts without
SAFETY coverage, leaving the assertion-safety ratchet red (21 > 17) for
every branch on current main. Replace the casts with a typeof-checked
read so the assertions are removed rather than annotated; each value is
still validated by its normalizer. SAFETY comments cannot work in this
file: the ratchet's raw scanner never rescans template tokens, so
comments after the first substitution template are unreadable to it.
* chore(protocol): refresh Swift models against current main
* chore(i18n): re-baseline the native inventory on current main
* docs(secrets): state the default-on tool policy and how to disable it
* fix(secrets): tell the model what the store actually does
The shipped tool description named the three actions and nothing else,
and no parameter carried a description. The model could not tell that
request blocks a human, that reason is shown to that human, what secret
and env select, or - the silent-failure case - that a secret stored with
no allowedHosts can never be substituted, so a successful request could
produce a permanently unusable credential. Move the description to the
presets module beside ask_user and document every parameter.
* refactor(agents): share one blocking-question lifecycle between tools
ask_user and secrets each carried their own registration, wait, and
cancel logic, and they had diverged: ask_user recovers an answer that
lands between its wait timeout and the cancel, while secrets discarded
it and reported no_answer even though the Gateway had already stored the
credential. One shared canceller and answer reader fixes that race for
both, folds the two divergent gateway-call types into one, and drops two
type assertions in favour of the canonical record guard (ask_user's
assertion baseline shrinks 11 -> 8).
Net +49 production lines: the shared module costs more than the
duplication it removes, and buys the correctness fix plus a single owner
for question lifecycle.
* fix(ui): keep the allowed-hosts field readable as an input
Main's composer restructure moved the free-text input styling into the
option-row context, so the store-request hosts field - which sits outside
a row - lost its border and read as static text. It is the one field the
operator is meant to review and edit before releasing a credential, so
give it its own border and focus ring.
* fix(secrets): close two credential-boundary holes in agent requests
Requests are now protected-secret only. list renders env values, so an
agent could request kind=env, watch a human type it into a masked box
under a no-visibility promise, then read it straight back; the tool text
even claimed values are never returned. Environment values stay operator
-set in Settings or the CLI, where they are agent-readable by design.
Store-bound questions are also bound to the run that requested them. The
resolve path authorized only the answering client, so a terminated or
replaced agent run could still have a credential written on its behalf -
the recorded runId was provenance, not closure-bound authority. Minting
now requires a runId and resolution revalidates that exact live run
immediately before the store write, with no await in between, failing
closed as QUESTION_REQUESTER_INACTIVE.
Both reported by ClawSweeper as P1 credential-boundary findings.
* feat(ios): adopt gateway user accent in chat
The iOS chat surface previously always used the hardcoded brand accent.
Read ui.prefs.accent ?? ui.seamColor (the Control UI user-accent
contract) from the existing config.get branding refresh and feed it to
the shared chat kit's userAccent seam, matching the macOS chat window.
Invalid values fall through; accent resets on gateway switch.
* fix(ios): contrast-aware accent ink and live config.changed refresh
Address ClawSweeper review findings: the shared chat kit now derives
user-text/send-glyph ink from the accent via the Control UI WCAG rule
(relative luminance > 0.179 -> black), fixing unreadable light accents
on iOS and macOS alike; the iOS server-event switch routes
config.changed through the guarded branding refresh so an accent
change reaches a connected app without reconnect.
* fix(apple): gate gateway RPC polling on the hello method catalog
Released 2026.7.x gateways authorize before method dispatch and reject
unknown methods with INVALID_REQUEST / "missing scope: operator.admin",
which never names the method, so the shared Swift kit's error-text
matchers could not detect an unsupported gateway: queued sends wedged in
a delay-free sessions.branches.list reconcile loop, question.list burned
its retry budget per health event, and progressCard.get fired a rejected
fetch per event.
Port the Android fix pattern (#126540): generalize the progressCard-only
transport seam into tri-state gatewayAdvertisesMethod(_:), make hello
catalog parsing distinguish absent (nil) from empty, route branch
listing through a catalog-checking dispatch point with a typed
BranchListingUnadvertisedError, tighten error-text matching to the
modern "unknown method:" shape, and skip question.list/progressCard.get
when unadvertised. Regression tests encode the exact 2026.7.1-2 wire
shape and fail pre-fix (A/B verified).
* fix(apple): keep explicit unsupported branch-listing replies releasing sends
ClawSweeper P1: the tightened matcher dropped the shipped acceptance of
explicit unsupported/unimplemented GatewayResponseError replies that
name sessions.branches.list, which would wedge queued sends on a
pre-catalog gateway emitting that shape. Collapse the matcher onto the
bridged localizedDescription (errorDescription always prefixes the
method name), preserving both legacy qualifier shapes while still
rejecting bare missing-scope denials and the old false-positive-prone
INVALID_REQUEST arm; add the releasing-send regression test.
* fix(gateway): make activeRunIds presence mean a complete exact run set
Session rows no longer emit activeRunIds: [] while hasActiveRun is
true. Presence now means the complete exact set of direct run ids;
omission means identities are unavailable (projected/embedded owners);
[] only ever represents proven idle. Consumers stop guessing:
soleActiveSessionRunId() replaces the arbitrary [0] fallbacks in the
observer digest, transcript cache key, activity inspector, and
stale-terminal reconciliation, each falling back to its owner fact.
Follows the maintainer direction from #125983: the field stays as
Gateway-owned exact facts; producer-side liveness/observer projections
are a named follow-up.
* fix(gateway): clear unavailable active run ids in events
* fix(gateway): preserve idle active run sets
* fix(clients): close active run id cache gaps
* test(android): isolate history run snapshot
* fix: show sessions waiting for concurrency slots
* test: align queued session integration fixtures
* test: distinguish queued and reactivated followups
* fix: preserve queued state in workboard and android
* fix: project queued status through chat history
* test(ui): keep queued sidebar case under line cap
The stream:"plan" fallback on both native platforms is deliberate compatibility
debt with a fixed expiry, not a permanent contract. Record the sunset date, point
each platform at its twin and at the tracking issue, and correct the Android note
to v2026.8.x (the newest released tag also lacks progressCard.get).
Refs #125639
* fix(apps): render legacy plan events when the Gateway lacks the progress-card store
progressCard.get ships in no released Gateway tag, so iOS/macOS on any current
release silently showed no status card after the durable-card migration. Restore
legacy stream:"plan" rendering, synthesized into the unified progress card and
gated on a tri-state capability probe so a dual-emitting Gateway never fights the
durable store. Mirrors the Android fix in #125444.
* fix(apps): invalidate progress-card capability on gateway route replacement
A replacement route may be a different Gateway, so a cached known-absent store
must not authorize the legacy plan fallback against a new Gateway that dual-emits
both sources. Clear and re-probe on .routeChanged.
* feat(apps): migrate iOS/macOS plan surface to the durable progress card
Replace the legacy stream:"plan" agent-event pipeline (runId-scoped state,
run-gated pill) with the sessionKey-scoped progress-card store: the shared
chat surface now renders progressCard.get snapshots, refetches on
progressCard.changed pokes with revision dedupe, clears on null-revision
pokes, and persists the card after the run completes. The card renders
markdown through the shared markdown view plus typed steps. Legacy Apple-side
plan handling (agent-event case, run-snapshot plan reconciliation,
OpenClawChatPlanStep parsing) is deleted; gateway emission stays for Android.
Removes the ios progressCard.changed coverage allowlist entry so the check
enforces the handler.
* chore(i18n): refresh native inventory for the progress-card rename
* fix(apps): keep the last progress card when a refresh fails
A transient progressCard.get failure no longer clears an already-rendered
durable card; only a successful null fetch or a null-revision poke clears it.
* fix(ui): fork active sessions from stable history
* fix(protocol): align active fork types
* test(ui): match main-session fork routing
* fix(sessions): unify stable fork admission
* fix(native): fork active sessions from stable history
* fix(android): fork active sessions from stable history
* style(android): simplify active fork result handling
* fix(native): preserve legacy fork transport API
* test(native): complete legacy fork transport stub
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(apps): adopt the system-notice family in Apple chat (OpenClawChatUI)
Decode internal_system provenance and __openclaw history markers, classify
once into message/notice/divider rows (web-parity kinds), render minimal
hairline notice/divider SwiftUI rows, preserve metadata through history
reconciliation, live transport, and the transcript cache, and align the
transcript exporter with visible classification instead of leaking raw
[System] prompts.
* fix(apps): satisfy OpenClawKit periphery gate
Remove the dead visibleMessages projection (rows path replaced its
consumers) and annotate the provenance test-fixture initializer with the
repo-standard periphery:ignore rationale.
* fix(apps): refresh native i18n inventory after periphery cleanup
* fix(chat): close disclosures in coalesced HTML
Keep model-authored details closers visible to the shared disclosure contract when CommonMark absorbs them into type-6 HTML blocks across the Control UI and native apps.
* refactor(apps): isolate disclosure unfolding
Keep the native disclosure segmenter below its lint complexity and type-size limits, and refresh the source-owned native i18n inventory after the move.
* fix(apps): align disclosure helper visibility
Keep the extracted same-file helper compatible with Swift access control while leaving its implementation types hidden outside the source file.
* fix(apps): align source replacement visibility
Keep the source replacement helper at the same file-private boundary as the extracted disclosure unfolding types.
* feat(chat): show live run telemetry across clients
* fix(chat): satisfy native validation gates
* fix(chat): settle advertised terminal runs
* fix(android): clear stale run usage on gaps
* chore(i18n): align native source inventory
* fix(chat): reconcile telemetry with current projections
* refactor(chat): keep run state in owner extensions
* chore(i18n): refresh native source inventory
* test(ui): expect persistent working telemetry through streaming
Add agent ownership to observer digests, scope global observer delivery and reconciliation, and centralize application session selection across navigation and reconnects.
Co-authored-by: xbrxr03 <abrarhabib03@gmail.com>
* fix(ui): keep stable chat rows in insertion order and only sort live tool/stream items by timestamp
* fix(ui): keep live rows within current turn
* fix(ui): keep current work above queued turns
* fix(ui): keep streamed replies above queued turns
* fix(ui): preserve reconnecting run order
* fix(ui): preserve causal terminal ordering
* fix(ui): bound replay rows to owning turns
* fix(ui): keep question summaries in owning turns
* fix(ui): scope question run ownership to session
* fix(ui): restore reconnecting chat run identity
* fix(ui): remove unused chatItemTimestamp import in chat-thread-build.ts
* fix(ui): correlate question summaries with agent runs
Co-authored-by: Peter Lee <li.xialong@xydigit.com>
* chore(i18n): refresh native source baseline
Co-authored-by: Peter Lee <li.xialong@xydigit.com>
* fix(ui): remove unused chatItemTimestamp export and split tool-stream test file
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>