cotutor 0.1.0 → 0.2.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 (109) hide show
  1. package/README.md +35 -24
  2. package/agents/chinese-tutor.md +67 -0
  3. package/agents/{homework-aide.md → homework-tutor.md} +4 -4
  4. package/agents/math-tutor.md +68 -0
  5. package/agents/reading-tutor.md +65 -0
  6. package/agents/scene-maker.md +41 -0
  7. package/dist/cards/canvas.d.ts +22 -0
  8. package/dist/cards/canvas.js +65 -0
  9. package/dist/cards/choice.d.ts +20 -0
  10. package/dist/cards/choice.js +76 -0
  11. package/dist/cards/code.d.ts +9 -0
  12. package/dist/cards/code.js +13 -0
  13. package/dist/cards/fill.d.ts +14 -0
  14. package/dist/cards/fill.js +64 -0
  15. package/dist/cards/image.d.ts +13 -0
  16. package/dist/cards/image.js +35 -0
  17. package/dist/cards/index.d.ts +48 -0
  18. package/dist/cards/index.js +115 -0
  19. package/dist/cards/kind.d.ts +27 -0
  20. package/dist/cards/kind.js +7 -0
  21. package/dist/cards/read.d.ts +8 -0
  22. package/dist/cards/read.js +27 -0
  23. package/dist/cards/scene.d.ts +24 -0
  24. package/dist/cards/scene.js +67 -0
  25. package/dist/cards/text.d.ts +18 -0
  26. package/dist/cards/text.js +41 -0
  27. package/dist/cli/cert.d.ts +7 -0
  28. package/dist/cli/cert.js +36 -0
  29. package/dist/cli/doctor.d.ts +5 -2
  30. package/dist/cli/doctor.js +183 -18
  31. package/dist/cli/init.d.ts +1 -1
  32. package/dist/cli/init.js +41 -39
  33. package/dist/cli/main.js +128 -8
  34. package/dist/cli/serve.d.ts +15 -4
  35. package/dist/cli/serve.js +40 -6
  36. package/dist/cli/skeleton.d.ts +23 -5
  37. package/dist/cli/skeleton.js +131 -29
  38. package/dist/cli/skills.d.ts +40 -0
  39. package/dist/cli/skills.js +170 -0
  40. package/dist/cli/tutors.d.ts +84 -0
  41. package/dist/cli/tutors.js +274 -0
  42. package/dist/cli/workspace.d.ts +9 -1
  43. package/dist/cli/workspace.js +25 -4
  44. package/dist/lib/agent-file.d.ts +1 -1
  45. package/dist/lib/board.d.ts +15 -0
  46. package/dist/lib/board.js +107 -0
  47. package/dist/lib/context-pack.js +10 -1
  48. package/dist/lib/conversation.d.ts +43 -6
  49. package/dist/lib/conversation.js +53 -9
  50. package/dist/lib/index.d.ts +4 -0
  51. package/dist/lib/index.js +4 -0
  52. package/dist/lib/kid-board.d.ts +148 -0
  53. package/dist/lib/kid-board.js +294 -0
  54. package/dist/lib/kid-view.d.ts +44 -7
  55. package/dist/lib/kid-view.js +63 -8
  56. package/dist/lib/ledger.d.ts +8 -0
  57. package/dist/lib/ledger.js +8 -0
  58. package/dist/lib/plan.d.ts +2 -0
  59. package/dist/lib/plan.js +9 -0
  60. package/dist/lib/run-plan.d.ts +30 -0
  61. package/dist/lib/run-plan.js +32 -0
  62. package/dist/lib/stream.d.ts +15 -0
  63. package/dist/lib/stream.js +69 -0
  64. package/dist/lib/timetable.d.ts +22 -0
  65. package/dist/lib/timetable.js +106 -0
  66. package/dist/schema/board.d.ts +59 -0
  67. package/dist/schema/board.js +30 -0
  68. package/dist/schema/config.d.ts +76 -18
  69. package/dist/schema/config.js +67 -37
  70. package/dist/schema/context-pack.d.ts +7 -0
  71. package/dist/schema/context-pack.js +6 -0
  72. package/dist/schema/conversation.d.ts +114 -4
  73. package/dist/schema/conversation.js +29 -5
  74. package/dist/schema/index.d.ts +6 -2
  75. package/dist/schema/index.js +6 -2
  76. package/dist/schema/json-schema.d.ts +2 -0
  77. package/dist/schema/json-schema.js +23 -0
  78. package/dist/schema/ledger.d.ts +1 -1
  79. package/dist/schema/timetable.d.ts +26 -0
  80. package/dist/schema/timetable.js +18 -0
  81. package/dist/server/app.d.ts +67 -6
  82. package/dist/server/app.js +399 -38
  83. package/dist/server/kid-page.d.ts +1 -0
  84. package/dist/server/kid-page.js +768 -0
  85. package/dist/server/mock.d.ts +58 -0
  86. package/dist/server/mock.js +403 -0
  87. package/dist/server/parent-page.d.ts +8 -0
  88. package/dist/server/parent-page.js +393 -0
  89. package/dist/server/runner.d.ts +64 -0
  90. package/dist/server/runner.js +277 -0
  91. package/dist/server/scene-props.d.ts +16 -0
  92. package/dist/server/scene-props.js +39 -0
  93. package/dist/server/stage.d.ts +15 -0
  94. package/dist/server/stage.js +69 -0
  95. package/dist/server/store.d.ts +43 -0
  96. package/dist/server/store.js +210 -0
  97. package/dist/server/tts.d.ts +51 -0
  98. package/dist/server/tts.js +99 -0
  99. package/dist/stage/Assistant-Bold-ZDZZ6JHA.woff2 +0 -0
  100. package/dist/stage/Assistant-Medium-DZ25RZU3.woff2 +0 -0
  101. package/dist/stage/Assistant-Regular-PLF2XOGW.woff2 +0 -0
  102. package/dist/stage/Assistant-SemiBold-CZ5MX6FK.woff2 +0 -0
  103. package/dist/stage/index.html +10 -0
  104. package/dist/stage/stage.css +1 -0
  105. package/dist/stage/stage.js +4383 -0
  106. package/package.json +13 -4
  107. package/agents/chinese-teacher.md +0 -19
  108. package/agents/math-teacher.md +0 -19
  109. package/agents/reading-teacher.md +0 -19
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
3
- * `paths` 角色映射 + `agents` 的 {run, resume} 命令模板——再加老师表与政策。
3
+ * `paths` 角色映射 + `runtimes` 的 {run, resume} 命令模板——再加老师表与政策。
4
4
  * 字段归属铁律:老师的 .md 文件里只有 name / description / maxTurns / permissionMode / memory + 正文;
5
- * 人设(display / avatar / voice)、政策、开关全在这里。助教团页只改本文件,永不改链进来的定义文件。
5
+ * 人设(display / avatar / voice)、政策、开关全在这里。老师团页只改本文件,永不改链进来的定义文件。
6
6
  */
7
7
  import { z } from 'zod';
8
8
  /** 回复形式阶梯(《cotutor产品规划.md》):L0 确定性资源 / L1 口答 / L2 快卡 / L3 补讲 / L4 整包 */
@@ -24,6 +24,13 @@ export declare const PolicySchema: z.ZodObject<{
24
24
  recent: z.ZodNumber;
25
25
  planLines: z.ZodNumber;
26
26
  }, z.core.$strip>;
27
+ board: z.ZodEnum<{
28
+ auto: "auto";
29
+ off: "off";
30
+ }>;
31
+ scenes: z.ZodObject<{
32
+ dailyMax: z.ZodNumber;
33
+ }, z.core.$strip>;
27
34
  }, z.core.$strip>;
28
35
  export type Policy = z.infer<typeof PolicySchema>;
29
36
  /** 政策补丁:老师条目与 policyDefaults 都是补丁,逐层盖在 POLICY_DEFAULTS 上 */
@@ -43,13 +50,20 @@ export declare const PolicyPatchSchema: z.ZodObject<{
43
50
  recent: z.ZodOptional<z.ZodNumber>;
44
51
  planLines: z.ZodOptional<z.ZodNumber>;
45
52
  }, z.core.$strip>>;
53
+ board: z.ZodOptional<z.ZodEnum<{
54
+ auto: "auto";
55
+ off: "off";
56
+ }>>;
57
+ scenes: z.ZodOptional<z.ZodObject<{
58
+ dailyMax: z.ZodOptional<z.ZodNumber>;
59
+ }, z.core.$strip>>;
46
60
  }, z.core.$strip>;
47
61
  export type PolicyPatch = z.infer<typeof PolicyPatchSchema>;
48
62
  /** 2026-09-08 拍板的缺省:60 字、30 条/日、3 次重生、验收关、上下文包各 10 */
49
63
  export declare const POLICY_DEFAULTS: Policy;
50
64
  /** agent 名:与 .claude/agents/<name>.md 的 frontmatter name 一致,小写字母数字连字符 */
51
65
  export declare const AGENT_NAME_RE: RegExp;
52
- export declare const TeacherSchema: z.ZodObject<{
66
+ export declare const TutorSchema: z.ZodObject<{
53
67
  display: z.ZodString;
54
68
  subject: z.ZodOptional<z.ZodString>;
55
69
  avatar: z.ZodOptional<z.ZodString>;
@@ -72,25 +86,42 @@ export declare const TeacherSchema: z.ZodObject<{
72
86
  recent: z.ZodOptional<z.ZodNumber>;
73
87
  planLines: z.ZodOptional<z.ZodNumber>;
74
88
  }, z.core.$strip>>;
89
+ board: z.ZodOptional<z.ZodEnum<{
90
+ auto: "auto";
91
+ off: "off";
92
+ }>>;
93
+ scenes: z.ZodOptional<z.ZodObject<{
94
+ dailyMax: z.ZodOptional<z.ZodNumber>;
95
+ }, z.core.$strip>>;
75
96
  }, z.core.$strip>>;
97
+ runtime: z.ZodOptional<z.ZodString>;
76
98
  }, z.core.$strip>;
77
- export type Teacher = z.infer<typeof TeacherSchema>;
99
+ export type Tutor = z.infer<typeof TutorSchema>;
78
100
  /**
79
- * 运行时预设:一个 CLI 一组 {run, resume} 命令模板。占位符:
101
+ * 运行时:一个 CLI 一组 {run, resume} 命令模板。占位符:
80
102
  * {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
81
- * 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换预设。
103
+ * 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换运行时。
82
104
  */
83
- export declare const AgentPresetSchema: z.ZodObject<{
105
+ export declare const RuntimeSchema: z.ZodObject<{
84
106
  run: z.ZodArray<z.ZodString>;
85
107
  resume: z.ZodArray<z.ZodString>;
86
108
  }, z.core.$strip>;
87
- export type AgentPreset = z.infer<typeof AgentPresetSchema>;
88
- export declare const AgentsSchema: z.ZodObject<{
109
+ export type Runtime = z.infer<typeof RuntimeSchema>;
110
+ export declare const RuntimesSchema: z.ZodObject<{
89
111
  default: z.ZodString;
90
112
  }, z.core.$catchall<z.ZodObject<{
91
113
  run: z.ZodArray<z.ZodString>;
92
114
  resume: z.ZodArray<z.ZodString>;
93
115
  }, z.core.$strip>>>;
116
+ /**
117
+ * 配音运行时:老师说完,应用把 kidText 合成一段音频给孩子端播。占位符 {text} {voice} {out}(输出文件路径)。
118
+ * 命令要把音频写到 {out};没配、没装、失败 → 这条没有音频,孩子端退回浏览器自带的合成声。
119
+ */
120
+ export declare const TtsSchema: z.ZodObject<{
121
+ say: z.ZodArray<z.ZodString>;
122
+ }, z.core.$strip>;
123
+ export type Tts = z.infer<typeof TtsSchema>;
124
+ export declare const TTS_DEFAULT: Tts;
94
125
  /** paths 里 CLI 认识的角色;其余角色原样保留给应用层。vault 侧角色相对 vault 解析,没配 vault 就相对 workspace 根 */
95
126
  export declare const PATH_ROLES: readonly ["vault", "photos", "diary", "plans", "profile", "timetable"];
96
127
  export type PathRole = (typeof PATH_ROLES)[number];
@@ -105,6 +136,10 @@ export declare const CotutorConfigSchema: z.ZodObject<{
105
136
  }, z.core.$strip>;
106
137
  server: z.ZodDefault<z.ZodObject<{
107
138
  port: z.ZodDefault<z.ZodNumber>;
139
+ https: z.ZodOptional<z.ZodObject<{
140
+ cert: z.ZodString;
141
+ key: z.ZodString;
142
+ }, z.core.$strip>>;
108
143
  }, z.core.$strip>>;
109
144
  paths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
110
145
  policyDefaults: z.ZodDefault<z.ZodObject<{
@@ -123,8 +158,15 @@ export declare const CotutorConfigSchema: z.ZodObject<{
123
158
  recent: z.ZodOptional<z.ZodNumber>;
124
159
  planLines: z.ZodOptional<z.ZodNumber>;
125
160
  }, z.core.$strip>>;
161
+ board: z.ZodOptional<z.ZodEnum<{
162
+ auto: "auto";
163
+ off: "off";
164
+ }>>;
165
+ scenes: z.ZodOptional<z.ZodObject<{
166
+ dailyMax: z.ZodOptional<z.ZodNumber>;
167
+ }, z.core.$strip>>;
126
168
  }, z.core.$strip>>;
127
- teachers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
169
+ tutors: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
128
170
  display: z.ZodString;
129
171
  subject: z.ZodOptional<z.ZodString>;
130
172
  avatar: z.ZodOptional<z.ZodString>;
@@ -147,19 +189,30 @@ export declare const CotutorConfigSchema: z.ZodObject<{
147
189
  recent: z.ZodOptional<z.ZodNumber>;
148
190
  planLines: z.ZodOptional<z.ZodNumber>;
149
191
  }, z.core.$strip>>;
192
+ board: z.ZodOptional<z.ZodEnum<{
193
+ auto: "auto";
194
+ off: "off";
195
+ }>>;
196
+ scenes: z.ZodOptional<z.ZodObject<{
197
+ dailyMax: z.ZodOptional<z.ZodNumber>;
198
+ }, z.core.$strip>>;
150
199
  }, z.core.$strip>>;
200
+ runtime: z.ZodOptional<z.ZodString>;
151
201
  }, z.core.$strip>>>;
152
- agents: z.ZodObject<{
202
+ runtimes: z.ZodObject<{
153
203
  default: z.ZodString;
154
204
  }, z.core.$catchall<z.ZodObject<{
155
205
  run: z.ZodArray<z.ZodString>;
156
206
  resume: z.ZodArray<z.ZodString>;
157
207
  }, z.core.$strip>>>;
208
+ tts: z.ZodDefault<z.ZodObject<{
209
+ say: z.ZodArray<z.ZodString>;
210
+ }, z.core.$strip>>;
158
211
  }, z.core.$strip>;
159
212
  export type CotutorConfig = z.infer<typeof CotutorConfigSchema>;
160
- /** 老师的有效政策 = POLICY_DEFAULTS ← policyDefaults ← teachers[name].policy */
161
- export declare function resolvePolicy(config: CotutorConfig, teacher: string): Policy;
162
- export interface TeacherView {
213
+ /** 老师的有效政策 = POLICY_DEFAULTS ← policyDefaults ← tutors[name].policy */
214
+ export declare function resolvePolicy(config: CotutorConfig, tutor: string): Policy;
215
+ export interface TutorView {
163
216
  name: string;
164
217
  display: string;
165
218
  subject?: string;
@@ -170,13 +223,18 @@ export interface TeacherView {
170
223
  policy: Policy;
171
224
  }
172
225
  /** 老师列表(带有效政策);kidOnly = 只要孩子端能看到的(enabled 且不 hidden) */
173
- export declare function listTeachers(config: CotutorConfig, opts?: {
226
+ export declare function listTutors(config: CotutorConfig, opts?: {
174
227
  kidOnly?: boolean;
175
- }): TeacherView[];
176
- /** 预设模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
177
- export declare function fillPreset(argv: readonly string[], vars: {
228
+ }): TutorView[];
229
+ /** 运行时模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
230
+ export declare function fillRuntime(argv: readonly string[], vars: {
178
231
  agent: string;
179
232
  prompt: string;
180
233
  session?: string;
181
234
  agentBody?: string;
182
235
  }): string[];
236
+ export declare function fillTts(argv: readonly string[], vars: {
237
+ text: string;
238
+ voice: string;
239
+ out: string;
240
+ }): string[];
@@ -1,25 +1,29 @@
1
1
  /**
2
2
  * cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
3
- * `paths` 角色映射 + `agents` 的 {run, resume} 命令模板——再加老师表与政策。
3
+ * `paths` 角色映射 + `runtimes` 的 {run, resume} 命令模板——再加老师表与政策。
4
4
  * 字段归属铁律:老师的 .md 文件里只有 name / description / maxTurns / permissionMode / memory + 正文;
5
- * 人设(display / avatar / voice)、政策、开关全在这里。助教团页只改本文件,永不改链进来的定义文件。
5
+ * 人设(display / avatar / voice)、政策、开关全在这里。老师团页只改本文件,永不改链进来的定义文件。
6
6
  */
7
7
  import { z } from 'zod';
8
8
  /** 回复形式阶梯(《cotutor产品规划.md》):L0 确定性资源 / L1 口答 / L2 快卡 / L3 补讲 / L4 整包 */
9
9
  export const REPLY_FORMS = ['L0', 'L1', 'L2', 'L3', 'L4'];
10
10
  export const PolicySchema = z.object({
11
- /** 老师单条回复给孩子看的字数上限;超出截断并改走 L3(《cotutor契约草案.md》§4) */
12
- replyMaxChars: z.number().int().positive(),
11
+ /** 老师说给孩子听的每一句的字数上限;超出在句末截断(《cotutor契约草案.md》§4;板书后按句算) */
12
+ replyMaxChars: z.number().int().positive().describe('老师说给孩子听的每一句的字数上限(板书讲稿一行一句,按句截);超出在句末截断'),
13
13
  /** 每孩每日消息上限;超限老师头像灰掉 */
14
- dailyMessages: z.number().int().nonnegative(),
14
+ dailyMessages: z.number().int().nonnegative().describe('孩子每日可发消息条数(家长发的不算);到了头像灰'),
15
15
  /** 每日重生上限 */
16
- dailyRegen: z.number().int().nonnegative(),
16
+ dailyRegen: z.number().int().nonnegative().describe('每日重生上限'),
17
17
  /** 验收开关:false = 产物直接给孩子(缺省);true = 先经家长验收 */
18
- reviewGate: z.boolean(),
18
+ reviewGate: z.boolean().describe('验收开关:true = 产物先经家长验收才给孩子;false = 直接给(缺省)'),
19
19
  /** 这位老师可用的回复形式 */
20
- forms: z.array(z.enum(REPLY_FORMS)),
20
+ forms: z.array(z.enum(REPLY_FORMS)).describe('这位老师可用的回复形式:L0 确定性资源 / L1 口答 / L2 快卡 / L3 补讲 / L4 整包'),
21
21
  /** 上下文包的两个数:最近观察条数、计划行数 */
22
22
  contextPack: z.object({ recent: z.number().int().nonnegative(), planLines: z.number().int().nonnegative() }),
23
+ /** 板书开关:auto = 老师判断要不要出卡(缺省);off = 只说话不出卡 */
24
+ board: z.enum(['auto', 'off']).describe('板书:auto = 讲题讲概念时老师出卡(缺省);off = 只说话不出卡'),
25
+ /** 场景作业(scene-maker 做课包,$3–5 / 10–15 分钟一个):每天最多起几个;配在 scene-maker 身上或 policyDefaults */
26
+ scenes: z.object({ dailyMax: z.number().int().nonnegative().describe('每天最多起几个场景作业(一个 ≈ 一轮问答的 30 倍费用)') }),
23
27
  });
24
28
  /** 政策补丁:老师条目与 policyDefaults 都是补丁,逐层盖在 POLICY_DEFAULTS 上 */
25
29
  export const PolicyPatchSchema = z.object({
@@ -29,6 +33,8 @@ export const PolicyPatchSchema = z.object({
29
33
  reviewGate: PolicySchema.shape.reviewGate.optional(),
30
34
  forms: PolicySchema.shape.forms.optional(),
31
35
  contextPack: z.object({ recent: z.number().int().nonnegative().optional(), planLines: z.number().int().nonnegative().optional() }).optional(),
36
+ board: PolicySchema.shape.board.optional(),
37
+ scenes: z.object({ dailyMax: z.number().int().nonnegative().optional() }).optional(),
32
38
  });
33
39
  /** 2026-09-08 拍板的缺省:60 字、30 条/日、3 次重生、验收关、上下文包各 10 */
34
40
  export const POLICY_DEFAULTS = {
@@ -38,29 +44,39 @@ export const POLICY_DEFAULTS = {
38
44
  reviewGate: false,
39
45
  forms: ['L0', 'L1', 'L3', 'L4'],
40
46
  contextPack: { recent: 10, planLines: 10 },
47
+ board: 'auto',
48
+ scenes: { dailyMax: 2 },
41
49
  };
42
50
  /** agent 名:与 .claude/agents/<name>.md 的 frontmatter name 一致,小写字母数字连字符 */
43
51
  export const AGENT_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
44
- export const TeacherSchema = z.object({
52
+ export const TutorSchema = z.object({
45
53
  /** 显示名(孩子端头像下的字、计划文件的 H2 标题) */
46
- display: z.string().min(1),
47
- subject: z.string().optional(),
54
+ display: z.string().min(1).describe('显示名:孩子端头像下的字,计划文件的 H2 标题'),
55
+ subject: z.string().optional().describe('学科:与课程表的学科列、观察的 subject 对齐即归到这位老师'),
48
56
  /** 头像:一个 emoji 或图片相对路径 */
49
- avatar: z.string().optional(),
57
+ avatar: z.string().optional().describe('头像:一个 emoji'),
50
58
  /** voxtell 音色 id(2026-09-08 拍板);没有 = 用 voxtell 缺省 */
51
- voice: z.string().optional(),
52
- enabled: z.boolean().default(true),
59
+ voice: z.string().optional().describe('voxtell 音色 id(voxtell voices 可查);不配就不配音,孩子端用浏览器的声'),
60
+ enabled: z.boolean().default(true).describe('开关:false 时两端都不见'),
53
61
  /** 孩子端不露(规划老师、记账员这类) */
54
- hidden: z.boolean().default(false),
55
- policy: PolicyPatchSchema.optional(),
62
+ hidden: z.boolean().default(false).describe('孩子端不露(规划老师这类只和家长打交道的)'),
63
+ policy: PolicyPatchSchema.optional().describe('覆盖 policyDefaults 的字段,没写的继承'),
64
+ /** 这位老师用哪个运行时(runtimes 里的键);不配用 runtimes.default。scene-maker 这种要更大预算与时限的配一个自己的 */
65
+ runtime: z.string().optional().describe('这位老师用的运行时(runtimes 里的键;不配用 default)——预算、时限不同的老师配自己的'),
56
66
  });
57
67
  /**
58
- * 运行时预设:一个 CLI 一组 {run, resume} 命令模板。占位符:
68
+ * 运行时:一个 CLI 一组 {run, resume} 命令模板。占位符:
59
69
  * {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
60
- * 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换预设。
70
+ * 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换运行时。
61
71
  */
62
- export const AgentPresetSchema = z.object({ run: z.array(z.string()).min(1), resume: z.array(z.string()).min(1) });
63
- export const AgentsSchema = z.object({ default: z.string().min(1) }).catchall(AgentPresetSchema);
72
+ export const RuntimeSchema = z.object({ run: z.array(z.string()).min(1), resume: z.array(z.string()).min(1) });
73
+ export const RuntimesSchema = z.object({ default: z.string().min(1) }).catchall(RuntimeSchema);
74
+ /**
75
+ * 配音运行时:老师说完,应用把 kidText 合成一段音频给孩子端播。占位符 {text} {voice} {out}(输出文件路径)。
76
+ * 命令要把音频写到 {out};没配、没装、失败 → 这条没有音频,孩子端退回浏览器自带的合成声。
77
+ */
78
+ export const TtsSchema = z.object({ say: z.array(z.string()).min(1) });
79
+ export const TTS_DEFAULT = { say: ['voxtell', 'say', '{text}', '--voice', '{voice}', '--json', '-o', '{out}'] };
64
80
  /** paths 里 CLI 认识的角色;其余角色原样保留给应用层。vault 侧角色相对 vault 解析,没配 vault 就相对 workspace 根 */
65
81
  export const PATH_ROLES = ['vault', 'photos', 'diary', 'plans', 'profile', 'timetable'];
66
82
  export const ROLE_DEFAULTS = {
@@ -75,32 +91,39 @@ export const CotutorConfigSchema = z
75
91
  .object({
76
92
  version: z.literal(1),
77
93
  /** 孩子端标题,无品牌 */
78
- title: z.string().default('cotutor'),
94
+ title: z.string().default('cotutor').describe('孩子端标题,无品牌'),
79
95
  kid: z.object({
80
96
  /** 短名,进路径 ~/cotutor/<slug>/,不是真名 */
81
97
  slug: z.string().regex(AGENT_NAME_RE),
82
98
  name: z.string().optional(),
83
99
  grade: z.string().optional(),
84
100
  }),
85
- server: z.object({ port: z.number().int().min(1).max(65535).default(5180) }).default({ port: 5180 }),
86
- paths: z.record(z.string(), z.string()).default({}),
87
- policyDefaults: PolicyPatchSchema.default({}),
88
- teachers: z.record(z.string().regex(AGENT_NAME_RE), TeacherSchema).default({}),
89
- agents: AgentsSchema,
101
+ server: z
102
+ .object({
103
+ port: z.number().int().min(1).max(65535).default(5180),
104
+ /** 只给这个 workspace 用的证书(相对 workspace 根),是例外;不配则用机器级 ~/.config/cotutor/certs/(cotutor cert 会建,所有 workspace 共用;iPad 上录音要 HTTPS) */
105
+ https: z.object({ cert: z.string().min(1), key: z.string().min(1) }).optional(),
106
+ })
107
+ .default({ port: 5180 }),
108
+ paths: z.record(z.string(), z.string()).default({}).describe('角色 → 目录:vault 指 Obsidian vault 根;photos / diary / plans / profile / timetable 相对 vault;不配 vault 就相对 workspace 根'),
109
+ policyDefaults: PolicyPatchSchema.default({}).describe('所有老师的政策缺省;没写的用出厂缺省(60 字 / 30 条 / 3 次 / 验收关 / L0,L1,L3,L4 / 10,10)'),
110
+ tutors: z.record(z.string().regex(AGENT_NAME_RE), TutorSchema).default({}).describe('老师表:键 = .claude/agents/<键>.md 的 frontmatter name;人设、开关、政策都在这里,老师文件里只有正文'),
111
+ runtimes: RuntimesSchema.describe('运行时:default 指一个键;每个运行时 {run, resume} 命令模板,占位 {agent} {agentBody} {prompt} {session};模型、预算、时限写在这里'),
112
+ tts: TtsSchema.default(TTS_DEFAULT).describe('配音命令模板,占位 {text} {voice} {out}'),
90
113
  })
91
114
  .superRefine((c, ctx) => {
92
- if (!(c.agents.default in c.agents) || c.agents.default === 'default') {
115
+ if (!(c.runtimes.default in c.runtimes) || c.runtimes.default === 'default') {
93
116
  ctx.addIssue({
94
117
  code: 'custom',
95
- path: ['agents', 'default'],
96
- message: `预设 "${c.agents.default}" 不存在;agents 里要有同名的 {run, resume}`,
118
+ path: ['runtimes', 'default'],
119
+ message: `运行时 "${c.runtimes.default}" 不存在;runtimes 里要有同名的 {run, resume}`,
97
120
  });
98
121
  }
99
122
  });
100
- /** 老师的有效政策 = POLICY_DEFAULTS ← policyDefaults ← teachers[name].policy */
101
- export function resolvePolicy(config, teacher) {
102
- const layers = [config.policyDefaults, config.teachers[teacher]?.policy ?? {}];
103
- const out = { ...POLICY_DEFAULTS, contextPack: { ...POLICY_DEFAULTS.contextPack } };
123
+ /** 老师的有效政策 = POLICY_DEFAULTS ← policyDefaults ← tutors[name].policy */
124
+ export function resolvePolicy(config, tutor) {
125
+ const layers = [config.policyDefaults, config.tutors[tutor]?.policy ?? {}];
126
+ const out = { ...POLICY_DEFAULTS, contextPack: { ...POLICY_DEFAULTS.contextPack }, scenes: { ...POLICY_DEFAULTS.scenes } };
104
127
  for (const p of layers) {
105
128
  if (p.replyMaxChars !== undefined)
106
129
  out.replyMaxChars = p.replyMaxChars;
@@ -116,12 +139,16 @@ export function resolvePolicy(config, teacher) {
116
139
  out.contextPack.recent = p.contextPack.recent;
117
140
  if (p.contextPack?.planLines !== undefined)
118
141
  out.contextPack.planLines = p.contextPack.planLines;
142
+ if (p.board !== undefined)
143
+ out.board = p.board;
144
+ if (p.scenes?.dailyMax !== undefined)
145
+ out.scenes.dailyMax = p.scenes.dailyMax;
119
146
  }
120
147
  return out;
121
148
  }
122
149
  /** 老师列表(带有效政策);kidOnly = 只要孩子端能看到的(enabled 且不 hidden) */
123
- export function listTeachers(config, opts = {}) {
124
- return Object.entries(config.teachers)
150
+ export function listTutors(config, opts = {}) {
151
+ return Object.entries(config.tutors)
125
152
  .filter(([, t]) => !opts.kidOnly || (t.enabled && !t.hidden))
126
153
  .map(([name, t]) => ({
127
154
  name,
@@ -134,11 +161,14 @@ export function listTeachers(config, opts = {}) {
134
161
  policy: resolvePolicy(config, name),
135
162
  }));
136
163
  }
137
- /** 预设模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
138
- export function fillPreset(argv, vars) {
164
+ /** 运行时模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
165
+ export function fillRuntime(argv, vars) {
139
166
  return argv.map((a) => a
140
167
  .replaceAll('{agent}', vars.agent)
141
168
  .replaceAll('{prompt}', vars.prompt)
142
169
  .replaceAll('{session}', vars.session ?? '')
143
170
  .replaceAll('{agentBody}', vars.agentBody ?? '{agentBody}'));
144
171
  }
172
+ export function fillTts(argv, vars) {
173
+ return argv.map((a) => a.replaceAll('{text}', vars.text).replaceAll('{voice}', vars.voice).replaceAll('{out}', vars.out));
174
+ }
@@ -9,6 +9,7 @@ export declare const FocusSchema: z.ZodObject<{
9
9
  artifact: z.ZodOptional<z.ZodString>;
10
10
  step: z.ZodOptional<z.ZodNumber>;
11
11
  circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
+ card: z.ZodOptional<z.ZodString>;
12
13
  }, z.core.$strip>;
13
14
  export type Focus = z.infer<typeof FocusSchema>;
14
15
  export declare const ContextPackSchema: z.ZodObject<{
@@ -23,11 +24,17 @@ export declare const ContextPackSchema: z.ZodObject<{
23
24
  artifact: z.ZodOptional<z.ZodString>;
24
25
  step: z.ZodOptional<z.ZodNumber>;
25
26
  circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ card: z.ZodOptional<z.ZodString>;
26
28
  }, z.core.$strip>>;
27
29
  plan: z.ZodDefault<z.ZodArray<z.ZodString>>;
28
30
  recent: z.ZodDefault<z.ZodArray<z.ZodObject<{
29
31
  date: z.ZodString;
30
32
  claim: z.ZodString;
31
33
  }, z.core.$strip>>>;
34
+ board: z.ZodOptional<z.ZodEnum<{
35
+ auto: "auto";
36
+ off: "off";
37
+ }>>;
38
+ cards: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
39
  }, z.core.$strip>;
33
40
  export type ContextPack = z.infer<typeof ContextPackSchema>;
@@ -11,6 +11,8 @@ export const FocusSchema = z.object({
11
11
  step: z.number().int().nonnegative().optional(),
12
12
  /** 圈了什么 */
13
13
  circled: z.array(z.string()).optional(),
14
+ /** 孩子发消息时正开着的那张卡(<job>/<n>) */
15
+ card: z.string().optional(),
14
16
  });
15
17
  export const ContextPackSchema = z.object({
16
18
  from: z.enum(MESSAGE_FROM),
@@ -23,4 +25,8 @@ export const ContextPackSchema = z.object({
23
25
  plan: z.array(z.string()).default([]),
24
26
  /** 最近 N 条本学科观察(已按 recent 截) */
25
27
  recent: z.array(z.object({ date: z.string(), claim: z.string() })).default([]),
28
+ /** 政策旋钮 board = off 时带上,老师只说话不出卡;auto 不写 */
29
+ board: z.enum(['auto', 'off']).optional(),
30
+ /** 上一轮之后孩子改过状态的卡,每张一句(「<job>/<n> choice「问题」 选了「B …」(答案:「…」)」) */
31
+ cards: z.array(z.string()).optional(),
26
32
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 会话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
3
- * 索引里的 kidText / artifacts 是精简视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
2
+ * 对话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
3
+ * 索引里的 kidText / artifacts 是孩子视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
4
4
  */
5
5
  import { z } from 'zod';
6
6
  export declare const DATE_RE: RegExp;
@@ -17,7 +17,12 @@ export declare const ConversationMessageSchema: z.ZodObject<{
17
17
  artifact: z.ZodOptional<z.ZodString>;
18
18
  step: z.ZodOptional<z.ZodNumber>;
19
19
  circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
+ card: z.ZodOptional<z.ZodString>;
20
21
  }, z.core.$strip>>;
22
+ action: z.ZodOptional<z.ZodEnum<{
23
+ continue: "continue";
24
+ submit: "submit";
25
+ }>>;
21
26
  result: z.ZodDefault<z.ZodEnum<{
22
27
  error: "error";
23
28
  running: "running";
@@ -26,14 +31,64 @@ export declare const ConversationMessageSchema: z.ZodObject<{
26
31
  costUsd: z.ZodOptional<z.ZodNumber>;
27
32
  kidText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
33
  artifacts: z.ZodDefault<z.ZodArray<z.ZodString>>;
34
+ runtime: z.ZodOptional<z.ZodString>;
35
+ holdup: z.ZodOptional<z.ZodNullable<z.ZodObject<{
36
+ question: z.ZodString;
37
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
38
+ label: z.ZodString;
39
+ note: z.ZodOptional<z.ZodString>;
40
+ recommended: z.ZodOptional<z.ZodBoolean>;
41
+ }, z.core.$strip>>>;
42
+ }, z.core.$strip>>>;
43
+ handoff: z.ZodOptional<z.ZodNullable<z.ZodObject<{
44
+ to: z.ZodString;
45
+ why: z.ZodOptional<z.ZodString>;
46
+ refs: z.ZodDefault<z.ZodArray<z.ZodString>>;
47
+ }, z.core.$strip>>>;
48
+ handoffJob: z.ZodOptional<z.ZodNullable<z.ZodObject<{
49
+ tutor: z.ZodString;
50
+ job: z.ZodString;
51
+ }, z.core.$strip>>>;
52
+ error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
53
+ section: z.ZodOptional<z.ZodNullable<z.ZodObject<{
54
+ cards: z.ZodArray<z.ZodObject<{
55
+ kind: z.ZodString;
56
+ props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
57
+ state: z.ZodOptional<z.ZodUnknown>;
58
+ assets: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
+ }, z.core.$strip>>;
60
+ lines: z.ZodArray<z.ZodObject<{
61
+ text: z.ZodString;
62
+ audio: z.ZodNullable<z.ZodString>;
63
+ marks: z.ZodArray<z.ZodObject<{
64
+ card: z.ZodNumber;
65
+ phrase: z.ZodString;
66
+ }, z.core.$strip>>;
67
+ ask: z.ZodBoolean;
68
+ anchor: z.ZodNullable<z.ZodNumber>;
69
+ cues: z.ZodArray<z.ZodObject<{
70
+ card: z.ZodNumber;
71
+ name: z.ZodString;
72
+ arg: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>>;
74
+ }, z.core.$strip>>;
75
+ partial: z.ZodOptional<z.ZodBoolean>;
76
+ }, z.core.$strip>>>;
77
+ parentText: z.ZodOptional<z.ZodString>;
78
+ warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
79
+ cards: z.ZodOptional<z.ZodArray<z.ZodObject<{
80
+ card: z.ZodString;
81
+ text: z.ZodString;
82
+ }, z.core.$strip>>>;
83
+ audio: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
84
  }, z.core.$strip>;
30
85
  export type ConversationMessage = z.infer<typeof ConversationMessageSchema>;
31
86
  export declare const ConversationIndexSchema: z.ZodObject<{
32
- teacher: z.ZodString;
87
+ tutor: z.ZodString;
33
88
  date: z.ZodString;
34
89
  session: z.ZodDefault<z.ZodNullable<z.ZodObject<{
35
90
  id: z.ZodString;
36
- agent: z.ZodString;
91
+ runtime: z.ZodString;
37
92
  }, z.core.$strip>>>;
38
93
  messages: z.ZodDefault<z.ZodArray<z.ZodObject<{
39
94
  job: z.ZodString;
@@ -48,7 +103,12 @@ export declare const ConversationIndexSchema: z.ZodObject<{
48
103
  artifact: z.ZodOptional<z.ZodString>;
49
104
  step: z.ZodOptional<z.ZodNumber>;
50
105
  circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
106
+ card: z.ZodOptional<z.ZodString>;
51
107
  }, z.core.$strip>>;
108
+ action: z.ZodOptional<z.ZodEnum<{
109
+ continue: "continue";
110
+ submit: "submit";
111
+ }>>;
52
112
  result: z.ZodDefault<z.ZodEnum<{
53
113
  error: "error";
54
114
  running: "running";
@@ -57,6 +117,56 @@ export declare const ConversationIndexSchema: z.ZodObject<{
57
117
  costUsd: z.ZodOptional<z.ZodNumber>;
58
118
  kidText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
59
119
  artifacts: z.ZodDefault<z.ZodArray<z.ZodString>>;
120
+ runtime: z.ZodOptional<z.ZodString>;
121
+ holdup: z.ZodOptional<z.ZodNullable<z.ZodObject<{
122
+ question: z.ZodString;
123
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
124
+ label: z.ZodString;
125
+ note: z.ZodOptional<z.ZodString>;
126
+ recommended: z.ZodOptional<z.ZodBoolean>;
127
+ }, z.core.$strip>>>;
128
+ }, z.core.$strip>>>;
129
+ handoff: z.ZodOptional<z.ZodNullable<z.ZodObject<{
130
+ to: z.ZodString;
131
+ why: z.ZodOptional<z.ZodString>;
132
+ refs: z.ZodDefault<z.ZodArray<z.ZodString>>;
133
+ }, z.core.$strip>>>;
134
+ handoffJob: z.ZodOptional<z.ZodNullable<z.ZodObject<{
135
+ tutor: z.ZodString;
136
+ job: z.ZodString;
137
+ }, z.core.$strip>>>;
138
+ error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
139
+ section: z.ZodOptional<z.ZodNullable<z.ZodObject<{
140
+ cards: z.ZodArray<z.ZodObject<{
141
+ kind: z.ZodString;
142
+ props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
143
+ state: z.ZodOptional<z.ZodUnknown>;
144
+ assets: z.ZodOptional<z.ZodArray<z.ZodString>>;
145
+ }, z.core.$strip>>;
146
+ lines: z.ZodArray<z.ZodObject<{
147
+ text: z.ZodString;
148
+ audio: z.ZodNullable<z.ZodString>;
149
+ marks: z.ZodArray<z.ZodObject<{
150
+ card: z.ZodNumber;
151
+ phrase: z.ZodString;
152
+ }, z.core.$strip>>;
153
+ ask: z.ZodBoolean;
154
+ anchor: z.ZodNullable<z.ZodNumber>;
155
+ cues: z.ZodArray<z.ZodObject<{
156
+ card: z.ZodNumber;
157
+ name: z.ZodString;
158
+ arg: z.ZodOptional<z.ZodString>;
159
+ }, z.core.$strip>>;
160
+ }, z.core.$strip>>;
161
+ partial: z.ZodOptional<z.ZodBoolean>;
162
+ }, z.core.$strip>>>;
163
+ parentText: z.ZodOptional<z.ZodString>;
164
+ warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
165
+ cards: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ card: z.ZodString;
167
+ text: z.ZodString;
168
+ }, z.core.$strip>>>;
169
+ audio: z.ZodOptional<z.ZodNullable<z.ZodString>>;
60
170
  }, z.core.$strip>>>;
61
171
  costUsd: z.ZodDefault<z.ZodNumber>;
62
172
  }, z.core.$strip>;
@@ -1,30 +1,54 @@
1
1
  /**
2
- * 会话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
3
- * 索引里的 kidText / artifacts 是精简视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
2
+ * 对话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
3
+ * 索引里的 kidText / artifacts 是孩子视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
4
4
  */
5
5
  import { z } from 'zod';
6
6
  import { FocusSchema, MESSAGE_FROM } from "./context-pack.js";
7
+ import { BoardSectionSchema } from "./board.js";
8
+ import { HandoffSchema, HoldupAskSchema } from "./sections.js";
7
9
  export const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
8
10
  export const ConversationMessageSchema = z.object({
9
- /** 任务 id,同时是转录文件名的一段:<date>.<job>.log */
11
+ /** 一轮的 id(job),也是转录文件名的一段:<date>.<job>.log */
10
12
  job: z.string().min(1),
11
13
  at: z.string().min(1),
12
14
  from: z.enum(MESSAGE_FROM),
13
15
  /** 消息原文(不含上下文包) */
14
16
  text: z.string(),
15
17
  focus: FocusSchema.optional(),
18
+ /** 孩子端的两个不打字的动作:继续(不计每日上限)/ 交给老师(把卡上的状态交出去) */
19
+ action: z.enum(['continue', 'submit']).optional(),
16
20
  result: z.enum(['running', 'ok', 'error']).default('running'),
17
21
  costUsd: z.number().optional(),
18
22
  /** 孩子视图文本;null = 这次运行没有给孩子的话(出错或空) */
19
23
  kidText: z.string().nullable().optional(),
20
24
  /** 本次运行新增的产物 id */
21
25
  artifacts: z.array(z.string()).default([]),
26
+ /** 跑这条用的运行时名(runtimes 里的键);换运行时时新开会话 */
27
+ runtime: z.string().optional(),
28
+ /** 最终文本里剥出来的「待裁量」段:家长视图渲染成选项按钮(R2 物化,免得页面再解析日志) */
29
+ holdup: HoldupAskSchema.nullable().optional(),
30
+ /** 最终文本里剥出来的「转交」段:应用据此自动起目标老师的一轮(from: system) */
31
+ handoff: HandoffSchema.nullable().optional(),
32
+ /** 自动转交起的那一轮:目标老师与 job;没起(上限、忙、老师不在)的原因在 warnings */
33
+ handoffJob: z.object({ tutor: z.string(), job: z.string() }).nullable().optional(),
34
+ /** 不 ok 时的原因(subtype / terminal_reason),家长视图红条 */
35
+ error: z.string().nullable().optional(),
36
+ /** 最终文本解析出的板书节(卡 + 讲稿;孩子端下发前剥答案);null = 这轮没有 */
37
+ section: BoardSectionSchema.nullable().optional(),
38
+ /** 最终文本里第一个 H2 起给家长的尾巴(「## 家长」等),孩子看不到 */
39
+ parentText: z.string().optional(),
40
+ /** 解析板书时的提醒(卡没解析成等),家长视图显示;孩子端不报 */
41
+ warnings: z.array(z.string()).optional(),
42
+ /** 这条消息带给老师的卡(上一轮之后孩子改过状态的):id 与 describe 出的那句;家长视图显示「孩子在板书上做的」 */
43
+ cards: z.array(z.object({ card: z.string().min(1), text: z.string() })).optional(),
44
+ /** kidText 的配音文件名(conversations/<老师>/ 下,如 2026-09-09.1620-1.mp3);null = 没合成(老师没配音色、tts 运行时没配或失败) */
45
+ audio: z.string().nullable().optional(),
22
46
  });
23
47
  export const ConversationIndexSchema = z.object({
24
- teacher: z.string().min(1),
48
+ tutor: z.string().min(1),
25
49
  date: z.string().regex(DATE_RE),
26
50
  /** 当天会话;首条消息跑完后写入,之后每条 --resume 它 */
27
- session: z.object({ id: z.string().min(1), agent: z.string().min(1) }).nullable().default(null),
51
+ session: z.object({ id: z.string().min(1), runtime: z.string().min(1) }).nullable().default(null),
28
52
  messages: z.array(ConversationMessageSchema).default([]),
29
53
  costUsd: z.number().default(0),
30
54
  });
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * cotutor 契约(类型即文档)。每个文件一个契约面:
3
- * config cotutor.json(老师表、政策、运行时预设、paths 角色)
3
+ * config cotutor.json(老师表、政策、运行时、paths 角色)
4
4
  * context-pack 每条消息前的上下文包
5
- * conversation 一老师一天一份的会话索引
5
+ * conversation 一老师一天一份的对话索引
6
6
  * ledger 观察与产物两本追加式账本
7
7
  * sections 最终文本里的「待裁量」「转交」段
8
8
  * plan 计划文件
9
+ * timetable 课程表(vault 里的 markdown 表)
9
10
  * 运行时校验用 zod,失败信息经 explainIssues 变成修复指南。
10
11
  */
11
12
  export * from './issues.ts';
@@ -14,4 +15,7 @@ export * from './context-pack.ts';
14
15
  export * from './conversation.ts';
15
16
  export * from './ledger.ts';
16
17
  export * from './sections.ts';
18
+ export * from './board.ts';
17
19
  export * from './plan.ts';
20
+ export * from './timetable.ts';
21
+ export * from './json-schema.ts';