cotutor 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +247 -21
- package/NOTICE +42 -0
- package/README.md +107 -24
- package/bin/cotutor.js +4 -4
- package/cards/canvas/card.css +8 -0
- package/cards/canvas/card.md +68 -0
- package/cards/choice/card.css +18 -0
- package/cards/choice/card.md +55 -0
- package/cards/code/card.css +5 -0
- package/cards/code/card.md +46 -0
- package/cards/dictation/card.css +10 -0
- package/cards/dictation/card.md +97 -0
- package/cards/image/card.css +10 -0
- package/cards/image/card.md +52 -0
- package/cards/lecture/card.css +9 -0
- package/cards/lecture/card.md +69 -0
- package/cards/paint/card.css +20 -0
- package/cards/paint/card.md +77 -0
- package/cards/photo/card.css +15 -0
- package/cards/photo/card.md +61 -0
- package/cards/read/card.css +6 -0
- package/cards/read/card.md +51 -0
- package/cards/text/card.css +3 -0
- package/cards/text/card.md +69 -0
- package/cards/tianzige/card.css +17 -0
- package/cards/tianzige/card.md +78 -0
- package/cards/tutor/card.css +66 -0
- package/cards/tutor/card.md +148 -0
- package/cards/word/card.css +24 -0
- package/cards/word/card.md +91 -0
- package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +159 -0
- package/dist/app/activity-eval-rules.d.ts +62 -0
- package/dist/app/activity-eval-rules.js +276 -0
- package/dist/app/activity-eval.d.ts +65 -0
- package/dist/app/activity-eval.js +158 -0
- package/dist/app/build.d.ts +11 -0
- package/dist/app/build.js +45 -0
- package/dist/app/cli/activities.d.ts +63 -0
- package/dist/app/cli/activities.js +158 -0
- package/dist/app/cli/cert.d.ts +7 -0
- package/dist/app/cli/cert.js +36 -0
- package/dist/app/cli/chalk/bake.d.ts +10 -0
- package/dist/app/cli/chalk/bake.js +58 -0
- package/dist/app/cli/chalk/browser.d.ts +16 -0
- package/dist/app/cli/chalk/browser.js +142 -0
- package/dist/app/cli/chalk/build.d.ts +14 -0
- package/dist/app/cli/chalk/build.js +100 -0
- package/dist/app/cli/chalk/dub.d.ts +51 -0
- package/dist/app/cli/chalk/dub.js +106 -0
- package/dist/app/cli/chalk/load-scene.d.ts +34 -0
- package/dist/app/cli/chalk/load-scene.js +117 -0
- package/dist/app/cli/chalk/main.d.ts +7 -0
- package/dist/app/cli/chalk/main.js +198 -0
- package/dist/app/cli/chalk/serve.d.ts +4 -0
- package/dist/app/cli/chalk/serve.js +28 -0
- package/dist/app/cli/chalk/snap.d.ts +65 -0
- package/dist/app/cli/chalk/snap.js +103 -0
- package/dist/app/cli/chalk/typecheck.d.ts +7 -0
- package/dist/app/cli/chalk/typecheck.js +51 -0
- package/dist/app/cli/doctor.d.ts +23 -0
- package/dist/app/cli/doctor.js +617 -0
- package/dist/app/cli/grades.d.ts +14 -0
- package/dist/app/cli/grades.js +44 -0
- package/dist/{cli → app/cli}/init.d.ts +3 -1
- package/dist/app/cli/init.js +155 -0
- package/dist/app/cli/main.js +715 -0
- package/dist/app/cli/migrate.d.ts +58 -0
- package/dist/app/cli/migrate.js +253 -0
- package/dist/app/cli/serve.d.ts +37 -0
- package/dist/app/cli/serve.js +98 -0
- package/dist/app/cli/shipped.d.ts +30 -0
- package/dist/app/cli/shipped.js +47 -0
- package/dist/app/cli/skeleton.d.ts +38 -0
- package/dist/app/cli/skeleton.js +161 -0
- package/dist/app/cli/skills.d.ts +55 -0
- package/dist/app/cli/skills.js +189 -0
- package/dist/app/cli/themes.d.ts +52 -0
- package/dist/app/cli/themes.js +148 -0
- package/dist/app/cli/tutors.d.ts +73 -0
- package/dist/app/cli/tutors.js +250 -0
- package/dist/app/cli/usage.d.ts +2 -0
- package/dist/app/cli/usage.js +30 -0
- package/dist/app/dictation/recognize.d.ts +18 -0
- package/dist/app/dictation/recognize.js +6 -0
- package/dist/app/dictation/route.d.ts +52 -0
- package/dist/app/dictation/route.js +510 -0
- package/dist/app/diff.d.ts +11 -0
- package/dist/app/diff.js +71 -0
- package/dist/app/http/access.d.ts +62 -0
- package/dist/app/http/access.js +180 -0
- package/dist/app/http/app.d.ts +246 -0
- package/dist/app/http/app.js +1212 -0
- package/dist/app/http/dev-activities.d.ts +115 -0
- package/dist/app/http/dev-activities.js +193 -0
- package/dist/app/http/dev-overview.d.ts +108 -0
- package/dist/app/http/dev-overview.js +209 -0
- package/dist/app/http/dev-tutor-files.d.ts +80 -0
- package/dist/app/http/dev-tutor-files.js +151 -0
- package/dist/app/http/parent-home.d.ts +37 -0
- package/dist/app/http/parent-home.js +203 -0
- package/dist/app/http/range.d.ts +9 -0
- package/dist/app/http/range.js +19 -0
- package/dist/app/http/raw-view.d.ts +117 -0
- package/dist/app/http/raw-view.js +149 -0
- package/dist/app/http/send-file.d.ts +7 -0
- package/dist/app/http/send-file.js +29 -0
- package/dist/app/http/stage.d.ts +24 -0
- package/dist/app/http/stage.js +97 -0
- package/dist/app/http/theme.d.ts +13 -0
- package/dist/app/http/theme.js +41 -0
- package/dist/app/machine/bailian.d.ts +28 -0
- package/dist/app/machine/bailian.js +88 -0
- package/dist/app/mock/mock-live.d.ts +16 -0
- package/dist/app/mock/mock-live.js +140 -0
- package/dist/app/mock/mock.d.ts +89 -0
- package/dist/app/mock/mock.js +1117 -0
- package/dist/app/replay.d.ts +94 -0
- package/dist/app/replay.js +170 -0
- package/dist/app/skills/analyze-doc.d.ts +3 -0
- package/dist/app/skills/analyze-doc.js +47 -0
- package/dist/app/skills/home-doc.d.ts +5 -0
- package/dist/app/skills/home-doc.js +112 -0
- package/dist/app/skills/tune-doc.d.ts +3 -0
- package/dist/app/skills/tune-doc.js +80 -0
- package/dist/app/talk/list.d.ts +11 -0
- package/dist/app/talk/list.js +9 -0
- package/dist/app/talk/route.d.ts +16 -0
- package/dist/app/talk/route.js +142 -0
- package/dist/app/workspace.d.ts +58 -0
- package/dist/{cli → app}/workspace.js +82 -36
- package/dist/app/ws/route.d.ts +25 -0
- package/dist/app/ws/route.js +29 -0
- package/dist/app/ws/ws.d.ts +31 -0
- package/dist/app/ws/ws.js +169 -0
- package/dist/board/canvas-chat.d.ts +97 -0
- package/dist/board/canvas-chat.js +175 -0
- package/dist/board/card-guards.d.ts +15 -0
- package/dist/board/card-guards.js +38 -0
- package/dist/board/cards/canvas.d.ts +41 -0
- package/dist/board/cards/canvas.js +83 -0
- package/dist/board/cards/choice.d.ts +20 -0
- package/dist/board/cards/choice.js +64 -0
- package/dist/board/cards/code.d.ts +9 -0
- package/dist/board/cards/code.js +10 -0
- package/dist/board/cards/dictation.d.ts +42 -0
- package/dist/board/cards/dictation.js +105 -0
- package/dist/board/cards/docs.d.ts +47 -0
- package/dist/board/cards/docs.js +152 -0
- package/dist/board/cards/image.d.ts +13 -0
- package/dist/board/cards/image.js +26 -0
- package/dist/board/cards/index.d.ts +62 -0
- package/dist/board/cards/index.js +144 -0
- package/dist/board/cards/kind.d.ts +33 -0
- package/dist/board/cards/kind.js +9 -0
- package/dist/board/cards/lecture.d.ts +26 -0
- package/dist/board/cards/lecture.js +62 -0
- package/dist/board/cards/paint.d.ts +38 -0
- package/dist/board/cards/paint.js +85 -0
- package/dist/board/cards/photo.d.ts +15 -0
- package/dist/board/cards/photo.js +29 -0
- package/dist/board/cards/read.d.ts +8 -0
- package/dist/board/cards/read.js +18 -0
- package/dist/board/cards/text.d.ts +20 -0
- package/dist/board/cards/text.js +59 -0
- package/dist/board/cards/tianzige.d.ts +16 -0
- package/dist/board/cards/tianzige.js +64 -0
- package/dist/board/cards/tutor.d.ts +72 -0
- package/dist/board/cards/tutor.js +134 -0
- package/dist/board/cards/word.d.ts +20 -0
- package/dist/board/cards/word.js +76 -0
- package/dist/board/lecture.d.ts +100 -0
- package/dist/board/lecture.js +204 -0
- package/dist/board/paper.d.ts +20 -0
- package/dist/board/paper.js +76 -0
- package/dist/board/parse.d.ts +120 -0
- package/dist/board/parse.js +330 -0
- package/dist/board/presentation.d.ts +295 -0
- package/dist/board/presentation.js +615 -0
- package/dist/board/sections.d.ts +16 -0
- package/dist/board/sections.js +130 -0
- package/dist/brain/api-tools.d.ts +8 -0
- package/dist/brain/api-tools.js +190 -0
- package/dist/brain/api.d.ts +62 -0
- package/dist/brain/api.js +411 -0
- package/dist/brain/cli/adapters.d.ts +60 -0
- package/dist/brain/cli/adapters.js +91 -0
- package/dist/brain/cli/qwen.d.ts +11 -0
- package/dist/brain/cli/qwen.js +141 -0
- package/dist/brain/cli/run-plan.d.ts +59 -0
- package/dist/brain/cli/run-plan.js +71 -0
- package/dist/brain/cli/run.d.ts +35 -0
- package/dist/brain/cli/run.js +256 -0
- package/dist/brain/cli/stream-json.d.ts +18 -0
- package/dist/brain/cli/stream-json.js +86 -0
- package/dist/brain/cli/types.d.ts +144 -0
- package/dist/brain/cli/types.js +3 -0
- package/dist/brain/cli/warm.d.ts +53 -0
- package/dist/brain/cli/warm.js +103 -0
- package/dist/brain/index.d.ts +145 -0
- package/dist/brain/index.js +74 -0
- package/dist/brain/stream.d.ts +20 -0
- package/dist/brain/stream.js +70 -0
- package/dist/brain/transcript.d.ts +45 -0
- package/dist/brain/transcript.js +132 -0
- package/dist/chalk/actions.d.ts +37 -0
- package/dist/chalk/actions.js +69 -0
- package/dist/chalk/bundle.d.ts +26 -0
- package/dist/chalk/bundle.js +69 -0
- package/dist/chalk/check.d.ts +28 -0
- package/dist/chalk/check.js +291 -0
- package/dist/chalk/circle.d.ts +64 -0
- package/dist/chalk/circle.js +193 -0
- package/dist/chalk/clock.d.ts +84 -0
- package/dist/chalk/clock.js +84 -0
- package/dist/chalk/geometry.d.ts +44 -0
- package/dist/chalk/geometry.js +139 -0
- package/dist/chalk/glyphs/capitals.d.ts +12 -0
- package/dist/chalk/glyphs/capitals.js +264 -0
- package/dist/chalk/glyphs/index.d.ts +21 -0
- package/dist/chalk/glyphs/index.js +18 -0
- package/dist/chalk/glyphs/letters.d.ts +13 -0
- package/dist/chalk/glyphs/letters.js +280 -0
- package/dist/chalk/glyphs/metrics.d.ts +33 -0
- package/dist/chalk/glyphs/metrics.js +28 -0
- package/dist/chalk/glyphs/registry.d.ts +7 -0
- package/dist/chalk/glyphs/registry.js +8 -0
- package/dist/chalk/glyphs/sample.d.ts +32 -0
- package/dist/chalk/glyphs/sample.js +102 -0
- package/dist/chalk/glyphs/skeleton.d.ts +64 -0
- package/dist/chalk/glyphs/skeleton.js +126 -0
- package/dist/chalk/glyphs/types.d.ts +58 -0
- package/dist/chalk/glyphs/types.js +17 -0
- package/dist/chalk/index.d.ts +23 -0
- package/dist/chalk/index.js +15 -0
- package/dist/chalk/manifest.d.ts +41 -0
- package/dist/chalk/manifest.js +6 -0
- package/dist/chalk/paint.d.ts +180 -0
- package/dist/chalk/paint.js +210 -0
- package/dist/chalk/pointer.d.ts +8 -0
- package/dist/chalk/pointer.js +21 -0
- package/dist/chalk/prepare.d.ts +11 -0
- package/dist/chalk/prepare.js +15 -0
- package/dist/chalk/scene-schema.d.ts +199 -0
- package/dist/chalk/scene-schema.js +91 -0
- package/dist/chalk/seed.d.ts +3 -0
- package/dist/chalk/seed.js +10 -0
- package/dist/chalk/text-metrics.d.ts +8 -0
- package/dist/chalk/text-metrics.js +156 -0
- package/dist/chalk/timeline.d.ts +47 -0
- package/dist/chalk/timeline.js +84 -0
- package/dist/chalk/types.d.ts +150 -0
- package/dist/chalk/types.js +8 -0
- package/dist/lib/index.d.ts +14 -8
- package/dist/lib/index.js +14 -8
- package/dist/media/draw.d.ts +23 -0
- package/dist/media/draw.js +44 -0
- package/dist/media/mp3.d.ts +6 -0
- package/dist/media/mp3.js +58 -0
- package/dist/media/mp4.d.ts +1 -0
- package/dist/media/mp4.js +58 -0
- package/dist/media/tts-voices.d.ts +18 -0
- package/dist/media/tts-voices.js +606 -0
- package/dist/media/tts.d.ts +56 -0
- package/dist/media/tts.js +174 -0
- package/dist/media/vision.d.ts +25 -0
- package/dist/media/vision.js +71 -0
- package/dist/memory/commit.d.ts +67 -0
- package/dist/memory/commit.js +130 -0
- package/dist/memory/context.d.ts +86 -0
- package/dist/memory/context.js +97 -0
- package/dist/memory/diary.d.ts +84 -0
- package/dist/memory/diary.js +183 -0
- package/dist/memory/grade.d.ts +2 -0
- package/dist/memory/grade.js +7 -0
- package/dist/{lib → memory}/plan.d.ts +3 -1
- package/dist/{lib → memory}/plan.js +10 -1
- package/dist/memory/proposals.d.ts +11 -0
- package/dist/memory/proposals.js +43 -0
- package/dist/memory/retrieval.d.ts +44 -0
- package/dist/memory/retrieval.js +157 -0
- package/dist/memory/timetable.d.ts +19 -0
- package/dist/memory/timetable.js +95 -0
- package/dist/memory/vault-notes.d.ts +165 -0
- package/dist/memory/vault-notes.js +359 -0
- package/dist/memory/vault.d.ts +70 -0
- package/dist/memory/vault.js +169 -0
- package/dist/realtime/bailian.d.ts +120 -0
- package/dist/realtime/bailian.js +288 -0
- package/dist/realtime/cues.d.ts +51 -0
- package/dist/realtime/cues.js +94 -0
- package/dist/realtime/listen.d.ts +32 -0
- package/dist/realtime/listen.js +53 -0
- package/dist/realtime/preview.d.ts +23 -0
- package/dist/realtime/preview.js +38 -0
- package/dist/realtime/prompt.d.ts +8 -0
- package/dist/realtime/prompt.js +13 -0
- package/dist/realtime/voices.d.ts +19 -0
- package/dist/realtime/voices.js +71 -0
- package/dist/runtime/activities.d.ts +17 -0
- package/dist/runtime/activities.js +46 -0
- package/dist/runtime/activity-state.d.ts +22 -0
- package/dist/runtime/activity-state.js +83 -0
- package/dist/runtime/activity.d.ts +63 -0
- package/dist/runtime/activity.js +428 -0
- package/dist/runtime/agent-file.d.ts +24 -0
- package/dist/runtime/agent-file.js +59 -0
- package/dist/runtime/auto-bookkeep.d.ts +30 -0
- package/dist/runtime/auto-bookkeep.js +84 -0
- package/dist/{lib → runtime/context}/context-pack.d.ts +3 -3
- package/dist/runtime/context/context-pack.js +182 -0
- package/dist/runtime/context/thread-digest.d.ts +4 -0
- package/dist/runtime/context/thread-digest.js +35 -0
- package/dist/runtime/context/tutor-rules.d.ts +25 -0
- package/dist/runtime/context/tutor-rules.js +47 -0
- package/dist/runtime/delivery/delivery.d.ts +94 -0
- package/dist/runtime/delivery/delivery.js +145 -0
- package/dist/runtime/delivery/playback.d.ts +25 -0
- package/dist/runtime/delivery/playback.js +29 -0
- package/dist/runtime/delivery/player.d.ts +145 -0
- package/dist/runtime/delivery/player.js +221 -0
- package/dist/runtime/delivery/queue.d.ts +89 -0
- package/dist/runtime/delivery/queue.js +59 -0
- package/dist/runtime/home/edit.d.ts +39 -0
- package/dist/runtime/home/edit.js +132 -0
- package/dist/runtime/home/home.d.ts +180 -0
- package/dist/runtime/home/home.js +311 -0
- package/dist/runtime/home/store.d.ts +142 -0
- package/dist/runtime/home/store.js +440 -0
- package/dist/runtime/journal/conversation.d.ts +46 -0
- package/dist/runtime/journal/conversation.js +89 -0
- package/dist/runtime/journal/events.d.ts +27 -0
- package/dist/runtime/journal/events.js +136 -0
- package/dist/runtime/journal/kid-log.d.ts +105 -0
- package/dist/runtime/journal/kid-log.js +133 -0
- package/dist/runtime/journal/ledger.d.ts +17 -0
- package/dist/{lib → runtime/journal}/ledger.js +7 -32
- package/dist/runtime/journal/mix.d.ts +33 -0
- package/dist/runtime/journal/mix.js +88 -0
- package/dist/runtime/journal/mouth-index.d.ts +26 -0
- package/dist/runtime/journal/mouth-index.js +34 -0
- package/dist/runtime/journal/mouth.d.ts +54 -0
- package/dist/runtime/journal/mouth.js +102 -0
- package/dist/runtime/journal/recorder.d.ts +46 -0
- package/dist/runtime/journal/recorder.js +58 -0
- package/dist/runtime/journal/store.d.ts +123 -0
- package/dist/runtime/journal/store.js +316 -0
- package/dist/runtime/kid-input.d.ts +16 -0
- package/dist/runtime/kid-input.js +195 -0
- package/dist/runtime/kid-view.d.ts +124 -0
- package/dist/runtime/kid-view.js +157 -0
- package/dist/runtime/lecture.d.ts +86 -0
- package/dist/runtime/lecture.js +237 -0
- package/dist/runtime/live/call.d.ts +219 -0
- package/dist/runtime/live/call.js +1211 -0
- package/dist/runtime/live/turns.d.ts +48 -0
- package/dist/runtime/live/turns.js +102 -0
- package/dist/runtime/runner.d.ts +289 -0
- package/dist/runtime/runner.js +898 -0
- package/dist/runtime/talk-run.d.ts +21 -0
- package/dist/runtime/talk-run.js +34 -0
- package/dist/scenes/dictation/match.d.ts +57 -0
- package/dist/scenes/dictation/match.js +255 -0
- package/dist/scenes/dictation/recognize.d.ts +86 -0
- package/dist/scenes/dictation/recognize.js +163 -0
- package/dist/scenes/dictation/store.d.ts +112 -0
- package/dist/scenes/dictation/store.js +99 -0
- package/dist/scenes/look.d.ts +26 -0
- package/dist/scenes/look.js +1 -0
- package/dist/scenes/paint/painter.d.ts +84 -0
- package/dist/scenes/paint/painter.js +170 -0
- package/dist/scenes/paint/prompt.d.ts +23 -0
- package/dist/scenes/paint/prompt.js +36 -0
- package/dist/scenes/talk/en-words.d.ts +8 -0
- package/dist/scenes/talk/en-words.js +44 -0
- package/dist/scenes/talk/list.d.ts +44 -0
- package/dist/scenes/talk/list.js +88 -0
- package/dist/scenes/talk/store.d.ts +27 -0
- package/dist/scenes/talk/store.js +61 -0
- package/dist/scenes/talk/warmup.d.ts +16 -0
- package/dist/scenes/talk/warmup.js +39 -0
- package/dist/schema/activity.d.ts +149 -0
- package/dist/schema/activity.js +113 -0
- package/dist/schema/board.d.ts +200 -0
- package/dist/schema/board.js +42 -0
- package/dist/schema/config.d.ts +353 -80
- package/dist/schema/config.js +293 -76
- package/dist/schema/context-pack.d.ts +82 -13
- package/dist/schema/context-pack.js +97 -10
- package/dist/schema/conversation-files.d.ts +43 -0
- package/dist/schema/conversation-files.js +26 -0
- package/dist/schema/conversation.d.ts +459 -13
- package/dist/schema/conversation.js +148 -8
- package/dist/schema/events.d.ts +138 -0
- package/dist/schema/events.js +1 -0
- package/dist/schema/home.d.ts +46 -0
- package/dist/schema/home.js +30 -0
- package/dist/schema/index.d.ts +19 -4
- package/dist/schema/index.js +19 -4
- package/dist/schema/json-schema.d.ts +2 -0
- package/dist/schema/json-schema.js +23 -0
- package/dist/schema/kid-device.d.ts +20 -0
- package/dist/schema/kid-device.js +32 -0
- package/dist/schema/ledger.d.ts +9 -64
- package/dist/schema/ledger.js +7 -31
- package/dist/schema/memory.d.ts +73 -0
- package/dist/schema/memory.js +47 -0
- package/dist/schema/plan.d.ts +2 -2
- package/dist/schema/plan.js +2 -2
- package/dist/schema/sections.d.ts +32 -25
- package/dist/schema/sections.js +21 -22
- package/dist/schema/theme.d.ts +25 -0
- package/dist/schema/theme.js +26 -0
- package/dist/schema/timetable.d.ts +26 -0
- package/dist/schema/timetable.js +18 -0
- package/dist/schema/workspace.d.ts +41 -0
- package/dist/schema/workspace.js +9 -0
- package/dist/shared/errors.d.ts +8 -0
- package/dist/shared/errors.js +13 -0
- package/dist/shared/home.d.ts +2 -0
- package/dist/shared/home.js +16 -0
- package/dist/shared/human-notes.d.ts +5 -0
- package/dist/shared/human-notes.js +7 -0
- package/dist/shared/qianwen.d.ts +50 -0
- package/dist/shared/qianwen.js +109 -0
- package/dist/shared/time.d.ts +9 -0
- package/dist/shared/time.js +22 -0
- package/dist/shared/tokens.d.ts +18 -0
- package/dist/shared/tokens.js +11 -0
- package/dist/shared/wav.d.ts +10 -0
- package/dist/shared/wav.js +50 -0
- package/dist/stage/Assistant-Bold-ZDZZ6JHA.woff2 +0 -0
- package/dist/stage/Assistant-Medium-DZ25RZU3.woff2 +0 -0
- package/dist/stage/Assistant-Regular-PLF2XOGW.woff2 +0 -0
- package/dist/stage/Assistant-SemiBold-CZ5MX6FK.woff2 +0 -0
- package/dist/stage/chunk-226TNLQA.js +129 -0
- package/dist/stage/chunk-24LGQEKE.js +1 -0
- package/dist/stage/chunk-25XV6UY3.js +10 -0
- package/dist/stage/chunk-2AZI3NHJ.js +1 -0
- package/dist/stage/chunk-2BQZTTBE.js +1 -0
- package/dist/stage/chunk-2M7CGY3Y.js +11 -0
- package/dist/stage/chunk-2VR4WFQ7.js +10 -0
- package/dist/stage/chunk-2ZYXBUWG.js +9 -0
- package/dist/stage/chunk-32LAWMGY.js +1 -0
- package/dist/stage/chunk-3LEMTSOO.js +11 -0
- package/dist/stage/chunk-3QCKGZYE.js +32 -0
- package/dist/stage/chunk-44P66WFA.js +1 -0
- package/dist/stage/chunk-4AM6FRR6.js +7 -0
- package/dist/stage/chunk-4QDE4SUK.js +1 -0
- package/dist/stage/chunk-4QESHV3U.js +1 -0
- package/dist/stage/chunk-4SG5QPHB.js +2 -0
- package/dist/stage/chunk-4T4UVCOC.js +1 -0
- package/dist/stage/chunk-4WUEPPG3.js +34 -0
- package/dist/stage/chunk-4YCN6UZ5.js +99 -0
- package/dist/stage/chunk-5V7ZY2AP.js +1 -0
- package/dist/stage/chunk-5VS5Y2VJ.js +6 -0
- package/dist/stage/chunk-5W4NO2ZD.js +62 -0
- package/dist/stage/chunk-65CRNICM.js +1 -0
- package/dist/stage/chunk-66MULO7F.js +5 -0
- package/dist/stage/chunk-6COFQTBS.js +162 -0
- package/dist/stage/chunk-6VZG3M3I.js +8 -0
- package/dist/stage/chunk-7HVDH43T.js +1 -0
- package/dist/stage/chunk-7JLJN4EZ.js +8 -0
- package/dist/stage/chunk-7QDSP4LN.js +1 -0
- package/dist/stage/chunk-7R6OQAO3.js +1 -0
- package/dist/stage/chunk-7VTTHZKW.js +7 -0
- package/dist/stage/chunk-ATHR5KEN.js +10 -0
- package/dist/stage/chunk-B6R5LTP2.js +36 -0
- package/dist/stage/chunk-BD4WE264.js +1 -0
- package/dist/stage/chunk-BONANU47.js +206 -0
- package/dist/stage/chunk-BTVW7OFK.js +89 -0
- package/dist/stage/chunk-BUTJIDHG.js +8 -0
- package/dist/stage/chunk-C2IPYMML.js +84 -0
- package/dist/stage/chunk-C54FTPPV.js +9 -0
- package/dist/stage/chunk-C6LOUF2Z.js +90 -0
- package/dist/stage/chunk-CFF3PILO.js +339 -0
- package/dist/stage/chunk-CI2FQ2JY.js +9 -0
- package/dist/stage/chunk-CW5O4RV7.js +12 -0
- package/dist/stage/chunk-CYABNQYD.js +1 -0
- package/dist/stage/chunk-D7FFA4NS.js +15 -0
- package/dist/stage/chunk-DUNNXU6Z.js +8 -0
- package/dist/stage/chunk-E3E4Q6JF.js +6 -0
- package/dist/stage/chunk-EB3U3LH7.js +9 -0
- package/dist/stage/chunk-EGSZ736B.js +8 -0
- package/dist/stage/chunk-EOBXW5VQ.js +2 -0
- package/dist/stage/chunk-ERZC3B7Y.js +11 -0
- package/dist/stage/chunk-ESDOOBIH.js +11 -0
- package/dist/stage/chunk-EVU4QAL4.js +178 -0
- package/dist/stage/chunk-EVVZI2NY.js +39 -0
- package/dist/stage/chunk-FBKAZUKD.js +39 -0
- package/dist/stage/chunk-FDVKYQJN.js +1 -0
- package/dist/stage/chunk-FGPVYHVR.js +5 -0
- package/dist/stage/chunk-FHUD4AZC.js +1 -0
- package/dist/stage/chunk-FKUJ2DMY.js +96 -0
- package/dist/stage/chunk-FNZGVIJV.js +1 -0
- package/dist/stage/chunk-GBYSEGYM.js +78 -0
- package/dist/stage/chunk-GCO4TDHX.js +1 -0
- package/dist/stage/chunk-GKTML2LG.js +10 -0
- package/dist/stage/chunk-GMDIHHXQ.js +11 -0
- package/dist/stage/chunk-GOUNACI3.js +10 -0
- package/dist/stage/chunk-GXAYATPF.js +3 -0
- package/dist/stage/chunk-H2K2DDXV.js +8 -0
- package/dist/stage/chunk-H3UJI6ZY.js +13 -0
- package/dist/stage/chunk-H6PUGCME.js +7 -0
- package/dist/stage/chunk-HNBUIMIG.js +1 -0
- package/dist/stage/chunk-HRZY2BJQ.js +10 -0
- package/dist/stage/chunk-HSPJGALW.js +30 -0
- package/dist/stage/chunk-IBYORHIP.js +25 -0
- package/dist/stage/chunk-IHF4EWTY.js +1 -0
- package/dist/stage/chunk-ISBRPNKI.js +1 -0
- package/dist/stage/chunk-IZ3OI3C2.js +2 -0
- package/dist/stage/chunk-JEFZAC2R.js +1 -0
- package/dist/stage/chunk-JFIGRDPO.js +1 -0
- package/dist/stage/chunk-JFVYBOOC.js +9 -0
- package/dist/stage/chunk-JGD4TUTQ.js +1 -0
- package/dist/stage/chunk-JO4BEHDH.js +4 -0
- package/dist/stage/chunk-JOWBS42C.js +1 -0
- package/dist/stage/chunk-JP3TM2TG.js +1 -0
- package/dist/stage/chunk-JVK2C52O.js +1 -0
- package/dist/stage/chunk-K4I76XBX.js +14 -0
- package/dist/stage/chunk-KA6CZZ4M.js +1 -0
- package/dist/stage/chunk-KESSJ36M.js +1 -0
- package/dist/stage/chunk-LKATEVRU.js +1 -0
- package/dist/stage/chunk-LS4EJ2EI.js +1 -0
- package/dist/stage/chunk-LUKJQC2X.js +70 -0
- package/dist/stage/chunk-LWULR3SM.js +1 -0
- package/dist/stage/chunk-LWVRFMKX.js +2 -0
- package/dist/stage/chunk-LXC4ZBPZ.js +1 -0
- package/dist/stage/chunk-M2UP255H.js +1 -0
- package/dist/stage/chunk-MAPPXUZQ.js +231 -0
- package/dist/stage/chunk-MBMPW7Z5.js +1 -0
- package/dist/stage/chunk-MD3HMMTK.js +1 -0
- package/dist/stage/chunk-MLPCUIKR.js +17 -0
- package/dist/stage/chunk-MNQIRPDQ.js +8 -0
- package/dist/stage/chunk-MRG7MJVS.js +1 -0
- package/dist/stage/chunk-MU6EXW72.js +10 -0
- package/dist/stage/chunk-MVU2X6LU.js +4 -0
- package/dist/stage/chunk-N6UA5CZV.js +36 -0
- package/dist/stage/chunk-NLZIT2TJ.js +7 -0
- package/dist/stage/chunk-NOJR3J7M.js +1 -0
- package/dist/stage/chunk-NT7ARDOL.js +1 -0
- package/dist/stage/chunk-NWOGMPZH.js +1 -0
- package/dist/stage/chunk-NXE4KMG3.js +40 -0
- package/dist/stage/chunk-O4YCZXD2.js +257 -0
- package/dist/stage/chunk-OAJZ7HLK.js +1 -0
- package/dist/stage/chunk-OCFCFASO.js +1 -0
- package/dist/stage/chunk-ODGPG5RY.js +7 -0
- package/dist/stage/chunk-ODUEA52Q.js +3 -0
- package/dist/stage/chunk-OGDJ3FCI.js +1 -0
- package/dist/stage/chunk-OLDJ55TT.js +1 -0
- package/dist/stage/chunk-OOCL4ATI.js +2 -0
- package/dist/stage/chunk-OQEFDUDV.js +1 -0
- package/dist/stage/chunk-OQWVEWG7.js +4 -0
- package/dist/stage/chunk-OUIX6BGK.js +1 -0
- package/dist/stage/chunk-OV7OZYUO.js +7 -0
- package/dist/stage/chunk-PFGDY33O.js +1 -0
- package/dist/stage/chunk-PFJDUJB3.js +139 -0
- package/dist/stage/chunk-PGEW76ER.js +1 -0
- package/dist/stage/chunk-PP76TAQX.js +32 -0
- package/dist/stage/chunk-PTBOP4OT.js +2 -0
- package/dist/stage/chunk-PX2RVHCK.js +1 -0
- package/dist/stage/chunk-Q7K3PNAD.js +1 -0
- package/dist/stage/chunk-QDDPLZDB.js +1 -0
- package/dist/stage/chunk-QLGLZ77F.js +1 -0
- package/dist/stage/chunk-QOPFDFHI.js +1 -0
- package/dist/stage/chunk-QXT4KAEJ.js +1 -0
- package/dist/stage/chunk-R4YVZCIT.js +1 -0
- package/dist/stage/chunk-R6P6EHWA.js +62 -0
- package/dist/stage/chunk-RCH3OXBI.js +9 -0
- package/dist/stage/chunk-RHPE44I2.js +1 -0
- package/dist/stage/chunk-RJ2R4FVC.js +1 -0
- package/dist/stage/chunk-RJH2YVTD.js +1 -0
- package/dist/stage/chunk-RLWWW5OB.js +6 -0
- package/dist/stage/chunk-S3WO35Z7.js +120 -0
- package/dist/stage/chunk-S7ZGX6MW.js +1 -0
- package/dist/stage/chunk-SIAWX27B.js +1 -0
- package/dist/stage/chunk-SLG4X622.js +1 -0
- package/dist/stage/chunk-SLJRBC77.js +168 -0
- package/dist/stage/chunk-SRORWQJZ.js +1 -0
- package/dist/stage/chunk-STZTUIIF.js +2 -0
- package/dist/stage/chunk-SVQ6MWNV.js +9 -0
- package/dist/stage/chunk-TL3HLL2S.js +132 -0
- package/dist/stage/chunk-U3DOE6SF.js +24 -0
- package/dist/stage/chunk-UABQIZKW.js +6 -0
- package/dist/stage/chunk-UDK3R764.js +1 -0
- package/dist/stage/chunk-UFF4E54B.js +41 -0
- package/dist/stage/chunk-UFP5Q4AY.js +1 -0
- package/dist/stage/chunk-UMH2TSW2.js +72 -0
- package/dist/stage/chunk-UTASVNF5.js +10 -0
- package/dist/stage/chunk-UW7ML5ML.js +88 -0
- package/dist/stage/chunk-V3J7F6OP.js +1 -0
- package/dist/stage/chunk-VEHP5B5Q.js +5 -0
- package/dist/stage/chunk-VRXWB733.js +9 -0
- package/dist/stage/chunk-VVDHBBS5.js +5 -0
- package/dist/stage/chunk-VYFOH6FU.js +106 -0
- package/dist/stage/chunk-W3VZTLQS.js +3 -0
- package/dist/stage/chunk-W7WNRWTZ.js +8 -0
- package/dist/stage/chunk-WHUCV53C.js +1 -0
- package/dist/stage/chunk-WULMYHIF.js +1 -0
- package/dist/stage/chunk-X54EX7AW.js +9 -0
- package/dist/stage/chunk-X565ISZM.js +1 -0
- package/dist/stage/chunk-XGZMDECB.js +1 -0
- package/dist/stage/chunk-XHONC4RF.js +172 -0
- package/dist/stage/chunk-XIK4XQ6E.js +24 -0
- package/dist/stage/chunk-XJQQWFTE.js +1 -0
- package/dist/stage/chunk-XM4WOU3I.js +13 -0
- package/dist/stage/chunk-XM7Y5HP5.js +38 -0
- package/dist/stage/chunk-XNYYRGD5.js +292 -0
- package/dist/stage/chunk-XOYVR3SV.js +1 -0
- package/dist/stage/chunk-XQIW5HCE.js +1 -0
- package/dist/stage/chunk-YEWL7FUW.js +8 -0
- package/dist/stage/chunk-YI55PY3V.js +1 -0
- package/dist/stage/chunk-YM63HI6W.js +1 -0
- package/dist/stage/chunk-YNLGLU4P.js +9 -0
- package/dist/stage/chunk-YTNMNOWP.js +1 -0
- package/dist/stage/chunk-YV62XKTC.js +1 -0
- package/dist/stage/chunk-YVBA3QW6.js +1 -0
- package/dist/stage/chunk-Z2SY2HOC.js +1 -0
- package/dist/stage/chunk-ZBTCQEBK.js +10 -0
- package/dist/stage/chunk-ZCUFN74D.js +1 -0
- package/dist/stage/chunk-ZHOULXEJ.js +11 -0
- package/dist/stage/chunk-ZTRZIET2.js +1 -0
- package/dist/stage/index.html +11 -0
- package/dist/stage/stage.css +1 -0
- package/dist/stage/stage.js +1 -0
- package/dist/ui/dev/dev-page.d.ts +1 -0
- package/dist/ui/dev/dev-page.js +2248 -0
- package/dist/ui/dev/dev-theme.d.ts +6 -0
- package/dist/ui/dev/dev-theme.js +67 -0
- package/dist/ui/dev/qr-page.d.ts +8 -0
- package/dist/ui/dev/qr-page.js +61 -0
- package/dist/ui/dev/voice-gallery-page.d.ts +1 -0
- package/dist/ui/dev/voice-gallery-page.js +121 -0
- package/dist/ui/dev/voice-scenes.d.ts +21 -0
- package/dist/ui/dev/voice-scenes.js +30 -0
- package/dist/ui/dictation/en-box.d.ts +2 -0
- package/dist/ui/dictation/en-box.js +86 -0
- package/dist/ui/dictation/en-page.d.ts +1 -0
- package/dist/ui/dictation/en-page.js +484 -0
- package/dist/ui/dictation/page.d.ts +1 -0
- package/dist/ui/dictation/page.js +1084 -0
- package/dist/ui/dictation/parent-page.d.ts +1 -0
- package/dist/ui/dictation/parent-page.js +234 -0
- package/dist/ui/dictation/reel-mode.d.ts +1 -0
- package/dist/ui/dictation/reel-mode.js +87 -0
- package/dist/ui/dictation/reel-page.d.ts +1 -0
- package/dist/ui/dictation/reel-page.js +62 -0
- package/dist/ui/dictation/reel.d.ts +163 -0
- package/dist/ui/dictation/reel.js +436 -0
- package/dist/ui/dictation/ui.d.ts +44 -0
- package/dist/ui/dictation/ui.js +80 -0
- package/dist/ui/dictation/unlock.d.ts +7 -0
- package/dist/ui/dictation/unlock.js +16 -0
- package/dist/ui/kid/audio-client.d.ts +11 -0
- package/dist/ui/kid/audio-client.js +74 -0
- package/dist/ui/kid/board-mark-art.d.ts +37 -0
- package/dist/ui/kid/board-mark-art.js +5 -0
- package/dist/ui/kid/board-mark.d.ts +24 -0
- package/dist/ui/kid/board-mark.js +58 -0
- package/dist/ui/kid/kid-log.d.ts +15 -0
- package/dist/ui/kid/kid-log.js +87 -0
- package/dist/ui/kid/kid-nav.d.ts +11 -0
- package/dist/ui/kid/kid-nav.js +22 -0
- package/dist/ui/kid/kid-page.d.ts +9 -0
- package/dist/ui/kid/kid-page.js +3719 -0
- package/dist/ui/kid/letters.d.ts +26 -0
- package/dist/ui/kid/letters.js +51 -0
- package/dist/ui/kid/photo-edit.d.ts +83 -0
- package/dist/ui/kid/photo-edit.js +106 -0
- package/dist/ui/kid/photo-ui.d.ts +23 -0
- package/dist/ui/kid/photo-ui.js +60 -0
- package/dist/ui/kid/reel.d.ts +419 -0
- package/dist/ui/kid/reel.js +648 -0
- package/dist/ui/kid/tianzige.d.ts +8 -0
- package/dist/ui/kid/tianzige.js +45 -0
- package/dist/ui/kid/tutor-face.d.ts +35 -0
- package/dist/ui/kid/tutor-face.js +84 -0
- package/dist/ui/kid/voice-ui.d.ts +70 -0
- package/dist/ui/kid/voice-ui.js +316 -0
- package/dist/ui/kid/voice-view.d.ts +71 -0
- package/dist/ui/kid/voice-view.js +75 -0
- package/dist/ui/kid/word-draw.d.ts +2 -0
- package/dist/ui/kid/word-draw.js +107 -0
- package/dist/ui/kid/word-emoji.d.ts +10 -0
- package/dist/ui/kid/word-emoji.js +66 -0
- package/dist/ui/kid/word-layout.d.ts +25 -0
- package/dist/ui/kid/word-layout.js +29 -0
- package/dist/ui/parent/parent-home-page.d.ts +1 -0
- package/dist/ui/parent/parent-home-page.js +414 -0
- package/dist/ui/parent/parent-page.d.ts +2 -0
- package/dist/ui/parent/parent-page.js +507 -0
- package/dist/ui/shared/brand.d.ts +36 -0
- package/dist/ui/shared/brand.js +51 -0
- package/dist/ui/shared/favicon.d.ts +12 -0
- package/dist/ui/shared/favicon.js +96 -0
- package/dist/ui/shared/icon.d.ts +9 -0
- package/dist/ui/shared/icon.js +35 -0
- package/dist/ui/shared/inline-lib.d.ts +1 -0
- package/dist/ui/shared/inline-lib.js +28 -0
- package/dist/ui/shared/logo.d.ts +31 -0
- package/dist/ui/shared/logo.js +43 -0
- package/dist/ui/shared/page-dom.d.ts +12 -0
- package/dist/ui/shared/page-dom.js +36 -0
- package/dist/ui/shared/png.d.ts +1 -0
- package/dist/ui/shared/png.js +45 -0
- package/dist/ui/shared/qr-svg.d.ts +3 -0
- package/dist/ui/shared/qr-svg.js +32 -0
- package/dist/ui/shared/qr.d.ts +14 -0
- package/dist/ui/shared/qr.js +281 -0
- package/dist/ui/shared/reel-dock.d.ts +22 -0
- package/dist/ui/shared/reel-dock.js +365 -0
- package/dist/ui/shared/tutor-color.d.ts +15 -0
- package/dist/ui/shared/tutor-color.js +47 -0
- package/dist/ui/talk/page.d.ts +6 -0
- package/dist/ui/talk/page.js +294 -0
- package/grades/grade-2/README.md +11 -0
- package/grades/grade-2/VALIDATION.md +41 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273.md +157 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273./344/276/213/345/255/220.md +78 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/200/237/344/274/236.jpg +0 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/260/217/347/214/264/346/221/230/346/241/203.jpg +0 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235.md +64 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235./344/276/213/345/255/220.md +31 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/220/254/345/206/231.md +48 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +79 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/chinese-tutor/activities//346/211/271/346/224/271.md +81 -0
- package/grades/grade-2/agents/chinese-tutor/activities//346/211/271/346/224/271./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235.md +94 -0
- package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235./344/276/213/345/255/220.md +63 -0
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +82 -0
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor.md +59 -0
- package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276.md +149 -0
- package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276./344/276/213/345/255/220.md +13 -0
- package/grades/grade-2/agents/english-tutor/activities//345/220/254/345/206/231.md +47 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/english-tutor/activities//346/211/271/346/224/271.md +80 -0
- package/grades/grade-2/agents/english-tutor/activities//346/211/271/346/224/271./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +86 -0
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor.md +57 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202.md +56 -0
- package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202./344/276/213/345/255/220.md +52 -0
- package/grades/grade-2/agents/math-tutor/activities//346/211/271/346/224/271.md +80 -0
- package/grades/grade-2/agents/math-tutor/activities//346/211/271/346/224/271./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +81 -0
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor.md +51 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/index.json +44 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-1.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-2.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-3.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-4.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-5.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/bake.json +1 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/manifest.json +63 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/scene.json +247 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.md +40 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.ts +26 -0
- package/grades/grade-2/examples//345/271/263/345/235/207/345/210/206./344/276/213/345/255/220.md +24 -0
- package/grades/grade-2/examples//346/225/260/345/255/246/345/271/263/345/235/207/345/210/206.md +42 -0
- package/grades/grade-2/validation/2026-10-27.json +4410 -0
- package/package.json +32 -12
- package/skills/chalk-cli/SKILL.md +62 -0
- package/skills/chalk-scene/SKILL.md +91 -0
- package/skills/chalk-teaching/SKILL.md +82 -0
- package/skills/chalk-teaching/models//344/272/244/351/224/231/346/225/260/345/210/227/345/210/206/350/241/214.md +8 -0
- package/skills/chalk-teaching/models//345/207/221/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//345/221/250/346/234/237/350/247/204/345/276/213.md +6 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/345/242/236/345/207/217.md +6 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/244/247/346/225/260/346/261/202/345/260/217/346/225/260.md +7 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/267/256.md +6 -0
- package/skills/chalk-teaching/models//345/260/217/346/243/222/350/277/233/344/275/215/345/212/240.md +7 -0
- package/skills/chalk-teaching/models//345/260/217/346/243/222/351/200/200/344/275/215/345/207/217.md +6 -0
- package/skills/chalk-teaching/models//346/216/222/351/230/237/347/202/271/351/230/265.md +7 -0
- package/skills/chalk-teaching/models//346/227/266/351/227/264/346/225/260/350/275/264.md +6 -0
- package/skills/chalk-teaching/models//346/245/274/345/261/202/351/227/264/351/232/224/345/233/276.md +7 -0
- package/skills/chalk-teaching/models//347/240/264/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//347/253/226/345/274/217/351/200/200/344/275/215.md +6 -0
- package/skills/chalk-teaching/models//347/272/277/346/256/265/345/233/276.md +7 -0
- package/skills/chalk-teaching/models-index.md +24 -0
- package/skills/chalk-teaching/references//350/256/262/350/247/243/346/250/241/346/235/277/344/270/211/346/241/243.md +32 -0
- package/skills/chalk-teaching/references//350/257/264/346/230/216/346/226/207/344/273/266.md +102 -0
- package/skills/chalk-verify/SKILL.md +57 -0
- package/skills/chalk-verify/references//350/257/204/345/210/244/346/240/207/345/207/206.md +69 -0
- package/skills/cotutor-analyze/SKILL.md +106 -0
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +31 -0
- package/skills/cotutor-board/SKILL.md +373 -0
- package/skills/cotutor-board/references/README.md +38 -0
- package/skills/cotutor-board/references/canvas.md +68 -0
- package/skills/cotutor-board/references/choice.md +55 -0
- package/skills/cotutor-board/references/code.md +46 -0
- package/skills/cotutor-board/references/image.md +52 -0
- package/skills/cotutor-board/references/lecture.md +69 -0
- package/skills/cotutor-board/references/paint.md +77 -0
- package/skills/cotutor-board/references/photo.md +61 -0
- package/skills/cotutor-board/references/read.md +51 -0
- package/skills/cotutor-board/references/text.md +69 -0
- package/skills/cotutor-board/references/tianzige.md +78 -0
- package/skills/cotutor-board/references/word.md +91 -0
- package/skills/cotutor-home/SKILL.md +169 -0
- package/skills/cotutor-home/references/dictation.md +97 -0
- package/skills/cotutor-home/references/tutor.md +148 -0
- package/skills/cotutor-home/references//345/221/275/344/273/244.md +21 -0
- package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +142 -0
- package/skills/cotutor-tune/SKILL.md +147 -0
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +50 -0
- package/skills/cotutor-tutor/SKILL.md +261 -0
- package/skills/cotutor-tutor/references//344/270/212/344/270/213/346/226/207/345/214/205.md +78 -0
- package/themes/default/kid.css +86 -0
- package/themes/default/theme.json +26 -0
- package/agents/chinese-teacher.md +0 -19
- package/agents/homework-aide.md +0 -17
- package/agents/math-teacher.md +0 -19
- package/agents/planner.md +0 -18
- package/agents/reading-teacher.md +0 -19
- package/dist/cli/doctor.d.ts +0 -19
- package/dist/cli/doctor.js +0 -190
- package/dist/cli/init.js +0 -114
- package/dist/cli/main.js +0 -123
- package/dist/cli/serve.d.ts +0 -19
- package/dist/cli/serve.js +0 -22
- package/dist/cli/skeleton.d.ts +0 -21
- package/dist/cli/skeleton.js +0 -82
- package/dist/cli/workspace.d.ts +0 -61
- package/dist/lib/agent-file.d.ts +0 -10
- package/dist/lib/agent-file.js +0 -16
- package/dist/lib/context-pack.js +0 -46
- package/dist/lib/conversation.d.ts +0 -28
- package/dist/lib/conversation.js +0 -44
- package/dist/lib/kid-view.d.ts +0 -26
- package/dist/lib/kid-view.js +0 -38
- package/dist/lib/ledger.d.ts +0 -21
- package/dist/lib/sections.d.ts +0 -12
- package/dist/lib/sections.js +0 -151
- package/dist/lib/transcript.d.ts +0 -36
- package/dist/lib/transcript.js +0 -95
- package/dist/server/app.d.ts +0 -13
- package/dist/server/app.js +0 -45
- /package/dist/{cli → app/cli}/main.d.ts +0 -0
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发消息 = 拼上下文包 → 按运行时 spawn / resume → stream-json 落 conversations/<老师>/<日期>.<job>.log → 索引更新
|
|
3
|
+
* (最终文本 → 板书节 section + 家长尾巴 parentText + 解析提醒 warnings)。
|
|
4
|
+
* 流式:stdout 经本进程落盘,同时喂 PartialReader 拼当前回复正文 → parseBoard(partial) → 内存里的 partial section
|
|
5
|
+
* (孩子端 pending 条目带着它,卡随围栏闭合逐张出现);讲稿不配音,交给实时的嘴照念。
|
|
6
|
+
* 点读段、单词等卡的资产在索引写好之后后台配,孩子端点到还没好的段等它到,等不到就不出声。
|
|
7
|
+
* 一老师同时只跑一条(老师还在回上一条就 409),跨天自动新开(索引按本地日期分文件,新文件没 session 就不带 --resume)。
|
|
8
|
+
* 话题(2026-09-11):一天可多个,每个话题自己的会话(index.sessions[thread]);newThread / 系统消息 / 今天第一条开新话题(不 resume、不带旧卡),
|
|
9
|
+
* 指定 thread 接着今天的旧话题(resume 它的会话),缺省接当前话题。
|
|
10
|
+
* 埋点(2026-09-11):每轮记 timing(进程起的时刻、首卡、进程退出、配音收尾,毫秒),家长视图每轮一行。
|
|
11
|
+
* 进程 cwd 是老师目录 agents/<name>/。
|
|
12
|
+
*/
|
|
13
|
+
import { lecturePack, readLecture, storedMarks } from "./lecture.js";
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
17
|
+
import { buildContextPack } from "./context/context-pack.js";
|
|
18
|
+
import { addMessage, applyRun, lastJobOf, changedCards, jobId, sameRuntime, sessionFor } from "./journal/conversation.js";
|
|
19
|
+
import { localDate, localMinute } from "../shared/time.js";
|
|
20
|
+
import { cardId, conversationFiles, threads } from "../schema/index.js";
|
|
21
|
+
import { bookkeepingPrompt, kidQuestions, textbookHeadings } from "../memory/diary.js";
|
|
22
|
+
import { cardAssets, cardLabel, describeCard, tutorCardKinds } from "../board/cards/index.js";
|
|
23
|
+
import { boardGuideFor, cardGuidesFor } from "../board/cards/docs.js";
|
|
24
|
+
import { activityCards, parseActivity, renderTutorSection } from "./activity.js";
|
|
25
|
+
import { activityFile } from "./activities.js";
|
|
26
|
+
import { activityState, paintGate, paintTool, readActivity, threadActivity } from "./activity-state.js";
|
|
27
|
+
import { controlsTools, createBrain } from "../brain/index.js";
|
|
28
|
+
import { endsWithToolCall, lineFlags, parseBoard } from "../board/parse.js";
|
|
29
|
+
import { readyBeats, beatsOf } from "../board/presentation.js";
|
|
30
|
+
import { deriveKidView, truncateReply } from "./kid-view.js";
|
|
31
|
+
import { parseSections } from "../board/sections.js";
|
|
32
|
+
import { threadDigest } from "./context/thread-digest.js";
|
|
33
|
+
import { getRuntime, runtimeUses, boardPreloaded } from "../brain/cli/run-plan.js";
|
|
34
|
+
import { beatTimings } from "./journal/events.js";
|
|
35
|
+
import {} from "../schema/index.js";
|
|
36
|
+
import { BOARD_CARD_KINDS, MEMORY_MAX_PER_TURN, MEMORY_TIDY_CAP, VAULT_PACK_ROLES, isApiRuntime, resolvePolicy } from "../schema/index.js";
|
|
37
|
+
import { UsageError } from "../shared/errors.js";
|
|
38
|
+
import { readAgentBody, readCardStates, readIndex, snapshotSources, writeIndex, writeRunFile } from "./journal/store.js";
|
|
39
|
+
import { readTextbooks, scanVault, writeVaultMemory } from "../memory/vault.js";
|
|
40
|
+
import { learningContext } from "../memory/context.js";
|
|
41
|
+
import { childChanges, planDiary, planMemory, recoverPending, writeDiaryBlock } from "../memory/commit.js";
|
|
42
|
+
import { kidSaid } from "../memory/proposals.js";
|
|
43
|
+
import { memoryCount, textHash, tidyMemoryPrompt } from "../memory/vault-notes.js";
|
|
44
|
+
import { TUTOR_RULES_PATH, takesTutorRules, tutorRulesBody, BOARD_GUIDE_IN_SYSTEM, BOARD_GUIDE_PATH, boardGuideBody, boardGuideReads } from "./context/tutor-rules.js";
|
|
45
|
+
import { DubQueue } from "../media/tts.js";
|
|
46
|
+
import { continueContext } from "./home/store.js";
|
|
47
|
+
import { kidInput } from "./kid-input.js";
|
|
48
|
+
import { dashscopeAuth } from "../realtime/bailian.js";
|
|
49
|
+
import { qianwenKeys } from "../shared/qianwen.js";
|
|
50
|
+
import { DRAW_ONLY_TEXT, drawingPackLine } from "../board/paper.js";
|
|
51
|
+
import { makeDraw } from "../media/draw.js";
|
|
52
|
+
import { paintLines, startPaints } from "../scenes/paint/painter.js";
|
|
53
|
+
export class BusyError extends Error {
|
|
54
|
+
constructor(tutor, job) {
|
|
55
|
+
super(`${tutor} 还在回上一条(${job}),等它说完再发`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** API 运行时的长话题:上一轮结束时上下文(tokens.context)过了这么多,这一轮整段压缩(2026-10-29 定 32k) */
|
|
59
|
+
export const COMPACT_TOKENS = 32_000;
|
|
60
|
+
/**
|
|
61
|
+
* 这一轮该不该整段压缩:这个话题最近一轮报的上下文(只有 API 运行时报)过了 COMPACT_TOKENS,而且上一轮不是刚压缩过的
|
|
62
|
+
* (压缩完还过线,说明系统提示与笔记本身就大,再压也小不下去,隔一轮再看)
|
|
63
|
+
*/
|
|
64
|
+
function dueCompact(messages, ths, thread) {
|
|
65
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
66
|
+
if (ths[i] !== thread)
|
|
67
|
+
continue;
|
|
68
|
+
if (messages[i].compacted)
|
|
69
|
+
return false;
|
|
70
|
+
if (messages[i].tokens?.context !== undefined)
|
|
71
|
+
return messages[i].tokens.context >= COMPACT_TOKENS;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 上下文包的取材(《obsidian仓库设计.md》§7 每轮那行,2026-09-17 改):课程表命中的时段 + 老师守则(有脸的老师)+ 当前学期 + 档案与入口文件的原文(按 entryChars 截)
|
|
77
|
+
* + 参考路径 + 本周计划里本老师的行 + 最近 14 天日记里本学科的「- 观察:」行。
|
|
78
|
+
* 守则与笔记原文总是带上;同一话题里没改过的由 send 换成「未变」。
|
|
79
|
+
*/
|
|
80
|
+
export async function gatherContext(ws, tutor, input, report) {
|
|
81
|
+
const t = ws.config.tutors[tutor];
|
|
82
|
+
const policy = resolvePolicy(ws.config, tutor);
|
|
83
|
+
const pack = { at: localMinute(input.at), focus: input.focus, plan: [], recent: [] };
|
|
84
|
+
// vault 那几段(src/memory/context.ts):activity 话题的记忆是它自己的(agent: <老师>/<activity>);档案、入口文件、日记观察照旧共用
|
|
85
|
+
const mem = await learningContext(ws, { at: input.at, agent: input.agent ?? tutor, subject: t?.subject, display: t?.display, teacher: Boolean(t) && tutor.endsWith('-tutor'), ...(input.query ? { query: input.query } : {}), limits: { entryChars: policy.contextPack.entryChars, planLines: policy.contextPack.planLines, recent: policy.contextPack.recent } }, report);
|
|
86
|
+
if (mem.slot)
|
|
87
|
+
pack.slot = mem.slot;
|
|
88
|
+
if (mem.semester)
|
|
89
|
+
pack.semester = mem.semester;
|
|
90
|
+
pack.notes = [];
|
|
91
|
+
// 有脸的老师的公共守则:机器技能 cotutor-tutor 的原文,排在家长笔记前面(不截;同一话题没改过由 send 换成「未变」)
|
|
92
|
+
if (takesTutorRules(tutor)) {
|
|
93
|
+
try {
|
|
94
|
+
pack.notes.push({ role: 'rules', path: TUTOR_RULES_PATH, text: tutorRulesBody(await readFile(join(ws.root, TUTOR_RULES_PATH), 'utf8'), input.kinds ?? tutorCardKinds(ws.config.tutors[tutor]?.cards) ?? undefined) });
|
|
95
|
+
pack.rules = TUTOR_RULES_PATH;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
pack.rules = `缺:${TUTOR_RULES_PATH} 不在(cotutor upgrade 补),照你老师文件里的做`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
pack.notes.push(...mem.notes);
|
|
102
|
+
pack.profile = mem.profile;
|
|
103
|
+
if (mem.child !== undefined)
|
|
104
|
+
pack.child = mem.child;
|
|
105
|
+
if (mem.entry !== undefined)
|
|
106
|
+
pack.entry = mem.entry;
|
|
107
|
+
pack.memory = mem.memory;
|
|
108
|
+
if (mem.lesson)
|
|
109
|
+
pack.lesson = mem.lesson;
|
|
110
|
+
if (mem.refs)
|
|
111
|
+
pack.refs = mem.refs;
|
|
112
|
+
pack.plan = mem.plan;
|
|
113
|
+
pack.recent = mem.recent;
|
|
114
|
+
if (mem.lookup.length)
|
|
115
|
+
pack.lookup = mem.lookup;
|
|
116
|
+
pack.vault = vaultPack(ws.paths);
|
|
117
|
+
return { ...pack, retrieved: mem.retrieved };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* activity 的那两段:`activity:`(名字、参数、应用算的)每条都带;
|
|
121
|
+
* 「老师怎么带」那节(按参数与这个话题的卡裁过、剥了注释)+ 这个 activity 多带的卡的写法,作为一条笔记放进话题第一条(<cotutor-activity>),
|
|
122
|
+
* 续话题没改过写「未变」(同守则)。文件不在、读不了 → activityGuide 写「缺」,老师照自己的文件做。返回给这轮的提醒。
|
|
123
|
+
*/
|
|
124
|
+
export async function attachActivity(ws, tutor, pack, run, where, inSystem = false) {
|
|
125
|
+
pack.activity = { name: run.name, params: run.params, state: {} };
|
|
126
|
+
const path = relative(ws.root, activityFile(ws.root, tutor, run.name)).split(sep).join('/');
|
|
127
|
+
const text = await readFile(join(ws.root, path), 'utf8').catch(() => null);
|
|
128
|
+
if (text === null) {
|
|
129
|
+
pack.activityGuide = `缺:${path} 不在,照你老师文件里的做`;
|
|
130
|
+
return [`activity ${run.name}:${path} 不在,老师没拿到「老师怎么带」`];
|
|
131
|
+
}
|
|
132
|
+
const a = parseActivity(text).activity;
|
|
133
|
+
pack.activity.state = await activityState(ws, tutor, where.date, where.messages, where.thread, run, a, where.now);
|
|
134
|
+
// activity 已经是这个会话的系统提示:不再在第一条里带原文
|
|
135
|
+
if (inSystem) {
|
|
136
|
+
pack.activityGuide = ACTIVITY_IN_SYSTEM;
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const mine = tutorCardKinds(ws.config.tutors[tutor]?.cards);
|
|
140
|
+
const cards = activityCards(a, mine ?? undefined);
|
|
141
|
+
const body = renderTutorSection(a, { params: run.params, cards });
|
|
142
|
+
if (body === null)
|
|
143
|
+
return [];
|
|
144
|
+
// 这个 activity 要用、老师的板书写法里没有的卡:写法跟在后面(不动系统提示)
|
|
145
|
+
const extra = mine ? a.cards.filter((k) => !mine.includes(k)) : [];
|
|
146
|
+
const notes = pack.notes ?? [];
|
|
147
|
+
const at = Math.max(notes.findIndex((n) => n.role === 'boardGuide'), notes.findIndex((n) => n.role === 'rules'));
|
|
148
|
+
notes.splice(at + 1, 0, { role: 'activityGuide', path, text: extra.length ? `${body}\n\n## 这个 activity 多用的卡\n\n${cardGuidesFor(extra)}` : body });
|
|
149
|
+
pack.notes = notes;
|
|
150
|
+
pack.activityGuide = path;
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
/** 按老师裁的板书写法落在哪(相对 workspace 根;机器文件,内容变了文件名跟着变) */
|
|
154
|
+
export const TUTOR_BOARD_DIR = '.cotutor/board';
|
|
155
|
+
/** activity 在系统提示里时,上下文包 activityGuide 那一行 */
|
|
156
|
+
export const ACTIVITY_IN_SYSTEM = '已在你的系统提示里(这个话题的 activity,照它带)';
|
|
157
|
+
export async function activityParts(ws, tutor, run) {
|
|
158
|
+
const a = await readActivity(ws, tutor, run.name);
|
|
159
|
+
if (!a?.tutor)
|
|
160
|
+
return null;
|
|
161
|
+
const kinds = [...new Set(['text', ...a.cards.filter((k) => BOARD_CARD_KINDS.includes(k))])];
|
|
162
|
+
const body = renderTutorSection(a, { params: run.params, cards: kinds }) ?? '';
|
|
163
|
+
const guideBody = boardGuideFor(kinds);
|
|
164
|
+
const guide = { body: guideBody, path: `${TUTOR_BOARD_DIR}/${tutor}-${textHash(guideBody).slice(0, 8)}.md` };
|
|
165
|
+
const display = ws.config.tutors[tutor]?.display ?? tutor;
|
|
166
|
+
await writeIfChanged(join(ws.root, guide.path), guideBody);
|
|
167
|
+
return { body, kinds, guide, memory: { agent: `${tutor}/${run.name}`, display: `${display}·${run.name}` } };
|
|
168
|
+
}
|
|
169
|
+
async function writeIfChanged(file, text) {
|
|
170
|
+
if ((await readFile(file, 'utf8').catch(() => null)) === text)
|
|
171
|
+
return;
|
|
172
|
+
await mkdir(dirname(file), { recursive: true });
|
|
173
|
+
await writeFile(file, text);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 这位老师的板书写法:正文 + 相对 workspace 根的路径(给 {boardFile} 与上下文包)。
|
|
177
|
+
* 没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor),写到 .cotutor/board/<老师>-<hash>.md,
|
|
178
|
+
* 文件名带内容的 hash:清单一改路径就变,预热进程的 argv 对不上,不会拿着旧写法去答。读不到 SKILL.md → null。
|
|
179
|
+
*/
|
|
180
|
+
export async function boardGuideOf(ws, tutor) {
|
|
181
|
+
const kinds = tutorCardKinds(ws.config.tutors[tutor]?.cards);
|
|
182
|
+
if (!kinds) {
|
|
183
|
+
try {
|
|
184
|
+
return { body: boardGuideBody(await readFile(join(ws.root, BOARD_GUIDE_PATH), 'utf8')), path: BOARD_GUIDE_PATH };
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const body = boardGuideFor(kinds);
|
|
191
|
+
const path = `${TUTOR_BOARD_DIR}/${tutor}-${textHash(body).slice(0, 8)}.md`;
|
|
192
|
+
const file = join(ws.root, path);
|
|
193
|
+
if (!existsSync(file)) {
|
|
194
|
+
await mkdir(join(ws.root, TUTOR_BOARD_DIR), { recursive: true });
|
|
195
|
+
await writeFile(file, body);
|
|
196
|
+
}
|
|
197
|
+
return { body, path };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 板书写法怎么递给老师(2026-09-19):有脸的老师、板书没关才带。运行时预载了 → 上下文包只写一行事实;
|
|
201
|
+
* 没预载 → 原文作为一条笔记放进话题第一条(<cotutor-board>),排在守则后面,续话题没改过写「未变」。返回板书写法(给 {systemBody} 与 {boardFile})。
|
|
202
|
+
*/
|
|
203
|
+
export async function attachBoardGuide(ws, tutor, pack, o) {
|
|
204
|
+
if (!takesTutorRules(tutor) || o.boardOff)
|
|
205
|
+
return null;
|
|
206
|
+
const guide = await boardGuideOf(ws, tutor);
|
|
207
|
+
if (!guide) {
|
|
208
|
+
pack.boardGuide = `缺:${BOARD_GUIDE_PATH} 不在(cotutor upgrade 补),照你会的写`;
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (o.preloaded) {
|
|
212
|
+
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
|
213
|
+
return guide;
|
|
214
|
+
}
|
|
215
|
+
pack.boardGuide = guide.path;
|
|
216
|
+
const notes = pack.notes ?? [];
|
|
217
|
+
const at = notes.findIndex((n) => n.role === 'rules');
|
|
218
|
+
notes.splice(at + 1, 0, { role: 'boardGuide', path: guide.path, text: guide.body });
|
|
219
|
+
pack.notes = notes;
|
|
220
|
+
return guide;
|
|
221
|
+
}
|
|
222
|
+
/** 回复停在写成正文的工具调用上(后面只剩空行;「## 」尾巴不算):交给脑,CLI 据此 resume 一次 */
|
|
223
|
+
const stuckOnTool = (text) => endsWithToolCall(parseSections(text).body);
|
|
224
|
+
/**
|
|
225
|
+
* 老师正文({agentBody})与系统提示({systemBody} = 正文 + 板书写法)、板书写法的文件({boardFile}):模板用到才读。send 与预热同一份,预热起的进程 argv 才对得上。
|
|
226
|
+
* 顺手把板书写法挂到上下文包上(attachBoardGuide)。没递板书写法(工具人、板书关了)的 {boardFile} 照旧是出厂的 SKILL.md。
|
|
227
|
+
*/
|
|
228
|
+
async function systemParts(ws, tutor, runtime, policy, pack, act = null) {
|
|
229
|
+
const wantsSystemBody = runtimeUses(runtime, '{systemBody}');
|
|
230
|
+
// activity 话题:系统提示换成 activity 的(Qwen 走 {systemBody});模板没有它就退回老师文件 + 第一条带原文
|
|
231
|
+
if (act && takesTutorRules(tutor) && wantsSystemBody) {
|
|
232
|
+
const boardOff = policy.board === 'off';
|
|
233
|
+
if (!boardOff)
|
|
234
|
+
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
|
235
|
+
const systemBody = boardOff ? act.body : `${act.body}\n\n${act.guide.body}`;
|
|
236
|
+
return { agentBody: act.body, systemBody, boardFile: join(ws.root, boardOff ? BOARD_GUIDE_PATH : act.guide.path), agent: tutor, activityInSystem: true };
|
|
237
|
+
}
|
|
238
|
+
const agentBody = runtimeUses(runtime, '{agentBody}') || wantsSystemBody ? await readAgentBody(ws, tutor) : undefined;
|
|
239
|
+
const guide = await attachBoardGuide(ws, tutor, pack, { preloaded: boardPreloaded(runtime), boardOff: policy.board === 'off' });
|
|
240
|
+
const systemBody = wantsSystemBody && agentBody !== undefined ? (guide ? `${agentBody}\n\n${guide.body}` : agentBody) : undefined;
|
|
241
|
+
return { agentBody, systemBody, boardFile: join(ws.root, guide?.path ?? BOARD_GUIDE_PATH), agent: tutor, activityInSystem: false };
|
|
242
|
+
}
|
|
243
|
+
/** 这轮带的笔记版本:role → `路径@hash`(记进消息;同一话题下一轮对得上就不再带原文) */
|
|
244
|
+
export function noteVersions(pack) {
|
|
245
|
+
return Object.fromEntries((pack.notes ?? []).map((n) => [n.role, `${n.path}@${textHash(n.text)}`]));
|
|
246
|
+
}
|
|
247
|
+
/** 续会话时:上一轮这个话题已经带过、版本没变的笔记去掉原文,YAML 里注明「未变」 */
|
|
248
|
+
export function dropSeenNotes(pack, seen) {
|
|
249
|
+
const now = noteVersions(pack);
|
|
250
|
+
const keep = (pack.notes ?? []).filter((n) => seen[n.role] !== now[n.role]);
|
|
251
|
+
const out = { ...pack, notes: keep };
|
|
252
|
+
for (const n of pack.notes ?? [])
|
|
253
|
+
if (!keep.includes(n))
|
|
254
|
+
out[n.role] = `${pack[n.role]}(未变,原文在本话题前面)`;
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
/** 这个话题里最近一次记下的笔记版本(新话题、没记过 → {}) */
|
|
258
|
+
export function seenNotes(messages, thread) {
|
|
259
|
+
const ths = threads(messages);
|
|
260
|
+
for (let i = messages.length - 1; i >= 0; i--)
|
|
261
|
+
if (ths[i] === thread && messages[i].notes)
|
|
262
|
+
return messages[i].notes;
|
|
263
|
+
return {};
|
|
264
|
+
}
|
|
265
|
+
/** 老师该读得到的目录:workspace 根(作业照片、技能)与 vault(教材、日记);会话 cwd 是老师目录,这些在它外面(qwen 读外面要先划进来) */
|
|
266
|
+
export function readDirsOf(ws) {
|
|
267
|
+
return [...new Set([ws.root, ws.paths.vault])];
|
|
268
|
+
}
|
|
269
|
+
/** 干跑:现在给这位老师发这句话,上下文包会是什么样、每段从哪来、截了多少;不起模型、不写盘 */
|
|
270
|
+
export async function packDryRun(ws, tutor, input) {
|
|
271
|
+
const t = ws.config.tutors[tutor];
|
|
272
|
+
if (!t)
|
|
273
|
+
throw new UsageError(`没有叫 ${tutor} 的老师;cotutor.json 的 tutors 里有:${Object.keys(ws.config.tutors).join('、')}`);
|
|
274
|
+
const report = {};
|
|
275
|
+
const act = input.activity ? await activityParts(ws, tutor, input.activity) : null;
|
|
276
|
+
const pack = await gatherContext(ws, tutor, { from: input.from, at: input.at, ...(input.from === 'kid' ? { query: input.text } : {}), ...(act ? { agent: act.memory.agent, kinds: act.kinds } : {}) }, report);
|
|
277
|
+
const policy = resolvePolicy(ws.config, tutor);
|
|
278
|
+
if (policy.board === 'off')
|
|
279
|
+
pack.board = 'off';
|
|
280
|
+
const { runtime } = getRuntime(ws.config, t.runtime);
|
|
281
|
+
// 和 send 同一条规矩:孩子的话不带工具时,只有工具才用得上的路径不进包
|
|
282
|
+
if (policy.tools !== 'on' && input.from === 'kid' && controlsTools(runtime)) {
|
|
283
|
+
delete pack.refs;
|
|
284
|
+
delete pack.vault;
|
|
285
|
+
}
|
|
286
|
+
const parts = await systemParts(ws, tutor, runtime, policy, pack, act);
|
|
287
|
+
if (input.activity)
|
|
288
|
+
await attachActivity(ws, tutor, pack, input.activity, { date: localDate(input.at), messages: [], thread: '', now: input.at.getTime() }, parts.activityInSystem);
|
|
289
|
+
return { prompt: buildContextPack(pack, input.text, policy.contextPack), pack, report };
|
|
290
|
+
}
|
|
291
|
+
/** 上下文包的 vault: 段:root 绝对,其余角色相对 root;在 root 外面(家长把日记指到别处)就给绝对路径 */
|
|
292
|
+
export function vaultPack(paths) {
|
|
293
|
+
const root = paths.vault;
|
|
294
|
+
const out = { root };
|
|
295
|
+
for (const r of VAULT_PACK_ROLES) {
|
|
296
|
+
const p = paths[r];
|
|
297
|
+
if (!p)
|
|
298
|
+
continue;
|
|
299
|
+
const rel = relative(root, p);
|
|
300
|
+
out[r] = !rel || rel.startsWith('..') || isAbsolute(rel) ? p : rel;
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
export class Runner {
|
|
305
|
+
active = new Map();
|
|
306
|
+
/** 还在后台跑的资产生成(测试与关服前 flush) */
|
|
307
|
+
background = new Set();
|
|
308
|
+
getWs;
|
|
309
|
+
opts;
|
|
310
|
+
listeners = new Set();
|
|
311
|
+
/** noteEvent 每个事件文件一条追加链 */
|
|
312
|
+
notes = new Map();
|
|
313
|
+
/** 脑执行:起老师、读输出、断流接着跑、预热下一轮(src/brain/) */
|
|
314
|
+
brain;
|
|
315
|
+
/** 预热池的旧入口(测试看有没有、杀掉);迁完删 */
|
|
316
|
+
get spares() {
|
|
317
|
+
return { has: (tutor, slot) => this.brain.warmed(tutor, slot), drop: (tutor, slot) => this.brain.dropWarm(tutor, slot) };
|
|
318
|
+
}
|
|
319
|
+
/** 页面拉今天时的预热:每位老师(与带的话题)上次看的时刻 */
|
|
320
|
+
prewarmedAt = new Map();
|
|
321
|
+
/** 起子进程用的环境(测试注入) */
|
|
322
|
+
get env() {
|
|
323
|
+
return this.opts.env;
|
|
324
|
+
}
|
|
325
|
+
/** 当前的 workspace(cotutor.json 热重载后是新的;实时老师经它拿) */
|
|
326
|
+
get ws() {
|
|
327
|
+
return this.getWs();
|
|
328
|
+
}
|
|
329
|
+
/** 画图卡交给谁画(测试与 mock 注入假的) */
|
|
330
|
+
draw;
|
|
331
|
+
constructor(getWs, opts = {}) {
|
|
332
|
+
this.getWs = getWs;
|
|
333
|
+
this.opts = opts;
|
|
334
|
+
this.draw = opts.draw ?? makeDraw({ env: opts.env });
|
|
335
|
+
// API 运行时的凭证与实时那边同一条查找链(家长端配的在最前);只有按量 key、没有业务空间 id 也能用(走公网域名,同 qwen)。
|
|
336
|
+
// COTUTOR_BRAIN_ENDPOINT:测试与探针指到假百炼
|
|
337
|
+
const env = opts.env ?? process.env;
|
|
338
|
+
this.brain = createBrain({ workspace: () => { const ws = this.getWs(); return { config: ws.config, root: ws.root }; }, env: opts.env, warmIdleMs: opts.warmIdleMs, auth: () => dashscopeAuth(env) ?? (qianwenKeys(env).payg ? { key: qianwenKeys(env).payg, workspaceId: null } : null), endpoint: env.COTUTOR_BRAIN_ENDPOINT || undefined });
|
|
339
|
+
}
|
|
340
|
+
/** 孩子端发一条的验与拼(HTTP 与实时老师共用,./kid-input.ts):拼成 send 的入参,或回 reject;不起老师 */
|
|
341
|
+
accept(tutor, body, date, deviceId) {
|
|
342
|
+
return kidInput(this.getWs(), tutor, body, date, deviceId);
|
|
343
|
+
}
|
|
344
|
+
running(tutor) {
|
|
345
|
+
const a = this.active.get(tutor);
|
|
346
|
+
return a ? { job: a.job, date: a.date } : null;
|
|
347
|
+
}
|
|
348
|
+
/** 等所有后台资产生成完(测试用) */
|
|
349
|
+
async flush() {
|
|
350
|
+
while (this.background.size)
|
|
351
|
+
await Promise.all([...this.background]);
|
|
352
|
+
}
|
|
353
|
+
/** 订阅每一轮的事件(runtime/journal/events.ts;cotutor send 现场打印、serve --trace);返回退订函数。事件同时追加到 <日期>.<job>.events.jsonl */
|
|
354
|
+
onEvent(fn) {
|
|
355
|
+
this.listeners.add(fn);
|
|
356
|
+
return () => void this.listeners.delete(fn);
|
|
357
|
+
}
|
|
358
|
+
/** 别处给某一轮补事件(实时老师的嘴,src/runtime/live/call.ts;t 已经换成离这一轮老师进程起来的毫秒):追加到 events.jsonl、给订阅者;这一轮跑完了也行 */
|
|
359
|
+
async noteEvent(tutor, date, job, event) {
|
|
360
|
+
const file = conversationFiles(this.getWs().dirs.conversations, tutor, date).events(job);
|
|
361
|
+
// 同一个文件按序追加(几条一起来时不乱序)
|
|
362
|
+
const chain = (this.notes.get(file) ?? Promise.resolve()).then(() => appendFile(file, `${JSON.stringify(event)}\n`)).catch(() => { });
|
|
363
|
+
this.notes.set(file, chain);
|
|
364
|
+
void chain.finally(() => { if (this.notes.get(file) === chain)
|
|
365
|
+
this.notes.delete(file); });
|
|
366
|
+
await chain;
|
|
367
|
+
for (const fn of this.listeners) {
|
|
368
|
+
try {
|
|
369
|
+
fn({ tutor, date, job, event });
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
/* 订阅者的错不影响 */
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** 杀掉所有预热的进程(测试收尾;服务退出时它们的 stdin 断了会自己退) */
|
|
377
|
+
close() {
|
|
378
|
+
this.brain.close();
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 预热:给这位老师起好下一轮的进程,两个位置都补齐——
|
|
382
|
+
* fresh:新开会话(新话题、接着上次都用它);
|
|
383
|
+
* resume:接 thread(页面选着的话题;不给或今天没有 = 当天末条所在的话题,和 send 缺省一样)的会话,它没有会话就不留。
|
|
384
|
+
* 只给有脸的、开着的、运行时 stdin: "stream-json"、这会儿没在跑的老师起;throttleMs 内同一话题看过就不再看(页面轮询今天)。
|
|
385
|
+
* 起了返回 true;起不了不报错,下一轮冷起。warm: false(cotutor send:跑完就退,留着备用进程会把它挂一小时)不起。
|
|
386
|
+
*/
|
|
387
|
+
async prewarm(tutor, opts = {}) {
|
|
388
|
+
if (this.opts.warm === false)
|
|
389
|
+
return false;
|
|
390
|
+
const t0 = Date.now();
|
|
391
|
+
const seen = `${tutor}\n${opts.thread ?? ''}`;
|
|
392
|
+
if (opts.throttleMs && t0 - (this.prewarmedAt.get(seen) ?? -Infinity) < opts.throttleMs)
|
|
393
|
+
return false;
|
|
394
|
+
this.prewarmedAt.set(seen, t0);
|
|
395
|
+
const ws = this.getWs();
|
|
396
|
+
const t = ws.config.tutors[tutor];
|
|
397
|
+
if (!t?.enabled || !takesTutorRules(tutor) || this.active.has(tutor))
|
|
398
|
+
return false;
|
|
399
|
+
const { runtime } = getRuntime(ws.config, t.runtime);
|
|
400
|
+
if (!this.brain.canWarm(runtime))
|
|
401
|
+
return false;
|
|
402
|
+
const date = localDate((this.opts.now ?? (() => new Date()))());
|
|
403
|
+
const index = await readIndex(ws, tutor, date);
|
|
404
|
+
const known = threads(index.messages);
|
|
405
|
+
const thread = opts.thread && known.includes(opts.thread) ? opts.thread : known.at(-1);
|
|
406
|
+
const session = thread ? sessionFor(index, thread) : null;
|
|
407
|
+
const policy = resolvePolicy(ws.config, tutor);
|
|
408
|
+
const parts = await systemParts(ws, tutor, runtime, policy, { at: '', plan: [], recent: [] });
|
|
409
|
+
const base = { prompt: '', runtime: t.runtime, effort: policy.effort, tools: policy.tools === 'on' ? 'on' : 'off', readDirs: readDirsOf(ws) };
|
|
410
|
+
// 预热的是孩子的下一句:工具照孩子那轮填(带照片的对不上,那轮冷起)。新话题照老师文件;接着的话题是 activity 的,照它的系统提示
|
|
411
|
+
const fresh = this.brain.plan({ ...base, ...parts, session: null });
|
|
412
|
+
const run = thread ? threadActivity(index.messages, thread) : null;
|
|
413
|
+
const act = run ? await activityParts(ws, tutor, run) : null;
|
|
414
|
+
const resumeParts = act ? await systemParts(ws, tutor, runtime, policy, { at: '', plan: [], recent: [] }, act) : parts;
|
|
415
|
+
const resume = session ? this.brain.plan({ ...base, ...resumeParts, session }) : null;
|
|
416
|
+
const cwd = join(ws.dirs.agents, tutor);
|
|
417
|
+
await mkdir(cwd, { recursive: true });
|
|
418
|
+
// 读索引这会儿孩子可能已经开口了:这轮在跑就不起(它收尾时会再起)
|
|
419
|
+
if (this.active.has(tutor))
|
|
420
|
+
return false;
|
|
421
|
+
await this.brain.prepare(fresh);
|
|
422
|
+
if (this.active.has(tutor))
|
|
423
|
+
return false;
|
|
424
|
+
this.brain.warm(tutor, 'fresh', fresh, { cwd, date, lastJob: null });
|
|
425
|
+
if (resume?.resume && thread)
|
|
426
|
+
this.brain.warm(tutor, 'resume', resume, { cwd, date, lastJob: lastJobOf(index, thread) });
|
|
427
|
+
else
|
|
428
|
+
this.brain.dropWarm(tutor, 'resume');
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
/** 正在回的那条已经出来的板书(流式);没在跑或还没出卡 → null */
|
|
432
|
+
partial(tutor) {
|
|
433
|
+
return this.active.get(tutor)?.partial ?? null;
|
|
434
|
+
}
|
|
435
|
+
async send(tutor, input) {
|
|
436
|
+
const ws = this.getWs();
|
|
437
|
+
const t = ws.config.tutors[tutor];
|
|
438
|
+
if (!t)
|
|
439
|
+
throw new UsageError(`没有叫 ${tutor} 的老师;cotutor.json 的 tutors 里有:${Object.keys(ws.config.tutors).join('、')}`);
|
|
440
|
+
if (!t.enabled)
|
|
441
|
+
throw new UsageError(`${t.display} 已关闭(cotutor.json tutors.${tutor}.enabled = false),打开再发`);
|
|
442
|
+
const photos = input.photos?.filter(Boolean) ?? [];
|
|
443
|
+
// 小课堂画的一处带一张截图:和照片一样进上下文包 photos:(排在孩子拍的后面),这一轮带工具读图;消息的 photos 只记孩子拍的
|
|
444
|
+
const shots = input.lecture?.marks?.flatMap((m) => (m.image ? [m.image] : [])) ?? [];
|
|
445
|
+
// 纸上交的画:那张画与清空前存的那几版也给老师 Read,排在照片与小课堂截图后面
|
|
446
|
+
const drawn = input.drawing ? [input.drawing.image, ...(input.drawing.cleared ?? [])] : [];
|
|
447
|
+
const seen = [...photos, ...shots, ...drawn];
|
|
448
|
+
const text = input.text.trim() || (input.action === 'submit' ? '(交了答案,没说话)' : photos.length ? (photos.length === 1 ? '(拍了一张)' : `(拍了 ${photos.length} 张)`) : input.drawing ? DRAW_ONLY_TEXT : '');
|
|
449
|
+
if (!text)
|
|
450
|
+
throw new UsageError('消息是空的');
|
|
451
|
+
const busy = this.active.get(tutor);
|
|
452
|
+
if (busy)
|
|
453
|
+
throw new BusyError(tutor, busy.job);
|
|
454
|
+
const now = (this.opts.now ?? (() => new Date()))();
|
|
455
|
+
const date = input.date ?? localDate(now);
|
|
456
|
+
// 占位要在第一个 await 之前(2026-10-17):查完 busy 到 spawn 之间十几个 await,孩子连点两下两条都过检查、算出同一个 job、起两个进程。
|
|
457
|
+
// 占位先进 map,job 等读了索引再填;起不来(索引坏、话题不在、CLI 没起)就放掉占位
|
|
458
|
+
const active = { job: '(准备中)', date, partial: null, done: new Promise(() => { }) };
|
|
459
|
+
this.active.set(tutor, active);
|
|
460
|
+
try {
|
|
461
|
+
return await this.begin(ws, tutor, input, { t, now, date, photos, seen, text }, active);
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
if (this.active.get(tutor) === active)
|
|
465
|
+
this.active.delete(tutor);
|
|
466
|
+
throw err;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** send 的后半:占位已在 active 里,这里读索引、拼包、起进程;抛了由 send 放掉占位 */
|
|
470
|
+
async begin(ws, tutor, input, prep, active) {
|
|
471
|
+
const { t, now, date, photos, seen, text } = prep;
|
|
472
|
+
const index = await readIndex(ws, tutor, date);
|
|
473
|
+
const job = jobId(now, index.messages.length + 1);
|
|
474
|
+
// 话题:新开(newThread / 没指定话题的系统消息 / 今天第一条)= 自己的 job;指定的要在这天的索引里;缺省接当前话题。会话按话题 resume
|
|
475
|
+
const known = threads(index.messages);
|
|
476
|
+
let thread;
|
|
477
|
+
if (input.newThread || (input.from === 'system' && !input.thread) || !known.length)
|
|
478
|
+
thread = job;
|
|
479
|
+
else if (input.thread) {
|
|
480
|
+
if (!known.includes(input.thread))
|
|
481
|
+
throw new UsageError(`${date} 没有话题 ${input.thread};有:${[...new Set(known)].join('、')}`);
|
|
482
|
+
thread = input.thread;
|
|
483
|
+
}
|
|
484
|
+
else
|
|
485
|
+
thread = known[known.length - 1];
|
|
486
|
+
const { name: runtimeName, runtime } = getRuntime(ws.config, input.runtime ?? t.runtime);
|
|
487
|
+
// 话题只属于开它的那种运行时(2026-10-31):会话是别的运行时的(换过运行时)就接不上,这条开新话题,不把两种会话混在一个话题里;记账在 bookkeep 里先跳过了
|
|
488
|
+
if (thread !== job && !input.bookkeep && !sameRuntime(index, thread, runtimeName))
|
|
489
|
+
thread = job;
|
|
490
|
+
const fresh = thread === job;
|
|
491
|
+
const session = fresh ? null : sessionFor(index, thread);
|
|
492
|
+
// 长话题整段压缩(API 运行时):之前的模型历史不再发,前情照索引拼;这一轮笔记带全文,不写「未变」(原文所在的那几轮不发了)
|
|
493
|
+
const compact = !fresh && isApiRuntime(runtime) && dueCompact(index.messages, known, thread);
|
|
494
|
+
// activity:新话题照这条带的,续话题照话题第一条记的。系统提示、卡、记忆都是它的(记账轮也是,接着同一个会话);上下文包的 activity 段记账轮不带
|
|
495
|
+
// 隔天「接着」是新话题:没带 activity 就沿用被接的那个话题的(参数也是那时的),不然退回老师文件,没有那套做法和卡
|
|
496
|
+
const threadRun = fresh ? (input.activity ?? (input.continues ? threadActivity((await readIndex(ws, tutor, input.continues.date)).messages, input.continues.thread) : null)) : threadActivity(index.messages, thread);
|
|
497
|
+
const act = threadRun ? await activityParts(ws, tutor, threadRun) : null;
|
|
498
|
+
// 取材的查询:孩子这句话,加首页按钮的讲法(系统任务不查)
|
|
499
|
+
const query = input.from === 'kid' ? [text, input.home?.brief].filter(Boolean).join('\n') : '';
|
|
500
|
+
const gathered = await gatherContext(ws, tutor, { from: input.from, at: now, focus: input.focus, ...(query ? { query } : {}), ...(act ? { agent: act.memory.agent, kinds: act.kinds } : {}) });
|
|
501
|
+
const policy = resolvePolicy(ws.config, tutor);
|
|
502
|
+
const { agentBody, systemBody, boardFile, agent, activityInSystem } = await systemParts(ws, tutor, runtime, policy, gathered, act);
|
|
503
|
+
const activityRun = input.bookkeep ? null : threadRun;
|
|
504
|
+
const activityWarn = activityRun ? await attachActivity(ws, tutor, gathered, activityRun, { date, messages: index.messages, thread, now: now.getTime() }, activityInSystem) : [];
|
|
505
|
+
// 家长笔记原文:新会话整篇带;续会话时这个话题带过、没改的只写「未变」
|
|
506
|
+
const notes = noteVersions(gathered);
|
|
507
|
+
const pack = session && !compact ? dropSeenNotes(gathered, seenNotes(index.messages, thread)) : gathered;
|
|
508
|
+
// 入口文件是可选的(2026-10-30,《vault重构方案》§8:ray 的三篇都是空壳,讲法偏好家长写在首页按钮里):缺了不再每轮提醒,doctor 里提示一次
|
|
509
|
+
if (policy.board === 'off')
|
|
510
|
+
pack.board = 'off';
|
|
511
|
+
// 这个话题里上一轮之后孩子在卡上做的事:逐张 describe 进上下文包,也记进这条消息(家长视图「孩子在板书上做的」);新话题不带
|
|
512
|
+
// 画板交上来的图(状态里是相对 workspace 根的路径):包里那行换成绝对路径(Read 只认绝对路径,老师的 cwd 在 agents/<名>/),消息里照旧记相对的
|
|
513
|
+
const changed = fresh || input.bookkeep ? [] : changedCards(index, await readCardStates(ws, tutor, date), thread);
|
|
514
|
+
let drawings = 0;
|
|
515
|
+
const cards = await Promise.all(changed.map(async (c) => {
|
|
516
|
+
const card = index.messages.find((m) => m.job === c.job)?.section?.cards[c.n];
|
|
517
|
+
const id = cardId(c.job, c.n);
|
|
518
|
+
if (!card)
|
|
519
|
+
return { card: id, text: JSON.stringify(c.file.state) };
|
|
520
|
+
const st = card.kind === 'canvas' ? c.file.state : null;
|
|
521
|
+
const image = typeof st?.image === 'string' ? st.image : undefined;
|
|
522
|
+
// 清空前存的那几版也是图,一样换绝对路径、一样要 Read
|
|
523
|
+
const cleared = Array.isArray(st?.cleared) ? st.cleared : [];
|
|
524
|
+
if (!image && !cleared.some((x) => typeof x?.image === 'string'))
|
|
525
|
+
return { card: id, text: describeCard(card, c.file.state) };
|
|
526
|
+
drawings++;
|
|
527
|
+
const abs = { ...st, ...(image ? { image: join(ws.root, image) } : {}), cleared: cleared.map((x) => (typeof x?.image === 'string' ? { ...x, image: join(ws.root, x.image) } : x)) };
|
|
528
|
+
return { card: id, text: describeCard(card, c.file.state), pack: describeCard(card, abs) };
|
|
529
|
+
}));
|
|
530
|
+
// 这轮带不带工具(policy.tools,2026-10-04):孩子说的话缺省不带,只凭上下文包答;带照片的、交了画的(要 Read 看图)、
|
|
531
|
+
// 系统任务带只读的(以前是整份白名单,照片里一句话就能让老师拿 Bash 读写 workspace 外);policy.tools: on 的老师总是整份。
|
|
532
|
+
// 不带的那轮,只有工具才用得上的路径(refs / vault)不进包;模板管不了工具的运行时(没有 {tools} / {toolArgs}),照旧
|
|
533
|
+
const tools = policy.tools === 'on' ? 'on' : input.from !== 'kid' || seen.length > 0 || drawings > 0 ? 'read' : 'off';
|
|
534
|
+
const bare = tools === 'off' && controlsTools(runtime);
|
|
535
|
+
if (bare) {
|
|
536
|
+
delete pack.refs;
|
|
537
|
+
delete pack.vault;
|
|
538
|
+
}
|
|
539
|
+
// 画图卡:这个话题上一节有画图卡,告诉老师画好没有(老师看不到图)
|
|
540
|
+
if (!fresh && !input.bookkeep) {
|
|
541
|
+
const prevAt = index.messages.map((m, i) => ({ m, th: known[i] })).reverse().find((x) => x.th === thread && x.m.result === 'ok' && x.m.section);
|
|
542
|
+
if (prevAt?.m.section?.cards.some((c) => c.kind === 'paint'))
|
|
543
|
+
for (const line of await paintLines(ws, tutor, date, prevAt.m.job, prevAt.m.section))
|
|
544
|
+
cards.push({ card: line.slice(0, line.indexOf(' ')), text: line.slice(line.indexOf(' ') + 1) });
|
|
545
|
+
}
|
|
546
|
+
if (cards.length)
|
|
547
|
+
pack.cards = cards.map((c) => `${c.card} ${c.pack ?? c.text}`);
|
|
548
|
+
if (seen.length) {
|
|
549
|
+
pack.photos = seen;
|
|
550
|
+
pack.photoFiles = seen.map((p) => join(ws.root, p));
|
|
551
|
+
}
|
|
552
|
+
// 纸上的画排在 photos 最后:它自己,再是清掉的那几版
|
|
553
|
+
if (input.drawing)
|
|
554
|
+
pack.drawing = drawingPackLine(input.drawing, seen.length - (input.drawing.cleared?.length ?? 0));
|
|
555
|
+
if (input.home)
|
|
556
|
+
pack.home = input.home;
|
|
557
|
+
// 小课堂:课包现读;读不出来(删了、坏了)就不带,这条照发,家长端提醒
|
|
558
|
+
const lecture = input.lecture ? await readLecture(ws, input.lecture.bundle) : null;
|
|
559
|
+
const marks = input.lecture?.marks?.length ? storedMarks(lecture, input.lecture.marks, photos.length) : [];
|
|
560
|
+
if (input.lecture && lecture)
|
|
561
|
+
pack.lecture = lecturePack(lecture, input.lecture, marks);
|
|
562
|
+
const lectureWarn = input.lecture && !lecture ? [`小课堂:${input.lecture.bundle} 读不出来(bundles/ 与 lectures/ 下都没有能放的),老师没拿到课的内容`] : [];
|
|
563
|
+
const continued = input.continues && fresh ? (input.continues.pack ?? (await continueContext(ws, tutor, input.continues.date, input.continues.thread))) : null;
|
|
564
|
+
if (continued)
|
|
565
|
+
pack.continue = continued;
|
|
566
|
+
if (input.live) {
|
|
567
|
+
const l = input.live;
|
|
568
|
+
pack.live = { ...(l.caught ? { caught: l.caught } : {}), ...(l.cut !== undefined ? { cut: l.cut } : {}), ...(l.warmup?.length ? { warmup: l.warmup } : {}), ...(l.phone !== undefined ? { phone: l.phone } : {}), ...(l.app ? { app: l.app } : {}), ...(l.draw?.length ? { draw: l.draw } : {}) };
|
|
569
|
+
}
|
|
570
|
+
const prompt = buildContextPack(pack, text, policy.contextPack);
|
|
571
|
+
const req = { agent, prompt, agentBody, systemBody, boardFile, runtime: input.runtime ?? t.runtime, effort: policy.effort, tools, readDirs: readDirsOf(ws), session, ...(compact ? { compact: { digest: threadDigest(index.messages.filter((_, i) => known[i] === thread)) } } : {}), ...(pack.photoFiles?.length ? { images: pack.photoFiles } : {}), stuckOnTool };
|
|
572
|
+
const plan = this.brain.plan(req);
|
|
573
|
+
// 原声:落在这轮旁边(删话题一起删);写不下就当没有,消息照发
|
|
574
|
+
const started = addMessage(index, { job, thread, at: pack.at, from: input.from, text, focus: input.focus, ...(input.action ? { action: input.action } : {}), ...(cards.length ? { cards: cards.map(({ card, text }) => ({ card, text })) } : {}), ...(photos.length ? { photos } : {}), ...(input.drawing ? { drawing: input.drawing } : {}), ...(input.device ? { device: input.device } : {}), ...(input.deviceId ? { deviceId: input.deviceId } : {}), ...(input.bookkeep ? { bookkeep: input.bookkeep } : {}), ...(input.tidy ? { tidy: true } : {}), ...(input.replayOf ? { replayOf: input.replayOf } : {}), ...(input.eval ? { eval: true } : {}), ...(input.live ? { live: true } : {}), ...(input.via ? { via: input.via } : {}), ...(continued && input.continues ? { continues: { date: input.continues.date, thread: input.continues.thread } } : {}), ...(input.lecture ? { lecture: { bundle: input.lecture.bundle, title: lecture?.title ?? input.lecture.bundle, watchedMs: input.lecture.watchedMs, finished: input.lecture.finished, pauses: input.lecture.pauses, ...(input.lecture.again ? { again: true } : {}), ...(lecture?.video ? { video: true } : {}), ...(marks.length ? { marks } : {}), ...(input.lecture.log?.length ? { log: input.lecture.log } : {}) } } : {}), ...(Object.keys(notes).length ? { notes } : {}), ...(fresh && activityRun ? { activity: activityRun } : {}), ...(fresh && input.talk ? { talk: input.talk } : {}), ...(compact ? { compacted: true } : {}), ...(lectureWarn.length || activityWarn.length ? { warnings: [...lectureWarn, ...activityWarn] } : {}), result: 'running', artifacts: [], runtime: plan.runtime });
|
|
575
|
+
await writeIndex(ws, started);
|
|
576
|
+
// 运行记录(《vault重构方案》§7):除了老师文件与技能,还记这轮带进上下文的每篇笔记、替老师查的每个文件,事后重建得出老师看到的是哪一版
|
|
577
|
+
const notesSent = (pack.notes ?? []).map((n) => ({ role: n.role, path: n.path, hash: textHash(n.text), chars: n.text.length, ...(n.cut ? { cut: true } : {}) }));
|
|
578
|
+
await writeRunFile(ws, tutor, date, job, { at: pack.at, prompt, plan, agentBody: agentBody !== undefined, sources: { ...(await snapshotSources(ws, tutor)), notes: notesSent, retrieved: gathered.retrieved } });
|
|
579
|
+
// 这个 CLI 在 workspace 里要的机器文件(qwen 的隔离家目录):备不好这轮照起,起不来的原因会在 err.log
|
|
580
|
+
await this.brain.prepare(plan);
|
|
581
|
+
active.job = job;
|
|
582
|
+
if (input.live)
|
|
583
|
+
active.live = true;
|
|
584
|
+
// 实时的一轮讲稿是嘴念的,不按 replyMaxChars 截(截了孩子听到半句:10-20「题目已经…」后面「你补上 ēng 就行」没了)
|
|
585
|
+
const runPolicy = input.live ? { ...policy, replyMaxChars: Number.MAX_SAFE_INTEGER } : policy;
|
|
586
|
+
// 预热的进程按这个话题末条 job 对(新开记 null);spawn 在同一拍里接过来
|
|
587
|
+
const lastJob = plan.resume ? lastJobOf(index, thread) : null;
|
|
588
|
+
active.done = this.spawn(ws, tutor, date, job, thread, req, plan, runPolicy, active, lastJob).finally(() => {
|
|
589
|
+
if (this.active.get(tutor) === active)
|
|
590
|
+
this.active.delete(tutor);
|
|
591
|
+
// 孩子 / 家长这轮完了,马上起好下一轮的进程;系统轮(记账、整理记忆)之后孩子多半不在,不起
|
|
592
|
+
if (input.from !== 'system' && !input.replayOf && !input.eval)
|
|
593
|
+
void this.prewarm(tutor).catch(() => { });
|
|
594
|
+
});
|
|
595
|
+
return { tutor, date, job, thread, plan, done: active.done };
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* 记账(《obsidian仓库设计.md》§4 / §6):家长晚上点一次。这天索引里每个还没记过、有孩子的话或打了分的话题,各起一轮记账任务
|
|
599
|
+
* (from: system、resume 那个话题的会话,消息正文是 bookkeepingPrompt),顺着跑,老师回的「## 记账」段由 settleDiary 落进日记。
|
|
600
|
+
* 立刻返回排了哪些、跳过哪些;链在后台跑(flush 会等,done 也行)。老师正忙 → BusyError。
|
|
601
|
+
* tidy: false 记完不整理记忆(自动记账记今天的话题时,孩子晚上可能还来,src/runtime/auto-bookkeep.ts)。
|
|
602
|
+
*/
|
|
603
|
+
async bookkeep(tutor, date, only, opts = {}) {
|
|
604
|
+
const ws = this.getWs();
|
|
605
|
+
const t = ws.config.tutors[tutor];
|
|
606
|
+
if (!t)
|
|
607
|
+
throw new UsageError(`没有叫 ${tutor} 的老师;cotutor.json 的 tutors 里有:${Object.keys(ws.config.tutors).join('、')}`);
|
|
608
|
+
// 上次记到一半断了的(写完日记、没记进索引):先对照日记补记,不然这里当没记过又记一遍。老师正跑着时它的 pending 是真在写,不碰
|
|
609
|
+
const read = await readIndex(ws, tutor, date);
|
|
610
|
+
const recovered = this.active.get(tutor) ? { index: read, changed: false } : await recoverPending(ws, read);
|
|
611
|
+
if (recovered.changed)
|
|
612
|
+
await writeIndex(ws, recovered.index);
|
|
613
|
+
const index = recovered.index;
|
|
614
|
+
// 整理记忆那轮自成一个话题,不是学的东西,不记账
|
|
615
|
+
const all = [...new Set(threads(index.messages).filter((_, i) => !index.messages[i].tidy))];
|
|
616
|
+
const queued = [];
|
|
617
|
+
const skipped = [];
|
|
618
|
+
const runtimeName = t.runtime ?? ws.config.runtimes.default;
|
|
619
|
+
for (const th of only ?? all) {
|
|
620
|
+
if (!all.includes(th))
|
|
621
|
+
skipped.push({ thread: th, why: `${date} 没有这个话题` });
|
|
622
|
+
else if (index.booked[th])
|
|
623
|
+
skipped.push({ thread: th, why: `记过了(${index.booked[th]})` });
|
|
624
|
+
// 换过运行时:老师接不上那时的对话,硬记就是编(2026-10-31 副本上试过,还被整理进了记忆)
|
|
625
|
+
else if (!sameRuntime(index, th, runtimeName))
|
|
626
|
+
skipped.push({ thread: th, why: `这个话题是在「${index.sessions[th].runtime}」下聊的,老师现在用「${runtimeName}」,接不上那时的对话,不记账`, oldRuntime: index.sessions[th].runtime });
|
|
627
|
+
else if (!only && !kidQuestions(index.messages, th).length && index.ratings[th] === undefined)
|
|
628
|
+
skipped.push({ thread: th, why: '没有孩子的话也没打分' });
|
|
629
|
+
else
|
|
630
|
+
queued.push(th);
|
|
631
|
+
}
|
|
632
|
+
if (!queued.length)
|
|
633
|
+
return { queued, skipped, done: Promise.resolve() };
|
|
634
|
+
const busy = this.active.get(tutor);
|
|
635
|
+
if (busy)
|
|
636
|
+
throw new BusyError(tutor, busy.job);
|
|
637
|
+
const headings = textbookHeadings(await readTextbooks(ws));
|
|
638
|
+
const chain = (async () => {
|
|
639
|
+
for (const th of queued) {
|
|
640
|
+
try {
|
|
641
|
+
const idx = await readIndex(ws, tutor, date);
|
|
642
|
+
// 话题里有照片:提示词多一句,让 summary 把册子 / 页 / 题号 / 题面写成文字(日记不存图,《obsidian仓库设计.md》§5)
|
|
643
|
+
const ths = threads(idx.messages);
|
|
644
|
+
const photos = idx.messages.reduce((n, m, i) => n + (ths[i] === th ? (m.photos?.length ?? 0) : 0), 0);
|
|
645
|
+
const started = await this.send(tutor, { from: 'system', text: bookkeepingPrompt({ thread: th, rating: idx.ratings[th], keepScore: ws.config.vault.keepScore, headings, photos }), thread: th, date, bookkeep: { thread: th } });
|
|
646
|
+
await started.done;
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
/* 这条记不了(老师忙 / 起不来),下一条照记;索引里没记 booked,再点一次记账会重试 */
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// 账记完,这位老师整理一遍自己的记忆(新会话,结果直接落盘;《obsidian仓库设计.md》§8)
|
|
653
|
+
if (opts.tidy === false)
|
|
654
|
+
return;
|
|
655
|
+
try {
|
|
656
|
+
const tidy = await this.tidyMemory(tutor, date);
|
|
657
|
+
if (tidy)
|
|
658
|
+
await tidy.done;
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
/* 整理不了(老师忙 / 起不来)不影响记账;下次记账再整理 */
|
|
662
|
+
}
|
|
663
|
+
})();
|
|
664
|
+
this.background.add(chain);
|
|
665
|
+
void chain.finally(() => this.background.delete(chain));
|
|
666
|
+
return { queued, skipped, done: chain };
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* 记账后整理记忆:这位老师有记忆文件、里面有条目才起一轮(新会话、from: system、tidy);没有就返回 null。
|
|
670
|
+
* 老师回的「## 记忆」段照单落盘(settleMemory 不限条数)。
|
|
671
|
+
*/
|
|
672
|
+
async tidyMemory(tutor, date) {
|
|
673
|
+
const ws = this.getWs();
|
|
674
|
+
const memory = (await scanVault(ws)).notes.find((n) => n.props.cotutor === 'memory' && n.props.agent?.trim() === tutor);
|
|
675
|
+
const count = memory ? memoryCount(memory.text) : 0;
|
|
676
|
+
if (!count)
|
|
677
|
+
return null;
|
|
678
|
+
return this.send(tutor, { from: 'system', text: tidyMemoryPrompt({ date, count, cap: MEMORY_TIDY_CAP, diaryFile: join(ws.paths.diary, `${date}.md`) }), date, tidy: true });
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* 「## 记忆」段 → 建议与要怎么改,还不写:讲课的轮每轮最多 MEMORY_MAX_PER_TURN 条(整理轮不限);回放、eval 不写。
|
|
682
|
+
* 证据对孩子在这个话题里说过的话(整理轮自成话题,对这一天的);有脸的老师写的 `孩子:` 进孩子的记忆。
|
|
683
|
+
* seen = 这轮上下文包里老师看到的那两篇的版本(消息的 notes.memory / notes.child,`路径@hash`)
|
|
684
|
+
*/
|
|
685
|
+
async planMemoryOf(ws, tutor, date, items, asked, said, clip, owner) {
|
|
686
|
+
if (asked?.replayOf || asked?.eval)
|
|
687
|
+
return { proposals: [], plan: null, child: null, warnings: [asked.eval ? 'eval 不写记忆' : '回放不写记忆'] };
|
|
688
|
+
const seenOf = (v) => {
|
|
689
|
+
const at = v?.lastIndexOf('@') ?? -1;
|
|
690
|
+
return v && at > 0 ? { path: v.slice(0, at), hash: v.slice(at + 1), clip } : undefined;
|
|
691
|
+
};
|
|
692
|
+
const seen = seenOf(asked?.notes?.memory);
|
|
693
|
+
const childSeen = seenOf(asked?.notes?.child);
|
|
694
|
+
const shared = Boolean(ws.config.tutors[tutor]) && tutor.endsWith('-tutor');
|
|
695
|
+
return planMemory(ws, { agent: owner?.agent ?? tutor, display: owner?.display ?? ws.config.tutors[tutor]?.display ?? tutor, items, max: asked?.tidy ? null : MEMORY_MAX_PER_TURN, date, said, shared, ...(seen ? { seen } : {}), ...(childSeen ? { childSeen } : {}) });
|
|
696
|
+
}
|
|
697
|
+
async spawn(ws, tutor, date, job, thread, req, plan, policy, active, lastJob) {
|
|
698
|
+
const replyMaxChars = policy.replyMaxChars;
|
|
699
|
+
const files = conversationFiles(ws.dirs.conversations, tutor, date);
|
|
700
|
+
// 埋点:从这一轮起算,首卡 = partial 板书第一次有卡(孩子端第一次看到东西),done = 进程退出,dubbed = 配音收尾
|
|
701
|
+
const t0 = Date.now();
|
|
702
|
+
const timing = { startedAt: new Date(t0).toISOString() };
|
|
703
|
+
const since = () => Date.now() - t0;
|
|
704
|
+
// 事件:每道工序的关键点发一条,追加到 events.jsonl(按序),同时给内存里的订阅者;写盘失败不影响这一轮
|
|
705
|
+
let eventsChain = Promise.resolve();
|
|
706
|
+
const events = [];
|
|
707
|
+
const emit = (e) => {
|
|
708
|
+
const event = { t: since(), ...e };
|
|
709
|
+
events.push(event);
|
|
710
|
+
eventsChain = eventsChain.then(() => appendFile(files.events(job), `${JSON.stringify(event)}\n`)).catch(() => { });
|
|
711
|
+
for (const fn of this.listeners) {
|
|
712
|
+
try {
|
|
713
|
+
fn({ tutor, date, job, event });
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
/* 订阅者的错不影响这一轮 */
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
// 讲稿不配音(嘴照念):拍关就就绪;卡自己的声音(点读段、单词)照配
|
|
721
|
+
const cardVoice = ws.config.tutors[tutor]?.voice;
|
|
722
|
+
const queue = cardVoice ? new DubQueue(cardVoice, files.err(job), { cacheDir: ws.dirs.tts, env: this.opts.env }) : null;
|
|
723
|
+
if (queue)
|
|
724
|
+
queue.report = (e) => emit(e.kind === 'queued' ? { lane: 'tts', kind: 'queued', label: e.label } : e.kind === 'done' ? { lane: 'tts', kind: 'done', label: e.label, ms: e.ms, file: e.file ?? '' } : { lane: 'tts', kind: 'failed', label: e.label, ms: e.ms, error: e.error ?? '?' });
|
|
725
|
+
let cardsSeen = 0;
|
|
726
|
+
let linesSeen = 0;
|
|
727
|
+
// 就绪(流式):重算前几拍就绪(卡等它),涨了发 ready:beat;第一拍就绪记 firstReadyMs、发 ready:speak(嘴能开口了)
|
|
728
|
+
let readySeen = 0;
|
|
729
|
+
const speak = () => {
|
|
730
|
+
if (timing.firstReadyMs !== undefined)
|
|
731
|
+
return;
|
|
732
|
+
timing.firstReadyMs = since();
|
|
733
|
+
emit({ lane: 'ready', kind: 'speak' });
|
|
734
|
+
};
|
|
735
|
+
const settleReady = () => {
|
|
736
|
+
if (!active.partial)
|
|
737
|
+
return;
|
|
738
|
+
const n = readyBeats(active.partial, { done: false });
|
|
739
|
+
if (n <= readySeen)
|
|
740
|
+
return;
|
|
741
|
+
const beats = beatsOf(active.partial);
|
|
742
|
+
for (; readySeen < n; readySeen++) {
|
|
743
|
+
speak();
|
|
744
|
+
emit({ lane: 'ready', kind: 'beat', beat: readySeen, card: beats[readySeen]?.card ?? null, first: readySeen === 0 });
|
|
745
|
+
}
|
|
746
|
+
active.partial = { ...active.partial, ready: n };
|
|
747
|
+
};
|
|
748
|
+
// 流式:脑的正文变了就(节流 150ms)重解析成 partial 板书
|
|
749
|
+
let dirty = false;
|
|
750
|
+
let timer = null;
|
|
751
|
+
const reparse = () => {
|
|
752
|
+
timer = null;
|
|
753
|
+
if (!dirty)
|
|
754
|
+
return;
|
|
755
|
+
dirty = false;
|
|
756
|
+
// 先剥「## 记账」再解析,和定稿的 deriveKidView 同一条路;不剥的话固定段写在前面的那轮流式时一张卡都出不来(2026-09-13 控制台里看见的)
|
|
757
|
+
const { section } = parseBoard(parseSections(brainRun.text()).body, { partial: true });
|
|
758
|
+
const lines = section.lines.map((l) => ({ ...l, text: truncateReply(l.text, replyMaxChars).text, audio: null }));
|
|
759
|
+
active.partial = section.cards.length || lines.length ? { cards: section.cards, lines, partial: true, ready: readySeen } : null;
|
|
760
|
+
if (section.cards.length && timing.firstCardMs === undefined)
|
|
761
|
+
timing.firstCardMs = since();
|
|
762
|
+
for (; cardsSeen < section.cards.length; cardsSeen++)
|
|
763
|
+
emit({ lane: 'main', kind: 'card', card: cardsSeen, label: `${section.cards[cardsSeen].kind} ${cardLabel(section.cards[cardsSeen])}`.trim() });
|
|
764
|
+
for (; linesSeen < lines.length; linesSeen++)
|
|
765
|
+
emit({ lane: 'main', kind: 'line', line: linesSeen, text: lines[linesSeen].text, ...lineFlags(lines[linesSeen], section.cards) });
|
|
766
|
+
settleReady();
|
|
767
|
+
};
|
|
768
|
+
// 起老师(src/brain/):预热的进程在这一拍里接过来(send 查完 busy 到这里之间没有 await);正文、工具、断流随流回报
|
|
769
|
+
const brainRun = this.brain.start(req, plan, { tutor, key: { cwd: join(ws.dirs.agents, tutor), date, lastJob }, files: { log: files.log(job), err: files.err(job), history: files.brain(thread) }, stall: policy.stall }, {
|
|
770
|
+
onStart: (s) => emit({ lane: 'main', kind: 'start', cli: s.cli, runtime: s.runtime, resume: s.resume, ...(s.warmBornAt !== undefined ? { warmMs: t0 - s.warmBornAt } : {}) }),
|
|
771
|
+
onText: () => {
|
|
772
|
+
dirty = true;
|
|
773
|
+
if (!timer)
|
|
774
|
+
timer = setTimeout(reparse, 150);
|
|
775
|
+
},
|
|
776
|
+
onTool: (tl) => emit({ lane: 'main', kind: 'tool', name: tl.name, sub: tl.sub }),
|
|
777
|
+
onStall: (s) => emit({ lane: 'main', kind: 'stall', idleMs: s.idleMs, retry: s.retry, kept: s.kept }),
|
|
778
|
+
onExit: (at) => {
|
|
779
|
+
timing.doneMs = at - t0;
|
|
780
|
+
if (timer)
|
|
781
|
+
clearTimeout(timer);
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
if (brainRun.warm)
|
|
785
|
+
timing.warm = true;
|
|
786
|
+
const { transcript, tools, warnings } = await brainRun.done;
|
|
787
|
+
emit({ lane: 'main', kind: 'exit', ok: transcript.final.ok, ...(transcript.final.reason ? { reason: transcript.final.reason } : {}), ...(transcript.final.inTokens !== undefined ? { tokens: { in: transcript.final.inTokens, out: transcript.final.outTokens ?? 0 } } : {}), ...(transcript.final.numTurns !== undefined ? { turns: transcript.final.numTurns } : {}) });
|
|
788
|
+
const kidView = deriveKidView(transcript, { replyMaxChars }, { live: active.live });
|
|
789
|
+
kidView.warnings.push(...warnings);
|
|
790
|
+
// 整块出的(没走流式):卡与句这时才第一次见到,也发一遍
|
|
791
|
+
if (kidView.section) {
|
|
792
|
+
for (; cardsSeen < kidView.section.cards.length; cardsSeen++)
|
|
793
|
+
emit({ lane: 'main', kind: 'card', card: cardsSeen, label: `${kidView.section.cards[cardsSeen].kind} ${cardLabel(kidView.section.cards[cardsSeen])}`.trim() });
|
|
794
|
+
for (; linesSeen < kidView.section.lines.length; linesSeen++)
|
|
795
|
+
emit({ lane: 'main', kind: 'line', line: linesSeen, text: kidView.section.lines[linesSeen].text, ...lineFlags(kidView.section.lines[linesSeen], kidView.section.cards) });
|
|
796
|
+
}
|
|
797
|
+
if (timing.firstReadyMs === undefined && (kidView.section?.lines.length || kidView.kidText))
|
|
798
|
+
timing.firstReadyMs = since();
|
|
799
|
+
if (kidView.section)
|
|
800
|
+
emit({ lane: 'ready', kind: 'all', cards: kidView.section.cards.length, lines: kidView.section.lines.length });
|
|
801
|
+
if (kidView.section)
|
|
802
|
+
timing.beats = beatTimings(events, beatsOf(kidView.section));
|
|
803
|
+
// 重新读索引再并入:跑的这段时间里别的字段(比如家长改了别的)不被旧对象盖掉
|
|
804
|
+
const latest = await readIndex(ws, tutor, date);
|
|
805
|
+
// 板书写法已经递到手里(系统提示或话题第一条),老师还用工具去读它 → 预载没起作用,家长端这轮上点名(换模型 / CLI / 版本后最先坏的地方)
|
|
806
|
+
const reread = takesTutorRules(tutor) && policy.board !== 'off' ? boardGuideReads(tools) : [];
|
|
807
|
+
if (reread.length)
|
|
808
|
+
kidView.warnings.push(`板书写法已经递给老师了,这轮它还是用工具去读了一遍(${reread.join(';')}):多一次模型来回。cotutor doctor 的 runtime.*.board 看这个运行时走的哪条递法`);
|
|
809
|
+
// 清单外的卡(《卡片协议.md》「谁拿到哪些卡」):照常解析、照常显示,只在这轮记一条,家长据此调清单
|
|
810
|
+
// activity 话题的卡是 activity 自己的(加 text);别的话题照老师的清单
|
|
811
|
+
const jobAt = latest.messages.findIndex((m) => m.job === job);
|
|
812
|
+
const run = jobAt >= 0 ? threadActivity(latest.messages, threads(latest.messages)[jobAt]) : null;
|
|
813
|
+
const actFile = run ? await readActivity(ws, tutor, run.name) : null;
|
|
814
|
+
const kinds = actFile ? ['text', ...actFile.cards] : tutorCardKinds(ws.config.tutors[tutor]?.cards);
|
|
815
|
+
const outside = kinds && kidView.section ? [...new Set(kidView.section.cards.map((c) => c.kind).filter((k) => !kinds.includes(k)))] : [];
|
|
816
|
+
if (outside.length)
|
|
817
|
+
kidView.warnings.push(actFile ? `板书里有这个 activity 清单外的卡:${outside.join('、')}(照常显示;要它就加进 agents/${tutor}/activities/${run.name}.md 头上的 cards)` : `板书里有清单外的卡:${outside.join('、')}(照常显示;要它就加进 cotutor.json tutors.${tutor}.cards)`);
|
|
818
|
+
let next = applyRun(latest, job, { transcript, kidView, runtime: plan.runtime, timing, tools });
|
|
819
|
+
const patch = (f) => { next = { ...next, messages: next.messages.map((m) => (m.job === job ? f(m) : m)) }; };
|
|
820
|
+
const asked = latest.messages.find((m) => m.job === job);
|
|
821
|
+
// 老师不直接写 vault,回的段由应用落盘;写了什么、没写成为什么都在这条的 warnings 里(家长视图看 remembered 与提醒)
|
|
822
|
+
// 「## 记忆」段:每条是建议,证据对不上孩子说过的不记;其余增 / 改 / 删落进这位 agent 的记忆文件,memoryLines 留给删话题时撤
|
|
823
|
+
const memory = kidView.memory.length ? await this.planMemoryOf(ws, tutor, date, kidView.memory, asked, kidSaid(latest.messages, asked?.tidy ? null : thread), policy.contextPack.entryChars, actFile && run ? { agent: `${tutor}/${run.name}`, display: `${ws.config.tutors[tutor]?.display ?? tutor}·${run.name}` } : undefined) : null;
|
|
824
|
+
if (memory?.proposals.length)
|
|
825
|
+
patch((m) => ({ ...m, memoryProposals: memory.proposals }));
|
|
826
|
+
// 记账那轮:「## 记账」段 → 这个话题在日记里的一段;写进去的原文记在这轮上,删话题时从日记里摘掉(memory/vault.ts 的 undoThreadVault)
|
|
827
|
+
const bookThread = asked?.bookkeep?.thread;
|
|
828
|
+
const t = ws.config.tutors[tutor];
|
|
829
|
+
const diary = bookThread ? planDiary({ date, index: next, thread: bookThread, bookkeeping: kidView.bookkeeping, subject: t?.subject ?? t?.display ?? tutor, keepScore: ws.config.vault.keepScore }) : null;
|
|
830
|
+
const vaultWarnings = [...(memory?.warnings ?? []), ...(diary ? ('warning' in diary ? [diary.warning] : diary.notes) : [])];
|
|
831
|
+
// 先在索引上记一笔 pending,再写 vault,写完换成结果(《运行层行为设计》§5):中间断了,下次记账、删话题前由 recoverPending 对照文件补
|
|
832
|
+
const mp = memory?.plan;
|
|
833
|
+
const cp = memory?.child;
|
|
834
|
+
const db = diary && 'block' in diary && bookThread ? { file: diary.file, block: diary.block, thread: bookThread } : null;
|
|
835
|
+
const pendingOf = (x) => ({ file: x.file, base: x.base === null ? null : textHash(x.base), hash: textHash(x.text), changes: x.changes, lines: x.lines });
|
|
836
|
+
if (mp || cp || db) {
|
|
837
|
+
const pending = { ...(mp ? { memory: pendingOf(mp) } : {}), ...(cp ? { child: pendingOf(cp) } : {}), ...(db ? { diary: db } : {}) };
|
|
838
|
+
patch((m) => ({ ...m, pending }));
|
|
839
|
+
await writeIndex(ws, next);
|
|
840
|
+
patch(({ pending: _, ...m }) => m);
|
|
841
|
+
}
|
|
842
|
+
const remembered = [];
|
|
843
|
+
for (const [x, child] of [[mp, false], [cp, true]]) {
|
|
844
|
+
if (!x)
|
|
845
|
+
continue;
|
|
846
|
+
const w = await writeVaultMemory(ws, x).catch((err) => `${child ? '孩子的记忆' : '记忆'}没写进去:${err instanceof Error ? err.message : String(err)}`);
|
|
847
|
+
if (w) {
|
|
848
|
+
vaultWarnings.push(w);
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
remembered.push(...(child ? childChanges(x.changes) : x.changes));
|
|
852
|
+
patch((m) => (child ? { ...m, childLines: x.lines } : { ...m, memoryLines: x.lines }));
|
|
853
|
+
}
|
|
854
|
+
if (remembered.length)
|
|
855
|
+
patch((m) => ({ ...m, remembered }));
|
|
856
|
+
if (db) {
|
|
857
|
+
try {
|
|
858
|
+
await writeDiaryBlock(ws, date, db.block);
|
|
859
|
+
patch((m) => ({ ...m, diaryBlock: db.block }));
|
|
860
|
+
next = { ...next, booked: { ...next.booked, [db.thread]: job } };
|
|
861
|
+
emit({ lane: 'ledger', kind: 'diary', thread: db.thread, file: db.file });
|
|
862
|
+
}
|
|
863
|
+
catch (err) {
|
|
864
|
+
vaultWarnings.push(`记账:日记没写进去:${err instanceof Error ? err.message : String(err)}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (vaultWarnings.length)
|
|
868
|
+
patch((m) => ({ ...m, warnings: [...(m.warnings ?? []), ...vaultWarnings] }));
|
|
869
|
+
await writeIndex(ws, next);
|
|
870
|
+
emit({ lane: 'index', kind: 'written', warnings: next.messages.find((m) => m.job === job)?.warnings?.length ?? 0 });
|
|
871
|
+
await eventsChain;
|
|
872
|
+
// 资产:点读段逐段配音到 .cards/<n>/<k>.mp3,后台跑,不拦着这一轮收尾;孩子端每次拉 today 都会看到新生成好的
|
|
873
|
+
if (queue && kidView.section) {
|
|
874
|
+
const jobs = [];
|
|
875
|
+
kidView.section.cards.forEach((card, n) => {
|
|
876
|
+
const list = cardAssets(card);
|
|
877
|
+
if (!list.length)
|
|
878
|
+
return;
|
|
879
|
+
jobs.push(mkdir(files.cardAssetsDir(job, n), { recursive: true }).then(() => Promise.all(list.map((a) => queue.add(files.cardAsset(job, n, a.file), a.text, `第 ${n + 1} 张卡的 ${a.file} `)))));
|
|
880
|
+
});
|
|
881
|
+
if (jobs.length) {
|
|
882
|
+
const bg = Promise.all(jobs).then(() => undefined, () => undefined);
|
|
883
|
+
this.background.add(bg);
|
|
884
|
+
void bg.finally(() => this.background.delete(bg));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// 画图卡:后台画,一张一张排着;回放不画
|
|
888
|
+
if (kidView.section?.cards.some((c) => c.kind === 'paint')) {
|
|
889
|
+
const clock = () => (this.opts.now?.() ?? new Date()).getTime();
|
|
890
|
+
const gate = await paintGate(ws, tutor, date, job, clock).catch(() => null);
|
|
891
|
+
const painter = await paintTool(ws, tutor, date, job).catch(() => null);
|
|
892
|
+
const bg = startPaints(ws, tutor, date, job, kidView.section, { draw: this.draw, now: this.opts.now, replay: Boolean(latest.messages.find((m) => m.job === job)?.replayOf), ...(gate ? { gate } : {}), painter });
|
|
893
|
+
this.background.add(bg);
|
|
894
|
+
void bg.finally(() => this.background.delete(bg));
|
|
895
|
+
}
|
|
896
|
+
return next;
|
|
897
|
+
}
|
|
898
|
+
}
|