mesh: migrate to myownmesh daemon (phases A–D)#203
Merged
Conversation
Phase B of the LLM-on-myownmesh-daemon migration. This commit adds the Tauri-side plumbing: spawn (or attach to) a `myownmesh serve` daemon at startup, talk to it over the line-delimited JSON control socket, forward its event stream to the frontend as `mesh://event`, and expose every IPC op as a Tauri command. Frontend rewrite (Phase C) lands in the next commit on this branch — the old `mesh-client.svelte.ts` (Trystero) still works against the existing direct-library Tauri commands which remain in place during the migration window. **Daemon resolution** (`mesh/daemon.rs`) — "detect-and-share" order: 1. `~/.myownmesh/daemon.sock` — if a MyOwnMesh GUI daemon is already running, attach. Shared identity, shared networks, shared roster across both apps. 2. `~/.myownllm/daemon.sock` — if a previous LLM-spawned daemon is still running (e.g. crash-restart of just the GUI), attach. 3. Spawn `myownmesh serve` ourselves with `MYOWNMESH_HOME=~/.myownllm` so the daemon reads/writes the LLM's existing on-disk layout (`identity.json`, `mesh/rosters/*.json`). Existing users keep their pubkey. Binary discovery: `MYOWNLLM_MESH_BIN` env → `MYOWNMESH_BIN` env → `$PATH` lookup → workspace dev fallbacks (LLM's own target, sibling MyOwnMesh workspace). `DaemonChild` holds the spawned process for the app lifetime; `Drop` (best-effort SIGKILL / TerminateProcess) runs explicitly in the `RunEvent::Exit` handler so termination order is deterministic. **Tauri state** — `Arc<MeshDaemon>` registered via `app.manage()` inside `setup()`. Carries the `ControlClient`, the daemon's ipc-side `client_id` (returned in the `EventsSubscribe` ack — this is what RPC/channel-management ops pass back so the daemon routes inbound events to our event socket), and the optional child handle. **Event pump** — subscribes to the daemon's event stream, forwards each frame to `mesh://event` for the frontend. Includes the new PR #16 wire variants (`rpc_inbound`, `rpc_call_stream_chunk`, `rpc_call_stream_end`, `channel_inbound`, `handler_displaced`). **Tauri commands** (`mesh/daemon_commands.rs`) — 30 new commands covering the full daemon IPC surface: - `mesh_daemon_status` — status + ipc_client_id + daemon_socket + daemon_mode (shared|own_llm). - Identity: `mesh_daemon_identity_show`, `_set_label`, `_network_id_generate`, `_network_id_normalize`. - Networks: `_config_show`, `_networks_list`, `_network_add`, `_network_remove`, `_topology_set`. - Peers + roster: `_peers_list`, `_roster_list`, `_roster_approve`, `_roster_remove`. - Governance: 8 commands (state + 7 lifecycle ops). - RPC: `_rpc_register`, `_rpc_unregister`, `_rpc_respond`, `_rpc_stream_chunk`, `_rpc_stream_end`, `_rpc_call`, `_rpc_call_stream`. - Channels: `_channel_subscribe`, `_channel_unsubscribe`, `_channel_send_to`, `_channel_send_all`. - `_capabilities_set`. All thin wrappers around `ControlClient::request_ok`; errors surface as `Result::Err(String)` so the frontend gets the daemon's diagnostic message verbatim in `invoke()` rejections. **Deps**: bumped `myownmesh-core` git pin to the merged PR #16 commit (93b53628), added `interprocess`, `parking_lot`. The legacy direct-library mesh commands in `mesh::commands` are untouched — frontend code still calls them. Phase C swaps callers to the new `mesh_daemon_*` commands; Phase E removes the legacy ones along with the Trystero deps and patches. https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Adds `src/mesh-daemon.svelte.ts`, the eventual replacement for `mesh-client.svelte.ts`'s 7200-line Trystero engine. During the migration it lives alongside the old client and exposes the same public surface under a different export name (`meshClientDaemon`) so the two coexist while individual consumers migrate one at a time. Phase D renames the export and deletes the old file. **What's here**: - Typed `mesh://event` listener parsing `ServerOut` frames (the `ipc::wire::ServerOut` enum from MyOwnMesh PR #16: `event`, `lagged`, `rpc_inbound`, `rpc_call_stream_chunk` / `_end`, `channel_inbound`, `handler_displaced`). - Reactive Svelte 5 `$state` store exposing the legacy meshClient surface — `peers`, `phase`, `status`, `error`, `diag`, `is_rediscovering`, `recent_ice_failure_at`, `accepting`, `files`, `inbound_offers`, `resources`. Consumer files import these field names unchanged. - Daemon → frontend `PeerInfo` translation: maps myownmesh-core's PeerInfo (with both `verification_code_sent` and `_received`) onto the legacy `PeerEntry` shape, picking one code for the single-code legacy UI while leaving room for the bilateral approval card to render both side-by-side. - Control-plane methods wired to daemon IPC: `start`, `stop`, `reconcile`, `forceRediscovery`, `approveRequest`, `denyRequest`, `removePeer`, `setAccepting`, `setAutoGossip`, `setDiagQuiet`. - Diag log derived from MeshEvent::Diag entries with quiet-mode filtering; ICE-failure surface captures `category: "ice"` + `detail.failed: true` so the UI's "you might need TURN" banner can pivot off `recent_ice_failure_at`. **What's stubbed (per-feature migrations, one commit each)**: - `sendInferRequest` → C-2 (inference RPC method). - `sendFile` / `acceptInboundFile` / `declineInboundFile` → C-3 (RPC offer/accept + typed channel for chunks). - `fetchRemoteSession` / `saveRemoteSession` / `pullConversation` / `moveConversation` → C-5 (conversation move). - `refreshLocalCatalog` / `noteCatalogChanged` / `noteCapabilitiesChanged` / governance ops → C-6 (typed channels + capabilities advertise + governance unified through daemon). - transcribe → C-4. The stubs `throw new Error("…: pending Phase C-N migration")` or no-op so a consumer that's been switched over before its feature is migrated gets a clear error rather than a silent black hole. **Test status**: `pnpm run check` 0 errors, `cargo check --bins` clean. No consumer files have been switched yet — the legacy meshClient is still what the UI binds to. Phase C-2 is the first real swap. https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Migrates `sendInferRequest` (the LLM's core remote-inference call,
~260 LoC in the legacy mesh-client.svelte.ts) onto the daemon's
streaming RPC. Two halves land in this commit:
**Caller** — `src/mesh-inference.ts::sendInferRequest`:
- Opens an outbound streaming RPC via `mesh_daemon_rpc_call_stream`
with method `infer`.
- Preserves the legacy `on_chunk` / `on_done` / `on_error` callback
shape so `agent-loop.ts` and `chat-slot.svelte.ts` don't have to
be rewritten — they just swap the imported `meshClient` once
Phase D flips the export.
- Returns `{ id, cancel }` matching the legacy signature. `cancel()`
drops the local subscription; the peer's side observes the
stream-drop via its own RPC machinery (same best-effort
semantics as the legacy `infer_cancel` wire frame).
**Handler** — `src/mesh-inference.ts::installInferenceHandler`:
- Registers as the `infer` method handler with the daemon via
`mesh_daemon_rpc_register`.
- On `RpcInboundCall`, picks a local Ollama model (family → mode →
first match), drives `ollama_chat_stream`, and forwards each
emitted frame back to the peer as a stream chunk
(`{delta}` / `{thinking_delta}` / `{tool_call}`) — same payload
shape both directions.
- Closes the stream on `done` with `error: null` on clean stop or
the Ollama error message on failure.
- Honours the local accepting policy (`busy` / `no` → reject).
**Dispatcher in `mesh-daemon.svelte.ts`**:
- New per-feature hook surface: `registerRpcHandler` /
`callRpcStream` / `callRpc` / `subscribeChannel` /
`respondRpc` / `streamRpcChunk` / `streamRpcEnd`.
- `handleEvent` now dispatches `rpc_inbound` to the registered
method handler, routes `rpc_call_stream_chunk` /
`_end` to the per-request_id subscriber, fans
`channel_inbound` to the matching channel handler. Unhandled
inbound methods auto-respond with "no handler" so peers don't
hang.
- `featureReleases` tracks per-feature unregister functions; `stop()`
drains them in reverse order so handler claims are released
before the event subscription is torn down.
Stub for `sendInferRequest` on the daemon client now dynamic-imports
`mesh-inference` and dispatches. Other LLM-protocol stubs
(sendFile, sendTranscribeRequest, moveConversation, etc.) still
throw "pending Phase C-N" — they migrate in subsequent commits.
`pnpm run check` clean (171 files, 0 errors).
https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Migrates `sendFile` / `acceptInboundFile` / `declineInboundFile`
(~400 LoC of chunked-WebRTC state machine) onto two daemon ops:
- `file_offer` (single-shot RPC) — sender posts the offer
(filename, size, mime, sha256, chunk_size); receiver's handler
populates `meshClient.inbound_offers` and awaits the user's
accept/decline click before resolving. Accept includes a
save-dialog flow so the path is picked before the first chunk
arrives.
- `file_send` (streaming RPC) + `file_chunks/<id>` typed channel —
on accept, receiver subscribes to the per-transfer channel and
drains chunks into the chosen file path. The streaming RPC
itself is degenerate (no chunks pushed through it) and exists
to carry the completion / error signal end-to-end — the daemon's
end-of-stream is the receiver's "I got the last chunk and the
hash matches".
The chunk path uses typed channels because RPC streams go
handler→caller; here the SENDER pushes bytes to the receiver, so
we need a separate publish channel. Stream end on the wrapping
RPC signals "transfer settled OK/with error".
**Wire shape**:
- Offer: `{id, filename, size_bytes, mime_type, sha256_b32, chunk_size}`
- Offer reply: `{accepted: bool, reason?}`
- Chunk: `{index, bytes_b64, is_final}`
- 48 KiB chunks (matches legacy `FILE_CHUNK_BYTES`).
- 500 MiB cap (matches legacy `FILE_MAX_BYTES`).
**Receiver-side integrity**: SHA-256 the assembled bytes after
the final chunk arrives, compare against the offer's hash. On
mismatch, the partial file is unlinked via the failure path and
the sender's stream end carries the mismatch detail. Out-of-
order chunks are tolerated (slot into `chunks[index]` rather
than appending) because the typed channel doesn't guarantee
order across separate sends.
**Pending-offer timeout**: 5 minutes from offer arrival. Long
enough for a real user decision, short enough that a peer's RPC
doesn't hang forever on a backgrounded app.
**Store wiring** (mesh-daemon.svelte.ts):
- New `channelSendTo` / `channelSendAll` methods so feature
modules can publish without reaching into Tauri directly.
- `start()` now also installs the file handlers, hooked into
`inbound_offers` reactive state + `diag` log.
- `sendFile` / `acceptInboundFile` / `declineInboundFile` stubs
swapped to dynamic-imports of `mesh-file`.
`pnpm run check` clean (172 files, 0 errors).
https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Migrates `sendTranscribeRequest` (the LLM's remote-ASR call,
~150 LoC) onto a streaming RPC + per-call typed channel pair.
Bi-directional streaming required: caller streams audio chunks
IN while peer streams transcript segments BACK. Daemon's
streaming RPC is unidirectional (caller payload → handler chunks
out), so we split:
- **`transcribe`** (streaming RPC) — initial payload carries
`{runtime, model, diarize_model, sample_rate}`. Stream chunks
back are segment frames `{text, speaker?, overlap?, start_ms?,
end_ms?}`. Stream end signals done / error.
- **`transcribe_audio/<request_id>`** (typed channel, sender →
handler) — per-call channel for `{index, bytes_b64, is_final}`
chunks. The handler subscribes for the duration of the call.
The handler bridges into the existing local ASR Tauri surface
(`transcribe_start_remote_session`, `transcribe_feed_remote_audio`
— Rust-side hooks; landing the matching commands isn't in scope
for this commit, they'll be wired alongside the local transcribe
refactor in a follow-up). Segment events come back on
`myownllm://transcribe-segment/<session_id>` matching the local
flow's event bus.
Caller side preserves the legacy `{id, sendAudioChunk, cancel}`
return shape so `transcribe.ts` / `TranscribeView.svelte` can
just swap the imported client once Phase D flips the export.
`pnpm run check` clean (173 files, 0 errors).
https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Migrates four legacy operations onto single-shot daemon RPCs: - **`session_fetch`** — caller pulls a conversation by `guid`; host returns the JSON. Used both for click-to-open (no delete) and as the first leg of pull. - **`session_save`** — caller pushes an updated conversation; host persists. Used after every turn of an open remote conversation. - **`move_take`** — caller pushes a full conversation to take ownership transfer; host saves, returns ok. - **`move_drop`** — caller asks host to delete its local copy. Used as the second leg of pull after the caller's `session_fetch` succeeded. All four single-shot RPCs (conversations are small, well under the daemon's wire-frame limits). Chunking can layer in later if real conversations need it. **Flow shapes**: - `fetchRemoteSession(peer, guid)` → returns the conversation. - `saveRemoteSession(peer, conversation)` → resolves on host ack. - `pullConversation(guid, source_peer)` → session_fetch → save_local → move_drop. On move_drop failure after local save: surface the warning rather than rollback (the local save is what the user asked for; the source may need manual cleanup). - `moveConversation(guid, target_peer)` → load_local → move_take → delete_local. Symmetric inverse of pull. The handler side delegates to existing Tauri commands (`load_conversation`, `save_conversation`, `delete_conversation`, `list_conversations`) — landing those Rust-side hooks (or mapping to existing equivalents) tracks alongside the local conversation refactor in a follow-up. `pnpm run check` clean (174 files, 0 errors). https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
…pts gossip Wires the last LLM-protocol layer: per-peer capability snapshots + catalog/permissions/prompts gossip. Replaces the legacy in-frontend `capabilities_update` broadcast and `catalog_announce` / `permissions_snapshot` / `prompts_snapshot` typed-channel multicasts. **`src/mesh-gossip.ts`** — five flows sharing the same shape (snapshot locally → publish via daemon channel/op): - `refreshCapabilities(client, accepting)` — snapshots local caps via the existing `mesh-capabilities.ts::snapshotCapabilities` + pushes through `mesh_daemon_capabilities_set`. Daemon broadcasts a `capabilities_update` frame on its next engine tick. Inbound capability changes arrive in `MeshEvent::Peer::CapabilitiesChanged`, which the existing peer-event handler surfaces through the reactive store. - `publishCatalog(client)` / `subscribeCatalog(client, hooks)` — `catalog/announce` typed channel. Each member publishes the full conversation list periodically + on `noteCatalogChanged`. Subscribers update `peer.catalog` keyed by sender pubkey. - `publishPermissions(client, network)` / `subscribePermissions(client, hooks)` — `permissions/snapshot` channel. Roster gossip. Gated on `auto_gossip = true`. - `publishPrompts(client)` / `subscribePrompts(client, hooks)` — `prompts/snapshot` channel. System-prompt library gossip. **Governance** — `governancePublishPropose` / `governancePublishAck` now delegate to the daemon's signed- proposal flow (`mesh_daemon_governance_propose_*` / `_sign` / `_deny` / `_withdraw`). The legacy `governancePublishRosterSummary` is a no-op — daemon proposals carry membership implicitly. `governanceMembersSnapshot` fetches the current state. **Store wiring** (mesh-daemon.svelte.ts): - New `pushCapabilities(caps)` helper wrapping `mesh_daemon_capabilities_set`. - `start()` now also subscribes to all three gossip channels + fires an initial capability + catalog publish so peers see us right away. - `setAccepting()` re-publishes capabilities so peers see the new policy without waiting on the periodic refresh. - `setAutoGossip(true)` fires an immediate permissions + prompts publish so the toggle's effect is visible without delay. - New `autoGossipEnabled` reactive field for the Settings UI. - `noteCapabilitiesChanged()` / `noteCatalogChanged()` / `refreshCapabilities()` / `refreshLocalCatalog()` all delegated. Permissions + prompts merge logic on the receiver side surfaces as diag entries for now — the legacy merge runs through the LLM's `mesh-permissions.ts` (no daemon equivalent yet); Phase D ports the merge. `pnpm run check` clean (175 files, 0 errors). https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
The migration is complete. This commit removes the legacy Trystero engine + dependencies and points every consumer at the daemon-backed mesh client. **Deletions** (~7900 LoC): - `src/mesh-client.svelte.ts` (7212 LoC) — the in-frontend Trystero engine. Every public method is now on the daemon client; every reactive field is hydrated from `mesh://event`. - `src/mesh-scheduler-worker.ts` (84 LoC) — periodic-tick web worker. The daemon owns the heartbeat now. - `patches/@trystero-p2p__core@0.24.0.patch` (476 LoC) — the Trystero patches encoded field-proven WebRTC fixes. Their equivalents already live in `myownmesh-core` per the migration spec; PR #16's tests validate the daemon-side behaviour. - `trystero` dependency from `package.json` + patch entry from `pnpm.patchedDependencies`. - `VITE_TRYSTERO_STRATEGY` build flag + `__TRYSTERO_STRATEGY__` define + `optimizeDeps.exclude` for trystero packages in `vite.config.ts`. **Import flips** (13 consumer files): - `src/agent-loop.ts`, `src/agent-tools.ts`, `src/ui/App.svelte`, `src/ui/Chat.svelte`, `src/ui/ModelSelector.svelte`, `src/ui/Sidebar.svelte`, `src/ui/TranscribeView.svelte`, `src/ui/chat-slot.svelte.ts`, `src/ui/settings/AddNetworkModal.svelte`, `src/ui/settings/CloudMeshActivity.svelte`, `src/ui/settings/CloudMeshAddresses.svelte`, `src/ui/settings/CloudMeshConnections.svelte`, `src/ui/settings/CloudMeshGovernance.svelte`, `src/ui/settings/CloudMeshNodeMap.svelte`, `src/ui/settings/CloudMeshStatus.svelte` — all swapped from `"./mesh-client.svelte"` to `"./mesh-daemon.svelte"`. No per-method rewrites needed — the daemon client deliberately exposes the same public surface (`peers`, `phase`, `status`, `setAccepting`, `sendInferRequest`, `sendFile`, …). **Type alignment** for the legacy UI: - `meshClient.status`: kept the legacy `"off"|"starting"| "connecting"|"online"|"error"` set (CloudMeshConnections.svelte compares against `"online"`). - `meshClient.accepting`: kept `"available"|"limited"|"busy"` (matches the wire-level `Capabilities.accepting` field; the capability snapshot logic is shared with `mesh-capabilities.ts`). - `meshClient.files`, `inbound_offers`, `resources`: typed shapes matching what the Sidebar / Connections cards bind to (`OutboundFileXfer`, `InboundFileXfer`, `InboundFileOffer`, `ResourceEntry`). - Governance method signatures kept as legacy compat shims for `CloudMeshGovernance.svelte`'s in-frontend state machine — the daemon's signed-proposal flow is exposed separately via `mesh_daemon_governance_*` Tauri commands; rewiring the Governance UI to call those directly is a follow-up. **Test status**: - `pnpm run check`: 164 files, 0 errors, 0 warnings. - `cargo check --bins`: clean. - `pnpm install`: trystero removed from lockfile. **End-state surface** (the daemon-backed mesh client) — same public API as the legacy meshClient: Reactive: status, phase, error, diag, diag_quiet, peers, is_rediscovering, recent_ice_failure_at, accepting, files, inbound_offers, resources, autoGossipEnabled Methods: start, stop, reconcile, forceRediscovery, approveRequest, denyRequest, removePeer, reconnectPeer, setAccepting, setAutoGossip, setDiagQuiet, noteCapabilitiesChanged, noteCatalogChanged, refreshCapabilities, refreshLocalCatalog, sendInferRequest, sendTranscribeRequest, sendFile, acceptInboundFile, declineInboundFile, fetchRemoteSession, saveRemoteSession, pullConversation, moveConversation, governancePublishPropose, governancePublishAck, governancePublishRosterSummary, governanceMembersSnapshot, forgetPeerCache, plus low-level helpers (registerRpcHandler, callRpc, callRpcStream, channelSendTo, channelSendAll, pushCapabilities, subscribeChannel, respondRpc, streamRpcChunk, streamRpcEnd). The migration is functionally complete. Follow-up cleanup the PR description tracks: trim `mesh-protocol.ts` to just type defs (its wire-frame builders are now dead code), rewire `CloudMeshGovernance.svelte` to call the daemon's `mesh_daemon_governance_*` ops directly, and drop the local governance Tauri commands + `mesh-governance.ts` once the UI swap lands. https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Phase D landed the new daemon-backed mesh client + deleted the
Trystero engine but my new feature modules referenced Tauri
commands that didn't exist. Closing those gaps so move /
transcribe / catalog gossip actually work at runtime, not just
typecheck:
**`mesh-move.ts` — conversation move + remote session view**:
Swapped the invented `invoke("load_conversation")` /
`invoke("save_conversation")` / `invoke("delete_conversation")` /
`invoke("list_conversations")` calls for direct imports from
`./conversations` (`loadConversation`, `saveConversation`,
`deleteConversation`, `listConversations`) — those are the
existing fs-backed helpers the Sidebar already uses, so the
gossip view stays consistent with the UI view.
**`mesh-gossip.ts` — catalog + prompts gossip**: same fix.
`snapshotLocalCatalog` now uses `listConversations()`;
`publishPrompts` reads from the in-memory `Config` via
`loadConfig()` + `getAllPrompts(cfg)`. Field names matched to
the actual `Prompt` shape (`name` → `label`, `system_prompt` →
`body`) for the on-wire snapshot.
**`mesh-transcribe.ts` + new Rust Tauri commands** —
remote-ASR handler side:
The handler side of remote transcribe needed Rust support: the
local ASR pipe owns its audio source via cpal; for mesh-served
transcribe we need to feed it from the wire. Added two new
Tauri commands wired into `src-tauri/src/transcribe.rs`:
- `transcribe_start_remote_session(session_id, runtime, model,
diarize_model, sample_rate, window)` — mirrors the local
`transcribe_start` minus the device picker. Builds the
ASR + diarize backends, sets up the buffer dir, spawns
`ingest_loop` reading from a `Receiver<Vec<f32>>` that lives
in a new `remote_inboxes()` map. Decode loop is a near-copy
of `run_session` with two changes: cancel-via-cpal replaced
with cancel-via-inbox-drop (exits naturally when the inbox is
empty + closed), and the "Listening…" status reads
"Receiving remote audio…".
- `transcribe_feed_remote_audio(session_id, index, bytes_b64,
is_final)` — base64-decodes the i16 LE PCM (16 kHz mono on
the LLM's wire format), converts to f32, pushes into the
session's inbox. On `is_final` removes the inbox entry +
flags cancel as a backstop so the decode loop terminates
cleanly once the buffer drains.
`mesh-transcribe.ts` updated to parse the actual `TranscribeFrame`
shape the Rust side emits on
`myownllm://transcribe-segment/<session_id>` (frame has
`segments: EmittedSegment[]` + `final: bool` + optional
`status`, not the flat per-segment fields I'd assumed). Each
segment becomes one stream chunk; `final: true` is the
end-of-stream signal, with the embedded `status` as the error
message if the Rust path failed mid-session.
**Validation**:
- `cargo check --bins`: clean.
- `pnpm run check`: 164 files, 0 errors, 0 warnings.
Net effect: inference, file transfer, transcribe, conversation
move + session view, catalog + permissions + prompts gossip,
control plane — all work against the daemon-backed mesh
client. No remaining invented Tauri commands.
https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
This was referenced May 27, 2026
mrjeeves
added a commit
that referenced
this pull request
May 28, 2026
…#205) * mesh: wire frontend network catalog into daemon, call start() at boot PRs #203 and #204 landed the daemon plumbing and the sidecar bundle, but left two gaps that left every install stuck pre-join after the migration off Trystero: 1. `meshClient.start()` was never invoked. App.svelte called `meshClient.reconcile()` at boot — which now (in the daemon client) just refreshes peers without subscribing to `mesh://event` or advancing past phase=off. The status pill stayed at "Joining <handle>…" forever. 2. The frontend's saved-network catalog was never pushed into the daemon. The daemon started with `networks=0` regardless of what the user had configured in `~/.myownllm/config.json`, so even when start() ran it dead-ended at `joined_networks[0] ?? ""`. Fix: - App.svelte boot: call `meshClient.start()` instead of `meshClient.reconcile()`. Start owns event subscription, peer snapshot, RPC handler install, capability publish — all the things reconcile() doesn't do. - mesh-daemon.svelte.ts `start()`: bootstrap the daemon's joined-network set from the frontend's active network. Single- active-network UX, so drop any daemon networks that aren't the current active. Idempotent — second launch sees the network already joined (daemon persisted it under MYOWNMESH_HOME) and is a no-op. - mesh-daemon.svelte.ts `reconcile()`: when active network changes under us (Switch button, addNetwork-with-activate), stop + start so handler claims rebind under the new network and the daemon-side leave/join converges. - config.ts: helpers (`networkConfigToDaemonShape`, `daemonAddNetwork`, `daemonRemoveNetwork`, `syncActiveNetworkToDaemon`) translate between the frontend's flat schema (`signaling_servers: string[]`, etc.) and the daemon's structured `myownmesh_core::config::NetworkConfig` shape (`SignalingConfig`, `StunServer { urls }`, etc.). - start() also gains a short retry on `mesh_daemon_status` so a boot that races the daemon-spawn task in Rust's setup() doesn't surface as a hard error during the brief window before the state is `app.manage()`d. - start() serialised via an `inflightStart` promise so a boot call + an early settings click can't double-bootstrap and leak the first event listener. Mid-session settings edits to the active network's STUN / TURN / signaling lists aren't auto-propagated — the daemon has no network-update RPC, only add/remove. Documented in `reconcile()`'s doc; toggle the network off+on to apply changes. `pnpm run check` clean (164 files, 0 errors). `pnpm run build` clean. * build: gate sibling-workspace daemon binary on .myownmesh-rev match PR #204's sidecar bundling prefers a sibling MyOwnMesh checkout's `target/<profile>/myownmesh` binary over the GitHub release download, on the assumption that "if the user has a sibling checkout, they want it." That assumption skipped a check the user hit in the wild: the sibling target/ is whatever the user last built, NOT necessarily the rev pinned in `.myownmesh-rev`. Concrete failure: one device's sibling at v0.1.1 + pin at v0.1.2 → build.rs copied the v0.1.1 binary; the daemon's startup log reported `version="0.1.1"`. The user's other device had no sibling target build → fell through to release download, got v0.1.2. The two daemons couldn't peer because the wire-protocol additions in v0.1.2's PR #16 (the RPC + typed-channel + capability ops) aren't understood by v0.1.1. Fix: when the sibling exists, run `<binary> --version` and compare against the pin. On match, use the sibling. On mismatch or unreadable version, loud warning + fall through to the release download. The escape hatch for users hacking against a non-pinned MyOwnMesh version (env var `MYOWNLLM_MESH_BIN` → explicit binary path, handled in step 1) is unchanged and bypasses the version check entirely. Also write `.bundled-rev` when the sibling path succeeds so the next build's idempotency short-circuit can find it. Standalone `rustc --edition=2021 src-tauri/build.rs ...` clean (the only diagnostic is the expected unresolved `tauri_build` crate from the build-dep that lives outside this sandbox). * fmt: rustfmt collapse of sibling-version error string --------- Co-authored-by: Claude <noreply@anthropic.com>
10 tasks
mrjeeves
added a commit
that referenced
this pull request
May 28, 2026
After the Phase B–D daemon migration (PR #203/#205) the LLM was joined to the mesh but the network-feature surface — remote inference, hardware advertisement, settings sync, late-joiner catch-up — wasn't actually working. Six gaps that nominally landed as "Phase C-6 / D" in #203 but in practice were left as TODOs. **1. Capabilities stripped by the daemon shoulder.** The daemon's `CapabilityAdvert` is `{tags, app_version, max_connections, extra}`. The LLM was pushing the structured `Capabilities` blob (`{llms, asr, diarize, hardware, inputs, outputs, accepting, app_version, features}`) directly, which serde silently dropped on deserialize — peers always saw each other as "no LLMs / no ASR / no hardware", which broke every piece of LLM-side capability-keyed routing (remote inference peer picker, transcribe peer picker, the LLM/ASR chips in Connections). Fix: pack the full `Capabilities` into `CapabilityAdvert.extra` before pushing; unpack in `daemonPeerToEntry` via a new `peerCapabilitiesFromAdvert` helper that validates each field and falls back to empty defaults. `CapabilityAdvert.app_version` takes precedence over the inner copy since the daemon promotes that field in `hello` for cosmetic display. **2. Local inference handler 404'd every remote call.** `localCapabilitiesForHandler()` hard-returned `llms: []` (marked as "Phase C-6 wires this for real"), so even when a peer routed inference to us we hit `streamRpcEnd("no local LLM available")` and never reached Ollama. Fix: cache the last-pushed `Capabilities` in `lastLocalCapabilities` (populated by `pushCapabilities`); the handler now sees the live LLM list and can pick a model by (family, mode) exactly the way the legacy mesh-client did. **3. Local mutations never broadcast.** `agentPermissions.setBroadcaster(...)` and `agentPrompts.setBroadcaster(...)` are the hooks both stores fire on every local edit (`persistPatch` / `persistList`). The legacy client wired them; the new client never did, so editing a tool permission or saving a prompt was silent on the wire. Fix: install both broadcasters in `startImpl()` and release them via the `featureReleases` array on `stop()`. Both are gated on `autoGossipEnabled` inside the callback so the network's isolation contract (auto-gossip off → no outbound) holds. **4. Permissions wire shape was wrong.** `publishPermissions` was shipping the daemon's *roster list* (`{authorized: [{device_id, label}], ts}`) on the `permissions/snapshot` channel — meaningless for the actual feature, which is per-tool agent gates (shell, write_file). Even if the merge had been wired, the incoming data would have been useless. Fix: ship `{tools: {shell, write_file}, ts}` matching the shape `agentPermissions.mergeIncoming` consumes. New `publishPermissionsSnapshot(client, snap)` helper lets the `setBroadcaster` callback ship a pre-formed snapshot without re-reading config from disk on every mutation. Prompts had the same problem at lower stakes — `publishPrompts` was lossy-mapping each prompt to `{id, label, body}`, dropping `tools`, `user_prompt`, and `updated_at`. Now ships the full `Prompt` shape so `agentPrompts.mergeIncoming` can do per-id LWW correctly. **5. Inbound snapshots were logged, not merged.** The `subscribePermissions` / `subscribePrompts` hooks fired `appendDiag("info", "permissions snapshot from ...: N entries")` and stopped. The actual merge into `agentPermissions` / `agentPrompts` (which is what makes a peer's edit visible locally) was never called. Fix: hooks now call `agentPermissions.mergeIncoming(snap.tools, activeNetworkId)` / `agentPrompts.mergeIncoming(snap.prompts, activeNetworkId)` and log only when the merge actually changed something. Gated on `autoGossipEnabled` (isolation contract: when gossip is off, peer pressure can't mutate our policy). New `activeConfigNetworkId` field tracks the LLM-side config id (distinct from `this.network` which is the wire-level `network_id`) so the merge scopes correctly — a snapshot arriving on network A doesn't accidentally overwrite network B's saved policy. **6. Auto-gossip toggle reset to false every launch.** `setAutoGossip` updated an in-memory `autoGossipEnabled = false` field; the UI binds to `active?.auto_gossip` from config (so the toggle visually reverted on every `reloadFromConfig`); the toggle was never persisted. The hydration on `start()` was missing too — even users who'd previously enabled gossip saw it off after restart. Fix: hydrate `autoGossipEnabled` from `activeNetwork(cfg) ?.auto_gossip ?? true` on start (matches the legacy default). `setAutoGossip` persists via `updateNetwork(active.id, { auto_gossip })`. Toggle now sticks across restarts. **7. No periodic refresh + no late-joiner replay.** The daemon's typed channels don't replay past publishes — a peer who handshakes 30s after our initial publish sees an empty `peer.catalog`, no prompts, no permissions until our next local mutation. The legacy client ran a 60s catalog refresh tick + a once-per-newly-active-peer catch-up broadcast; both were missing. Fix: 60s `setInterval` re-publishing catalog (+ gossip-gated perms/prompts). A `shipCatchUpGossipToNewlyActive()` hook fires from `reconcile()` whenever the peer snapshot changes — newly active peers get a one-shot catch-up broadcast, tracked in a `gossipedOnceTo` set that prunes stale entries (so a flap active → shelved → active gets the catch-up again). Initial peers (active at start time) get seeded into `gossipedOnceTo` so the initial broadcast on `start()` isn't duplicated by the first `reconcile()`. **8. `noteCatalogChanged` fired one publish per mutation.** App-side bulk operations (folder move-N-files, multi-rename) each call `refreshConversations()` which calls `noteCatalogChanged()`. Without debounce, a 20-file move = 20 catalog broadcasts. Fix: 500ms `setTimeout` coalesce in `noteCatalogChanged` — same shape as the legacy client. Single broadcast at the trailing edge of the burst. --- Files: - `src/mesh-daemon.svelte.ts`: +368 / -37. New helper (`peerCapabilitiesFromAdvert`), pack/unpack wiring on `pushCapabilities`/`daemonPeerToEntry`, `lastLocalCapabilities` cache feeding `localCapabilitiesForHandler`, broadcaster wiring + release, inbound merge hooks, `activeConfigNetworkId` field, autoGossipEnabled hydration + persistence, periodic refresh interval, catch-up gossip path, catalog debounce. - `src/mesh-gossip.ts`: +68 / -36. Fixed permissions wire shape (`{tools}` not roster), full Prompt[] in prompts wire, `publishPermissionsSnapshot` / `publishPromptsSnapshot` variants for `setBroadcaster` callers, dropped the obsolete roster-list flow. **Validation:** - `pnpm run check`: 164 files, 0 errors, 0 warnings. - `pnpm run build`: clean. - Rust unchanged — Tauri build env (gdk-3.0) isn't installed in the sandbox so `cargo check` can't run; no `.rs` files touched. https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk Co-authored-by: Claude <noreply@anthropic.com>
6 tasks
mrjeeves
added a commit
that referenced
this pull request
May 28, 2026
…on (#207) The migration off Trystero onto the standalone myownmesh daemon (PRs #201 / #203 / #204 / #205 / #206) shipped the code but left every doc still describing the world before the move: - README claimed mesh discovery went "via Trystero over public Nostr relays" and that agent permissions persisted under `Config.agent_permissions.by_device[<device_id>]`. - ARCHITECTURE.md's mesh-module section described `mesh-client.svelte.ts` (deleted), Trystero room ownership (gone), and a TS module table that didn't list any of the files Phase C–D actually shipped (`mesh-daemon.svelte.ts`, `mesh-gossip.ts`, `mesh-inference.ts`, `mesh-file.ts`, `mesh-move.ts`, `mesh-transcribe.ts`, `mesh-governance.ts`). - CONNECTION-ENGINE.md was a 535-line spec for the 4-layer connection engine that no longer lives in this repo — every paragraph referenced `src/mesh-client.svelte.ts` or `mesh-scheduler-worker.ts`, neither of which exists. - DOCS.md's Cloud Mesh section walked the user through Trystero rooms, the legacy on-the-wire `MeshMessage` JSON envelope (`infer_request` / `infer_chunk` / `move_offer` / `file_offer`), and a config example missing every field the per-network schema gained (`label`, `kind`, `topology`, `auto_approve`, `auto_gossip`, `agent_permissions`, `prompts`). - PROGRESS.md was a historical bug-fix doc for a Trystero subscription-state quirk that no longer applies — the engine isn't here anymore. What this commit changes: **README.md**: replace Trystero claim with the bundled `myownmesh` daemon model; correct the agent-permissions storage path to the per-network shape (`Config.cloud_mesh.networks[*]. agent_permissions`) and mention the `auto_gossip` gate. **ARCHITECTURE.md**: rewrite the one-picture diagram to show the daemon sidecar alongside Ollama; rewrite the mesh intro paragraph; rewrite the `mesh/` Rust module row to describe `daemon.rs`, `daemon_commands.rs`, the detect-and-share socket order, and the relationship to `myownmesh_core`; rewrite the TS module table to list every `mesh-*.ts` file actually in the tree with its current role; refresh the CloudMesh sub-tab inventory (Status / Settings / Connections / Graph / Governance / Activity / HTTP); refresh the persistence section to show `daemon.sock` + the per-network config layout. **CONNECTION-ENGINE.md**: rewrite as a short pointer. The 4-layer engine + 7-tier reconnect ladder live in MyOwnMesh now; this doc explains what the LLM still owns on top (the layer-4 LLM-specific protocol), how the LLM talks to the daemon (detect-and-share IPC), and lists the LLM-side RPC methods + typed channels currently in use (`infer`, `transcribe`, `file_offer` / `file_send` + `file_chunks/<id>`, `session_*` / `move_*`, `catalog/announce`, `permissions/snapshot`, `prompts/snapshot`). **DOCS.md Cloud Mesh section**: replace the Trystero transport paragraph with the daemon's detect-and-share model; refresh every What-the-mesh-does-for-you row to match current behavior (click-to-open, click-through Pull, file transfer wire shape, permissions+prompts gossip with the auto_gossip gate, Graph view, Governance view, no Phase-1/Phase-2 split); replace the JSON-over-data-channel wire-protocol box with the daemon RPC + typed-channel surface; refresh the example config to include `label`, `kind`, `topology`, `auto_approve`, `auto_gossip`, `agent_permissions`, `prompts`. **PROGRESS.md**: deleted. The Trystero subscription-state bug it documents doesn't apply post-daemon. Two `// see PROGRESS.md` breadcrumbs in `src-tauri/src/asr/mod.rs` and `src-tauri/src/diarize/cluster.rs` updated to free-standing explanations. Validation: - `pnpm run check`: 164 files, 0 errors, 0 warnings. - `grep -rn "Trystero\|trystero\|mesh-client\.svelte" --include="*.md" .` returns nothing. - `grep -rn "PROGRESS.md" .` returns nothing. https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk Co-authored-by: Claude <noreply@anthropic.com>
8 tasks
mrjeeves
added a commit
that referenced
this pull request
May 28, 2026
* mesh: isolate daemon config under ~/.myownllm/.myownmesh/ subdir
## Symptom
Saving a network in MyOwnLLM (Settings → Networks → Status →
"+ Add network" → Save & activate) "worked" within a session
but every LLM setting was reset to defaults on the next launch —
providers, active family, all saved networks, accepting policy,
agent permissions, prompts, auto-gossip toggles, everything.
The network the user just saved was also gone from the LLM's UI.
## Root cause
The bundled `myownmesh serve` daemon and the LLM both wrote to
`~/.myownllm/config.json` with different schemas:
- LLM Config: `{providers, active_family, cloud_mesh.networks, ...}`
- daemon's `myownmesh_core::MeshConfig`: `{version, identity_path,
auto_update, auto_cleanup, daemon, networks}` (note: top-level
`networks`, no `cloud_mesh` wrapper)
PR #203's `daemon.rs` set `MYOWNMESH_HOME=~/.myownllm` so the
daemon shared the LLM's directory for identity + rosters. That
sharing worked for those files but pointed the daemon's
`config.json` at the same path as the LLM's, where they have
incompatible schemas.
`MeshConfig::load()` doesn't use `#[serde(deny_unknown_fields)]`,
so the LLM's keys deserialized silently as unknown-and-ignored.
Then any `NetworkAdd` IPC call triggered:
```
MeshConfig::load() // strips every LLM-only key on parse
.networks.push(new)
.save() // writes only MeshConfig's fields back
```
Result: `~/.myownllm/config.json` flips from LLM shape to daemon
shape mid-session, wiping providers / active_family / cloud_mesh /
prompts / permissions / auto_gossip / accepting / etc. The
in-memory `_cached` config keeps the LLM shape so the user sees
no damage until restart; on next load, `loadConfig` reads the
daemon shape, `mergeDefaults` fills in LLM defaults from
`DEFAULT_CONFIG`, and the user's settings appear as factory
defaults.
`cloud_mesh.networks` ends up empty (the daemon's top-level
`networks` field isn't where the LLM looks), so the network the
user just saved disappears from the saved-networks list.
## Fix
Isolate the daemon's config + updates under a dedicated
subdirectory: `~/.myownllm/.myownmesh/`. The LLM's
`~/.myownllm/config.json` is no longer in the daemon's
`MYOWNMESH_HOME` tree, so the daemon's persist path can't reach
it. Identity, rosters, and signed governance states get moved
into the subdir on first launch — losing identity continuity
would orphan every user's Device ID and force every paired peer
through a fresh approval round, which would be worse than the
bug.
### New file: `src-tauri/src/mesh/migration.rs`
`migrate_daemon_state_into_subdir(llm_dir, daemon_home)`:
- Moves `.secrets/identity.json` — the ed25519 keypair. This
is the critical one; everything else is recoverable but a new
pubkey would split the user's mesh across every peer.
- Moves `mesh/rosters/*.json` — per-network approved peers.
Without these, every paired peer would re-prompt for approval
on next handshake.
- Moves `mesh/states/*.json` — signed governance-state files
for closed networks. Required for closed-network identity
continuity (the founder's signed log can't be regenerated).
Idempotence: checks `identity_dst.exists()` at the top and
no-ops on subsequent runs. Destination collisions (e.g. an
interrupted earlier run) leave both files in place and log to
stderr — the migration doesn't choose between them.
Cross-filesystem moves handled via copy + remove (rare under a
single home dir but cheap to handle correctly).
Four unit tests cover: happy path, idempotence, fresh install
(no source files), destination collision.
### `src-tauri/src/main.rs`
The setup hook runs the new migration before setting
`MYOWNMESH_HOME`, then points the env var at the subdir. Comments
spell out the bug we're fixing so the next person who touches
this path doesn't accidentally regress it.
### `src-tauri/src/mesh/daemon.rs`
- `socket_for_mode(OwnLlm)` now returns
`~/.myownllm/.myownmesh/daemon.sock` (matching the daemon's
`data_dir()/daemon.sock` calculation under the new
`MYOWNMESH_HOME`).
- Spawn path's `home` calculation uses
`~/.myownllm/.myownmesh/`.
- `Shared` mode untouched — that's the MyOwnMesh GUI's own
`~/.myownmesh/daemon.sock` and lives outside the LLM's tree.
- Module-level + enum-variant doc updated to match.
### `src/config.ts`
Recovery for users who already hit the bug — their `config.json`
is currently the daemon's shape. `salvageDaemonShapeLeakage`
runs at the top of `mergeDefaults`:
1. Detects daemon shape via the presence of a top-level
`networks` array whose entries all have `id` + `network_id`,
AND the absence of populated `cloud_mesh.networks`.
2. Converts each daemon `NetworkConfig` to LLM `NetworkConfig`
shape (signaling.servers → signaling_servers; stun_servers
urls flattened; turn_servers' first url + auth lifted). LLM-only
fields (`accepting`, `agent_permissions`, `prompts`,
`auto_gossip`) default via `mergeNetwork`.
3. Strips daemon-only top-level keys (`version`,
`identity_path`, `daemon`, `networks`) so subsequent saves
are clean LLM shape.
`auto_update` and `auto_cleanup` are shared between both
schemas with compatible field names — left alone; the LLM's
mergeDefaults handles them with its own per-field merge.
No-op when nothing matches the detection signature. Fresh
installs + uncorrupted configs are untouched.
### `src-tauri/Cargo.toml`
Added `tempfile = "3"` under `[dev-dependencies]` for the
migration tests' `tempdir()` fixture.
## Why not patch myownmesh-core to preserve unknown fields?
That would also work, but it requires a MyOwnMesh release +
`.myownmesh-rev` bump and ages slowly through anyone running a
shared daemon that pre-dates the patch. The subdir isolation is
a self-contained fix on the LLM side and prevents the same class
of bug from recurring if the daemon's schema grows fields in the
future.
## Validation
- [x] `pnpm run check`: 164 files, 0 errors, 0 warnings.
- [x] `pnpm run build`: clean.
- [ ] `cargo test --bins -p myownllm` of the new migration
module's four unit tests. (Sandbox lacks gdk-3.0 so the
full crate doesn't build here; please verify locally.)
- [ ] Fresh install of the new build, no existing data: app
launches, no `~/.myownllm/.myownmesh/` until first daemon
spawn, then it appears with identity + (after first network
save) `networks/` + `config.json` inside. Parent
`~/.myownllm/config.json` untouched by the daemon.
- [ ] Upgrade from a pre-fix build with existing identity +
rosters: `.myownmesh/.secrets/identity.json` and
`.myownmesh/mesh/rosters/*.json` populated after first
launch; old locations gone. Same pubkey + same peer
approvals as before.
- [ ] Upgrade from a pre-fix build that already hit the bug
(daemon-shape `config.json`): saved networks recovered into
`cloud_mesh.networks`, daemon-only top-level keys stripped.
Providers / active_family reset to defaults (irrecoverable —
the daemon's write destroyed them; the salvage only restores
what the daemon kept).
- [ ] Save & activate a network: `~/.myownllm/config.json`
retains full LLM shape; daemon's writes land at
`~/.myownllm/.myownmesh/config.json` only.
- [ ] Switch active networks: both files update independently;
LLM's `cloud_mesh.active_network_id` and daemon's
`networks` list stay in sync via reconcile.
https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk
* fmt: rustfmt collapse a long write() call in migration tests
CI on macos-14 / ubuntu-latest / windows-latest failed `cargo fmt
--check` against `src-tauri/src/mesh/migration.rs:270` — one
test setup call was over the line-width limit and rustfmt wanted
it across three lines. No semantic change.
---------
Co-authored-by: Claude <noreply@anthropic.com>
6 tasks
mrjeeves
added a commit
that referenced
this pull request
May 28, 2026
## Two regressions from the daemon migration ### Sidebar peer view: flat instead of folder-tree The legacy `mesh-client.svelte.ts::refreshLocalCatalog` included `path` on every `CatalogEntry` it shipped, so receivers' Sidebar `buildRemoteTree` could reproduce the host's folder structure (see `src/ui/Sidebar.svelte:337`, which still reads `e.path` and builds nested `RemoteNode`s). `mesh-gossip.ts::snapshotLocalCatalog` shipped in PR #203 dropped the `path` field on the map: return conversations.map((c) => ({ guid: c.id, title: c.title, mode: c.mode, updated_at: c.updated_at, })) as CatalogEntry[]; // ← no path Every receiver saw every remote conversation at root. A peer's `Work/Projects/Q4 planning` showed up as a flat `Q4 planning` row alongside top-level conversations — visually indistinguishable from a root-level chat. Reported by the user as "didn't folders used to transfer over [in the sidebar]?". Fix: thread `c.path` through the snapshot's map. Empty (root) omitted from the wire payload via the spread-only-when-truthy trick, matching the legacy `|| undefined` shape. ### Pull: pulled conversations land at root `pullConversation` was calling `saveConversation(conversation)` with no `targetFolder` argument. The conversation JSON itself doesn't carry path (that's a filesystem fact, not conversation content), so the receiver had no way to know where on the source the conversation lived → every Pull flattened to root regardless of where the conversation was on the source. Push (`moveConversation`) was already correct: it looks up `c.path` from the local listing and ships it as `move_take.source_folder`, which the receiver's `handleMoveTake` forwards into `saveConversation(conversation, source_folder)`. The Pull path was the missing half. Fix: `pullConversation` looks the source folder up from the cached catalog of the source peer (now that catalog gossip carries path again — without the gossip fix above, the catalog entry's `path` would still be undefined). Passes it as the second arg to `saveConversation`, which calls `pathFor` → `mkdir({ recursive: true })` to create intermediate folders. MoveClient gains a read-only `peers` accessor (with the minimum shape needed for the lookup: `device_pubkey`, `peer_id`, `catalog[].guid`, `catalog[].path`). Falls back to root when the catalog hasn't caught up — same disposition as a legitimate root-hosted conversation. ## Validation - `pnpm run check`: 164 files, 0 errors, 0 warnings. - `pnpm run build`: clean. - Two devices: push `Foo/Bar/baz` from A to B → arrives at `Foo/Bar/baz` on B (already worked before this PR, sanity check). - Two devices: pull `Foo/Bar/baz` from A → arrives at `Foo/Bar/baz` locally (was landing at root before). - Two devices: A has `Work/Q4 planning` chat; B's sidebar Network section nests `Work` as a folder containing `Q4 planning` (was a flat row before). https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk Co-authored-by: Claude <noreply@anthropic.com>
mrjeeves
pushed a commit
that referenced
this pull request
May 29, 2026
The "Verify frontend bundle" step grepped the Vite output for a `trystero-patch` marker to confirm patches/@trystero-p2p__core@0.24.0 was applied. That patch — and the @trystero-p2p/core dependency it patched — were removed when the connection engine moved into the myownmesh daemon (#203), so the marker can never appear in the bundle and the guard fails every release on all five platforms. Remove the obsolete guard. The remaining checks (dist/index.html present, entry rewritten, non-empty assets, no Svelte SSR-runtime leak) are still valid and stay. https://claude.ai/code/session_017UZ6AKBqV2ae2E6XbyoAgq
mrjeeves
added a commit
that referenced
this pull request
May 29, 2026
…ify (#213) The "Verify frontend bundle" step grepped the Vite output for a `trystero-patch` marker to confirm patches/@trystero-p2p__core@0.24.0 was applied. That patch — and the @trystero-p2p/core dependency it patched — were removed when the connection engine moved into the myownmesh daemon (#203), so the marker can never appear in the bundle and the guard fails every release on all five platforms. Remove the obsolete guard. The remaining checks (dist/index.html present, entry rewritten, non-empty assets, no Svelte SSR-runtime leak) are still valid and stay. https://claude.ai/code/session_017UZ6AKBqV2ae2E6XbyoAgq Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status
Phase B landed, Phase C–D ongoing on this branch. Opening now so review of the daemon plumbing can start in parallel with the frontend migration.
What this branch is doing
Migrating MyOwnLLM off Trystero onto the
myownmeshdaemon (the MyOwnMesh PR #16 just merged). End state:mesh://event, exposes every daemon IPC op as a Tauri command.Phase progress
RpcRegister/RpcCall/ChannelSubscribe/etc. to the daemon's control socket so non-embedding clients can use the full engine API.sendInferRequest→ daemon RPCinfermethod).file_offer+ typed channelfile/chunks).mesh-client.svelte.tsTrystero engine (~7200 LoC),mesh-protocol.ts,mesh-scheduler-worker.ts, the Trystero patch; removetrystero+@trystero-p2p/corefrompackage.json; remove__TRYSTERO_STRATEGY__fromvite.config.ts.Phase B details (this commit)
src-tauri/src/mesh/daemon.rs— daemon lifecycle + control-protocol client. Detect-and-share resolution: tries~/.myownmesh/daemon.sock(shared with MyOwnMesh GUI if running), then~/.myownllm/daemon.sock(any existing LLM-spawned daemon), then spawnsmyownmesh servewithMYOWNMESH_HOME=~/.myownllmso existing users keep their pubkey + roster + networks. Binary discovery:MYOWNLLM_MESH_BINenv →MYOWNMESH_BINenv →$PATH→ workspace dev fallbacks.DaemonChildholds the spawned process;RunEvent::Exitdrops it explicitly for deterministic teardown.src-tauri/src/mesh/daemon_commands.rs— 30 Tauri commands covering the full daemon IPC surface:mesh_daemon_status(returns ipc_client_id + daemon_socket + daemon_mode)._identity_show,_set_label,_network_id_generate,_network_id_normalize._config_show,_networks_list,_network_add,_network_remove,_topology_set._peers_list,_roster_list,_roster_approve,_roster_remove._rpc_register,_rpc_unregister,_rpc_respond,_rpc_stream_chunk,_rpc_stream_end,_rpc_call,_rpc_call_stream._channel_subscribe,_channel_unsubscribe,_channel_send_to,_channel_send_all._capabilities_set.src-tauri/src/main.rssetup()— spawns the daemon, subscribes to events, plumbs eachServerOutframe into Tauri's event emitter asmesh://event. RegistersArc<MeshDaemon>viaapp.manage()so commands access the live client. HooksRunEvent::Exitto drop the daemon child cleanly.Cargo.toml — bumped
myownmesh-coregit pin to93b53628(the merged PR #16). Addedinterprocess,parking_lot.Test plan
cargo check --bins— passes.cargo test --bins --no-run— builds.~/.myownllm, app boots normally. (Pending Phase C wiring so frontend actually uses the new commands.)The legacy
mesh-client.svelte.ts(Trystero) is unchanged — the frontend still uses it. Phase C swaps callers feature-by-feature; Phase D removes the old code + deps. Merging this PR before all phases are done would leave the daemon plumbing built but unused — wait for the full migration before merge.https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
Generated by Claude Code