Skip to content

v1.1.2

Choose a tag to compare

@github-actions github-actions released this 07 Oct 04:04
· 204 commits to main since this release
cae0695

Added

  • definePlans and forPlans in @intelligo-dev/billing/plans make a
    catalogue's slugs a type: a plan's slug must equal its key, and a
    feature matrix or per-plan map that names a plan the catalogue lacks
    (chat: ["free", "por"]) fails to compile instead of denying the
    feature at runtime. The scaffold's lib/plans.ts uses them.

  • Each config seam declares where it runs (boundaries in the
    registry's requires.json): client when the browser bundles it,
    server otherwise, derived from the items' imports by an architecture
    test. intelligo doctor warns when a client seam imports server code
    (server-only, a server @intelligo-dev/* module, a Node built-in) or
    reads a non-public environment variable, and the config-seams page says
    which seams the browser bundles.

  • intelligo --version, intelligo <command> --help for one command's
    usage, and documented exit codes: 0 done, 1 a check found problems or
    the command failed, 2 the command was used wrongly (an unknown command
    or a missing argument now exits 2).

  • intelligo doctor asks the database when DATABASE_URL is set (and
    --offline is not): whether the framework's and the app's migrations
    are applied, whether pgvector is available, and whether the billing
    row's currency is the one lib/billing-config.ts formats. It accepts
    AUTH_SECRET as the app does (with a note to rename it), warns about a
    Resend key without EMAIL_FROM and, in production, a non-https URL or a
    test Stripe key. Its migrations line now says what it checks: the
    framework chain's integrity, not the database.

  • --json on doctor, upgrade --check and sync --check, for CI.

  • intelligo upgrade --diff <path> shows how the app's copy differs from
    the current template, and upgrade --accept <path> keeps the app's copy
    and stops reporting it as a conflict until the template changes again.

  • intelligo sync records each seam's shipped default, and sync --check
    reports seam-changed when a release changes it (the app's copy is still
    kept); sync --diff <path> shows the change. Files an earlier release
    shipped and none of the app's items do now are reported orphaned.

  • intelligo remove <item> deletes the files only that item ships and
    forgets it, keeping its seams; it refuses an item another needs.

  • intelligo sync --force copies the files it replaces to
    .intelligo/backup/<time>/ first.

  • executionRefusalResponse(run) in @intelligo-dev/next/route: the chat
    route's answer to a refused execution for any route — 503
    MODEL_UNAVAILABLE or BILLING_NOT_CONFIGURED, 402 QUOTA_EXCEEDED.
    The scaffold's assistant route uses it with withWorkspace (template
    1.20.0).

  • assertEnv({ extra }) and validateEnv({ extra }) check a product's own
    variables beside the framework's; the thrown error names the missing
    variables, and validateEnv warns about a pair set by half (OAuth id
    without secret, a Stripe key without its webhook secret, a storage
    bucket without credentials).

  • LOG_LEVEL sets how much the server logs.

  • clearUpgradeMessages and clearActionLabels, for tests.

Fixed

  • A conversation with an inline image no longer sends the image again
    with every later message. useChat sends the whole transcript each
    turn, so a 3 MB image added about 4 MB of base64 to every request
    after it, past a serverless body limit for the rest of the chat. The
    chat item's transport uses sendWithoutEarlierFiles from
    @intelligo-dev/chat/client, which replaces earlier messages' inline
    bytes with a placeholder, and the handler puts the stored file back
    (or drops the part when that message was never stored).

  • A conversation opens on its latest 100 messages, with "Show earlier
    messages" for the rest (loadEarlierMessages), instead of reading its
    whole transcript on every load. An approval continuation reads the one
    message it checks. @intelligo-dev/core/conversations adds
    getRecentMessages (a page, oldest first, with hasEarlier),
    getMessage and getMessagesByIds. A turn on a reopened long
    conversation shows the model what the thread holds, windowed as
    before; turn.history() still reads everything.

  • Registry items name a version range for every npm dependency. The
    build pins each one to the range the registry is developed and tested
    against (its own package.json), so an item installs the major it was
    written for rather than whatever is newest that day; @intelligo-dev/*
    stays unversioned, since intelligo create installed it and sync
    refuses another release's pages.

  • intelligo sync delivers reworded registry copy. Message files kept
    the app's value for every key, so a changed sentence or argument never
    arrived and nothing said so. A sync now records a short hash of each
    message it shipped (registry.messages in the manifest): a value the
    app never changed takes the new text, and a key the registry dropped
    goes unless the app reworded it. sync --check reports
    messages-changed for the app's own wording of a key whose registry
    text changed, and fails with messages-arguments when that wording
    uses an ICU argument the registry's text no longer passes. The first
    sync after upgrading records the hashes and changes nothing.

  • The scaffold registers per-plan chat request limits (RATE_LIMITS in
    lib/plans.ts, 10 a minute on free and 60 on pro). It registered
    none, so every plan got the conservative default of 10.

  • A key a new release adds arrives in English only, and an app whose
    default locale is not English threw on it until translated: the
    scaffold's i18n/request.ts falls back from the locale to the default
    locale to English (app-scaffold template 1.21.0).

  • The pricing and billing-settings pages read actionRoles from their
    config seams with in, so an app whose lib/plan-card-config.tsx or
    lib/credit-bundle-config.tsx predates the field — intelligo sync
    keeps seams as they are — compiles again. Since 1.1.0 it failed to
    type-check.

  • A chat turn holds what it can spend. Admission held one model call
    against a fixed 16K-token prompt, while a turn with tools may call the
    model once per step and a long conversation sends more than 16K; the
    shortfall was forgiven at settlement. executions.begin takes a
    workload (inputTokens, steps), estimateWorstCaseCharge scales by
    it, and the chat handler passes the prepared prompt's estimate and, for
    an agent with tools, maxSteps.

  • A chat turn that errors after its first steps is charged for them. The
    stream error released the hold, so every step the provider had already
    billed was free; the finished steps (and a step the provider had begun
    to answer) are now settled.

  • Tokens a tool spent on another model are recorded however low the
    balance has run: executions.begin({ alreadySpent: true }) skips
    admission for work that already happened, and the chat handler uses it
    for that usage, which a refusal used to drop.

  • A product's deriveTitle runs once the turn is admitted, so a refused
    turn no longer pays a model to name its conversation.

  • Two plan grants to one workspace at the same moment — two local
    invoices settled together — both count: grants take turns on a
    per-workspace lock, so the second adds its days to the first's end
    instead of both starting from the same one.

  • A job whose handler kills its worker every time stops being reclaimed
    once it has used its attempts, and is recorded failed. drain takes
    deadlineMs: jobs it claimed but had no time to start go back with the
    attempt unspent, reported as deferred.

  • An execution that receives usage after it was failed — abandoned by the
    stale sweep while it still ran — logs it and records an
    execution.usage_after_failure audit event instead of dropping it
    without a trace.

  • A local invoice its provider's callback names is asked about again
    even when it was recorded failed, and granted if it was paid after all
    (settleLocalInvoice({ recheckFailed })).

  • Uploaded files are deleted. Nothing called listOrphanAttachments, and
    a deleted workspace or user took its attachment rows with it and left
    the objects in the bucket. The database now queues an object's key
    whenever its row is deleted (storage_deletions), and
    sweepAttachments({ storage, olderThan }) from
    @intelligo-dev/core/attachments deletes uploads no conversation
    claimed, then the queued objects; a key leaves the queue only once
    storage has deleted it. The maintenance route runs it when a storage
    adapter is bound (maintenance template 1.3.0).

  • Looking up a request's usage by workspace and request id uses an
    index (usage_records_workspace_request_idx) instead of scanning the
    workspace's records.

Security

  • The platform-admin role keeps only what the console uses of
    Better-Auth's admin plugin — finding users and impersonating one — and
    /admin/impersonate-user is closed over HTTP
    (SERVICE_ONLY_ADMIN_PATHS): setting roles, passwords and emails,
    banning, deleting and raw impersonation went around the reason and the
    audit event.

  • An impersonation's reason is recorded on its admin.impersonation.started
    event, and one that the session then refused leaves an
    admin.impersonation.refused event instead of a start that never
    happened (requireAdminOrRefuse takes metadata).

  • A promotion from PLATFORM_ADMIN_EMAILS is audited
    (admin.platform_admin.granted, via the allowlist), and
    intelligo admin revoke <email> takes the role away, ends the user's
    sessions and audits it, warning when the allowlist would promote the
    address again. requirePlatformAdmin returns promoted.

  • @intelligo-dev/admin's cross-tenant read models check that a platform
    admin is asking, so a page that forgot requireAdmin refuses instead of
    listing every tenant. Calling them outside a request (a script) reads the
    tables directly instead.

  • Deleting an account needs a session signed in within the last day and
    refuses an impersonated one (reauthentication_required, with a message
    in profile-settings).

  • Email logs mask the recipient in production and leave the subject out,
    which can carry user-written names.

  • Model-written Markdown loads images only from hosts it is given. An
    image URL fetches on render, and a reply steered by what the model read
    could send the reader's address, and anything put in the URL, to any
    server — on a shared chat, for every visitor. The ai-markdown
    component's Markdown takes imagePrefixes (none by default), and the
    chat item's lib/chat-config.tsx takes imageHosts for replies.

  • An invitation email's subject no longer carries the inviter's or the
    workspace's name — user-written text in a line signed with the
    product's domain — and the server refuses a display name over 50
    characters or one containing a link, which only the sign-up form did.

  • A chat upload with no declared length is read only up to the limit
    instead of buffered whole, and a stored attachment is served inline
    only when it is a kind that runs nothing (raster images, PDF, plain
    text); an uploaded SVG or HTML page downloads instead.

  • Deleting a workspace no longer deletes its trial grant: the row stays,
    detached (trial_credits.workspace_id is set null), so the per-email
    and per-address trial limits still count it and a deleted workspace is
    not a way to a fresh trial. The expiry sweep expires a detached grant
    and tells nobody.

  • Better-Auth counts sign-in, sign-up and reset attempts in the database
    (rate_limits) instead of in each process, so several instances, or a
    serverless deployment, share one limit.

  • Every audit row keeps its actor's email as it was when the row was
    written (audit_events.actor_email, filled by the database), so the
    trail still names someone after the account is deleted, and both audit
    tables refuse TRUNCATE.

  • A shared conversation's link is a random token rather than its id
    (conversations.share_token), and sharing it again after unsharing
    issues a new one, so a link handed out before stops working.
    getPublicConversation and getPublicMessages take the link's
    segment, and shareRef(row) gives it; a conversation shared before
    this release keeps answering to its id until it is shared again.

  • Dependencies moved past their published advisories without new
    overrides: Next.js ~16.3.8 (the scaffold's package.json too,
    app-scaffold template 1.19.0), and in the lockfile sharp, dompurify,
    devalue, smol-toml, source-map-js, http-cache-semantics and the
    MCP SDK to their patched releases. Two scoped overrides cover what no
    parent allows yet: katex ^0.18.2 (held at 0.16 by @streamdown/math)
    and js-yaml@3>argparse ^2.0.1, which drops sprintf-js (js-yaml 3
    uses argparse only for its command-line binary). pnpm audit reports
    one advisory with no release at all: braces, reached through shadcn's
    build tooling with patterns the developer writes.

Changed

  • create prints the database, migration and admin steps after
    scaffolding. add with no feature no longer lists retired ones. sync
    records the framework version it installed in the manifest, and refuses
    to install pages for a version other than the installed packages'
    without --force. The CLI finds @intelligo-dev/* packages the way
    Node does, from the app upward, so a dependency hoisted to a workspace
    root is found and an app's own packages/core is not taken for the
    framework's.
  • Plan allowances in another currency than the billing row still count
    as none, but the mismatch is logged once instead of every plan reading
    as zero without a word.

Upgrading

Run intelligo migrate before deploying: 0007_schema_hardening adds
the auth rate-limit table, the audit actor email, share tokens and the
storage deletion queue, and keeps trial grants when a workspace is
deleted. It only adds or loosens, so it can run ahead of the deploy.

Run intelligo sync for every item: the items now name dependency
ranges, the chat sends earlier files without their bytes and opens long
conversations on their latest page, and the manifest starts recording
the message hashes later syncs compare against. Allow image hosts for
replies in lib/chat-config.tsx (imageHosts) if your agent shows
images. intelligo upgrade --check lists the scaffold files that
changed (app-scaffold 1.21.0, maintenance 1.3.0): i18n/request.ts
falls back to English, lib/plans.ts gains definePlans and
RATE_LIMITS (register them with registerRateLimits), and the
maintenance route sweeps uploads; intelligo upgrade --diff <path>
shows each change.

A shared conversation's link is now a token. Links handed out before
keep working until the conversation is shared again. Code that called
getPublicConversation or getPublicMessages with an id passes the
link's segment (shareRef(row)).

Platform admins keep only finding users and impersonating through the
console; anything else of Better-Auth's admin plugin is gone from the
role. Deleting an account needs a sign-in from the last day.