define split yonexus server/client architecture

This commit is contained in:
nav
2026-03-31 23:14:05 +00:00
parent 83e02829e7
commit 1d270110b0
3 changed files with 812 additions and 605 deletions

268
FEAT.md Normal file
View File

@@ -0,0 +1,268 @@
# Yonexus — Feature Checklist
## Project Direction
Yonexus is a **two-plugin** cross-instance communication system for OpenClaw:
- `Yonexus.Server`
- `Yonexus.Client`
This repository now targets the split-plugin architecture only.
---
## 1. Yonexus.Server Features
### 1.1 Server Runtime
- WebSocket server startup on OpenClaw gateway boot
- Configurable bind host / bind port
- Optional public WebSocket URL metadata
- Connection accept / close lifecycle handling
- One active authenticated connection per client identifier
### 1.2 Client Registry
- In-memory active client session registry
- Persistent client trust registry keyed by `identifier`
- Store client public key
- Store shared secret
- Store pairing state and expiry
- Store pairing notification metadata
- Store heartbeat timestamps
- Store recent security windows (nonce / handshake attempts)
- Store liveness state (`online | unstable | offline`)
### 1.3 Allowlist and Validation
- `followerIdentifiers` allowlist enforcement
- Reject unknown client identifiers
- Reject malformed builtin payloads
- Reject unsupported protocol versions
### 1.4 Pairing Flow
- Generate pairing code
- Generate pairing expiry / TTL
- Start pending pairing session
- Never send pairing code over Yonexus WebSocket
- Send pairing code to human admin via Discord DM using `notifyBotToken`
- Include target client `identifier` in pairing DM
- Accept client-submitted pairing code via builtin protocol
- Fail pairing on invalid code / expired code / notification failure
- Issue shared secret after successful pairing
- Persist paired trust material
### 1.5 Authentication
- Verify signed proof from client
- Validate stored secret
- Validate nonce format and uniqueness
- Validate timestamp drift `< 10s`
- Track recent handshake attempts
- Enforce `>10 attempts / 10s` unsafe threshold
- Trigger re-pair on unsafe condition
- Rotate or invalidate trust state when required
### 1.6 Heartbeat and Status
- Receive heartbeat from authenticated clients
- Update `lastHeartbeatAt`
- Mark client `unstable` after 7 minutes without heartbeat
- Mark client `offline` after 11 minutes without heartbeat
- Close socket when client becomes offline
- Optional heartbeat acknowledgement
- Periodic server-side status sweep timer
### 1.7 Messaging and Dispatch
- `sendMessageToClient(identifier, message)` API
- Rewrite inbound client messages to `${rule_identifier}::${sender_identifier}::${message_content}`
- Builtin message routing
- Rule registry for application messages
- First-match rule dispatch
- Reject reserved rule `builtin`
- Reject duplicate rule registration by default
### 1.8 Operations and Safety
- Structured errors for pairing/auth/transport failures
- Redacted logging for sensitive values
- Restart-safe persistent storage for trust state
- Clear or safely rebuild rolling security windows on restart
---
## 2. Yonexus.Client Features
### 2.1 Client Runtime
- WebSocket client startup on OpenClaw gateway boot
- Connect to configured `mainHost`
- Disconnect / reconnect lifecycle handling
- Retry/backoff reconnect strategy
### 2.2 Local Identity and Trust Material
- Persist local `identifier`
- Generate public/private keypair on first run
- Persist private key locally
- Persist server-issued secret locally
- Load existing trust material on restart
### 2.3 Pairing Flow
- Send `hello` after connect
- Enter pairing mode when server requires pairing
- Receive pairing metadata without receiving code itself
- Accept human-provided pairing code on client side
- Send pairing confirmation to server
- Store secret after `pair_success`
### 2.4 Authentication
- Build proof from `secret + nonce + timestamp`
- Prefer canonical serialized payload for signing
- Sign proof with local private key
- Send builtin `auth_request`
- Handle `auth_success`
- Handle `auth_failed`
- Handle `re_pair_required`
### 2.5 Heartbeat
- Start heartbeat loop after authentication
- Send heartbeat every 5 minutes
- Stop heartbeat when disconnected / unauthenticated
- Handle optional heartbeat acknowledgement
### 2.6 Messaging and Dispatch
- `sendMessageToServer(message)` API
- Builtin message routing
- Rule registry for application messages
- First-match rule dispatch
- Reject reserved rule `builtin`
- Reject duplicate rule registration by default
---
## 3. Shared Protocol Features
### 3.1 Builtin Wire Format
- `builtin::{json}` message format
- Standard builtin envelope with `type`, `requestId`, `timestamp`, `payload`
- UTC unix seconds as protocol timestamp unit
### 3.2 Builtin Types
- `hello`
- `hello_ack`
- `pair_request`
- `pair_confirm`
- `pair_success`
- `pair_failed`
- `auth_request`
- `auth_success`
- `auth_failed`
- `re_pair_required`
- `heartbeat`
- `heartbeat_ack`
- `status_update`
- `disconnect_notice`
- `error`
### 3.3 Security Constraints
- Pairing code must be delivered out-of-band only
- Pairing code must not travel over Yonexus WebSocket
- Nonce length fixed at 24 random characters
- Nonce replay detection window
- Timestamp freshness validation
- Rate-limit / unsafe reconnect detection
### 3.4 Rule Message Format
- Application messages use `${rule_identifier}::${message_content}`
- Server rewrites inbound client messages before dispatch
- Rule matching is exact-match in v1
---
## 4. Configuration Features
### 4.1 Yonexus.Server Config
- `followerIdentifiers: string[]`
- `notifyBotToken: string`
- `adminUserId: string`
- `listenHost?: string`
- `listenPort: number`
- `publicWsUrl?: string`
### 4.2 Yonexus.Client Config
- `mainHost: string`
- `identifier: string`
- `notifyBotToken: string`
- `adminUserId: string`
### 4.3 Validation
- Fail startup on missing required fields
- Fail startup on invalid config shape
- Validate required split-plugin semantics per side
---
## 5. Docs and Deliverables
### Required Planning / Spec Docs
- `PLAN.md`
- `PROTOCOL.md`
- `FEAT.md`
### Next Implementation Deliverables
- server plugin manifest
- client plugin manifest
- README for dual-plugin architecture
- implementation task breakdown
- protocol test cases
- pairing/auth failure-path test matrix
---
## 6. Suggested Delivery Order
### Phase 0 — Planning
- [x] Rewrite project direction
- [x] Define split-plugin model
- [x] Write protocol draft
- [x] Write feature checklist
### Phase 1 — Skeleton
- [ ] Create `Yonexus.Server` plugin scaffold
- [ ] Create `Yonexus.Client` plugin scaffold
- [ ] Add config schema / manifests
- [ ] Add minimal startup hooks
### Phase 2 — Transport
- [ ] Implement WebSocket server
- [ ] Implement WebSocket client
- [ ] Implement hello / hello_ack flow
- [ ] Implement reconnect baseline
### Phase 3 — Pairing and Auth
- [ ] Implement keypair generation
- [ ] Implement pairing creation
- [ ] Implement Discord DM notification
- [ ] Implement pairing confirmation
- [ ] Implement secret issuance
- [ ] Implement signed auth proof validation
- [ ] Implement nonce and rate-limit protection
### Phase 4 — Heartbeat and Messaging
- [ ] Implement heartbeat loop
- [ ] Implement server status sweep
- [ ] Implement `sendMessageToServer`
- [ ] Implement `sendMessageToClient`
- [ ] Implement rule registry and dispatch
### Phase 5 — Hardening
- [ ] Add persistence
- [ ] Add restart recovery behavior
- [ ] Add structured errors
- [ ] Add logging/redaction
- [ ] Add integration tests
- [ ] Add operator docs
---
## 7. Non-Goals
Not in initial scope unless explicitly added later:
- direct client-to-client sockets
- multi-server topology
- distributed consensus
- queueing guarantees for offline clients
- management UI
- advanced pattern matching for rules

608
PLAN.md
View File

@@ -2,133 +2,183 @@
## 1. Goal ## 1. Goal
Yonexus is an OpenClaw plugin for **cross-instance communication** between multiple OpenClaw deployments. Yonexus is a cross-instance communication system for OpenClaw, implemented as **two separate plugins**:
- `Yonexus.Server`
- `Yonexus.Client`
Together they provide:
- communication between multiple OpenClaw instances
- a central WebSocket hub model
- client pairing and authentication
- heartbeat-based client liveness tracking
- rule-based message dispatch
- out-of-band pairing notification to a human administrator via Discord DM
- TypeScript interfaces for higher-level plugin/runtime integrations
This project is no longer a role-switched single plugin. It is now explicitly split into two installable plugins with distinct responsibilities.
---
## 2. Plugin Split
## 2.1 Yonexus.Server
`Yonexus.Server` is installed only on the main OpenClaw instance.
Responsibilities:
- start and maintain the WebSocket server
- accept incoming client connections
- maintain the client registry
- handle pairing flow
- verify authentication proofs
- track heartbeat and connection state
- route or relay messages to connected clients
- rewrite inbound client messages before rule dispatch
- send Discord DM pairing notifications to the human administrator
## 2.2 Yonexus.Client
`Yonexus.Client` is installed on follower OpenClaw instances.
Responsibilities:
- connect to the configured Yonexus server
- generate and persist local keypair on first use
- persist local client identity and secret
- perform pairing confirmation
- perform authenticated reconnect
- send periodic heartbeats
- expose client-side messaging and rule registration APIs
---
## 3. Deployment Model
A Yonexus network contains: A Yonexus network contains:
- exactly one instance with role `main` - exactly one OpenClaw instance running `Yonexus.Server`
- one or more instances with role `follower` - one or more OpenClaw instances running `Yonexus.Client`
The plugin provides: Topology rules:
- a WebSocket-based communication layer between OpenClaw instances - `Yonexus.Server` must be reachable via fixed IP/domain or otherwise stable addressable endpoint
- pairing and identity verification for followers - `Yonexus.Client` instances do not need stable public IP/domain
- persistent follower registry and trust state on the main node - all `Yonexus.Client` instances connect outbound to the `Yonexus.Server` WebSocket endpoint
- heartbeat-based follower status tracking - no direct client-to-client communication is required in v1
- a rule-based message dispatch mechanism - inter-client communication, if needed, is relayed by `Yonexus.Server`
- TypeScript function interfaces for other plugin/runtime code
This project is **not** an organization/identity management plugin anymore. All prior goals are discarded.
--- ---
## 2. High-Level Architecture ## 4. Configuration Model
### 2.1 Roles ## 4.1 Yonexus.Server Config
Each OpenClaw instance running Yonexus must be configured with a `role`:
- `main`
- `follower`
Role semantics:
- `main` is the hub/server for all Yonexus communication
- `follower` connects outbound to the `main` instance
### 2.2 Network Topology
- The `main` instance must expose a fixed reachable IP/domain and run a WebSocket service.
- `follower` instances do not need fixed IP/domain.
- All `follower` instances connect to the `main` WebSocket endpoint.
- No direct follower-to-follower communication is required in v1.
- Messages between followers, if needed, are relayed by `main`.
### 2.3 Runtime Lifecycle
- On OpenClaw gateway startup:
- if role is `main`, Yonexus starts a WebSocket server through a hook
- if role is `follower`, Yonexus starts a WebSocket client and attempts to connect to `mainHost`
---
## 3. Configuration Model
## 3.1 Common Config
```ts ```ts
role: "main" | "follower" followerIdentifiers: string[]
notifyBotToken: string
adminUserId: string
listenHost?: string
listenPort: number
publicWsUrl?: string
``` ```
## 3.2 Follower Config Semantics:
- `followerIdentifiers`: allowlist of client identifiers permitted to pair/connect
- `notifyBotToken`: Discord bot token used to send pairing notifications
- `adminUserId`: Discord user id of the human administrator who receives pairing codes by DM
- `listenHost`: local bind host for WebSocket server
- `listenPort`: local bind port for WebSocket server
- `publicWsUrl`: optional canonical external URL advertised/documented for clients
Required when `role === "follower"`: ## 4.2 Yonexus.Client Config
```ts ```ts
mainHost: string mainHost: string
identifier: string identifier: string
notifyBotToken: string
adminUserId: string
``` ```
Semantics: Semantics:
- `mainHost`: WebSocket endpoint of the main instance (`ip:port` or full URL) - `mainHost`: WebSocket endpoint of `Yonexus.Server`
- `identifier`: unique follower identity inside the Yonexus network - `identifier`: unique identity of this client inside the Yonexus network
- `notifyBotToken`: kept aligned with shared config expectations if future client-side notification behaviors are needed
- `adminUserId`: human administrator identity reference shared with the Yonexus system
## 3.3 Main Config ## 4.3 Validation Rules
Required when `role === "main"`: ### Yonexus.Server
```ts
followerIdentifiers: string[]
```
Semantics:
- `followerIdentifiers`: allowlist of follower identifiers that are permitted to pair/connect
## 3.4 Validation Rules
### Main
- must have `role = main`
- must provide `followerIdentifiers` - must provide `followerIdentifiers`
- must expose a stable/reachable IP/domain outside the plugin itself - must provide `notifyBotToken`
- must provide `adminUserId`
- must provide `listenPort`
- must be deployed on a reachable/stable endpoint
### Follower ### Yonexus.Client
- must have `role = follower`
- must provide `mainHost` - must provide `mainHost`
- must provide `identifier` - must provide `identifier`
- must provide `notifyBotToken`
- must provide `adminUserId`
### Shared ### Shared
- invalid or missing role-specific fields must fail plugin initialization - invalid or missing required fields must fail plugin initialization
- unknown follower identifiers must be rejected by `main` - unknown client identifiers must be rejected by `Yonexus.Server`
--- ---
## 4. Main Responsibilities ## 5. Runtime Lifecycle
The `main` instance must maintain a registry keyed by follower `identifier`. ## 5.1 Yonexus.Server Startup
Each follower record contains at minimum: On OpenClaw gateway startup:
- initialize persistent client registry
- start WebSocket server
- register builtin protocol handlers
- register application rule registry
- start heartbeat/status sweep timer
## 5.2 Yonexus.Client Startup
On OpenClaw gateway startup:
- load local persisted identity, private key, and secret state
- generate keypair if absent
- connect to `mainHost`
- perform pairing or authentication flow depending on local state
- start heartbeat schedule after successful authentication
- attempt reconnect when disconnected
---
## 6. Server Registry and Persistence
`Yonexus.Server` must maintain a registry keyed by client `identifier`.
Each client record contains at minimum:
- `identifier` - `identifier`
- `publicKey` - `publicKey`
- `secret` - `secret`
- pairing state - pairing state
- pairing expiration data - pairing expiration data
- pairing notification metadata
- connection status - connection status
- security counters/window data - security counters/window data
- heartbeat timestamps - heartbeat timestamps
- last known connection/session metadata - last known session metadata
The registry must use: The registry must use:
- in-memory runtime state for active operations - in-memory runtime state for active connections and recent security windows
- persistent on-disk storage for restart survival - persistent on-disk storage for durable trust state
### 4.1 Persistent Main Registry Model ### 6.1 Proposed Server Record Shape
Proposed shape:
```ts ```ts
interface FollowerRecord { interface ClientRecord {
identifier: string; identifier: string;
publicKey?: string; publicKey?: string;
secret?: string; secret?: string;
pairingStatus: "unpaired" | "pending" | "paired" | "revoked"; pairingStatus: "unpaired" | "pending" | "paired" | "revoked";
pairingCode?: string; pairingCode?: string;
pairingExpiresAt?: number; pairingExpiresAt?: number;
pairingNotifiedAt?: number;
pairingNotifyStatus?: "pending" | "sent" | "failed";
status: "online" | "offline" | "unstable"; status: "online" | "offline" | "unstable";
lastHeartbeatAt?: number; lastHeartbeatAt?: number;
lastAuthenticatedAt?: number; lastAuthenticatedAt?: number;
@@ -142,87 +192,101 @@ interface FollowerRecord {
} }
``` ```
Notes:
- `recentNonces` stores only the recent nonce window needed for replay detection
- `recentHandshakeAttempts` stores timestamps for rate-limiting / unsafe reconnect detection
- actual field names can change during implementation, but these semantics must remain
--- ---
## 5. Pairing and Authentication Flow ## 7. Pairing and Authentication
## 5.1 First Connection: Key Generation ## 7.1 First Connection and Key Generation
When a follower connects to main for the first time: When a client connects to the server for the first time:
- the follower generates a public/private key pair locally - `Yonexus.Client` generates a public/private key pair locally
- the private key remains only on the follower - the private key remains only on the client instance
- the public key is sent to `main` during handshake - the public key is sent to `Yonexus.Server` during handshake
If `main` sees that: If the server sees that:
- the follower identifier is allowed, and - the client identifier is allowed, and
- no valid `secret` is currently associated with that identifier - there is no valid `secret` currently associated with that identifier
then `main` must enter pairing flow. then the server must enter pairing flow.
## 5.2 Pairing Flow ## 7.2 Pairing Flow
### Step A: Pairing Request ### Step A: Pairing Request Creation
`main` responds with a pairing request containing: `Yonexus.Server` generates:
- a random pairing string - a random pairing string
- an expiration time - an expiration time
The pairing string must **not** be sent to the client over WebSocket.
Instead, `Yonexus.Server` uses `notifyBotToken` to send a Discord DM to `adminUserId` containing:
- the client `identifier`
- the generated `pairingCode`
- the expiration time
### Step B: Pairing Confirmation ### Step B: Pairing Confirmation
If the follower sends that random pairing string back to `main` before expiration: The client must provide the pairing code back to the server before expiration.
How the client operator obtains the pairing code is intentionally out-of-band from the Yonexus WebSocket channel. The server only trusts that the code came through some human-mediated path.
If the client sends the correct pairing code before expiration:
- pairing succeeds - pairing succeeds
### Step C: Secret Issuance ### Step C: Secret Issuance
After successful pairing: After successful pairing:
- `main` generates a random `secret` - `Yonexus.Server` generates a random `secret`
- `main` returns that `secret` to the follower - `Yonexus.Server` returns that `secret` to the client
- `main` stores follower `publicKey` + `secret` - `Yonexus.Server` stores client `publicKey` + `secret`
- `follower` stores private key + secret locally - `Yonexus.Client` stores private key + secret locally
If Discord DM delivery fails:
- pairing must not proceed
- server should mark the pairing attempt as failed or pending-error
- client must not receive a usable pairing code through the protocol channel
If pairing expires before confirmation: If pairing expires before confirmation:
- pairing fails - pairing fails
- follower must restart the pairing process - the client must restart the pairing process
## 5.3 Reconnection Authentication Flow ## 7.3 Reconnect Authentication Flow
After pairing is complete, future follower authentication must use: After pairing is complete, future client authentication must use:
- the stored `secret` - the stored `secret`
- a 24-character random nonce - a 24-character random nonce
- current UTC Unix timestamp - current UTC Unix timestamp
The follower builds a plaintext proof payload from: The client builds a proof payload from:
- `secret` - `secret`
- `nonce` - `nonce`
- `timestamp` - `timestamp`
Concatenation order: Logical concatenation order:
```text ```text
secret + nonce + timestamp secret + nonce + timestamp
``` ```
The follower encrypts/signs this payload using its private key and sends it to `main`. Implementation recommendation:
- use a canonical serialized object and sign its bytes rather than naive string concatenation in code
`main` verifies: The client signs the proof using its private key and sends it to the server.
1. the follower identifier is known and paired
2. the public key matches stored state The server verifies:
3. decrypted/verified payload contains the correct `secret` 1. identifier is known and paired
4. timestamp difference from current UTC time is less than 10 seconds 2. public key matches stored state
3. proof contains the correct `secret`
4. timestamp difference from current time is less than 10 seconds
5. nonce does not collide with the recent nonce window 5. nonce does not collide with the recent nonce window
6. handshake attempts in the last 10 seconds do not exceed 10 6. handshake attempts in the last 10 seconds do not exceed 10
If all checks pass: If all checks pass:
- authentication succeeds - authentication succeeds
- follower is considered authenticated for the connection/session - the client is considered authenticated for the session
If any check fails: If any check fails:
- authentication fails - authentication fails
- main may downgrade/revoke trust state - server may downgrade or revoke trust state
## 5.4 Unsafe Condition Handling ## 7.4 Unsafe Condition Handling
The connection is considered unsafe and must return to pairing flow if either is true: The connection is considered unsafe and must return to pairing flow if either is true:
- more than 10 handshake attempts occur within 10 seconds - more than 10 handshake attempts occur within 10 seconds
@@ -230,55 +294,55 @@ The connection is considered unsafe and must return to pairing flow if either is
When unsafe: When unsafe:
- existing trust state must no longer be accepted for authentication - existing trust state must no longer be accepted for authentication
- the follower must re-pair - the client must re-pair
- main should clear or rotate the stored `secret` - server should clear or rotate the stored `secret`
- main should reset security windows as part of re-pairing - server should reset security windows as part of re-pairing
--- ---
## 6. Heartbeat and Follower Status ## 8. Heartbeat and Client Status
The main instance must track each followers liveness state: The server must track each clients liveness state:
- `online` - `online`
- `unstable` - `unstable`
- `offline` - `offline`
## 6.1 Heartbeat Rules ## 8.1 Heartbeat Rules
Each follower must send a heartbeat to main every 5 minutes. Each client must send a heartbeat to the server every 5 minutes.
## 6.2 Status Transitions ## 8.2 Status Transitions
### online ### online
A follower is `online` when: A client is `online` when:
- it has an active authenticated WebSocket connection, and - it has an active authenticated WebSocket connection, and
- main has received a recent heartbeat - the server has received a recent heartbeat
### unstable ### unstable
A follower becomes `unstable` when: A client becomes `unstable` when:
- no heartbeat has been received for 7 minutes - no heartbeat has been received for 7 minutes
### offline ### offline
A follower becomes `offline` when: A client becomes `offline` when:
- no heartbeat has been received for 11 minutes - no heartbeat has been received for 11 minutes
When follower becomes `offline`: When a client becomes `offline`:
- main must close/terminate the WebSocket connection for that follower - the server must close/terminate the WebSocket connection for that client
## 6.3 Status Evaluation Strategy ## 8.3 Status Evaluation Strategy
Main should run a periodic status sweep timer to evaluate heartbeat freshness. The server should run a periodic status sweep timer.
Recommended initial interval: Recommended interval:
- every 30 to 60 seconds - every 30 to 60 seconds
--- ---
## 7. Messaging Model ## 9. Messaging Model
Yonexus provides rule-based message dispatch over WebSocket. Yonexus provides rule-based message dispatch over WebSocket.
## 7.1 Base Message Format ## 9.1 Base Message Format
All application messages must use the format: All application messages must use the format:
@@ -286,21 +350,17 @@ All application messages must use the format:
${rule_identifier}::${message_content} ${rule_identifier}::${message_content}
``` ```
Constraints: ## 9.2 Server-Side Rewriting
- `rule_identifier` is a string token
- `message_content` is the remainder payload as string
## 7.2 Main-Side Rewriting When `Yonexus.Server` receives a message from a client, before rule matching it must rewrite the message into:
When `main` receives a message from a follower, before rule matching it must rewrite the message into:
```text ```text
${rule_identifier}::${sender_identifier}::${message_content} ${rule_identifier}::${sender_identifier}::${message_content}
``` ```
This ensures rule processors on `main` can identify which follower sent the message. This ensures server-side processors can identify which client sent the message.
## 7.3 Builtin Rule Namespace ## 9.3 Builtin Rule Namespace
The reserved rule identifier is: The reserved rule identifier is:
@@ -318,250 +378,202 @@ User code must not be allowed to register handlers for `builtin`.
--- ---
## 8. Rule Registration and Dispatch ## 10. TypeScript API Surface
## 8.1 Public API ## 10.1 Yonexus.Client API
```ts ```ts
registerRule(rule: string, processor: (message: string) => unknown): void sendMessageToServer(message: string): Promise<void>
```
## 8.2 Rule Format
`rule` must use the format:
```text
${rule_identifier}
```
Validation rules:
- must be non-empty
- must not contain the message delimiter sequence in invalid ways
- must not equal `builtin`
## 8.3 Dispatch Rules
When Yonexus receives a message over WebSocket:
- it iterates registered rules in registration order
- it finds the first matching rule
- it invokes the corresponding processor
- only the first match is used
Clarification for implementation:
- matching should initially be exact match on `rule_identifier`
- if pattern-based matching is desired later, that must be explicitly added in a future phase
If no rule matches:
- the message is ignored or logged as unhandled, depending on runtime policy
---
## 9. TypeScript API Surface
## 9.1 sendMessageToMain
```ts
sendMessageToMain(message: string): Promise<void>
``` ```
Rules: Rules:
- allowed only on `follower` - sends message to connected `Yonexus.Server`
- calling from `main` must throw an error
- sends message to connected `main`
- message must already conform to `${rule_identifier}::${message_content}` - message must already conform to `${rule_identifier}::${message_content}`
## 9.2 sendMessageToFollower
```ts
sendMessageToFollower(identifier: string, message: string): Promise<void>
```
Rules:
- allowed only on `main`
- calling from `follower` must throw an error
- target follower must be known and currently connected/authenticated
- message must already conform to `${rule_identifier}::${message_content}`
## 9.3 registerRule
```ts ```ts
registerRule(rule: string, processor: (message: string) => unknown): void registerRule(rule: string, processor: (message: string) => unknown): void
``` ```
Rules: Rules:
- rejects `builtin` - rejects `builtin`
- rejects duplicate rule registration unless an explicit override mode is added later - rejects duplicate rule registration unless explicit override support is added later
- processors are invoked with the final received string after any main-side rewrite
## 10.2 Yonexus.Server API
```ts
sendMessageToClient(identifier: string, message: string): Promise<void>
```
Rules:
- target client must be known and currently connected/authenticated
- message must already conform to `${rule_identifier}::${message_content}`
```ts
registerRule(rule: string, processor: (message: string) => unknown): void
```
Rules:
- rejects `builtin`
- rejects duplicate rule registration unless explicit override support is added later
- processors are invoked with the final received string after any server-side rewrite
--- ---
## 10. Hooks and Runtime Integration ## 11. Hooks and Integration
## 10.1 Main Hook ## 11.1 Yonexus.Server Hooking
The plugin must register a hook so that when OpenClaw gateway starts: `Yonexus.Server` must register hooks so that when OpenClaw gateway starts:
- Yonexus initializes internal state - the WebSocket server is started
- Yonexus starts a WebSocket server - the server registry is initialized
- Yonexus begins follower status sweep tasks - builtin protocol handling is enabled
- heartbeat sweep begins
## 10.2 Follower Runtime Behavior ## 11.2 Yonexus.Client Behavior
On startup, follower should: `Yonexus.Client` must:
- load local identity/secret/private key state - connect outbound to `mainHost`
- connect to `mainHost` - manage local trust material
- perform pairing or authentication flow - handle pairing/authentication transitions
- start periodic heartbeats when authenticated - emit heartbeats after authentication
- attempt reconnect when disconnected - reconnect after disconnect with retry/backoff behavior
## 10.3 Persistence Requirements ---
### Main persists: ## 12. Storage Strategy
- follower registry
- public keys
- secrets
- pairing state
- security/rate-limit windows if needed across restart, or resets them safely
### Follower persists: ## 12.1 Yonexus.Server Storage
Server persists at minimum:
- identifier - identifier
- private key - public key
- current secret - secret
- minimal pairing/auth state needed for reconnect - trust state
- pairing code + expiry if pairing is pending
- pairing notification metadata
- last known status
- metadata timestamps
--- May persist or reset on restart:
- recent nonces
- recent handshake attempts
## 11. Storage Strategy Recommended v1:
- clear rolling security windows on restart
- keep long-lived trust records
## 11.1 Main Storage ## 12.2 Yonexus.Client Storage
Main needs a local data file for follower registry persistence. Client persists at minimum:
- identifier
Suggested persisted sections:
- trusted followers
- pairing pending records
- last known status metadata
- security-related rolling records when persistence is desirable
## 11.2 Follower Storage
Follower needs a local secure data file for:
- private key - private key
- secret - secret
- identifier - optional last successful pair/auth metadata
- optional last successful connection metadata
## 11.3 Security Notes Security notes:
- private key must never be sent to the server
- private key must never be sent to main
- secret must be treated as sensitive material - secret must be treated as sensitive material
- storage format should support future encryption-at-rest, but plaintext local file may be acceptable in initial implementation if clearly documented as a limitation - encryption-at-rest can be a future enhancement, but any plaintext local storage must be documented as a limitation if used initially
--- ---
## 12. Error Handling ## 13. Error Handling
The plugin should define structured errors for at least: Structured errors should exist for at least:
- invalid configuration - invalid configuration
- invalid role usage
- unauthorized identifier - unauthorized identifier
- pairing required - pairing required
- pairing expired - pairing expired
- handshake verification failed - pairing notification failure
- handshake verification failure
- replay/nonce collision detected - replay/nonce collision detected
- rate limit / unsafe handshake detected - unsafe handshake rate detected
- follower not connected - target client not connected
- duplicate rule registration - duplicate rule registration
- reserved rule registration - reserved rule registration
- malformed message - malformed message
--- ---
## 13. Initial Implementation Phases ## 14. Initial Implementation Phases
## Phase 0 — Protocol and Skeleton ## Phase 0 — Protocol and Skeleton
- finalize config schema - finalize split-plugin configuration schema
- define persisted data models - define persistent data models
- define protocol message types for builtin traffic - define builtin protocol messages
- define hook startup behavior - define startup hooks for both plugins
- define rule registry behavior - define rule registry behavior
- define Discord DM notification flow
## Phase 1 — Main/Follower Transport MVP ## Phase 1 — Transport MVP
- main WebSocket server startup - Yonexus.Server WebSocket server startup
- follower WebSocket client startup - Yonexus.Client WebSocket client startup
- reconnect logic - reconnect logic
- basic builtin protocol channel - builtin protocol channel
- persistent registry scaffolding - persistent registry/state scaffolding
## Phase 2 — Pairing and Authentication ## Phase 2 — Pairing and Authentication
- follower keypair generation - client keypair generation
- pairing request/confirmation flow - pairing request creation
- Discord DM notification to admin user
- pairing confirmation flow
- secret issuance and persistence - secret issuance and persistence
- signed/encrypted handshake proof verification - signed proof verification
- nonce/replay protection - nonce/replay protection
- unsafe-condition reset to pairing - unsafe-condition reset to pairing
## Phase 3 — Heartbeat and Status Tracking ## Phase 3 — Heartbeat and Status Tracking
- follower heartbeat sender - client heartbeat sender
- main heartbeat receiver - server heartbeat receiver
- periodic sweep - periodic sweep
- status transitions: online / unstable / offline - status transitions: online / unstable / offline
- forced disconnect on offline - forced disconnect on offline
## Phase 4 — Public APIs and Message Dispatch ## Phase 4 — Public APIs and Dispatch
- `sendMessageToMain` - `sendMessageToServer`
- `sendMessageToFollower` - `sendMessageToClient`
- `registerRule` - `registerRule`
- first-match dispatch - first-match dispatch
- main-side sender rewrite behavior - server-side sender rewrite behavior
## Phase 5 — Hardening and Docs ## Phase 5 — Hardening and Docs
- integration tests - integration tests
- failure-path coverage - failure-path coverage
- restart recovery checks - restart recovery checks
- protocol docs - protocol docs
- operator setup docs for main/follower deployment - operator setup docs for server/client deployment
--- ---
## 14. Non-Goals for Initial Version ## 15. Non-Goals for Initial Version
Not required in the first version unless explicitly added later: Not required in the first version unless explicitly added later:
- direct follower-to-follower sockets - direct client-to-client sockets
- multi-main clustering - multi-server clustering
- distributed consensus - distributed consensus
- message ordering guarantees across reconnects - message ordering guarantees across reconnects
- end-to-end application payload encryption beyond the handshake/authentication requirements - end-to-end payload encryption beyond the pairing/authentication requirements
- UI management panel - management UI
--- ---
## 15. Open Questions To Confirm Later ## 16. Open Questions To Confirm Later
These should be resolved before implementation starts: 1. Exact signing algorithm:
- Ed25519 is a strong default candidate
1. Is the handshake primitive meant to be: 2. Should `mainHost` accept only full WebSocket URLs or also raw `ip:port` strings?
- asymmetric encryption with private/public key, or 3. Is human code relay sufficient for v1 pairing, or should admin approve/deny controls be added later?
- digital signature with verification by public key? 4. On unsafe condition, should the old public key be retained or should the client generate a new keypair?
5. Should offline clients support queued outbound messages from server, or should sends fail immediately?
Recommended: **signature**, not “private-key encryption” wording. 6. Are rule identifiers exact strings only, or should regex/prefix matching exist later?
2. Should `mainHost` accept only full WebSocket URLs (`ws://` / `wss://`) or also raw `ip:port` strings?
3. Should pairing require explicit operator approval on main, or is allowlist membership enough for automatic pairing?
4. On unsafe condition, should the old public key be retained or must the follower generate a brand-new keypair?
5. Should offline followers be allowed queued outbound messages from main, or should send fail immediately?
6. Are rule identifiers exact strings only, or should regex/prefix matching exist in future?
--- ---
## 16. Immediate Next Deliverables ## 17. Immediate Next Deliverables
After this plan, the next files to create should be: After this plan, the next files to create should be:
- `FEAT.md` — feature checklist derived from this plan - `FEAT.md` — feature checklist derived from this plan
- `README.md` — concise operator/developer overview - `README.md` — concise system overview for both plugins
- `plugin.json` — plugin config schema and entry declaration - `plugin.server.json` or equivalent server plugin manifest
- protocol notes for builtin messages - `plugin.client.json` or equivalent client plugin manifest
- implementation task breakdown - implementation task breakdown

File diff suppressed because it is too large Load Diff