cotutor 0.3.0 → 0.4.1

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.
Files changed (209) hide show
  1. package/LICENSE +9 -7
  2. package/NOTICE +3 -3
  3. package/README.md +65 -18
  4. package/avatars/chinese.webp +0 -0
  5. package/avatars/english.webp +0 -0
  6. package/avatars/math.webp +0 -0
  7. package/bin/cotutor.js +13 -3
  8. package/cards/photo/card.css +10 -3
  9. package/cards/photo/card.md +2 -2
  10. package/cards/tutor/card.css +13 -0
  11. package/cards/tutor/card.md +2 -1
  12. package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +1 -1
  13. package/dist/app/activity-eval.js +3 -2
  14. package/dist/app/cli/activities.d.ts +8 -1
  15. package/dist/app/cli/activities.js +42 -4
  16. package/dist/app/cli/cert.d.ts +11 -0
  17. package/dist/app/cli/cert.js +36 -9
  18. package/dist/app/cli/chalk/browser.d.ts +2 -2
  19. package/dist/app/cli/chalk/browser.js +22 -7
  20. package/dist/app/cli/chalk/main.js +3 -2
  21. package/dist/app/cli/doctor.d.ts +11 -5
  22. package/dist/app/cli/doctor.js +156 -181
  23. package/dist/app/cli/firewall.d.ts +24 -0
  24. package/dist/app/cli/firewall.js +79 -0
  25. package/dist/app/cli/init.js +8 -5
  26. package/dist/app/cli/main.js +379 -27
  27. package/dist/app/cli/migrate.d.ts +2 -11
  28. package/dist/app/cli/migrate.js +1 -59
  29. package/dist/app/cli/port-holder.d.ts +16 -0
  30. package/dist/app/cli/port-holder.js +34 -0
  31. package/dist/app/cli/port.d.ts +37 -0
  32. package/dist/app/cli/port.js +116 -0
  33. package/dist/app/cli/sandbox.d.ts +25 -0
  34. package/dist/app/cli/sandbox.js +166 -0
  35. package/dist/app/cli/serve-log.d.ts +13 -0
  36. package/dist/app/cli/serve-log.js +90 -0
  37. package/dist/app/cli/serve.d.ts +18 -4
  38. package/dist/app/cli/serve.js +78 -8
  39. package/dist/app/cli/service-windows.d.ts +50 -0
  40. package/dist/app/cli/service-windows.js +117 -0
  41. package/dist/app/cli/service.d.ts +152 -0
  42. package/dist/app/cli/service.js +443 -0
  43. package/dist/app/cli/skeleton.d.ts +1 -1
  44. package/dist/app/cli/skeleton.js +13 -13
  45. package/dist/app/cli/skills.d.ts +5 -1
  46. package/dist/app/cli/skills.js +14 -4
  47. package/dist/app/cli/tray-icons.d.ts +11 -0
  48. package/dist/app/cli/tray-icons.js +87 -0
  49. package/dist/app/cli/tray-ids.d.ts +6 -0
  50. package/dist/app/cli/tray-ids.js +6 -0
  51. package/dist/app/cli/tray-mac.d.ts +8 -0
  52. package/dist/app/cli/tray-mac.js +140 -0
  53. package/dist/app/cli/tray-run.d.ts +1 -0
  54. package/dist/app/cli/tray-run.js +42 -0
  55. package/dist/app/cli/tray-windows.d.ts +5 -0
  56. package/dist/app/cli/tray-windows.js +166 -0
  57. package/dist/app/cli/tray.d.ts +236 -0
  58. package/dist/app/cli/tray.js +670 -0
  59. package/dist/app/cli/usage.d.ts +1 -1
  60. package/dist/app/cli/usage.js +11 -6
  61. package/dist/app/dictation/route.js +3 -2
  62. package/dist/app/http/app.d.ts +5 -10
  63. package/dist/app/http/app.js +47 -98
  64. package/dist/app/http/avatars.d.ts +17 -0
  65. package/dist/app/http/avatars.js +35 -0
  66. package/dist/app/http/dev-tutor-files.js +1 -1
  67. package/dist/app/http/parent-home.d.ts +14 -0
  68. package/dist/app/http/parent-home.js +69 -6
  69. package/dist/app/http/parent-look.d.ts +2 -0
  70. package/dist/app/http/parent-look.js +151 -0
  71. package/dist/app/http/raw-view.js +3 -3
  72. package/dist/app/http/voice-preview.d.ts +4 -0
  73. package/dist/app/http/voice-preview.js +34 -0
  74. package/dist/app/machine/bailian.d.ts +0 -2
  75. package/dist/app/machine/bailian.js +5 -6
  76. package/dist/app/machine/update.d.ts +25 -0
  77. package/dist/app/machine/update.js +88 -0
  78. package/dist/app/mock/mock-live.js +1 -1
  79. package/dist/app/mock/mock.d.ts +4 -2
  80. package/dist/app/mock/mock.js +20 -12
  81. package/dist/app/replay.js +4 -4
  82. package/dist/app/skills/home-doc.js +1 -1
  83. package/dist/app/workspace.d.ts +9 -4
  84. package/dist/app/workspace.js +33 -10
  85. package/dist/app/ws/route.d.ts +8 -2
  86. package/dist/app/ws/route.js +7 -3
  87. package/dist/board/parse.d.ts +0 -2
  88. package/dist/board/parse.js +0 -6
  89. package/dist/board/presentation.d.ts +9 -15
  90. package/dist/board/presentation.js +8 -10
  91. package/dist/brain/api-tools.js +2 -1
  92. package/dist/brain/api.d.ts +12 -17
  93. package/dist/brain/api.js +16 -33
  94. package/dist/brain/index.d.ts +23 -71
  95. package/dist/brain/index.js +6 -69
  96. package/dist/brain/log.d.ts +5 -0
  97. package/dist/brain/log.js +22 -0
  98. package/dist/brain/runtime.d.ts +30 -0
  99. package/dist/brain/runtime.js +23 -0
  100. package/dist/brain/{cli/stream-json.d.ts → stream-json.d.ts} +2 -4
  101. package/dist/brain/{cli/stream-json.js → stream-json.js} +0 -4
  102. package/dist/brain/stream.d.ts +5 -5
  103. package/dist/brain/stream.js +7 -7
  104. package/dist/brain/transcript.d.ts +3 -3
  105. package/dist/brain/transcript.js +6 -6
  106. package/dist/brain/{cli/types.d.ts → types.d.ts} +3 -48
  107. package/dist/brain/types.js +14 -0
  108. package/dist/chalk/glyphs/skeleton.d.ts +3 -3
  109. package/dist/chalk/glyphs/skeleton.js +1 -1
  110. package/dist/lib/index.d.ts +1 -1
  111. package/dist/lib/index.js +1 -1
  112. package/dist/memory/diary.js +3 -3
  113. package/dist/realtime/bailian.d.ts +1 -1
  114. package/dist/realtime/bailian.js +3 -2
  115. package/dist/realtime/preview.js +1 -1
  116. package/dist/realtime/voices.d.ts +2 -0
  117. package/dist/realtime/voices.js +57 -57
  118. package/dist/runtime/activities.d.ts +13 -1
  119. package/dist/runtime/activities.js +28 -2
  120. package/dist/runtime/activity-state.js +2 -2
  121. package/dist/runtime/agent-file.d.ts +3 -3
  122. package/dist/runtime/agent-file.js +3 -3
  123. package/dist/runtime/context/tutor-rules.d.ts +2 -2
  124. package/dist/runtime/context/tutor-rules.js +3 -3
  125. package/dist/runtime/home/home.d.ts +12 -1
  126. package/dist/runtime/home/home.js +8 -1
  127. package/dist/runtime/home/store.d.ts +1 -1
  128. package/dist/runtime/home/store.js +36 -21
  129. package/dist/runtime/journal/mix.d.ts +15 -6
  130. package/dist/runtime/journal/mix.js +60 -19
  131. package/dist/runtime/journal/recorder.d.ts +58 -13
  132. package/dist/runtime/journal/recorder.js +106 -16
  133. package/dist/runtime/journal/store.d.ts +4 -4
  134. package/dist/runtime/journal/store.js +6 -6
  135. package/dist/runtime/kid-input.js +2 -3
  136. package/dist/runtime/live/call.d.ts +12 -0
  137. package/dist/runtime/live/call.js +51 -18
  138. package/dist/runtime/live/turns.js +1 -1
  139. package/dist/runtime/runner.d.ts +9 -55
  140. package/dist/runtime/runner.js +52 -173
  141. package/dist/scenes/paint/painter.js +4 -3
  142. package/dist/schema/config.d.ts +43 -22
  143. package/dist/schema/config.js +45 -47
  144. package/dist/schema/conversation.d.ts +6 -0
  145. package/dist/schema/home.d.ts +11 -1
  146. package/dist/schema/home.js +7 -2
  147. package/dist/schema/workspace.d.ts +27 -0
  148. package/dist/schema/workspace.js +28 -0
  149. package/dist/shared/default-port.d.ts +2 -0
  150. package/dist/shared/default-port.js +2 -0
  151. package/dist/shared/qianwen.d.ts +14 -10
  152. package/dist/shared/qianwen.js +26 -22
  153. package/dist/shared/site-url.d.ts +2 -0
  154. package/dist/shared/site-url.js +5 -0
  155. package/dist/shared/upgrade.d.ts +9 -0
  156. package/dist/shared/upgrade.js +23 -0
  157. package/dist/ui/dev/dev-page.js +25 -252
  158. package/dist/ui/dev/dev-theme.d.ts +1 -1
  159. package/dist/ui/dev/dev-theme.js +1 -1
  160. package/dist/ui/dev/voice-gallery-page.js +1 -1
  161. package/dist/ui/kid/board-mark.d.ts +2 -2
  162. package/dist/ui/kid/board-mark.js +2 -2
  163. package/dist/ui/kid/home-tiles.d.ts +14 -0
  164. package/dist/ui/kid/home-tiles.js +44 -0
  165. package/dist/ui/kid/kid-page.js +68 -103
  166. package/dist/ui/kid/voice-ui.d.ts +1 -1
  167. package/dist/ui/kid/voice-ui.js +1 -1
  168. package/dist/ui/kid/word-emoji.js +5 -3
  169. package/dist/ui/parent/parent-home-page.js +337 -7
  170. package/dist/ui/parent/parent-page.js +6 -2
  171. package/dist/ui/parent/parent-settings-page.d.ts +1 -0
  172. package/dist/ui/parent/parent-settings-page.js +133 -0
  173. package/dist/ui/{dev → parent}/qr-page.d.ts +2 -2
  174. package/dist/ui/{dev → parent}/qr-page.js +19 -14
  175. package/dist/ui/shared/brand.d.ts +3 -1
  176. package/dist/ui/shared/brand.js +3 -1
  177. package/grades/grade-2/README.md +1 -1
  178. package/grades/grade-2/VALIDATION.md +1 -1
  179. package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +1 -1
  180. package/grades/grade-2/agents/chinese-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
  181. package/grades/grade-2/agents/chinese-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
  182. package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +1 -1
  183. package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +1 -1
  184. package/grades/grade-2/agents/english-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
  185. package/grades/grade-2/agents/english-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
  186. package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +1 -1
  187. package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +1 -1
  188. package/grades/grade-2/agents/math-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
  189. package/grades/grade-2/agents/math-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
  190. package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +1 -1
  191. package/package.json +6 -2
  192. package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +2 -2
  193. package/skills/cotutor-board/references/photo.md +2 -2
  194. package/skills/cotutor-home/SKILL.md +1 -0
  195. package/skills/cotutor-home/references/tutor.md +2 -1
  196. package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +2 -1
  197. package/skills/cotutor-tune/references//345/255/227/346/256/265.md +6 -3
  198. package/skills/cotutor-tutor/SKILL.md +5 -13
  199. package/dist/brain/cli/adapters.d.ts +0 -60
  200. package/dist/brain/cli/adapters.js +0 -91
  201. package/dist/brain/cli/qwen.d.ts +0 -11
  202. package/dist/brain/cli/qwen.js +0 -141
  203. package/dist/brain/cli/run-plan.d.ts +0 -59
  204. package/dist/brain/cli/run-plan.js +0 -71
  205. package/dist/brain/cli/run.d.ts +0 -35
  206. package/dist/brain/cli/run.js +0 -256
  207. package/dist/brain/cli/types.js +0 -3
  208. package/dist/brain/cli/warm.d.ts +0 -53
  209. package/dist/brain/cli/warm.js +0 -103
@@ -7,7 +7,7 @@
7
7
  * 没念的拍丢掉,孩子听到第几句(src/runtime/delivery/playback.ts 按声音排到哪算)进下一轮的上下文包 live:。嘴只接住、只照念,不讲新内容(2026-10-19 P1);画板开着时例外(边画边聊,见下)。
8
8
  *
9
9
  * 孩子端 → 这里:二进制帧 = PCM16 16k 单声道一块(按着时才推);文本帧 = JSON
10
- * {t:'hold'} 按下 · {t:'release', body} 松手(body 同 POST /api/kid/conversations/<老师>/messages 少 text:thread / newThread / device…)
10
+ * {t:'hold'} 按下 · {t:'release', body} 松手(body 同 send 的那条少 text:thread / newThread / device…,runner.accept 验)
11
11
  * {t:'hush'} 停下不开新一轮(上滑取消):嘴在出就掐、孩子端清了、没念的丢掉
12
12
  * {t:'send', body} 不是说的(打字、拍照、交卡、首页按钮;舞台开着带 focus.card):body 同那条 POST · {t:'view', w, h} 屏幕多大 · {t:'bye'}
13
13
  * {t:'staged'} 讲稿 [[play]] 的小课堂那一段放完了(或放不了、被关):后面的讲稿接着交给嘴
@@ -31,12 +31,15 @@
31
31
  * 孩子说完(像跟读,src/scenes/talk/warmup.ts)开闸下一拍,不接住、不交脑;对老师说的照收下办(打断、接住、交脑);ECHO_WAIT_MS 没开口接着走,连着两拍没开口停下交脑(app: quiet)。
32
32
  * 每拍记进摸底(录像 {t:'echo'},只记不发),交脑时带上(live.warmup)。[[call]] 那句念完孩子端响铃,这里马上交脑一轮(app: call,不接住),
33
33
  * 之后的接住换英文(cue.ts 的 CATCH_EN),交脑带打电话几分钟(live.phone)。孩子端连上发来的第一条带 talk(口语单)时嘴先说「好,我们先热热身!」。
34
- * 录像:进出的消息、两边的声音都记(CallRecorder),挂了落在 conversations/<老师>/<日期>.live/<时分秒>.reel.json / .wav。
34
+ * 录像:进出的消息、两边的声音都记(CallRecorder),落在 conversations/<老师>/<日期>.live/<时分秒>.reel.json / .wav(时分秒 = 这一段开始的时刻)。
35
+ * 按段落(2026-11-06):两轮之间安静着(心跳时看),这一段 IDLE_CUT_MS 没动静、或录了 MAX_TAKE_MS,就收下这一段落盘、接着录新的一段;挂了落最后一段。
36
+ * 一段一轮都不会切开(录像按轮在一段里找按下、松手、turn、read);没动静的一段不落。
35
37
  * 字幕重放:嘴照念的声音按轮另留一份,出了的都留(孩子按下时清掉、没放出来的也在),
36
38
  * 一段出完(或被掐、断了)就落 <日期>.<job>.mouth.wav 与每句的起点 .mouth.json(src/runtime/journal/mouth-index.ts);接住那句不在里面。孩子端从 /api/kid/conversations/<老师>/mouth/<日期>/<job> 取。
37
39
  * 事件:嘴这一道(mouth:按下、松手 / 发了、接住开始放、第几句开始放、打断)补进那一轮的 events.jsonl(Runner.noteEvent),工作台的时间线上看;
38
40
  * 按下、松手、接住在脑起来之前,先攒着,等那一轮的老师进程起来(main:start)再按它的起点换成相对毫秒(负的)。
39
41
  */
42
+ import { existsSync } from 'node:fs';
40
43
  import { mkdir, writeFile } from 'node:fs/promises';
41
44
  import { join } from 'node:path';
42
45
  import { localDate } from "../../shared/time.js";
@@ -50,7 +53,7 @@ import { wavOfPcm } from "../../shared/wav.js";
50
53
  import { mouthInstructions } from "../../realtime/prompt.js";
51
54
  import { mouthVoice } from "../../schema/index.js";
52
55
  import { INPUT_RATE, MAX_IMAGE_B64, OUTPUT_RATE, RealtimeSession } from "../../realtime/bailian.js";
53
- import { CallRecorder } from "../journal/recorder.js";
56
+ import { CallRecorder, renderTake } from "../journal/recorder.js";
54
57
  import { MouthEvents, MouthTakes } from "../journal/mouth.js";
55
58
  import { Turns } from "./turns.js";
56
59
  /** 嘴接住的那句最多等多久再把孩子的话交给脑(接住的字一般 1 秒内齐;脑知道嘴说了什么,第一句就不再重复) */
@@ -77,6 +80,12 @@ const IDLE_SWAP_MS = 240_000;
77
80
  /** 热身:[[echo]] / [[say]] 那句在孩子端放完,这么久没开口就接着下一拍(不催);连着这么多拍没开口就停下交脑 */
78
81
  const ECHO_WAIT_MS = 8000;
79
82
  const QUIET_BEATS = 2;
83
+ /**
84
+ * 录像切段:这一段这么久没动静、或录了这么久(两轮之间安静着才切),就收下落盘、接着录新的一段。
85
+ * 页面开着不关能挂几个钟头(2026-10-25 一段 2 小时、361 MB 的 wav,几乎没说话):不切的话声音全攒在内存里,挂断时一起混
86
+ */
87
+ const IDLE_CUT_MS = 5 * 60_000;
88
+ const MAX_TAKE_MS = 30 * 60_000;
80
89
  /** 打电话:[[call]] 那句放完先响这么久的铃,脑的英文开场来得再早也等它(孩子听得到在打电话) */
81
90
  const RING_MS = 3000;
82
91
  /**
@@ -170,6 +179,8 @@ export class LiveCall {
170
179
  chatQuiet = 0;
171
180
  chatQuietUntil = 0;
172
181
  offEvents = null;
182
+ /** 录像一段一段排着落盘(按顺序、一次混一段);挂了等它们都落完 */
183
+ saving = Promise.resolve();
173
184
  ended = false;
174
185
  sock;
175
186
  deps;
@@ -526,6 +537,7 @@ export class LiveCall {
526
537
  keepAlive() {
527
538
  const s = this.session;
528
539
  this.maybeSwap();
540
+ this.maybeCut();
529
541
  if (!s?.isReady || this.holding || this.vadOn || s.responding || Date.now() - this.lastActive > (this.deps.keepAliveMs ?? KEEP_ALIVE_MS))
530
542
  return;
531
543
  // 一直听、声音一直在推:本来就有输入,不用跳
@@ -563,6 +575,32 @@ export class LiveCall {
563
575
  this.send({ t: 'flush' });
564
576
  }
565
577
  }
578
+ /**
579
+ * 录像切段:两轮之间安静着(孩子没在说、嘴没在说也没在放、脑没在写、没在等转写),这一段没动静够久了或录得够长了,就收下落盘。
580
+ * 一轮不会被切开:录像按轮在一段里找按下、松手、turn 和 read
581
+ */
582
+ maybeCut() {
583
+ if (this.ended || !this.quiet() || this.playing() || this.pendingVoice || this.draft || this.echoing || this.chatHeard !== null)
584
+ return;
585
+ const idle = this.rec.quietMs() >= (this.deps.idleCutMs ?? IDLE_CUT_MS);
586
+ const long = this.rec.stirred && this.rec.takeMs() >= (this.deps.maxTakeMs ?? MAX_TAKE_MS);
587
+ if (idle || long)
588
+ this.saveTake(this.rec.cut());
589
+ }
590
+ /** 落一段录像(排在前面那几段后面):没动静的、只有连上那两条的不落 */
591
+ saveTake(raw) {
592
+ if (!raw.stirred || raw.msgs.length <= 2)
593
+ return;
594
+ const { ctx, tutor } = this.deps;
595
+ this.saving = this.saving.then(async () => {
596
+ const take = await renderTake(raw);
597
+ const dir = join(ctx.runner.ws.dirs.conversations, tutor, `${localDate(raw.startedAt)}.live`);
598
+ await mkdir(dir, { recursive: true });
599
+ const name = takeName(dir, raw.startedAt);
600
+ await writeFile(join(dir, `${name}.wav`), wavOfPcm(take.callPcm, MIX_RATE));
601
+ await writeFile(join(dir, `${name}.reel.json`), JSON.stringify(take.reel));
602
+ }).catch((err) => { console.error('live: 录像落盘失败', err); });
603
+ }
566
604
  /** 孩子的一条文本消息 */
567
605
  onText(raw) {
568
606
  if (this.ended)
@@ -1155,21 +1193,8 @@ export class LiveCall {
1155
1193
  if (this.said?.kind === 'read')
1156
1194
  this.mouthEnd(this.said, false);
1157
1195
  await this.takes.flush();
1158
- try {
1159
- const take = this.rec.finish();
1160
- if (take.reel.msgs.length > 2) {
1161
- const { ctx, tutor } = this.deps;
1162
- const at = this.rec.startedAt;
1163
- const dir = join(ctx.runner.ws.dirs.conversations, tutor, `${localDate(at)}.live`);
1164
- const name = [at.getHours(), at.getMinutes(), at.getSeconds()].map((n) => String(n).padStart(2, '0')).join('');
1165
- await mkdir(dir, { recursive: true });
1166
- await writeFile(join(dir, `${name}.wav`), wavOfPcm(take.callPcm, MIX_RATE));
1167
- await writeFile(join(dir, `${name}.reel.json`), JSON.stringify(take.reel));
1168
- }
1169
- }
1170
- catch (err) {
1171
- console.error('live: 录像落盘失败', err);
1172
- }
1196
+ this.saveTake(this.rec.finish());
1197
+ await this.saving;
1173
1198
  this.sock.close(1000, why);
1174
1199
  this.deps.onFinished?.(why);
1175
1200
  }
@@ -1178,6 +1203,14 @@ export class LiveCall {
1178
1203
  void this.end('kid-gone'); }
1179
1204
  }
1180
1205
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
1206
+ /** 一段录像的文件名:开始的时分秒;这一秒已经有一段了(同一位老师两次通话、切段撞在同一秒)往后挪一秒,不盖掉 */
1207
+ const takeName = (dir, at) => {
1208
+ const hms = (d) => [d.getHours(), d.getMinutes(), d.getSeconds()].map((n) => String(n).padStart(2, '0')).join('');
1209
+ let name = hms(at);
1210
+ for (let k = 1; k < 60 && existsSync(join(dir, `${name}.reel.json`)); k++)
1211
+ name = hms(new Date(at.getTime() + k * 1000));
1212
+ return name;
1213
+ };
1181
1214
  /** 孩子端发来的图(data:image/jpeg;base64,…)→ 字节;不是 jpeg、太大(百炼一帧上限)回 null */
1182
1215
  const jpegOf = (v) => {
1183
1216
  const m = typeof v === 'string' ? /^data:image\/jpeg;base64,([A-Za-z0-9+/=]+)$/.exec(v) : null;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 实时那一轮交给脑(从 call.ts 拆出来,2026-10-29,行为不变;以后是 runtime/turn):
3
- * 孩子端发来的那条验过、拼好(runner.accept,和 HTTP 共用),带上这一轮实时的那些(live:)交给 runner.send;
3
+ * 孩子端发来的那条验过、拼好(runner.accept;孩子端只走这条路,HTTP 的发 2026-11-04 删了),带上这一轮实时的那些(live:)交给 runner.send;
4
4
  * 这位老师别处正在写(BusyError)就排着,它写完把排着的几句合并成一条再发;脑在写时孩子又说的(submit 时 running)也排着,这一轮写完一起发。
5
5
  * 一轮写完,收尾时把流式没发出来的讲稿从正式的一节补上(整块出的运行时、最后几句)。
6
6
  */
@@ -11,9 +11,8 @@
11
11
  * 进程 cwd 是老师目录 agents/<name>/。
12
12
  */
13
13
  import { type IncomingMark } from './lecture.ts';
14
- import { type BrainExecutor, type Slot } from '../brain/index.ts';
14
+ import { type BrainExecutor, type RunPlan } from '../brain/index.ts';
15
15
  import { type BoardSection, type Device } from '../board/presentation.ts';
16
- import { type RunPlan } from '../brain/cli/run-plan.ts';
17
16
  import { type RunEvent, type RunEventEnvelope } from '../schema/index.ts';
18
17
  import { type ContextPack, type ConversationIndex, type ActivityRun, type ConversationMessage, type Focus, type MessageFrom, type MessageVia } from '../schema/index.ts';
19
18
  import type { Workspace } from '../schema/index.ts';
@@ -59,7 +58,7 @@ export interface SendInput {
59
58
  drawing?: NonNullable<ConversationMessage['drawing']>;
60
59
  /** 这条是回放(app/replay.ts):原轮的 job,记进消息;调用方已经把 Runner 指到 evals/ */
61
60
  replayOf?: string;
62
- /** cotutor activity eval 跑的一轮:不写记忆、不预热(画照画,画家由 eval 换成假的) */
61
+ /** cotutor activity eval 跑的一轮:不写记忆(画照画,画家由 eval 换成假的) */
63
62
  eval?: true;
64
63
  /** 孩子看完小课堂后的第一条:上下文包带 lecture:(课的每句、看的情况、画过的几处),消息记 lecture;again = 问过以后再看一遍又画了 */
65
64
  lecture?: {
@@ -130,58 +129,35 @@ export declare function attachActivity(ws: Workspace, tutor: string, pack: Conte
130
129
  thread: string;
131
130
  now: number;
132
131
  }, inSystem?: boolean): Promise<string[]>;
133
- /** 按老师裁的板书写法落在哪(相对 workspace 根;机器文件,内容变了文件名跟着变) */
134
- export declare const TUTOR_BOARD_DIR = ".cotutor/board";
135
132
  /** activity 在系统提示里时,上下文包 activityGuide 那一行 */
136
133
  export declare const ACTIVITY_IN_SYSTEM = "\u5DF2\u5728\u4F60\u7684\u7CFB\u7EDF\u63D0\u793A\u91CC(\u8FD9\u4E2A\u8BDD\u9898\u7684 activity,\u7167\u5B83\u5E26)";
137
134
  /**
138
135
  * activity 话题的系统提示(九点五):不读老师文件,人设与做法都是 activity 自己的。
139
136
  * body 「老师怎么带」按参数与这个话题的卡裁好、剥了注释
140
137
  * kinds 这个话题的卡:activity 头上的 cards 加 text(不并老师的)
141
- * guide 只讲这几种卡的板书写法,写到 .cotutor/board/
138
+ * guide 只讲这几种卡的板书写法
142
139
  * memory 这个 activity 自己的记忆:agent: <老师>/<activity>,文件名 <老师显示名>·<activity>
143
140
  * activity 文件不在、没有「老师怎么带」→ null(退回老师文件)。
144
141
  */
145
142
  export interface ActivityParts {
146
143
  body: string;
147
144
  kinds: string[];
148
- guide: {
149
- body: string;
150
- path: string;
151
- };
145
+ guide: string;
152
146
  memory: {
153
147
  agent: string;
154
148
  display: string;
155
149
  };
156
150
  }
157
151
  export declare function activityParts(ws: Workspace, tutor: string, run: ActivityRun): Promise<ActivityParts | null>;
158
- /**
159
- * 这位老师的板书写法:正文 + 相对 workspace 根的路径(给 {boardFile} 与上下文包)。
160
- * 没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor),写到 .cotutor/board/<老师>-<hash>.md,
161
- * 文件名带内容的 hash:清单一改路径就变,预热进程的 argv 对不上,不会拿着旧写法去答。读不到 SKILL.md → null。
162
- */
163
- export declare function boardGuideOf(ws: Workspace, tutor: string): Promise<{
164
- body: string;
165
- path: string;
166
- } | null>;
167
- /**
168
- * 板书写法怎么递给老师(2026-09-19):有脸的老师、板书没关才带。运行时预载了 → 上下文包只写一行事实;
169
- * 没预载 → 原文作为一条笔记放进话题第一条(<cotutor-board>),排在守则后面,续话题没改过写「未变」。返回板书写法(给 {systemBody} 与 {boardFile})。
170
- */
171
- export declare function attachBoardGuide(ws: Workspace, tutor: string, pack: ContextPack, o: {
172
- preloaded: boolean;
173
- boardOff: boolean;
174
- }): Promise<{
175
- body: string;
176
- path: string;
177
- } | null>;
152
+ /** 这位老师的板书写法:没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor)。读不到 SKILL.md → null */
153
+ export declare function boardGuideOf(ws: Workspace, tutor: string): Promise<string | null>;
178
154
  /** 这轮带的笔记版本:role → `路径@hash`(记进消息;同一话题下一轮对得上就不再带原文) */
179
155
  export declare function noteVersions(pack: ContextPack): Record<string, string>;
180
156
  /** 续会话时:上一轮这个话题已经带过、版本没变的笔记去掉原文,YAML 里注明「未变」 */
181
157
  export declare function dropSeenNotes(pack: ContextPack, seen: Record<string, string>): ContextPack;
182
158
  /** 这个话题里最近一次记下的笔记版本(新话题、没记过 → {}) */
183
159
  export declare function seenNotes(messages: readonly ConversationMessage[], thread: string): Record<string, string>;
184
- /** 老师该读得到的目录:workspace 根(作业照片、技能)与 vault(教材、日记);会话 cwd 是老师目录,这些在它外面(qwen 读外面要先划进来) */
160
+ /** 只读工具读得到的目录:workspace 根(作业照片、技能)与 vault(教材、日记) */
185
161
  export declare function readDirsOf(ws: Workspace): string[];
186
162
  /** 干跑:现在给这位老师发这句话,上下文包会是什么样、每段从哪来、截了多少;不起模型、不写盘 */
187
163
  export declare function packDryRun(ws: Workspace, tutor: string, input: {
@@ -205,16 +181,9 @@ export declare class Runner {
205
181
  private readonly listeners;
206
182
  /** noteEvent 每个事件文件一条追加链 */
207
183
  private readonly notes;
208
- /** 脑执行:起老师、读输出、断流接着跑、预热下一轮(src/brain/) */
184
+ /** 脑执行:请模型、读输出(src/brain/) */
209
185
  readonly brain: BrainExecutor;
210
- /** 预热池的旧入口(测试看有没有、杀掉);迁完删 */
211
- get spares(): {
212
- has(tutor: string, slot?: Slot): boolean;
213
- drop(tutor: string, slot?: Slot): void;
214
- };
215
- /** 页面拉今天时的预热:每位老师(与带的话题)上次看的时刻 */
216
- private readonly prewarmedAt;
217
- /** 起子进程用的环境(测试注入) */
186
+ /** key、假端点从哪读的环境(测试注入) */
218
187
  get env(): NodeJS.ProcessEnv | undefined;
219
188
  /** 当前的 workspace(cotutor.json 热重载后是新的;实时老师经它拿) */
220
189
  get ws(): Workspace;
@@ -223,8 +192,6 @@ export declare class Runner {
223
192
  constructor(getWs: () => Workspace, opts?: {
224
193
  now?: () => Date;
225
194
  env?: NodeJS.ProcessEnv;
226
- warmIdleMs?: number;
227
- warm?: boolean;
228
195
  draw?: Draw;
229
196
  });
230
197
  /** 孩子端发一条的验与拼(HTTP 与实时老师共用,./kid-input.ts):拼成 send 的入参,或回 reject;不起老师 */
@@ -239,19 +206,6 @@ export declare class Runner {
239
206
  onEvent(fn: (e: RunEventEnvelope) => void): () => void;
240
207
  /** 别处给某一轮补事件(实时老师的嘴,src/runtime/live/call.ts;t 已经换成离这一轮老师进程起来的毫秒):追加到 events.jsonl、给订阅者;这一轮跑完了也行 */
241
208
  noteEvent(tutor: string, date: string, job: string, event: RunEvent): Promise<void>;
242
- /** 杀掉所有预热的进程(测试收尾;服务退出时它们的 stdin 断了会自己退) */
243
- close(): void;
244
- /**
245
- * 预热:给这位老师起好下一轮的进程,两个位置都补齐——
246
- * fresh:新开会话(新话题、接着上次都用它);
247
- * resume:接 thread(页面选着的话题;不给或今天没有 = 当天末条所在的话题,和 send 缺省一样)的会话,它没有会话就不留。
248
- * 只给有脸的、开着的、运行时 stdin: "stream-json"、这会儿没在跑的老师起;throttleMs 内同一话题看过就不再看(页面轮询今天)。
249
- * 起了返回 true;起不了不报错,下一轮冷起。warm: false(cotutor send:跑完就退,留着备用进程会把它挂一小时)不起。
250
- */
251
- prewarm(tutor: string, opts?: {
252
- throttleMs?: number;
253
- thread?: string;
254
- }): Promise<boolean>;
255
209
  /** 正在回的那条已经出来的板书(流式);没在跑或还没出卡 → null */
256
210
  partial(tutor: string): BoardSection | null;
257
211
  send(tutor: string, input: SendInput): Promise<SendStarted>;