cotutor 0.3.0 → 0.4.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.
Files changed (205) hide show
  1. package/LICENSE +9 -7
  2. package/NOTICE +3 -3
  3. package/README.md +36 -13
  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 +5 -0
  17. package/dist/app/cli/cert.js +20 -7
  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 +6 -5
  22. package/dist/app/cli/doctor.js +107 -171
  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 +347 -24
  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 +138 -0
  42. package/dist/app/cli/service.js +429 -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 +33 -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 +205 -0
  58. package/dist/app/cli/tray.js +618 -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 +45 -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/mock/mock-live.js +1 -1
  77. package/dist/app/mock/mock.d.ts +4 -2
  78. package/dist/app/mock/mock.js +20 -12
  79. package/dist/app/replay.js +4 -4
  80. package/dist/app/skills/home-doc.js +1 -1
  81. package/dist/app/workspace.d.ts +9 -4
  82. package/dist/app/workspace.js +33 -10
  83. package/dist/app/ws/route.d.ts +8 -2
  84. package/dist/app/ws/route.js +7 -3
  85. package/dist/board/parse.d.ts +0 -2
  86. package/dist/board/parse.js +0 -6
  87. package/dist/board/presentation.d.ts +9 -15
  88. package/dist/board/presentation.js +8 -10
  89. package/dist/brain/api-tools.js +2 -1
  90. package/dist/brain/api.d.ts +12 -17
  91. package/dist/brain/api.js +16 -33
  92. package/dist/brain/index.d.ts +23 -71
  93. package/dist/brain/index.js +6 -69
  94. package/dist/brain/log.d.ts +5 -0
  95. package/dist/brain/log.js +22 -0
  96. package/dist/brain/runtime.d.ts +30 -0
  97. package/dist/brain/runtime.js +23 -0
  98. package/dist/brain/{cli/stream-json.d.ts → stream-json.d.ts} +2 -4
  99. package/dist/brain/{cli/stream-json.js → stream-json.js} +0 -4
  100. package/dist/brain/stream.d.ts +5 -5
  101. package/dist/brain/stream.js +7 -7
  102. package/dist/brain/transcript.d.ts +3 -3
  103. package/dist/brain/transcript.js +6 -6
  104. package/dist/brain/{cli/types.d.ts → types.d.ts} +3 -48
  105. package/dist/brain/types.js +14 -0
  106. package/dist/chalk/glyphs/skeleton.d.ts +3 -3
  107. package/dist/chalk/glyphs/skeleton.js +1 -1
  108. package/dist/lib/index.d.ts +1 -1
  109. package/dist/lib/index.js +1 -1
  110. package/dist/memory/diary.js +3 -3
  111. package/dist/realtime/bailian.d.ts +1 -1
  112. package/dist/realtime/bailian.js +3 -2
  113. package/dist/realtime/preview.js +1 -1
  114. package/dist/realtime/voices.d.ts +2 -0
  115. package/dist/realtime/voices.js +57 -57
  116. package/dist/runtime/activities.d.ts +13 -1
  117. package/dist/runtime/activities.js +28 -2
  118. package/dist/runtime/activity-state.js +2 -2
  119. package/dist/runtime/agent-file.d.ts +3 -3
  120. package/dist/runtime/agent-file.js +3 -3
  121. package/dist/runtime/context/tutor-rules.d.ts +2 -2
  122. package/dist/runtime/context/tutor-rules.js +3 -3
  123. package/dist/runtime/home/home.d.ts +12 -1
  124. package/dist/runtime/home/home.js +8 -1
  125. package/dist/runtime/home/store.d.ts +1 -1
  126. package/dist/runtime/home/store.js +36 -21
  127. package/dist/runtime/journal/mix.d.ts +15 -6
  128. package/dist/runtime/journal/mix.js +60 -19
  129. package/dist/runtime/journal/recorder.d.ts +58 -13
  130. package/dist/runtime/journal/recorder.js +106 -16
  131. package/dist/runtime/journal/store.d.ts +4 -4
  132. package/dist/runtime/journal/store.js +6 -6
  133. package/dist/runtime/kid-input.js +2 -3
  134. package/dist/runtime/live/call.d.ts +12 -0
  135. package/dist/runtime/live/call.js +51 -18
  136. package/dist/runtime/live/turns.js +1 -1
  137. package/dist/runtime/runner.d.ts +9 -55
  138. package/dist/runtime/runner.js +52 -173
  139. package/dist/scenes/paint/painter.js +4 -3
  140. package/dist/schema/config.d.ts +43 -22
  141. package/dist/schema/config.js +45 -47
  142. package/dist/schema/conversation.d.ts +6 -0
  143. package/dist/schema/home.d.ts +11 -1
  144. package/dist/schema/home.js +7 -2
  145. package/dist/schema/workspace.d.ts +27 -0
  146. package/dist/schema/workspace.js +28 -0
  147. package/dist/shared/default-port.d.ts +2 -0
  148. package/dist/shared/default-port.js +2 -0
  149. package/dist/shared/qianwen.d.ts +14 -10
  150. package/dist/shared/qianwen.js +26 -22
  151. package/dist/shared/site-url.d.ts +2 -0
  152. package/dist/shared/site-url.js +5 -0
  153. package/dist/ui/dev/dev-page.js +25 -252
  154. package/dist/ui/dev/dev-theme.d.ts +1 -1
  155. package/dist/ui/dev/dev-theme.js +1 -1
  156. package/dist/ui/dev/voice-gallery-page.js +1 -1
  157. package/dist/ui/kid/board-mark.d.ts +2 -2
  158. package/dist/ui/kid/board-mark.js +2 -2
  159. package/dist/ui/kid/home-tiles.d.ts +14 -0
  160. package/dist/ui/kid/home-tiles.js +44 -0
  161. package/dist/ui/kid/kid-page.js +68 -103
  162. package/dist/ui/kid/voice-ui.d.ts +1 -1
  163. package/dist/ui/kid/voice-ui.js +1 -1
  164. package/dist/ui/kid/word-emoji.js +5 -3
  165. package/dist/ui/parent/parent-home-page.js +315 -7
  166. package/dist/ui/parent/parent-page.js +6 -2
  167. package/dist/ui/parent/parent-settings-page.d.ts +1 -0
  168. package/dist/ui/parent/parent-settings-page.js +133 -0
  169. package/dist/ui/{dev → parent}/qr-page.d.ts +2 -2
  170. package/dist/ui/{dev → parent}/qr-page.js +19 -14
  171. package/dist/ui/shared/brand.d.ts +3 -1
  172. package/dist/ui/shared/brand.js +3 -1
  173. package/grades/grade-2/README.md +1 -1
  174. package/grades/grade-2/VALIDATION.md +1 -1
  175. package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +1 -1
  176. 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
  177. 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
  178. package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +1 -1
  179. package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +1 -1
  180. 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
  181. 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
  182. package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +1 -1
  183. package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +1 -1
  184. 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
  185. 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
  186. package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +1 -1
  187. package/package.json +2 -1
  188. package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +2 -2
  189. package/skills/cotutor-board/references/photo.md +2 -2
  190. package/skills/cotutor-home/SKILL.md +1 -0
  191. package/skills/cotutor-home/references/tutor.md +2 -1
  192. package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +2 -1
  193. package/skills/cotutor-tune/references//345/255/227/346/256/265.md +6 -3
  194. package/skills/cotutor-tutor/SKILL.md +5 -13
  195. package/dist/brain/cli/adapters.d.ts +0 -60
  196. package/dist/brain/cli/adapters.js +0 -91
  197. package/dist/brain/cli/qwen.d.ts +0 -11
  198. package/dist/brain/cli/qwen.js +0 -141
  199. package/dist/brain/cli/run-plan.d.ts +0 -59
  200. package/dist/brain/cli/run-plan.js +0 -71
  201. package/dist/brain/cli/run.d.ts +0 -35
  202. package/dist/brain/cli/run.js +0 -256
  203. package/dist/brain/cli/types.js +0 -3
  204. package/dist/brain/cli/warm.d.ts +0 -53
  205. package/dist/brain/cli/warm.js +0 -103
@@ -1,24 +1,20 @@
1
1
  /**
2
2
  * 脑执行:一轮教学里「让脑想、写出正文」的那一步。
3
- * runner 拼好上下文包与系统提示交到这里;这里起老师(或接预热好的)、读输出、断流接着跑,随流回报正文与工具,收尾给出整轮的转录。
3
+ * runner 拼好上下文包与系统提示交到这里;这里请模型(应用直连百炼,./api.ts)、读输出,随流回报正文与工具,收尾给出整轮的转录。
4
4
  * 执行完不等于这一轮交付完:板书解析、配音、记忆与日记的收尾归 runner。
5
- * 两种实现,按 cotutor.json 里这轮的运行时分:{run, resume} 起 CLI(./cli/run.ts,各家方言在 ./cli/ 的适配器);{api, model} 应用直连模型(./api.ts)。
5
+ * 2026-11-02 以前还能起 Qwen Code(命令行);那时的轮次照样读得出(./log.ts),运行时用不了了(./runtime.ts)。
6
6
  */
7
- import { type CliEvent } from './cli/adapters.ts';
8
- import { type RunPlan } from './cli/run-plan.ts';
9
7
  import type { ToolCall, Transcript } from './transcript.ts';
10
- import { type CotutorConfig, type Policy, type Runtime } from '../schema/index.ts';
11
- import type { ToolSet } from './cli/adapters.ts';
12
- import type { Slot } from './cli/warm.ts';
13
- export type { ToolSet } from './cli/adapters.ts';
14
- export type { Slot } from './cli/warm.ts';
15
- /**
16
- * CLI 那一家给外面用的几样(经 brain 的出口给,外面不直接引 clis/):工具名属于哪一类(家长端看原文、板书写法有没有被读);
17
- * 出厂的运行时模板(init 写 cotutor.json);doctor 体检 CLI 运行时用的(退役没有、适配器、读输出、备机器文件、起进程的环境)
18
- */
19
- export { adapterFor, factoryRuntimes, parserOf, prepareCli, processEnv, runtimeIssue, toolKind, type ToolKind } from './cli/adapters.ts';
8
+ import type { CotutorConfig, Policy } from '../schema/index.ts';
9
+ import type { ToolSet } from './types.ts';
10
+ import type { RunPlan } from './runtime.ts';
11
+ import { type BrainLine } from './api.ts';
12
+ export type { ToolKind, ToolSet } from './types.ts';
13
+ export { toolKind } from './types.ts';
14
+ export { parseLogLine } from './log.ts';
15
+ export { RuntimeError, getRuntime, runtimeIssue, type RunPlan } from './runtime.ts';
20
16
  export { FACTORY_API_RUNTIMES } from './api.ts';
21
- /** 一轮要脑做的:哪个运行时、接哪个会话、系统提示的几样、这条消息(上下文包 + 孩子的话)、思考量、工具 */
17
+ /** 一轮要脑做的:哪个运行时、接哪个会话、系统提示、这条消息(上下文包 + 孩子的话)、思考量、工具 */
22
18
  export interface BrainRequest {
23
19
  /** 运行时名,缺省 cotutor.json 的 runtimes.default */
24
20
  runtime?: string;
@@ -29,35 +25,24 @@ export interface BrainRequest {
29
25
  } | null;
30
26
  agent: string;
31
27
  prompt: string;
32
- agentBody?: string;
28
+ /** 系统提示:老师正文(或 activity)+ 板书写法 */
33
29
  systemBody?: string;
34
- boardFile?: string;
35
30
  effort?: Policy['effort'];
36
31
  tools: ToolSet;
37
- /** 老师该读得到、在会话 cwd 外面的目录 */
32
+ /** 只读工具读得到的目录(workspace 根、vault) */
38
33
  readDirs: readonly string[];
39
- /** 本轮附的照片(绝对路径):API 直接附成图;CLI 不用它,老师按上下文包里的路径自己读 */
34
+ /** 本轮附的照片(绝对路径),直接附成图 */
40
35
  images?: readonly string[];
41
- /** 整段压缩(API 运行时;上一轮结束时上下文过了 32k):之前的模型历史不再发,digest 是应用照索引拼的前情;prompt 这一轮带笔记全文。CLI 不用它 */
36
+ /** 整段压缩(上一轮结束时上下文过了 32k):之前的模型历史不再发,digest 是应用照索引拼的前情;prompt 这一轮带笔记全文 */
42
37
  compact?: {
43
38
  digest: string;
44
39
  };
45
- /** 回复停在写成正文的工具调用上了吗(板书的规矩,runner 交进来);CLI 据此 resume 一次告诉老师不会有结果,没给就不查。API 不用它 */
46
- stuckOnTool?: (text: string) => boolean;
47
- }
48
- /** 接预热好的进程要对得上的:老师目录(进程 cwd)、日期、这个话题末条 job(新开记 null) */
49
- export interface WarmKey {
50
- cwd: string;
51
- date: string;
52
- lastJob: string | null;
53
40
  }
54
41
  export interface BrainHooks {
55
- /** 起了一次(第一次、断流接着跑、工具调用写成正文后接着跑);warmBornAt = 接过来的预热进程起来的时刻 */
56
42
  onStart(s: {
57
43
  cli: string;
58
44
  runtime: string;
59
45
  resume: boolean;
60
- warmBornAt?: number;
61
46
  }): void;
62
47
  /** 当前回复的正文变了(读 BrainRun.text()) */
63
48
  onText(): void;
@@ -65,23 +50,16 @@ export interface BrainHooks {
65
50
  name: string;
66
51
  sub: boolean;
67
52
  }): void;
68
- onStall(s: {
69
- idleMs: number;
70
- retry: number;
71
- kept: number;
72
- }): void;
73
- /** 进程都退了,还没读日志收尾;at = Date.now() */
53
+ /** 模型回完了,还没读日志收尾;at = Date.now() */
74
54
  onExit(at: number): void;
75
55
  }
76
56
  export interface BrainRun {
77
- /** 接的是预热好的进程 */
78
- warm: boolean;
79
- /** 当前这条回复的正文(断流接着跑的,含被杀那次已经写出的) */
57
+ /** 当前这条回复的正文 */
80
58
  text(): string;
81
59
  done: Promise<BrainResult>;
82
60
  }
83
61
  export interface BrainResult {
84
- /** final 总有:进程退了没有收尾的,记成没成(原因指向 err.log) */
62
+ /** final 总有:没收尾的记成没成 */
85
63
  transcript: Transcript & {
86
64
  final: NonNullable<Transcript['final']>;
87
65
  };
@@ -90,56 +68,30 @@ export interface BrainResult {
90
68
  /** 给家长端这一轮的提醒,按出现顺序 */
91
69
  warnings: string[];
92
70
  }
93
- /** 这一轮落在哪:原始输出(CLI 的 stream-json / API 的统一事件)、stderr、这个话题的模型历史(API 用;CLI 的历史在它自己的会话里) */
71
+ /** 这一轮落在哪:统一事件、stderr、这个话题的模型历史 */
94
72
  export interface BrainFiles {
95
73
  log: string;
96
74
  err: string;
97
75
  history: string;
98
76
  }
99
77
  export interface BrainExecutor {
100
- /** 定这一轮怎么跑。现在就是 CLI 的命令规划(run 文件照记命令行);API 脑进来时再分出共同的 runtime / resume / session 与各家私有的 */
78
+ /** 定这一轮怎么跑:哪个运行时、接不接着会话(run 文件照记) */
101
79
  plan(req: BrainRequest): RunPlan;
102
- /** 这一轮在 workspace 里要的机器文件(qwen 的隔离家目录);备不好照起,原因进 err.log */
103
- prepare(plan: RunPlan): Promise<void>;
104
- /**
105
- * 跑一轮。同步返回:接预热进程在调用的这一拍里(调用方查完 busy 到这里之间不能有 await)。
106
- * files:原始输出与 stderr 落哪;stall:断流看门狗
107
- */
80
+ /** 跑一轮。files:统一事件与 stderr 落哪;stall:断流看门狗 */
108
81
  start(req: BrainRequest, plan: RunPlan, ctx: {
109
82
  tutor: string;
110
- key: WarmKey;
111
83
  files: BrainFiles;
112
84
  stall: Policy['stall'];
113
85
  }, hooks: BrainHooks): BrainRun;
114
- /** 这个运行时能不能预热(消息走 stdin 的才行) */
115
- canWarm(runtime: Runtime): boolean;
116
- /** 给下一轮起好进程;这个位置已有对得上的就不动 */
117
- warm(tutor: string, slot: Slot, plan: RunPlan, key: WarmKey): void;
118
- warmed(tutor: string, slot?: Slot): boolean;
119
- dropWarm(tutor: string, slot?: Slot): void;
120
- /** 杀掉所有预热的进程(测试收尾;服务退出时它们的 stdin 断了会自己退) */
121
- close(): void;
122
86
  }
123
87
  /**
124
- * auth:百炼按量 key,知道的话带业务空间 id(API 运行时用;runner 给的是和实时那边同一条查找链);endpoint:测试与探针指到假百炼(…/compatible-mode/v1)
88
+ * line:这个模型发到哪、带哪把 key(runner 给 compatibleLine);endpoint:测试与探针指到假百炼(…/compatible-mode/v1)
125
89
  */
126
90
  export declare function createBrain(opts: {
127
91
  workspace: () => {
128
92
  config: CotutorConfig;
129
93
  root: string;
130
94
  };
131
- env?: NodeJS.ProcessEnv;
132
- warmIdleMs?: number;
133
- auth?: () => {
134
- key: string;
135
- workspaceId: string | null;
136
- } | null;
95
+ line?: BrainLine;
137
96
  endpoint?: string;
138
97
  }): BrainExecutor;
139
- /** 这个运行时关得住工具吗:关得住的,孩子那轮不带工具时,只有工具才用得上的路径(refs、vault)不进上下文包 */
140
- export declare function controlsTools(runtime: Runtime): boolean;
141
- /**
142
- * 读一轮的 .log:API 的轮次是一行一条统一事件,CLI 的按运行时的适配器读(运行时后来删了或改了按 stream-json)。
143
- * 两种行长得不一样(统一事件以 {"kind": 开头),一行一行认,运行时从 CLI 换成 API 以后,以前的轮次照样读得出
144
- */
145
- export declare function logParser(runtimes: Readonly<Record<string, unknown>>, name: string | undefined): (line: string) => CliEvent[];
@@ -1,74 +1,11 @@
1
- /**
2
- * 脑执行:一轮教学里「让脑想、写出正文」的那一步。
3
- * runner 拼好上下文包与系统提示交到这里;这里起老师(或接预热好的)、读输出、断流接着跑,随流回报正文与工具,收尾给出整轮的转录。
4
- * 执行完不等于这一轮交付完:板书解析、配音、记忆与日记的收尾归 runner。
5
- * 两种实现,按 cotutor.json 里这轮的运行时分:{run, resume} 起 CLI(./cli/run.ts,各家方言在 ./cli/ 的适配器);{api, model} 应用直连模型(./api.ts)。
6
- */
7
- import { controlsTools as cliControlsTools, parserForRuntime } from "./cli/adapters.js";
8
- import { getRuntime } from "./cli/run-plan.js";
9
- import { isApiRuntime } from "../schema/index.js";
10
- import { API_CLI, ApiBrain, parseEventLine } from "./api.js";
11
- import { CliBrain } from "./cli/run.js";
12
- /**
13
- * CLI 那一家给外面用的几样(经 brain 的出口给,外面不直接引 clis/):工具名属于哪一类(家长端看原文、板书写法有没有被读);
14
- * 出厂的运行时模板(init 写 cotutor.json);doctor 体检 CLI 运行时用的(退役没有、适配器、读输出、备机器文件、起进程的环境)
15
- */
16
- export { adapterFor, factoryRuntimes, parserOf, prepareCli, processEnv, runtimeIssue, toolKind } from "./cli/adapters.js";
1
+ import { ApiBrain } from "./api.js";
2
+ export { toolKind } from "./types.js";
3
+ export { parseLogLine } from "./log.js";
4
+ export { RuntimeError, getRuntime, runtimeIssue } from "./runtime.js";
17
5
  export { FACTORY_API_RUNTIMES } from "./api.js";
18
6
  /**
19
- * auth:百炼按量 key,知道的话带业务空间 id(API 运行时用;runner 给的是和实时那边同一条查找链);endpoint:测试与探针指到假百炼(…/compatible-mode/v1)
7
+ * line:这个模型发到哪、带哪把 key(runner 给 compatibleLine);endpoint:测试与探针指到假百炼(…/compatible-mode/v1)
20
8
  */
21
9
  export function createBrain(opts) {
22
- return new Brains(opts.workspace, new CliBrain(opts), new ApiBrain({ workspace: opts.workspace, auth: opts.auth ?? (() => null), endpoint: opts.endpoint }));
23
- }
24
- /** 按这轮的运行时分给 CLI 或 API;预热只有 CLI 有 */
25
- class Brains {
26
- workspace;
27
- cli;
28
- api;
29
- constructor(workspace, cli, api) {
30
- this.workspace = workspace;
31
- this.cli = cli;
32
- this.api = api;
33
- }
34
- plan(req) {
35
- return isApiRuntime(getRuntime(this.workspace().config, req.runtime).runtime) ? this.api.plan(req) : this.cli.plan(req);
36
- }
37
- prepare(plan) {
38
- return plan.cli === API_CLI ? Promise.resolve() : this.cli.prepare(plan);
39
- }
40
- start(req, plan, ctx, hooks) {
41
- return plan.cli === API_CLI ? this.api.start(req, plan, ctx, hooks) : this.cli.start(req, plan, ctx, hooks);
42
- }
43
- canWarm(runtime) {
44
- return !isApiRuntime(runtime) && this.cli.canWarm(runtime);
45
- }
46
- warm(tutor, slot, plan, key) {
47
- if (plan.cli !== API_CLI)
48
- this.cli.warm(tutor, slot, plan, key);
49
- }
50
- warmed(tutor, slot) {
51
- return this.cli.warmed(tutor, slot);
52
- }
53
- dropWarm(tutor, slot) {
54
- this.cli.dropWarm(tutor, slot);
55
- }
56
- close() {
57
- this.cli.close();
58
- }
59
- }
60
- /** 这个运行时关得住工具吗:关得住的,孩子那轮不带工具时,只有工具才用得上的路径(refs、vault)不进上下文包 */
61
- export function controlsTools(runtime) {
62
- return isApiRuntime(runtime) || cliControlsTools(runtime);
63
- }
64
- /**
65
- * 读一轮的 .log:API 的轮次是一行一条统一事件,CLI 的按运行时的适配器读(运行时后来删了或改了按 stream-json)。
66
- * 两种行长得不一样(统一事件以 {"kind": 开头),一行一行认,运行时从 CLI 换成 API 以后,以前的轮次照样读得出
67
- */
68
- export function logParser(runtimes, name) {
69
- const cli = parserForRuntime(runtimes, name);
70
- return (line) => {
71
- const e = parseEventLine(line);
72
- return e.length ? e : cli(line);
73
- };
10
+ return new ApiBrain({ workspace: opts.workspace, line: opts.line ?? (() => null), endpoint: opts.endpoint });
74
11
  }
@@ -0,0 +1,5 @@
1
+ import type { CliEvent } from './types.ts';
2
+ /** 统一事件的一行;不是这种行回 [] */
3
+ export declare function parseEventLine(line: string): CliEvent[];
4
+ /** .log 的一行 → 统一事件:先当统一事件,不是再当旧的 stream-json */
5
+ export declare function parseLogLine(line: string): CliEvent[];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 一轮的 .log 怎么读:API 轮次一行一条统一事件({"kind": 开头,./api.ts 写的);2026-11-02 以前的轮次是 Qwen Code 的 stream-json(./stream-json.ts)。
3
+ * 两种行长得不一样,一行一行认,同一份记录里换过也读得出。
4
+ */
5
+ import { parseStreamJson } from "./stream-json.js";
6
+ /** 统一事件的一行;不是这种行回 [] */
7
+ export function parseEventLine(line) {
8
+ if (!line.startsWith('{"kind":'))
9
+ return [];
10
+ try {
11
+ const e = JSON.parse(line);
12
+ return typeof e.kind === 'string' ? [e] : [];
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ }
18
+ /** .log 的一行 → 统一事件:先当统一事件,不是再当旧的 stream-json */
19
+ export function parseLogLine(line) {
20
+ const e = parseEventLine(line);
21
+ return e.length ? e : parseStreamJson(line);
22
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 这一轮用哪个运行时、接不接着会话。运行时只有 API 一种(`{api: "bailian", model}`,./api.ts)。
3
+ * 2026-11-02 起不再起命令行(Qwen Code;Claude CLI 10-28 就去掉了):旧 cotutor.json 里写着 `{run, resume}` 的照样读得进来,
4
+ * 但用不了——doctor 报出来,用到它的那位老师发不出去,说怎么改。以前那种运行时下聊的话题照样能看(sameRuntime:接不上、不记账)。
5
+ */
6
+ import { type ApiRuntime, type CotutorConfig, type Runtime } from '../schema/index.ts';
7
+ import type { ToolSet } from './types.ts';
8
+ export interface RunPlan {
9
+ /** 运行时名(runtimes 里的键) */
10
+ runtime: string;
11
+ /** 哪种脑跑的(API 的是 api;旧轮次的 run.json 里是 CLI 的适配器名) */
12
+ cli: string;
13
+ /** 这轮带不带工具 */
14
+ tools: ToolSet;
15
+ /** 旧轮次起 CLI 的命令行;API 的是空 */
16
+ argv: string[];
17
+ /** 这次是接着会话还是新开 */
18
+ resume: boolean;
19
+ /** 接着的会话 id */
20
+ session: string | null;
21
+ env: Record<string, string>;
22
+ }
23
+ export declare class RuntimeError extends Error {
24
+ }
25
+ /** 命令行运行时(旧配置里的 `{run, resume}`)用不了,说怎么改;API 运行时 null */
26
+ export declare function runtimeIssue(runtime: Runtime): string | null;
27
+ export declare function getRuntime(config: CotutorConfig, name?: string): {
28
+ name: string;
29
+ runtime: ApiRuntime;
30
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 这一轮用哪个运行时、接不接着会话。运行时只有 API 一种(`{api: "bailian", model}`,./api.ts)。
3
+ * 2026-11-02 起不再起命令行(Qwen Code;Claude CLI 10-28 就去掉了):旧 cotutor.json 里写着 `{run, resume}` 的照样读得进来,
4
+ * 但用不了——doctor 报出来,用到它的那位老师发不出去,说怎么改。以前那种运行时下聊的话题照样能看(sameRuntime:接不上、不记账)。
5
+ */
6
+ import { isApiRuntime } from "../schema/index.js";
7
+ export class RuntimeError extends Error {
8
+ }
9
+ /** 命令行运行时(旧配置里的 `{run, resume}`)用不了,说怎么改;API 运行时 null */
10
+ export function runtimeIssue(runtime) {
11
+ return isApiRuntime(runtime) ? null : '命令行运行时(Qwen Code 等)2026-11-02 起不再支持:把 runtimes.default 和 tutors.*.runtime 改成 api 或 api-flash(应用直连百炼),再删掉这一条';
12
+ }
13
+ export function getRuntime(config, name) {
14
+ const n = name ?? config.runtimes.default;
15
+ const p = config.runtimes[n];
16
+ if (n === 'default' || !p || typeof p === 'string') {
17
+ const known = Object.keys(config.runtimes).filter((k) => k !== 'default');
18
+ throw new RuntimeError(`运行时 "${n}" 不存在;cotutor.json 的 runtimes 里有:${known.join('、')}`);
19
+ }
20
+ if (!isApiRuntime(p))
21
+ throw new RuntimeError(`运行时 "${n}":${runtimeIssue(p)}`);
22
+ return { name: n, runtime: p };
23
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Qwen Code 的 stream-json(`--output-format stream-json`,一行一个 JSON)读成统一事件。
3
- * 同时保留历史日志的费用字段解析,让已有对话仍能查看。Qwen Code 0.25 的输出:
2
+ * 旧轮次的 .log:2026-11-02 以前老师的脑是起 Qwen Code(或更早的 Claude Code),输出是 stream-json(一行一个 JSON),这里读成统一事件,
3
+ * 那时的记录照样能看(看原文、读了什么、转录)。不再起 CLI,只读。Qwen Code 0.25 的输出:
4
4
  * - `{"type":"system","subtype":"init","session_id","tools":[…]}`(qwen 的 json 模式叫 session_start)
5
5
  * - `{"type":"stream_event","event":{…}}`:`--include-partial-messages` 才有;message_start 开新的一条,content_block_delta 的 text_delta 是正文、thinking_delta 是在想
6
6
  * - `{"type":"assistant","message":{"id","content":[text / tool_use 块],"usage"}}`:一条整的回复;usage 是这一次请求自己的
@@ -14,5 +14,3 @@ import type { CliEvent } from './types.ts';
14
14
  /** 结果块的文字:字符串原样,块数组拼各块的 text */
15
15
  export declare const blockText: (c: unknown) => string;
16
16
  export declare function parseStreamJson(line: string): CliEvent[];
17
- /** stream-json 输入的一条用户消息(`--input-format stream-json` 一行一条) */
18
- export declare function streamJsonUserMessage(prompt: string): string;
@@ -80,7 +80,3 @@ export function parseStreamJson(line) {
80
80
  }
81
81
  return out;
82
82
  }
83
- /** stream-json 输入的一条用户消息(`--input-format stream-json` 一行一条) */
84
- export function streamJsonUserMessage(prompt) {
85
- return `${JSON.stringify({ type: 'user', message: { role: 'user', content: prompt } })}\n`;
86
- }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * 流式:老师还在说的时候,从 CLI 的输出事件里拼出「当前这条顶层回复的正文」,给板书的 partial 解析用。
3
- * 有增量的 CLI(claude 加 `--include-partial-messages`、qwen 0.25 同)每个字来一条 delta,每条新回复前有 start;没有增量开关的只有整条 assistant——两种都认:
2
+ * 流式:老师还在说的时候,从脑的输出事件里拼出「当前这条顶层回复的正文」,给板书的 partial 解析用。
3
+ * 增量来的每一小段是一条 delta,每条新回复前有 start;只有整条 assistant 的(旧轮次的一些 CLI)也认:
4
4
  * delta 往当前文本上追加,start 时当前段带围栏或 H2 就留下(与 kid-view 的 kidSource 同一条规则:老师板书完再用工具,卡不丢)、否则丢掉,
5
5
  * 新段从空开始;顶层 assistant 的正文直接当作当前段(与拼出来的一样)。text() = 留下的段 + 当前段。
6
- * 子代理的一律跳过。读哪家的输出由 CLI 适配器定(src/brain/cli/),这里只认统一事件。
6
+ * 子代理的一律跳过(旧轮次的 CLI 才有)。这里只认统一事件(./log.ts 读)。
7
7
  */
8
- import { type CliEvent } from './cli/types.ts';
8
+ import { type CliEvent } from './types.ts';
9
9
  export interface PartialReader {
10
10
  /** 喂一块 stdout(原始字节,行没收完的先攒着);返回文本有没有变 */
11
11
  feed(chunk: string): boolean;
@@ -16,5 +16,5 @@ export interface PartialReader {
16
16
  /** 只要当前这段(还没收到整条 assistant 的那段;断流时它没进会话,要递回给老师) */
17
17
  current(): string;
18
18
  }
19
- /** parse:一行输出 → 统一事件(CLI 适配器的 parse);缺省按 stream-json 读 */
19
+ /** parse:一行输出 → 统一事件;缺省 parseLogLine(统一事件,旧轮次的 stream-json 也认) */
20
20
  export declare function createPartialReader(parse?: (line: string) => CliEvent[]): PartialReader;
@@ -1,15 +1,15 @@
1
1
  /**
2
- * 流式:老师还在说的时候,从 CLI 的输出事件里拼出「当前这条顶层回复的正文」,给板书的 partial 解析用。
3
- * 有增量的 CLI(claude 加 `--include-partial-messages`、qwen 0.25 同)每个字来一条 delta,每条新回复前有 start;没有增量开关的只有整条 assistant——两种都认:
2
+ * 流式:老师还在说的时候,从脑的输出事件里拼出「当前这条顶层回复的正文」,给板书的 partial 解析用。
3
+ * 增量来的每一小段是一条 delta,每条新回复前有 start;只有整条 assistant 的(旧轮次的一些 CLI)也认:
4
4
  * delta 往当前文本上追加,start 时当前段带围栏或 H2 就留下(与 kid-view 的 kidSource 同一条规则:老师板书完再用工具,卡不丢)、否则丢掉,
5
5
  * 新段从空开始;顶层 assistant 的正文直接当作当前段(与拼出来的一样)。text() = 留下的段 + 当前段。
6
- * 子代理的一律跳过。读哪家的输出由 CLI 适配器定(src/brain/cli/),这里只认统一事件。
6
+ * 子代理的一律跳过(旧轮次的 CLI 才有)。这里只认统一事件(./log.ts 读)。
7
7
  */
8
- import { parseStreamJson } from "./cli/stream-json.js";
9
- import { textsOf } from "./cli/types.js";
8
+ import { parseLogLine } from "./log.js";
9
+ import { textsOf } from "./types.js";
10
10
  const KEEP = /^\s*(?:```|~~~|## )/m;
11
- /** parse:一行输出 → 统一事件(CLI 适配器的 parse);缺省按 stream-json 读 */
12
- export function createPartialReader(parse = parseStreamJson) {
11
+ /** parse:一行输出 → 统一事件;缺省 parseLogLine(统一事件,旧轮次的 stream-json 也认) */
12
+ export function createPartialReader(parse = parseLogLine) {
13
13
  let rest = '';
14
14
  let text = '';
15
15
  // 当前段收到过整条 assistant 了(已进会话,不算「断在半截」)
@@ -1,5 +1,5 @@
1
- import type { CliEvent, CliFinal } from './cli/types.ts';
2
- /** 一行输出 → 统一事件(CLI 适配器的 parse) */
1
+ import type { CliEvent, CliFinal } from './types.ts';
2
+ /** 一行输出 → 统一事件(./log.ts) */
3
3
  export type LineParser = (line: string) => CliEvent[];
4
4
  export interface TranscriptItem {
5
5
  /** text 说话 / tool 用工具 / tool-error 工具报错 / done 收尾 */
@@ -12,7 +12,7 @@ export type TranscriptRow = TranscriptItem | {
12
12
  kind: 'fold';
13
13
  tools: TranscriptItem[];
14
14
  };
15
- /** 收尾:result 的最终正文、成没成、费用或 token(见 src/brain/cli/types.ts 的 CliFinal) */
15
+ /** 收尾:result 的最终正文、成没成、费用或 token(见 ./types.ts 的 CliFinal) */
16
16
  export type TranscriptFinal = CliFinal;
17
17
  export interface Transcript {
18
18
  sessionId: string | null;
@@ -1,9 +1,9 @@
1
1
  /**
2
- * 老师进程的转录(.log,一行一个 JSON)→ 家长视图条目 + 最终文本。起初抄自 growth-apps 的 gen-transcript(2026-08-26,claude 2.1.220),
3
- * 现在按 CLI 适配器读成统一事件再拼(src/brain/cli/,缺省 stream-json,claude / qwen 同族):子代理的条目标 sub,
4
- * 收尾的最终正文单独取出作 final——各家 CLI 都有的稳定锚点,孩子视图只看它。
2
+ * 一轮的转录(.log,一行一个 JSON)→ 家长视图条目 + 最终文本。起初抄自 growth-apps 的 gen-transcript(2026-08-26,claude 2.1.220),
3
+ * 现在读成统一事件再拼(./log.ts:API 轮次的统一事件,旧轮次的 stream-json):子代理的条目标 sub(旧轮次的 CLI 才有),
4
+ * 收尾的最终正文单独取出作 final,孩子视图只看它。
5
5
  */
6
- import { parseStreamJson } from "./cli/stream-json.js";
6
+ import { parseLogLine } from "./log.js";
7
7
  import { tokenText } from "../shared/tokens.js";
8
8
  const FOLD_AT = 3;
9
9
  /**
@@ -58,7 +58,7 @@ export function toolArg(input) {
58
58
  }
59
59
  return '';
60
60
  }
61
- export function toolCalls(text, parse = parseStreamJson) {
61
+ export function toolCalls(text, parse = parseLogLine) {
62
62
  const calls = [];
63
63
  const byId = new Map();
64
64
  for (const line of text.split('\n')) {
@@ -86,7 +86,7 @@ export function toolCalls(text, parse = parseStreamJson) {
86
86
  }
87
87
  return calls;
88
88
  }
89
- export function parseTranscript(text, parse = parseStreamJson) {
89
+ export function parseTranscript(text, parse = parseLogLine) {
90
90
  let sessionId = null;
91
91
  const items = [];
92
92
  let final = null;
@@ -1,12 +1,10 @@
1
1
  /**
2
- * CLI 适配器:一个 agent CLI 一个文件,写「这个 CLI 怎么说话」——
3
- * 输出流怎么读、消息怎么写进 stdin、工具叫什么怎么关、思考量怎么调、起进程要什么环境、出厂模板长什么样。
4
- * 选哪个模型、预算、时限还在 cotutor.json 的运行时模板里(数据);应用的其余部分只认这里的统一事件,不认哪家的 JSON。
5
- * 接一个新 CLI:照 qwen.ts 写一个文件,在 index.ts 的 ADAPTERS 里登记。
2
+ * 脑的统一事件与工具:API 脑(./api.ts)写进 .log、孩子端的流式板书、家长端看原文、转录都只认这些。
3
+ * 名字里的 Cli 是 2026-11-02 以前起 Qwen Code 时留下的;那时的 .log 是 stream-json,由 ./stream-json.ts 读成同样的事件。
6
4
  */
7
- import type { CliRuntime, Policy } from '../../schema/index.ts';
8
5
  /** 工具的类别:应用要知道「读了哪个文件」「是不是用技能回读板书写法」时按类别认,不比工具名 */
9
6
  export type ToolKind = 'read' | 'shell' | 'search' | 'edit' | 'skill' | 'other';
7
+ export declare const toolKind: (name: string) => ToolKind;
10
8
  /**
11
9
  * 这轮带不带工具(政策 tools 与消息种类定):off 一个不带;read 只读(看照片、读 vault、查教材);
12
10
  * on 与 read 一样只读,不提供 shell 与写入工具
@@ -99,46 +97,3 @@ export type CliEvent = {
99
97
  kind: 'result';
100
98
  final: CliFinal;
101
99
  };
102
- /** 运行时模板里与适配器有关的占位能拿到的值 */
103
- export interface PlanContext {
104
- /** workspace 根(适配器的私有文件放在它的 .cotutor/ 下);测试与不关心的调用可以不给 */
105
- root?: string;
106
- effort: Policy['effort'];
107
- /** 这轮带不带工具 */
108
- tools: ToolSet;
109
- }
110
- export interface CliAdapter {
111
- /** 适配器名:运行时的 cli 字段,或 run[0] 的文件名 */
112
- name: string;
113
- /** 一行原始输出 → 统一事件;读不懂的行返回 [] */
114
- parse(line: string): CliEvent[];
115
- /** stdin 模式(运行时 stdin: "stream-json")下写进 stdin 的那一行用户消息,带换行 */
116
- stdinMessage(prompt: string): string;
117
- /** 模板里 `{tools}` 填什么(一个参数里的文字) */
118
- toolList(set: ToolSet): string;
119
- /** 模板里独占一个参数的 `{toolArgs}` 展开成哪几个参数(可以是零个);readDirs = 这轮该读得到的目录(workspace 根、vault),会话 cwd 是老师目录,它们在 cwd 外面 */
120
- toolArgs(set: ToolSet, ctx: {
121
- readDirs: readonly string[];
122
- }): string[];
123
- toolKind(name: string): ToolKind;
124
- /** 这轮带或不带工具时,进程起来报的工具表(init 事件)应该正好是哪几样;多出来的记提醒(CLI 升版本会带进新工具)。不给 = 不核对 */
125
- expectedTools?(set: ToolSet): readonly string[];
126
- /** 起进程前把这个 CLI 在 workspace 里要的机器文件备好(幂等,内容一样就不写) */
127
- prepare?(root: string): Promise<void>;
128
- /** 由这轮的计划定下来的环境变量(进预热的比对:变了的备用进程不能用);没有就不写 */
129
- planEnv?(ctx: PlanContext): Record<string, string>;
130
- /** 起进程前最后过一遍环境(认证等);不改就原样返回 */
131
- env(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
132
- /** cotutor doctor 查这个 CLI 时多看几样(version 是 `--version` 的第一行);不给就只查装没装 */
133
- check?(ctx: {
134
- version: string;
135
- env: NodeJS.ProcessEnv;
136
- }): {
137
- name: string;
138
- ok: boolean;
139
- detail: string;
140
- fix?: string;
141
- }[];
142
- /** 出厂运行时模板:键是运行时名 */
143
- runtimes(): Record<string, CliRuntime>;
144
- }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 脑的统一事件与工具:API 脑(./api.ts)写进 .log、孩子端的流式板书、家长端看原文、转录都只认这些。
3
+ * 名字里的 Cli 是 2026-11-02 以前起 Qwen Code 时留下的;那时的 .log 是 stream-json,由 ./stream-json.ts 读成同样的事件。
4
+ */
5
+ /** 工具名 → 类别:API 脑的三样(./api-tools.ts)与旧轮次里 Qwen Code、Claude Code 的工具名;不认识的是 other */
6
+ const TOOL_KINDS = {
7
+ read_file: 'read', read_many_files: 'read', zoom_image: 'read', list_directory: 'read', glob: 'search', grep_search: 'search',
8
+ run_shell_command: 'shell', edit: 'edit', write_file: 'edit', notebook_edit: 'edit', skill: 'skill',
9
+ Read: 'read', Bash: 'shell', Grep: 'search', Glob: 'search', Edit: 'edit', Write: 'edit', NotebookEdit: 'edit', MultiEdit: 'edit', Skill: 'skill',
10
+ };
11
+ export const toolKind = (name) => TOOL_KINDS[name] ?? 'other';
12
+ /** 一条回复的正文段 / 工具调用 */
13
+ export const textsOf = (parts) => parts.flatMap((p) => (p.type === 'text' ? [p.text] : []));
14
+ export const toolsOf = (parts) => parts.flatMap((p) => (p.type === 'tool' ? [p.tool] : []));
@@ -35,9 +35,9 @@ export interface GlyphPenOptions {
35
35
  /** 采样步长(归一化单位);缺省 SAMPLE_STEP。调小更平滑,但动画 DOM 体积约按平方涨 */
36
36
  sampleStep?: number;
37
37
  /**
38
- * 字形表(provider):缺省内置拉丁包(a–z / A–Z)。扩展字形数据包
39
- * (如 drawtell-glyphs-hanzi)导出同构的 Map,从这里传入即共用同一支笔——
40
- * 归一化约定由各包自定并写明(拉丁 = 四线三格;汉字 = 格子高 1、底线 y=0、顶 y=-1)。
38
+ * 字形表(provider):缺省内置拉丁包(a–z / A–Z)。别的字形做成同构的 Map 从这里传入,就和字母共用同一支笔;
39
+ * 归一化约定由传入方定并写明(拉丁 = 四线三格;汉字可取格子高 1、底线 y=0、顶 y=-1)。
40
+ * 汉字要逐笔写时从 hanzi-writer-data 的 medians 现做(本包已依赖它,田字格卡就读它),不另挑字表。
41
41
  */
42
42
  glyphs?: ReadonlyMap<string, Glyph>;
43
43
  }
@@ -45,7 +45,7 @@ export class UnknownGlyphError extends Error {
45
45
  /** node 的 strip-only 类型擦除不支持构造函数参数属性,字段显式声明 */
46
46
  ch;
47
47
  constructor(ch) {
48
- super(`字形表未收录 '${ch}'。内置包只有 a–z 与 A–Z;汉字等扩展字形经 glyphs 选项传入(如 drawtell-glyphs-hanzi)。`);
48
+ super(`字形表未收录 '${ch}'。内置包只有 a–z 与 A–Z;别的字形做成同构的 Map 经 glyphs 选项传入。`);
49
49
  this.ch = ch;
50
50
  }
51
51
  }
@@ -11,5 +11,5 @@ export * from '../memory/plan.ts';
11
11
  export * from '../runtime/journal/ledger.ts';
12
12
  export * from '../runtime/journal/conversation.ts';
13
13
  export * from '../shared/time.ts';
14
- export * from '../brain/cli/run-plan.ts';
14
+ export * from '../brain/runtime.ts';
15
15
  export * from '../memory/timetable.ts';
package/dist/lib/index.js CHANGED
@@ -11,5 +11,5 @@ export * from "../memory/plan.js";
11
11
  export * from "../runtime/journal/ledger.js";
12
12
  export * from "../runtime/journal/conversation.js";
13
13
  export * from "../shared/time.js";
14
- export * from "../brain/cli/run-plan.js";
14
+ export * from "../brain/runtime.js";
15
15
  export * from "../memory/timetable.js";
@@ -1,11 +1,11 @@
1
- import { threads } from "../schema/index.js";
1
+ import { threads, viaSendsLabel } from "../schema/index.js";
2
2
  /** 孩子在这个话题里说的话:from: kid、不是「继续」/ 交答案这种动作、非空;多行并成一行 */
3
3
  export function kidQuestions(messages, thread) {
4
4
  const ths = threads(messages);
5
5
  const out = [];
6
6
  messages.forEach((m, i) => {
7
- // 首页的开场 / 接着按钮发的是按钮上的字,不是孩子说的
8
- if (ths[i] !== thread || m.from !== 'kid' || m.action || (m.via && typeof m.via.button === 'number'))
7
+ // 首页的开场 / 接着按钮、方砖发的是按钮上的字,不是孩子说的
8
+ if (ths[i] !== thread || m.from !== 'kid' || m.action || (m.via && viaSendsLabel(m.via)))
9
9
  return;
10
10
  const text = m.text.replace(/\s+/g, ' ').trim();
11
11
  if (text && text !== '继续')
@@ -55,7 +55,7 @@ export interface RealtimeEvents {
55
55
  onError?(message: string): void;
56
56
  onClose?(reason: string): void;
57
57
  }
58
- /** 百炼按量的 key 与业务空间 id(Token Plan 没有 omni realtime;key 怎么找见 src/shared/qianwen.ts,业务空间 id 先看家长端配的、再看环境、再看 voxtell 的配置) */
58
+ /** 百炼按量的 key 与业务空间 id(Token Plan 没有 omni realtime;都在 ~/.config/cotutor/bailian.json,见 src/shared/qianwen.ts) */
59
59
  export declare function dashscopeAuth(env?: NodeJS.ProcessEnv): {
60
60
  key: string;
61
61
  workspaceId: string;
@@ -28,7 +28,7 @@ export const OUTPUT_RATE = 24000;
28
28
  export const MAX_IMAGE_B64 = 262144 - 1024;
29
29
  const TRANSCRIBE_MODEL = 'qwen3-asr-flash-realtime';
30
30
  const HEARTBEAT_PCM = Buffer.alloc(INPUT_RATE * 2);
31
- /** 百炼按量的 key 与业务空间 id(Token Plan 没有 omni realtime;key 怎么找见 src/shared/qianwen.ts,业务空间 id 先看家长端配的、再看环境、再看 voxtell 的配置) */
31
+ /** 百炼按量的 key 与业务空间 id(Token Plan 没有 omni realtime;都在 ~/.config/cotutor/bailian.json,见 src/shared/qianwen.ts) */
32
32
  export function dashscopeAuth(env = process.env) {
33
33
  const key = qianwenKeys(env).payg;
34
34
  const workspaceId = bailianWorkspaceId(env);
@@ -95,8 +95,9 @@ export class RealtimeSession {
95
95
  async connect() {
96
96
  let url = this.opts.endpoint;
97
97
  let key = '';
98
+ // 指了端点的是测试与探针的假百炼,不看 key
98
99
  if (url)
99
- key = this.opts.env?.DASHSCOPE_API_KEY ?? 'sk-test';
100
+ key = 'sk-test';
100
101
  else {
101
102
  const auth = this.opts.auth ?? dashscopeAuth(this.opts.env);
102
103
  if (!auth)