# 麦洛学习项目交接 > 交接基线:2026-08-15 > > 项目根:/Users/inmanw/项目/麦洛学习 > > 代码:/Users/inmanw/项目/麦洛学习/OpenMAIC > > 文档:/Users/inmanw/项目/麦洛学习/docs ## 1. 一句话现状 三端能力边界、大型连续课程生成、完整 classroom 冻结发布和精确版本学习链路已经落地; 当前仍是一份 OpenMAIC Next.js 16.1.2 应用与文件仓库,learner desktop / ops / server 尚未物理拆分,发布层尚未迁到 Postgres/对象存储,learner 身份与资源 owner 也尚未完成。这两项都是公网生产上线前的硬前置。 ## 2. 接手前必须知道的本机事实 | 项目 | 当前事实 | |---|---| | OpenMAIC 版本 | package version 0.3.2 | | Next.js | 16.1.2 | | 版本管理 | OpenMAIC 当前不是 Git worktree,没有可依赖的 commit/diff/rollback | | 环境文件 | .env.local 当前存在;不要输出、分享或提交其中内容 | | .env | 当前不存在 | | 代码与文档位置 | 文档在 OpenMAIC 同级的 docs,不在代码目录内 | | 存储 | 课程发布主链路仍使用 data 下的文件仓库 | | 写入拓扑 | 只能单 Node.js 写实例 | 在建立 Git 基线前,任何后续改动都应小批次、保留触及文件清单,并先备份 data 与 .env.local。 ## 3. 产品与能力矩阵 | 能力 | learner desktop | ops | server | |---|---:|---:|---:| | 普通文本单课件后台生成 | 是 | 同构代码仍可用 | 当前承载 job/API | | 材料/Interactive/职教前台生成 | 是 | 同构代码仍可用 | 提供生成 API | | 原单课件编辑与 Stage | 是 | 模块审阅 | 否 | | 大型课程主 Agent | 否 | 是 | 当前同进程运行 | | 框架人工确认、模块重试 | 否 | 是 | 否 | | classroom 冻结与课程发布 | 否 | 冻结并发起 | 整批事务接收与发布 | | courseware / manifest 读服务 | 消费 | 发布 | 主责 | | 大课目录与精确模块学习 | 是 | 验收 | 提供数据 | | Agent/Chat/白板/PBL/音频 | 原样保留 | 审阅时原样保留 | 供数据/模型端点 | 当前部署角色为 all、ops、server、learner。生产默认 learner,开发/测试默认 all。 大型课程运营能力由 role + ACCESS_CODE 会话保护;浏览器不再持有发布 token。 server 角色已是无头服务:所有页面以及运营/API 家族返回 404,但这不等于 其他 API 已有 learner 身份和资源级授权。当前已额外隐藏 provider 验证、 MP4 export、开发态 persistence、usage、全局 job 列表和未鉴权媒体代理; 原 Chat/Quiz/PBL/TTS 与单课件生成等待正式 learner capability,不得直接删除。 全局 job 列表的 HEAD 与 GET 同样隐藏,内部整课发布在尾斜杠形式下也仍是 POST-only。ops 远程发布成功后会用当前 publication receipt 保护 source classroom, 发布和 job/source 删除还有同进程互斥。这不包含被重生清除的历史回执,也不是 跨副本锁;多实例生产化时必须改为持久发布历史和数据库事务/分布式锁。 ACCESS_CODE 会话默认 TTL 为 7 天,cookie Max-Age 与服务端验证同步;登录只接受 同源 application/json,实际请求体最大 8 KiB,且有进程内失败限流。所有 cookie 授权的 ops 写请求还会校验同源 Origin。 反向代理部署要配置 OPS_PUBLIC_ORIGIN 作为 canonical origin;该校验不信任 X-Forwarded-Host。 ## 4. 不可跨越的保护边界 本项目的原则是:不改原课堂内核,只改外围编排、权限、发布、目录和存储。 受保护范围: - components/stage.tsx - components/edit/PlaybackChromeRoot.tsx - components/chat/** - components/roundtable/** - components/scene-renderers/** - lib/playback/** - lib/chat/** - lib/action/** - lib/whiteboard/** - lib/pbl/** - 原 Stage / Scene / Action DSL 和 /classroom/{id} 链路 验收不变式: 1. learner 发布模块仍走 /learn → /classroom → 原 Stage。 2. learner 外层没有第二套 AssistantPanel;原 ChatArea/Roundtable 是唯一课堂问答入口。 3. frozen bundle 带完整 Agent persona/voice、Stage/Scene 白板、四类 Scene、Action、 HTML、Quiz、PBL、媒体和 speech 音频。 4. 大课模块仍复用原单课件 agent,没有第二套内容生成器。 5. 拆分工作不得以修复无关 Chat 断言为理由修改 protected core。 ## 5. 已实现主链路 ### 5.1 learner desktop 单课件双路径 app/page.tsx 的当前分流: - 普通纯文本需求 → POST /api/generate-classroom → 持久化后台 job; - 有材料,或 Interactive/职教开关打开 → lib/generation/foreground-session.ts 保存文件引用和 generationSession → 原 /generation-preview。 后台路径可以关闭页面后从 /tasks 查看;前台路径保留浏览器拥有的材料与原互动/职教生成。 ### 5.2 大型课程 POST /api/courses → 主 Agent CourseFramework → framework_ready → 人工确认 → 模块按序复用 classroom job → outputDigest / continuityInputRefs → 全部成功 主 Agent 输出 targetAudience、courseGoals、continuityContract,以及每模块 generationPrompt、输入/输出知识边界和 excludedTopics。任一模块失败会暂停后续模块; 修复后按顺序续跑。 所有大课模块固定 Interactive Mode 与 TTS。若没有生成的非空 HTML interactive Scene, 在 persistClassroom 前失败。 ### 5.3 发布 validate CourseRecord snapshot → persisted classroom 转完整 frozen bundle → 每模块暂存 unpublished 精确版本 → 再验证来源未变化 → 本批切 published → schemaVersion=2 manifest 发布保留完整课堂,并拒绝缺 Agent、音频、媒体、HTML、连续性或发生 digest 漂移的模块。 每个 manifest module 固定 coursewareId + coursewareVersion + contentHash。 发布校验还使用 sourceRevisionHash 锁定完整持久化 classroom;ops 只在 server 返回 的 id/version/contentHash 逐项一致后保存带 sourceRevisionHash 的 publication receipt。 独立部署时,ops 把 metadata 与全部模块 ZIP 作为一个 multipart 请求发往 /api/internal/course-publish,server 只接受 COURSEWARE_PUBLISH_TOKEN Bearer。模块在 schema-v2 manifest exact pin 提交前不会进入公共 catalog/detail/download。未配置远程 origin 时的同源 fallback 复用同一内容幂等与可见性规则。 contentHash v2 忽略 manifest.json.exportedAt,使相同内容的远程/本地重试返回既有 manifest;无 contentHashVersion 的真正历史包仍按 v1 原始字节哈希验证。 ### 5.4 学习 manifest 精确 pin → registry 精确版本 → bundle 内外 id/version/hash/completeness 校验 → 物化 Agent/音频/媒体/document → hash 隔离的本地 stage id → 原 Stage 发布文档不带 outlines,所以课堂页不会触发场景/媒体恢复生成。进入原 Stage 后,原课堂 教师/助教、动态评分和 PBL 运行时仍可按原设计调用模型。 ## 6. 关键代码地图 ### 角色与鉴权 | 路径 | 作用 | |---|---| | lib/config/deployment-role.ts | 角色解析、生产 fail closed、manage_courses | | lib/server/ops-access.ts | ops role + ACCESS_CODE cookie route guard | | middleware.ts | 运营页面/API 第一层边界 | | app/api/coursewares/route.ts | ops 会话或 server Bearer 的双发布入口 | | e2e/tests/deployment-role-boundary.spec.ts | learner 隐藏/拒绝运营能力 | ### 单课件双路径 | 路径 | 作用 | |---|---| | app/page.tsx | 普通后台与材料/Interactive/职教前台分流 | | lib/generation/foreground-session.ts | 文件持久化与原 preview session 适配 | | app/generation-preview | 原前台生成 | | app/api/generate-classroom | 后台 job | | lib/server/classroom-generation.ts | 原单课件服务端生成管线与 HTML 门 | ### 主 Agent 与连续性 | 路径 | 作用 | |---|---| | lib/course-framework/types.ts | CourseFramework、generationPrompt、digest/ref 契约 | | lib/prompts/templates/course-framework/system.md | 主 Agent prompt | | lib/course-framework/generate-framework.ts | 严格解析、校验与重试 | | lib/course-framework/compat.ts | 旧 CourseRecord 读取兼容 | | lib/course-framework/runner.ts | 人工确认后的顺序单课件复用 | | lib/course-framework/module-digest.ts | 实际产出摘要与 semanticHash | | lib/course-framework/publish-validation.ts | digest、连续性、HTML 发布门 | | lib/course-framework/store.ts | 文件记录、原子更新、读时自愈 | ### 冻结与版本 | 路径 | 作用 | |---|---| | lib/server/classroom-courseware-publish.ts | persisted classroom → 完整 frozen bundle | | lib/bundle/packager.ts | bundle 组装、完整性和内容 hash | | lib/bundle/serialize.ts | Stage/Agent/Scene/Action portable manifest | | lib/courseware-repo | append-only 课件版本与 bundle bytes | | lib/course-manifest-repo | schema v2 课程清单历史 | | app/api/courses/{id}/publish | 暂存、二次校验、提交、失败补偿 | ### learner | 路径 | 作用 | |---|---| | app/api/learn/courses | 课程目录与精确 manifest;legacy 返回 409 | | app/learn/course/{id} | 课程模块列表和精确 pin 导航 | | lib/bundle/learner-load.ts | 严格身份/哈希/完整性校验与回滚 | | lib/import/import-classroom-core.ts | portable manifest → 原课堂 document | | app/learn/{coursewareId} | 加载并跳转 classroom | | app/classroom/{id} | 仍挂原 Stage;learner query 只加返回导航 | ## 7. 环境与数据目录 关键角色变量: - OPENMAIC_DEPLOYMENT_ROLE - NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE - ACCESS_CODE - ACCESS_CODE_SESSION_TTL_SECONDS - OPS_PUBLIC_ORIGIN - COURSEWARE_PUBLISH_TOKEN - COURSE_PUBLISH_SERVER_BASE_URL - COURSEWARE_PUBLIC_BASE_URL 严禁重新加入 NEXT_PUBLIC_COURSEWARE_PUBLISH_TOKEN。 关键目录变量: - CLASSROOM_DATA_DIR - CLASSROOM_JOBS_DIR - COURSE_FRAMEWORK_DIR - COURSEWARE_DATA_DIR - COURSEWARE_BUNDLE_DIR - COURSE_MANIFEST_DIR - COURSEWARE_MAX_UPLOAD_BYTES - COURSE_PUBLISH_MAX_UPLOAD_BYTES - COURSE_PUBLISH_TIMEOUT_MS 模型按需配置 MODEL_ROUTES 与 provider/TTS/image/video/web-search 变量。 大课至少需要 course-framework 与原 generate-classroom/scene stages 可解析; 发布需要所有 speech 的持久化音频。 通用 NEXT_PUBLIC_PERSISTENCE、DATABASE_URL、ASSET_S3_BUCKET 不等于发布仓库已经迁移。 完整矩阵和示例见 deployment-3-tier.md。 默认数据: | 目录 | 内容 | |---|---| | data/classrooms | classroom JSON、本地 media/audio 子目录 | | data/classroom-jobs | 单课件 job | | data/course-frameworks | ops CourseRecord | | data/coursewares | courseware 版本历史 metadata | | data/courseware-bundles | id/version ZIP | | data/course-manifests | course manifest 版本历史 | | data/usage | 计量 | | data/tts-cache | QA/TTS 缓存能力仍在代码中 | ## 8. 单实例限制与未来存储 当前 version 分配与 read-modify-write 依赖进程内 Map 锁。临时文件 + rename/link 只能保证 文件操作层面的原子性,不能协调多个 Node.js 进程。发布失败只会由应用层把本批 记录补偿回 unpublished,不具有跨进程隔离性、原子提交或崩溃一致性。现阶段: - ops/framework writer 单实例; - server courseware/manifest writer 单实例; - 生成和发布期间避免滚动重启; - runner 中断后依赖持久记录自愈,再由运营续跑; - 备份 metadata history 与 bundle bytes 必须成对。 公网生产上线或多实例前必须完成: - Postgres 唯一键和事务分配 courseware/manifest version; - 行锁或 advisory lock 保护每个 course/courseware; - 对象存储以 id/version/hash 为 key 并 put-if-absent; - CDN 只分发已被 schema-v2 manifest exact pin 的 immutable bundle;当前文件下载路由已用 同一门禁隐藏 promote→manifest 的中间状态,未来对象存储不能以公开 URL 绕过它; - 持久任务队列取代内存 runner; - ops 已可通过内部 HTTP 一次提交 metadata 与全部 bundle;独立部署必须配置 COURSE_PUBLISH_SERVER_BASE_URL,不再直接写 server 文件。 ## 9. legacy manifest 重发 不要手工给旧记录填 current latest。 1. learner API 对旧记录返回 409,这是预期 fail closed。 2. 备份旧 singleton/history 文件。 3. 在 ops 打开原 CourseRecord 并运行发布校验。 4. 若缺 outputDigest、continuityInputRefs、Interactive HTML、音频或媒体,从最早失败模块 开始按顺序重生成。 5. 人工审阅后重发课程。 6. 新 manifest version 使用 schemaVersion=2 并精确 pin;旧版本保留审计但不可学习。 7. 若原 CourseRecord/classroom 已丢失,只能按新课程重新生产,不能伪造历史身份。 ## 10. 验收结果与命令 ### 已确认结果 | 检查 | 结果 | |---|---| | 首页双路径 Playwright | 3 passed | | learner 部署边界 Playwright | 1 passed | | 三端角色/ops access 定向单测 | 通过 | | 主 Agent、连续性、Interactive 回归 | 通过 | | 冻结发布相关合并回归 | 10 files / 85 tests 通过 | | 本次文档交接定向回归 | 18 files / 108 tests 通过 | | 本次 TypeScript 检查 | pnpm exec tsc --noEmit,exit 0 | | 本次四文档格式检查 | Prettier,exit 0 | | 受保护课堂广泛回归 | 130 files / 1,294 tests 中 1,291 通过,3 个 Chat 基线漂移 | | 本次隔离 Chat 复现 | 3 files;40 passed / 3 failed | | i18n key check | exit 1;9 个非 zh-CN/en-US locale 各缺 180 个键 | ### 定向回归 在 OpenMAIC 目录执行: pnpm exec vitest run \ tests/config/deployment-role.test.ts \ tests/server/ops-access.test.ts \ tests/generation/foreground-session.test.ts \ tests/server/classroom-outline-mode.test.ts \ tests/server/classroom-generation-retry.test.ts \ tests/course-framework \ tests/bundle \ tests/courseware ### 静态与构建 pnpm exec tsc --noEmit pnpm build pnpm exec prettier ../docs/architecture-3-tier.md ../docs/deployment-3-tier.md ../docs/large-course-mode.md ../docs/handover.md --check ### 浏览器边界 pnpm exec playwright test \ e2e/tests/home-to-generation.spec.ts \ e2e/tests/deployment-role-boundary.spec.ts \ --project=chromium ### i18n 已知缺口 pnpm check:i18n-keys 该命令当前预期失败,输出为 ar-SA、es-MX、fr-FR、ja-JP、ko-KR、pt-BR、ru-RU、 vi-VN、zh-TW 各缺 180 个相对 en-US 的键。运行时有 fallback,但发布前应补齐。 ## 11. 已知 3 个受保护 Chat 基线漂移 隔离命令: pnpm exec vitest run \ tests/lib/chat/pi/director-tool-wiring.test.ts \ tests/lib/chat/pi/route-cue-user.test.ts \ tests/lib/chat/pi/prompts.test.ts 稳定失败: 1. tests/lib/chat/pi/prompts.test.ts - keeps concept and mechanism questions teacher-led before student reactions - 当前 prompt 明确课堂只有 teacher + user,旧断言仍期待 assistant fallback 文字。 2. tests/lib/chat/pi/route-cue-user.test.ts - hands successful web evidence to one child and clears it before later delegations - 旧 fixture 断言 URL 必须直接出现在 child prompt 字符串。 3. tests/lib/chat/pi/route-cue-user.test.ts - does not leak consumed web evidence after the selected child fails - 同属 evidence attachment 与旧 prompt 字符串断言漂移。 大型课程和冻结发布改造未修改 lib/chat/** 或 app/api/chat/pi。按用户确定的保护边界, 这 3 项只记录、隔离复现,不在本改造中修改 Chat 内核、prompt 或断言。 ## 12. 未完成事项 按优先级: 1. 建立 Git 基线并备份 data/.env.local。 2. 完成 learner 用户身份、classroom/job owner、资源级授权、课程权益和成本限额。 在此之前,不得宣称匿名 Chat、生成、TTS、搜索等高成本 API 已安全。 3. 把 courseware/manifest metadata 迁到 Postgres、bundle 迁到私有对象存储,并在 CDN 层保留 manifest exact-pin 可见性门禁。 4. 按 deployment-3-tier.md 的顺序拆 server → learner desktop → ops;内部整批 HTTP adapter 已完成,可直接迁移而不是重新设计。 5. 将大课目录 localStorage 完成标记改为 runtime 进度聚合。 6. 补齐 9 个 locale 的 180 个键。 7. 在真实部署网络上补一条 ops → server → learner smoke;仓内已有两个隔离文件根、mock fetch 的无 LLM 跨进程契约测试。 拆分期间继续坚持:移动边界、替换 adapter、保持契约;不要复制或重写原 Stage/Chat。