cotutor 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -7
- package/NOTICE +3 -3
- package/README.md +36 -13
- package/avatars/chinese.webp +0 -0
- package/avatars/english.webp +0 -0
- package/avatars/math.webp +0 -0
- package/bin/cotutor.js +13 -3
- package/cards/photo/card.css +10 -3
- package/cards/photo/card.md +2 -2
- package/cards/tutor/card.css +13 -0
- package/cards/tutor/card.md +2 -1
- package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +1 -1
- package/dist/app/activity-eval.js +3 -2
- package/dist/app/cli/activities.d.ts +8 -1
- package/dist/app/cli/activities.js +42 -4
- package/dist/app/cli/cert.d.ts +5 -0
- package/dist/app/cli/cert.js +20 -7
- package/dist/app/cli/chalk/browser.d.ts +2 -2
- package/dist/app/cli/chalk/browser.js +22 -7
- package/dist/app/cli/chalk/main.js +3 -2
- package/dist/app/cli/doctor.d.ts +6 -5
- package/dist/app/cli/doctor.js +107 -171
- package/dist/app/cli/firewall.d.ts +24 -0
- package/dist/app/cli/firewall.js +79 -0
- package/dist/app/cli/init.js +8 -5
- package/dist/app/cli/main.js +347 -24
- package/dist/app/cli/migrate.d.ts +2 -11
- package/dist/app/cli/migrate.js +1 -59
- package/dist/app/cli/port-holder.d.ts +16 -0
- package/dist/app/cli/port-holder.js +34 -0
- package/dist/app/cli/port.d.ts +37 -0
- package/dist/app/cli/port.js +116 -0
- package/dist/app/cli/sandbox.d.ts +25 -0
- package/dist/app/cli/sandbox.js +166 -0
- package/dist/app/cli/serve-log.d.ts +13 -0
- package/dist/app/cli/serve-log.js +90 -0
- package/dist/app/cli/serve.d.ts +18 -4
- package/dist/app/cli/serve.js +78 -8
- package/dist/app/cli/service-windows.d.ts +50 -0
- package/dist/app/cli/service-windows.js +117 -0
- package/dist/app/cli/service.d.ts +138 -0
- package/dist/app/cli/service.js +429 -0
- package/dist/app/cli/skeleton.d.ts +1 -1
- package/dist/app/cli/skeleton.js +13 -13
- package/dist/app/cli/skills.d.ts +5 -1
- package/dist/app/cli/skills.js +14 -4
- package/dist/app/cli/tray-icons.d.ts +11 -0
- package/dist/app/cli/tray-icons.js +87 -0
- package/dist/app/cli/tray-ids.d.ts +6 -0
- package/dist/app/cli/tray-ids.js +6 -0
- package/dist/app/cli/tray-mac.d.ts +8 -0
- package/dist/app/cli/tray-mac.js +140 -0
- package/dist/app/cli/tray-run.d.ts +1 -0
- package/dist/app/cli/tray-run.js +33 -0
- package/dist/app/cli/tray-windows.d.ts +5 -0
- package/dist/app/cli/tray-windows.js +166 -0
- package/dist/app/cli/tray.d.ts +205 -0
- package/dist/app/cli/tray.js +618 -0
- package/dist/app/cli/usage.d.ts +1 -1
- package/dist/app/cli/usage.js +11 -6
- package/dist/app/dictation/route.js +3 -2
- package/dist/app/http/app.d.ts +5 -10
- package/dist/app/http/app.js +45 -98
- package/dist/app/http/avatars.d.ts +17 -0
- package/dist/app/http/avatars.js +35 -0
- package/dist/app/http/dev-tutor-files.js +1 -1
- package/dist/app/http/parent-home.d.ts +14 -0
- package/dist/app/http/parent-home.js +69 -6
- package/dist/app/http/parent-look.d.ts +2 -0
- package/dist/app/http/parent-look.js +151 -0
- package/dist/app/http/raw-view.js +3 -3
- package/dist/app/http/voice-preview.d.ts +4 -0
- package/dist/app/http/voice-preview.js +34 -0
- package/dist/app/machine/bailian.d.ts +0 -2
- package/dist/app/machine/bailian.js +5 -6
- package/dist/app/mock/mock-live.js +1 -1
- package/dist/app/mock/mock.d.ts +4 -2
- package/dist/app/mock/mock.js +20 -12
- package/dist/app/replay.js +4 -4
- package/dist/app/skills/home-doc.js +1 -1
- package/dist/app/workspace.d.ts +9 -4
- package/dist/app/workspace.js +33 -10
- package/dist/app/ws/route.d.ts +8 -2
- package/dist/app/ws/route.js +7 -3
- package/dist/board/parse.d.ts +0 -2
- package/dist/board/parse.js +0 -6
- package/dist/board/presentation.d.ts +9 -15
- package/dist/board/presentation.js +8 -10
- package/dist/brain/api-tools.js +2 -1
- package/dist/brain/api.d.ts +12 -17
- package/dist/brain/api.js +16 -33
- package/dist/brain/index.d.ts +23 -71
- package/dist/brain/index.js +6 -69
- package/dist/brain/log.d.ts +5 -0
- package/dist/brain/log.js +22 -0
- package/dist/brain/runtime.d.ts +30 -0
- package/dist/brain/runtime.js +23 -0
- package/dist/brain/{cli/stream-json.d.ts → stream-json.d.ts} +2 -4
- package/dist/brain/{cli/stream-json.js → stream-json.js} +0 -4
- package/dist/brain/stream.d.ts +5 -5
- package/dist/brain/stream.js +7 -7
- package/dist/brain/transcript.d.ts +3 -3
- package/dist/brain/transcript.js +6 -6
- package/dist/brain/{cli/types.d.ts → types.d.ts} +3 -48
- package/dist/brain/types.js +14 -0
- package/dist/chalk/glyphs/skeleton.d.ts +3 -3
- package/dist/chalk/glyphs/skeleton.js +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/memory/diary.js +3 -3
- package/dist/realtime/bailian.d.ts +1 -1
- package/dist/realtime/bailian.js +3 -2
- package/dist/realtime/preview.js +1 -1
- package/dist/realtime/voices.d.ts +2 -0
- package/dist/realtime/voices.js +57 -57
- package/dist/runtime/activities.d.ts +13 -1
- package/dist/runtime/activities.js +28 -2
- package/dist/runtime/activity-state.js +2 -2
- package/dist/runtime/agent-file.d.ts +3 -3
- package/dist/runtime/agent-file.js +3 -3
- package/dist/runtime/context/tutor-rules.d.ts +2 -2
- package/dist/runtime/context/tutor-rules.js +3 -3
- package/dist/runtime/home/home.d.ts +12 -1
- package/dist/runtime/home/home.js +8 -1
- package/dist/runtime/home/store.d.ts +1 -1
- package/dist/runtime/home/store.js +36 -21
- package/dist/runtime/journal/mix.d.ts +15 -6
- package/dist/runtime/journal/mix.js +60 -19
- package/dist/runtime/journal/recorder.d.ts +58 -13
- package/dist/runtime/journal/recorder.js +106 -16
- package/dist/runtime/journal/store.d.ts +4 -4
- package/dist/runtime/journal/store.js +6 -6
- package/dist/runtime/kid-input.js +2 -3
- package/dist/runtime/live/call.d.ts +12 -0
- package/dist/runtime/live/call.js +51 -18
- package/dist/runtime/live/turns.js +1 -1
- package/dist/runtime/runner.d.ts +9 -55
- package/dist/runtime/runner.js +52 -173
- package/dist/scenes/paint/painter.js +4 -3
- package/dist/schema/config.d.ts +43 -22
- package/dist/schema/config.js +45 -47
- package/dist/schema/conversation.d.ts +6 -0
- package/dist/schema/home.d.ts +11 -1
- package/dist/schema/home.js +7 -2
- package/dist/schema/workspace.d.ts +27 -0
- package/dist/schema/workspace.js +28 -0
- package/dist/shared/default-port.d.ts +2 -0
- package/dist/shared/default-port.js +2 -0
- package/dist/shared/qianwen.d.ts +14 -10
- package/dist/shared/qianwen.js +26 -22
- package/dist/shared/site-url.d.ts +2 -0
- package/dist/shared/site-url.js +5 -0
- package/dist/ui/dev/dev-page.js +25 -252
- package/dist/ui/dev/dev-theme.d.ts +1 -1
- package/dist/ui/dev/dev-theme.js +1 -1
- package/dist/ui/dev/voice-gallery-page.js +1 -1
- package/dist/ui/kid/board-mark.d.ts +2 -2
- package/dist/ui/kid/board-mark.js +2 -2
- package/dist/ui/kid/home-tiles.d.ts +14 -0
- package/dist/ui/kid/home-tiles.js +44 -0
- package/dist/ui/kid/kid-page.js +68 -103
- package/dist/ui/kid/voice-ui.d.ts +1 -1
- package/dist/ui/kid/voice-ui.js +1 -1
- package/dist/ui/kid/word-emoji.js +5 -3
- package/dist/ui/parent/parent-home-page.js +315 -7
- package/dist/ui/parent/parent-page.js +6 -2
- package/dist/ui/parent/parent-settings-page.d.ts +1 -0
- package/dist/ui/parent/parent-settings-page.js +133 -0
- package/dist/ui/{dev → parent}/qr-page.d.ts +2 -2
- package/dist/ui/{dev → parent}/qr-page.js +19 -14
- package/dist/ui/shared/brand.d.ts +3 -1
- package/dist/ui/shared/brand.js +3 -1
- package/grades/grade-2/README.md +1 -1
- package/grades/grade-2/VALIDATION.md +1 -1
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +1 -1
- package/grades/grade-2/agents/chinese-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
- package/grades/grade-2/agents/chinese-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +1 -1
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +1 -1
- package/grades/grade-2/agents/english-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
- package/grades/grade-2/agents/english-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +1 -1
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +1 -1
- package/grades/grade-2/agents/math-tutor/activities//346/{211/271/346/224/271.md → 243/200/346/237/245/344/275/234/344/270/232.md} +4 -4
- package/grades/grade-2/agents/math-tutor/activities//346/{211/271/346/224/271. → 243/200/346/237/245/344/275/234/344/270/232.}/344/276/213/345/255/220.md +2 -2
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +1 -1
- package/package.json +2 -1
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +2 -2
- package/skills/cotutor-board/references/photo.md +2 -2
- package/skills/cotutor-home/SKILL.md +1 -0
- package/skills/cotutor-home/references/tutor.md +2 -1
- package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +2 -1
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +6 -3
- package/skills/cotutor-tutor/SKILL.md +5 -13
- package/dist/brain/cli/adapters.d.ts +0 -60
- package/dist/brain/cli/adapters.js +0 -91
- package/dist/brain/cli/qwen.d.ts +0 -11
- package/dist/brain/cli/qwen.js +0 -141
- package/dist/brain/cli/run-plan.d.ts +0 -59
- package/dist/brain/cli/run-plan.js +0 -71
- package/dist/brain/cli/run.d.ts +0 -35
- package/dist/brain/cli/run.js +0 -256
- package/dist/brain/cli/types.js +0 -3
- package/dist/brain/cli/warm.d.ts +0 -53
- package/dist/brain/cli/warm.js +0 -103
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* 进程 cwd 是老师目录 agents/<name>/。
|
|
12
12
|
*/
|
|
13
13
|
import { type IncomingMark } from './lecture.ts';
|
|
14
|
-
import { type BrainExecutor, type
|
|
14
|
+
import { type BrainExecutor, type RunPlan } from '../brain/index.ts';
|
|
15
15
|
import { type BoardSection, type Device } from '../board/presentation.ts';
|
|
16
|
-
import { type RunPlan } from '../brain/cli/run-plan.ts';
|
|
17
16
|
import { type RunEvent, type RunEventEnvelope } from '../schema/index.ts';
|
|
18
17
|
import { type ContextPack, type ConversationIndex, type ActivityRun, type ConversationMessage, type Focus, type MessageFrom, type MessageVia } from '../schema/index.ts';
|
|
19
18
|
import type { Workspace } from '../schema/index.ts';
|
|
@@ -59,7 +58,7 @@ export interface SendInput {
|
|
|
59
58
|
drawing?: NonNullable<ConversationMessage['drawing']>;
|
|
60
59
|
/** 这条是回放(app/replay.ts):原轮的 job,记进消息;调用方已经把 Runner 指到 evals/ */
|
|
61
60
|
replayOf?: string;
|
|
62
|
-
/** cotutor activity eval
|
|
61
|
+
/** cotutor activity eval 跑的一轮:不写记忆(画照画,画家由 eval 换成假的) */
|
|
63
62
|
eval?: true;
|
|
64
63
|
/** 孩子看完小课堂后的第一条:上下文包带 lecture:(课的每句、看的情况、画过的几处),消息记 lecture;again = 问过以后再看一遍又画了 */
|
|
65
64
|
lecture?: {
|
|
@@ -130,58 +129,35 @@ export declare function attachActivity(ws: Workspace, tutor: string, pack: Conte
|
|
|
130
129
|
thread: string;
|
|
131
130
|
now: number;
|
|
132
131
|
}, inSystem?: boolean): Promise<string[]>;
|
|
133
|
-
/** 按老师裁的板书写法落在哪(相对 workspace 根;机器文件,内容变了文件名跟着变) */
|
|
134
|
-
export declare const TUTOR_BOARD_DIR = ".cotutor/board";
|
|
135
132
|
/** activity 在系统提示里时,上下文包 activityGuide 那一行 */
|
|
136
133
|
export declare const ACTIVITY_IN_SYSTEM = "\u5DF2\u5728\u4F60\u7684\u7CFB\u7EDF\u63D0\u793A\u91CC(\u8FD9\u4E2A\u8BDD\u9898\u7684 activity,\u7167\u5B83\u5E26)";
|
|
137
134
|
/**
|
|
138
135
|
* activity 话题的系统提示(九点五):不读老师文件,人设与做法都是 activity 自己的。
|
|
139
136
|
* body 「老师怎么带」按参数与这个话题的卡裁好、剥了注释
|
|
140
137
|
* kinds 这个话题的卡:activity 头上的 cards 加 text(不并老师的)
|
|
141
|
-
* guide
|
|
138
|
+
* guide 只讲这几种卡的板书写法
|
|
142
139
|
* memory 这个 activity 自己的记忆:agent: <老师>/<activity>,文件名 <老师显示名>·<activity>
|
|
143
140
|
* activity 文件不在、没有「老师怎么带」→ null(退回老师文件)。
|
|
144
141
|
*/
|
|
145
142
|
export interface ActivityParts {
|
|
146
143
|
body: string;
|
|
147
144
|
kinds: string[];
|
|
148
|
-
guide:
|
|
149
|
-
body: string;
|
|
150
|
-
path: string;
|
|
151
|
-
};
|
|
145
|
+
guide: string;
|
|
152
146
|
memory: {
|
|
153
147
|
agent: string;
|
|
154
148
|
display: string;
|
|
155
149
|
};
|
|
156
150
|
}
|
|
157
151
|
export declare function activityParts(ws: Workspace, tutor: string, run: ActivityRun): Promise<ActivityParts | null>;
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
* 没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor),写到 .cotutor/board/<老师>-<hash>.md,
|
|
161
|
-
* 文件名带内容的 hash:清单一改路径就变,预热进程的 argv 对不上,不会拿着旧写法去答。读不到 SKILL.md → null。
|
|
162
|
-
*/
|
|
163
|
-
export declare function boardGuideOf(ws: Workspace, tutor: string): Promise<{
|
|
164
|
-
body: string;
|
|
165
|
-
path: string;
|
|
166
|
-
} | null>;
|
|
167
|
-
/**
|
|
168
|
-
* 板书写法怎么递给老师(2026-09-19):有脸的老师、板书没关才带。运行时预载了 → 上下文包只写一行事实;
|
|
169
|
-
* 没预载 → 原文作为一条笔记放进话题第一条(<cotutor-board>),排在守则后面,续话题没改过写「未变」。返回板书写法(给 {systemBody} 与 {boardFile})。
|
|
170
|
-
*/
|
|
171
|
-
export declare function attachBoardGuide(ws: Workspace, tutor: string, pack: ContextPack, o: {
|
|
172
|
-
preloaded: boolean;
|
|
173
|
-
boardOff: boolean;
|
|
174
|
-
}): Promise<{
|
|
175
|
-
body: string;
|
|
176
|
-
path: string;
|
|
177
|
-
} | null>;
|
|
152
|
+
/** 这位老师的板书写法:没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor)。读不到 SKILL.md → null */
|
|
153
|
+
export declare function boardGuideOf(ws: Workspace, tutor: string): Promise<string | null>;
|
|
178
154
|
/** 这轮带的笔记版本:role → `路径@hash`(记进消息;同一话题下一轮对得上就不再带原文) */
|
|
179
155
|
export declare function noteVersions(pack: ContextPack): Record<string, string>;
|
|
180
156
|
/** 续会话时:上一轮这个话题已经带过、版本没变的笔记去掉原文,YAML 里注明「未变」 */
|
|
181
157
|
export declare function dropSeenNotes(pack: ContextPack, seen: Record<string, string>): ContextPack;
|
|
182
158
|
/** 这个话题里最近一次记下的笔记版本(新话题、没记过 → {}) */
|
|
183
159
|
export declare function seenNotes(messages: readonly ConversationMessage[], thread: string): Record<string, string>;
|
|
184
|
-
/**
|
|
160
|
+
/** 只读工具读得到的目录:workspace 根(作业照片、技能)与 vault(教材、日记) */
|
|
185
161
|
export declare function readDirsOf(ws: Workspace): string[];
|
|
186
162
|
/** 干跑:现在给这位老师发这句话,上下文包会是什么样、每段从哪来、截了多少;不起模型、不写盘 */
|
|
187
163
|
export declare function packDryRun(ws: Workspace, tutor: string, input: {
|
|
@@ -205,16 +181,9 @@ export declare class Runner {
|
|
|
205
181
|
private readonly listeners;
|
|
206
182
|
/** noteEvent 每个事件文件一条追加链 */
|
|
207
183
|
private readonly notes;
|
|
208
|
-
/**
|
|
184
|
+
/** 脑执行:请模型、读输出(src/brain/) */
|
|
209
185
|
readonly brain: BrainExecutor;
|
|
210
|
-
/**
|
|
211
|
-
get spares(): {
|
|
212
|
-
has(tutor: string, slot?: Slot): boolean;
|
|
213
|
-
drop(tutor: string, slot?: Slot): void;
|
|
214
|
-
};
|
|
215
|
-
/** 页面拉今天时的预热:每位老师(与带的话题)上次看的时刻 */
|
|
216
|
-
private readonly prewarmedAt;
|
|
217
|
-
/** 起子进程用的环境(测试注入) */
|
|
186
|
+
/** key、假端点从哪读的环境(测试注入) */
|
|
218
187
|
get env(): NodeJS.ProcessEnv | undefined;
|
|
219
188
|
/** 当前的 workspace(cotutor.json 热重载后是新的;实时老师经它拿) */
|
|
220
189
|
get ws(): Workspace;
|
|
@@ -223,8 +192,6 @@ export declare class Runner {
|
|
|
223
192
|
constructor(getWs: () => Workspace, opts?: {
|
|
224
193
|
now?: () => Date;
|
|
225
194
|
env?: NodeJS.ProcessEnv;
|
|
226
|
-
warmIdleMs?: number;
|
|
227
|
-
warm?: boolean;
|
|
228
195
|
draw?: Draw;
|
|
229
196
|
});
|
|
230
197
|
/** 孩子端发一条的验与拼(HTTP 与实时老师共用,./kid-input.ts):拼成 send 的入参,或回 reject;不起老师 */
|
|
@@ -239,19 +206,6 @@ export declare class Runner {
|
|
|
239
206
|
onEvent(fn: (e: RunEventEnvelope) => void): () => void;
|
|
240
207
|
/** 别处给某一轮补事件(实时老师的嘴,src/runtime/live/call.ts;t 已经换成离这一轮老师进程起来的毫秒):追加到 events.jsonl、给订阅者;这一轮跑完了也行 */
|
|
241
208
|
noteEvent(tutor: string, date: string, job: string, event: RunEvent): Promise<void>;
|
|
242
|
-
/** 杀掉所有预热的进程(测试收尾;服务退出时它们的 stdin 断了会自己退) */
|
|
243
|
-
close(): void;
|
|
244
|
-
/**
|
|
245
|
-
* 预热:给这位老师起好下一轮的进程,两个位置都补齐——
|
|
246
|
-
* fresh:新开会话(新话题、接着上次都用它);
|
|
247
|
-
* resume:接 thread(页面选着的话题;不给或今天没有 = 当天末条所在的话题,和 send 缺省一样)的会话,它没有会话就不留。
|
|
248
|
-
* 只给有脸的、开着的、运行时 stdin: "stream-json"、这会儿没在跑的老师起;throttleMs 内同一话题看过就不再看(页面轮询今天)。
|
|
249
|
-
* 起了返回 true;起不了不报错,下一轮冷起。warm: false(cotutor send:跑完就退,留着备用进程会把它挂一小时)不起。
|
|
250
|
-
*/
|
|
251
|
-
prewarm(tutor: string, opts?: {
|
|
252
|
-
throttleMs?: number;
|
|
253
|
-
thread?: string;
|
|
254
|
-
}): Promise<boolean>;
|
|
255
209
|
/** 正在回的那条已经出来的板书(流式);没在跑或还没出卡 → null */
|
|
256
210
|
partial(tutor: string): BoardSection | null;
|
|
257
211
|
send(tutor: string, input: SendInput): Promise<SendStarted>;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -11,26 +11,24 @@
|
|
|
11
11
|
* 进程 cwd 是老师目录 agents/<name>/。
|
|
12
12
|
*/
|
|
13
13
|
import { lecturePack, readLecture, storedMarks } from "./lecture.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
14
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
15
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
17
16
|
import { buildContextPack } from "./context/context-pack.js";
|
|
18
|
-
import { addMessage, applyRun,
|
|
17
|
+
import { addMessage, applyRun, changedCards, jobId, sameRuntime, sessionFor } from "./journal/conversation.js";
|
|
19
18
|
import { localDate, localMinute } from "../shared/time.js";
|
|
20
|
-
import { cardId, conversationFiles, threads } from "../schema/index.js";
|
|
19
|
+
import { cardVoice, cardId, conversationFiles, threads } from "../schema/index.js";
|
|
21
20
|
import { bookkeepingPrompt, kidQuestions, textbookHeadings } from "../memory/diary.js";
|
|
22
21
|
import { cardAssets, cardLabel, describeCard, tutorCardKinds } from "../board/cards/index.js";
|
|
23
22
|
import { boardGuideFor, cardGuidesFor } from "../board/cards/docs.js";
|
|
24
23
|
import { activityCards, parseActivity, renderTutorSection } from "./activity.js";
|
|
25
|
-
import {
|
|
24
|
+
import { findActivityFile } from "./activities.js";
|
|
26
25
|
import { activityState, paintGate, paintTool, readActivity, threadActivity } from "./activity-state.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
26
|
+
import { createBrain, getRuntime } from "../brain/index.js";
|
|
27
|
+
import { lineFlags, parseBoard } from "../board/parse.js";
|
|
29
28
|
import { readyBeats, beatsOf } from "../board/presentation.js";
|
|
30
29
|
import { deriveKidView, truncateReply } from "./kid-view.js";
|
|
31
30
|
import { parseSections } from "../board/sections.js";
|
|
32
31
|
import { threadDigest } from "./context/thread-digest.js";
|
|
33
|
-
import { getRuntime, runtimeUses, boardPreloaded } from "../brain/cli/run-plan.js";
|
|
34
32
|
import { beatTimings } from "./journal/events.js";
|
|
35
33
|
import {} from "../schema/index.js";
|
|
36
34
|
import { BOARD_CARD_KINDS, MEMORY_MAX_PER_TURN, MEMORY_TIDY_CAP, VAULT_PACK_ROLES, isApiRuntime, resolvePolicy } from "../schema/index.js";
|
|
@@ -45,8 +43,7 @@ import { TUTOR_RULES_PATH, takesTutorRules, tutorRulesBody, BOARD_GUIDE_IN_SYSTE
|
|
|
45
43
|
import { DubQueue } from "../media/tts.js";
|
|
46
44
|
import { continueContext } from "./home/store.js";
|
|
47
45
|
import { kidInput } from "./kid-input.js";
|
|
48
|
-
import {
|
|
49
|
-
import { qianwenKeys } from "../shared/qianwen.js";
|
|
46
|
+
import { compatibleLine } from "../shared/qianwen.js";
|
|
50
47
|
import { DRAW_ONLY_TEXT, drawingPackLine } from "../board/paper.js";
|
|
51
48
|
import { makeDraw } from "../media/draw.js";
|
|
52
49
|
import { paintLines, startPaints } from "../scenes/paint/painter.js";
|
|
@@ -123,7 +120,7 @@ export async function gatherContext(ws, tutor, input, report) {
|
|
|
123
120
|
*/
|
|
124
121
|
export async function attachActivity(ws, tutor, pack, run, where, inSystem = false) {
|
|
125
122
|
pack.activity = { name: run.name, params: run.params, state: {} };
|
|
126
|
-
const path = relative(ws.root,
|
|
123
|
+
const path = relative(ws.root, findActivityFile(ws.root, tutor, run.name)).split(sep).join('/');
|
|
127
124
|
const text = await readFile(join(ws.root, path), 'utf8').catch(() => null);
|
|
128
125
|
if (text === null) {
|
|
129
126
|
pack.activityGuide = `缺:${path} 不在,照你老师文件里的做`;
|
|
@@ -150,8 +147,6 @@ export async function attachActivity(ws, tutor, pack, run, where, inSystem = fal
|
|
|
150
147
|
pack.activityGuide = path;
|
|
151
148
|
return [];
|
|
152
149
|
}
|
|
153
|
-
/** 按老师裁的板书写法落在哪(相对 workspace 根;机器文件,内容变了文件名跟着变) */
|
|
154
|
-
export const TUTOR_BOARD_DIR = '.cotutor/board';
|
|
155
150
|
/** activity 在系统提示里时,上下文包 activityGuide 那一行 */
|
|
156
151
|
export const ACTIVITY_IN_SYSTEM = '已在你的系统提示里(这个话题的 activity,照它带)';
|
|
157
152
|
export async function activityParts(ws, tutor, run) {
|
|
@@ -160,85 +155,42 @@ export async function activityParts(ws, tutor, run) {
|
|
|
160
155
|
return null;
|
|
161
156
|
const kinds = [...new Set(['text', ...a.cards.filter((k) => BOARD_CARD_KINDS.includes(k))])];
|
|
162
157
|
const body = renderTutorSection(a, { params: run.params, cards: kinds }) ?? '';
|
|
163
|
-
const guideBody = boardGuideFor(kinds);
|
|
164
|
-
const guide = { body: guideBody, path: `${TUTOR_BOARD_DIR}/${tutor}-${textHash(guideBody).slice(0, 8)}.md` };
|
|
165
158
|
const display = ws.config.tutors[tutor]?.display ?? tutor;
|
|
166
|
-
|
|
167
|
-
return { body, kinds, guide, memory: { agent: `${tutor}/${run.name}`, display: `${display}·${run.name}` } };
|
|
159
|
+
return { body, kinds, guide: boardGuideFor(kinds), memory: { agent: `${tutor}/${run.name}`, display: `${display}·${run.name}` } };
|
|
168
160
|
}
|
|
169
|
-
|
|
170
|
-
if ((await readFile(file, 'utf8').catch(() => null)) === text)
|
|
171
|
-
return;
|
|
172
|
-
await mkdir(dirname(file), { recursive: true });
|
|
173
|
-
await writeFile(file, text);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* 这位老师的板书写法:正文 + 相对 workspace 根的路径(给 {boardFile} 与上下文包)。
|
|
177
|
-
* 没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor),写到 .cotutor/board/<老师>-<hash>.md,
|
|
178
|
-
* 文件名带内容的 hash:清单一改路径就变,预热进程的 argv 对不上,不会拿着旧写法去答。读不到 SKILL.md → null。
|
|
179
|
-
*/
|
|
161
|
+
/** 这位老师的板书写法:没写 cards → 出厂的 cotutor-board SKILL.md;写了 → 按清单从包里现拼(boardGuideFor)。读不到 SKILL.md → null */
|
|
180
162
|
export async function boardGuideOf(ws, tutor) {
|
|
181
163
|
const kinds = tutorCardKinds(ws.config.tutors[tutor]?.cards);
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const body = boardGuideFor(kinds);
|
|
191
|
-
const path = `${TUTOR_BOARD_DIR}/${tutor}-${textHash(body).slice(0, 8)}.md`;
|
|
192
|
-
const file = join(ws.root, path);
|
|
193
|
-
if (!existsSync(file)) {
|
|
194
|
-
await mkdir(join(ws.root, TUTOR_BOARD_DIR), { recursive: true });
|
|
195
|
-
await writeFile(file, body);
|
|
164
|
+
if (kinds)
|
|
165
|
+
return boardGuideFor(kinds);
|
|
166
|
+
try {
|
|
167
|
+
return boardGuideBody(await readFile(join(ws.root, BOARD_GUIDE_PATH), 'utf8'));
|
|
196
168
|
}
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* 板书写法怎么递给老师(2026-09-19):有脸的老师、板书没关才带。运行时预载了 → 上下文包只写一行事实;
|
|
201
|
-
* 没预载 → 原文作为一条笔记放进话题第一条(<cotutor-board>),排在守则后面,续话题没改过写「未变」。返回板书写法(给 {systemBody} 与 {boardFile})。
|
|
202
|
-
*/
|
|
203
|
-
export async function attachBoardGuide(ws, tutor, pack, o) {
|
|
204
|
-
if (!takesTutorRules(tutor) || o.boardOff)
|
|
205
|
-
return null;
|
|
206
|
-
const guide = await boardGuideOf(ws, tutor);
|
|
207
|
-
if (!guide) {
|
|
208
|
-
pack.boardGuide = `缺:${BOARD_GUIDE_PATH} 不在(cotutor upgrade 补),照你会的写`;
|
|
169
|
+
catch {
|
|
209
170
|
return null;
|
|
210
171
|
}
|
|
211
|
-
if (o.preloaded) {
|
|
212
|
-
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
|
213
|
-
return guide;
|
|
214
|
-
}
|
|
215
|
-
pack.boardGuide = guide.path;
|
|
216
|
-
const notes = pack.notes ?? [];
|
|
217
|
-
const at = notes.findIndex((n) => n.role === 'rules');
|
|
218
|
-
notes.splice(at + 1, 0, { role: 'boardGuide', path: guide.path, text: guide.body });
|
|
219
|
-
pack.notes = notes;
|
|
220
|
-
return guide;
|
|
221
172
|
}
|
|
222
|
-
/** 回复停在写成正文的工具调用上(后面只剩空行;「## 」尾巴不算):交给脑,CLI 据此 resume 一次 */
|
|
223
|
-
const stuckOnTool = (text) => endsWithToolCall(parseSections(text).body);
|
|
224
173
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
174
|
+
* 系统提示(2026-09-19 起板书写法跟着进系统提示):有脸的老师、板书没关才带板书写法,上下文包只写一行「已在系统提示里」。
|
|
175
|
+
* activity 话题:系统提示换成 activity 的人设与做法 + 它的卡的写法;工具人(没有守则)的 activity 退回老师文件 + 第一条带原文(attachActivity)。
|
|
227
176
|
*/
|
|
228
|
-
async function systemParts(ws, tutor,
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
if (act && takesTutorRules(tutor) && wantsSystemBody) {
|
|
232
|
-
const boardOff = policy.board === 'off';
|
|
177
|
+
async function systemParts(ws, tutor, policy, pack, act = null) {
|
|
178
|
+
const boardOff = policy.board === 'off';
|
|
179
|
+
if (act && takesTutorRules(tutor)) {
|
|
233
180
|
if (!boardOff)
|
|
234
181
|
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
|
235
|
-
|
|
236
|
-
return { agentBody: act.body, systemBody, boardFile: join(ws.root, boardOff ? BOARD_GUIDE_PATH : act.guide.path), agent: tutor, activityInSystem: true };
|
|
182
|
+
return { systemBody: boardOff ? act.body : `${act.body}\n\n${act.guide}`, activityInSystem: true };
|
|
237
183
|
}
|
|
238
|
-
const agentBody =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
184
|
+
const agentBody = await readAgentBody(ws, tutor);
|
|
185
|
+
if (!takesTutorRules(tutor) || boardOff)
|
|
186
|
+
return { systemBody: agentBody, activityInSystem: false };
|
|
187
|
+
const guide = await boardGuideOf(ws, tutor);
|
|
188
|
+
if (!guide) {
|
|
189
|
+
pack.boardGuide = `缺:${BOARD_GUIDE_PATH} 不在(cotutor upgrade 补),照你会的写`;
|
|
190
|
+
return { systemBody: agentBody, activityInSystem: false };
|
|
191
|
+
}
|
|
192
|
+
pack.boardGuide = BOARD_GUIDE_IN_SYSTEM;
|
|
193
|
+
return { systemBody: `${agentBody}\n\n${guide}`, activityInSystem: false };
|
|
242
194
|
}
|
|
243
195
|
/** 这轮带的笔记版本:role → `路径@hash`(记进消息;同一话题下一轮对得上就不再带原文) */
|
|
244
196
|
export function noteVersions(pack) {
|
|
@@ -262,7 +214,7 @@ export function seenNotes(messages, thread) {
|
|
|
262
214
|
return messages[i].notes;
|
|
263
215
|
return {};
|
|
264
216
|
}
|
|
265
|
-
/**
|
|
217
|
+
/** 只读工具读得到的目录:workspace 根(作业照片、技能)与 vault(教材、日记) */
|
|
266
218
|
export function readDirsOf(ws) {
|
|
267
219
|
return [...new Set([ws.root, ws.paths.vault])];
|
|
268
220
|
}
|
|
@@ -277,13 +229,12 @@ export async function packDryRun(ws, tutor, input) {
|
|
|
277
229
|
const policy = resolvePolicy(ws.config, tutor);
|
|
278
230
|
if (policy.board === 'off')
|
|
279
231
|
pack.board = 'off';
|
|
280
|
-
const { runtime } = getRuntime(ws.config, t.runtime);
|
|
281
232
|
// 和 send 同一条规矩:孩子的话不带工具时,只有工具才用得上的路径不进包
|
|
282
|
-
if (policy.tools !== 'on' && input.from === 'kid'
|
|
233
|
+
if (policy.tools !== 'on' && input.from === 'kid') {
|
|
283
234
|
delete pack.refs;
|
|
284
235
|
delete pack.vault;
|
|
285
236
|
}
|
|
286
|
-
const parts = await systemParts(ws, tutor,
|
|
237
|
+
const parts = await systemParts(ws, tutor, policy, pack, act);
|
|
287
238
|
if (input.activity)
|
|
288
239
|
await attachActivity(ws, tutor, pack, input.activity, { date: localDate(input.at), messages: [], thread: '', now: input.at.getTime() }, parts.activityInSystem);
|
|
289
240
|
return { prompt: buildContextPack(pack, input.text, policy.contextPack), pack, report };
|
|
@@ -297,7 +248,7 @@ export function vaultPack(paths) {
|
|
|
297
248
|
if (!p)
|
|
298
249
|
continue;
|
|
299
250
|
const rel = relative(root, p);
|
|
300
|
-
out[r] = !rel || rel.startsWith('..') || isAbsolute(rel) ? p : rel;
|
|
251
|
+
out[r] = !rel || rel.startsWith('..') || isAbsolute(rel) ? p : rel.split(sep).join('/');
|
|
301
252
|
}
|
|
302
253
|
return out;
|
|
303
254
|
}
|
|
@@ -310,15 +261,9 @@ export class Runner {
|
|
|
310
261
|
listeners = new Set();
|
|
311
262
|
/** noteEvent 每个事件文件一条追加链 */
|
|
312
263
|
notes = new Map();
|
|
313
|
-
/**
|
|
264
|
+
/** 脑执行:请模型、读输出(src/brain/) */
|
|
314
265
|
brain;
|
|
315
|
-
/**
|
|
316
|
-
get spares() {
|
|
317
|
-
return { has: (tutor, slot) => this.brain.warmed(tutor, slot), drop: (tutor, slot) => this.brain.dropWarm(tutor, slot) };
|
|
318
|
-
}
|
|
319
|
-
/** 页面拉今天时的预热:每位老师(与带的话题)上次看的时刻 */
|
|
320
|
-
prewarmedAt = new Map();
|
|
321
|
-
/** 起子进程用的环境(测试注入) */
|
|
266
|
+
/** key、假端点从哪读的环境(测试注入) */
|
|
322
267
|
get env() {
|
|
323
268
|
return this.opts.env;
|
|
324
269
|
}
|
|
@@ -332,10 +277,10 @@ export class Runner {
|
|
|
332
277
|
this.getWs = getWs;
|
|
333
278
|
this.opts = opts;
|
|
334
279
|
this.draw = opts.draw ?? makeDraw({ env: opts.env });
|
|
335
|
-
// API
|
|
280
|
+
// API 运行时的 key 与配音、画图同一套选法(~/.config/cotutor/):Token Plan 有这个模型就走它,否则百炼按量;只有按量 key、没有业务空间 id 也能用(走公网域名)。
|
|
336
281
|
// COTUTOR_BRAIN_ENDPOINT:测试与探针指到假百炼
|
|
337
282
|
const env = opts.env ?? process.env;
|
|
338
|
-
this.brain = createBrain({ workspace: () => { const ws = this.getWs(); return { config: ws.config, root: ws.root }; },
|
|
283
|
+
this.brain = createBrain({ workspace: () => { const ws = this.getWs(); return { config: ws.config, root: ws.root }; }, line: (model) => compatibleLine(model, env), endpoint: env.COTUTOR_BRAIN_ENDPOINT || undefined });
|
|
339
284
|
}
|
|
340
285
|
/** 孩子端发一条的验与拼(HTTP 与实时老师共用,./kid-input.ts):拼成 send 的入参,或回 reject;不起老师 */
|
|
341
286
|
accept(tutor, body, date, deviceId) {
|
|
@@ -373,61 +318,6 @@ export class Runner {
|
|
|
373
318
|
}
|
|
374
319
|
}
|
|
375
320
|
}
|
|
376
|
-
/** 杀掉所有预热的进程(测试收尾;服务退出时它们的 stdin 断了会自己退) */
|
|
377
|
-
close() {
|
|
378
|
-
this.brain.close();
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* 预热:给这位老师起好下一轮的进程,两个位置都补齐——
|
|
382
|
-
* fresh:新开会话(新话题、接着上次都用它);
|
|
383
|
-
* resume:接 thread(页面选着的话题;不给或今天没有 = 当天末条所在的话题,和 send 缺省一样)的会话,它没有会话就不留。
|
|
384
|
-
* 只给有脸的、开着的、运行时 stdin: "stream-json"、这会儿没在跑的老师起;throttleMs 内同一话题看过就不再看(页面轮询今天)。
|
|
385
|
-
* 起了返回 true;起不了不报错,下一轮冷起。warm: false(cotutor send:跑完就退,留着备用进程会把它挂一小时)不起。
|
|
386
|
-
*/
|
|
387
|
-
async prewarm(tutor, opts = {}) {
|
|
388
|
-
if (this.opts.warm === false)
|
|
389
|
-
return false;
|
|
390
|
-
const t0 = Date.now();
|
|
391
|
-
const seen = `${tutor}\n${opts.thread ?? ''}`;
|
|
392
|
-
if (opts.throttleMs && t0 - (this.prewarmedAt.get(seen) ?? -Infinity) < opts.throttleMs)
|
|
393
|
-
return false;
|
|
394
|
-
this.prewarmedAt.set(seen, t0);
|
|
395
|
-
const ws = this.getWs();
|
|
396
|
-
const t = ws.config.tutors[tutor];
|
|
397
|
-
if (!t?.enabled || !takesTutorRules(tutor) || this.active.has(tutor))
|
|
398
|
-
return false;
|
|
399
|
-
const { runtime } = getRuntime(ws.config, t.runtime);
|
|
400
|
-
if (!this.brain.canWarm(runtime))
|
|
401
|
-
return false;
|
|
402
|
-
const date = localDate((this.opts.now ?? (() => new Date()))());
|
|
403
|
-
const index = await readIndex(ws, tutor, date);
|
|
404
|
-
const known = threads(index.messages);
|
|
405
|
-
const thread = opts.thread && known.includes(opts.thread) ? opts.thread : known.at(-1);
|
|
406
|
-
const session = thread ? sessionFor(index, thread) : null;
|
|
407
|
-
const policy = resolvePolicy(ws.config, tutor);
|
|
408
|
-
const parts = await systemParts(ws, tutor, runtime, policy, { at: '', plan: [], recent: [] });
|
|
409
|
-
const base = { prompt: '', runtime: t.runtime, effort: policy.effort, tools: policy.tools === 'on' ? 'on' : 'off', readDirs: readDirsOf(ws) };
|
|
410
|
-
// 预热的是孩子的下一句:工具照孩子那轮填(带照片的对不上,那轮冷起)。新话题照老师文件;接着的话题是 activity 的,照它的系统提示
|
|
411
|
-
const fresh = this.brain.plan({ ...base, ...parts, session: null });
|
|
412
|
-
const run = thread ? threadActivity(index.messages, thread) : null;
|
|
413
|
-
const act = run ? await activityParts(ws, tutor, run) : null;
|
|
414
|
-
const resumeParts = act ? await systemParts(ws, tutor, runtime, policy, { at: '', plan: [], recent: [] }, act) : parts;
|
|
415
|
-
const resume = session ? this.brain.plan({ ...base, ...resumeParts, session }) : null;
|
|
416
|
-
const cwd = join(ws.dirs.agents, tutor);
|
|
417
|
-
await mkdir(cwd, { recursive: true });
|
|
418
|
-
// 读索引这会儿孩子可能已经开口了:这轮在跑就不起(它收尾时会再起)
|
|
419
|
-
if (this.active.has(tutor))
|
|
420
|
-
return false;
|
|
421
|
-
await this.brain.prepare(fresh);
|
|
422
|
-
if (this.active.has(tutor))
|
|
423
|
-
return false;
|
|
424
|
-
this.brain.warm(tutor, 'fresh', fresh, { cwd, date, lastJob: null });
|
|
425
|
-
if (resume?.resume && thread)
|
|
426
|
-
this.brain.warm(tutor, 'resume', resume, { cwd, date, lastJob: lastJobOf(index, thread) });
|
|
427
|
-
else
|
|
428
|
-
this.brain.dropWarm(tutor, 'resume');
|
|
429
|
-
return true;
|
|
430
|
-
}
|
|
431
321
|
/** 正在回的那条已经出来的板书(流式);没在跑或还没出卡 → null */
|
|
432
322
|
partial(tutor) {
|
|
433
323
|
return this.active.get(tutor)?.partial ?? null;
|
|
@@ -499,7 +389,7 @@ export class Runner {
|
|
|
499
389
|
const query = input.from === 'kid' ? [text, input.home?.brief].filter(Boolean).join('\n') : '';
|
|
500
390
|
const gathered = await gatherContext(ws, tutor, { from: input.from, at: now, focus: input.focus, ...(query ? { query } : {}), ...(act ? { agent: act.memory.agent, kinds: act.kinds } : {}) });
|
|
501
391
|
const policy = resolvePolicy(ws.config, tutor);
|
|
502
|
-
const {
|
|
392
|
+
const { systemBody, activityInSystem } = await systemParts(ws, tutor, policy, gathered, act);
|
|
503
393
|
const activityRun = input.bookkeep ? null : threadRun;
|
|
504
394
|
const activityWarn = activityRun ? await attachActivity(ws, tutor, gathered, activityRun, { date, messages: index.messages, thread, now: now.getTime() }, activityInSystem) : [];
|
|
505
395
|
// 家长笔记原文:新会话整篇带;续会话时这个话题带过、没改的只写「未变」
|
|
@@ -529,10 +419,9 @@ export class Runner {
|
|
|
529
419
|
}));
|
|
530
420
|
// 这轮带不带工具(policy.tools,2026-10-04):孩子说的话缺省不带,只凭上下文包答;带照片的、交了画的(要 Read 看图)、
|
|
531
421
|
// 系统任务带只读的(以前是整份白名单,照片里一句话就能让老师拿 Bash 读写 workspace 外);policy.tools: on 的老师总是整份。
|
|
532
|
-
// 不带的那轮,只有工具才用得上的路径(refs / vault)
|
|
422
|
+
// 不带的那轮,只有工具才用得上的路径(refs / vault)不进包
|
|
533
423
|
const tools = policy.tools === 'on' ? 'on' : input.from !== 'kid' || seen.length > 0 || drawings > 0 ? 'read' : 'off';
|
|
534
|
-
|
|
535
|
-
if (bare) {
|
|
424
|
+
if (tools === 'off') {
|
|
536
425
|
delete pack.refs;
|
|
537
426
|
delete pack.vault;
|
|
538
427
|
}
|
|
@@ -568,29 +457,22 @@ export class Runner {
|
|
|
568
457
|
pack.live = { ...(l.caught ? { caught: l.caught } : {}), ...(l.cut !== undefined ? { cut: l.cut } : {}), ...(l.warmup?.length ? { warmup: l.warmup } : {}), ...(l.phone !== undefined ? { phone: l.phone } : {}), ...(l.app ? { app: l.app } : {}), ...(l.draw?.length ? { draw: l.draw } : {}) };
|
|
569
458
|
}
|
|
570
459
|
const prompt = buildContextPack(pack, text, policy.contextPack);
|
|
571
|
-
const req = { agent, prompt,
|
|
460
|
+
const req = { agent: tutor, prompt, systemBody, runtime: input.runtime ?? t.runtime, effort: policy.effort, tools, readDirs: readDirsOf(ws), session, ...(compact ? { compact: { digest: threadDigest(index.messages.filter((_, i) => known[i] === thread)) } } : {}), ...(pack.photoFiles?.length ? { images: pack.photoFiles } : {}) };
|
|
572
461
|
const plan = this.brain.plan(req);
|
|
573
462
|
// 原声:落在这轮旁边(删话题一起删);写不下就当没有,消息照发
|
|
574
463
|
const started = addMessage(index, { job, thread, at: pack.at, from: input.from, text, focus: input.focus, ...(input.action ? { action: input.action } : {}), ...(cards.length ? { cards: cards.map(({ card, text }) => ({ card, text })) } : {}), ...(photos.length ? { photos } : {}), ...(input.drawing ? { drawing: input.drawing } : {}), ...(input.device ? { device: input.device } : {}), ...(input.deviceId ? { deviceId: input.deviceId } : {}), ...(input.bookkeep ? { bookkeep: input.bookkeep } : {}), ...(input.tidy ? { tidy: true } : {}), ...(input.replayOf ? { replayOf: input.replayOf } : {}), ...(input.eval ? { eval: true } : {}), ...(input.live ? { live: true } : {}), ...(input.via ? { via: input.via } : {}), ...(continued && input.continues ? { continues: { date: input.continues.date, thread: input.continues.thread } } : {}), ...(input.lecture ? { lecture: { bundle: input.lecture.bundle, title: lecture?.title ?? input.lecture.bundle, watchedMs: input.lecture.watchedMs, finished: input.lecture.finished, pauses: input.lecture.pauses, ...(input.lecture.again ? { again: true } : {}), ...(lecture?.video ? { video: true } : {}), ...(marks.length ? { marks } : {}), ...(input.lecture.log?.length ? { log: input.lecture.log } : {}) } } : {}), ...(Object.keys(notes).length ? { notes } : {}), ...(fresh && activityRun ? { activity: activityRun } : {}), ...(fresh && input.talk ? { talk: input.talk } : {}), ...(compact ? { compacted: true } : {}), ...(lectureWarn.length || activityWarn.length ? { warnings: [...lectureWarn, ...activityWarn] } : {}), result: 'running', artifacts: [], runtime: plan.runtime });
|
|
575
464
|
await writeIndex(ws, started);
|
|
576
465
|
// 运行记录(《vault重构方案》§7):除了老师文件与技能,还记这轮带进上下文的每篇笔记、替老师查的每个文件,事后重建得出老师看到的是哪一版
|
|
577
466
|
const notesSent = (pack.notes ?? []).map((n) => ({ role: n.role, path: n.path, hash: textHash(n.text), chars: n.text.length, ...(n.cut ? { cut: true } : {}) }));
|
|
578
|
-
await writeRunFile(ws, tutor, date, job, { at: pack.at, prompt, plan, agentBody:
|
|
579
|
-
// 这个 CLI 在 workspace 里要的机器文件(qwen 的隔离家目录):备不好这轮照起,起不来的原因会在 err.log
|
|
580
|
-
await this.brain.prepare(plan);
|
|
467
|
+
await writeRunFile(ws, tutor, date, job, { at: pack.at, prompt, plan, agentBody: true, sources: { ...(await snapshotSources(ws, tutor)), notes: notesSent, retrieved: gathered.retrieved } });
|
|
581
468
|
active.job = job;
|
|
582
469
|
if (input.live)
|
|
583
470
|
active.live = true;
|
|
584
471
|
// 实时的一轮讲稿是嘴念的,不按 replyMaxChars 截(截了孩子听到半句:10-20「题目已经…」后面「你补上 ēng 就行」没了)
|
|
585
472
|
const runPolicy = input.live ? { ...policy, replyMaxChars: Number.MAX_SAFE_INTEGER } : policy;
|
|
586
|
-
|
|
587
|
-
const lastJob = plan.resume ? lastJobOf(index, thread) : null;
|
|
588
|
-
active.done = this.spawn(ws, tutor, date, job, thread, req, plan, runPolicy, active, lastJob).finally(() => {
|
|
473
|
+
active.done = this.spawn(ws, tutor, date, job, thread, req, plan, runPolicy, active).finally(() => {
|
|
589
474
|
if (this.active.get(tutor) === active)
|
|
590
475
|
this.active.delete(tutor);
|
|
591
|
-
// 孩子 / 家长这轮完了,马上起好下一轮的进程;系统轮(记账、整理记忆)之后孩子多半不在,不起
|
|
592
|
-
if (input.from !== 'system' && !input.replayOf && !input.eval)
|
|
593
|
-
void this.prewarm(tutor).catch(() => { });
|
|
594
476
|
});
|
|
595
477
|
return { tutor, date, job, thread, plan, done: active.done };
|
|
596
478
|
}
|
|
@@ -694,7 +576,7 @@ export class Runner {
|
|
|
694
576
|
const shared = Boolean(ws.config.tutors[tutor]) && tutor.endsWith('-tutor');
|
|
695
577
|
return planMemory(ws, { agent: owner?.agent ?? tutor, display: owner?.display ?? ws.config.tutors[tutor]?.display ?? tutor, items, max: asked?.tidy ? null : MEMORY_MAX_PER_TURN, date, said, shared, ...(seen ? { seen } : {}), ...(childSeen ? { childSeen } : {}) });
|
|
696
578
|
}
|
|
697
|
-
async spawn(ws, tutor, date, job, thread, req, plan, policy, active
|
|
579
|
+
async spawn(ws, tutor, date, job, thread, req, plan, policy, active) {
|
|
698
580
|
const replyMaxChars = policy.replyMaxChars;
|
|
699
581
|
const files = conversationFiles(ws.dirs.conversations, tutor, date);
|
|
700
582
|
// 埋点:从这一轮起算,首卡 = partial 板书第一次有卡(孩子端第一次看到东西),done = 进程退出,dubbed = 配音收尾
|
|
@@ -718,8 +600,8 @@ export class Runner {
|
|
|
718
600
|
}
|
|
719
601
|
};
|
|
720
602
|
// 讲稿不配音(嘴照念):拍关就就绪;卡自己的声音(点读段、单词)照配
|
|
721
|
-
const
|
|
722
|
-
const queue =
|
|
603
|
+
const voice = cardVoice(ws.config.tutors, tutor);
|
|
604
|
+
const queue = voice ? new DubQueue(voice, files.err(job), { cacheDir: ws.dirs.tts, env: this.opts.env }) : null;
|
|
723
605
|
if (queue)
|
|
724
606
|
queue.report = (e) => emit(e.kind === 'queued' ? { lane: 'tts', kind: 'queued', label: e.label } : e.kind === 'done' ? { lane: 'tts', kind: 'done', label: e.label, ms: e.ms, file: e.file ?? '' } : { lane: 'tts', kind: 'failed', label: e.label, ms: e.ms, error: e.error ?? '?' });
|
|
725
607
|
let cardsSeen = 0;
|
|
@@ -765,24 +647,21 @@ export class Runner {
|
|
|
765
647
|
emit({ lane: 'main', kind: 'line', line: linesSeen, text: lines[linesSeen].text, ...lineFlags(lines[linesSeen], section.cards) });
|
|
766
648
|
settleReady();
|
|
767
649
|
};
|
|
768
|
-
//
|
|
769
|
-
const brainRun = this.brain.start(req, plan, { tutor,
|
|
770
|
-
onStart: (s) => emit({ lane: 'main', kind: 'start', cli: s.cli, runtime: s.runtime, resume: s.resume
|
|
650
|
+
// 请老师(src/brain/):正文、工具随流回报
|
|
651
|
+
const brainRun = this.brain.start(req, plan, { tutor, files: { log: files.log(job), err: files.err(job), history: files.brain(thread) }, stall: policy.stall }, {
|
|
652
|
+
onStart: (s) => emit({ lane: 'main', kind: 'start', cli: s.cli, runtime: s.runtime, resume: s.resume }),
|
|
771
653
|
onText: () => {
|
|
772
654
|
dirty = true;
|
|
773
655
|
if (!timer)
|
|
774
656
|
timer = setTimeout(reparse, 150);
|
|
775
657
|
},
|
|
776
658
|
onTool: (tl) => emit({ lane: 'main', kind: 'tool', name: tl.name, sub: tl.sub }),
|
|
777
|
-
onStall: (s) => emit({ lane: 'main', kind: 'stall', idleMs: s.idleMs, retry: s.retry, kept: s.kept }),
|
|
778
659
|
onExit: (at) => {
|
|
779
660
|
timing.doneMs = at - t0;
|
|
780
661
|
if (timer)
|
|
781
662
|
clearTimeout(timer);
|
|
782
663
|
},
|
|
783
664
|
});
|
|
784
|
-
if (brainRun.warm)
|
|
785
|
-
timing.warm = true;
|
|
786
665
|
const { transcript, tools, warnings } = await brainRun.done;
|
|
787
666
|
emit({ lane: 'main', kind: 'exit', ok: transcript.final.ok, ...(transcript.final.reason ? { reason: transcript.final.reason } : {}), ...(transcript.final.inTokens !== undefined ? { tokens: { in: transcript.final.inTokens, out: transcript.final.outTokens ?? 0 } } : {}), ...(transcript.final.numTurns !== undefined ? { turns: transcript.final.numTurns } : {}) });
|
|
788
667
|
const kidView = deriveKidView(transcript, { replyMaxChars }, { live: active.live });
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 一张一张排着画。孩子端没有错误:没画成就停在分幅那几句,原因只给家长看。
|
|
6
6
|
*/
|
|
7
7
|
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
8
|
-
import { join, relative } from 'node:path';
|
|
8
|
+
import { join, relative, sep } from 'node:path';
|
|
9
9
|
import { cardId, conversationFiles } from "../../schema/index.js";
|
|
10
10
|
import { PaintPropsSchema } from "../../board/cards/index.js";
|
|
11
11
|
import { PAINT_DEFAULT, PAINT_NEGATIVE } from "../../schema/index.js";
|
|
@@ -124,7 +124,7 @@ export function paintNote(r) {
|
|
|
124
124
|
const why = e.startsWith('daily:') ? `今天画了 ${e.slice(6).split('/')[0]} 张,到了当时一天的上限(现在不限了)`
|
|
125
125
|
: e.startsWith('gate:') ? `应用拦下了,没交给画家(activity「应用拦的」:${e.slice(5)})`
|
|
126
126
|
: e === 'replay' ? '回放不画'
|
|
127
|
-
: e === 'no_key' ? '拿不到百炼 key(
|
|
127
|
+
: e === 'no_key' ? '拿不到百炼 key(家长端「首页」里填)'
|
|
128
128
|
: e === 'stale' ? '画到一半服务重起了'
|
|
129
129
|
: e.includes('DataInspectionFailed') ? '百炼的内容审核没过'
|
|
130
130
|
: e === 'timeout' ? '等了太久(超时)'
|
|
@@ -148,7 +148,8 @@ export async function enrichPaints(ws, tutor, date, job, at, section, opts = {})
|
|
|
148
148
|
if (r?.state === 'drawing' && since(r.at) > PAINT_STALE_MS)
|
|
149
149
|
r = { state: 'none', at: r.at, error: 'stale' };
|
|
150
150
|
const drawn = r ? (r.state === 'done' ? 'done' : r.state) : since(at) > PAINT_STALE_MS ? 'none' : 'drawing';
|
|
151
|
-
|
|
151
|
+
// 孩子端按它拼取图的地址:Windows 上 relative 给的是反斜杠,换成 /
|
|
152
|
+
const pic = drawn === 'done' ? relative(ws.root, files.cardAsset(job, n, PAINT_PNG)).split(sep).join('/') : undefined;
|
|
152
153
|
return { ...c, props: { ...c.props, drawn, ...(pic ? { pic } : {}), ...(opts.parent ? { note: paintNote(r) } : {}) } };
|
|
153
154
|
}));
|
|
154
155
|
return { ...section, cards };
|