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.
- package/README.md +35 -24
- package/agents/chinese-tutor.md +67 -0
- package/agents/{homework-aide.md → homework-tutor.md} +4 -4
- package/agents/math-tutor.md +68 -0
- package/agents/reading-tutor.md +65 -0
- package/agents/scene-maker.md +41 -0
- package/dist/cards/canvas.d.ts +22 -0
- package/dist/cards/canvas.js +65 -0
- package/dist/cards/choice.d.ts +20 -0
- package/dist/cards/choice.js +76 -0
- package/dist/cards/code.d.ts +9 -0
- package/dist/cards/code.js +13 -0
- package/dist/cards/fill.d.ts +14 -0
- package/dist/cards/fill.js +64 -0
- package/dist/cards/image.d.ts +13 -0
- package/dist/cards/image.js +35 -0
- package/dist/cards/index.d.ts +48 -0
- package/dist/cards/index.js +115 -0
- package/dist/cards/kind.d.ts +27 -0
- package/dist/cards/kind.js +7 -0
- package/dist/cards/read.d.ts +8 -0
- package/dist/cards/read.js +27 -0
- package/dist/cards/scene.d.ts +24 -0
- package/dist/cards/scene.js +67 -0
- package/dist/cards/text.d.ts +18 -0
- package/dist/cards/text.js +41 -0
- package/dist/cli/cert.d.ts +7 -0
- package/dist/cli/cert.js +36 -0
- package/dist/cli/doctor.d.ts +5 -2
- package/dist/cli/doctor.js +183 -18
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +41 -39
- package/dist/cli/main.js +128 -8
- package/dist/cli/serve.d.ts +15 -4
- package/dist/cli/serve.js +40 -6
- package/dist/cli/skeleton.d.ts +23 -5
- package/dist/cli/skeleton.js +131 -29
- package/dist/cli/skills.d.ts +40 -0
- package/dist/cli/skills.js +170 -0
- package/dist/cli/tutors.d.ts +84 -0
- package/dist/cli/tutors.js +274 -0
- package/dist/cli/workspace.d.ts +9 -1
- package/dist/cli/workspace.js +25 -4
- package/dist/lib/agent-file.d.ts +1 -1
- package/dist/lib/board.d.ts +15 -0
- package/dist/lib/board.js +107 -0
- package/dist/lib/context-pack.js +10 -1
- package/dist/lib/conversation.d.ts +43 -6
- package/dist/lib/conversation.js +53 -9
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +4 -0
- package/dist/lib/kid-board.d.ts +148 -0
- package/dist/lib/kid-board.js +294 -0
- package/dist/lib/kid-view.d.ts +44 -7
- package/dist/lib/kid-view.js +63 -8
- package/dist/lib/ledger.d.ts +8 -0
- package/dist/lib/ledger.js +8 -0
- package/dist/lib/plan.d.ts +2 -0
- package/dist/lib/plan.js +9 -0
- package/dist/lib/run-plan.d.ts +30 -0
- package/dist/lib/run-plan.js +32 -0
- package/dist/lib/stream.d.ts +15 -0
- package/dist/lib/stream.js +69 -0
- package/dist/lib/timetable.d.ts +22 -0
- package/dist/lib/timetable.js +106 -0
- package/dist/schema/board.d.ts +59 -0
- package/dist/schema/board.js +30 -0
- package/dist/schema/config.d.ts +76 -18
- package/dist/schema/config.js +67 -37
- package/dist/schema/context-pack.d.ts +7 -0
- package/dist/schema/context-pack.js +6 -0
- package/dist/schema/conversation.d.ts +114 -4
- package/dist/schema/conversation.js +29 -5
- package/dist/schema/index.d.ts +6 -2
- package/dist/schema/index.js +6 -2
- package/dist/schema/json-schema.d.ts +2 -0
- package/dist/schema/json-schema.js +23 -0
- package/dist/schema/ledger.d.ts +1 -1
- package/dist/schema/timetable.d.ts +26 -0
- package/dist/schema/timetable.js +18 -0
- package/dist/server/app.d.ts +67 -6
- package/dist/server/app.js +399 -38
- package/dist/server/kid-page.d.ts +1 -0
- package/dist/server/kid-page.js +768 -0
- package/dist/server/mock.d.ts +58 -0
- package/dist/server/mock.js +403 -0
- package/dist/server/parent-page.d.ts +8 -0
- package/dist/server/parent-page.js +393 -0
- package/dist/server/runner.d.ts +64 -0
- package/dist/server/runner.js +277 -0
- package/dist/server/scene-props.d.ts +16 -0
- package/dist/server/scene-props.js +39 -0
- package/dist/server/stage.d.ts +15 -0
- package/dist/server/stage.js +69 -0
- package/dist/server/store.d.ts +43 -0
- package/dist/server/store.js +210 -0
- package/dist/server/tts.d.ts +51 -0
- package/dist/server/tts.js +99 -0
- package/dist/stage/Assistant-Bold-ZDZZ6JHA.woff2 +0 -0
- package/dist/stage/Assistant-Medium-DZ25RZU3.woff2 +0 -0
- package/dist/stage/Assistant-Regular-PLF2XOGW.woff2 +0 -0
- package/dist/stage/Assistant-SemiBold-CZ5MX6FK.woff2 +0 -0
- package/dist/stage/index.html +10 -0
- package/dist/stage/stage.css +1 -0
- package/dist/stage/stage.js +4383 -0
- package/package.json +13 -4
- package/agents/chinese-teacher.md +0 -19
- package/agents/math-teacher.md +0 -19
- package/agents/reading-teacher.md +0 -19
package/dist/schema/config.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
|
|
3
|
-
* `paths` 角色映射 + `
|
|
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
|
|
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
|
|
99
|
+
export type Tutor = z.infer<typeof TutorSchema>;
|
|
78
100
|
/**
|
|
79
|
-
*
|
|
101
|
+
* 运行时:一个 CLI 一组 {run, resume} 命令模板。占位符:
|
|
80
102
|
* {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
|
|
81
|
-
* 政策旋钮(预算、轮数、模型)写进模板,换 agent
|
|
103
|
+
* 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换运行时。
|
|
82
104
|
*/
|
|
83
|
-
export declare const
|
|
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
|
|
88
|
-
export declare const
|
|
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
|
-
|
|
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
|
-
|
|
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 ←
|
|
161
|
-
export declare function resolvePolicy(config: CotutorConfig,
|
|
162
|
-
export interface
|
|
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
|
|
226
|
+
export declare function listTutors(config: CotutorConfig, opts?: {
|
|
174
227
|
kidOnly?: boolean;
|
|
175
|
-
}):
|
|
176
|
-
/**
|
|
177
|
-
export declare function
|
|
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[];
|
package/dist/schema/config.js
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
|
|
3
|
-
* `paths` 角色映射 + `
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
68
|
+
* 运行时:一个 CLI 一组 {run, resume} 命令模板。占位符:
|
|
59
69
|
* {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
|
|
60
|
-
* 政策旋钮(预算、轮数、模型)写进模板,换 agent
|
|
70
|
+
* 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换运行时。
|
|
61
71
|
*/
|
|
62
|
-
export const
|
|
63
|
-
export const
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
115
|
+
if (!(c.runtimes.default in c.runtimes) || c.runtimes.default === 'default') {
|
|
93
116
|
ctx.addIssue({
|
|
94
117
|
code: 'custom',
|
|
95
|
-
path: ['
|
|
96
|
-
message:
|
|
118
|
+
path: ['runtimes', 'default'],
|
|
119
|
+
message: `运行时 "${c.runtimes.default}" 不存在;runtimes 里要有同名的 {run, resume}`,
|
|
97
120
|
});
|
|
98
121
|
}
|
|
99
122
|
});
|
|
100
|
-
/** 老师的有效政策 = POLICY_DEFAULTS ← policyDefaults ←
|
|
101
|
-
export function resolvePolicy(config,
|
|
102
|
-
const layers = [config.policyDefaults, config.
|
|
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
|
|
124
|
-
return Object.entries(config.
|
|
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
|
-
/**
|
|
138
|
-
export function
|
|
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
|
-
*
|
|
3
|
-
* 索引里的 kidText / artifacts
|
|
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
|
-
|
|
87
|
+
tutor: z.ZodString;
|
|
33
88
|
date: z.ZodString;
|
|
34
89
|
session: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
35
90
|
id: z.ZodString;
|
|
36
|
-
|
|
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
|
-
*
|
|
3
|
-
* 索引里的 kidText / artifacts
|
|
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
|
-
/**
|
|
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
|
-
|
|
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),
|
|
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
|
});
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* cotutor 契约(类型即文档)。每个文件一个契约面:
|
|
3
|
-
* config cotutor.json(
|
|
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';
|