Repository navigation
v1.1.2
Added
-
definePlansandforPlansin@intelligo-dev/billing/plansmake a
catalogue's slugs a type: a plan'sslugmust 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'slib/plans.tsuses them. -
Each config seam declares where it runs (
boundariesin the
registry'srequires.json):clientwhen the browser bundles it,
serverotherwise, derived from the items' imports by an architecture
test.intelligo doctorwarns 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> --helpfor 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 doctorasks the database whenDATABASE_URLis set (and
--offlineis 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 onelib/billing-config.tsformats. It accepts
AUTH_SECRETas the app does (with a note to rename it), warns about a
Resend key withoutEMAIL_FROMand, 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. -
--jsonondoctor,upgrade --checkandsync --check, for CI. -
intelligo upgrade --diff <path>shows how the app's copy differs from
the current template, andupgrade --accept <path>keeps the app's copy
and stops reporting it as a conflict until the template changes again. -
intelligo syncrecords each seam's shipped default, andsync --check
reportsseam-changedwhen 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 reportedorphaned. -
intelligo remove <item>deletes the files only that item ships and
forgets it, keeping its seams; it refuses an item another needs. -
intelligo sync --forcecopies 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_UNAVAILABLEorBILLING_NOT_CONFIGURED, 402QUOTA_EXCEEDED.
The scaffold's assistant route uses it withwithWorkspace(template
1.20.0). -
assertEnv({ extra })andvalidateEnv({ extra })check a product's own
variables beside the framework's; the thrown error names the missing
variables, andvalidateEnvwarns about a pair set by half (OAuth id
without secret, a Stripe key without its webhook secret, a storage
bucket without credentials). -
LOG_LEVELsets how much the server logs. -
clearUpgradeMessagesandclearActionLabels, for tests.
Fixed
-
A conversation with an inline image no longer sends the image again
with every later message.useChatsends 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
chatitem's transport usessendWithoutEarlierFilesfrom
@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/conversationsadds
getRecentMessages(a page, oldest first, withhasEarlier),
getMessageandgetMessagesByIds. 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 ownpackage.json), so an item installs the major it was
written for rather than whatever is newest that day;@intelligo-dev/*
stays unversioned, sinceintelligo createinstalled it andsync
refuses another release's pages. -
intelligo syncdelivers 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.messagesin 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 --checkreports
messages-changedfor the app's own wording of a key whose registry
text changed, and fails withmessages-argumentswhen 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_LIMITSin
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'si18n/request.tsfalls back from the locale to the default
locale to English (app-scaffold template 1.21.0). -
The
pricingandbilling-settingspages readactionRolesfrom their
config seams within, so an app whoselib/plan-card-config.tsxor
lib/credit-bundle-config.tsxpredates 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.begintakes a
workload(inputTokens,steps),estimateWorstCaseChargescales 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
deriveTitleruns 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 recordedfailed.draintakes
deadlineMs: jobs it claimed but had no time to start go back with the
attempt unspent, reported asdeferred. -
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_failureaudit 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/attachmentsdeletes 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-adminrole keeps only what the console uses of
Better-Auth's admin plugin — finding users and impersonating one — and
/admin/impersonate-useris 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.refusedevent instead of a start that never
happened (requireAdminOrRefusetakesmetadata). -
A promotion from
PLATFORM_ADMIN_EMAILSis 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.requirePlatformAdminreturnspromoted. -
@intelligo-dev/admin's cross-tenant read models check that a platform
admin is asking, so a page that forgotrequireAdminrefuses 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
inprofile-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. Theai-markdown
component'sMarkdowntakesimagePrefixes(none by default), and the
chatitem'slib/chat-config.tsxtakesimageHostsfor 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_idis 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 refuseTRUNCATE. -
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.
getPublicConversationandgetPublicMessagestake the link's
segment, andshareRef(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'spackage.jsontoo,
app-scaffold template 1.19.0), and in the lockfilesharp,dompurify,
devalue,smol-toml,source-map-js,http-cache-semanticsand 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)
andjs-yaml@3>argparse^2.0.1, which dropssprintf-js(js-yaml 3
uses argparse only for its command-line binary).pnpm auditreports
one advisory with no release at all:braces, reached through shadcn's
build tooling with patterns the developer writes.
Changed
createprints the database, migration and admin steps after
scaffolding.addwith 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 ownpackages/coreis 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.