Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
52ea6f6
feat(grok): add fail-closed stream compatibility
CompleteDotTech Jul 26, 2026
7fb62a3
ci(grok): require signed registry trust material
CompleteDotTech Jul 26, 2026
9b63024
fix(grok): resolve compatibility module in Node tests
CompleteDotTech Jul 26, 2026
14d081e
fix(grok): anchor cached schema revisions
CompleteDotTech Jul 26, 2026
62df71f
test(grok): cover compatibility route wiring
CompleteDotTech Jul 26, 2026
57b36cc
fix(grok): harden compatibility routing
CompleteDotTech Jul 26, 2026
d18c0d9
fix(grok): quarantine malformed compatibility frames
CompleteDotTech Jul 26, 2026
1e1136b
fix(grok): preserve fail-closed fallback integrity
CompleteDotTech Jul 26, 2026
36acd29
fix(grok): scrub proxy credentials from probes
CompleteDotTech Jul 26, 2026
6bdd2fe
fix(grok): preserve trusted registry cache invariants
CompleteDotTech Jul 26, 2026
48afb1d
fix(grok): redact fallback protocol failures
CompleteDotTech Jul 26, 2026
1d5b88b
fix(grok): bind tool schemas to verified revisions
CompleteDotTech Jul 26, 2026
f3f28e2
test(grok): cover structured fallback route wiring
CompleteDotTech Jul 26, 2026
64bf2d8
fix(grok): preserve safe plain fallback turns
CompleteDotTech Jul 26, 2026
e45d311
test(grok): update shared routing contracts
CompleteDotTech Jul 26, 2026
2b962aa
test(grok): persist shared compatibility diagnostics
CompleteDotTech Jul 26, 2026
7c33223
fix(grok): fail closed after schema expiry
CompleteDotTech Jul 26, 2026
244d290
fix(grok): bound registry refresh trust
CompleteDotTech Jul 26, 2026
463f894
fix(grok): preserve reordered combined tool completion
CompleteDotTech Jul 26, 2026
605596c
test(grok): cover preflight-backed capability probing
CompleteDotTech Jul 26, 2026
d14f3cc
docs: plan direct OpenClaw gateway dispatch
CompleteDotTech Jul 26, 2026
1cec85d
docs: clarify OpenClaw gateway dispatch plan
CompleteDotTech Jul 26, 2026
f206b5a
docs: fence cancelled OpenClaw gateway runs
CompleteDotTech Jul 26, 2026
db065fb
feat(chat): dispatch OpenClaw turns through Gateway
CompleteDotTech Jul 26, 2026
1f5122c
fix(chat): validate OpenClaw Gateway hello
CompleteDotTech Jul 26, 2026
ce30719
test(chat): use the published Gateway hello shape
CompleteDotTech Jul 26, 2026
5d70ebc
test(chat): count the Gateway dispatch path in send-route buffer pins
BunsDev Jul 26, 2026
6558908
test(chat): update stop-registry contract for Gateway dispatch
CompleteDotTech Jul 26, 2026
a1c541b
test(chat): cover Gateway first-turn stub persistence
CompleteDotTech Jul 26, 2026
f37aa12
test(chat): cover Gateway work-branch persistence
CompleteDotTech Jul 26, 2026
85abfc6
fix(openclaw): require stable Gateway idempotency
CompleteDotTech Jul 26, 2026
49936a7
fix(openclaw): isolate Gateway credentials from CLI fallback
CompleteDotTech Jul 26, 2026
01ef03f
fix(openclaw): fence Gateway events during reconnect
CompleteDotTech Jul 26, 2026
997f664
docs(openclaw): record published tool schema boundary
CompleteDotTech Jul 26, 2026
1a11aa7
fix(openclaw): preserve Gateway stream generations
CompleteDotTech Jul 26, 2026
df0a597
fix(openclaw): preserve transient Gateway reconnects
CompleteDotTech Jul 26, 2026
5b0733f
docs(openclaw): distinguish chat and tool support
CompleteDotTech Jul 26, 2026
51e2e7a
fix(openclaw): require exact chat capability contract
CompleteDotTech Jul 26, 2026
7ca04b9
fix(openclaw): gate Gateway dispatch on paired credentials
CompleteDotTech Jul 26, 2026
41750ae
docs(openclaw): record paired-device activation blocker
CompleteDotTech Jul 26, 2026
2796e9c
fix(openclaw): isolate all Gateway settings from CLI fallback
CompleteDotTech Jul 26, 2026
0869089
fix(openclaw): fail closed on opaque Gateway chat frames
CompleteDotTech Jul 26, 2026
a7df1c4
fix(openclaw): guard dispatcher without paired credentials
CompleteDotTech Jul 26, 2026
e3bc290
fix(openclaw): require chat routing capability
CompleteDotTech Jul 26, 2026
1ba9d77
fix(openclaw): fence frames after transport close
CompleteDotTech Jul 26, 2026
a0e6fa8
fix(openclaw): drop frames from closed transports
CompleteDotTech Jul 26, 2026
f55039c
fix(openclaw): retain fallback on Gateway startup failures
CompleteDotTech Jul 26, 2026
38e9bf9
fix(copilot): preflight resolved launch artifacts
CompleteDotTech Jul 26, 2026
4956bd9
fix(chat): fail closed on invalid Copilot JSONL plans
CompleteDotTech Jul 26, 2026
5bdb32b
fix(ui): show runtime preflight diagnostics
CompleteDotTech Jul 26, 2026
dab3284
test(copilot): cover unsafe Windows shim resolution
CompleteDotTech Jul 26, 2026
bc8293f
fix(copilot): preflight scoped direct launch plan
CompleteDotTech Jul 26, 2026
5db6e83
fix: show unavailable familiar runtimes
CompleteDotTech Jul 26, 2026
7bed3f5
fix: preserve Grok launch diagnostics after preflight
CompleteDotTech Jul 26, 2026
272637b
test: wire familiar runtime capability coverage
CompleteDotTech Jul 26, 2026
a29bc56
test: skip POSIX launcher fixtures on Windows
CompleteDotTech Jul 26, 2026
590c7d6
test: make Coven path expectations platform-aware
CompleteDotTech Jul 26, 2026
3db3df6
test: guard Windows migration symlink recovery
CompleteDotTech Jul 26, 2026
6c0d47f
feat(settings): refresh daemon controls and iOS chat launch (#3929)
BunsDev Jul 26, 2026
252179e
fix(omnigent): scope vault tokens to server URL (#3918)
BunsDev Jul 26, 2026
a7c2029
Merge pull request #3903 from OpenCoven/agent/issue-3845-grok-tool-ac…
CompleteDotTech Jul 26, 2026
30286a4
Merge pull request #3905 from OpenCoven/agent/openclaw-gateway-tool-a…
CompleteDotTech Jul 26, 2026
6ac6f0c
fix(security): bind vaulted hub tokens to origin (#3917)
BunsDev Jul 26, 2026
8c76d26
Merge pull request #3914 from OpenCoven/fix/copilot-jsonl-preflight-3…
CompleteDotTech Jul 26, 2026
1d4529d
Merge main into agent/issue-3861-grok-launch
CompleteDotTech Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,26 @@ jobs:
echo "NEXT_PUBLIC_COVEN_OPENCODE_SCHEMA_REGISTRY_CHECKPOINT=$NEXT_PUBLIC_COVEN_OPENCODE_SCHEMA_REGISTRY_CHECKPOINT"
} >> "$GITHUB_ENV"

- name: Require signed Grok compatibility registry
shell: bash
env:
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL: ${{ secrets.GROK_SCHEMA_REGISTRY_URL }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY: ${{ secrets.GROK_SCHEMA_REGISTRY_PUBLIC_KEY }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS: ${{ secrets.GROK_SCHEMA_REGISTRY_PUBLIC_KEYS }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT: ${{ secrets.GROK_SCHEMA_REGISTRY_CHECKPOINT }}
run: |
node scripts/check-grok-registry-release.mjs
{
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL=$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY<<COVEN_GROK_KEY_EOF"
printf '%s\n' "$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY"
echo "COVEN_GROK_KEY_EOF"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS<<COVEN_GROK_KEYS_EOF"
printf '%s\n' "$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS"
echo "COVEN_GROK_KEYS_EOF"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT=$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT"
} >> "$GITHUB_ENV"

# Decode the App Store Connect API key onto the runner for the custom
# macOS release script. The path is exported into the env for later
# steps. Runs only on the macOS leg.
Expand Down
7 changes: 7 additions & 0 deletions apps/ios/CovenCave/CovenCave/State/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ final class AppModel {
var familiarOrder: [String] = []

var threads: [ChatThread] = []
/// Default Chats destination: the newest active conversation. Pinning only
/// affects list order and never makes an older thread the launch default.
var mostRecentThread: ChatThread? {
threads
.filter { !$0.archived }
.max { $0.updatedAt < $1.updatedAt }
}
/// Process-lifetime launch intent. It survives destination remounts until a
/// matching hydrated thread can be opened, then is consumed exactly once.
var launchThreadId: String?
Expand Down
15 changes: 15 additions & 0 deletions apps/ios/CovenCave/CovenCave/Views/ChatsHomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ struct ChatsHomeView: View {
.onAppear {
consumeLaunchThreadIntent()
consumeGlobalRequests()
selectMostRecentThreadIfNeeded()
}
.onChange(of: app.threads.map(\.id)) { _, _ in
consumeLaunchThreadIntent()
selectMostRecentThreadIfNeeded()
}
// A slash command (`/new`, `/familiar <name>`) or a task link asked to
// open a specific thread — surface it in the detail column.
Expand Down Expand Up @@ -390,6 +392,19 @@ struct ChatsHomeView: View {
open(.thread(thread))
}

/// Open Chats at the latest active conversation without stealing focus from
/// a deep link, cross-view handoff, New Chat, or an existing selection.
private func selectMostRecentThreadIfNeeded() {
guard selection == nil,
!showNewChat,
app.threadToOpen == nil,
app.launchThreadId == nil,
!app.newChatRequested,
let thread = app.mostRecentThread
else { return }
open(.thread(thread))
}

/// Consume a cross-destination thread handoff on first appearance and on
/// later updates. Clearing the one-shot intent prevents re-appearance from
/// reopening the same conversation.
Expand Down
2 changes: 1 addition & 1 deletion apps/ios/CovenCave/CovenCave/Views/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ struct ConnectingView: View {
.accessibilityHidden(true)
.padding(.bottom, 34)

Text("Opening the Cave")
Text("Entering the Cave")
.font(.title.weight(.medium))
.fontDesign(.serif)
.italic()
Expand Down
27 changes: 27 additions & 0 deletions apps/ios/CovenCave/CovenCaveTests/LaunchThreadIntentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,31 @@ final class LaunchThreadIntentTests: XCTestCase {
app.threads = [expected]
XCTAssertTrue(app.consumeLaunchThreadIntent() === expected)
}

func testMostRecentThreadUsesUpdateTimeAndSkipsArchivedThreads() {
let app = AppModel()
let olderPinned = ChatThread(id: "older-pinned", title: "Older pinned", familiarIds: [])
olderPinned.updatedAt = Date(timeIntervalSince1970: 100)
olderPinned.pinned = true

let newest = ChatThread(id: "newest", title: "Newest", familiarIds: [])
newest.updatedAt = Date(timeIntervalSince1970: 200)

let archived = ChatThread(id: "archived", title: "Archived", familiarIds: [])
archived.updatedAt = Date(timeIntervalSince1970: 300)
archived.archived = true

app.threads = [olderPinned, archived, newest]

XCTAssertTrue(app.mostRecentThread === newest)
}

func testMostRecentThreadIsNilWithoutAnActiveConversation() {
let app = AppModel()
let archived = ChatThread(id: "archived", title: "Archived", familiarIds: [])
archived.archived = true
app.threads = [archived]

XCTAssertNil(app.mostRecentThread)
}
}
15 changes: 15 additions & 0 deletions docs/grok-compatibility-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Grok Build compatibility registry

Grok Build's built-in profile is limited to the xAI-documented `text`, `thought`, `end`, and `error` `streaming-json` frames. It contains no tool-event aliases. A tool schema is enabled only when the exact locally resolved launcher advertises the value-bearing `--output-format streaming-json` option and a selected Ed25519-signed bundle explicitly names every envelope field and lifecycle event **and pins those aliases to exact locally probed Grok Build versions**. Help/version probes never receive credential-bearing environment variables and run no model request.

Release configuration is public verification material, never a signing key:

- `GROK_SCHEMA_REGISTRY_URL` — canonical credential-free HTTPS bundle URL.
- `GROK_SCHEMA_REGISTRY_PUBLIC_KEY`, or `GROK_SCHEMA_REGISTRY_PUBLIC_KEYS` — PEM Ed25519 trust anchor(s), with one to four key IDs for rotation. A bundle signed against a multi-key keyring must carry its exact `keyId`.
- `GROK_SCHEMA_REGISTRY_CHECKPOINT` — JSON `{ "sequence": number, "payloadHash": "<lowercase sha256>" }` that anchors first use and rollback resistance.

The release maps these to `NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_*`; production reads only those packaged anchors. Development may use `COVEN_GROK_SCHEMA_REGISTRY_*`, which production deliberately ignores. Registry downloads reject redirects, credentials, oversized or stalled bodies, malformed bundles, invalid signatures, checkpoint regressions, and cache-anchor rollbacks. The per-user cache is bounded, atomically replaced under a short writer lock, and keeps a bounded immutable high-water journal so a resumed stale writer cannot lower the accepted sequence. It is always reverified; an unknown or malformed selected event quarantines that schema in-process and future turns fall back to plain text. If a newer remote contract was previously accepted, its cache expires, or its anchor is missing/corrupt, Cave does not revive the older compiled parser; it waits in plain chat for a verified refresh. The compiled baseline also expires rather than parsing future output indefinitely. Do not publish a Grok tool schema until its precise stdout envelope is source-verified and captured from an approved non-production fixture. Never store a private key in this repository, app configuration, or release secrets.

## Evidence

Verified on 2026-07-26 from xAI's [Grok Build overview](https://docs.x.ai/build/overview), which documents headless `grok -p ... --output-format streaming-json`, and the upstream [headless-mode source documentation](https://github.com/xai-org/grok-build/blob/47348d13ec4508dcfe440e34c6d511bb02998fb2/crates/codegen/xai-grok-pager/docs/user-guide/14-headless-mode.md) at Grok Build revision [`47348d13ec4508dcfe440e34c6d511bb02998fb2`](https://github.com/xai-org/grok-build/tree/47348d13ec4508dcfe440e34c6d511bb02998fb2). Those sources establish the baseline text/thought/end/error transport only; they do **not** document tool lifecycle envelope names. No live Grok capture is stored or required. Future signed schemas need separately recorded source evidence for every added event and field before release owners publish them.
135 changes: 135 additions & 0 deletions docs/specs/2026-07-25-openclaw-gateway-dispatch-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# OpenClaw Gateway-dispatch implementation plan

**GitHub:** #3865 (implementation issue), #3847 (parent compatibility work),
and #3852 (the retained safe CLI/plain-chat stop point). This document records
both the shipped chat-only v4 boundary and the remaining plan for full tool
lifecycle support.

## Decision

Cave must not observe a CLI-created OpenClaw run. The CLI does not expose the
Gateway's accepted run ID before `session.tool` events may arrive, so such an
observer cannot attribute tool cards safely when sessions overlap.

When a Gateway meets the supported compatibility contract, Cave dispatches the
turn through the authenticated Gateway itself. The same Gateway connection owns
the accepted `runId`, subscribes to session events, and accepts only events
belonging to that exact run. The current CLI bridge stays the authoritative
fallback for every other runtime.

## Target contract after a tool schema is published

Full tool activity requires a published, versioned `session.tool` event name,
payload validator, and lifecycle fixtures. Once those exist, Cave must request
only the documented capabilities, validate the negotiated role/scopes and
methods, and bind tool events to the Gateway-accepted run ID. Until then, no
capability string or observed frame is a substitute for a payload contract.

The direct dispatcher supplies an idempotency key, receives the accepted run
identifier, then binds all live state to `(sessionKey, agentId, runId)`. A tool
call key is `(runId, toolCallId)`, not a session-wide call ID.

No runtime is upgraded heuristically. Older protocol versions, unavailable
packages, unpaired devices, missing `operator.write`, an absent capability, or
an unknown schema use the existing CLI/plain-chat path with a visible
diagnostic. A protocol-version mismatch is a compatibility boundary, not a
reason to guess a field shape.

## Runtime sequence

1. Resolve the local OpenClaw runtime and Gateway endpoint without passing
Gateway credentials to a fallback child process.
2. Create or load a paired device identity from OS-backed secret storage;
authenticate with the reference Gateway client and validate `hello-ok` plus
negotiated policy limits. Never persist credentials in plaintext or include
them in logs, caches, SSE, or diagnostics.
3. Establish the selected canonical-session subscription before dispatching
the turn. Add any additional subscription only when its published schema
and contract fixture are available.
4. Send `chat.send` with the Cave message, canonical session key, agent ID,
and an idempotency key derived from the Cave request ID. Record the
Gateway-accepted `runId`.
5. Project only matching, schema-validated events to Cave SSE. Maintain a
per-run high-water sequence, reject replay, and fail the owned turn on a
forward gap until a published history-reconciliation contract is available.
6. On terminal chat state, persist the response. After a published tool schema
is supported, also persist reconciled tool cards. On
cancellation, first persist a per-run `cancelled` terminal fence, then abort
the exact `runId`, close the stream, and settle only its unfinished cards.
Every event, reconciliation, and persistence path checks that fence: a
queued or late result for that run may not replace cancelled card or turn
state with success.
7. Before a `chat.send` acknowledgement, resolve an ambiguous dispatch using
its idempotency key and authoritative Gateway status/history. Start the CLI
fallback only after acceptance is disproven; a lost acknowledgement is not
permission to duplicate the turn.
8. After acceptance, use the official keepalive/liveness policy. On reconnect,
restore the validated session subscription and resume only validated frames
for the accepted run. Add history reconciliation only alongside its
published schema; if recovery fails, terminate and settle the Gateway-owned
turn, never replacing it with a CLI invocation.

## Compatibility and upgrade policy

- Depend on the official protocol/client packages rather than local copies of
WebSocket framing, signing, or schemas.
- Keep an explicit profile table keyed by protocol version and package release
range. A profile declares exact methods, events, scopes, payload validators,
limits, and migration behavior.
- Generate/capture protocol conformance fixtures from each supported package
release. Include supported, old/unsupported, future/unknown, missing-scope,
pairing-required, replay, sequence-gap, disconnect, cancellation, and
concurrent-run cases.
- Upgrade only after the schema diff and fixtures pass. Unknown wire versions
fail closed to CLI; a new Cave release adds a tested profile.

## Current release boundary (2026-07-26)

The only published protocol/client release is the `2026.7.2-beta.4` beta
package pair, negotiating wire protocol v4. It publishes `HelloOkSchema`,
`ChatEventSchema`, `chat.send`, `chat.abort`, and
`sessions.messages.subscribe`. Cave validates that exact chat-only contract in
its dispatcher, including the accepted `(sessionKey, agentId, runId)` tuple.

Cave currently keeps the live route fail-closed **before client construction**:
the reference client delegates device identity, challenge signing, and token
lifecycle to host-owned `GatewayClientHostDeps`, and Cave does not yet have the
required cross-platform OS-backed credential-store boundary. In particular,
`OPENCLAW_GATEWAY_TOKEN` and `OPENCLAW_GATEWAY_DEVICE_TOKEN` cannot activate a
write-capable direct turn; the existing CLI/plain-chat bridge remains the
fallback. This is intentional until real paired-device storage is shipped.

The release also does **not** publish a `session.tool` event name, payload
schema, or validator. Cave emits no Gateway tool card for this release and does
not request an unpublished tool-event capability. A method/event capability
string is not a substitute for a versioned payload contract.

| Package profile | Wire protocol | Runtime projection | Tool cards | Upgrade rule |
| --- | --- | --- | --- | --- |
| `2026.7.2-beta.4` | v4 only | None until Cave has OS-backed paired-device credentials; dispatcher tests validate only correlated `chat` frames | Disabled: no published schema | Add credential-store integration, then a fixture and explicit profile only when OpenClaw publishes a stable tool payload validator. |
| Any other version/profile | Not assumed | None | Disabled | Keep CLI/plain chat with a visible compatibility diagnostic. |

Before enabling tool cards, record the package release, exported validator,
schema diff, and fixtures for lifecycle, foreign-run rejection, malformed
payload, replay, gap, disconnect, and cancellation. Do not infer a tool shape
from an observed Gateway frame.

## Verification

Add a route-level Gateway fixture that performs the real authenticated
handshake, subscription, `chat.send` acknowledgement, and emitted chat
lifecycle. It must prove that matching chat frames reach SSE and persistence
and that otherwise-valid concurrent-session frames are rejected. Once a
published tool validator exists, extend it with start/update/result cards,
history reconciliation, and every fallback boundary above.

## Delivery slices

1. Add official protocol/client dependencies, capability/profile discovery,
paired-device credential storage, and protocol fixtures.
2. Implement one owned Gateway turn with chat SSE projection and a CLI fallback
selected before dispatch.
3. Implement correlated tool lifecycle, persistence, cancellation, and
reconciliation.
4. Add cross-version conformance and route-level integration tests; document
operator setup and upgrade support boundaries.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
"@iconify/react": "6.0.2",
"@lezer/highlight": "1.2.3",
"@milkdown/crepe": "7.21.2",
"@openclaw/gateway-client": "2026.7.2-beta.4",
"@openclaw/gateway-protocol": "2026.7.2-beta.4",
"@tailwindcss/browser": "4.3.1",
"@tauri-apps/api": "2.11.1",
"@tauri-apps/plugin-notification": "2.3.3",
Expand Down Expand Up @@ -101,6 +103,7 @@
"sharp": "0.34.5",
"shiki": "4.3.0",
"sucrase": "3.35.1",
"typebox": "1.3.6",
"ws": "8.21.0",
"yaml": "2.9.0"
},
Expand Down
Loading
Loading