openvibers/openvibe.sdk CHANGELOG.md

CHANGELOG.md

main 177.3 KB · 2187 lines · markdown

Changelog

0.166.0 — 2026-11-13

scripts/test-services.sh without Docker: the same services from local binaries (CI checks on shared nodes). A
check job on a shared node runs a repository's GitHub Actions job with act in host mode, with no Docker daemon and no
root, so eval "$(node_modules/openvibe-sdk/scripts/test-services.sh up)" && npm test && npm run test:pg, the test line
of nearly every OpenVibers repository, could not start its containers there.

  • With no working Docker (docker info does not answer), up starts PostgreSQL 18 (initdb, SCRAM, fsync off),
    PgBouncer (transaction mode; each test role is looked up with auth_query and SCRAM is passed through) and Valkey from
    local binaries, as the current user, under one throwaway directory (OV_TEST_SERVICES_DIR, default
    $TMPDIR/ovsdk-services-<uid>), on the same ports with the same users, and prints the same three variables. A second
    up reuses what runs; down stops them and removes the directory (and the containers, when Docker is there).
  • OV_TEST_SERVICES=docker|local chooses; OV_TEST_SERVICES_PATH limits where the binaries are looked for. Ubuntu:
    apt install postgresql-18 pgbouncer valkey-server (their own services can stay off).
  • Verified on ov-node-01 as an unprivileged user with no Docker: testing-db, db, pg-outbox, db-ambient,
    valkey and govern all ran against the local services (no case skipped) and down left nothing behind. Tests:
    test-services (no Docker and no binaries says what is missing and starts nothing; OV_TEST_SERVICES overrides).

0.165.0 — 2026-11-13

openvibe-sdk/agents/soul: who an agent is (agents.soul@1, Contracts 0.424.0; Design rules 175
actor-is-the-work, 176 souls-grow-by-yes). The owner, 11-13: "you should have different bots … they each have a
soul … your agents are created based on your memories and personality". Pure functions:

  • validate(soul): the shape and the rules a schema cannot say (no secret in a soul; no permission in a soul, which
    is the blueprint's hands and rules).
  • toPrompt(soul, { name }): one stable block of plain words (the dials in words, habits, beliefs, pushback, never,
    notes), placed before memory so a provider's cached prefix never changes inside a conversation; it says the soul
    never widens what the agent may do.
  • toSoulMd, toIdentityMd, fromSoulMd: the SOUL.md and IDENTITY.md layout OpenClaw and Hermes read, out and back;
    a SOUL.md from elsewhere has its sections mapped (Core Truths, Boundaries, Vibe…) and the rest kept as notes.
  • change(soul, patch): an agent's suggestion for its own soul, as a proposed soul and the change in words; nothing
    is applied (its person says yes).
  • Tests: agents-soul.

0.164.0 — 2026-11-12

  • New openvibe-sdk/ai-meter: every product meters its AI calls the same way (ai-metrics.md; Contracts 0.421.0
    ai.call.recorded@1 1.1.0 and ai.call-report@1).
  • startCall() measures each call on its own clock: TTFT and latency less any queue, tokens per second after the
    first token (or Ollama's own decode time), tool calls, fallbacks, the error class, and the charged price when the
    provider gives one.
  • normalize() reads the usage of OpenAI chat completions and Responses, Anthropic Messages, Gemini and Ollama.
    The input it reports is the uncached input, with cache reads and writes and reasoning tokens apart.
  • createMeterClient() batches the records to AI's POST /api/v1/usage/calls with the service token.
    - It retries a network error, a 408, 425, 429 or 5xx with backoff, and asks for a new token after a 401.
    - A batch refused for any other reason is dropped and counted, never sent again.
    - AI prices any call that brings no price and publishes it.

0.163.0 — 2026-11-12

From reading 24 open agent projects (one-system-v2.md). Three new modules, all pure functions:

  • openvibe-sdk/agents/rules: permissions as data, from Gemini CLI and OpenCode (agents.permission-rule@1).
  • decide() lets the last matching rule win; with no rule, the run's level decides.
  • Unattended work never waits on an ask. It settles to allow at the levels approve-for-me and full, and to deny below
    them.
  • The floor always asks; with nobody there, it is denied.
  • fromBlueprint() compiles a blueprint to rules: only the tools it names, never .git/hooks or its own config,
    and its sandbox and network.
  • openvibe-sdk/agents/router: pays for the strong model only when it is likely to matter, in RouteLLM's shape
    over OpenVibe Decisions.
  • It asks for a calibrated probability that the small model is not enough, and routes against a threshold
    calibrated to a budget (the share of steps that should go strong).
  • It draws the quality-cost points the scoreboards show.
  • The strategy chain is the person's pick, then the blueprint's pin, then the router. When Decisions is down, the
    cheaper model runs.
  • openvibe-sdk/agents/metrics: what a run measured (agents.run@1 1.4.0).
  • The charged cost comes first; an estimate counts only where nothing was charged, and unknown is null, never 0.
  • It records tokens with cache reads and writes, steps, latency, a named exit and evidence.
  • Also passAtK (unbiased), passHatK and flakeRate.

0.162.0 — 2026-11-12

  • New openvibe-sdk/agents/blueprint: make your own agent (agent-blueprints.md; Contracts 0.419.0's
    agents.blueprint@1 and agents.install@1). These are pure functions, so a browser, Link, a CLI and every service
    check and run a blueprint the same way:
  • validate: the shape a person edits, plus the rules a schema cannot say. It carries no secret (anything that
    looks like a key or a token is refused where it is). Every {{input}} is declared. Level full has a budget.
    Machine and robot tools have somewhere to run. A shared place says what it needs, and never names a machine. A
    tool served over MCP has its shapes. A team never contains itself.
  • withDefaults: it talks only, asks first, keeps nothing, answers its owner, and spends 25¢ a run and $2 a day.
  • asks: the install screen in plain words, with the keys an install records as agreed. unagreed lists what an
    install has not agreed to yet.
  • wider: what a new version asks for that the old one did not. That includes a tool going from ask to allow, a
    higher level, a bigger budget, more people who may ask, more memory, a new place, trigger or team member. An
    empty list means an install set to "same-or-less" takes the version without asking.
  • fill: inputs, typed and checked.
  • compile: one run. It returns agents.run-start@1, the loop's system prompt (with the floor said in words) and
    tools, the Dispatcher's need, pin, rungs and price cap, the places and the install's machines, memory, and the
    team. Try it, or a run without an install, asks before every tool and spends at most 5¢.
  • estimate: the price of a run on the cheapest choice it may use, through agents/dispatch, and the rungs on
    which it is free.

0.161.0 — 2026-11-12

  • openvibe-sdk/cam: capture(options) opens a camera through the site's own shared capture core (Shared's
    ov-capture.js, OVCapture).
  • It loads the core once from the calling site's own /shared/ (never another origin) and resolves to a
    MediaStream. A page that already has OVCapture uses it.
  • The options are OVCapture's own: mode, facing, lens, mic, resolution, audio and video.
  • Cam's in-page kit (CAM_KIT_SRC / CAM_KIT_SRI) is re-pinned to the kit Cam serves today, checked against
    the live file at its versioned address.

0.160.0 — 2026-11-12

The agent engine's loop, Dispatcher and Run API kit: openvibe-sdk/agents/loop, /dispatch and /api (agent-engine.md §§3, 9). A model, tools and the person's words become
a run's thread. It runs chat and ask turns on a server with no workspace (Vibe answering, an agent using OpenVibe's own
products as tools).

  • runLoop(options) resolves { messages, stop, error, usage, cost, turns, toolCalls }. Stop is done,
    terminated, budget, turns, aborted or error.
  • The model adapter: complete({ messages, tools, system, signal, budgetLeft, onDelta }) returns the next turn:
    text, thinking, tool calls, why it stopped, usage and cost. onDelta streams the turn as it is written.
  • Tools run in parallel, or in sequence when the loop or a tool asks. Parallel runs are prepared in order, run
    together, and their results kept in the model's order.
  • beforeToolCall is the approval gate (block, with a reason, or end the run); afterToolCall may rewrite a
    result.
  • steering(): words the person sent while it works, taken before the next turn. followUp(): words after
    it would stop. finishTurn() may end or continue the loop. transformContext() compacts or filters the
    thread before each turn, and entries marked ui_only never reach the model.
  • The budget (usd, tokens, turns, steps) stops it between turns, never inside one; the model is told what is left.
  • What it keeps, whatever the hooks say:
  • A call cut off by the output limit never runs; the model is asked to send it again.
  • An unknown tool, input its schema refuses, a throw or a timeout comes back to the model as an error, never as a
    crash.
  • The same call repeated past repeatLimit gets a reminder instead of a run, and an idempotent tool answers the same
    call from the run's memo.
  • A long output is clipped before the model sees it; the whole result stays in the thread.
  • createStepSink({ append, update, delta, startSeq, startTurn, runId }) turns the loop's events into
    agents.step@1 steps. In order: the person's words, the thinking (folded), one tool step updated when it ends, then
    the agent's words streamed into their step. A person's message opens the next turn (Contracts 0.417.0).
  • checkInput(schema, value): the JSON Schema subset tools use.
  • New openvibe-sdk/agents/dispatch: the Dispatcher (agent-engine.md §3), as pure functions over a person's
    choices:
  • ladder(choices, { need, pin, avoid, budgetLeft }) orders them: their own machine, a signed-in plan, their own
    key, the free allowance, then credits, cheapest first inside a rung, and a smaller model before a stronger one
    unless the step needs strength. What cannot do the step (a capability, the context, the speed, the budget) is
    listed with why. A person's pick wins when it can, and is otherwise passed over with the reason.
  • escalate(current, evidence, choices) moves to a stronger model only on checkable evidence: a failed test,
    schema, tool or grader, or the model's own "not sure". A grader's failure also changes family.
  • harnessFor(task, harnesses, { plans }) chooses by capability: the loop for turns with tools, the person's
    signed-in plan, else dsh.
  • placeFor(run, { machines, cloud }): the person's computer first (free), a Codes runner for a repository
    elsewhere, an OpenVibe computer when allowed, Bot for a robot.
  • estimate(choice, tokens) and slice(parentBudget, share).
  • New openvibe-sdk/agents/api: the Run API from a product's own store (server).
  • createRunApi({ product, subject, store, prefix }) returns handle(req, res, next) for Express or any Node http
    server: the list, start, get, steps, events, messages, stop and approvals routes of Contracts' docs/agents-runs.md.
  • The store has list, get, steps, start, say, stop, answer and, optionally, follow to push its own events
    (otherwise the stream reads new steps). A method a store lacks answers 409.
  • No subject is 401, another person's run 404, and a start without words or with an unknown kind 422.
  • Every run carries its ref. A store's own { status, code } errors go out as they are; anything else is a 500 that
    says nothing of the store. A mandate from a caller is dropped (intake's hook sets it).
  • Read back with the SDK's own client in its test.
  • Ported from pi's agent loop (earendil-works/pi, packages/agent/src/agent-loop.ts, MIT, Copyright (c) 2025 Mario
    Zechner): the inner loop over tools and steering, the outer loop over follow-ups, the turn hook, parallel and
    sequential tools, and the two tool hooks. The model API, the message shape, the budget, the memo, the repeat guard,
    the timeouts and the step sink are OpenVibe's.

0.159.0 — 2026-11-12

The agent engine's client (Contracts 0.414.0: everything a person asks an AI to do is a run with a thread of steps).

  • openvibe-sdk/agents runs: runs.list({ kind, status, q, limit, cursor, before, agent, parentRun }),
    runs.get(run) (null on 404), runs.steps(run, { after, limit }), runs.start(text | agents.run-start@1, { idempotencyKey }), runs.say(run, text | { text, attachments }) (continue a conversation or steer work),
    runs.stop(run, { reason }), runs.events(run, { after }) and runs.follow(run, { onRun, onStep, onDelta, onApproval, onEvent }), which resolves with the end status.
  • approvals.answer(run, approval, answer) by the approval's id, its object, or the step seq that asked.
  • createFrameAgentsClient(client): every product's runs through the Frame (/api/frame/runs…), where a run is
    named by its ref (<product>:<id>); createAgentsClient(client, { prefix }) for any product's Run API.
  • Helpers: refOf, parseRef, isEnd, needsPerson (a run waiting for a message is a conversation between
    turns, not something that needs its person), KINDS, STATUSES, END_STATUSES.
  • The stream: the engine's events (run, step, delta, approval, end) are read beside AG-UI's; only a
    step advances the resume cursor, so a snapshot or more text that shares a step's id is never skipped.
  • The plugin interfaces (model adapter, tool, sandbox, memory, planner, reviewer) stay as JSDoc types; the loop
    that calls them is the next release.
  • run, post, events, desks, takeOver, handBack and replay are unchanged.

0.158.0 — 2026-11-12

  • openvibe-sdk/views: one view API for every content kind (Contracts media.view-request@1, the media.viewed
    and media.engaged events).
  • createViewsClient(client).record(event | [events]) posts a preview, watch or live heartbeat for
    ref: { service, kind, id } to Media's POST /api/v1/views, up to 20 at once. seconds is cumulative for the
    session and item, so a retry does not count twice; a signed-out viewer keeps an opaque session id.
  • .counts(ref) reads one item's { views, engaged, previews, preview_share, peak_concurrent } from
    GET /api/v1/views/counts. Media never stores an IP address or a user agent with a view, and a preview counts
    from 1 second.

0.157.0 — 2026-11-12

See it work: a run's desks, control and replay

  • Add to openvibe-sdk/agents: desks(run) → the run's desks with their view and still, takeOver(run, desk),
    handBack(run, desk, note) and replay(run) → the steps with the still each saw. They call the same-named routes
    under the client's service (/api/v1/runs/:id/desks/...), so a page and a script make the same calls. No tag: the
    main session releases this after the sites' stage lands.

0.156.0 — 2026-11-12

  • openvibe-sdk/exchange: reviewed compute prices, keyless.
  • createExchangeClient(…).computePrices({ kind }) and the standalone createComputePriceClient() read
    GET https://openvibe.exchange/api/compute/prices: each GPU or computer's hourly and per-second price, the
    provider's own price page, the date it was checked and the date it must be reviewed again, filtered to
    kind: 'cpu' | 'gpu' when asked.
  • The public endpoint needs no key, sends Access-Control-Allow-Origin: * and is cached for 60 seconds. The
    keyed client still speaks the API host (unchanged); its price read goes to siteUrl (default
    https://openvibe.exchange) without its developer key.

0.155.0 — 2026-11-12

  • Add design.icons.suggest(label, { context, limit, rank }) to openvibe-sdk/design: the best glyphs for a button's
    words by meaning (the glyph's name, its META keywords and its other-set names), with rank: true asking a small model
    to order the shortlist when the words are new. The answer is the same shape as GET /api/v1/glyphs/suggest, including
    new, ranked, model and, when known, draft_price.
  • design.icons.get() and .search() are public now, like the Design API they call: no access token. .draft() still
    needs one, and the client still sends a token when it has one.

0.154.0 — 2026-11-12

  • Add the Frame's data client to openvibe-sdk/frame (and sdk.frame in the root, browser and ESM entries):
    createFrameClient(client) gives choices(task), query(q, { scopes }), providers(), ask(body) and
    events(run), the same calls every site's composer makes through OpenVibe.Network. choices answers the Model sheet
    (OpenVibe's models enriched from Exchange, the person's keys, computers and plans); ask starts a question and
    events reuses agents.events for its AG-UI stream, whose ANSWER event carries the numbered citations. Types are
    in types/frame.d.ts. The shared navbar, footer and online() are unchanged.

0.153.0 — 2026-11-11

  • Add objects.rotate(id, { degrees }) to openvibe-sdk/media (the v2 object client): turn a stored JPEG, PNG or WebP
    picture clockwise by 90, 180 or 270 degrees. Media keeps the uploader's original beside the turned copy, every
    generated size follows, and only the picture's owner may turn it (subject / actingUserId on a service client).

0.152.0 — 2026-11-11

A train of two pull requests: openvibe-sdk/elements (#227) and openvibe-sdk/ui-layout (#226).

  • Reserved for openvibe-sdk/ui-layout in OpenVibers/OpenVibe.SDK#226. That module is not released by this entry; it
    keeps the changelog's minor sequence while that pull request is open, and its own entry replaces this one at merge. (#227)
  • Add openvibe-sdk/ui-layout (also sdk.uiLayout in both entries): validate(catalog, layout) is the one
    check the server, the editor and the AI answer use, returning { ok, reasons[] } in plain words. It refuses a
    missing, hidden or undersized required part, anything pressed under 44 px (and any part under the catalog's own
    pixel minimum, e.g. Stop at 64), a part covering a required part or breaking a never_covers rule, a tint that
    is not a catalog theme token or a style/binding the part does not allow, a part the catalog does not have, and
    any URL, markup or code. applyDefaults(catalog, device?) builds the valid default arrangement (required parts
    first, every part visible, 44 px met) and merge(layout, patch) applies a patch by part id without mutating
    the layout. Browser-safe, no dependencies; exhaustive tests. (#226)

0.151.0 — 2026-11-11

  • Add related-origin passkey sign-in to openvibe-sdk/sso (opt-in relatedPasskeys: true): POST /auth/passkey/options
    and POST /auth/passkey/verify broker a WebAuthn ceremony for this site's exact origin through OpenVibe.Network as
    its OAuth client. The random browser binding stays in the site's own __Host- cookie; Network verifies the exact
    origin and RP ID and returns a single-use result that becomes the site's ordinary session. Browsers without
    PublicKeyCredential.getClientCapabilities().relatedOrigins keep the existing OAuth/passkey path.

0.150.0 — 2026-11-11

  • Add ai.turn(input, options) for file-capable ai.complete runs. Its typed output_file option stores a text, Markdown, CSV or JSON answer in Media and returns private ai.file artifact references; existing ai.files.read() reads a returned object.
  • Type the image task's owner daily cap and guest find switch in AiTaskDefaults.

0.149.0 — 2026-11-11

  • Extend the existing codes client with modelPlans.get/put/presets() and tasks.create({ plan | preset }), typed for codes.model-plan@1 on Contracts 0.406.0.

0.148.0 — 2026-11-11

  • #208: add openvibe-sdk/composer, the shared composer's client: attachments.upload(file, { onProgress, signal }) (multipart through XMLHttpRequest, real browser progress), attachments.remove(id), composer.prefs.get()/put(prefs, rev) and mentions.search(q, kinds), typed by composer.attachment@1, composer.prefs@1 and composer.mention@1.
  • #210: add feed.list({ kind, cursor }) for the cross-site Community Feed, keep the existing Community reader callable, and type preview video media.
  • #211: add the openvibe-sdk/sso one-time cross-site sign-in handoff: POST /auth/handoff mints a 60 s ticket and sso.handoff() arrival middleware redeems it before the page renders.
  • #212: add agents SDK methods to fetch run snapshots, follow resumable AG-UI event streams and post approvals.
  • #213: add wallet.changes() for the owner-scoped Vibes, OpenCoins and channel-points gain and loss window, and
    wallet.markRead() for each currency's own read cursor.
  • #214: add browser-safe player.current(), player.handoff(url) and player.on(listener) for a live stream carried
    between OpenVibe sites by Shared's floating player.
  • #215: add typed Chat readers for a person's compacted day, week and month memory, plus personal erase and opt-out.
  • #216: add exchange.createCatalogSearchClient().search(q, filters) for public model and provider offers (prices,
    measured speed and Bench quality when available), and type the image search filters and source metadata.
  • #217: add the owner-only agent sheet reader and Actor pause, resume and memory controls through Network's relay.
  • #219: the app sign-in test checks a token's shape, not the letters "eyJ" (a macOS flake).
  • #220: homeFeed.network.popular({ limit }) (the signed-out front door's five things people do now) and
    homeFeed.network.tryNext({ limit }) (the signed-in person's own suggestions).
  • #222: type the exact reply target and its display name on a Community comment; a thread's first page includes two
    replies and the rest use the parent cursor.
  • #207: add codes.tasks for the Dock's Code tab: list({ limit }), create({ text, mode, repo?, attachments? }) and
    stop(id) (codes.task-summary@1).
  • openvibe-contracts pinned at v0.406.0.

0.147.0 — 2026-11-10

  • #181: add chat.activity (the person's feed: mentions, replies, reactions, server invites, accepted friend requests and roles), rooms.react() and reply_to_id on room and DM sends.
  • #182: add the system and can_run machine metadata to the machine reader type.
  • #201: add quest.cardData({ token, timeZone }) for the shared quest card: today's quest, the public catalog kept ten minutes and the person's badges and quest log.
  • #202: add chat.embeds(message) and readEmbeds() for the typed chat.embed@1 address previews on a chat message.
  • #204: add tools.run(tool, input, { prefer }) for worker placement (auto, home, host) over JSON and multipart requests, with execution receipt types and tools.jobs.get(id) examples.
  • #205: add the browser-safe, typed userCard.createUserCardClient(core).get(username) public Network aggregate; hidden or unavailable fields stay optional and the read sends no credentials.

0.146.0 — 2026-11-10

  • #183: add the private history reader for cursor reads, search, summaries, agent recent reads, settings and owner-scoped deletes.
  • #184: add frame.online() with member presence, anonymous guest counts and live channel/site items.
  • #186: add quest.today() for the authenticated daily Quest response.
  • #195: add bot.embedUrl(robotId, mode) for panel, overlay and compact embeds.
  • #196: add merged code search, cited deep answers and cursor iteration to Search.
  • #197: add filtered, paged Inventory holdings reads and counts, plus async holding pages.

0.145.0 — 2026-11-10

  • bench.ratings.get: shared fitted ratings and confidence intervals with a twenty-game floor. Browser-safe, anonymous and typed; pairs with ai.metrics across sites.

0.144.0 — 2026-11-10

  • ai.metrics: public daily model series, up-to-six-model comparisons, task leaderboards, listed price history and hourly provider health. Typed sample counts and nullable measurements; available in browser and server clients without credentials.

0.143.0 (2026-11-10)

  • sso: a plain sign-in is quiet first. GET /auth/login with no silent, prompt or login_hint now asks the Network with prompt=none before it shows a page: a browser signed in at openvibe.network comes back signed in, with no chooser. Before, a page that needs a person (and every link to one from another site, such as "Open" in the Dock) landed on the chooser although the Network knew the browser. When the Network knows nobody (login_required), or always asks for this app (interaction_required), the callback goes on to the chooser by itself (/auth/login?ask=1, the new way to ask for the chooser outright). A person who signed out on this site (ov_sso_hint=guest) gets the chooser at once, as do "switch" and "add an account" (prompt, login_hint). The navbar's own silent sign-in (?silent=1) is unchanged: with nobody at the Network it returns to the page with ?sso=none.

0.142.0 (2026-11-10)

  • chat.rooms.memory(id) and the moderator/person writes: what a room remembers between summaries (GET /api/v1/rooms/:id/memory). memory() answers { room, entries, kinds, can_edit, me }, each entry with its kind, text, confidence, when it was learned and its sources (the message it came from). addMemory(id, { text, kind?, confidence?, source_message_ids? }) and correctMemory(id, entryId, patch) are a moderator's; removeMemory(id, entryId) removes any for a moderator and one learned from the caller's own messages for anyone; forgetMyMemory(id) forgets everything learned from the caller's messages in that room. Typed as RoomMemory, MemoryEntry and MemoryInput in types/chat.d.ts.

0.141.0 — 2026-11-10

  • community.comments.upload(ref, file) uploads a Community paste comment attachment through Community; comments.create() accepts typed attachment references.

0.140.0 — 2026-11-10

  • chat.history.pages() asynchronously iterates newest-first cursor pages; chat.history.attach() loads older pages on scroll while preserving the visible message anchor. The same helper is available to Live and Dock.

0.139.0 (2026-11-10)

  • community.comments: shared comment thread resolve/read/write/edit/delete and visibility methods, including ordered pages and paged replies.

0.138.0 (2026-11-10)

  • chat.rooms.insight(id): a room's public AI insight (chat.room-insight-result@1), as GET /api/v1/rooms/:id/insight answers it: { overview: { text, topics, people, messages, from, to, updated_at }, timeline: [{ at, kind, title, text, message_id }], state: 'ready' | 'quiet' | 'off' }. id is a room slug or global; a room the rollout does not cover answers null (404 like rooms.get). No account is needed, so an anonymous client reads it too. Typed as RoomInsight in types/chat.d.ts.

0.137.0 (2026-11-10)

  • Add moneyReasons (billing.money-reason@1): the plain words for a movement of OpenCoins, Vibes or Balance, for a service that holds the raw reason row. It re-exports openvibe-contracts' list when the installed package has it, otherwise the same list copied at Contracts 0.392.0: describe(key, { site, resolve, subject }) answers { key, text, subject: { name, url } | null, site, known }, and describeRow(row) reads { reason, app_id }. An undeclared key reads "for something on <site>" with known: false.

0.136.0 — 2026-11-09

  • Network levels client: public person, batch and board reads, and service awards.

0.135.0 (2026-11-10)

  • ov.ai.files: attach files through Media, list/read/remove them, and expose typed ai.file@1 and conversational turn shapes.

0.134.0 (2026-11-09)

  • ov.ai.creations(): the public catalogue of everything AI can make on OpenVibe (GET /api/v1/creations, ai.creation-list@1) — id, product, a plain name, what you give it and what comes back, the workflow or registered task behind it, who may use it (guest, signed-in, 18+ floor), whether it can cost coins, and where to start. Cached five minutes and CORS open; no token is sent. Types: AiCreation, AiCreationList.

0.133.0 (2026-11-09)

  • ov.blog.changelog({ service, kind, area, q, limit, before }): the network changelog's blog.changelog-entry@2 rows, newest first

0.132.0 (2026-11-09)

  • openvibe-sdk/wiki: articles on blocks (articles.create/update({ blocks }), pages.blocks), picture suggestions from the wiki.article.images task (images.suggest), and the content.wiki writer (articles.draft), whose revision is a pending proposal a person reviews.

0.131.0 (2026-11-09)

  • openvibe-sdk/community: the Feed's types complete, with pagination guidance

0.130.0 (2026-11-09)

  • openvibe-sdk/meme: ov.meme.templates(), .render() (PNG, GIF or MP4) and .get()

0.129.0 — 2026-11-09

  • openvibe-sdk/git (G2): changes and review — changes.list/get/create/comment/approve/merge/close/summary,
    issues.list/get/create/labels/close, compare(), blame(), search() and reviewModel(). Review requests
    carry the expected head SHA, merge methods are checked, and mutations are POSTs with retries disabled.

0.128.0 (2026-11-09)

  • ov.ai: tasks(), choices.get/set, run(task, input, {choice}) and admin.defaults(); runs.create accepts task and choice

0.127.0 — 2026-11-09

  • openvibe-sdk/git (ov.git): repositories, trees, blobs, commits, branches, tags, releases, contributors,
    insights and stars; commits.create() writes a branch and files without a clone; tokens.create() makes a
    short-lived clone token; clone(repo, { to: 'link' }) hands a clone to Link.

0.126.0 (2026-11-09)

  • Add openvibe-sdk/pics: the public gallery and picture reads, private uploads with resumable chunks, publication, likes and albums on OpenVibe.Pics.

0.125.0 (2026-11-09)

  • openvibe-sdk/blog: posts are written and read as Markdown or as blocks (publishing.block@1), ?format=blocks

0.124.0 (2026-11-09)

  • OpenVibe Link: openvibe-sdk/node is openvibe-sdk/link (ov.node is ov.link), building
    platform.link-capabilities@1 and mapping a Link to its platform.resource-offer@1 and
    platform.runtime-offer@1. A link principal token is never a user token. The Contracts pin moves to
    v0.372.0.

0.123.0 (2026-11-09)

  • ov.exchange: keys (list, create, revoke), private usage, deployments, and web, image and icon search with fetch (the Exchange API platform)

0.122.0 (2026-11-09)

  • dms.viewPreference() and setViewPreference(): a conversation's saved view, bubbles or room

0.121.0 (2026-11-09)

  • ov.cam.settings.get/put: a person's saved camera choices (Cam #116)

0.120.0 (2026-11-09)

  • createUploadClient(): resumable Media uploads in 8 MiB parts, three at a time, with a session reference to resume; ov.ui.progress(el)

0.119.0 (2026-11-09)

  • openvibe-sdk/wallet: the wallet, Vibes, the store and items, through Billing and Inventory with the person's bearer and idempotency keys

0.118.0 (2026-11-09)

  • openvibe-sdk/design: components() and component(name) read OpenVibe.Design's public design.component@1 registry
    (a missing name is null, a bad one is refused before a request). They are on the same createDesignClient as the
    icons; accessToken is now optional and only the icon API needs it.

0.117.0 (2026-11-09)

  • ov.cam loads Cam's kit at its versioned URL and its new hash (Cam #116 changed the kit)

0.116.0 (2026-11-09)

  • npm metadata (keywords, homepage, funding), the README's first screen and CITATION.cff

0.115.0 (2026-11-09)

  • Tools discovery: ov.tools.popular() and ov.tools.trending()

0.114.0 (2026-11-09)

  • Dock v2: Vibe conversations (ov.chat.vibe.conversations) and Actor skill runs (ov.actor.skills.run)

0.113.0 — 2026-11-09

  • Add the Feed and status posts to openvibe-sdk/community: feed({ kind, site, q, following, cursor, limit }) reads
    Community's GET /api/v1/feed (one stream over Pulse from every OpenVibe site) and posts.create({ text, pictures })
    posts up to four Media pictures with required alt text, in the Feed at once.

0.112.0 — 2026-11-09

  • Add the person's stats to openvibe-sdk/report: report.me({ days }) (GET /api/v1/me/stats, the signed-in person's own numbers), report.person(username) (GET /api/v1/u/:name/stats, only the sections they published) and report.publish({ sites, activity, made, ai, money }, { days }) (PUT /api/v1/me/stats/published, all five switches named). The answers are report.me-stats@1; a source that does not answer leaves its section out, never a zero. me and publish need the person's own session token; person is public and is a 404 when nothing is published.

0.111.0 — 2026-11-09

  • openvibe-sdk/shoes: the Open.Shoes catalogue and sizing tools.
  • createShoesClient(client) (server-side; ./shoes is browser-blocked) exposes public reads only:
    products.list({ kind, q, size }), products.get(id), products.offers(id), tools.laceLength({...})
    and sizes.convert({ system, size, brand }).

0.110.0 — 2026-11-09

  • openvibe-sdk/design: a typed client for the Design glyph API.
  • createDesignClient({ accessToken, baseUrl }) returns icons.search(q), icons.draft({ name, sentence, styles, sourceSvg })
    and icons.get(name, { style }) for the authenticated glyph draft, get and search calls. It is server-side only
    (the ./design subpath is browser-blocked) and takes a signed-in access token rather than a service token.
  • Styles are checked against the set the API accepts (thin, light, regular, bold, solid, duotone).

0.109.0 — 2026-11-09

  • Add Vibe compare methods to openvibe-sdk/chat for submitting answers and recording a user's preferred model.

0.108.0 — 2026-11-09

  • Add daily quest reads, progress, step type listings and reviewable step contributions to openvibe-sdk/quest.

0.107.0 — 2026-11-09

  • Add ov.cam.open() for the Cam recorder, with typed modes, options and file results across Node and browser exports.

0.106.0 (2026-11-09)

  • Add openvibe-sdk/home-feed and homeFeed to the top-level and browser exports, with home layout, Today frame, and personal feed methods.

0.105.0 (2026-11-09)

  • Add Report AI leaderboard, model, provider, comparison and picks reads, plus Exchange Arena start and vote methods.

0.104.0 (2026-11-08)

  • Add createAccountEmailsClient to openvibe-sdk/identity for managing the signed-in person's verified Network email addresses.

0.103.0 (2026-11-08)

  • Add openvibe-sdk/credentials: createCredentialsClient for a person's own provider credentials on OpenVibe.AI:
    list() (metadata only: kind, provider, label, created, last used, expired), add() for an API key or a browser
    session cookie, remove(), and balance() (that credential's fixed read-only balance readers: Anthropic Console
    prepaid credits and current spend, OpenAI credit grants). A secret never comes back from any call.

0.102.0 (2026-11-08)

  • Add openvibe-sdk/trust: createTrustClient({ networkUrl, getToken }) with .me() (the caller's own signals, a
    person's or a guest's token) and .of(subject) (a service token with identity.trust.read), the typed
    identity.trust@1 result for accounts and guests, and pure helpers (isNew, atLeast, the age-band order) for Node
    and the browser bundle. Signals are age bands and aggregate counts only: never an IP, email or last-seen time.

0.101.0 (2026-11-08)

  • Add bounded locker/server.inspectEnvelopePrefix validation against an actual stored ciphertext byte count.
  • Allow service-bound staged envelope receipts in share updates, with the existing recipient, nonce and atomic reseal checks.
  • Allow Locker public/private key export after importPersonKeys, so a recovered locker can upload and share using the same WebCrypto operations.

0.100.0 (2026-11-08)

  • Add openvibe-sdk/compute: shared compute strategies, hard placement constraints, RAM-aware pool scaling, exact billing arithmetic and provider quote/lifecycle adapters. See docs/compute.md for gates and unsupported purchase paths.

0.99.0 (2026-11-08)

  • openvibe-sdk/events: outboxSchema() indexes the rejected rows rejected() counts. Every outbox made with the
    helper now also gets <table>_rejected on (rejected_at) WHERE rejected_at IS NOT NULL. Before it, the count read
    the whole outbox every time it was not cached (523 ms and 112 M buffers a day on Deals, 2026-11-08), because the
    product had no index on rejected_at. A service that already has its outbox table must add the same statement in an
    expand migration: the DDL is a migration helper, never run at boot, so CREATE INDEX IF NOT EXISTS in this helper
    only reaches tables created after it.

0.98.1 (2026-11-07)

  • ai.batches.cancel says what cancelling does: only the prompts that never had a run are cancelled, and
    nothing is charged for them. A prompt that already has a run (the provider is working on it) goes on and
    settles on its own path: it keeps its status, the answer says so, and nobody is told it was not charged.
    The batch itself is cancelled only when nothing is left in flight. The same note is on AiBatchItem.reason.
    No runtime change.

0.98.0 (2026-11-07)

  • ai.runs.create (and the direct operations) may ask for the class the caller may wait in:
    { latency: 'interactive' | 'background' | 'batch' }, and for a batch run { deadlineHours: 1..72 }
    (how long the answer may take). The request carries options.latency and options.deadline_hours.
    The class belongs to the workflow: a caller may ask for a slower class and never for a faster one, and a
    faster ask is clamped by the service, not refused — the run answers with the class it really ran in
    (run.latency_class), the tier it was priced on (run.service_tier) and, when it was clamped,
    run.latency_clamped.
  • ai.runs.waitFor learns that a batch run may take hours: when the wait is over and the run is a batch
    run, the pending run is returned (its status says where it stands) instead of an error. The answer comes
    later, with the run's completion event (ai.run.succeeded) or the webhook the run was made with.
  • Add ai.batches: create(items, { model, deadlineHours, idempotencyKey }), get(id), list() and
    cancel(id) for OpenVibe.AI's /api/v1/batches — many prompts answered later, at half the retail
    price. A cancelled batch cancels the items that had not run; nothing is charged for them.
  • Add the types LatencyClass, ServiceTier, AiBatch, AiBatchItem and AiBatches, and
    CLASSES (the three classes) next to TERMINAL.

0.97.1 (2026-11-07)

  • types/menu.d.ts: MenuRequest gives url and method the | undefined that node:http's
    IncomingMessage has, and MenuResponse no longer adds an index signature, so a plain
    IncomingMessage/ServerResponse handler type-checks under exactOptionalPropertyTypes
    (Games' createMenuCardHandler). No runtime change.

0.97.0 (2026-11-07)

  • Add openvibe-sdk/menu: menuCard({ id, site, title, kind, items, more, origin, ttlSeconds, updatedAt })
    builds one validated frame.menu-card@1 with the house rules applied — plain text (markup and control
    characters stripped) cut to the contract's lengths, at most six items (the contract's cap), https links on
    the site's own origin or an openvibe.* host, and an optional item image ({ src, w, h, alt }) with the same
    rule. A bad field throws a TypeError naming it; an empty items array is valid and Network skips the card.
  • Add menuRoute(getCards, { maxAgeSeconds = 300, origin, log }): the Express/node:http handler for
    GET /.well-known/openvibe-menu.json. It calls getCards(), validates and caps the file at three cards, and
    answers Cache-Control: public, max-age=… with CORS *; a build failure or an invalid card answers []
    (never a 500) and logs once. MENU_PATH is the route.
  • TypeScript types, README ("Put your content in the OpenVibe menu"), the API surface and the pack test follow;
    the devDependency moves to openvibe-contracts v0.324.0, whose frame.menu-card@1 tests validate every card
    this module builds.

0.96.0 (2026-11-07)

  • openvibe-sdk/actor gains contacts: list(), presets(), get(), create() (optionally from a preset),
    update(), remove() and setLevel(), for a person's agents as contacts and their approval levels (plan §12
    A-1). contacts.list() is GET /api/v1/agents with the person's own token, which now answers their contacts
    (Vibe first), so agents.list() and the new agents.catalog() call the explicit public catalog
    GET /api/v1/agent-systems and keep their old meaning. tasks.create types agent_id and level.

0.95.0 (2026-11-07)

  • Add openvibe-sdk/cam: defineOvCamera({ src }) injects OpenVibe.Cam's framework-free <ov-camera> element
    once (default https://openvibe.cam/embed/ov-camera.js) and resolves to the class. It is browser-only by nature:
    calling it in Node rejects instead of touching document. OV_CAMERA_SRC is the default URL.
  • Type the element: OvCameraElement (start(), stop(), capture(), flip()), OvCameraAttributes (facing,
    filters, timer, mode, upload), OvCameraCaptureDetail / OvCameraErrorDetail, the two event types and the
    HTMLElementTagNameMap entry. upload="off" is still the default: a capture is a Blob in the page until the
    site sends it somewhere.

0.94.0 (2026-11-07)

  • openvibe-sdk/streams gains S7: schedule(), listSchedules(), cancelSchedule() and remindSchedule() for
    scheduled public agent streams; steps.post() and postStep() for any harness; digest.read() and poll.open() /
    poll.result() for a Co-pilot's service token; and onControl() to honour pause, curtain, mute and end by polling
    the stream's control log. The guards run before anything leaves the process (an scd_… id, a step's title and
    status, two to four poll options, a 10–300 second poll).
  • Add openvibe-sdk/reports: write() posts a session report with the person's own token (private by default,
    idempotent per source and task ref) and read() gets one the caller may see. The Report service redacts every text
    field; the client never sends a raw transcript.
  • Add examples/cli-agent-stream.js: about 40 lines that start a stream, spawn any CLI command, post its steps and end
    the stream with the command's result, using the same ticket path a Link desk uses.

0.93.0 (2026-11-06)

  • openvibe-sdk/exchange: the seller side gains the endpoint form, so any OpenAI-compatible server can sell, not
    only an OpenVibe Link machine. seller.offer({ endpoint, models, price, hours, concurrency, audience, name }) — with
    no machineId — registers the server on OpenVibe.Exchange's public seller API
    (POST /api/v1/exchange/seller/offers) with the same developer key the buyer side uses. The owner is the person the
    key acts for (a developer app's project owner), never a body field, and the SDK refuses an endpoint that is not https
    before anything is sent.
  • The platform reaches that address only through its egress guard (https, a public DNS answer checked inside the
    connection, never an IP literal, redirects judged again, a timeout and a response byte cap, no buyer identity in a
    header) and verifies it exactly as a machine running Link (canaries, the model fingerprint, about 1% spot re-runs, the free
    tier until verified, self-dealing earns 0). Its answers are labelled community-endpoint:<id> on the receipt.
  • seller.endpointOffers() lists the caller's endpoint offers with their verification;
    seller.removeEndpointOffer(id) removes one. machineId still selects the Network registry form.
  • seller.probe() is unchanged, and seller.offer still probes the server's OpenAI model list first when endpoint
    is given (probe: false turns that off).

0.92.0 (2026-11-06)

  • Add openvibe-sdk/chat-apps: the three chat apps (Telegram, Discord, Slack) in one place, so a service that answers
    a chat app reads them the same way (the plan of 2026-10-30, stage A4: "your agent in Telegram, Discord and Slack").
  • The checks: verifyTelegram() (the X-Telegram-Bot-Api-Secret-Token header, compared in constant time),
    verifySlack() (v0 over "v0:<timestamp>:<body>", the timestamp within five minutes), verifyDiscord() (Ed25519
    over "<timestamp><body>" with the application's public key) and verifyDelivery() (the provider names its own
    scheme; a provider this does not know is never verified). The code came from OpenVibe.Watch, which has checked
    these deliveries since 2026-11-03 (Watch #194 and #195).
  • The buttons: signCallback() and verifyCallback() carry a whole signed payload with its expiry;
    signButton() and verifyButton() carry a 16-character row id in 44 bytes, inside Telegram's 64-byte
    callback_data, and the caller checks who may press it, what it does, that it is used once and that it has
    not expired against its own row. A token that was changed, replayed or is too old is refused.
  • The messages: parseTelegramUpdate(), parseDiscordInteraction(), parseDiscordMessage(), parseSlackEvent()
    and parseSlackInteraction() give each app's payload the same shape (who said it, where, what they said,
    whether it was said to the bot, and the button that was pressed), never the app's own JSON and never a
    message the bot itself sent. splitText() splits one answer the way the app accepts it, and commandOf()
    reads a command and its arguments.

0.91.0 (2026-11-06)

  • openvibe-sdk/streams: the Agent Streams plan, stage 1 — an agent's own stream on OpenVibe.Live (a stream of kind agent, id ags_…, no stream key and no slot, published with a ticket instead of a key).
  • startAgentStream({ title, task, visibility, allowSites }) answers { stream, publish }: the stream (live.agent-stream@1) and the publish part (live.agent-stream-publish@1) with the WHIP url, the publish ticket (shown once; Live keeps only its hash) and the moment it dies.
  • renewTicket(id) and revokeTicket(id): the ticket lives four hours (the desk renews it at three) and revoking it is the kill switch — Live ends the WHIP session at once.
  • state(id, { ticket }) is what the desk polls every ten seconds: { viewers, encode, curtain, ends_at }. While encode is false the desk stops encoding and sends a snapshot every five seconds with postFrame(id, bytes, { ticket, contentType }) (JPEG or WebP, at most 200 KB).
  • setCurtain(id, up, reason) raises the desk's privacy screen (while it is up Live pauses every viewer's consumer, so a viewer sees "Private screen" and never why), setVisibility(id, v) switches who may watch, end(id) ends the stream (the card stays, with the last frame and the VOD) and viewTicket(id) mints the player's view ticket (ten minutes).
  • The publish ticket is a bearer of its own: on the desk's routes (state, postFrame) the desk sends the ticket, never the person's token. Every body is checked in the module before it leaves, and a bad id, visibility, curtain reason or an oversized frame is refused with a TypeError.
  • The capability behind it is live.agent_stream.publish for the audience openvibe.live (Contracts 0.298.0): a Link principal acting for its owner, Codes or Actor. Never a developer-app token, never another person's stream.

0.90.0 (2026-11-06)

  • Subject assertions (ADR-003, the 2026-11-06 amendment; plan H1). A money call carries a Network-signed
    proof that the person allowed the calling service to act for them, beside the name of the person.
  • requestSubjectAssertion({ userToken, audience, token | tokenClient, network | internalUrl }) asks Network to
    sign the person's own access token into a 60-second assertion for one audience (openvibe.billing). The
    person's token is the only thing that can produce one: without it Network signs nothing.
  • verifySubjectAssertion(assertion, { jwks, audience, act, seen }) checks the ES256 signature against
    Network's JWKS (the EC keys, cached like the RS256 ones), the audience, the expiry and the jti; a forged,
    expired, wrong-audience or wrong-service assertion is refused with a stable code
    (assertion.bad_signature, assertion.expired, assertion.wrong_audience, assertion.actor_mismatch, …).
  • createSubjectAssertionVerifier({ jwks, audience }) remembers the jtis it has seen, so a replayed assertion is
    refused inside the replay window.
  • subjectAssertionHeader(assertion) gives the x-ov-subject-assertion header for the call.

0.89.0 (2026-11-06)

  • Add openvibe-sdk/exchange: OpenVibe.Exchange, the buyer and the seller side of the model exchange (the plan of 2026-11-06, "For builders", item 7).
  • The buyer side speaks the Exchange’s v1 API with a Network developer key: chat() (streaming and not, the OpenAI shape), models(), offers(model), catalog(), presets() (list, create, update, delete), usage() and receipt(id). Every call takes an AbortSignal, and a 429 or a 5xx is retried with a jittered backoff: a safe method always, a write only when it is idempotent, so a completion is never run twice.
  • The seller side registers an offer in the Network’s user-owned AI offers — the same registry OpenVibe Link’s "Offer to the network" writes to (POST /internal/ai/offers/report, the capability network.resource.report), so any server that speaks the OpenAI API can be a provider and not only a Node. The SDK sends the document that registry validates, field for field, and refuses an offer the registry would refuse before anything is sent; earnings() and verification() read the seller dashboard (the tokens served, the credits earned, the canaries, the spot re-runs, the reputation, the uptime).
  • The webhooks (usage, earnings, an offer going down) carry X-OpenVibe-Signature-V2; webhooks.verify checks the raw body in constant time and refuses a timestamp more than 300 s either way.

0.88.0 (2026-11-06)

  • openvibe-sdk/share learns locked items (Locker). A share records how its item is sealed — lock: none | people | password | link, set by register(ref, { lock }) and answered by GET — and describe() cannot override it. A locked item is never public (share.lock_public), and the lock cannot change after registration (share.lock_fixed). Expand-only migration: shares.lock, share_members.key_slot, share_transfers.key_slot.
  • A people-locked item carries each member's key slot (locker.people-slot@1). addMembers/update refuse a member without a slot (share.slot_required), refuse a slot for another subject or key, and answer share.key_changed (409) when the slot's recipient_key is not the RFC 7638 thumbprint of that person's key in Network's key directory now (GET /api/v1/people/:subject/keys, cached a minute). A person with no locker yet is share.no_key.
  • Removing somebody from a people-locked item is a re-seal: the save carries envelope (base64 of a people-locked locker.envelope@1), or removeMember(..., { envelope }). The envelope must not still hold the leaving member's slot (share.reseal_invalid), and it is stored through the new onReseal(ref, envelopeBytes, tx) hook inside the transaction, before the member row goes; a hook error rolls everything back. Without an envelope: share.reseal_required; without the hook: share.reseal_unavailable. A save that carries an envelope may be up to 8 MiB; every other body stays at 64 KiB.
  • A re-seal must be fresh (share.reseal_not_fresh): its nonce base may not repeat the one the module stored last, and a staying member's slot may not be byte for byte the stored one. Either means the envelope already stored was sent again, so the person leaving could still open what is served next. The last nonce base is kept in shares.reseal_nonce_base (expand-only).
  • A transfer of a people-locked item carries the recipient's key_slot with the offer (required, checked like a member slot); accepting drops the former owner's membership unless keep_access.
  • GET for a people-locked item answers each member's key_slot, and for the owner each member's key_fingerprint and fingerprint_changed, so the sheet can warn and re-wrap.
  • The agent client gains addLocked('@name', { contentKey }) (looks up the person's key, wraps, saves the member and the slot) and removeLocked('@name', { contentKey | envelope }) (re-seals for the owner and the members who stay, then removes); share({ base, token, network }) names the key directory. transfer(to, { key_slot }) passes the recipient's slot.
  • openvibe-sdk/locker: open/rewrap accept { contentKey }, so an agent that sealed an item can open or re-seal it without a private key. openvibe-sdk/locker/server exports peopleSlotOk.
  • Pins openvibe-contracts v0.296.0 (was v0.292.0): [email protected], [email protected]_slot and [email protected]_fingerprint are the shapes the locked sharing uses.

0.87.0 (2026-11-06)

  • The 1.0 core surface is frozen now. core, auth, events, db and media are stable before 1.0:
    removing or renaming one of their exports needs a major release. docs/one-point-oh.json keeps each name's tier,
    docs/api-surface.json records every export, and test/surface.test.js now pins the exact export list of the five
    entries, so a break there is a deliberate line in a diff rather than a surprise on a pin round.
  • Removed the two auth leaving names, planned for 1.0 in docs/one-point-oh.md: createCodeVerifier and
    pkceChallenge. They were the two halves of createPkcePair, which stays frozen; no OpenVibers repository's main
    imported them (checked 2026-11-06). Use createPkcePair() for both halves.
  • signDelivery and verifyDelivery (the v1 signature) stay for now. They are still the last leaving names, but
    signDelivery is imported by the test helpers of OpenVibe.Deals, Examples, Reviews, Tips, Trade and VIP on main, so
    they go once a pin round moves those onto signDeliveryHeaders / signDeliveryV2. parseDelivery still requires
    v2 by default.

0.86.0 (2026-11-06)

  • Add openvibe-sdk/locker: the Locker envelope (locker.envelope@1), end-to-end encryption for anything on OpenVibe, in Node and in the browser. WebCrypto only (globalThis.crypto.subtle), no other crypto library and nothing hand-rolled.
  • seal and open for text and bytes, and sealStream and openStream for files: a stream holds one 1 MiB chunk at a time, so a 64 MiB file costs a few MiB of memory. A stream that ends early throws at the end: discard what it yielded.
  • The size is padded with Padmé (PoPETs 2019): at most about twelve per cent over the real size, never below 256 bytes, and O(log log n) bits of leakage. Every chunk but the last is a whole chunk; the last holds the rest.
  • A people slot names the recipient's key by its RFC 7638 thumbprint, and the reader checks it before it derives anything.
  • The PBKDF2 iteration count is bounded on both sides: 600000 to 10000000, so a hostile header cannot make a reader work for hours.
  • The three locks: people (the content key is wrapped for each recipient with ECDH P-256 and HKDF-SHA-256), password (PBKDF2-SHA-256, 600000 iterations, the parameters read from the header) and link (the key travels in the part of the link after the #).
  • Person key pairs (ECDH P-256 for content keys, ECDSA P-256 for the fingerprint), the eight-word fingerprint (sixty-four bits, in two groups of four) and the signature that proves a person owns their signing key.
  • wrapFor and rewrap: adding a member to a people-locked item re-wraps the content key and leaves the content alone.
  • Add openvibe-sdk/locker/server: what a service needs to know about an envelope without opening it, the header's shape and the envelope's size, with no key and no decryption.
  • Add openvibe-sdk/locker/vectors: the shared test vectors, so the browser module (openvibe-shared/ov-locker.js) runs the same cases and gets the same bytes.
  • Add recoveryWords(n): a phrase of words from the same 256-word list, one byte per word from the platform's CSPRNG, so 24 words carry 192 bits. isRecoveryPhrase says whether every word of a typed phrase is one of the 256, and WORDS is the list itself.
  • A people lock can now be opened by key: open(bytes, { person }) finds the slot that names the reader's key, without a subject. An envelope with no slot for that key fails with the new code locker.no_slot, which says the key is not on the list (locker.wrong_key is for a key that is on it).
  • Add inspect(bytes): what an envelope says about itself — the lock, the version, the padded size, the recipients' key thumbprints and the password's iteration count — without a key and without decrypting anything.
  • The fingerprint's two groups of four are joined by a middle dot, not a hyphen: a hyphen reads as one compound word.
  • Pins openvibe-contracts v0.292.0 (locker.envelope.v1 and the key directory API).

0.85.0 (2026-11-05)

  • Add server-only openvibe-sdk/share: PostgreSQL/PGlite store and expand-only migration, audience/role checks, exact name resolution with persistent per-caller limits, expiring membership, access requests, and recipient-bound seven-day ownership transfers. Each mutation and its append-only audit and delivery records commit atomically. Transactional owner/audience hooks update local resources.
  • Add uniform HTTP router, OpenAPI 3.1 fragment, CJS/ESM/TypeScript exports and sdk.share({ base, token }).for(kind, id) agent client. Conditional member updates detect concurrent ACL changes. Failures return generic 404 without resource content; staff overrides are audited.
  • Pins openvibe-contracts v0.291.0 (share.v1). No service integrations or media migration are included in this core package.

0.84.0 (2026-11-04)

  • openvibe-sdk/vibes: Vibes Pay for developer apps (MONEY.md, D12). A server creates a charge with its app
    token (billing.vibes.charge), the person pays on OpenVibe's pay sheet in whole Vibes, and Billing moves them
    once to the project's Vibes wallet.
  • createVibesClient({appToken | tokenProvider, billingUrl}) with charges.create({amount, description, reference, return_url}), charges.get(id) and charges.refund(id, {reason?}). amount is a positive whole number of Vibes;
    a float, zero or negative amount is a TypeError before any request. The app's reference is the
    Idempotency-Key, so a retried create is the same charge. A token provider may be a function, an object with
    getToken()/invalidate(), or one with authHeaders(); a 401 invalidates it and retries once with the same key.
    problem+json becomes VibesError with status, code, body and retryable.
  • webhooks.verify(rawBody, signatureHeader, secret) checks the raw body in constant time against
    X-OpenVibe-Signature-V2: t=<unix seconds>,v2=<hex HMAC-SHA256 of "<t>.<raw body>">, and rejects a timestamp
    more than 300 s from the server's clock either way. The legacy sha256=<hex> (or bare hex) body-only form is
    accepted. Bad input is false, never an exception. The same function is exported as verifyWebhook.

0.83.0 (2026-11-03)

  • openvibe-sdk/placement: planAi(), the AI Exchange face of the ONE planner (plan-state 2026-10-22 §E,
    G-P1/P7; decisions D1 and D5). Not a second router: plan() still answers platform.placement-result@1 and is
    untouched. Off by default — without enabled: true the planner answers nothing and no behaviour changes.
  • The hard gates, before any scoring. Each one is a reason, never a weight: the exact artifact (an exact id
    is never substituted), the commercial right (a paid managed route needs OWN_DEPLOYMENT or
    AUTHORIZED_WHOLESALE), the privacy class and the trust tier, the capabilities (tools, json, vision,
    stream), the context bound, the health, and the cost ceiling. A candidate with no rate is never assumed
    free: it is rejected with no rate: the cost is unknown.
  • The objective is the cost of one success. cost_per_success = expected_cost / max(p_success, floor), so a
    candidate that is cheaper per token but fails more often loses to one that is dearer per token and succeeds
    more. p_success comes from optional quality evidence with a confidence: sparse evidence is marked, not
    trusted
    (no confidence, or fewer than 20 samples → sparse_evidence: true, no confidence, and the probability
    is pulled back to the prior), so a thin measurement can never dominate the ranking.
  • Money is integer nano-USD (decision D1), never a float of dollars: ceil(tokens * rate_nano_per_m / 1e6)
    charged once per token class (input, cached read, cache write, output) plus the per-request fee, and the cost
    per success rounded up to a whole nano-USD. openvibe-sdk/cost gains nanoUsd, chargeTokens,
    expectedCostNano, costPerSuccessNano and formatNanoUsd; the rates travel in the flat ai.model@1
    retail_rate shape or the nested settlement shape.
  • The answer explains itself. explain.rejected is [{ id, reason }] (as ai.route-explain@1 names it) and
    explain.objective says what was optimised for. Every reason is stripped of keys, subject ids, hosts, IPs,
    URLs and paths: no secret and no internal host ever reaches the answer.
  • Hysteresis and affinity stay the ones plan() has: current is kept unless another candidate is clearly
    better (minGain), and affinity keeps a workload on the deployment it is warm on.

0.82.0 (2026-11-02)

  • openvibe-sdk/events outbox: pending() and rejected() keep their count (countsCacheMs, 30 s when
    NODE_ENV is production, off elsewhere), and callers at the same moment share one count. The metric gauges and
    readiness ask on every scrape and probe, and rejected() has no index, so a large outbox was read whole each time:
    115 ms on Deals, 56 ms on Trade, 49 ms on Sources, 45 ms on Search and 24 ms on Reviews (pg_stat_statements,
    2026-11-02). Pass countsCacheMs: 0 to count on every call.

0.81.0 (2026-11-02)

  • openvibe-sdk/vision: familiar faces (face.familiar), F1 of the L24 plan. The camera owner's own Vision
    library, reached with the owner's token:
  • list, create({ camera_ids, region, threshold }), get, update and remove the library. The region picks
    strict mode (Illinois, Texas, Washington, the EU, the UK; an unknown region too) or open mode.
  • enroll(libraryId, image, { name, subject }) names the crop the camera saw; in strict mode it answers a
    pending_consent person with no template and a private consent_request.url until the person accepts.
  • match(libraryId, image) matches only that library: a top match over its threshold, or unknown person.
  • people, patchPerson (rename or mute), removePerson, requestConsent, checkConsent, acceptConsent and
    denyConsent.
  • The types in types/vision.d.ts say what the JSON says; no template, crop or other library is ever in an answer.
  • openvibe-sdk/meme: the client for OpenVibe.Meme's encyclopedia, trends, analyser, deep dives and generator.
  • createMemeClient(client, { baseUrl? }) (both browser and server; audience openvibe.meme, service id meme).
  • search({ scope, q, status, label, kind, sort, window, channel, limit, cursor }), entry(slug), revisions(slug),
    trends({ window: 'day'|'week', limit }), analyze({ url | media_id }), research(topic) (202, poll
    researchGet(id)), saveResearch(id) and generate({ template | template_slug | topic, lines? }).
  • analyze refuses locally unless exactly one of url/media_id is given, and generate refuses with neither a
    template nor a topic; no request is sent for either. Types are in types/meme.d.ts; the browser bundle includes
    the client. The meme namespace is on openvibe-sdk and in the browser bundle, and openvibe-sdk/meme
    (createMemeClient) is in docs/api-surface.json.
  • openvibe-sdk/app-signin: "Sign in with OpenVibe" for developer apps, in one call.
  • createAppSignIn({ clientId, clientSecret, redirectUri, networkUrl, audience, scope, session }) gives a router
    with /login, /callback, /logout, /me and /health, built on the SDK's own auth helpers
    (authorization code + PKCE, the state check): nothing here re-implements OAuth.
  • The same recovery as openvibe-sdk/sso 0.73.0: a lost or expired flow and a code the Network no longer accepts
    start the sign-in once more by themselves; the second failure shows the same plain page (failurePage, now
    exported by openvibe-sdk/sso), with "Try again" and the way back. Neither ever sets a session.
  • The same client check: GET /health and checkClient() answer whether the Network accepts the app's
    credentials (a code that is never issued; cached 10 minutes; 503 only when they are refused).
  • The app token is verified offline against the Network's JWKS and only trusted when it was minted for THIS app
    (sub = app:<clientId>), before on_behalf_of names the person.
  • The session: your own store (session: { get, set, clear }) or, by default, an in-memory store with an opaque
    httpOnly cookie. The secret, the PKCE verifier and the token never reach the browser and never appear in a log.
  • openvibe-sdk/live: the rest of the Go live screen. live.slots.history(id) (the slot's past streams),
    live.slots.quality(id, { resolution, fps, bitrate }) and live.slots.titleIdeas(id, { weekday, hour, title }) (the
    card's ✨ Suggest: three titles for the next stream). With these, an app does everything Live's Go live page does
    (https://openvibe.live/docs/streams-api).

0.80.0 (2026-11-01)

  • openvibe-sdk/explicit: nudity is art. The owner, 2026-10-31: "naked and nude is fine… stop being against art on
    my platform." isExplicit() now gates what OpenVibe.Live's own rule gates (Live#469), word for word.
  • Still gated: sex acts and pornography (sexual acts or activity, having sex, porn, masturbation, intercourse,
    oral sex, penetration, pornographic or sexually explicit content), and any text that puts a child and nudity
    together.
  • No longer gated: nudity and body words on their own. A nude figure-drawing class publishes; "a child is shown
    naked" never does.

0.79.0 (2026-10-31)

  • openvibe-sdk/machines: the network's machines hub (plan L34, step M2; Contracts 0.258.0). A person's machines
    are the network's: Bot stays the registry underneath, and OpenVibe.Network fronts it on openvibe.link/machines for
    the person and for the apps the person consented to.
  • createMachines({ networkUrl, token, fetch, timeoutMs }): token returns the caller's own bearer token — the
    person's session token, or an app's token holding network.machines.read or network.machines.manage with the
    person's consent.
  • list() → { machines }, get(id) → { machine }, catalogue() → { roles } (what a machine may do for the
    network), pair() → { pairing } (a one-time code and the command to run), rename(id, name) → { machine },
    roles(id, roles) → { machine } (the whole set, not a change), revoke(id) → { machine }.
  • A refusal throws a MachinesError with the hub's own status and code (capability.denied,
    capability.owner_denied, registry.unknown_role, a 404 for a machine that is not the caller's). A hub that does
    not answer is a machines.timeout, never a hang.
  • The machines' own capabilities stay where they are: openvibe-sdk/bot's machines.desk() and machines.chat()
    talk to Bot itself and are unchanged.

0.78.0 (2026-10-31)

  • openvibe-sdk/consent: the client for the L24 consent registry. Nothing a person makes is off-limits to AI or
    data use by design; each use is off until that person turns it on in OpenVibe.Network. A service asks this check
    before it uses a person's content or likeness.
  • createConsent({ networkUrl, token, ttlMs = 60_000, fetch, log }): token returns a service token holding
    network.consent.read. allowed(subject, purpose, { kind, client, feature }) → a boolean;
    filter(subjects, purpose, opts) → the allowed subset, in input order, in batches of 500;
    onChange(event) drops a subject's answers for a purpose on network.consent.changed (wire the payload or the
    envelope); mock(map) answers from { subject: ['purpose'] } without the network.
  • Fails closed: a network error, the 2 s timeout, a non-200, a malformed answer or a token that cannot be
    minted is false for everyone asked. A failure is never cached as a yes; it logs at most once a minute.
  • An answer is cached for ttlMs (60 s), per subject, purpose, kind, client and feature; an answer in flight when
    a change arrives is not stored, so a revocation cannot be overwritten.
  • Server-side (browser: null): the token is a service token. Types are in types/consent.d.ts, the dataset-build
    example in docs/consent.md.

0.77.0 (2026-10-31)

  • AI text may describe people (the owner, 2026-10-31: "there should be no reason it can't describe people … remove
    all that crap"). openvibe-sdk/no-looks is deprecated. noLooks, noLooksTags and hasLooks return what they are
    given, and RULES_VERSION is 'off', so a service that still calls them keeps working until its next release
    removes the calls.
  • openvibe-sdk/explicit: isExplicit(...texts), the rule that stays. AI text that says a scene shows sexual
    content is not published. no-looks re-exports it.

0.76.0 (2026-10-30)

  • openvibe-sdk/placement: placement v1 — quality, the person's rules, learning and a reason (agent plan A1).
  • place(requirements, offers, ctx) is plan() with the plan's road: the person's rules and ownership, a quality
    floor (default 0.5) with a fallback, the success estimate, cost per success, escalation and one plain sentence
    saying why. It returns { result, line, ranked, rejected, fallback }; result is exactly what plan() returns
    for the offers left in, validated as today.
  • learn(outcomes, {person, key, kind, ok, at}) and successOf(outcomes, {person, key, kind, prior, now}) keep
    and read plain counts with a 14-day half-life, a Beta mean, Bench priors, the global weight min(1, 20/nG) over
    everyone else's counts (the person's rows are not counted twice) and the dotted-kind fallback.
    escalate(previous, failure, requirements, offers, ctx) handles quality, busy, offline, refused, budget and
    timeout, and asks the person after two escalations. It learns the failed try exactly once and returns the new
    counts as outcomes; the caller persists that set and must not record the same failure again.
    optionKey(offer) and lineFor(facts) are exported too.
  • On the person's own free machine the line claims only what the step asked for: harness:tools (the harness's
    own tools, including the signed-in CLI) says "your sign-in", and harness:host-access (the person's machine and
    its files) says "your files". With neither it says "your own machine".
  • plan() takes an optional success: { [offer_id]: p }: when present the cost part of the score is
    estimated_cost_usd / p, while estimated_cost_usd in the result stays the raw cost. When absent, scoring is
    unchanged.
  • Behaviour fix: user-owned is never a default. When requirements.trust is absent, plan() now uses
    DEFAULT_TRUST (first-party, partner, community, external). The contract
    (platform.workload-requirements@1) says exactly that: "when absent, first-party, partner, community and
    external. user-owned is never a default: a user-owned offer is eligible only when this list names it". A
    stranger's machine must never be a default. TRUST_ORDER keeps all five classes as the preference order;
    DEFAULT_TRUST is the four-class default. place() also requires a user-owned offer's owner to be the person
    of the step.
  • Streaming machines. A step that asks for a GPU or 2 or more cores excludes an offer whose
    health.streaming is true, with the reason "it is streaming". The field is not in
    platform.resource-offer@1 yet (Link reports screen.state in its own status, Bot's offer does not carry it),
    so this release fixes the name Link can start sending; a top-level offer.streaming is tolerated too.
  • README and [docs/placement.md](docs/placement.md): the API, the rules, the estimate, escalation and the line
    templates.

All notable changes to openvibe-sdk. The package follows semver; while it is 0.x, a minor
release may change an API and says so here.

0.75.0 (2026-10-30)

  • openvibe-sdk/redact: one secret scrubber for Link and AI (Memory M2).
  • redact(text) → { text, count } ports OpenVibe Link's internal/memory/redact.go (Node #87): the same shapes,
    the whole-text pass with a sentinel before the per-item pass, the unterminated private key block, Luhn card
    numbers, and the deliberately wide environment-assignment rule. onlySecret(text) says an item was nothing but a
    secret, so AI refuses it.
  • test/redact-corpus.json holds every case of Node's TestRedactEveryShape, the split and unterminated key
    blocks, the .env block and the harmless texts that must not change. Node copies the file and runs the same
    cases against its Go scrubber, so the two ends share one list and one corpus.
  • Pure functions, no dependencies: exported for Node and the browser, with ESM and TypeScript entries.

All notable changes to openvibe-sdk. The package follows semver; while it is 0.x, a minor
release may change an API and says so here.

0.74.0 (2026-10-30)

  • openvibe-sdk/account-data: telemetry leaves with the person, and a guard finds a table nobody covers (ADR-033).
  • createAccountData handles telemetry_samples by itself when the table exists: the person's samples are exported
    as telemetry.json (at, service, operation, resource, status, latency_ms), and a deletion anonymizes
    them (subject set to NULL, the sample kept) instead of removing them. A service that lists the table itself
    keeps its own entry.
  • uncovered(db, {tables, taskKeyed, exempt}) lists every table in the current schema with a person-shaped column
    (subject, owner, created_by, user_id, person) that the table map, the telemetry handling, a task-keyed
    list or a reasoned exemption does not cover. A service's test asserts the list is empty, so a new table fails CI
    until it is covered or exempted with a reason.

0.73.0 (2026-10-30)

  • openvibe-sdk/sso: a sign-in that recovers, explains itself and can be checked.
  • A lost or expired flow (no or another state cookie) and a code the Network no longer accepts (invalid_grant) start
    the sign-in once more by themselves. A two-minute ov_oauth_retry cookie keeps it from looping, and neither ever
    sets a session.
  • A second failure, a client the Network refuses (invalid_client) and a Network that does not answer show a small,
    self-contained page in plain words, with "Try again" and "Back to <site>", in place of a bare line of text.
  • A refused client is logged as a configuration error that names OV_OAUTH_CLIENT_ID / OV_OAUTH_CLIENT_SECRET.
  • GET /auth/health and checkClient(): a token request with a code that is never issued. invalid_grant means the
    client is fine; invalid_client that its id or secret is wrong (503). The answer is cached 10 minutes.
  • Why: OpenVibe.Cam ran for a day on the placeholder secret from its .env.example. Every sign-in ended on "The
    Network refused the sign-in code", and nothing checked.
  • siteName option for the pages.
  • legacyCookies: { access, refresh } moves a site off its own sign-in code without signing anyone out. Its old
    cookies are read as the session until the next sign-in or refresh writes ov_token/ov_refresh and clears them;
    logout clears both. httpOnlyAccess: true keeps the access token httpOnly, as those sites did (their navbar asks
    /auth/me).

0.72.0 (2026-10-29)

  • openvibe-sdk/vision (VIS-5, plan §5.8b): typed browser and server calls for Vision's image tools, private search, face privacy and consent APIs, and MCP discovery. The service enforces token grants and consent; the client preserves problem errors and binary blur results.

0.71.0 (2026-10-28)

  • openvibe-sdk/bench (BENCH-4, plan §4.12): browser-safe Bench client for kinds, tasks, pinned rounds, outside entries, judgements, boards, model summaries, streamed open JSONL datasets and private evaluations. mcpTools() lists the live Bench MCP tools through POST /mcp. Calls use the bench service origin and openvibe.bench audience; optional actingSubject sends X-OV-Subject. HTTP errors remain OpenVibeError.

0.70.0 (2026-10-28)

  • openvibe-sdk/search: the Search Fabric (Contracts 0.225.0 search.fabric.query; plan 2026-10-28 §4.5). One
    query over several scopes, routed the cheapest way that answers.
  • search.fabric(text, { scopes, freshness, depth, answer, limit, cursor, owner, type, lang, site }) returns
    search.fabric-result@1:
    - results: each has a scope and a kind (a document in the index, or a web page with its provider, URL and
    dates);
    - groups, citations, and route.explain (what each step did and what it cost).
  • Scopes:
    - network and mine (the person's own; a service passes actingSubject);
    - web: OpenVibe's open index first, then DeepSeek for a signed-in caller or a service, then Wikipedia,
    cached;
    - news, images, video, products, people, places and code.
  • search.query(text, { scopes }) is the same call; without scopes, query() is the index search as before.
  • search.providers() lists the web ladder's search.provider-offer@1 items.
  • Reads use GET, so the client may retry them.
  • Types: FabricOptions, FabricAnswer, FabricResult, FabricExplainStep, ProviderOffer; fabricOf() builds
    the query string.

0.69.0 (2026-10-28)

  • openvibe-sdk/tools: domain history (OpenVibe.Tools' /api/v1/domains, 2026-10-28). What the WHOIS and RDAP
    tools saw of a domain over time, public registration facts only (registrar, dates, status, name servers, DNSSEC;
    never contact details).
  • tools.domains.history('example.com') gives its snapshots and what changed when; it is null when nothing was kept.
  • tools.domains.lists({ limit }) gives the most looked up domains and those of them expiring soon (each looked up
    five times or more).
  • Both read without a token; a malformed name is refused in the client. Types: DomainHistory, DomainLists,
    DomainFacts.

0.68.0 (2026-10-28)

  • openvibe-sdk/food: food boxes (OpenVibe.Food's /boxes, 2026-10-28). Every meal kit, ready meal and food box
    side by side: food.boxes.list({ kind, diet, q }), .get(slug) (with the box's codes on OpenVibe.Coupons and its
    score on OpenVibe.Reviews, null when there are none) and .cost(slug, { people, meals, weeks }) (what its stated price
    comes to).
  • Each box's facts were read on the service's own pages, with the page and the day (sources, checked).
  • Types: FoodBox, FoodBoxCost, BoxKind.

0.67.0 (2026-10-27)

  • openvibe-sdk/bot: a machine's own models (AI on your own machines; OpenVibe Link v0.27.0's ai plugin).
  • bot.machines.chat(id, { model, messages, max_tokens?, temperature? }) asks one of them for one reply (bot.command@1
    kind ai) and answers { model, message, usage?, ms? }. A machine that does not answer throws an OpenVibeError
    with code bot.ai_nack.
  • bot.machines.aiModels(id, on) is the owner's "Let my AI use this computer's models".
  • Types: Machine for machines.list(), with computer.ai (the models and whether a server answers) and
    computer.ai_speed (how fast the last model answered there, Link v0.29.0); MachineChatInput and MachineChatResult;
    MachineCommandInput.kind takes ai.

0.66.0 (2026-10-27)

  • openvibe-sdk/bot: machines and desks (the Desks plan; the roadmap's SDK section). bot.machines.list(),
    .computer(id), .command(id, { kind, value }) (bot.command@1's computer kinds) and .deskTurn(id, desk).
  • bot.machines.desk(id, { name?, kind?, profile? }) is a desk of your own on a machine: a browser alone or a whole
    screen, with its own files and sign-ins (OpenVibe Link v0.19.0 to v0.26.0).
  • start, stop and status.
  • browser, screen, input and exec on that desk only, never on the person's own screen. exec takes
    detach for an app that keeps running until the desk stops.
  • turn() says whether a person is using the desk now.
  • Each method answers the machine's data. A command the machine does not do throws an OpenVibeError with code
    bot.desk_nack.

0.65.0 (2026-10-26)

  • openvibe-sdk/sso: switch account and use another account (the owner, 2026-10-26: sign-in "like the Google
    sign-in does … open to SDK and API"). GET /auth/login?prompt=select_account shows OpenVibe.Network's account
    chooser, ?prompt=login its sign-in form, and ?login_hint=<username> names the account the chooser continues as when
    the browser has it (OpenID Connect; Network's docs/sign-in.md). ?silent=1 stays prompt=none; anything else is
    dropped.
  • openvibe-sdk/chat: Vibe, support inboxes and feeds (#78; the owner, 2026-10-24: OpenVibe Chat "fully modular
    and SDK and API"). createChatClient gains vibe.* (a person's chats with Vibe), inboxes.* (the team's side of a
    support inbox: conversations, replies, members, verified visitors, saved replies, actions, assign, translate) and
    feeds.* (the owner's side of a feed; view and onLive send no token). createInboxVisitor(client, { inbox, token, user }) is a visitor's side with no account and no cookies (visit, send, messages, handoff, rate, sendPicture), and
    createFeedPublisher(client, { feed, key }) publishes vibe coding events with a feed's key (claudeCode(hookJson)).
  • openvibe-sdk/themes (#80, #81; the owner, 2026-10-25: the theme system in the SDK and API). createThemesClient
    reads and sets the account's theme on Network (catalog with seasons, active, set, setDisplay, seasons, submissions,
    import), and followTheme(fn) keeps a page, canvas or game in step with the theme someone picked on any OpenVibe site
    (readTheme(): id, mode, tokens, display, season). DISPLAY_OPTIONS.scheme is manual | auto (Shared 4.12's Light /
    Dark / Auto), and each season says its light half.
  • openvibe-sdk/play: titles.remove (#82) deletes a sandbox title you manage (OpenVibe.Play#42; a developer's
    sandbox app registers up to 3 sandbox- titles since Play#41). A production title is kept (409 play.title_kept).
  • openvibe-contracts v0.213.0 (#79).

All additions: nothing exported before changes.

0.64.0 (2026-10-25)

  • parseDelivery requires a v2 signature by default. A delivery without X-OpenVibe-Signature-V2 is refused
    unless the caller passes { requireV2: false }, which is deprecated. A v1 signature (X-OpenVibe-Signature) signs no
    time, so a captured delivery verified forever. OpenVibe.Events sends only v2, and every production receiver
    already passed requireV2: true, so nothing that runs changes. A test that signs with signDelivery and parses
    without options now gets null: sign with signDeliveryHeaders instead.
  • signDelivery and verifyDelivery are deprecated and leave in 1.0, with createCodeVerifier and
    pkceChallenge (the halves of createPkcePair, unused anywhere).
  • What 1.0 freezes, name by name. [docs/one-point-oh.md](docs/one-point-oh.md) puts each name of the five core
    entries in a tier (frozen, platform or leaving), with the reasons and how many repositories use it.
    docs/one-point-oh.json holds the lists. test/surface.test.js fails when a name there has no tier, and
    scripts/surface.js --check marks a breaking change with its tier.
  • types/events.d.ts: signDeliveryHeaders returns the timestamp and the v2 header. The type had also listed a v1
    header that the function never set.
  • no-looks knows nudity words (naked, half-naked, nude, undressed and their variants), and hasLooks skips
    character names (Iron Man, Pac-Man, Spider-Man), as noLooks already did. Live's AI moments
    job titled a clip "A Naked Man Lying On Their Back With Legs" on Live's home (2026-10-24), and a Community paste
    read "Noteworthy Half-Naked Moment". hasLooks now rejects both, and noLooks repairs sentences as it does for
    "shirtless". RULES_VERSION changes, so services that repair stored rows run their repair again.
  • no-looks knows clothes below the waist after "in" (shorts, jeans, pants, trousers, leggings, skirts,
    sweatpants, swimwear and the like): "The streamer in shorts kneels on grass…" had survived every one of
    Community's repairs since April 2026. A shop word after it ("in the pants department") or a pocket keeps the phrase.
    Team kits and uniforms are not touched.
  • openvibe-sdk/front-page (new): frontPageOk(text) says whether a title or line may lead a front page (no
    built-in slur category, no strong profanity in five languages, no hate topic), and containsCoreSlur(text) is the
    slur check alone. The categories are OpenVibe.Chat's built-in ones, ported unchanged. An anonymous screenshot titled
    with a slur sat in OpenVibe.Community's "Most viewed" (2026-10-24), and Live had its own copy of the rule for its
    quote strip; front pages now share one.
  • no-looks gains isExplicit(...texts): true when AI text says a scene shows sexual content (body parts, sex
    acts, pornography, nudity). The looks rules only remove an adjective ("a man … reaching toward their genitals"), so a
    service asks before it publishes AI text at all. Live's AI auto-clip 483 went public on openvibe.media with such a
    description on 2026-10-23. Live#344 keeps its own copy of the pattern until it pins this release. RULES_VERSION
    includes it.
  • A JWKS client with no key retries by itself on its backoff (1 s up to 5 min) until a fetch succeeds, instead of
    only when a token next needs checking. On 2026-10-23, Trade and the Tools gateway started before Network after the
    reboot and reported "no key" on /status for six hours, until a restart; their first sign-in would have waited for
    the fetch.

0.63.0 (2026-10-24)

  • The public surface is pinned. docs/api-surface.json lists every name each of the 66 entries exports and what it
    is; test/surface.test.js fails when they disagree, and node scripts/surface.js --check names each change as
    breaking (a name gone, a kind changed, a constant's key gone) or added. The first step toward 1.0's freeze of
    core, auth, events, db and media. Nothing in the package's behaviour changes.
  • Outbox relays pause on a 429. createPgOutbox (and so createServiceOutbox and the usage relay) used to hit
    Events' per-actor limit again on every tick after a 429, because the rows it claims next are under the same limit.
    Search's re-index bursts were refused 935 times on 2026-10-23, and every event was delivered only on a retry.
  • A 429 now pauses the whole relay until the limit's window reopens: its retry_after_seconds, else a minute, at
    most an hour.
  • Passes before then send nothing. The refused rows back off as before, and a 429 never rejects a row.
  • Nothing changes for a service that stays under its limit.
  • createServiceOutbox prunes sent rows. Rows sent more than pruneAfterMs ago (default 7 days; false keeps
    them) are deleted every 6 hours while the relay runs, and once a minute after start(); out.prune() runs it now.
  • Services that never called outbox.prune() kept every event they ever sent: on 2026-10-23, Work had 52,130 rows
    (119 MB) since 10-10, and Trade, Deals and Help since September or early October.
  • A service that already prunes itself is unaffected (a second prune finds nothing).

0.62.0 (2026-10-23)

  • openvibe-sdk/no-looks: pronouns. A pronoun is a guessed gender too, so the 27 Community summaries repaired on
    10-22 that still said "he looks at the camera" or "on his device" are covered. Outside quotes:
  • he and she become "the person" (the verb stays as written: "he looks" → "the person looks"; he'll → the person
    will, he'd → the person would);
  • his and her before a word become "their"; him, and her after a verb, a preposition or at the end, become "them";
  • himself and herself become "themself", hers and a final his "theirs".
    Quoted speech stays word for word. RULES_VERSION changes, so services that repair stored rows run their repair
    once more.
  • openvibe-sdk/live: where a slot publishes. live.slots.profile(slotId) reads a slot's settings and ingest
    (GET /api/streams/managed/:id/profile, owner): its stream key, Live's WHIP origin and RTMP URL, or, for a slot
    OpenRestream ingests, OpenRestream's own WHIP and RTMP URLs and no key. live.whip.forSlot(slotId) turns that into
    { url, key, authority } to pass to live.whip.publish(), so going live from code no longer builds the WHIP URL by
    hand (OpenVibe.Examples' go-live example, plan G2).

0.61.0 (2026-10-22)

  • openvibe-sdk/no-looks: the owner's rule that public AI text never describes a person, as one shared filter.
    noLooks(text), noLooksTags(tags), hasLooks(title) and RULES_VERSION, moved from OpenVibe.Live
    (Live#298–#302) with its test corpus, so Community, Search, MediaHub and every service that stores public AI text
    run the same rules. Pure and dependency-free (Node and browsers).

0.60.0 (2026-10-20)

  • openvibe-sdk/host and openvibe-sdk/news: the public reads of the last two products without a module.
    Host: the hosting catalog (providers, plans, promos, community offers, domain endings with registrar prices, a
    name's availability, YABS benchmarks), with Host's own filters (any other key is refused). News: topics and the
    published and retracted stories with their sources, timeline and perspectives. Both are sent without a token;
    editing stays with each site's editors.

0.59.0 (2026-10-20)

  • openvibe-sdk/space (createSpaceClient): OpenVibe.Space for apps and CI, public in openvibe-contracts 0.209.0.
    Deploy a static site as a tar.gz archive or files with their paths (activate at once, or keep a preview), activate,
    roll back, read a deploy's log; manage projects (members as deployer, maintainer or owner), sites, their serving
    settings and Git source, and custom domains. An app is a principal of its own in Space and reaches only the
    projects a person added it to. Uploads and creations are never retried on their own.

0.58.0 (2026-10-20)

  • openvibe-sdk/coupons, openvibe-sdk/deals and openvibe-sdk/trade: what a person does for themselves
    on those sites, public in openvibe-contracts 0.208.0. Coupons: which store a site is, its codes, submitting a code and
    reporting whether one worked (what a browser extension needs). Deals: hot and new offers, posting, price
    observations, votes, flags and watches. Trade: instruments with their sourced observations and filings, and the
    person's watchlists and alert rules. Moderation stays with each site. Writes that make a new row each time are
    never retried on their own.

0.57.0 (2026-10-20)

  • openvibe-sdk/reviews (createReviewsClient): OpenVibe.Reviews for apps, whose reads became public in
    openvibe-contracts 0.207.0: resolve a business, product or place from its identifiers (a match or candidates), list
    and read entities with their aggregate, signals, summary and history, and send a correction to the editors for the
    person (never retried on its own).
  • openvibe-sdk/wiki (createWikiClient): OpenVibe.Wiki's reads: search, spaces, and pages with their revisions,
    diffs and citations, as the person may read them (without one, public pages only).

0.56.0 (2026-10-20)

  • openvibe-sdk/blog (createBlogClient): OpenVibe.Blog, the first product whose API only its own site could use
    (its capabilities became public in openvibe-contracts 0.206.0). Read a blog's published posts and feed; for the
    person who authorized the app, create drafts, edit (a new revision, expected_revision guards a race), publish,
    unpublish, schedule, revert and delete posts. Blog acts for an app only as its on_behalf_of person. Creating a
    post or reverting makes a new one each time and is never retried on its own.

0.55.0 (2026-10-20)

  • Six more products through the SDK, each a server module over the routes its capabilities name in Contracts:
  • openvibe-sdk/food (createFoodClient): places to get food, the food list with prices, meal plans, a
    person's pantry with its "what can I make" matches, and AI recipe ideas.
  • openvibe-sdk/actor (createActorClient): tasks for AI agent systems (create, list, get, cancel, and
    tasks.events, the SSE stream until the task ends), the public agent list and dry-run routing, and a person's
    own agents with their runs and memory.
  • openvibe-sdk/run (createRunClient): jobs on worker nodes (submit with an Idempotency-Key, list, get,
    cancel, jobs.events until end, a browser stream ticket), forProject for X-OV-Project.
  • openvibe-sdk/video (createVideoClient): OpenVibe.Video's playlists of recordings and clips, and a
    person's public playlists.
  • openvibe-sdk/work (createWorkClient): the job board's search and sources, saved searches, tracked
    applications and the agent's help with one.
  • openvibe-sdk/zone (createZoneClient): a project's object zones and their usage.
  • A write that makes something new each time (a plan, recipe ideas, an agent, a run, a memory note, a playlist or an
    item in it, a saved search, an application without a listing) is never retried on its own. Keyed writes (Run's
    submit, Zone's create, an Actor task with an idempotency_key, an application for a listing) and changes are.

0.54.0 (2026-10-20)

  • openvibe-sdk/tips: OpenVibe.Tips through the SDK (it had 19 capabilities and no module). createTipsClient(client)
    covers its /api/v1: a creator's tip page (profiles), tipping (tip → { interaction, checkout_url, checkout_ref }) and paid messages, TTS, media and sound requests (send.*), receipts and external tips
    (interactions), goals, moderation (queue, log, moderators), overlays (tokens, configs, a test alert) and the
    supporter's own me.export / erase. Tips replays an Idempotency-Key once, so every write carries one and a
    retried tip is safe.

0.53.0 (2026-10-20)

  • openvibe-sdk/inventory: OpenVibe.Inventory through the SDK, as the owner asked of it ("cross site network wide
    shared multi purpose modular system … through sdk / api"). createInventoryClient(client) covers its /api/v1: the
    public reads (kinds, definitions, a person's items and equipped, equipped([subjects]) for up to 100 at once),
    the person's own (me.items, me.equip, instance_id: null clears a slot), and an issuer's (definitions.create /
    update, grants.create → { instance, created }, instances.consume / revoke). A grant is retried safely: its
    body's idempotency_key is what Inventory dedupes on. forPerson(usr_…) is the same client sending X-OV-Subject
    for a service acting for a person. Ids are checked against Inventory's own rules before any call.

0.52.0 (2026-10-20)

  • openvibe-sdk/live: OpenVibe.Live in the SDK, its flagship product had no module. createLiveClient(client)
    covers the routes documented at openvibe.live/documentation: who is live (streams.list, recentlyOnline), a
    channel (channels.get, url, toggleFollow), slots (slots.create … regenerateKey), going live
    (goLive(slotId, opts) → { stream, endpoint }), the session (update, heartbeat, endpoint, end), the content
    feeds (content.feed, content.moments), and WHIP publishing (whip.url, whip.publish → { answer, location },
    whip.end). Signed calls carry the person's Network token; /whip gets only the stream key, never the client's
    token. Writes carry no idempotency key and are never retried (a second go-live is a second session).

0.51.1 (2026-10-17)

  • openvibe-sdk/sso: GET /auth/me for a signed-out visitor (or an expired, foreign or non-session token) answers
    200 { user: null } instead of 401, as Blog, Community and Wiki do. The shared navbar treats both the same; the 401
    showed as a failed request in every signed-out visitor's console.

0.51.0 (2026-10-17)

  • openvibe-sdk/commerce: subscription tiers priced by OpenVibe.Billing (openvibe-contracts 0.201.0). createIntent
    (kind subscription) and subscribeWithCredit take an optional tier; listTiers(creator) reads a creator's tier
    prices and setTier({ creator, tier, priceCents, status?, key }) sets one (caps.tier = billing.tier.manage;
    listTiers reads with the intent capability when caps.tier is not given). Without a tier nothing changes.

0.50.1 (2026-10-17)

  • openvibe-sdk/db: a nested db.tx (a savepoint) marks its scope done when it ends, like the outermost one. Async
    work started inside a savepoint and still running afterwards (a timer, a fire-and-forget call) no longer keeps
    using the transaction's connection after it goes back to the pool.
  • Outbox: the relay's timers run in the async context the outbox was created in. A kick() from inside a
    transaction that stays open (an inbox handler that goes on fanning out) used to make the relay's ticks run inside
    that transaction, on its connection. OpenVibe.Network logged "Client was closed and is not queryable" for about a
    minute after each go-live notice.

0.50.0 (2026-10-16)

  • openvibe-sdk/play: cloud saves — play.saves.put(title, player, slot, { data, version }), get (null when the
    slot is empty), list and remove, for OpenVibe.Play's new save slots (8 per player per title, 64 KB of JSON each,
    versioned: a stale version is 409 play.save_conflict). Same token and capabilities as scores and achievements.

0.49.1 (2026-10-16)

  • Outbox: Events' 409 events.loop_detected is a permanent refusal: the row is marked rejected instead of being
    retried with backoff forever (Search retried 18 such events up to 35 times each after a Reviews loop was fixed).
    Every other 409 is still retried.

0.49.0 (2026-10-15)

  • openvibe-sdk/download: createDownloadClient({ key }) for OpenVibe.Download (the owner's "file storage and backup"):
    files.list/all/get/remove, folders.list/create/ensurePath, upload({ path | data, name, folderId }) in the
    drive's parts, usage(), and `sy