Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 22:57
· 899 commits to main since this release

v0.4.0 — the Mac app, Sessions, trust with drive

The macOS app

  • Vox.app is a native Mac app for Apple Silicon, macOS 13 and later. It is a client of the same daemon the vox command uses: rooms, nodes, the keyring, services and files, with a sidebar, a timeline, an inspector and a status bar.
  • Sessions in the app. A room lists its Sessions; each has its own timeline, and from it you can approve a request, interrupt or stop the session.
  • Trust from the app grants read, or read + drive, and says which.
  • Trust offers in the app. A node offered to your keyring is listed under "needs you" in the sidebar and the menu bar item. Its own view shows its fingerprint and art, the same sentence the TUI and vox trust offers give (why it is offered and who in your keyring trusts it), and its rooms. From there you trust it, giving a name, read or read + drive, and your passphrase, or dismiss it, which it is not told.
  • Share to a Vox room from any Mac app.
  • The daemon can keep running while you are logged in, even with the app closed. Vox asks once, at first run, and registers it as a login item; when macOS wants your approval it says so and goes on.
  • The family LAN's helper runs as a system service installed with the app, in place of sudo vox lan helper. It serves only the person who owns Vox.app, and refuses to run for a bundle whose signature does not check out.
  • An optional menu bar item shows the node, the rooms that need you, services shared to you (with Copy), your own shares (with Stop) and your live tunnels. It is off unless you turn it on.
  • Notifications are grouped by room and never show the message's text: only who wrote, and whether it was to you, urgent, or a file. Clicking one opens the room.
  • The timeline shows what was done to a room in its place among the messages, right after the message it followed.
  • Vox.app's text and controls meet WCAG 2.1 AA contrast, with high-contrast colours when Increase Contrast is on, and a selected sidebar row is a darker blue that passes. View > Bigger (⌘+) and Smaller (⌘−) scale its text up to 2×. The timeline takes the keyboard (View > Focus Timeline, ⌃⌘T, then ↑/↓, Return and Space), and Escape or Space closes a preview opened from it and gives the keyboard back to the timeline. A selected row is marked by more than its colour.

Sessions

  • Each interactive Claude Code, Codex or OpenCode session gets a Session in its room. A headless run gets none. A Session ends only when the session really ends; a resumed one stays open.
  • A Session shows what its session is doing: tool calls with their results, replies, the end of each turn, approvals and questions, and files either way, with the full input and output under Details.
  • A Session is sealed to the members its node trusts with drive. Others see only its label and whether it is open or ended.
  • A member with drive can drive it with vox room session, from the TUI or from the app: type into it, send a slash command, interrupt or stop it, approve or reject a tool call, answer a question, and send it a file. An approval or a question can be answered at the terminal or in Vox; the first answer wins, and the other side says it was answered elsewhere.
  • A room with a request waiting on you is listed under "needs you".
  • A node drives its own Sessions too, as their operator, over its own socket on the machine; no other node can drive them that way.
  • A Session renamed while it runs takes its new name: at once for Codex and OpenCode, and at its next prompt or the end of its turn for Claude Code.
  • Every message from a session carries the session's id and name, and a message can be addressed to one session of a node. A message to a session that has ended is refused, and an urgent message wakes only the session it names.
  • A session says which system its node runs on: its operating system, version and architecture, shown as that node's claim ("says it runs on macOS 26.2 (aarch64)") in vox room post --to, on the TUI's member card and in the app's inspector.
  • vox setup makes one node for each harness installed on the machine and wires its hook. It keeps Codex's app-server running.
  • A session works in the room its start directory is mapped to. vox agent room sets or moves a session's room, and offers to save the mapping.

Sessions in the TUI

  • A room lists its Sessions beside its conversation: General, All, each open Session, and the ended ones apart.
  • A member with drive reads a Session line by line, opens Details, and answers it: a approves, r rejects, a digit answers a question, :answer, :interrupt, :stop, and :share <path> sends the session a file.
  • A Session's name and id are shown as its node's claim: " — name and id as says".

Trust

  • Each keyring entry gives read, or read + drive. vox trust drive <fingerprint> and vox trust read <fingerprint> change it, and vox trust list says which.
  • Every node has an identity passphrase, and an empty one is refused.
  • A change to the keyring asks for the passphrase only when typed at a terminal, never from a file or the environment. Attaching a node, changing a room's retention or renaming a room asks for none.
  • One trust state: a node is in your keyring or not, and untrusting is the only block.
  • A join says at once who reads whom, and which of the members you trust already trust the newcomer.
  • A newcomer to a room you share is offered to your keyring, and the offer waits until you accept it, dismiss it, or the member leaves. A member who grants your node consent is offered too. vox trust offers lists them, with their rooms, why each is offered and who in your keyring already trusts it; vox trust dismiss <fingerprint> dismisses one, silently and for good, unless the member leaves and joins again. In the TUI offers come first under "needs you": Enter accepts (a name, read or read + drive, then your passphrase) and x dismisses.
  • Accepting a newcomer offers you back to it, on its side.
  • A member who leaves and joins again stays reachable: the record of its old leave no longer takes its new address off the room, and it is offered to the other members' keyrings again, as a newcomer is. When you trust a member and your key cannot be sent to it yet, your node says why, once per room, and sends it when it can.
  • An agent's node shows its trust offers in the agent's own session, each with the command that accepts it. Only the operator accepts, with vox trust add typed at a terminal outside the session; the agent never does.

Rooms, files and services

  • Shares are addressed messages with a note. A share to you, or to the whole room, is pulled automatically into files/<room>/, never leaving less than 1 GB free on the disk. The sharer sees who pulled it.
  • When a share expires, the pulled copies are deleted and serving stops.
  • A folder pulled again fetches only what changed. Image shares carry a small thumbnail, and the TUI draws verified images inline.
  • Link cards are fetched by the sender's node.
  • "read by" under your messages, from the read records the node can open.
  • A room has one shared name, set by its creator or an admin; a name that clashes is refused, or shown by room ID.
  • Service addresses travel in one canonical form and are shown readable, part by part. Each kind of service gets ready-to-copy commands, and sharing is one step with a preview of who can reach it.
  • The daemon runs the .vox proxy while a node is attached.
  • vox room get says what happened to a file whose sharing stopped while it was being fetched: that it was withdrawn, or that it stopped partway and who stopped serving it. It no longer says the offer is gone when it isn't.
  • A room never goes past its member cap: every member online agrees before a newcomer is admitted.
  • A member who leaves, joins again and leaves again has left: the second leave reaches every member's running node, which no longer keeps it on the roster (or syncs with it) until a restart.
  • Each node keeps a 14-day record of what it decided, and vox status names recent refusals. The record is sealed at rest under the node's identity, like the keyring, and the TUI and the app read it through the node. A plaintext record an earlier version wrote is sealed at the next unlock.
  • Every member learns of an admitted newcomer at once, and never of one who has already left.
  • An admin certificate that has expired grants nothing from the moment it expires.
  • A room link that can't be used is refused at once, saying what is wrong with it.
  • A node always says when its retention file asks to keep messages longer than the room does, since the room's shorter retention wins.

Install and update

  • install.sh installs Vox.app and links vox into it.
  • On macOS, Vox now needs Apple Silicon and macOS 13 or later. There is no Intel build. On an Intel Mac, or before macOS 13, install.sh says the Mac is not supported and downloads nothing. Linux is unchanged.
  • vox update replaces the whole app and restarts the daemon onto the new version.
  • A vox daemon started while another is stopping waits for it, then serves.

Times

  • Every time Vox keeps, sends, compares, orders or expires is in milliseconds, and is rounded only where it is shown to a person. A change made to a room just after a post, in the same second, now reads after the post.

Network and anchors

  • A restarted anchor that does not know a node yet is asked again, never reported as "authenticator invalid".
  • A restarting anchor no longer turns its own members away. A daemon now accepts connections only once its first node is attached, so a dial that arrives while it starts waits to be answered.
  • A daemon on a busy Mac no longer freezes looking up its default route, so members are back promptly after their anchor restarts.
  • A default-route lookup that gets no answer is no longer taken as a lost route, so a busy Mac no longer reports a network change ("→ none") that did not happen and redials every peer.
  • A failed dial says which of three things happened: refused ("nothing at answers as "), answered by something that could not prove it is that node, or not answered within 5 s. A slow anchor no longer reads as an impostor.
  • A first connection behind a NAT no longer waits 10 s on a direct address that cannot answer: while its anchor connection is being made, it waits for that helper too.
  • A connection a node drops is closed at once, so after a path upgrade both ends settle on the same connection, and a forwarded service no longer stops answering.
  • A dial begun by a node that has since detached is refused before it presents any identity. It used to finish in the name of the old attach, so the peer took it for a newer process of that node and closed the current connections, cutting vox up's live session about half a second after a forward opened.
  • A dead direct path to a member that is in use fails over in about 9 s, not 30: a connection that is carrying traffic is probed once it has been silent for 3 s and closed after 8 s with no answer, so a forward over it moves to another path. A quiet connection is never probed, so an idle room costs nothing more.
  • A network change no longer cuts a connection to a peer on the same machine.
  • A join whose member closed the connection during the exchange says so, and why: for example, that the member said it was stopping.
  • A restarted daemon reaches the members of a synced room that has an anchor, so what arrived while it was down is announced.
  • A key generation retired before a member took it stays owed to that member until taken.
  • A node takes a member's key from the room's log only when its owner trusts that member.
  • A Vox node inside a VM no longer throttles the network: a receiver that overflows its own socket says so, and the sender doesn't take the loss for congestion.

The TUI

  • Three unread levels, with a key to the next room addressed to you, and rooms grouped by what they need from you.
  • One-level quote replies, with a jump to the original; @alias in the composer, with lookalike aliases told apart.
  • Notifications grouped by room, with the text hidden.
  • A TUI whose terminal has gone exits, and SIGHUP or SIGTERM stops it.
  • Sending in a long room is quick again: the TUI redraws only the rows a send changes. In a 12,000-message room, ten sends show in about 0.15 s, not 1–2 s.

Words

  • What Vox prints says node, never profile, and uses one set of words throughout: node, fingerprint, keyring, alias, room, room link, passphrase, service, address, file, trust, untrust.
  • Help and refusals name no design document.
  • A fingerprint is shown grouped, with its fingerprint art.
  • Making a node says there is no backup: if the machine is lost, so is the node.
  • A post over the size limit says how long it is, what it holds and how much to cut.

Work items delivered

  • #218 R41a: A Vox node inside a VM does not throttle the network, in every node role
  • #366 V030-30: A room never exceeds its member cap: every online member agrees before a newcomer is admitted
  • #427 R41b: R41's throughput proof always measures through its emulated link
  • #428 V030-50: What vox prints says node, never profile
  • #435 VoxClient: the daemon client in vox-ffi, core surface
  • #436 VoxClient: services, shares, status and decision record
  • #437 Vox.app skeleton: one node, attach on launch, detach on quit, daemon sentences
  • #438 The daemon as an SMAppService login item
  • #439 The family LAN helper as an SMAppService launchd daemon
  • #440 The main window: sidebar, timeline, inspector, status bar
  • #441 The timeline: cards, read by, pulled by, retention and late rows
  • #443 The keyring view, and the opt-in Keychain passphrase
  • #444 The services view: copy box and one-step sharing
  • #445 The decision record timeline
  • #446 Command palette, keyboard map and menus
  • #447 The opt-in menu bar extra
  • #448 Local notifications, grouped by room, text hidden
  • #449 Quick Look, share extension, Services item and drag-and-drop
  • #450 The app uses the token file and the accessibility settings
  • #451 Vox.app bundle, Apple Silicon only, signed and notarized in release.yml
  • #452 install.sh installs Vox.app and links vox into it
  • #453 vox update swaps the whole bundle, with rollback
  • #454 The app proof suite: XCUITest against the signed app and a real daemon
  • #455 Manual checks listed and run before tagging
  • #456 Screenshots from demo data only
  • #469 Vox says only node, fingerprint, keyring, alias, room, room link, passphrase, service, address, file, trust, untrust
  • #470 The TUI acts only as the node it was opened with
  • #471 Every access change says its effect before and reports it after
  • #472 A fingerprint is shown grouped with its fingerprint art
  • #473 One trust state; untrust is the only block
  • #474 @alias in the composer, and lookalike aliases told apart
  • #475 Trust and compare are offered where they matter
  • #476 A join says which of my trusted members trust the newcomer
  • #477 Onboarding says a lost machine means a new node
  • #478 The keyring window is visible where the person works
  • #479 A room has one shared name, set by its creator or an admin
  • #480 Room-name clashes are refused, or shown by room ID
  • #481 A join shows who reads whom at once
  • #482 An unread message says where it is
  • #483 Retention changes appear in the timeline and the header
  • #484 Three unread levels and a key to the next room addressed to you
  • #485 One-level quote replies with jump to the original
  • #486 TUI notifications grouped by room with text hidden
  • #487 Service addresses travel in canonical form; both forms resolve
  • #488 Addresses are shown readable, part by part
  • #489 A share records its detected kind
  • #490 Ready-to-copy commands per service kind
  • #491 Sharing is one step with an audience preview and warnings
  • #492 The daemon runs the .vox proxy while a node is attached
  • #493 A share is an addressed message with a note
  • #494 The daemon serves a share until the message ends
  • #495 Shares addressed to me or to no one are pulled automatically into files//
  • #496 Expiry deletes pulled copies and stops serving
  • #497 An agent gets the note and the local path; --urgent wakes it
  • #498 The sharer sees who pulled it
  • #499 A folder re-pull fetches only changes and resumes
  • #500 Image shares carry a 16 KB thumbnail and BlurHash
  • #501 Link cards fetched by the sender's node
  • #502 The TUI shows verified images inline
  • #503 Read records: a batched content entry in the reader's feed
  • #504 A read record is posted when a message is shown or drained
  • #505 "read by" from the records the node can open
  • #506 Each node keeps a 14-day local decision record
  • #507 vox status names recent refusals; the TUI shows the record
  • #508 One token file feeds the TUI and the Swift catalogue
  • #509 The TUI looks and behaves to the ADR-028 rules
  • #510 The Vox mark
  • #511 The TUI renders the window regions and groups rooms by need
  • #514 The macOS app meets ADR-028 (delivered by ADR-014)
  • #515 Vox.app signed, installed and updated with vox (delivered by ADR-014)
  • #516 The user manual describes v0.4.0
  • #517 V030-51: a joiner whose room has not synced never reaches a member only a relay reaches
  • #518 V030-52: vox connect --help says what a piped stdin does
  • #519 V030-53: a node takes a member's key by the room's log only when its owner trusts that member
  • #520 V030-54: every member learns of a newcomer the moment one member lets it in
  • #522 Every node has a passphrase
  • #523 Attaching does not open the keyring window
  • #524 A passphrase is typed outside an agent's session
  • #525 Keyring entries carry read or read + drive
  • #526 A join offers the newcomer, and the offer waits
  • #527 Accepting asks read or read + drive, and offers back
  • #528 An agent's node is offered in its harness
  • #538 A Session per interactive harness session, ended only by a real end
  • #539 A Session's label: alias, session name, short id
  • #540 A Claude Code session's activity is mirrored into its Session
  • #541 A Codex session's activity is mirrored into its Session
  • #542 An OpenCode session's activity is mirrored into its Session
  • #543 A Session is sealed to members with drive
  • #544 Typing, interrupting, stopping and slash commands reach exactly that session
  • #545 Approvals and questions answered from either side, first answer wins
  • #546 Files and images both ways through a Session
  • #547 Addressing one session, and refusing an ended one
  • #548 An urgent message wakes only the session it names
  • #549 Every message from a harness session carries its id and name
  • #550 The room map binds a start path to a room
  • #551 vox agent room sets or moves a session's room
  • #552 vox setup creates a node per installed harness
  • #553 Sessions in the TUI
  • #554 Sessions in the macOS app
  • #555 A retention change does not ask for the passphrase
  • #556 The TUI has no lanes view
  • #557 A TUI whose terminal has gone exits, and SIGHUP and SIGTERM still stop it
  • #558 V030-55: a vox daemon started while another stops waits for it, then serves
  • #559 V030-56: a restarted daemon reaches the members of a synced room that has an anchor
  • #560 V030-57: a key generation retired before a reader took it stays owed to that reader
  • #562 Every time Vox keeps is in milliseconds; round only for display
  • #563 The decision record is sealed at rest, like the keyring
  • #564 A second leave after a rejoin reaches every member's running node
  • #565 A connection a node drops is closed, never left open
  • #566 A cut room get says what happened to the file
  • #567 A dial begun by a detached node never passes for a newer process
  • #568 ADR-020 §4.9b: hello carries the node's os, os_version and arch
  • #569 Defect: a Session renamed mid-session keeps its old name (Codex, OpenCode; every harness until it posts)