Repository navigation
v0.86.0
Cotal 0.86.0 is out!
Grab it: npm i cotal-ai@0.86.0
Changes in this release
-
Every auth-callout success now records its broker-generated user key before the minted JWT leaves.
startAuthCallouttakes a requiredrecordIssuance, and refuses to start without one. The authority plane's recorder writes one create-onlynatkey.<userNkey>row in the space's auth bucket with the bearer's own space, owner, actor, lifecycle, credential and view and the minted JWT'siat/exp, then re-checks the connect authority before the response. A failed write, an existing row for the key, or a revocation seen by that re-check is a signed deny. The plane removes rows only after theirexpplus the clock tolerance, at boot and on a timer (nativeIssuancePruneMs, default 60 s), with the bucket's marker TTL on each purge marker; a corrupt row fails the open or fences the plane. Core addsnativeIssuanceKey. Connections admitted before this change, and static JWTs, have no row. -
A Claude app conversation can join a local mesh.
serveGatewayStdio(host, options)in@cotal-ai/connector-coreserves the remote gateway's tools over stdio, so a local MCP server offers the samecotal_connect,cotal_waitand routed tools as the hosted connector without importing an MCP SDK. The hiddencotal app-identityplumbing command opens one conversation's own identity on the selected mesh: it answerstakenfor a live name, takes its channels and role from the mesh's default persona, provisions the identity's own delivery durables on a mesh with static credentials, and writes the credential to a 0600 file that never appears on stdout.cotal app-identity retire --dirremoves the identity's broker footprint and then that file, and each provision retires what a host that exited without retiring left behind. A mesh that uses per-user sign-in is refused. The Claude Code plugin's server now serves those tools to a session Cotal did not launch, in place ofcotal_how_to_join, and joins only when the conversation callscotal_connect.cotal setuprecords the CLI's own argv beside the plugin, so that server runs the CLI by absolute path; a connector's install action receives it asConnectorSetupInputfrom@cotal-ai/core. Started with--desktop, the same server suits Claude Desktop, whose working directory is no project. Without--space, provision sweeps the registry first, ascotal spawndoes, so a recorded mesh whose broker is down no longer makes the choice ambiguous. A conversation in a Claude Code session is woken when a message arrives: the plugin declares a monitor, each local server writes the connection id of each wake to a private socket of its own, and the monitor turns each valid id into a fixed event line, so no socket text reaches the model.GatewayOptions.wakein@cotal-ai/connector-coreis the hook for this: it signals a connection once until it is read, never while acotal_waitholds it, and again when a client starts listening, andcotal_connectsays whether the client will be woken.GATEWAY_INSTRUCTIONSnow tells a conversation how to keep listening when asked, and is exported in two parts,GATEWAY_CONNECTINGandGATEWAY_LISTENING. Claude Code runs thecotalplugin in place from the copycotal setupmaterializes under~/.cotal/claude-plugin, and before this change neither a repeatcotal setupnorcotal updaterewrote that copy, so after an upgrade plain sessions kept starting the old server, hooks and monitor. A setup provider can now declarerefresh(ConnectorSetupProvider.refreshin@cotal-ai/core), which brings what it installed to this release and installs nothing new; a repeatcotal setupandcotal updaterun it, andcotal updatefails when the refresh fails. The copy's version is now the release plus a digest of its files, andcotal statusreports the plugin as current or stale against it, in place of only whether it is installed.cotal setupalways runsclaude plugin marketplace updateafter adding the marketplace, since Claude Code 2.1.295 answers a repeat add with wording the old check did not match. On macOS and Windows, where Claude Desktop is installed,cotal setupadds acotalserver to Desktop'sclaude_desktop_config.jsonthat runs the same server with--desktop, keeps every other entry, refuses a file that is not valid JSON, and the refresh keeps the entry current;cotal statusreports it. -
cotal setupnow installs thecotalClaude Code plugin for your user account, so every Claude Code session on the machine gets thecotal_*tools in any project: the terminal, the IDE extensions and the Desktop app's local Code sessions. It used to install it only for the project setup ran in. Runningcotal setupagain moves the per-project installs an earlier release made under the one user-scope install.cotal updatestill only rewrites the plugin copy and does not change where it is installed. If Claude Code cannot change one project's settings, setup still removes the others and writes the Claude Desktop entry, then fails naming that project and theclaude plugin uninstallcommand to run there.cotal statusreadscurrent (user scope). Setup andcotal statusnow read a plugin list longer than 1 MiB, which a machine with many old per-project installs reaches; before, the output was cut short and status showedunknown. A session Cotal launches still loads one cotal server, because its--plugin-dircopy replaces the installed one for that session.The plugin's hook, which Claude Code starts for every tool call, prompt and stop, no longer bundles the
@cotal-ai/corepackage root. It shrinks from 945 KB to 14 KB and starts in about half the time.peerLabelis now also exported from the new@cotal-ai/core/peer-labelsubpath, and the root export is unchanged. -
saveAgentFilenow refuses ametakey that names a modeled frontmatter field (such asname,model,ownerorcapabilities) or a renamed one (channels,publish) before it creates a directory or writes the file. Metadata used to overwrite the fields it had just validated. Unknown scalar connector hints still round-trip unchanged. -
@cotal-ai/core/browser-controldescribes and invokes any endpoint from a strict-CSP page, with no Node built-in and no runtime code generation.@cotal-ai/core/browser-validators-buildgenerates its contract validators at build time. The envelope byte bounds (error code,traceparent,tracestate,baggage, event topic) now measure UTF-8 withTextEncoderinstead of Node'sBuffer, so an error reply no longer throwsReferenceError: Buffer is not definedin a browser. The bounds themselves are unchanged. -
generateBrowserContractsno longer refuses a valid schema whose string data reads like code, such as aconstofrequire(,eval(ornew Function. It now parses the standalone validator with acorn and refuses only an actual reference torequire,evalorFunction, or a call of a member with one of those names. Ajv runtime helper reads are rewritten from the same parse, and output that does not parse refuses. -
canonicalJsonandcontractDigestnow serialize a plain copy built during the I-JSON check instead of reading the value a second time. A Proxy whose later reads differ from what the check saw, or that answerstoJSON, could pass the check and come back as text holding a lone surrogate,nullfor a checked number, or unsorted keys, andcontractDigesthashed that text. The text now always encodes the data the check inspected. An array's length is read once, so a Proxy whose length changes during the copy can no longer add or drop an element, and a length that is not a non-negative integer is refused. Values that are already plain JSON canonicalize as before. -
The endpoint budget races in the action, work, checkpoint, receipt, guard and payment-proof paths now run through the shared
withDeadlinehelper instead of six private copies. Each keeps its error code and message. -
TURN_INPUT_SCHEMAandTURN_OUTPUT_SCHEMAare now deep-frozen at their declaration, like the other collections core exports. They were plain objects, so an importer could change theturncontract a manager serves and validates against. Their contents and contract digests are unchanged. The frozen-exports smoke now also admits the./browser-controland./browser-validators-buildentries. Each declares the functions it adds beyond the barrel and exposes no other new export. -
@cotal-ai/corenow exportsdigestIdToken, the one recipe behind every id derived from a digest (the 43-character base64url sha256). Run notice and migration ids, checkpoint answer ids, the runtime's derived pause tokens and the manager's turn hold token all use it. Before this, each module kept its own copy, so a change to one copy could pass the typecheck and quietly stop matching the ids the others derive. The derived ids are unchanged. -
Claude Code sessions report the directory their own
SessionStarthook supplies on startup, resume or fork, not the connector process directory, so sessions hosted in one process no longer all show the host's directory. The directory is absent until the hook reports it and is not changed by clear or compact.CotalEndpoint.setCardCwdandMeshAgent.setCwdset it, andMeshAgent's newwithholdProcessCwdoption leaves it unset at construction. -
The delivery daemon's lease watch now deletes its broker consumer when its stop handle runs, when the endpoint stops, when a manual reconnect retires a live connection, and when a newer lease watch replaces it mid-bind. Each of those retirements used to leave an idle consumer on the space's delivery bucket for five minutes. A lease-watch bind still in flight when the endpoint stops or reconnects now deletes its consumer instead of installing a watch for the retired connection, and that bind no longer delivers a lease event after
stop()has begun. -
The stop that
watchMembershipreturns now callsfinishMembershipWatchStopdirectly. It used to check the retained consumer identity itself first, which repeated part of the guard insidefinishMembershipWatchStop. One guard now decides when a stopped membership watch resolves, on both the stop path and the reconnect rearm path. Behavior is unchanged. -
peerLabelnow prints every control character and Unicode line or paragraph separator in a peer's name or role as a space, as it already did for line breaks and brackets. Before, a peer that put ESC, BEL, a C1 control or a tab in its name, or in the role of the presence record it writes itself, had those bytes printed raw on every surface that labels it, includingcotal endpoints,cotal status,cotal joinand the console. -
A run that names a placement instance now sends its placed seats' turns to that instance. Before, those turns went out on the class rail, where only the hosting manager maps the seat, so a live seat could be reported down once the re-issue budget ran out. The run host credential gains owner-pinned
turnon that one instance only. Unplaced runs keep their class-rail reach, and their L4002 message no longer claims more than the members the call reached. -
Readers now drop a presence row whose
card.role,activityorattentionis present and not a string, or whoseconditionis present and is not an object with a stringcode(null, a bare string and an object with nocodeincluded), and report it on the recoverable diagnostic path. A peer could previously put an object such as{"toString":null,"valueOf":null}in its own condition code or activity and make every other reader'scotal_rosterfail withCannot convert object to primitive valuewhile that row was live. -
resolveServiceno longer starts contract-store reads after it has failed. When one closure walk failed, the resolve rejected at once, but the walks queued behind the first window kept starting and each issued its own store reads for a call nobody could use. A failed walk now stops the pool from starting more; walks already in flight finish on their own. -
cotal_roster,cotal ps,cotal status,cotal endpoints,cotal consoleandcotal joinprint each peer's condition code and activity on one line and at most 200 characters. A control character or Unicode line separator shows as a\uXXXXescape, so a peer can no longer split its row into what reads as a second roster entry, pass terminal control bytes through, or flood every row with a 4000-character activity. The rule ispresenceTextin@cotal-ai/core, andoneLinemoved there from@cotal-ai/workspace, which still re-exports it. -
Smoke comments and check labels in core, cli and manager name the function, branch or SPEC section they point at instead of a line number that drifts with every edit above it. No behavior changes.
-
The spawn-name smoke's delegation cells now parse
agent-file.tsand look for a realassertValidOwnerTokencall inspawnNameError, and for any regex literal there besides the traversal guard. They used to match stripped source text, so a string that spelled the call, or the token alphabet restated as\w+, kept them green. No behavior changes. -
The spawn-name smoke's auth-mode cell now looks through parentheses before it checks the door's mode argument. Writing the call as
(target.mode === "user")or parenthesizing either operand used to fail the cell although the CLI behaves the same. A!==comparison, a baretrueand swapped arguments still fail it. No behavior changes. -
cotal down -fon an authenticated mesh now removes the channels its run owns. Theteardowncredential it mints could not read the membership feed, so the check for other members always failed and every owned channel was kept withmembership unknown (no feed), ending in! partial teardownwith exit 1, anddown -f --run <id>could never finish the run. The credential now holds the feed's read verbs and still cannot write it. A channel another member still uses is kept as before. -
validate()andtransform()now refuse a write to aconstbinding through a destructuring assignment ([c] = [1],({ c } = { c: 1 })) or afor...ofwith no declaration (for (c of xs)) with L2003. These used to validate clean and fail with L2003 only when the line ran, after any effects dispatched before it. -
validate()now refuses with L2032 awaitUntilprobe orterminalpredicate that writes a binding declared, or a value built, outside it. Both engines run them one level deeper than the call and refuse the write at run time, so such a program used to validate clean and then fail on the first observation of every run. -
A control command run without
--spacenow probes the registered brokers once per resolve. Before this, the control resolver probed every non-catalog record for its mode check, and the connect helper probed them all again before it chose the target;cotal update's manager continuity check added a third round of probes. Each round could wait out the confirming timeout on a record that did not answer, and a broker that started or stopped between rounds could leave the mode check and the connect resolving different targets.ConnectFlagsnow carries the sweep'sofflineset, andresolveControlTarget,resolveTargetOrThrow,connectOrThrow,connectUserControlOrThrowand their exiting forms read it instead of sweeping again. A named--spacestill skips the sweep. -
Callers can name a manager instance wherever a user-auth mesh with several live managers for one owner asks them to.
cotal modelsand every hostedcotal runverb take--on <instance>, and MCPcotal_runandcotal_personastake aninstanceargument under the same rules ascotal_spawn's. Acotalcommand run from a managed seat's shell now selects the manager that launched the seat (COTAL_MANAGER_INSTANCE), as the seat's tools already did, unless--onnames another. The ambiguity refusal still refuses, and now lists the owner's live manager instance ids and names the caller's own remedy:--on <instanceId>for the CLI, theinstanceargument for an agent's tools. Before, it named--manager-instance, which onlycotal agent-bearertakes, so none of these callers could answer it. -
The seed reconcile reads a built-in's
package.jsononce per check.installedExtensionPackagereturns the installed version and declaredmainfrom that one read, with the error policyinstalledExtensionVersionalready had, and the entry-file check takesmainfrom it. The check used to parse the file a second time and treat any failure of that read as a missing entry file, so a permission error there madecotal ext seed --repairsilently reinstall the built-in, or report a torn install while the entry file existed. A read failure now surfaces as the error it is. -
The comment on the
space addsecond-tenant refusal and the P7/P1 design note now nameensureRootForSpacein the CLI'supcommand as the root-pin refusal. They used to cite it asup.ts:2233, and that line had drifted onto an unrelated interface field. No behavior changes. -
A stray
space.<key>,account.<key>.json,manager.<key>.pidordelivery.<key>.pidwhose key decodes to.or..is now treated like any other name the workspace did not write.spaceFromSegmentreturns undefined,accountInventorylists the file undercorrupt, andrecordedRuntimeSpacesskips it, where each used to throw "cannot name a space" andcotal statusprinted that error as a hint. The three decoders now share one exportedspaceFromKey. -
cotal status --componentsnow names the HTTP status when the web dashboard's address answers/api/metawith a status outside 2xx, such asHTTP 401. It used to read the body first, so a plain-text or empty refusal printedhttp not answered. The status row andcotal web --detachnow send one shared readiness request from@cotal-ai/workspaceand read its answer the same way. That request no longer follows a redirect, so the readiness header goes only to the recorded address and a 3xx answer is reported as its status, such asHTTP 302, where both used to follow it and accept the identity the redirect target answered with. -
The pre-hex record sweep that runs when a mesh is recorded or removed now throws any error listing the registry directory other than ENOENT. Before, the sweep read every listing failure as an absent registry, so a removal could return while a pre-hex record of the same space stayed behind and listed the mesh again. An absent registry directory still has nothing to sweep.
-
The AG-UI mapping smoke's comments now describe the run-opening rule the mapper uses, the
ORIGIN_RULEandABSENT_ORIGIN_RULEenumerations restated once asattributableTurn, in place of the retiredpromptSource-presence gate. The mutation ledger names the population cell by its current name, and the restatement's doc block now sits onattributableTurn. No behavior changes. -
A forked Claude Code session no longer acts under the hosted Cotal connection of the session it was forked from. A fork inherits the parent's
connection_idin its transcript, so before this it reused the parent's mesh name from a new session. The plugin now records which session opened each hosted connection, from aPostToolUsehook oncotal_connect, in the plugin's data directory, and itsPreToolUsehook refuses that id in any other session with a reason that says to callcotal_connect. A resumed session and its subagents keep the session id and are unaffected, andcotal_disconnectreleases the record.runHookRelay's local answer now runs in every session and is told whether the session carries a mesh identity. -
A Claude Code session that Cotal launched now refuses the
cotal_*tools of every MCP server other than its local Cotal server. Claude Code loads connected-account connectors in a spawned session by default, so a hosted Cotal connector beside the plugin let one session join the mesh twice under two names. The plugin'sPreToolUsehook now denies those calls with the reason, and decides in the hook process, so the refusal holds when the control socket is down. A session Cotal did not launch keeps the hosted connector.runHookRelaytakes an optional answer that settles an event in the hook process before the control socket is resolved. -
The remote gateway gets
cotal_wait, so a hosted conversation can listen for messages. A remote MCP client cannot start a turn, so a hosted conversation heard a message only when it happened to call a tool.cotal_waitreturns at once when a wake-worthy message is buffered and otherwise as soon as one arrives, with the replycotal_inboxgives, or with none after a bound. Wake-worthy follows the agent's attention, as a local seat's push does. The bound isGatewayOptions.waitSecondsonregisterCotalGatewayTools, orwaitSecondsonhandleGatewayRequest, and defaults toGATEWAY_WAIT_SECONDS(50 seconds), under Claude Code's 60-second limit to a remote server's first byte, Cloudflare's origin timeout and claude.ai's tool-call timeout. A wait whose client closes the request stops listening, and a read whose client is gone clears nothing, so neither takes a message it cannot deliver. A wait ends when its connection closes, and a focus @mention that lands between two waits ends the next one.CotalToolSpec.runtakes an optionalToolCallwith the request'ssignal, whichcotal_inboxhonors. Every other routed tool exceptcotal_inboxnow adds a line to its result when messages are waiting, and the default instructions tell the model to listen withcotal_wait.MeshAgent.inboxWake()is the inbox half ofpendingWake(), without run turns.MeshAgentemitsstoppingonce whenstop()begins, whatever the transport's state. -
cotal agent-bearer --manager-callis now told to name a manager with--manager-instance <instanceId>when an owner has more than one live manager. Before, it was told to use the instance argument, which only an agent's tools take. The exchange names--manager-instancefor every actor-token caller, becausecotal agent-beareris that arm's one client, and the connector renames it to theinstanceargument when it runs the command for a tool. The refusal still refuses and still lists the owner's live manager instance ids. -
Report the connector process working directory in session display metadata alongside its hostname.
-
The OpenCode 1.x plugin's teardown and session-swap comments now state the rule the code beside them keeps, in the present tense, in place of accounts of earlier revisions and of how a review caught them. The swap note no longer names a
drivePendingflag the code does not have. No behavior changes. -
The Orca runtime's exit wait now settles within its bound when the Orca CLI survives SIGTERM. The async CLI calls behind
terminal wait,listandshowsent SIGTERM on timeout and then waited for the child to exit, so a child that ignored it held the wait open. Both the sync and async paths now share one set of exec options that kills a timed-out child with SIGKILL. -
The tmux terminal layout now refuses a multi-pane tab with no
splitbefore it writes a launch script or opens a window. Before this,open()started the first pane in a new window and then threw, so the caller got an error with no ref while the window and its command kept running, and every retry left another one. -
The first IdP pin is now written by exclusive create. Two processes enabling user auth on one state dir at once used to both succeed, each holding its own IdP while
idp.jsonnamed only the last writer. Now one pin wins: the other caller adopts it when it names the same IdP and is refused with the usual re-key message when it does not. -
When every remote manager an owner registered has exited, the manager-caller refusal now tells the owner to start one with
cotal supervise. It used to suggest naming an instance withmanagerInstanceId, which cannot revive a dead manager. The call is still refused and is never routed to the local manager. -
The auth service now records a manager-caller connection's credential expiry in the issued evidence it writes for that connection's generation. After the credential expires, the generation no longer resolves and is refused with
generation <id> expired at <exp> (SPEC 13.15). A run admitted while the generation was live keeps its admission and keeps renewing. -
A manager-authority answer whose body is not JSON now fails the request with an error that names the operation, the endpoint and the HTTP status. Before, the client replaced such a body with an empty object, so a 200 answer resolved as an empty result the endpoint never sent, and a refusal read the same as a refusal with an empty JSON body.
-
Keep the manager's delivery-aware marker, shutdown intent and spare capability when
cotal downdoes not stop every selected component. The manager stop no longer removes them itself:downremoves them after its all-stopped gate, and thecotal upteardowns and the delivery cutover preflight remove them after their own stop. -
cotal backup create, restore and quarantine now fail after 15 seconds when their isolated bootstrap broker stays alive but never accepts connections. The reachability wait recomputed its bound on every pass, so only the 30-minute operation deadline ended it. -
cotal upproves a surviving restore or ordinary-resume listener with one shared check. The two attempt kinds used to carry separate copies of the same ownership proof, so a check tightened in one could silently miss the other. Each kind now supplies only its own journal match and store path. -
cotal deliver --space <s> --dev-mintnow signs its dev cred with space<s>'s account. It used to pick the root's only space, so a root holding several spaces refused a correct--space, and a root holding one other space signed the cred with that space's account. A root with no account for<s>is now refused before any dial. -
The manager's doc comment on its launch capability check now says spawn and resume both ask before a seat's launch is built. It used to say they ask before any reserve or mint, which resume does not do: its batch reserves the retained names before the check runs. No behavior changes.
-
The instance-pin smoke grades its two recovery-counter cells against the re-issues that left the caller. In a two-manager space a re-issue rides the same class queue and can be refused again, so one call may recover several splits, and the cells asserted exactly one and went red on correct runs. Each now requires the counter to move and to equal the number of re-issued requests. No behavior changes.
-
The instance-pin smoke's admin sweep now sends
transcript-receiveschema-valid nonsense arguments: a random digest, size 0, and session and host names that match nothing. It used to send{}, which the envelope refused onrequiredbefore publishing, so the command never reached a responder and the cell that proves every admin command reaches the guard failed on every run. No behavior changes. -
@cotal-ai/managernow exportsmanagerBrowserContractSource(), the manager's registered cluster document with its closure digest and each distinct schema root. A host passes it togenerateBrowserContractsfrom@cotal-ai/core/browser-validators-buildto build the manager's browser validators. Before, the root exported only the document and digests, so no supported import carried the schema roots. Served commands, digests, wire, auth and runtime behavior are unchanged. -
Remove the internal review attributions from the static lifecycle module's comments, which the published build carried. Each comment keeps the reason it gives.
-
The manager no longer keeps every spawn acceptance for the life of its process. An acceptance is dropped five minutes after its goal ends, and an in-flight goal's acceptance is never dropped. A resubmission of a goal that has ended is answered from the goal's terminal and is never accepted again: a
succeededgoal returns the same acceptance, and any other outcome is refusedfailed-preconditionwith its terminal attached. Before, a resubmission the manager no longer held in memory, such as one after a restart, ran the accept again and was refused with an error telling the caller to reconcile the store. -
cotal supervise --space <s>now starts on a root that hosts several spaces when<s>has no registry entry. The root's account record for<s>counted as the host proof only while<s>was the root's sole space, so a second space made the manager exit before readiness with the ambiguity error that asks for the space it was given. -
cotal supervisenow handlesSIGTERMandSIGINTfrom the moment it starts. Before, its handlers were installed only after the manager finished starting, so a signal during startup killed the process with its liveness lease still held: no warning printed, and an immediate restart from the same workspace root was refused withalready serves ... stop it firstuntil the lease's 10 second TTL lapsed. A signal during startup now waits for the start to settle and runs the normal stop, which releases the lease. The process then exits 0, or, when the start itself failed, prints that error and exits 1. -
Report an unanswered placed-host discovery as catchable unreachable L4002 before relay submission. Preserve accepted-goal uncertainty and permission refusals. Keep a migrated seat's original host in its durable binding and restored roster, and refuse incompatible placement requests without consuming its kept hand-over.
-
Attach the seat-address documentation to its type in a single JSDoc block.
-
The web events-filter smoke's call-site cell now counts every mention of
chatOnlyin the web server and expects three, the declaration and its two calls. It used to count onlychatOnly(, so a third call written with a space, a line break, type arguments or?.(left it green. No behavior changes. -
The dashboard's static route now reads a page file before it writes the status line. A missing or unreadable file used to be answered with
200,cache-control: no-cacheand an empty body, because the read failed after the status was already sent, so the browser ran an empty script and the page failed later with an unrelated message. It is now answered with the request handler's500naming the failed read. Successful responses keep their headers, and the session check still runs before the route.
What's Changed
- refactor(cli): prove a surviving restore or resume listener with one shared check by @davidfarah2003 in #3993
- fix(workspace): name ensureRootForSpace for the up root-pin refusal by @davidfarah2003 in #3989
- test(core, cli, manager): name symbols and SPEC sections instead of line numbers in smoke citations by @davidfarah2003 in #3991
- fix(workspace,cli,web): send the dashboard readiness probe from one place by @davidfarah2003 in #3990
- fix(connector-core): print a peer's condition and activity on one bounded roster line by @davidfarah2003 in #3995
- docs(manager): state the ordering capabilityRefusal gives on both spawn and resume by @davidfarah2003 in #3994
- docs(claude): explain organization channel opt-in by @davidfarah2003 in #3998
- fix(core): print every control character in a peer label as a space by @davidfarah2003 in #3999
- test(core): parse spawnNameError for the spawn-name delegation cells by @davidfarah2003 in #4002
- fix(core): derive every digest id with one exported digestIdToken by @davidfarah2003 in #4001
- fix(manager): remove stale review attribution from lifecycle comments by @davidfarah2003 in #4007
- fix(connector-claude-code): correct attribution smoke documentation by @davidfarah2003 in #4008
- fix(core): keep one membership cleanup completion guard by @davidfarah2003 in #4009
- fix(core): let manifest teardown read its membership feed by @davidfarah2003 in #4011
- fix(website): import the setup prompt fence from one module in the docs sync by @davidfarah2003 in #4015
- refactor(core): route the endpoint budget races through withDeadline by @davidfarah2003 in #4016
- fix(connector-claude-code): keep a launched session on one Cotal path by @davidfarah2003 in #4006
- test(core): look through parentheses in the spawn-name smoke's auth-mode cell (#3614) by @davidfarah2003 in #4017
- test(web): count every chatOnly mention in the call-site guard by @davidfarah2003 in #4025
- fix(connector-claude-code): keep a hosted connection with the session that opened it by @davidfarah2003 in #4022
- test(smoke): scope seat environment proof builds to connector dependencies by @davidfarah2003 in #4027
- fix(cli): bound isolated broker startup by one reachability deadline by @davidfarah2003 in #4029
- fix(manager): use the selected account for explicit-space startup by @davidfarah2003 in #4032
- fix(core): reject malformed optional presence display fields by @davidfarah2003 in #4031
- fix(lang): validate captured writes in waitUntil callbacks by @davidfarah2003 in #4034
- fix(core): stop queued contract walks after resolution fails by @davidfarah2003 in #4035
- fix(delivery): sign development credentials with the resolved space by @davidfarah2003 in #4038
- fix(workspace): reuse registry sweeps during control resolution by @davidfarah2003 in #4041
- feat(cli,connector-core): let manager-caller callers name a manager instance by @davidfarah2003 in #4036
- fix(core): reject modeled fields in agent metadata by @davidfarah2003 in #4047
- fix(workspace): classify dot-decoding space keys as strays by @davidfarah2003 in #4048
- fix(tests): restore modeled-agent and runtime-record mutation anchors by @davidfarah2003 in #4054
- fix(workspace): surface unreadable legacy registry sweeps by @davidfarah2003 in #4044
- fix(cli): read seeded extension metadata once per check by @davidfarah2003 in #4045
- fix(manager): release a startup lease before exiting after a signal by @davidfarah2003 in #4046
- fix(auth): reject undecodable manager-authority responses by @davidfarah2003 in #4049
- fix(manager): bound terminal spawn acceptance retention by @davidfarah2003 in #4050
- fix(tests): make manager guard and split-recovery checks discriminating by @davidfarah2003 in #4052
- test(cli): scope shutdown mutation builds to their dependency closure by @davidfarah2003 in #4060
- fix(auth): preserve the first IdP pin during concurrent creation by @davidfarah2003 in #4061
- fix(cli): retain manager artifacts until coordinated teardown succeeds by @davidfarah2003 in #4062
- fix(orca): bound asynchronous calls when the CLI ignores SIGTERM by @davidfarah2003 in #4064
- fix(core): canonicalize one validated snapshot of each value by @davidfarah2003 in #4065
- fix(auth,connector-core): tell cotal agent-bearer to name --manager-instance by @davidfarah2003 in #4066
- fix(tmux): validate layout splits before launching a pane by @davidfarah2003 in #4069
- feat(connector-core): let a hosted conversation listen with cotal_wait by @davidfarah2003 in #4059
- fix(web): read static assets before committing success headers by @davidfarah2003 in #4074
- fix(lang): reject writes to const bindings during validation by @davidfarah2003 in #4075
- fix(core): fence stale delivery lease watch binds by @davidfarah2003 in #4078
- test(manager): strip ambient COTAL_ env before child spreads in three smokes by @davidfarah2003 in #4081
- feat(connector-claude-code): join a local mesh from Claude sessions Cotal did not launch by @davidfarah2003 in #4082
- test(runtime): wire manager-caller-transport smoke through shipped callout issuance by @davidfarah2003 in #4084
- feat(connector): report the session working directory by @davidfarah2003 in #4070
- feat(core): add browser-safe endpoint control and generated validators by @davidfarah2003 in #4102
- fix(auth): record the credential expiry in a manager-caller's issued evidence by @davidfarah2003 in #4085
- fix(connector-opencode): state the teardown and swap comment rules in the present tense by @davidfarah2003 in #4108
- fix(core): freeze turn schemas and declare browser export invariants by @davidfarah2003 in #4112
- fix(runtime): pin placed turns and preserve migrated seat hosts by @davidfarah2003 in #4110
- test(runtime): repair routed mutation fixtures and seat documentation by @davidfarah2003 in #4115
- ci(release): open version PRs without the native cold matrix by @davidfarah2003 in #4117
- feat(auth)!: record durable native key issuance before callout success by @davidfarah2003 in #4120
- feat(connector-claude-code): install the cotal plugin for the whole user account by @davidfarah2003 in #4125
- test(ci): exempt the live Claude plugin refresh suite from the smoke gate by @davidfarah2003 in #4126
- test(ci): gate the hosted session-directory smoke by @davidfarah2003 in #4127
Full Changelog: v0.85.0...v0.86.0