102 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
[Unreleased]
Breaking Changes
-
@openmaic/storage0.34.0: course ownership is no longer recorded ondocument_stages.PgDocumentStorenever reads or writesdocument_stages.owner_id; an owner-bound store scopes listings, writes, deletes, the freshness manifest and folder membership through the host's ownership relation, named with the newdocumentOwnershipoption ({ table, stageIdColumn?, ownerIdColumn?, tombstoneColumn?, claimOnCreate? }). The relation must cascade with the document rows (REFERENCES document_stages(id) ON DELETE CASCADE) or be cleaned alongside them; a leftover ownership row keeps the id reserved for its owner.documentOwnership: falseturns document scoping off, and on an owner-bound store it requiresallowCrossOwnerDocumentAccess: true, so binding an owner for folders or asset principals cannot silently expose every owner's documents.forOwner(ownerId)(orownerId) withoutdocumentOwnershipnow throws at construction instead of silently scoping nothing. A store that is not owner-bound is tenant-agnostic: it lists and writes every document, where it used to see only documents with no owner.AssetCollector'sassetReferencePrincipalsrequiresdocumentOwnershiptoo, because the backfill reads each document's owner from that relation. A newfolders: falseoption lets a host whose owndocument_stageshas nofolder_idcolumn use the store without folders. Fresh installs no longer get theowner_idcolumn or its two indexes (document_stages_owner_idx,document_stages_owner_folder_idx);document_stages_folder_idxreplaces the latter. -
Server persistence:
stage_metais the only record of who owns a course. At startup, courses whose owner was recorded only ondocument_stages.owner_idare adopted intostage_meta(idempotent, logged with a count; where the two records disagreestage_metastands and the disagreement is counted). To roll back to a release that still reads the column, first copy ownership back for courses created since the upgrade:UPDATE document_stages AS d SET owner_id = m.owner_id FROM stage_meta AS m WHERE m.stage_id = d.id AND d.owner_id IS NULL. -
Server persistence: runtime sessions (
/api/persistence/runtime/*) are keyed by the owner the owner identity seam resolves, not by a client-suppliedx-learner-keybehind the development token.PERSISTENCE_DEV_TOKEN,NEXT_PUBLIC_PERSISTENCE_TOKENandPERSISTENCE_ALLOW_INSECURE_DEV_AUTHare removed and ignored; the browser learns its learner key fromGET /api/persistence/learner-key. Runtime sessions written before this change were keyed by a browser-minted learner key and are no longer reachable; they are not migrated, because trusting a client-supplied old key would restore client-chosen identity. Course documents and media are unaffected. If the development token was your only access gate, put the deployment behindACCESS_CODEor a gateway, register owner auth methods, or turn server persistence off before upgrading: without the token the endpoint serves every visitor as their own anonymous owner. -
Server persistence: assets are allocated in a per-owner partition, so
ASSET_QUOTA_BYTESis a per-owner ceiling and only the owner can replace or delete an entry. Other owners read an entry by id while a live course of the entry's owner references it, and a document write references and commits only its owner's entries (and legacy ones), so naming another owner's id records nothing. Entries in the old shared partition stay readable by id, and can be replaced or deleted only by an owner who owns every course referencing them.
Deprecated
document_stages.owner_id: the document store no longer reads or writes it (only the boot backfill reads it, and a claim mirrors the new owner into it while it exists, so a rollback still finds consistent ownership). Existing installations keep the column (made nullable, its default removed) for this release so a rollback still finds it; it will be dropped in the next release.
Upgrade notes
- The first start creates the
owner_mergestable (with thestage_metaand owner-material schemas). Nothing is claimed until a claim is triggered; the default trigger is the explicitPOST /api/identity/claim. - The first start on an existing installation changes
document_stagesunder table locks.CREATE INDEX document_stages_folder_idxbuilds the new index under a SHARE lock, which blocks writes todocument_stagesfor the length of the build.DROP INDEXofdocument_stages_owner_idx/document_stages_owner_folder_idxtakes a brief ACCESS EXCLUSIVE lock. If a host addedNOT NULLor a default toowner_id,ALTER TABLE ... DROP NOT NULL/DROP DEFAULTalso takes ACCESS EXCLUSIVE, which blocks reads too and waits behind any open transaction on the table. Upgrade in a quiet window, or start one instance first and let it finish before the rest start (concurrent starts are serialized anyway; see Bug Fixes). Once done, later starts change nothing. - To keep the index build off the start path on a large table, create the index and drop the old ones yourself beforehand, outside a transaction:
CREATE INDEX CONCURRENTLY IF NOT EXISTS document_stages_folder_idx ON document_stages (folder_id, id) WHERE folder_id IS NOT NULL;,DROP INDEX CONCURRENTLY IF EXISTS document_stages_owner_idx;,DROP INDEX CONCURRENTLY IF EXISTS document_stages_owner_folder_idx;. The start then builds and drops nothing. It still takes and releases the same momentary SHARE lock that every otherCREATE INDEX IF NOT EXISTSin the schema takes on each start.
Features
- Owner identity: one resolution entry point with composable auth methods. A host registers an ordered list of
OwnerAuthMethods once frominstrumentation.tswithconfigureOwnerAuthentication({ methods, anonymousFallback? })(single-shot, sealed on first use, validated at boot). Each method answersauthenticated,not-applicable(no credential of its kind) orinvalid(its credential is present but bad); core keeps the firstauthenticatedanswer, refuses anyinvalidanswer with401 INVALID_CREDENTIALwithout asking later methods, and when no method applies falls back to the anonymous cookie owner, or answers401withanonymousFallback: false. Route handlers and Server Actions ask the same methods in the same order, once per request.PERSISTENCE_SHARED_OWNER_IDstill selects thesharedTeamowner on its own; beside host methods it is kept by listingsharedTeamAuthMethod()last, and setting the variable while a registration leaves it out fails startup. Identity gateways are covered by a documented recipe (a host method that verifies the gateway's signed JWT against the identity provider's keys), not by a built-in. Host methods live inlib/server/identity/host/, the only place the boundary test lets code read gateway identity headers or an incomingAuthorizationheader. On403 OWNER_RETIRED, core callsclearCredentialonly on the anonymous fallback and on methods that declareissuesAnonymousOwners: true, never on an account method. The unreleasedOWNER_AUTHENTICATOR/TRUSTED_PROXY_*variables of an earlier built-in gateway-header authenticator fail startup when set. - Owner identity: claiming anonymous work into an account. When a host auth method authenticates a non-anonymous principal and the same request also carries a valid anonymous owner cookie, core attaches a
pendingClaimnaming that anonymous owner (never for an anonymous principal or the built-insharedTeam, and a method cannot set one itself), which covers both an anonymous-then-sign-in flow and a deployment moving from anonymous use to accounts.POST /api/identity/claim(same-origin JSON only;OWNER_CLAIM_TRIGGER=autoclaims on the first request carrying a pending claim, except the explicit claim routes) moves the anonymous owner's folders, courses, materials, agent sessions, skills, runtime sessions and asset entries to the account in one transaction, records it in the newowner_mergestable, and drops the anonymous cookie. Same-named folders merge, a folder id the account already uses is renumbered, a colliding skill handle gets the first free numeric suffix, runtime sessions are re-keyed as stored, and quotas are not applied to what moves. Claims are idempotent per pair, refused for an anonymous owner another account already claimed, and never chain. A retired anonymous id writes nothing afterwards: creates and writes through/api/persistence, the folder, course, material and skill routes, and writes by id to moved rows answer403 OWNER_RETIRED, with aSet-Cookiethat drops the retired cookie. Agent runs, generated media and skills created by a run that started before the claim follow it to the account. Every owner write takes a per-owner advisory lock first, so a fenced write racing a claim is moved or refused; lock waits are bounded (OWNER_CLAIM_LOCK_WAIT_MS,OWNER_WRITE_LOCK_WAIT_MS) and running out answers503 OWNER_BUSYwithRetry-After. The anonymous cookie is a bearer credential: its holder can claim that anonymous work into a signed-in account, so clear it on shared devices. Hosts add their own tables withregisterClaimParticipant, and an auth method may implementdescribeStoredOwnerandclearCredential;principalFromStoredOwner(ownerId)describes a stored id for work without a request. The runtime contract'sPOST /runtime/learners/mergenow performs the same claim, allowed only from the request's own pending claim. The owner-events stream tells a retired owner only that it moved, never the account's id. @openmaic/storage0.35.1:reassignDocumentFolders(tx, { fromOwnerId, toOwnerId, documentOwnership })moves one owner's folders and filing to another (merging same-named folders, renumbering colliding ids);PgAssetStore.reassignPrincipal(fromKey, toKey)moves a principal's entries under both principals' write locks;PgUserSkillStore.mergeOwner(from, to)moves skills, renaming a handle the target holds to one neither side holds;PgRuntimeStore.reassignLearner(from, to)re-keys sessions without re-validating them.PgUserSkillStore(resolveFinalOwner) andPgRuntimeStore(resolveFinalLearner) take a hook that runs as the create transaction's first statement. Every HTTP handler (runtime, documents, assets) now answers aDocumentWriteRefusedErroras403with its code, and the newStorageBusyError(code, message, retryAfterSeconds)as503with its code andRetry-After.PgUserSkillStore.listorders ties by id.- Server persistence: host extension hooks, registered once from
instrumentation.tsnext to the owner auth methods and sealed on first use.configurePersistenceHooksaddsauthorizeCreate/onCreate(run once per created course inside its transaction; a refusal answers403 CREATE_REFUSEDand a throw rolls the create back), alibraryprovider for whatGET /api/stageslists (never an id the read path would refuse), andbeforeAssetAllocate(refuse an upload,createorreplace, with anyResponsebefore bytes are stored or quota counted). Hooks receive anactorwhosesourceis'request'(with the resolved principal) or'background'(an agent run; a refusal reaches the agent as a fixed message).configureAssetByteStorereplaces theASSET_S3_BUCKETswitch for the request path and the asset collector alike; underASSET_BYTE_EGRESS=redirecta store that does not declaresignsReadUrlsstops the server at boot. Nothing changes when no hook is registered. @openmaic/storage0.33.0:DocumentWriteRefusedError(stageId, code, message)lets a store refuse a write as policy; the document HTTP handler answers403with the error's code and applies nothing.isDocumentWriteRefusedErroralso recognizes one thrown by another copy of the package, by name and shape.- Server persistence:
ServerPersistenceProvider(lib/persistence/server-provider.ts) no longer exposes an unscopeddocumentStore, which wrote courses with no owner check and no host hooks. Use the owner-bound store (createOwnerBoundDocumentStore, orgetOwnerScopedDocumentStore). - Owner identity:
POST /api/chat/pianswers401when owner resolution refuses the request, like every other owner-resolving route, instead of continuing without an owner. Under the default anonymous fallback nothing changes.
Bug Fixes
- Server persistence: the course library and folders work without the agent runtime.
/api/stages/**(list, create, read, save, delete, manifest, scenes, freshness, status, generation-complete, publish, unpublish) and/api/folders/**now gate onDATABASE_URLalone instead of also requiringOPENMAIC_AGENT_RUNTIME_ENABLED, so a deployment with server persistence and the runtime off no longer answers404there and shows an empty, unavailable library. Agent routes (/api/agent/**,/api/skills/**,/api/materials/**) still require the runtime, and without aDATABASE_URLeverything answers404as before.GET /api/agent/runtimealso reportspersistence. - Startup: a configuration refused by the boot validation in
instrumentation.ts(a malformedASSET_QUOTA_BYTES,ASSET_PENDING_TTL_MS,OWNER_WRITE_LOCK_WAIT_MSorOWNER_CLAIM_LOCK_WAIT_MS; anOWNER_CLAIM_TRIGGERother thanexplicit/auto; the removedOWNER_AUTHENTICATOR/TRUSTED_PROXY_*variables; a malformedPERSISTENCE_SHARED_OWNER_ID, one withoutACCESS_CODEor beside a registration that leaves outsharedTeamAuthMethod(), orsharedTeamAuthMethod()registered without it or not last;ASSET_S3_BUCKETbeside a registered asset byte store, orASSET_BYTE_EGRESS=redirectwith a byte store that does not declaresignsReadUrls) now exits the Node.js server with code1after one[boot] Invalid server configurationline carrying the original message. Any other boot failure (a module missing from the build, a host registration call that throws) also exits with code1, printed as[boot] Server startup failedwith its stack. Previously the server logged "Failed to prepare server", kept listening, and answered every request with500. Warnings never stop the server. - Server persistence: two concurrent creates of one new course id by the same owner no longer refuse the second as
reserved-document: creates of one id take turns, and the second saves as an update without running the create hooks again. - Server persistence: instances starting at the same time against one database no longer fail schema setup on a catalog race (
duplicate key value violates unique constraint "pg_class_relname_nsp_index"/pg_type_typname_nsp_index,tuple concurrently updated), which made an instance's first request answer500. Every schema bootstrap (documents,stage_metaand its ownership backfill, owner materials, assets, runtime, agent sessions, session materials, user skills, and the asset collector's) now runs under one PostgreSQL advisory lock held on a dedicated connection.
Security
- Server persistence: operations on one owner-bound document store no longer share mutable state, so concurrent calls on a store an agent run shares with its tools each gate their own stage;
create_stagealso runs sequentially within a tool batch. - Server persistence: runtime data of a deleted course reads as absent and takes no new writes, however the request path is spelled.
@openmaic/storage0.32.0:PgDocumentStoretakesassetReferencePrincipals(andAssetCollectora matching per-owner function) so a document write can no longer commit or pin another principal's asset entry; a store can refusecreateSessionwithRuntimeStageNotFoundError(404 STAGE_NOT_FOUND); and a session create over a taken id answers409 SESSION_ALREADY_EXISTSwhoever holds it, instead of403for another learner's session.
[1.1.2] - 2026-09-28
A security release. Server-side requests to provider URLs that a caller can choose now connect only to the addresses that passed validation and refuse redirects, and error responses no longer carry provider response bodies or connection details. Read Behavior Changes before upgrading.
Security
- Provider connections: when a provider is not configured on the server, the settings UI can supply its own base URL, endpoint or model. Several routes validated such a URL once and then connected with a transport that resolved DNS again or followed redirects: PDF parsing and connectivity checks, the Azure voice list, model listing, image and video providers, and LLM calls. Some of these routes also echoed provider response bodies or connection errors back to the caller. This allowed requests to internal addresses and probing of internal services. These requests now go through the strict provider transport, which pins every connection to validated addresses and refuses redirects. IP-literal hosts and the built-in default URLs of unmanaged providers are held to the same policy. Caller-facing errors are fixed text. Client-supplied AliDocMind endpoints must be official hosts. GHSA-g87c-cm4q-cw5x #1704
- Classroom media generation downloads provider-returned image and video URLs through the strict provider transport: HTTPS public addresses only,
data:URLs decoded locally, and size bounded while streaming #1692 (by @Yi-111-a). Agent-runtime image and video tools now do the same #1704.
Behavior Changes
- A caller-supplied LLM, image/video, model-list, PDF-check or self-hosted MinerU base URL that answers with a redirect is refused instead of followed. Configure the final URL.
- A caller-supplied provider base URL containing a query string or fragment is refused.
- A caller-chosen loopback or private address, including an IP literal or an unmanaged provider's built-in
localhostdefault (Ollama, Lemonade, VoxCPM), needsALLOW_LOCAL_NETWORKS. Configure the provider on the server to keep it operator-managed. - Server-configured providers (LLM, image/video, TTS/ASR, MinerU, MinerU Cloud) may use local network addresses without
ALLOW_LOCAL_NETWORKS. Cloud metadata and reserved ranges stay blocked, and redirects from them followALLOW_LOCAL_NETWORKS. - A client-supplied AliDocMind endpoint must be an official
docmind-api.<region>.aliyuncs.comhost over HTTPS; other endpoints answer403 INVALID_URL. Server-configured endpoints are unchanged. - Provider check and generation errors are fixed messages without the provider's response text or connection errors:
/api/verify-pdf-providersuccess no longer includesstatus;/api/azure-voicesanswers provider failures with502;/api/provider/probe-modelsreports other HTTP failures as502with the status class only;- LLM errors for a client-selected endpoint read
Cannot connect to API: connection failed(orrequest timed out,redirects are not allowed) or the HTTP reason phrase; - MinerU Cloud errors report the HTTP status or numeric code.
- Agent-runtime video and poster downloads require HTTPS.
- Pinned provider requests connect directly and do not use Node's environment proxy (
NODE_USE_ENV_PROXYwithHTTP_PROXY/HTTPS_PROXY). - A provider id that names an inherited object property (such as
constructor) is no longer treated as server-configured.
[1.1.1] - 2026-09-27
A security release: MinerU Cloud document parsing now holds the upload and result URLs returned by the provider to the strict public address policy, validates every redirect hop, and bounds what it reads and decompresses.
Security
- MinerU Cloud parsing fetched the presigned upload URL and the result ZIP URL taken from the provider's response with a plain fetch, so when a caller supplied its own MinerU base URL (the provider not configured on the server), its endpoint could steer the server's
PUTof the uploaded document and the result download at internal addresses. Every MinerU Cloud request now goes through the strict provider transport (per-hop redirect validation and DNS pinning); the response-supplied upload and ZIP URLs must be HTTPS public addresses under every policy, the upload no longer follows redirects, address-policy refusals are not retried, and JSON, ZIP and decompressed entry sizes are bounded. The shared SSRF guard also classifies IPv4-compatible IPv6 addresses (::/96) by their embedded IPv4 GHSA-cpjc-vgjh-c5jp (reported by @AbelWangYaBo) #1688
[1.1.0] - 2026-09-24
Classroom chat now runs on an agent loop by default: learners can point at a slide element, an interactive component or a whiteboard drawing and ask about it, and the teacher can read the lesson, check the live state of an interactive experiment and search the web before answering. Settings are reorganized around the course workflow, with a model choice per generation step and first-class Token Plan connections (TokenDance, MiniMax, Seed and Kimi). Read Behavior Changes before upgrading.
Highlights
- Pi classroom chat is the default. The in-class conversation moves from the director graph to an agent loop that can read slides on demand, search the web and call classroom tools within a single answer #1628 #1637
- Ask about what you're looking at. Reference a single PPT element, an interactive component or a whiteboard element from the playback bar and ask about it; interactive pages that declare state are sampled at question time, so the teacher can explain the result the learner is actually seeing, and
read_sceneexposes an interactive page's static instructions #1508 #1632 #1656 - Settings, rebuilt around the course workflow. Choose a model for each generation step (outline, slides, interactive pages, scene actions and more), toggle each capability on or off, and manage Token Plans in one place #1644
- Token Plans. One-key presets for TokenDance (every modality) and the Kimi Coding Plan (text only; other modalities stay on your own providers) #1525 #1664
Features
- Models: add OpenRouter image and video providers #1356, Gemini 3.8 Flash and 3.7 Flash #1629, and Xiaomi MiMo V2.6 #1655
- TTS: pace classroom narration requests and classify MiniMax RPM limits so rate-limited runs slow down instead of failing #1650; auto-detect Vietnamese for browser-native narration #1487
- Storage: workbench image and video generation write through the asset pool (#1007 part 6) #1524; ZIP imports persist media in the server asset pool #1520
- Persistence: optional single-tenant mode that resolves every request to one shared owner (
PERSISTENCE_SHARED_OWNER_ID) #1639 - Render service: admission control and per-task resource budgets #1492
- Attribution: TokenDance gateway requests carry an
X-APP-URLheader identifying the OpenMAIC deployment #1675
Bug Fixes
- Generation: reject quiz options whose values are not A–Z and constrain them at the source #1651; reject unusable interactive scripts and surface runtime errors on the page #1649; keep narration speech free of formulas and LaTeX #1586; tolerate non-array
mediaGenerationsin outlines #1469; add a browser-safe package entry #1609; stabilize model picker teardown #1618 - Playback and audio: mobile narration survives past the first segment and discussion lines reuse one media element #1477 #1610; stop superseded scene engines #1510; queue widget messages until the iframe is ready #1532; resolve CDN-backed narration consistently #1521; keep refused narration instead of re-billing it #1523; derive the Azure SSML locale from the selected voice #1566; rebuild FormData bodies for the undici transport #1580
- PPTX import and editor: preserve tab columns, text insets, arrows, text/chart/image styling, shape autofit, Wingdings checkmarks, diagonal corners, table typography and punctuation wrapping; include every color attribute in the style cache key; share line bounds for alignment and dragging; bound ZIP inflation by default #1518 #1534 #1577 #1581 #1571 #1631 #1588
- Media and web search: refuse redirects on adapter generation and poll calls #1636; keep long Grok relay generations alive and inline image bytes #1364; emit keyframe images as data URLs #1444; stop leaking Brave's HTML challenge page into errors #1553; bound the model-discovery response body timeout #1478
- Export: surface video render rejection reasons #1414; include active line geometry in bounds #1626
- Server and storage: validate
pdfContentinput #1578; sanitize agent session text #1504; correlate 5xx responses with request IDs #1601; warn when access-code protection is disabled #1599; show effective upload limits in errors #1418 - Docker: create
/app/dataowned by the runtime user so classroom persistence works #1442; build workspace packages in the builder stage #1598 - Misc: send
taskEngineModeon the on-demand vocational scene path #716; POSIX zip entry names for exported skills on Windows #1641; i18n and persistence fixes #1595 #1596
Behavior Changes
- Pi is the default classroom chat runtime. To keep the legacy director-graph chat, build with
NEXT_PUBLIC_PI_CHAT_ENABLED=false#1628 - Stricter generated content. Quizzes whose option values are not A–Z and interactive pages with unparseable scripts are now rejected as invalid model output and retried or skipped by the existing path, instead of being saved broken #1651 #1649
- Settings layout. The web-search toggle moved from the generation toolbar into Settings; an existing preference is migrated once on first load #1644
- Courseware references are opt-in. The playback-bar reference entry (PPT, interactive and whiteboard) is enabled with
NEXT_PUBLIC_COURSEWARE_REFERENCE_ENABLED=true#1656
Other Changes
- Docs: align security and behavior claims with shipped code, and document the Docker builder heap limit #1592 #1607
- Refactors and CI: shared per-course session lifecycle, shared narration walk for export, MIME policy from the format registry, Node 24 publish actions, Windows-safe media tests #1619 #1621 #1590 #1569 #1584 #1615
Contributors
Thanks to the community contributors in this release: @AbelWangYaBo, @acse-bq23, @Ai-Eastern, @BeAIcoder, @Cham1229, @ciclou1, @dajiaohuang, @Duang777, @Hosuke, @HuntercodeT, @hydraxman, @LeoParkerOu, @lianglaibin116-cloud, @ly-wang19, @mianbaofang, @nqthiep, @PassCode023, @puxiao, @Qnh233, @ttkm2023, @WizKid1968, @YizukiAme, @zdnemz.
[1.0.3] - 2026-09-15
A security release: access-code tokens now expire and verification is throttled behind a trusted proxy, the render service applies a network policy to the untrusted HTML it renders, audio provider requests validate redirects and pin their connections, and Next.js is upgraded to patch a critical RCE. It also carries the fixes and features merged since 1.0.2.
Security
- Access-code verification tokens (
timestamp.HMAC) never expired because neither verifier checked the timestamp, andPOST /api/access-code/verifyhad no throttling. Both verifiers now enforce a 7-day server-side lifetime and reject non-canonical signatures, and verification is rate-limited per client whenTRUST_PROXY_HEADERS=true(with a warning to use a long randomACCESS_CODEwhen it is short) GHSA-qpmr-534w-hhpg (reported by @CaptBoykin) #1513 - The render service ran caller-supplied HTML in headless Chromium without the packager's Content-Security-Policy on the
/previewand/renderpaths, so inline script could reach loopback and internal addresses and, on/render, paint the response into the returned MP4. Both paths now inject a first-parsed CSP, the preview frame is additionally guarded by request interception, and framed same-origin.svg/.xhtmldocuments are sanitized GHSA-vqq3-22q7-289w (reported by @CaptBoykin) #1512 - Audio provider requests (TTS, ASR, voice registration and voice cloning) validated a client-supplied base URL once and then followed redirects and re-resolved DNS unvalidated, so a redirect or a rebinding answer could reach an internal address. These requests now validate every redirect hop and connect only to the addresses the guard validated, on every hop GHSA-9p8q-rcmg-pmjw (reported by @CaptBoykin) #1514
- Upgrade Next.js from 16.2.11 to 16.3.3, which patches an unauthenticated remote code execution on Windows-hosted servers (GHSA-p293-qw3h-jr36 / CVE-2026-75604) #1503
Features
- Storage: the server owns the asset entry lifecycle and releases assets on course deletion (the #1007 amendment) #1472 #1473
- Skills: add an inquiry-based exercise-class teaching skill grounded in the zone of proximal development #1382
Bug Fixes
- Importer: resolve embedded PPTX videos when the legacy link points at NULL, and upload video posters through the configured callback (
@openmaic/importer0.2.1) #1507 - Storage: keep jsonb writes valid when model output contains NUL or lone surrogates #1499; allow one owner material to be bound to multiple sessions #1500
- Classroom: render a transient server error as a retryable state instead of the terminal "course does not exist" card, on both the pane and the standalone route #1485
- Upload: resolve the generic Office MIME (
application/vnd.ms-office) via the filename extension #1498 - Settings: maintain the ASR language state invariant on provider selection #1443
- TTS: treat a custom provider's Add-dialog default base URL as a configured credential path so generation narration is not skipped #1482
- Export: tolerate malformed authored CSS in classroom exports instead of dropping later assets #1422
- Render service: preserve plan audio during chunk assembly #1358
- Docker: enable the Pro workbench flag in Docker builds and allow a non-TLS localhost cookie #1484
Other Changes
- Docs: document the deployment assumptions and pre-report checks in the security policy #1511
- Tests: isolate the image URL-guard test environment so it no longer inherits the generic OpenAI fallback #1476
[1.0.2] - 2026-09-14
A security release that closes a cloud-metadata SSRF gap, a DNS-rebinding bypass on media proxying, and a classroom overwrite, and tightens two request paths — read the Breaking Changes section first.
Security
/api/proxy-mediaand the outbound URL guard accepted the Alibaba Cloud instance-metadata address100.100.100.200because the metadata denylist was not applied before an IP literal was accepted. The guard now checks metadata hostnames and addresses — including mapped and transition encodings — before the literal and local-network branches, in every environment and regardless ofALLOW_LOCAL_NETWORKSGHSA-6xff-rgjg-v33f (reported by @lihua666a-cell)/api/proxy-mediavalidated a hostname and then letfetch()resolve it again at connect time, so a name whose answer changed between the two lookups could reach an internal address (DNS rebinding). The proxy now connects only to the addresses the guard validated, on every redirect hop, through a shared pinned dispatcher GHSA-23xq-m3mm-3j49 (reported by @ry2811)POST /api/classroomaccepted a caller-chosen classroom id and renamed a temporary file over an existing classroom, replacing its content. Ids are now server-generated and classroom files are created exclusively, with a bounded retry and a 409 on collision GHSA-87m4-6c66-pc68 (reported by @ry2811)POST /api/generate/ttsnow inspects a 200 response before storing or billing it: HTML, JSON and other non-audio bodies are rejected, and a URL embedded in a JSON envelope is never followed #1405
Breaking Changes
POST /api/classroomno longer honours a client-suppliedstage.id; theidin the response is the classroom's id #1489- The outbound URL guard now refuses IANA reserved, documentation, multicast and broadcast ranges (
240.0.0.0/4,198.18.0.0/15,192.0.2.0/24,2001:db8::/32, …) at both the URL and the connection layer, regardless ofALLOW_LOCAL_NETWORKS. CGNAT100.64.0.0/10(Tailscale and similar overlays) is blocked by default and allowed withALLOW_LOCAL_NETWORKS=true, like private ranges #1488
Features
- Playback: the global Reference courseware entry now grounds HTML-backed GenUI and Interactive scenes on one source-authored component — resolved again on the host against the request-start scene snapshot, sanitized and bounded, and shared with the Director and both child runtimes — behind the default-off
NEXT_PUBLIC_COURSEWARE_REFERENCE_ENABLEDbuild-time gate #1281 - Export: the export dialog checks render queue availability before compiling; a service that reports itself busy disables MP4 with a localized hint and a manual recheck, while ZIP and subtitle downloads stay available #1455
- Media: under server-backed persistence, generated media is written through the asset pool first and the allocated id is persisted into the document, so a reload regenerates nothing and other browsers resolve the same bytes #1392
- Providers: add GLM-5.3 and GLM-5.3-Flash, and make the GLM thinking adapter degrade a disabled request to the lightest effort for always-thinking models #1401
- Render service: emit one JSON lifecycle event per render and preview transition — submission, start with
queueWaitMs, finish with its outcome and duration, admission rejection, and preview request — carrying only bounded, low-cardinality fields #1397
Bug Fixes
- TTS: prepare the next discussion segment while the current clip plays, so synthesis latency overlaps playback while audio stays strictly ordered #1435
- Export: keep one in-memory compiled ZIP so an unchanged retry after a 429 submits the same result instead of recompiling #1456
- AI runtime: let non-streaming LLM calls outlive undici's 300 s headers timeout through a shared dispatcher with a 15-minute limit #1404; share one process-local thinking context across Next.js bundle evaluations #1378
- Importer: convert Equation 3.0 and MathType OLE formulas to LaTeX through MTEF v3 with degrade telemetry, and fix the non-transparent formula placeholder #1411; stop PPTX import from hanging in non-browser environments by bounding image loading and EMF/PDF rasterization #1424
- Quiz: resolve AI answer keys to option values before grading, failing closed when a key matches no option or several #1328
- Renderer: lazy-load the optional ECharts runtime, share one loading promise and wait for chart readiness during slide PNG export #1420
- Render service: keep preview browser evaluation self-contained so
tsx's__namehelper never reaches Chromium #1421; enforce the chunk worker cap and report the worker count actually observed #1359 - Materials: sanitize the owner id in the byte-store key so uploads work on Windows #1426
- Settings: read
data.errorfor image and video provider test results instead of renderingfailed: undefined#1459 - Classroom: adapt the completion page to short viewports instead of clipping its content above the scroll origin #1461
- Build: include the libvips native libraries in standalone tracing so sharp loads at boot #1407
Other Changes
- CI: add an opt-in issue triage agent that analyzes read-only by default and publishes only on an explicit run #1439
- Docs: state the advisory severity and CVE process in the security policy #1417
- Tests: cover preview callbacks across the
tsx/browser boundary #1454; make the material search budget test deterministic through an injectable clock #1453
[1.0.1] - 2026-09-06
A security and stability release. Everyone running 1.0.0 should upgrade; read the Breaking Changes section first, as this release tightens two defaults.
Security
- Classroom persistence rejected a stage id that escaped the classrooms directory
only on the read path. The write path now applies the same allowlist, and the
storage layer asserts a resolved classroom file stays inside
CLASSROOMS_DIRGHSA-p2wh-m28m-c5xw (reported by @skeletonsec) - Stored slide HTML is now restricted to the formatting vocabulary the renderer produces, sanitized at the classroom persistence boundary on both write and read, with a separate policy for KaTeX snapshots so formulas are not flattened GHSA-7rhf-2798-mvcj (reported by @skeletonsec)
- The outbound URL guard ran only under
NODE_ENV=production; it now runs at every call site in every environment, and a repository-scanning test fails if a gated call site reappears GHSA-9m7h-vh2h-rc3w (reported by @uziii2208) - Provider requests now re-validate every redirect hop through a shared transport and drop credential headers on a cross-origin hop, the way the platform fetch does GHSA-725p-44hx-v52c (reported by @skeletonsec)
- The development persistence authenticator is refused under
NODE_ENV=productionunless an explicit opt-in is set; it provides no user isolation and was never meant to serve production traffic - Bump next, js-yaml, undici, nanoid, lodash and sharp for disclosed advisories #1357
- Bound skill zip inflation instead of trusting the declared uncompressed size #1374, and bound
import_pptxparsing with a size cap and timeout #1269
Breaking Changes
- Node: the minimum supported runtime is now 22.19.0, up from 20.9.0 #1337
- The outbound URL guard now runs in every environment, not only production builds. A client-supplied provider base URL pointing at a loopback or private address — a local Ollama or Lemonade endpoint entered in Settings, for example — is rejected unless
ALLOW_LOCAL_NETWORKS=trueis set. Production deployments already behaved this way; development builds did not - Redirect hops are re-validated for every provider, including server-managed ones. An internal gateway that answers a redirect to a private address needs
ALLOW_LOCAL_NETWORKS=true - The development persistence authenticator no longer serves traffic under
NODE_ENV=production. A deployment that intentionally runs it on a trusted network must setPERSISTENCE_ALLOW_INSECURE_DEV_AUTH=true POST /api/classroomnow rejects a payload whose scenes do not satisfy the slide DSL, and a stage id outside[A-Za-z0-9_-]
Features
- Agent: add a fact-check skill #1274; let
generate_scenepasswidgetTypeandwidgetOutlinefor interactive pages #1259; makegenerate_videoasynchronous with a placeholder reference #1267 - Pro workbench: reference single PPT elements from chat #1224; generate concise conversation titles #1275
- Render service: add a
POST /previewendpoint #1285; report admission state machine-readably, with 429 reasons and an accepting flag on health #1351 - Providers: add Exa as a web-search provider #1342; register
deepseek-v4-flash-vision-expwith vision capability #1329 - Canvas: insert text by double-click #1310
Bug Fixes
- Classroom: speed up loading through media hydration, sidebar thumbnails and media range requests #1276; keep bottom table rows visible during playback #1366; keep videos playing inline on mobile #1321
- Agent runtime: preserve
generate_scenefailure causes #1316; boundgenerate_sceneretries per page #1370; normalize skill paths to POSIX before the loader #1297 - Workbench: handle stale workspace resume memory #1271; persist Pro conversation titles #1273; render LaTeX in assistant messages #1309
- Generation: assign unique IDs per media request #1368; load micropip before generated imports #1282; keep diagram node position off the hover transform #1339; normalize GPT Image 2 generation sizes #1346
- Providers and media: apply server-pinned models for image and video providers #1298; bypass the AI SDK for custom ASR providers so extra response fields survive #1283; turn TTS and media generation on from the Settings page #1311
- Storage: prune redundant intermediate
message_updateframes #1279 - DSL: migrate away legacy rotate/height fields on line elements #1261
- Export: recheck script readiness on download #1159
- Importer: fix
pnpm installfailing on Windows #1372 - Docs site: isolate the standalone build from product middleware #1307
Other Changes
- Build and tooling: enforce the direct dependency engine floor #1337; enforce LF line endings for text files #1296; replace
rm -rfwith a cross-platformfs.rmSyncin package scripts #1338 - CI: migrate GitHub Actions off the deprecated Node 20 runtime #1343
- Docs: sync the README with current code, multi-workbench skill support and the 1.0 videos #1334; swap the English and Chinese user-guide links in the README badges #1290
- Tests: isolate the media force-off test from the OpenAI fallback #1303
[1.0.0] - 2026-08-27
Features
- Agent workbench (Pro mode) — chat-first course building from the home page: the collapsible workspace shell #1206, the agent chat surface #1205, and the client data layer #1204, with Pro entry points that preserve mode-transition semantics #1208; owner-scoped folder routes, stage-metadata viewer surfaces, and the material upload contract #1215, plus stage and material HTTP routes #1203; the Pro workbench flag now implies the MAIC Editor gate #1223
- Durable agent runtime — server-backed course-building sessions: the driver model contract and stage route dialect #1165, the runtime foundations on the agent-session store #1167, a background session runner #1169, agent session and owner event streams #1170, and session lifecycle routes #1171 — sessions survive restarts, accept follow-up steering and cancellation, and stream a replayable event transcript; the runtime configuration surface is documented #1176
- Agent tools and skills — validated, provider-neutral course tools: neutral tool foundation libraries #1184, a
web_searchtool on the session runner #1185, session materials with afetch_urltool behind the URL trust gate #1190, material read/search #1192, stage read/patch #1194, page generation and deck editing #1198, roster and voice registration #1201, folder organisation #1202, image/video/PPTX import #1211, and the material extraction lifecycle #1212; a skills system #1189 with Feynman and spiral curriculum methods #1240, reference tools and skills ported in a parity audit #1241, and real skill management in Settings — list, download, delete, and upload #1244 - Provider-neutral server capabilities — image, video, and ASR models resolved from server config #1175, uniform capability force-off with a consistent missing-key contract #1181, startup validation of model routing config #1182, silent-behavior fixes from the provider audit #1196, and provider force-off enforced in agent tools with vendor identity scrubbed from tool results #1231
- Pluggable persistence — an agent-session store with a PostgreSQL backend over layered contracts #1163, an ownership scope on stage documents #1191, a per-session URL trust gate #1186, per-scene monotonic revisions via database triggers #1214, owner materials migrated to
oss_keywith the legacyasset_iddropped #1250, and per-owner quota reservations serialized with crashed uploads reclaimable #1232 - Materials and the asset byte model — uploaded sources ingested into the asset pool and extracted by id #1154, derived assets with lineage and an extraction cache #1164, a material library manifest with id-addressed generation images #1168, inline base64 images converted to pool assets on load #1172, legacy references converted to allocated asset ids #1101, media and materials moved onto the reference byte model with the asset-registry wiring retired #1242, a standardized asset manifest with converged export paths #1117 over one shared stored-bytes resolver #1116, opt-in indirect asset byte egress #1100, and an optional local ffmpeg/ffprobe media extractor #1213
- Editor: float the insert toolbar in the outer frame with collapse #1246; port the timeline TTS preview single-flight and voice-all state latching #1235
- Whiteboard and Pi Native Child: destructive whiteboard runtime operations #1173; Native Child whiteboard tools #1152 and additive whiteboard tools #1156, web search #1133, and a native child runtime with scoped Spotlight #1111
- Qwen TTS voice cloning #1160
- Download the narration script as Markdown or DOCX on export #1144
- A document lexical retrieval foundation for RAG #1078
- Video export: a bounded local chunk executor #1115
- German (de-DE) localization #1128
- OpenClaw skill: a secondary-development flow #1119
Bug Fixes
- Workbench: restore editor chrome, mode transition, streaming, materials, mentions, and folders #1229; restore the attach entry and add the rail settings entry, pinning all three entry points #1221; list PG-mode home courses via owner stages while keeping the interrupted terminal course card #1218; send the opening session message exactly once with references intact #1234; show newly created folders in the sidebar without a reload #1254; single-source the chat gutter so the timeline and composer share a left edge #1255 #1247; lock the pane-embedded classroom to edit mode #1256; label the extraction lifecycle tools on the timeline
- Agent runtime: control-plane routes answer 404, not 500, without a database, and the runtime reports itself unusable without one #1207; abort in-flight TTS on cancel and bound every provider request with a timeout #1217; bound every tool call with a timeout and never resurrect a cancelled session #1226; fence durable tool writes and consume cancel requests atomically #1230; fence session claims while an ask_user question is outstanding #1248; settle-time rescue tracks real delivery instead of a count offset #1249; repair orphaned and late tool results across interruption boundaries #1180; wake SSE tails and the runner on durable deltas for streaming fidelity #1222; carry reasoning through the completions dialect so the thinking strip renders #1239; revoke deleted-session URL authority and reject private ISATAP endpoints #1199
- Editor: complete element referencing with the renderer DOM contract and GenUI picking aligned to the reference #1238; resolve dock-bar i18n keys, remove the dock height drag, and wire element referencing #1233; allow dragging selected lines #1166; allow multi-tab edit mode by dropping the cross-tab edit lock #1161; align editable shape labels with text #1137; keep class roster cards from shrinking #1135
- Media: restore the reference classic media chain #1236; persist origin-independent classroom-media references from the agent runtime #1245
- Storage: asset writes no longer self-deadlock against pooled PostgreSQL #1225 #1227
- Importer: adapt the imported PPTX canvas size so decks render without overflow #1237
- Classroom: center adapted canvases in the stage and send navigation home during generation #1243
- Renderer: preserve literal text line breaks #1132
- Whiteboard: correct the inverted viewportRatio so boards render 16:9 landscape #1257
- Video export: make Cyrillic and Arabic Quiz fonts deterministic #1114; constrain GenUI iframe visibility #1125
- i18n: fix the Traditional Chinese translations in the importer #1127
Other Changes
- Docs: add the release version prefix to the README and drop the opt-in framing, surface the 1.0.0 user-guide badges at the top #1253, and a takeaway-style 1.0.0 announcement with bilingual guide links; announce 1.0.0 and refresh the feature overview #1216; document the agent runtime configuration surface #1176
- Tests: reconcile vendor-token debt counts with the integration line and after the main merge, mock both runtime gate exports in the control-plane route suites, and give material fixtures the extraction lifecycle fields; keep the PG contract suite order-independent #1200; cover indirect-egress CORS in Chromium #1138; wait for project cleanup instead of racing it in the render suite #1193; stop loading
.env.localinto unit tests by default #1162; guard the provider-neutral layers against vendor leakage #1197 - CI and build: cut wall-clock time and bound Playwright browser installs #1146; scope the Next typecheck to production sources #1179; add optional Docker mirrors and a pnpm cache #1139
- Workbench cleanup: retire the bookmark concept and the saved-courses drawer #1219; remove the in-editor agent panel #1210
- Storage: drop the unused active-stage API from the agent-session contract #1174
- Pi: simplify agent tool ownership and completion #1148
[0.3.2] - 2026-08-14
Features
- Video export hardening — fidelity polish for spotlight geometry, video clips, formulas, and subtitles #952, deterministic Quiz/PBL cover cards #995, self-contained static interactive HTML capture #1086, deterministic Quiz question-list scrolling #1102, a stable RenderExecutor seam #1104, explicit CPU resource profiles #1105, and effective parallel capture in the render service #1042
- Server-backed persistence completion — learner-data cutover: quiz + playback onto RuntimeStore #955; document-persistence cutover: stage/scene/outline onto DocumentStore #965; server-backed documents over an HTTP contract with a Postgres backend and one reference server #979; a one-command server-backed stack (compose profile + embedded API + docs) #982; dirty-set incremental saves with operation-level flush #983; settings/user-profile persistence through KVStore #1001; the KV HTTP contract and clients promoted to main #1020; learner whiteboard RuntimeStore foundation #1075
- Asset registry — allocated asset ids over the content-addressed blob layer #1024, unified media asset reference types #1038, generated assets allocated through the registry #1039, a server asset registry over a pluggable byte layer #1077, and the asset backend wired into the app #1089
@openmaic/generationpackage — scaffolding with pipeline types and packaged prompt assets #1063; outline generation #1065 and scene generation + PBL single-call planning #1087 moved into the package; the app consumes the package everywhere andlib/generationis deleted #1090- SDK contract ownership — interactive and PBL content kinds promoted into
@openmaic/dsl#1070, app consumes the contract's interactive/widget types #1073 and PBL project types #1079, and the renderer decouples its editing UI from the app #1072 - Course folder grouping #1005 (by @CXuPercy)
- Local FunASR ASR provider #1044
- Claude (
claude search) as a web-search provider #393 (by @joseph-mpo-yeti) - Per-stage routing for
generate-classroominstead of one model #1048; evidence-aware Pi Director context runtime #971; optional playback canvas renderer #958 - Amazon Bedrock LLM provider #538 (by @littlebullGit), Atlas Cloud LLM provider #948 (by @binyangzhu000-sudo), latest Claude/Gemini/Kimi/Grok models #993, and Grok 4.6 #1113
- French (fr-FR) locale #1068 (by @momo2lajoie), Spanish (Mexico) locale #942 (by @davidmedel), Vietnamese (vi-VN) locale #1025 (by @niitbeo), and 432 reviewed zh-TW translations #526 (by @alvinets)
Bug Fixes
- Persistence: stop corrupting binary response bodies in the route adapter #1088; omit undefined object fields at persistence boundaries #992; bind default fetch to globalThis in both HTTP store clients #984
- Chat: harden runtime sync, legacy migration, and record validation #1050; add opt-in streaming chat compatibility #990
- Access code: refetch server providers after the code is accepted #1081
- Render service: make parallel capture effective #1042; keep the entrypoint LF so the image starts on Windows #1027
- i18n: load every Inter subset so non-Latin text keeps the UI font #1026; translate ASR provider names in the generation toolbar #1028; localize Pro mode right rail labels #1023
- Render whiteboard math via KaTeX instead of raw LaTeX text #938; keep arrowheads visible when toggling thumbnails #1045
- AI: omit zero SiliconFlow thinking budget #1035; harden shared Pi transport contracts #1108
- Importer: handle custom shapes without paths #1015
- Vocational mode: keep the toggle thumb within the track #1032
- Editor packages: add npm provenance metadata #1098
- Release: dedupe the published dsl and close two release-path blind spots #1019; hand published versions to the marker job #1076
Other Changes
- PBL: retire the v1 write path and dead UI, converge on v2 #1060; split the planner core from the loop with an injectable LLM call path #1069; pin single-call prompt goldens #1071; route runtime agents through the shared LLM entry point #1006
- Document transform pipeline foundation #920; choreography overlays driven from descriptors #947; single-source the generated agent roster on the stage document #994
- Publish validated package tarballs #1040; publish storage and enforce version bumps #998; publish the OpenMAIC skill to ClawHub #1056
- Docs refresh and synchronization #996; align the environment variable template #1107; stabilize the Hyperframes lint command #1097
[0.3.1] - 2026-07-21
Features
- Video export (MP4) — Export a lesson as a rendered video: a
VideoTimelineIR with a pure compile pipeline #913, an L1 Hyperframes emitter with in-browser frame collection and ZIP export #931, and a service-backed MP4 render with in-app one-click export #937 (by @cosarah) — built on a shared orchestration spec inlib/choreography#890 (by @cosarah) and persisted TTS audio durations #862 (by @cosarah) - Server-backed runtime storage — A pluggable storage seam for classroom runtime state:
@openmaic/storageKV + asset primitives #858, a normalized DocumentStore #860, RuntimeStore sessions with append-only records #880, a DSL runtime envelope #870, device-anonymous learner identity #885, a runtime-event outbox with dual-write #893, cutovers for PBL learner state #902 #922 and chat sessions #926, and an HTTP backend contract with a Postgres backend and reference server #946 - Editor: direct manipulation — Select and drag slide elements #859, 8-point resize + rotate handles #881, marquee multi-select with multi-element drag #888, and a draggable insert toolbar #912, scaffolded as the
@openmaic/rendererv2 editing surface behind a machine-enforced import boundary #853 #855 - Edit with AI upgrades — Natural-language element edits through a typed EditIntent pipeline #896, validated JSON Patch element edits #927, and multi-session conversation history for the AI editor #801
- DSL self-ownership —
@openmaic/dslnow owns the Action playback verbs #787, ships JSON Schema artifacts with pure validators #817, activates the migration registry and runner #825, and owns element-level normalization and defaults wired into the generator #832 and the importer output boundary #845 - Document Parsing expansion — Multi-format course-material upload #741 and document bundles #844 (by @jackefn), audio/video media extraction with an AliDocMind provider #887 (by @yanpgwang), and a renamed Document Parsing surface with visible supported formats and extended MinerU support (by @yanpgwang)
- Add Azure OpenAI as an LLM provider #916 (by @hydraxman), SearXNG as a web-search provider #842 (by @PineSongCN), ComfyUI as an image provider #850 (by @PhillLittlewood), the GPT-5.6 model family #907, and an updated Doubao Seed model catalog #827
- Add one-click token-plan setup and a deployment usage dashboard #784 (by @yanpgwang)
- Add action-level playback navigation #843 (by @danishsshaikh)
- Redesign the narration timeline (action picker + inline insert) and enable it for interactive/PBL scenes #834
- Add in-editor authoring of classroom agents with a Stage-level roster #816
- Parallelize within-scene TTS generation #696 (by @ly-wang19)
- Feed the real HTML element inventory into interactive-action prompts #829 and add a postMessage listener contract to diagram/game/code widgets #872 (by @yanpgwang)
- Add an experimental Pi classroom runtime behind a flag #914
Bug Fixes
- Security: disable redirects in media connectivity probes #930 and harden provider redirect handling with ISATAP address detection #928 against SSRF (by @YizukiAme)
- Generation: strip reasoning blocks before JSON parsing #750 (by @yipwingtim), localize scene-generation errors #894 (by @wsun1), tolerate malformed generated slide data (by @yipwingtim), and honor outline node constraints in diagrams #911
- Editor: keep emptied or zero-action scenes playable, bind the outline by stable id, and surface incomplete content #814; show per-line loading while the batch "regenerate all TTS" runs #830
- Export: fix the unresponsive resource pack for interactive-only decks #933 (by @2046731121CC), compute SVG path bounding boxes via
getBounds()#656, keep sibling attributes when style is empty #683, and convert PPTX shadow offsets from px to pt #679 (by @ly-wang19) - Quiz: render formulas in quiz text #833 (by @dpersek); stop leaking questions on entry and pass results to the chat agent #823 (by @yanpgwang)
- Storage: store image files as array buffers #923 (by @YizukiAme) and accept image storage IDs containing underscores #918
- Chat: preserve message line breaks #908, and cap the roundtable non-presentation input height #917 (by @YizukiAme)
- TTS: respect string context when splitting the Doubao stream #677; web search: match Brave's current result-title markup #688 (by @ly-wang19)
- Stage: centralize the deck completion predicate #883 (by @dpersek)
- AI: close PROVIDERS/THINKING_CAPABILITIES metadata drift with a guard #809 (by @mvanhorn)
- Home: clarify the Interactive Mode selected state #901; lecture notes: render interactive-webpage widget actions #810
- Docker: fix the postinstall script failure in Docker builds #835 (by @Lee-Flier)
- mathml2omml: call
includes()instead of indexing it #681 (by @ly-wang19)
Other Changes
- Performance: dedupe editor alignment snap-lines in O(n) #692, diff code lines in O(n) via a prev-line map #706, and index assigned images by id in
fixElementDefaults#701 (by @ly-wang19) - Tests: cover
splitLongSpeechText/splitLongSpeechActions#694 (by @ly-wang19); settle dynamic imports #899 and drain debounced saves #897 before store-test teardown - Media providers: share the submit-poll task driver #900 and the auth probe across matching adapters #903, and remove the dead legacy pipeline chain #905 (by @YizukiAme)
- Packages: add repository metadata #813 and set
@openmaic/*package versions to 0.0.2 #812 (by @xuyuanwei678) - Docs: document the dev-server OOM workaround for large generations #808 (by @mvanhorn)
- Tighten GitHub issue intake #921
[0.3.0] - 2026-06-28
License
- Relicense the project from AGPL-3.0 to MIT
Breaking Changes
- Remove
allow-same-originfrom the interactivesrcDociframe sandbox for tighter isolation; interactive widgets that relied on same-origin access may need updates #726 (by @sebastiondev) - Restructure the slide DSL and renderer into standalone
@openmaic/*packages consumed by the app; the inline DSL shim is removed #707 #738
Features
- Project-Based Learning (PBL) v2 — Add the PBL v2 core schema and generation path #795 (by @cosarah), runtime APIs with classroom UI #799 (by @cosarah), in-timeline discussion authoring #798, auto-retry for transient scene-generation failures #788 (by @YizukiAme), and a planner eval harness #803 #805 (by @cosarah)
- Edit with AI — Add a Pro-mode editor agent that edits generated slides from a chat prompt #777
@openmaic/*SDK on npm — Publish the DSL, renderer, and importer SDK family to npm #778 #780, introduce themaic-import/maic-rendererworkspace packages #668 (by @xuyuanwei678), and promote the Stage/Scene lesson skeleton into@maic/dsl#740- Add optional per-stage LLM model routing #745
- Add GLM-5.2 and Kimi K2.7 Code #774, and Qwen3.7 Plus and Qwen3.7 Max #753, to the model registry
- Add a vocational-learning task engine with procedural skill widgets #685 (by @jackefn)
- Add Korean (ko-KR) translation #733 (by @moduvoice)
- Improve TTS with per-agent auto-voice quality and stable timbre registration #670, and unify the provider-enablement model with browser-native TTS off by default #665
- Add opt-in parallel scene-content generation #660 (by @ly-wang19)
- Add a document extractor provider foundation #704 (by @jackefn)
- Infer concise course titles from outlines for more readable course names #756
- Refactor widget actions into the unified scene action pipeline #796
Bug Fixes
- Importer: port PPTX shape-restoration hotfixes #789 (by @xuyuanwei678), fix hanging-indent bullet rendering #727 (by @xuyuanwei678), and guard SVG export against arc-first paths #638 (by @ly-wang19)
- Generation: make outline type changes take effect so interactive/PBL outlines are no longer downgraded to slides #772, stop regenerating deleted slides on finished decks #769, show full key-point text in the outline editor #782, and linearize outline streaming and interactive post-processing for better performance #732
- Agent: respond to the user's turn before lecturing #699, and constrain action narration to a single teacher voice #671
- Editor: stop dumping the raw tool-failure blob in AI edit tool cards #785, persist AgentBar voice and mode selections across reloads #723, and keep the edit runtime alive across read-only scenes #802 (by @cosarah)
- Audio: gate the speech button on ASR availability #711, revoke blob URLs when playback is rejected #652 (by @ly-wang19), and surface TTS provider rate limits as HTTP 429 #644 (by @ly-wang19)
- Renderer: make the code entrance animation play line by line #724 (by @tongshu2023)
- Security: point the SSRF local-network rejection at the
ALLOW_LOCAL_NETWORKSescape hatch #667 (by @mvanhorn) - Networking: bypass the proxy for loopback hosts and honor
NO_PROXY#718 (by @tongshu2023) - Fix overlay layout shift on the home and classroom pages #690 (by @cosarah)
Other Changes
- Drop the dead
ThumbnailSlidepath and fix@maic/dslNode ESM resolution #736 - Docs: correct the stale i18n location and supported-language list #640 (by @ly-wang19)
[0.2.2] - 2026-06-02
Features
- MAIC Editor (v0) — slide editing surface — A new Pro Mode toggle turns any generated slide into an editable canvas: select and edit text, insert text boxes and images, navigate and reorder slides from a thumbnail rail, with history-aware undo/redo. This is the first surface of the broader MAIC Editor framework (gated behind
NEXT_PUBLIC_MAIC_EDITOR_ENABLED) #615 - Editable outline before generation — The streaming course outline now morphs into an inline editor: review, edit, reorder, and add or delete scenes and bullet points, then confirm to generate the full course — so you catch structure problems before spending a full generation #558
- Offline-ready classroom export — Exported teaching resource packs and classroom ZIPs now inline external assets so interactive pages open fully offline, even when copied to another machine #613
- Add Claude Opus 4.8 and MiniMax M3 to the default model registry #635
- Add Gemini 3.5 Flash #584
- Add Xiaomi MiMo Token Plan support #578 (by @xuruiray)
- Add web search providers: Brave and Baidu #42 (by @YizukiAme), Bocha #524, and MiniMax #634
- Add Azure STT (Fast Transcription) as a speech-to-text provider #175 (by @ismailariyan)
- Add HappyHorse video adapter #509 (by @xuruiray) and Lemonade as an LLM provider #508
- Add OpenAI image generation environment-variable fallback #510 (by @xuruiray)
- Add generated-video manifest references so produced videos survive export/import #540
- Add Traditional Chinese (zh-TW) #517 (by @alvinets) and Brazilian Portuguese (pt-BR) #602 (by @hemanz) interface languages
Bug Fixes
- Server-configured providers are now admin-managed — providers set via server environment can no longer be overridden by client settings, preventing base-URL/key tampering on shared deployments #624; fixes server API-key fallback when the client echoes the provider base URL #533 (by @LooThao); auto-selects the server LLM model #577 (by @xuruiray); and enforces a "usable provider ⇒ concrete model" invariant #581
- Keep interactive scenes alive across remounts with an iframe keep-alive pool, so interactive content no longer reloads when navigating #629
- Restore the orchestration director's ability to answer the user's question and stop runaway turns (removed
maxTurns) #599; restore agent attribution in the director summary #554 (by @ashutoshrana) - Skip shapes with malformed SVG paths instead of aborting the whole PPTX export #505; prevent memory leaks and silent export failures #552 (by @arnow117)
- Add defensive checks in ChartElement to prevent crashes on malformed chart data #588 (by @tongshu2023)
- Let whiteboard code elements capture internal scroll/drag instead of the canvas #544 (by @cosarah)
- Preserve discussion triggers when importing classroom ZIPs #557 (by @cosarah)
- Fix generated video thumbnails #546
- Gate media snippets in the interactive-outlines prompt template #628
- Hide the unsupported MiniMax Hailuo fast text-to-video model #632; remove weak Lemonade recommended models #567 (by @cosarah)
- Fix Haiku 4.5 thinking controls #501
- Use an ESM import for TypeScript in the pptxgenjs rollup config #616
- Align zh-TW provider names with the rest of the locale set
Other Changes
- Add a Fumadocs-based documentation site #622
- Add a VoxCPM2 setup guide and tighten the README section #500 #502
- Fix the commercial licensing contact email #604 (by @DHQ1204)
[0.2.1] - 2026-04-26
Features
- VoxCPM2 TTS provider with voice cloning — OpenMAIC adapts to user-managed VoxCPM backends (vLLM-Omni, Nano-VLLM, official Python API). Clone any voice from a reference audio clip you upload or record in the browser, or let Auto Voice generate a fitting voice from each agent's persona at synthesis time. Voice profiles are stored locally to keep the serverless setup model. The Agent Bar exposes a searchable, previewable voice picker that draws from the global VoxCPM voice pool #496
- Per-model thinking configuration — First-class metadata for each model's reasoning capability (effort levels, on/off toggle, adjustable budget, or fixed thinking) flows through chat and all generation paths and is mapped to the right provider-specific request fields (Anthropic
thinking, OpenAIreasoning, etc.). The model selector becomes a unified provider/model/thinking popover with compact search and a much smaller toolbar footprint #494 - End-of-course completion page with persistent quiz state — When the outline is fully materialized, students see a course-complete view with quiz score card, scene-type stat cards, and a (motion-respecting) confetti celebration. Quiz answers persist on submit and grading results persist on completion, so navigating away and back restores the reviewing state with AI feedback intact instead of resetting #484
- Add latest released models including GPT-5.5, DeepSeek-V4 (
-pro,-flash), Xiaomi MiMo (mimo-v2.5-pro,mimo-v2.5), Tencent Hy3, and OpenRouter as a multi-provider gateway #481 #487 - Add OpenAI image generation (GPT-Image-2) as a media provider #481
- Refresh built-in model registries across Anthropic, DeepSeek, Kimi, Qwen, MiniMax, Grok, OpenAI, GLM, SiliconFlow, and Ollama; persisted local settings now rehydrate in registry order so newly curated lists appear consistent without clearing state #481
- Add inline search for recent classrooms on the home page with deferred filtering by name and description, keyboard-driven open/clear/collapse #476
- Add Deep-Interactive badge on classroom thumbnails for sessions generated with Interactive Mode #478
- Replace always-included media instruction blocks in generation prompts with conditional snippet includes gated on
imageEnabled/videoEnabled— disabled capabilities are removed from the prompt entirely instead of relying on negative-override directives the model often ignored #490 (by @YizukiAme)
Bug Fixes
- Fix language drift between outline and scene generation by unifying the languageDirective across the pipeline so the same target language flows from outline planning through every per-scene call #474
Other Changes
- Refactor whiteboard role prompts to file-based markdown templates and add a geometry-conflict detector (overlap, line-through-bbox, canvas clipping) that surfaces problems back to the model. Eval (flash, repeat 3, gemini-3.1-pro scorer) shows overall quality 5.4 → 6.1 and overlap 6.3 → 8.1 from prompt + detector alone #485
- Migrate orchestration prompt builders (
buildStructuredPrompt,buildDirectorPrompt,buildPBLSystemPrompt) from inline TS template literals to file-based markdown templates underlib/prompts/, sharing the loader infrastructure with the generation pipeline.prompt-builder.ts890 → 314 lines; future content tweaks land as markdown edits #459
[0.2.0] - 2026-04-20
Features
- Deep Interactive Mode — Generate hands-on interactive scenes (3D visualization, simulation, game, mind map/diagram, online programming) with an AI teacher who operates the UI to guide students. Fully responsive across desktop, tablet, and mobile #461
- Add code element support on the whiteboard — AI agents can write, display, and reference runnable code during lessons #385 (by @cosarah)
- Add Arabic (ar-SA) interface language #431 (by @YizukiAme)
- Add MinerU Cloud API as a PDF parsing provider, with a dedicated settings UI #438
- Add latest OpenAI models to the default config #416 (by @donghch)
- Add GLM-5.1 and GLM-5V-Turbo to GLM preset models #437
- Add international base URL shortcuts for GLM, Kimi, and MiniMax in provider settings #449
- Add anti-framing security headers (X-Frame-Options + CSP
frame-ancestors) with an optionalALLOWED_FRAME_ANCESTORSoverride #430 (by @YizukiAme) - Add i18n key alignment check to CI so missing or extra translation keys fail the build #447 (by @KanameMadoka520)
- Add whiteboard layout quality eval harness and unify it with the outline-language harness #425 #453
Bug Fixes
- Fix classroom ZIP export to use the latest classroom name from IndexedDB #435
- Fix spotlight cutout for text elements and add element-content variant for image/video #457
Other Changes
- Renew the README with Deep Interactive Mode showcase and visual assets #463 (by @Shirokumaaaa)
- Update Discord invite links across README, CONTRIBUTING, and issue templates
[0.1.1] - 2026-04-14
Features
- Add inline language inference for outline and PBL generation, replacing manual language selector #412 (by @cosarah)
- Add ACCESS_CODE site-level authentication for shared deployments #411
- Add classroom export and import as ZIP #418
- Add custom OpenAI-compatible TTS/ASR provider support #409
- Add Ollama as built-in provider with keyless activation #94 (by @f1rep0wr)
- Add Japanese (ja-JP) locale #365 (by @YizukiAme)
- Add Russian (ru-RU) locale #261 (by @maximvalerevich)
- Migrate i18n infrastructure to i18next framework #331 (by @cosarah)
- Add MiniMax provider support #182 (by @Hi-Jiajun)
- Add Doubao TTS 2.0 (Volcengine) provider #283
- Add configurable model selection for TTS and ASR #108 (by @ShaojieLiu)
- Add context-aware Tavily web search when PDF is uploaded #258 (by @nkmohit)
- Add course rename #58 (by @YizukiAme)
- Add end-to-end generation happy path test #405
Bug Fixes
- Fix DNS rebinding bypass in SSRF validation #386 (by @YizukiAme)
- Add ALLOW_LOCAL_NETWORKS env var for self-hosted deployments #366
- Fix custom provider baseUrl not persisting on creation #417 (by @YizukiAme)
- Hide Ollama from model selector when not configured #420 (by @cosarah)
- Fix agent configs not persisting in server-generated classrooms #336 (by @YizukiAme)
- Fix action filtering logic and add safety improvements #163 (by @zky001)
- Fix modifier-key combos triggering single-key shortcuts #359 (by @YizukiAme)
- Fix agent mode selection for conditionally set generatedAgentConfigs #373 (by @YizukiAme)
- Unify TTS model selection to per-provider and fix ElevenLabs model_id #326
- Allow model-level test connection without client-side API key #309 (by @cosarah)
- Add structured request context to all API error logs #337 (by @YizukiAme)
- Fix breathing bar background color in roundtable #307
Other Changes
- Add missing Ollama and Doubao provider names for ru-RU #389 (by @cosarah)
- Update Ollama logo to official version #400 (by @cosarah)
- Remove deprecated Gemini 3 Pro Preview model #142 (by @Orinameh)
- Update expired Discord invite link
- Create SECURITY.md #281 (by @fai1424)
New Contributors
@f1rep0wr, @maximvalerevich, @Hi-Jiajun, @cosarah, @zky001, @Orinameh, @fai1424
[0.1.0] - 2026-03-26
The first tagged release of OpenMAIC, including all improvements since the initial open-source launch.
Highlights
- Discussion TTS — Voice playback during discussion phase with per-agent voice assignment, supporting all TTS providers including browser-native #211
- Immersive Mode — Full-screen view with speech bubbles, auto-hide controls, and keyboard navigation #195 (by @YizukiAme)
- Discussion buffer-level pause — Freeze text reveal without aborting the AI stream #129 (by @YizukiAme)
- Keyboard shortcuts — Comprehensive roundtable controls: T/V/Esc/Space/M/S/C #256 (by @YizukiAme)
- Whiteboard enhancements — Pan, zoom, auto-fit #31, history and auto-save #40 (by @YizukiAme)
- New providers — ElevenLabs TTS #134 (by @nkmohit), Grok/xAI for LLM, image, and video #113 (by @KanameMadoka520)
- Server-side generation — Media and TTS generation on the server #75 (by @cosarah)
- 1.25x playback speed #131 (by @YizukiAme)
- OpenClaw integration — Generate classrooms from Feishu, Slack, Telegram, and 20+ messaging apps #4 (by @cosarah)
- Vercel one-click deploy #2 (by @cosarah)
Security
- Fix SSRF and credential forwarding via client-supplied baseUrl #30 (by @Wing900)
- Use resolved API key in chat route instead of client-sent key #221
Testing
New Contributors
@YizukiAme, @nkmohit, @KanameMadoka520, @Wing900, @Bortlesboat, @JokerQianwei, @humingfeng, @tsinglua, @mehulmpt, @ShaojieLiu, @Rowtion