Skip to content

v0.87.0

Choose a tag to compare

@davidfarah2003 davidfarah2003 released this 20 Oct 02:45
b50038c

Cotal 0.87.0 is out!

Grab it: npm i cotal-ai@0.87.0

Changes in this release

  • @cotal-ai/core/browser-control now opens a Manager caller in a strict-CSP page. openBrowserManagerCaller opens one connection for one explicitly selected Manager instance with the person's own manager-caller bearer: it checks the bearer's space, principal, lifecycle and instance before dialling, reads the issued caller, pins the describe to the instance and uses the host's generated contracts. It never reconnects on its own; reopen re-checks every pin. followGoal submits a goal once under the host's persisted envelope id and runs the stock goal follow; reconcileGoal reads an accepted goal back through goal-result; verifyGoalResultFact checks the fact with Web Crypto. Nothing retries, and an accepted goal is never submitted again. To share the follow, it moved into a module without transport-node and takes the host's fact verifier (submitAndFollowGoalWith), and the goal-result shape checks moved out of endpoint-action. The goal follow, at the package root and in the browser, now settles a goal from live progress only on a terminal whose subject names the instance and epoch that accepted it and the same goal as its body. Any other terminal, such as one another Manager publishes under its own instance, settles nothing and prompts one read of the goal's record, and a read fact committed by another instance is refused. The package root's submitAndFollowGoal, parseGoalResultFact, GOAL_TERMINAL_STATES and epGoalProgressGrantRow keep their names and signatures.

  • History reads on observer and admin credentials no longer leave consumers behind. channelHistory, multiChannelHistory and dmHistory used to read through nats.js ordered consumers that these profiles could not delete, so each read left two or three consumers for five minutes. A dashboard polling history filled the stream's consumer limit, and every later read failed with "maximum consumers limit reached". The reads now use the connection's own reader names, hist_<readerId>_<n> with n in 0..7, and delete each reader when its step ends. A static credential's readerId is its nkey. Under the auth callout it is the server's id and the client id the server assigned the connection, so a client cannot claim another connection's readers by choosing its inbox nonce. A connection runs at most eight history reads at once per stream, and a read past that waits for a free name. A consumer found under one of the names is never deleted, and a create that returns an existing consumer is never read through; the read moves to a free name.

    The observer profile gains $JS.API.CONSUMER.DELETE.<CHAT>.hist_<readerId>_<n> for those eight names, and admin also gains them on <DM>. Each row names one consumer of this connection and no other. Breaking: a static observer or admin credential minted before this release has no such row, and a current client's history read on it fails and says to remint it. Remint those files, and upgrade the auth service before or with the hosts that read history on a user-auth mesh. The auth callout's permissionsFor hook now receives the connection's reader id as a third argument, and the function calloutPermissions returns requires it.

  • A person who may spawn on their own Manager can now read what a launch there needs. A manager-caller view whose actor holds spawn carries the Manager's status, models and resolve-cwd commands on its one pinned instance. Before, those reads needed admin, and no view carried resolve-cwd, so a launch dialog that reads the Manager's connectors, model catalogs and a working directory was refused at the broker. A run-only or no-scope view gains nothing, ps and slots stay with admin, and the standing spawn set that reaches every Manager on the class rail is unchanged.

    A Manager whose runtime delegates seats outside its host refuses resolve-cwd before filesystem lookup. Every path gets the same execution-mode refusal, since delegated seats cannot use a Manager-host working directory.

    A user mesh's co-located Manager answers every owner's view, so its status now counts only the caller's own seats, as ps would list them, unless the caller holds admin. A remote Manager's seats all belong to its owner, and its count is unchanged. The auth service mints the view, so the new reads arrive with its upgrade. On a self-hosted user mesh, restart the Manager on this release with or before the auth service, so the count is bounded and delegated host cwd resolution is refused before the reads are minted.

  • Declare frozen private tool-result consent, revocation, serving-material and handle-mode command contracts, with closed parsers and a managed-agent issue/renew envelope. Add the consents anchor role and its tool-results scope check. These exports do not sign, issue, register, serve or enable the private path; public tool arguments and results remain withheld.

  • Accept the additive private tool-result contract in SPEC and the version-exact documentation bundle. Define consent, capture frontiers, fixed audiences, bounded revocation and exact-instance reads without implementing or enabling the path. Public tool arguments and results remain withheld; activation requires qualified owner implementations and a stock release.

  • cotal send, cotal endpoints, cotal personas list --running, cotal channels list and the name check in cotal spawn no longer leave consumers on the broker. Each one opened a transient endpoint that watched the presence bucket and the channel registry, and the operator credential cannot delete those consumers, so every command left one consumer on each bucket for five minutes. Five shells sending in a loop reached the broker's limit of 1000 consumers per stream, and further sends failed with maximum consumers limit reached. With a static or open-mesh credential the transient endpoint now reads both buckets once with a batched Direct Get and binds no consumer. cotal channels list reads the registry the same way through the new directGet option of readChannelRegistry. @cotal-ai/core adds the readOnce endpoint option and lastKvEntries, and the operator profile gains the Direct Get row on both buckets. On a user-auth mesh these commands still run on the user's bearer and keep their consumers.

  • A seat's launch-material directory (cotal-launch-* in the OS temp dir, holding the broker URL, the creds path and the control token) is now removed when the seat's process exits. Before this change a Claude seat left it behind after cotal stop, while the persona file and control socket of the same launch were removed. materialEnv in @cotal-ai/connector-core now takes the launch's artifact array as a second, required argument and appends the material's private directory to it, and every connector returns that array on LaunchSpec.artifacts, so the manager, the pty runtime and the foreground cotal spawn remove it the way they remove a persona file. Each connector now writes the material after its last refusal, so a refused launch leaves no material file behind. The second material file the Hermes launcher writes for its gateway child is not covered by this change.

  • Clarify private tool-result audience wording, distinguish the expired answer state from refusal codes, and spell out the consent replay row's existing signing and validity claims. No contract rule, runtime behavior or activation gate changes.

  • A repair cotal up after a crash now restores the manager and the delivery daemon in one run. A
    manager or delivery daemon that is killed, or a daemon that exits because its broker went away,
    cannot release its lease, and a replacement started inside the lease TTL used to lose the create and
    make up exit 1. The new manager now waits out a lease row that names its workspace root and a pid
    that is not running on the host, and up waits out a delivery lease held by the credential the
    folder gave its previous daemon while no daemon is recorded running. The wait never writes the
    lease: a row that is written again during it has a live holder and is refused, and a row held by
    anyone else is refused at once as before.

  • Add a consent-only verifier for pinned signed password authentication within five minutes and a host-only proof decision door. Retain validated authentication provenance without changing ordinary bearer exchange. Private tool-result consent, issuance and serving remain unavailable.

  • A tmux seat that outlives its manager is now adopted by the next manager instead of closed. The manager records each adoptable seat, and on startup the successor checks the recorded tmux server, window and pane, then manages the seat again under its existing credential so a run's pending turn continues on it. A seat whose pane has exited is retired as before.

  • The auth service serves POST /manager-instances on both faces: the signed-in human's own Manager instances, read-only. It verifies the same IdP proof the human exchange verifies, derives the owner the same way, and answers { v: 1, owner, complete: true, instances }, each instance with its placement and the verdict selection reads (candidate, not open, not registered, not this owner's, unknown). Selection is now built on the same list, and the plane openAuthAuthorityPlane returns gains listManagerInstances. A listed candidate is registration evidence, not proof that the instance answers. Browser-origin requests are refused as on every exchange route, and a failed gate read answers 503, never an empty list.

  • A cotal command started with a few hundred KB of argv, such as cotal send dm <name> "<480 KB text>", no longer exits 13 before it runs. The CLI loaded Ink and its wasm layout engine at startup for every command, and on macOS that engine's init never finished with an argv that large. Ink and the console UI now load only when cotal console runs.

  • cotal join under a provisioned credential now prints each channel post once. Such a join holds a durable delivery backstop for its channel, so every post reached it twice under the same message id, once on the live channel subscription and once from the backstop, and the join printed both. It now deduplicates by message id as the protocol requires of a receiver, acks the second copy, and never treats an empty id as a key.

  • A Linear token or OAuth state file on Windows is no longer refused as "readable by other users (mode 666)". Windows reports every file's POSIX mode as 666, so the check refused every token-mode account there, including token files Cotal wrote itself. The mode check now applies only where POSIX modes exist, as for the broker TLS key and launch material; on Windows the file's privacy is the ACL Cotal sets when it writes the file.

  • A seat spawned with supervise now survives its manager's death. When a manager ends without stopping the seat (a crash, or a pty seat that ended with it), the next manager of the same instance restarts the seat under the same name, lifecycle uid, credential, persona and cwd and counts it as one restart of the policy, so a workflow run resumed by that manager finishes its interrupted turn instead of failing it with L4002. The manager keeps what that restart needs in .cotal/seat-records/<space>/<name>.json while the seat runs. A seat whose budget is spent, or whose persona file or credential changed or expired, is retired as before. A seat resumed from a preserved inventory now also keeps the nkey seed its credential renewal needs: the manager read it with a pattern that matched no real creds file.

What's Changed

Full Changelog: v0.86.0...v0.87.0