Skip to content

Combined test branch: batch 2 plus all open PRs (2026-10-05) - #2075

Draft
joepio wants to merge 84 commits into
developfrom
combined/2026-10-05
Draft

joepio wants to merge 84 commits into
developfrom
combined/2026-10-05

Conversation

@joepio

@joepio joepio commented Oct 5, 2026

Copy link
Copy Markdown
Member

Before: about fifteen open PRs, each on its own branch.

After: one branch with all of them, for testing on one machine. It is batch 2 plus #1870, #1969, #2056, #2057, #2058, #2065, #2066, #2068, #2070, #2071, #2073 and #2074, merged onto develop.

How: PR heads were merged one by one; conflicts were only in changelogs, catalogs (re-extracted with wuchale), imports and tableAggregates.test.ts (kept both). Not included: the plugin stack (#1702 to #1788), #2045, #2072 and the Michiel PRs with stacked bases.


Generated by Claude Code

`@tomic/mcp` (browser/mcp) is a stdio MCP server for Claude Code, Claude
Desktop, Cursor and other clients. It runs locally and signs every edit
with the user's own Agent key, so writes are ordinary signed commits.
Tools: list_drives, get_resource (documents and meetings include their
text), search, semantic_search, query, get_user_classes, get_schema,
create_resource, edit_resource and delete_resource (never a whole drive).

The data verbs behind those tools now live in @tomic/lib
(assistant-tools.ts), and the in-app assistant calls the same functions,
so there is one implementation behind both surfaces. The compact JSON-AD
dialect, short subject refs and class helpers move from the data-browser
to @tomic/lib for the same reason. Reads no longer include the genesis
certificate, which is long and tells a model nothing.

This is steps 1 and 2 of planning/mcp-endpoint.md; a hosted endpoint
with OAuth (for claude.ai connectors) is not part of this change.
atomic-mcp now makes a key on the machine it runs on and prints a link.
In the app (/app/connect-agent) the person picks which drives it may
reach and whether it may edit, then clicks Allow. Account settings lists
connected apps with a Revoke button.

Only an agent may edit its own Agent resource, so the grant is not
recorded there: the ACLs on the shared drives are the record. The app
adds the key to read (and write), the MCP finds its access by searching
for resources that list it, and revoking removes it everywhere, including
from what it created. The app remembers which apps were connected on the
private drive, so settings can list them.

ATOMIC_AGENT_SECRET still works for scripts and CI.
Any app that makes its own key (a CLI, a script, the MCP server) can now
ask for access the same way: connectAgentUrl builds the approval link,
optionally asking for edit rights or specific resources, waitForGrant
polls until the person clicks Allow, and publishAgentName sets the name
shown under Connected apps. The approval page honours the write and
target hints as preselections; the person still decides.

The MCP server now uses these instead of its own copies.
atomic-cli connect makes a key on this machine, prints the approval link
(/app/connect-agent), waits for Allow and writes the config. Without a
config the CLI now points there instead of asking for the secret.

ad-generate connect does the same for the codegen CLI, so private
ontologies no longer need an agent secret in atomic.config.json, which
often ends up in a repository. The key file helper moves from the MCP
server into @tomic/lib/node (loadOrCreateLocalAgent), shared by both.

Also adds the new screens' strings to the translation catalogs.
POST /mcp (Streamable HTTP) for clients that cannot run a local process. The
node is the OAuth 2.1 authorization server (dynamic client registration,
PKCE S256, RFC 9728 and 8414 metadata) with stateless signed tokens derived
from the node key. The person approves in the app at /app/authorize-mcp, which
gives a fresh issued agent read rights on the drives they pick, so revoking is
removing it under Connected apps. The node never signs as the person. Tools:
list_drives, get_resource (with document text) and search.
Signup, the Share dialog and the invite accept card each had their own
name and picture form. They now share ProfileForm. The invite's
'Agent created!' dialog reuses the signup SecretStep, so the secret is
shown and confirmed the same way everywhere.
…ate cargo build-dir

scripts/run-branch.sh builds and serves any branch in its own worktree and
target dir (optionally behind a cloudflared https tunnel for phones and
tablets). build.rs now decides whether to rebuild the frontend from a content
hash instead of mtimes, so a fresh checkout or branch switch with identical
sources no longer pays minutes for a rebuild, and prints a line on the
terminal before the silent step. The wasm-pack cargo gets a private
CARGO_BUILD_BUILD_DIR: a global build-dir is shared by every cargo and one
running cargo run made every other build block silently at 0% CPU.
Portal consent and the integration proxy need to know which account an
agent belongs to. The only link so far was a sync enrollment, which needs
a drive and a paid plan or an invite, so a Free account was never
recognised. atomic-saas now links an agent to an account without a drive
(#138); this is the app's side of it.

Once the identity reconcile gate has settled on the identity the account
uses, it links that identity in the background, next to the device
directory: GET /api/agent-link, and when the account has no link or
another agent's, a challenge from POST /api/agent-link/challenge, signed
with createAuthentication like the enrollment proof, sent back to
POST /api/agent-link. One attempt per account and identity per session,
and it never throws.

Keys are compared in any spelling (both base64 alphabets, padded or not),
because the control plane answers the canonical unpadded base64url. An
account service without agent links, an unconfirmed email and an identity
linked to another account are left alone. A refused proof or a challenge
for another agent is reported to Sentry, since only a bug produces either.
Builds without an account service make no requests.
A checkbox sits left of each row number (on hover, always on touch). Once a row is ticked every row shows one and the toolbar next to the filter button shows the count with a delete button. Undo restores the whole batch.
Right-click a row's number for its resource menu. Right-clicking inside a shift+click or drag selection opens a menu that clears the values, sets one value in all of them, or deletes the rows they touch (one undo step). View tabs can be dragged to reorder them.
…r error toasts, fix stale rows after deleting a typed row
…ders in the account menu; undo the top bar change
Two people can message each other in a conversation only they can read.
The server that hosts it stores ciphertext; it still checks every write,
because the rights and signatures it needs stay readable.

- atomic_lib::conversation: an agent's X25519 encryption key, derived from
  its vault proof so every device has the same one; a keyring with one key
  per epoch, wrapped to each member; sealing and opening messages, bound to
  the conversation; report-by-reveal of a single message key. WASM exports
  for the browser.
- Ontology `conversations`: Conversation, SealedMessage, encryptionKey,
  conversationKeys, sealed, conversations.
- GET /conversations lists the conversations on the server the requester
  is in, so the other person finds one they did not start.
- A conversation is its own drive with read + append for its members and
  no writer, so nobody can change what someone else said.
- App: Messages section in the sidebar, "Message" on every avatar, a New
  message dialog, and the conversation page, which opens each message
  before ChatView shows it. Store.subscribeLive (as in #1949) keeps the
  conversation live. The app publishes the agent's key at start.
The managed passkey calls relied on a ceremony cookie that a linked device never keeps, so its finish call failed. The start response now carries the ceremony id and the finish call returns it in X-Passkey-Ceremony. The portal still reads the cookie when the header is absent.
Undo drained and rewrote every stroke in the Loro list and then serialized
up to 400 full stroke snapshots to localStorage, synchronously, per press.
replaceListItems now deletes and inserts only the changed middle of the
list, and the canvas writes its undo history once things settle (and on
pagehide / close).
The hosted distribution (VITE_ATOMIC_HOSTED_DISTRIBUTION) is called
atomic.place in the tab, description, splash caption, PWA manifest and
user-facing copy; a build from source keeps the neutral name AtomicServer.
Settings, account, sync and the other app pages now get their own title
(for example Settings · atomic.place) instead of the bare product name.
A pasted atomic:, did:ad: or http(s) subject is now a pick of its own, and
the agents with direct rights on the drive are offered next to the search
results, since their data is not in the drive and search never finds them.
Shows what the vault's storage is made of and how much can be freed, keeping
the part held only for undo separate and behind its own explicit button.
GET /drive-usage/breakdown returns every resource in a drive with its edit
history and file bytes. The app draws them as a squarified treemap with
drill-down and a sorted list, reachable from the sync page.
# Conflicts:
#	browser/CHANGELOG.md
# Conflicts:
#	CHANGELOG.md
#	browser/CHANGELOG.md
#	browser/data-browser/src/locales/de.po
#	browser/data-browser/src/locales/en.po
#	browser/data-browser/src/locales/es.po
#	browser/data-browser/src/locales/fr.po
joepio added 30 commits October 5, 2026 12:52
A browser kept an old identity's encrypted backup next to the account's
newer one, so the unlock screen listed the same address twice and only
one of them opened. Caching a backup now drops older ones of the same
account (the server holds one per account). Where an address still
appears twice, each choice says when it was saved and which identity it
is, and every choice can be forgotten on this device.
The consent page needs the identity the desktop app holds, which a browser
on the same machine does not. connect --desktop opens atomic://authorize-mcp,
the app routes it to the consent page, and after Allow it tells the loopback
listener and stays in the app instead of navigating away.
A node moving from one domain to another has to answer under both while links
to the old one are still around. The flag took a single suffix.
The identity the app has unlocked now signs the account in when it has no
session (the portal's secret sign-in), so nobody is signed in on one side and
out on the other. Sign-out goes through one helper that ends the account
session and removes the key, and a new /app/sign-out route lets the account
portal sign out through the app.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants