Files
OpenMAIC/CHANGELOG.md
T
2026-09-28 18:25:23 +08:00

102 KiB
Raw Permalink Blame History

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/storage 0.34.0: course ownership is no longer recorded on document_stages. PgDocumentStore never reads or writes document_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 new documentOwnership option ({ 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: false turns document scoping off, and on an owner-bound store it requires allowCrossOwnerDocumentAccess: true, so binding an owner for folders or asset principals cannot silently expose every owner's documents. forOwner(ownerId) (or ownerId) without documentOwnership now 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's assetReferencePrincipals requires documentOwnership too, because the backfill reads each document's owner from that relation. A new folders: false option lets a host whose own document_stages has no folder_id column use the store without folders. Fresh installs no longer get the owner_id column or its two indexes (document_stages_owner_idx, document_stages_owner_folder_idx); document_stages_folder_idx replaces the latter.

  • Server persistence: stage_meta is the only record of who owns a course. At startup, courses whose owner was recorded only on document_stages.owner_id are adopted into stage_meta (idempotent, logged with a count; where the two records disagree stage_meta stands 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-supplied x-learner-key behind the development token. PERSISTENCE_DEV_TOKEN, NEXT_PUBLIC_PERSISTENCE_TOKEN and PERSISTENCE_ALLOW_INSECURE_DEV_AUTH are removed and ignored; the browser learns its learner key from GET /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 behind ACCESS_CODE or 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_BYTES is 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_merges table (with the stage_meta and owner-material schemas). Nothing is claimed until a claim is triggered; the default trigger is the explicit POST /api/identity/claim.
  • The first start on an existing installation changes document_stages under table locks. CREATE INDEX document_stages_folder_idx builds the new index under a SHARE lock, which blocks writes to document_stages for the length of the build. DROP INDEX of document_stages_owner_idx / document_stages_owner_folder_idx takes a brief ACCESS EXCLUSIVE lock. If a host added NOT NULL or a default to owner_id, ALTER TABLE ... DROP NOT NULL / DROP DEFAULT also 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 other CREATE INDEX IF NOT EXISTS in 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 from instrumentation.ts with configureOwnerAuthentication({ methods, anonymousFallback? }) (single-shot, sealed on first use, validated at boot). Each method answers authenticated, not-applicable (no credential of its kind) or invalid (its credential is present but bad); core keeps the first authenticated answer, refuses any invalid answer with 401 INVALID_CREDENTIAL without asking later methods, and when no method applies falls back to the anonymous cookie owner, or answers 401 with anonymousFallback: false. Route handlers and Server Actions ask the same methods in the same order, once per request. PERSISTENCE_SHARED_OWNER_ID still selects the sharedTeam owner on its own; beside host methods it is kept by listing sharedTeamAuthMethod() 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 in lib/server/identity/host/, the only place the boundary test lets code read gateway identity headers or an incoming Authorization header. On 403 OWNER_RETIRED, core calls clearCredential only on the anonymous fallback and on methods that declare issuesAnonymousOwners: true, never on an account method. The unreleased OWNER_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 pendingClaim naming that anonymous owner (never for an anonymous principal or the built-in sharedTeam, 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=auto claims 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 new owner_merges table, 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 answer 403 OWNER_RETIRED, with a Set-Cookie that 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 answers 503 OWNER_BUSY with Retry-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 with registerClaimParticipant, and an auth method may implement describeStoredOwner and clearCredential; principalFromStoredOwner(ownerId) describes a stored id for work without a request. The runtime contract's POST /runtime/learners/merge now 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/storage 0.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) and PgRuntimeStore (resolveFinalLearner) take a hook that runs as the create transaction's first statement. Every HTTP handler (runtime, documents, assets) now answers a DocumentWriteRefusedError as 403 with its code, and the new StorageBusyError(code, message, retryAfterSeconds) as 503 with its code and Retry-After. PgUserSkillStore.list orders ties by id.
  • Server persistence: host extension hooks, registered once from instrumentation.ts next to the owner auth methods and sealed on first use. configurePersistenceHooks adds authorizeCreate / onCreate (run once per created course inside its transaction; a refusal answers 403 CREATE_REFUSED and a throw rolls the create back), a library provider for what GET /api/stages lists (never an id the read path would refuse), and beforeAssetAllocate (refuse an upload, create or replace, with any Response before bytes are stored or quota counted). Hooks receive an actor whose source is 'request' (with the resolved principal) or 'background' (an agent run; a refusal reaches the agent as a fixed message). configureAssetByteStore replaces the ASSET_S3_BUCKET switch for the request path and the asset collector alike; under ASSET_BYTE_EGRESS=redirect a store that does not declare signsReadUrls stops the server at boot. Nothing changes when no hook is registered.
  • @openmaic/storage 0.33.0: DocumentWriteRefusedError(stageId, code, message) lets a store refuse a write as policy; the document HTTP handler answers 403 with the error's code and applies nothing. isDocumentWriteRefusedError also 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 unscoped documentStore, which wrote courses with no owner check and no host hooks. Use the owner-bound store (createOwnerBoundDocumentStore, or getOwnerScopedDocumentStore).
  • Owner identity: POST /api/chat/pi answers 401 when 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 on DATABASE_URL alone instead of also requiring OPENMAIC_AGENT_RUNTIME_ENABLED, so a deployment with server persistence and the runtime off no longer answers 404 there and shows an empty, unavailable library. Agent routes (/api/agent/**, /api/skills/**, /api/materials/**) still require the runtime, and without a DATABASE_URL everything answers 404 as before. GET /api/agent/runtime also reports persistence.
  • Startup: a configuration refused by the boot validation in instrumentation.ts (a malformed ASSET_QUOTA_BYTES, ASSET_PENDING_TTL_MS, OWNER_WRITE_LOCK_WAIT_MS or OWNER_CLAIM_LOCK_WAIT_MS; an OWNER_CLAIM_TRIGGER other than explicit / auto; the removed OWNER_AUTHENTICATOR / TRUSTED_PROXY_* variables; a malformed PERSISTENCE_SHARED_OWNER_ID, one without ACCESS_CODE or beside a registration that leaves out sharedTeamAuthMethod(), or sharedTeamAuthMethod() registered without it or not last; ASSET_S3_BUCKET beside a registered asset byte store, or ASSET_BYTE_EGRESS=redirect with a byte store that does not declare signsReadUrls) now exits the Node.js server with code 1 after one [boot] Invalid server configuration line carrying the original message. Any other boot failure (a module missing from the build, a host registration call that throws) also exits with code 1, printed as [boot] Server startup failed with its stack. Previously the server logged "Failed to prepare server", kept listening, and answered every request with 500. 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 answer 500. Every schema bootstrap (documents, stage_meta and 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_stage also 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/storage 0.32.0: PgDocumentStore takes assetReferencePrincipals (and AssetCollector a matching per-owner function) so a document write can no longer commit or pin another principal's asset entry; a store can refuse createSession with RuntimeStageNotFoundError (404 STAGE_NOT_FOUND); and a session create over a taken id answers 409 SESSION_ALREADY_EXISTS whoever holds it, instead of 403 for 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 localhost default (Ollama, Lemonade, VoxCPM), needs ALLOW_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 follow ALLOW_LOCAL_NETWORKS.
  • A client-supplied AliDocMind endpoint must be an official docmind-api.<region>.aliyuncs.com host over HTTPS; other endpoints answer 403 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-provider success no longer includes status;
    • /api/azure-voices answers provider failures with 502;
    • /api/provider/probe-models reports other HTTP failures as 502 with the status class only;
    • LLM errors for a client-selected endpoint read Cannot connect to API: connection failed (or request 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_PROXY with HTTP_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 PUT of 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_scene exposes 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-URL header 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 mediaGenerations in 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 pdfContent input #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/data owned by the runtime user so classroom persistence works #1442; build workspace packages in the builder stage #1598
  • Misc: send taskEngineMode on 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, and POST /api/access-code/verify had no throttling. Both verifiers now enforce a 7-day server-side lifetime and reject non-canonical signatures, and verification is rate-limited per client when TRUST_PROXY_HEADERS=true (with a warning to use a long random ACCESS_CODE when 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 /preview and /render paths, 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/.xhtml documents 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/importer 0.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-media and the outbound URL guard accepted the Alibaba Cloud instance-metadata address 100.100.100.200 because 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 of ALLOW_LOCAL_NETWORKS GHSA-6xff-rgjg-v33f (reported by @lihua666a-cell)
  • /api/proxy-media validated a hostname and then let fetch() 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/classroom accepted 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/tts now 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/classroom no longer honours a client-supplied stage.id; the id in 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 of ALLOW_LOCAL_NETWORKS. CGNAT 100.64.0.0/10 (Tailscale and similar overlays) is blocked by default and allowed with ALLOW_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_ENABLED build-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 __name helper 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.error for image and video provider test results instead of rendering failed: 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_DIR GHSA-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=production unless 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_pptx parsing 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=true is 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 set PERSISTENCE_ALLOW_INSECURE_DEV_AUTH=true
  • POST /api/classroom now 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_scene pass widgetType and widgetOutline for interactive pages #1259; make generate_video asynchronous with a placeholder reference #1267
  • Pro workbench: reference single PPT elements from chat #1224; generate concise conversation titles #1275
  • Render service: add a POST /preview endpoint #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-exp with 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_scene failure causes #1316; bound generate_scene retries 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_update frames #1279
  • DSL: migrate away legacy rotate/height fields on line elements #1261
  • Export: recheck script readiness on download #1159
  • Importer: fix pnpm install failing 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 -rf with a cross-platform fs.rmSync in 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_search tool on the session runner #1185, session materials with a fetch_url tool 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_key with the legacy asset_id dropped #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.local into 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/generation package — 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 and lib/generation is 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-classroom instead 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 VideoTimeline IR 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 in lib/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/storage KV + 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/renderer v2 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/dsl now 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-origin from the interactive srcDoc iframe 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 the maic-import/maic-renderer workspace 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_NETWORKS escape 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 ThumbnailSlide path and fix @maic/dsl Node 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, OpenAI reasoning, 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 under lib/prompts/, sharing the loader infrastructure with the generation pipeline. prompt-builder.ts 890 → 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 optional ALLOWED_FRAME_ANCESTORS override #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

  • Add Vitest unit testing infrastructure #144
  • Add Playwright e2e testing framework #229

New Contributors

@YizukiAme, @nkmohit, @KanameMadoka520, @Wing900, @Bortlesboat, @JokerQianwei, @humingfeng, @tsinglua, @mehulmpt, @ShaojieLiu, @Rowtion