cotutor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/agents/chinese-teacher.md +19 -0
- package/agents/homework-aide.md +17 -0
- package/agents/math-teacher.md +19 -0
- package/agents/planner.md +18 -0
- package/agents/reading-teacher.md +19 -0
- package/bin/cotutor.js +17 -0
- package/dist/cli/doctor.d.ts +19 -0
- package/dist/cli/doctor.js +190 -0
- package/dist/cli/init.d.ts +17 -0
- package/dist/cli/init.js +114 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +123 -0
- package/dist/cli/serve.d.ts +19 -0
- package/dist/cli/serve.js +22 -0
- package/dist/cli/skeleton.d.ts +21 -0
- package/dist/cli/skeleton.js +82 -0
- package/dist/cli/workspace.d.ts +61 -0
- package/dist/cli/workspace.js +199 -0
- package/dist/lib/agent-file.d.ts +10 -0
- package/dist/lib/agent-file.js +16 -0
- package/dist/lib/context-pack.d.ts +12 -0
- package/dist/lib/context-pack.js +46 -0
- package/dist/lib/conversation.d.ts +28 -0
- package/dist/lib/conversation.js +44 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/kid-view.d.ts +26 -0
- package/dist/lib/kid-view.js +38 -0
- package/dist/lib/ledger.d.ts +21 -0
- package/dist/lib/ledger.js +92 -0
- package/dist/lib/plan.d.ts +12 -0
- package/dist/lib/plan.js +78 -0
- package/dist/lib/sections.d.ts +12 -0
- package/dist/lib/sections.js +151 -0
- package/dist/lib/transcript.d.ts +36 -0
- package/dist/lib/transcript.js +95 -0
- package/dist/schema/config.d.ts +182 -0
- package/dist/schema/config.js +144 -0
- package/dist/schema/context-pack.d.ts +33 -0
- package/dist/schema/context-pack.js +26 -0
- package/dist/schema/conversation.d.ts +63 -0
- package/dist/schema/conversation.js +30 -0
- package/dist/schema/index.d.ts +17 -0
- package/dist/schema/index.js +17 -0
- package/dist/schema/issues.d.ts +6 -0
- package/dist/schema/issues.js +37 -0
- package/dist/schema/ledger.d.ts +100 -0
- package/dist/schema/ledger.js +44 -0
- package/dist/schema/plan.d.ts +26 -0
- package/dist/schema/plan.js +19 -0
- package/dist/schema/sections.d.ts +30 -0
- package/dist/schema/sections.js +26 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.js +45 -0
- package/package.json +62 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
|
|
3
|
+
* `paths` 角色映射 + `agents` 的 {run, resume} 命令模板——再加老师表与政策。
|
|
4
|
+
* 字段归属铁律:老师的 .md 文件里只有 name / description / maxTurns / permissionMode / memory + 正文;
|
|
5
|
+
* 人设(display / avatar / voice)、政策、开关全在这里。助教团页只改本文件,永不改链进来的定义文件。
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
/** 回复形式阶梯(《cotutor产品规划.md》):L0 确定性资源 / L1 口答 / L2 快卡 / L3 补讲 / L4 整包 */
|
|
9
|
+
export declare const REPLY_FORMS: readonly ["L0", "L1", "L2", "L3", "L4"];
|
|
10
|
+
export type ReplyForm = (typeof REPLY_FORMS)[number];
|
|
11
|
+
export declare const PolicySchema: z.ZodObject<{
|
|
12
|
+
replyMaxChars: z.ZodNumber;
|
|
13
|
+
dailyMessages: z.ZodNumber;
|
|
14
|
+
dailyRegen: z.ZodNumber;
|
|
15
|
+
reviewGate: z.ZodBoolean;
|
|
16
|
+
forms: z.ZodArray<z.ZodEnum<{
|
|
17
|
+
L0: "L0";
|
|
18
|
+
L1: "L1";
|
|
19
|
+
L2: "L2";
|
|
20
|
+
L3: "L3";
|
|
21
|
+
L4: "L4";
|
|
22
|
+
}>>;
|
|
23
|
+
contextPack: z.ZodObject<{
|
|
24
|
+
recent: z.ZodNumber;
|
|
25
|
+
planLines: z.ZodNumber;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type Policy = z.infer<typeof PolicySchema>;
|
|
29
|
+
/** 政策补丁:老师条目与 policyDefaults 都是补丁,逐层盖在 POLICY_DEFAULTS 上 */
|
|
30
|
+
export declare const PolicyPatchSchema: z.ZodObject<{
|
|
31
|
+
replyMaxChars: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
dailyMessages: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
dailyRegen: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
reviewGate: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
forms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
36
|
+
L0: "L0";
|
|
37
|
+
L1: "L1";
|
|
38
|
+
L2: "L2";
|
|
39
|
+
L3: "L3";
|
|
40
|
+
L4: "L4";
|
|
41
|
+
}>>>;
|
|
42
|
+
contextPack: z.ZodOptional<z.ZodObject<{
|
|
43
|
+
recent: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
planLines: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
export type PolicyPatch = z.infer<typeof PolicyPatchSchema>;
|
|
48
|
+
/** 2026-09-08 拍板的缺省:60 字、30 条/日、3 次重生、验收关、上下文包各 10 */
|
|
49
|
+
export declare const POLICY_DEFAULTS: Policy;
|
|
50
|
+
/** agent 名:与 .claude/agents/<name>.md 的 frontmatter name 一致,小写字母数字连字符 */
|
|
51
|
+
export declare const AGENT_NAME_RE: RegExp;
|
|
52
|
+
export declare const TeacherSchema: z.ZodObject<{
|
|
53
|
+
display: z.ZodString;
|
|
54
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
55
|
+
avatar: z.ZodOptional<z.ZodString>;
|
|
56
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
57
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
58
|
+
hidden: z.ZodDefault<z.ZodBoolean>;
|
|
59
|
+
policy: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
replyMaxChars: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
dailyMessages: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
dailyRegen: z.ZodOptional<z.ZodNumber>;
|
|
63
|
+
reviewGate: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
forms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
65
|
+
L0: "L0";
|
|
66
|
+
L1: "L1";
|
|
67
|
+
L2: "L2";
|
|
68
|
+
L3: "L3";
|
|
69
|
+
L4: "L4";
|
|
70
|
+
}>>>;
|
|
71
|
+
contextPack: z.ZodOptional<z.ZodObject<{
|
|
72
|
+
recent: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
planLines: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
}, z.core.$strip>>;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
export type Teacher = z.infer<typeof TeacherSchema>;
|
|
78
|
+
/**
|
|
79
|
+
* 运行时预设:一个 CLI 一组 {run, resume} 命令模板。占位符:
|
|
80
|
+
* {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
|
|
81
|
+
* 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换预设。
|
|
82
|
+
*/
|
|
83
|
+
export declare const AgentPresetSchema: z.ZodObject<{
|
|
84
|
+
run: z.ZodArray<z.ZodString>;
|
|
85
|
+
resume: z.ZodArray<z.ZodString>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
export type AgentPreset = z.infer<typeof AgentPresetSchema>;
|
|
88
|
+
export declare const AgentsSchema: z.ZodObject<{
|
|
89
|
+
default: z.ZodString;
|
|
90
|
+
}, z.core.$catchall<z.ZodObject<{
|
|
91
|
+
run: z.ZodArray<z.ZodString>;
|
|
92
|
+
resume: z.ZodArray<z.ZodString>;
|
|
93
|
+
}, z.core.$strip>>>;
|
|
94
|
+
/** paths 里 CLI 认识的角色;其余角色原样保留给应用层。vault 侧角色相对 vault 解析,没配 vault 就相对 workspace 根 */
|
|
95
|
+
export declare const PATH_ROLES: readonly ["vault", "photos", "diary", "plans", "profile", "timetable"];
|
|
96
|
+
export type PathRole = (typeof PATH_ROLES)[number];
|
|
97
|
+
export declare const ROLE_DEFAULTS: Record<PathRole, string>;
|
|
98
|
+
export declare const CotutorConfigSchema: z.ZodObject<{
|
|
99
|
+
version: z.ZodLiteral<1>;
|
|
100
|
+
title: z.ZodDefault<z.ZodString>;
|
|
101
|
+
kid: z.ZodObject<{
|
|
102
|
+
slug: z.ZodString;
|
|
103
|
+
name: z.ZodOptional<z.ZodString>;
|
|
104
|
+
grade: z.ZodOptional<z.ZodString>;
|
|
105
|
+
}, z.core.$strip>;
|
|
106
|
+
server: z.ZodDefault<z.ZodObject<{
|
|
107
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
108
|
+
}, z.core.$strip>>;
|
|
109
|
+
paths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
110
|
+
policyDefaults: z.ZodDefault<z.ZodObject<{
|
|
111
|
+
replyMaxChars: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
dailyMessages: z.ZodOptional<z.ZodNumber>;
|
|
113
|
+
dailyRegen: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
reviewGate: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
forms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
116
|
+
L0: "L0";
|
|
117
|
+
L1: "L1";
|
|
118
|
+
L2: "L2";
|
|
119
|
+
L3: "L3";
|
|
120
|
+
L4: "L4";
|
|
121
|
+
}>>>;
|
|
122
|
+
contextPack: z.ZodOptional<z.ZodObject<{
|
|
123
|
+
recent: z.ZodOptional<z.ZodNumber>;
|
|
124
|
+
planLines: z.ZodOptional<z.ZodNumber>;
|
|
125
|
+
}, z.core.$strip>>;
|
|
126
|
+
}, z.core.$strip>>;
|
|
127
|
+
teachers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
128
|
+
display: z.ZodString;
|
|
129
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
130
|
+
avatar: z.ZodOptional<z.ZodString>;
|
|
131
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
132
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
133
|
+
hidden: z.ZodDefault<z.ZodBoolean>;
|
|
134
|
+
policy: z.ZodOptional<z.ZodObject<{
|
|
135
|
+
replyMaxChars: z.ZodOptional<z.ZodNumber>;
|
|
136
|
+
dailyMessages: z.ZodOptional<z.ZodNumber>;
|
|
137
|
+
dailyRegen: z.ZodOptional<z.ZodNumber>;
|
|
138
|
+
reviewGate: z.ZodOptional<z.ZodBoolean>;
|
|
139
|
+
forms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
140
|
+
L0: "L0";
|
|
141
|
+
L1: "L1";
|
|
142
|
+
L2: "L2";
|
|
143
|
+
L3: "L3";
|
|
144
|
+
L4: "L4";
|
|
145
|
+
}>>>;
|
|
146
|
+
contextPack: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
recent: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
planLines: z.ZodOptional<z.ZodNumber>;
|
|
149
|
+
}, z.core.$strip>>;
|
|
150
|
+
}, z.core.$strip>>;
|
|
151
|
+
}, z.core.$strip>>>;
|
|
152
|
+
agents: z.ZodObject<{
|
|
153
|
+
default: z.ZodString;
|
|
154
|
+
}, z.core.$catchall<z.ZodObject<{
|
|
155
|
+
run: z.ZodArray<z.ZodString>;
|
|
156
|
+
resume: z.ZodArray<z.ZodString>;
|
|
157
|
+
}, z.core.$strip>>>;
|
|
158
|
+
}, z.core.$strip>;
|
|
159
|
+
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 {
|
|
163
|
+
name: string;
|
|
164
|
+
display: string;
|
|
165
|
+
subject?: string;
|
|
166
|
+
avatar?: string;
|
|
167
|
+
voice?: string;
|
|
168
|
+
enabled: boolean;
|
|
169
|
+
hidden: boolean;
|
|
170
|
+
policy: Policy;
|
|
171
|
+
}
|
|
172
|
+
/** 老师列表(带有效政策);kidOnly = 只要孩子端能看到的(enabled 且不 hidden) */
|
|
173
|
+
export declare function listTeachers(config: CotutorConfig, opts?: {
|
|
174
|
+
kidOnly?: boolean;
|
|
175
|
+
}): TeacherView[];
|
|
176
|
+
/** 预设模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
|
|
177
|
+
export declare function fillPreset(argv: readonly string[], vars: {
|
|
178
|
+
agent: string;
|
|
179
|
+
prompt: string;
|
|
180
|
+
session?: string;
|
|
181
|
+
agentBody?: string;
|
|
182
|
+
}): string[];
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cotutor.json:每个 workspace 一份,一孩一 workspace。承接 drawtell.json 的做法——
|
|
3
|
+
* `paths` 角色映射 + `agents` 的 {run, resume} 命令模板——再加老师表与政策。
|
|
4
|
+
* 字段归属铁律:老师的 .md 文件里只有 name / description / maxTurns / permissionMode / memory + 正文;
|
|
5
|
+
* 人设(display / avatar / voice)、政策、开关全在这里。助教团页只改本文件,永不改链进来的定义文件。
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
/** 回复形式阶梯(《cotutor产品规划.md》):L0 确定性资源 / L1 口答 / L2 快卡 / L3 补讲 / L4 整包 */
|
|
9
|
+
export const REPLY_FORMS = ['L0', 'L1', 'L2', 'L3', 'L4'];
|
|
10
|
+
export const PolicySchema = z.object({
|
|
11
|
+
/** 老师单条回复给孩子看的字数上限;超出截断并改走 L3(《cotutor契约草案.md》§4) */
|
|
12
|
+
replyMaxChars: z.number().int().positive(),
|
|
13
|
+
/** 每孩每日消息上限;超限老师头像灰掉 */
|
|
14
|
+
dailyMessages: z.number().int().nonnegative(),
|
|
15
|
+
/** 每日重生上限 */
|
|
16
|
+
dailyRegen: z.number().int().nonnegative(),
|
|
17
|
+
/** 验收开关:false = 产物直接给孩子(缺省);true = 先经家长验收 */
|
|
18
|
+
reviewGate: z.boolean(),
|
|
19
|
+
/** 这位老师可用的回复形式 */
|
|
20
|
+
forms: z.array(z.enum(REPLY_FORMS)),
|
|
21
|
+
/** 上下文包的两个数:最近观察条数、计划行数 */
|
|
22
|
+
contextPack: z.object({ recent: z.number().int().nonnegative(), planLines: z.number().int().nonnegative() }),
|
|
23
|
+
});
|
|
24
|
+
/** 政策补丁:老师条目与 policyDefaults 都是补丁,逐层盖在 POLICY_DEFAULTS 上 */
|
|
25
|
+
export const PolicyPatchSchema = z.object({
|
|
26
|
+
replyMaxChars: PolicySchema.shape.replyMaxChars.optional(),
|
|
27
|
+
dailyMessages: PolicySchema.shape.dailyMessages.optional(),
|
|
28
|
+
dailyRegen: PolicySchema.shape.dailyRegen.optional(),
|
|
29
|
+
reviewGate: PolicySchema.shape.reviewGate.optional(),
|
|
30
|
+
forms: PolicySchema.shape.forms.optional(),
|
|
31
|
+
contextPack: z.object({ recent: z.number().int().nonnegative().optional(), planLines: z.number().int().nonnegative().optional() }).optional(),
|
|
32
|
+
});
|
|
33
|
+
/** 2026-09-08 拍板的缺省:60 字、30 条/日、3 次重生、验收关、上下文包各 10 */
|
|
34
|
+
export const POLICY_DEFAULTS = {
|
|
35
|
+
replyMaxChars: 60,
|
|
36
|
+
dailyMessages: 30,
|
|
37
|
+
dailyRegen: 3,
|
|
38
|
+
reviewGate: false,
|
|
39
|
+
forms: ['L0', 'L1', 'L3', 'L4'],
|
|
40
|
+
contextPack: { recent: 10, planLines: 10 },
|
|
41
|
+
};
|
|
42
|
+
/** agent 名:与 .claude/agents/<name>.md 的 frontmatter name 一致,小写字母数字连字符 */
|
|
43
|
+
export const AGENT_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
44
|
+
export const TeacherSchema = z.object({
|
|
45
|
+
/** 显示名(孩子端头像下的字、计划文件的 H2 标题) */
|
|
46
|
+
display: z.string().min(1),
|
|
47
|
+
subject: z.string().optional(),
|
|
48
|
+
/** 头像:一个 emoji 或图片相对路径 */
|
|
49
|
+
avatar: z.string().optional(),
|
|
50
|
+
/** voxtell 音色 id(2026-09-08 拍板);没有 = 用 voxtell 缺省 */
|
|
51
|
+
voice: z.string().optional(),
|
|
52
|
+
enabled: z.boolean().default(true),
|
|
53
|
+
/** 孩子端不露(规划老师、记账员这类) */
|
|
54
|
+
hidden: z.boolean().default(false),
|
|
55
|
+
policy: PolicyPatchSchema.optional(),
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* 运行时预设:一个 CLI 一组 {run, resume} 命令模板。占位符:
|
|
59
|
+
* {agent} 老师名 / {agentBody} 老师文件正文(给没有 --agent 的 CLI 塞系统提示)/ {prompt} 消息 / {session} 会话 id。
|
|
60
|
+
* 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换预设。
|
|
61
|
+
*/
|
|
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);
|
|
64
|
+
/** paths 里 CLI 认识的角色;其余角色原样保留给应用层。vault 侧角色相对 vault 解析,没配 vault 就相对 workspace 根 */
|
|
65
|
+
export const PATH_ROLES = ['vault', 'photos', 'diary', 'plans', 'profile', 'timetable'];
|
|
66
|
+
export const ROLE_DEFAULTS = {
|
|
67
|
+
vault: '.',
|
|
68
|
+
photos: 'photos',
|
|
69
|
+
diary: 'diary',
|
|
70
|
+
plans: 'plans',
|
|
71
|
+
profile: 'profile.md',
|
|
72
|
+
timetable: 'timetable.md',
|
|
73
|
+
};
|
|
74
|
+
export const CotutorConfigSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
version: z.literal(1),
|
|
77
|
+
/** 孩子端标题,无品牌 */
|
|
78
|
+
title: z.string().default('cotutor'),
|
|
79
|
+
kid: z.object({
|
|
80
|
+
/** 短名,进路径 ~/cotutor/<slug>/,不是真名 */
|
|
81
|
+
slug: z.string().regex(AGENT_NAME_RE),
|
|
82
|
+
name: z.string().optional(),
|
|
83
|
+
grade: z.string().optional(),
|
|
84
|
+
}),
|
|
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,
|
|
90
|
+
})
|
|
91
|
+
.superRefine((c, ctx) => {
|
|
92
|
+
if (!(c.agents.default in c.agents) || c.agents.default === 'default') {
|
|
93
|
+
ctx.addIssue({
|
|
94
|
+
code: 'custom',
|
|
95
|
+
path: ['agents', 'default'],
|
|
96
|
+
message: `预设 "${c.agents.default}" 不存在;agents 里要有同名的 {run, resume}`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
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 } };
|
|
104
|
+
for (const p of layers) {
|
|
105
|
+
if (p.replyMaxChars !== undefined)
|
|
106
|
+
out.replyMaxChars = p.replyMaxChars;
|
|
107
|
+
if (p.dailyMessages !== undefined)
|
|
108
|
+
out.dailyMessages = p.dailyMessages;
|
|
109
|
+
if (p.dailyRegen !== undefined)
|
|
110
|
+
out.dailyRegen = p.dailyRegen;
|
|
111
|
+
if (p.reviewGate !== undefined)
|
|
112
|
+
out.reviewGate = p.reviewGate;
|
|
113
|
+
if (p.forms !== undefined)
|
|
114
|
+
out.forms = [...p.forms];
|
|
115
|
+
if (p.contextPack?.recent !== undefined)
|
|
116
|
+
out.contextPack.recent = p.contextPack.recent;
|
|
117
|
+
if (p.contextPack?.planLines !== undefined)
|
|
118
|
+
out.contextPack.planLines = p.contextPack.planLines;
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** 老师列表(带有效政策);kidOnly = 只要孩子端能看到的(enabled 且不 hidden) */
|
|
123
|
+
export function listTeachers(config, opts = {}) {
|
|
124
|
+
return Object.entries(config.teachers)
|
|
125
|
+
.filter(([, t]) => !opts.kidOnly || (t.enabled && !t.hidden))
|
|
126
|
+
.map(([name, t]) => ({
|
|
127
|
+
name,
|
|
128
|
+
display: t.display,
|
|
129
|
+
subject: t.subject,
|
|
130
|
+
avatar: t.avatar,
|
|
131
|
+
voice: t.voice,
|
|
132
|
+
enabled: t.enabled,
|
|
133
|
+
hidden: t.hidden,
|
|
134
|
+
policy: resolvePolicy(config, name),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
/** 预设模板填占位符;{agentBody} 只在给了正文时替换,否则原样留着(doctor 会报) */
|
|
138
|
+
export function fillPreset(argv, vars) {
|
|
139
|
+
return argv.map((a) => a
|
|
140
|
+
.replaceAll('{agent}', vars.agent)
|
|
141
|
+
.replaceAll('{prompt}', vars.prompt)
|
|
142
|
+
.replaceAll('{session}', vars.session ?? '')
|
|
143
|
+
.replaceAll('{agentBody}', vars.agentBody ?? '{agentBody}'));
|
|
144
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上下文包:应用在每条消息前拼的固定 YAML 块(《cotutor契约草案.md》§2)。老师不用自己找上下文。
|
|
3
|
+
* 孩子由 workspace 决定,不进包。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export declare const MESSAGE_FROM: readonly ["kid", "parent", "system"];
|
|
7
|
+
export type MessageFrom = (typeof MESSAGE_FROM)[number];
|
|
8
|
+
export declare const FocusSchema: z.ZodObject<{
|
|
9
|
+
artifact: z.ZodOptional<z.ZodString>;
|
|
10
|
+
step: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type Focus = z.infer<typeof FocusSchema>;
|
|
14
|
+
export declare const ContextPackSchema: z.ZodObject<{
|
|
15
|
+
from: z.ZodEnum<{
|
|
16
|
+
kid: "kid";
|
|
17
|
+
parent: "parent";
|
|
18
|
+
system: "system";
|
|
19
|
+
}>;
|
|
20
|
+
at: z.ZodString;
|
|
21
|
+
slot: z.ZodOptional<z.ZodString>;
|
|
22
|
+
focus: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
artifact: z.ZodOptional<z.ZodString>;
|
|
24
|
+
step: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
plan: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
28
|
+
recent: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
29
|
+
date: z.ZodString;
|
|
30
|
+
claim: z.ZodString;
|
|
31
|
+
}, z.core.$strip>>>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export type ContextPack = z.infer<typeof ContextPackSchema>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上下文包:应用在每条消息前拼的固定 YAML 块(《cotutor契约草案.md》§2)。老师不用自己找上下文。
|
|
3
|
+
* 孩子由 workspace 决定,不进包。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export const MESSAGE_FROM = ['kid', 'parent', 'system'];
|
|
7
|
+
export const FocusSchema = z.object({
|
|
8
|
+
/** 当前对象(产物 id) */
|
|
9
|
+
artifact: z.string().optional(),
|
|
10
|
+
/** 正在看的 step */
|
|
11
|
+
step: z.number().int().nonnegative().optional(),
|
|
12
|
+
/** 圈了什么 */
|
|
13
|
+
circled: z.array(z.string()).optional(),
|
|
14
|
+
});
|
|
15
|
+
export const ContextPackSchema = z.object({
|
|
16
|
+
from: z.enum(MESSAGE_FROM),
|
|
17
|
+
/** 本地时间,分钟精度,如 2026-09-08T16:20 */
|
|
18
|
+
at: z.string().min(1),
|
|
19
|
+
/** 课程表命中的时段,如「数学 16:00-17:00」 */
|
|
20
|
+
slot: z.string().optional(),
|
|
21
|
+
focus: FocusSchema.optional(),
|
|
22
|
+
/** 本周计划里与本老师相关的行(已按 planLines 截) */
|
|
23
|
+
plan: z.array(z.string()).default([]),
|
|
24
|
+
/** 最近 N 条本学科观察(已按 recent 截) */
|
|
25
|
+
recent: z.array(z.object({ date: z.string(), claim: z.string() })).default([]),
|
|
26
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
|
|
3
|
+
* 索引里的 kidText / artifacts 是精简视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export declare const DATE_RE: RegExp;
|
|
7
|
+
export declare const ConversationMessageSchema: z.ZodObject<{
|
|
8
|
+
job: z.ZodString;
|
|
9
|
+
at: z.ZodString;
|
|
10
|
+
from: z.ZodEnum<{
|
|
11
|
+
kid: "kid";
|
|
12
|
+
parent: "parent";
|
|
13
|
+
system: "system";
|
|
14
|
+
}>;
|
|
15
|
+
text: z.ZodString;
|
|
16
|
+
focus: z.ZodOptional<z.ZodObject<{
|
|
17
|
+
artifact: z.ZodOptional<z.ZodString>;
|
|
18
|
+
step: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
result: z.ZodDefault<z.ZodEnum<{
|
|
22
|
+
error: "error";
|
|
23
|
+
running: "running";
|
|
24
|
+
ok: "ok";
|
|
25
|
+
}>>;
|
|
26
|
+
costUsd: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
kidText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
artifacts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export type ConversationMessage = z.infer<typeof ConversationMessageSchema>;
|
|
31
|
+
export declare const ConversationIndexSchema: z.ZodObject<{
|
|
32
|
+
teacher: z.ZodString;
|
|
33
|
+
date: z.ZodString;
|
|
34
|
+
session: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
35
|
+
id: z.ZodString;
|
|
36
|
+
agent: z.ZodString;
|
|
37
|
+
}, z.core.$strip>>>;
|
|
38
|
+
messages: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
39
|
+
job: z.ZodString;
|
|
40
|
+
at: z.ZodString;
|
|
41
|
+
from: z.ZodEnum<{
|
|
42
|
+
kid: "kid";
|
|
43
|
+
parent: "parent";
|
|
44
|
+
system: "system";
|
|
45
|
+
}>;
|
|
46
|
+
text: z.ZodString;
|
|
47
|
+
focus: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
artifact: z.ZodOptional<z.ZodString>;
|
|
49
|
+
step: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
circled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
|
+
}, z.core.$strip>>;
|
|
52
|
+
result: z.ZodDefault<z.ZodEnum<{
|
|
53
|
+
error: "error";
|
|
54
|
+
running: "running";
|
|
55
|
+
ok: "ok";
|
|
56
|
+
}>>;
|
|
57
|
+
costUsd: z.ZodOptional<z.ZodNumber>;
|
|
58
|
+
kidText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
artifacts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
60
|
+
}, z.core.$strip>>>;
|
|
61
|
+
costUsd: z.ZodDefault<z.ZodNumber>;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
export type ConversationIndex = z.infer<typeof ConversationIndexSchema>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话索引:一老师一天一份(《cotutor契约草案.md》§3)。每条消息 = 一次 -p 运行 = 一份 NDJSON 转录;
|
|
3
|
+
* 索引里的 kidText / artifacts 是精简视图的物化结果,孩子端不解析日志。跨天自动新开(2026-09-08 拍板)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { FocusSchema, MESSAGE_FROM } from "./context-pack.js";
|
|
7
|
+
export const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
8
|
+
export const ConversationMessageSchema = z.object({
|
|
9
|
+
/** 任务 id,同时是转录文件名的一段:<date>.<job>.log */
|
|
10
|
+
job: z.string().min(1),
|
|
11
|
+
at: z.string().min(1),
|
|
12
|
+
from: z.enum(MESSAGE_FROM),
|
|
13
|
+
/** 消息原文(不含上下文包) */
|
|
14
|
+
text: z.string(),
|
|
15
|
+
focus: FocusSchema.optional(),
|
|
16
|
+
result: z.enum(['running', 'ok', 'error']).default('running'),
|
|
17
|
+
costUsd: z.number().optional(),
|
|
18
|
+
/** 孩子视图文本;null = 这次运行没有给孩子的话(出错或空) */
|
|
19
|
+
kidText: z.string().nullable().optional(),
|
|
20
|
+
/** 本次运行新增的产物 id */
|
|
21
|
+
artifacts: z.array(z.string()).default([]),
|
|
22
|
+
});
|
|
23
|
+
export const ConversationIndexSchema = z.object({
|
|
24
|
+
teacher: z.string().min(1),
|
|
25
|
+
date: z.string().regex(DATE_RE),
|
|
26
|
+
/** 当天会话;首条消息跑完后写入,之后每条 --resume 它 */
|
|
27
|
+
session: z.object({ id: z.string().min(1), agent: z.string().min(1) }).nullable().default(null),
|
|
28
|
+
messages: z.array(ConversationMessageSchema).default([]),
|
|
29
|
+
costUsd: z.number().default(0),
|
|
30
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cotutor 契约(类型即文档)。每个文件一个契约面:
|
|
3
|
+
* config cotutor.json(老师表、政策、运行时预设、paths 角色)
|
|
4
|
+
* context-pack 每条消息前的上下文包
|
|
5
|
+
* conversation 一老师一天一份的会话索引
|
|
6
|
+
* ledger 观察与产物两本追加式账本
|
|
7
|
+
* sections 最终文本里的「待裁量」「转交」段
|
|
8
|
+
* plan 计划文件
|
|
9
|
+
* 运行时校验用 zod,失败信息经 explainIssues 变成修复指南。
|
|
10
|
+
*/
|
|
11
|
+
export * from './issues.ts';
|
|
12
|
+
export * from './config.ts';
|
|
13
|
+
export * from './context-pack.ts';
|
|
14
|
+
export * from './conversation.ts';
|
|
15
|
+
export * from './ledger.ts';
|
|
16
|
+
export * from './sections.ts';
|
|
17
|
+
export * from './plan.ts';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cotutor 契约(类型即文档)。每个文件一个契约面:
|
|
3
|
+
* config cotutor.json(老师表、政策、运行时预设、paths 角色)
|
|
4
|
+
* context-pack 每条消息前的上下文包
|
|
5
|
+
* conversation 一老师一天一份的会话索引
|
|
6
|
+
* ledger 观察与产物两本追加式账本
|
|
7
|
+
* sections 最终文本里的「待裁量」「转交」段
|
|
8
|
+
* plan 计划文件
|
|
9
|
+
* 运行时校验用 zod,失败信息经 explainIssues 变成修复指南。
|
|
10
|
+
*/
|
|
11
|
+
export * from "./issues.js";
|
|
12
|
+
export * from "./config.js";
|
|
13
|
+
export * from "./context-pack.js";
|
|
14
|
+
export * from "./conversation.js";
|
|
15
|
+
export * from "./ledger.js";
|
|
16
|
+
export * from "./sections.js";
|
|
17
|
+
export * from "./plan.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const EXPECT = {
|
|
2
|
+
string: '字符串',
|
|
3
|
+
number: '数字',
|
|
4
|
+
boolean: '布尔值(true/false)',
|
|
5
|
+
object: '对象',
|
|
6
|
+
array: '数组',
|
|
7
|
+
int: '整数',
|
|
8
|
+
};
|
|
9
|
+
export function explainIssues(issues) {
|
|
10
|
+
return issues.map((i) => {
|
|
11
|
+
const path = i.path.length ? i.path.map(String).join('.') : '(根)';
|
|
12
|
+
switch (i.code) {
|
|
13
|
+
case 'invalid_type':
|
|
14
|
+
return `${path}:应为${EXPECT[i.expected] ?? i.expected}${i.input === undefined ? ',现在缺失' : ''}`;
|
|
15
|
+
case 'invalid_value':
|
|
16
|
+
return `${path}:只能是 ${i.values.map((v) => JSON.stringify(v)).join(' / ')}`;
|
|
17
|
+
case 'too_small':
|
|
18
|
+
return `${path}:${i.origin === 'string' ? '不能为空' : `不能小于 ${i.minimum}`}`;
|
|
19
|
+
case 'too_big':
|
|
20
|
+
return `${path}:不能大于 ${i.maximum}`;
|
|
21
|
+
case 'invalid_format':
|
|
22
|
+
return `${path}:格式不对(${i.format}${i.pattern ? ` ${i.pattern}` : ''})`;
|
|
23
|
+
case 'unrecognized_keys':
|
|
24
|
+
return `${path}:不认识的字段 ${i.keys.join(', ')}`;
|
|
25
|
+
case 'invalid_union': {
|
|
26
|
+
// 挑问题最少的那个分支解释(如观察行:缺 claim 比缺 retracted+by 更接近)
|
|
27
|
+
const branches = i.errors ?? [];
|
|
28
|
+
const best = branches.slice().sort((a, b) => a.length - b.length)[0];
|
|
29
|
+
if (best?.length)
|
|
30
|
+
return explainIssues(best.map((e) => ({ ...e, path: [...i.path, ...e.path] }))).join(';');
|
|
31
|
+
return `${path}:不符合任何一种允许的形状`;
|
|
32
|
+
}
|
|
33
|
+
default:
|
|
34
|
+
return `${path}:${i.message}`;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公共账本(《cotutor契约草案.md》§5):observations.jsonl 与 artifacts.jsonl,追加式、一行一条、id 唯一;
|
|
3
|
+
* 改状态追加新行,同 id 后者为准,不改历史。retracted 用追加行(2026-09-08 拍板)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export declare const EvidenceSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
7
|
+
conversation: z.ZodString;
|
|
8
|
+
job: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
10
|
+
artifact: z.ZodString;
|
|
11
|
+
}, z.core.$strip>]>;
|
|
12
|
+
export type Evidence = z.infer<typeof EvidenceSchema>;
|
|
13
|
+
/** 完整观察行 */
|
|
14
|
+
export declare const ObservationSchema: z.ZodObject<{
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
date: z.ZodString;
|
|
17
|
+
author: z.ZodString;
|
|
18
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
19
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
20
|
+
claim: z.ZodString;
|
|
21
|
+
evidence: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
22
|
+
conversation: z.ZodString;
|
|
23
|
+
job: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
25
|
+
artifact: z.ZodString;
|
|
26
|
+
}, z.core.$strip>]>>;
|
|
27
|
+
retracted: z.ZodDefault<z.ZodBoolean>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export type Observation = z.infer<typeof ObservationSchema>;
|
|
30
|
+
/** 纠错行:只带 id + retracted + 谁 + 何时 */
|
|
31
|
+
export declare const ObservationRetractSchema: z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
retracted: z.ZodLiteral<true>;
|
|
34
|
+
by: z.ZodString;
|
|
35
|
+
date: z.ZodString;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type ObservationRetract = z.infer<typeof ObservationRetractSchema>;
|
|
38
|
+
export declare const ObservationLineSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
39
|
+
id: z.ZodString;
|
|
40
|
+
date: z.ZodString;
|
|
41
|
+
author: z.ZodString;
|
|
42
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
43
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
44
|
+
claim: z.ZodString;
|
|
45
|
+
evidence: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
46
|
+
conversation: z.ZodString;
|
|
47
|
+
job: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
49
|
+
artifact: z.ZodString;
|
|
50
|
+
}, z.core.$strip>]>>;
|
|
51
|
+
retracted: z.ZodDefault<z.ZodBoolean>;
|
|
52
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
retracted: z.ZodLiteral<true>;
|
|
55
|
+
by: z.ZodString;
|
|
56
|
+
date: z.ZodString;
|
|
57
|
+
}, z.core.$strip>]>;
|
|
58
|
+
export type ObservationLine = z.infer<typeof ObservationLineSchema>;
|
|
59
|
+
export declare const ARTIFACT_KINDS: readonly ["课包", "补讲", "批改", "计划", "其它"];
|
|
60
|
+
export declare const ARTIFACT_STATUS: readonly ["draft", "ready", "accepted", "retired"];
|
|
61
|
+
export type ArtifactStatus = (typeof ARTIFACT_STATUS)[number];
|
|
62
|
+
/** 产物事件行:首行要带 kind / by / path,后续行只带变化的字段 */
|
|
63
|
+
export declare const ArtifactEventSchema: z.ZodObject<{
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
66
|
+
课包: "课包";
|
|
67
|
+
补讲: "补讲";
|
|
68
|
+
批改: "批改";
|
|
69
|
+
计划: "计划";
|
|
70
|
+
其它: "其它";
|
|
71
|
+
}>>;
|
|
72
|
+
by: z.ZodOptional<z.ZodString>;
|
|
73
|
+
at: z.ZodString;
|
|
74
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
draft: "draft";
|
|
76
|
+
ready: "ready";
|
|
77
|
+
accepted: "accepted";
|
|
78
|
+
retired: "retired";
|
|
79
|
+
}>>;
|
|
80
|
+
path: z.ZodOptional<z.ZodString>;
|
|
81
|
+
source: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
conversation: z.ZodString;
|
|
83
|
+
job: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, z.core.$strip>>;
|
|
85
|
+
hash: z.ZodOptional<z.ZodString>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
export type ArtifactEvent = z.infer<typeof ArtifactEventSchema>;
|
|
88
|
+
/** 折叠后的产物 */
|
|
89
|
+
export interface Artifact {
|
|
90
|
+
id: string;
|
|
91
|
+
kind: (typeof ARTIFACT_KINDS)[number];
|
|
92
|
+
by: string;
|
|
93
|
+
at: string;
|
|
94
|
+
status: ArtifactStatus;
|
|
95
|
+
path?: string;
|
|
96
|
+
source?: ArtifactEvent['source'];
|
|
97
|
+
hash?: string;
|
|
98
|
+
/** 最后一次事件时间 */
|
|
99
|
+
updatedAt: string;
|
|
100
|
+
}
|