cotutor 0.3.0 → 0.4.1
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 +65 -18
- 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 +11 -0
- package/dist/app/cli/cert.js +36 -9
- 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 +11 -5
- package/dist/app/cli/doctor.js +156 -181
- 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 +379 -27
- 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 +152 -0
- package/dist/app/cli/service.js +443 -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 +42 -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 +236 -0
- package/dist/app/cli/tray.js +670 -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 +47 -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/machine/update.d.ts +25 -0
- package/dist/app/machine/update.js +88 -0
- 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/shared/upgrade.d.ts +9 -0
- package/dist/shared/upgrade.js +23 -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 +337 -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 +6 -2
- 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.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 };
|
package/dist/schema/config.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 人设(display / avatar / voice)、政策、开关全在这里。
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
+
import { DEFAULT_PORT } from '../shared/default-port.ts';
|
|
9
|
+
export { DEFAULT_PORT };
|
|
8
10
|
export declare const PolicySchema: z.ZodObject<{
|
|
9
11
|
replyMaxChars: z.ZodNumber;
|
|
10
12
|
contextPack: z.ZodObject<{
|
|
@@ -68,6 +70,23 @@ export declare const AGENT_NAME_RE: RegExp;
|
|
|
68
70
|
/** 老师卡上的常驻入口:不经首页发布、一直在那位老师的卡上 */
|
|
69
71
|
export declare const TUTOR_EXTRAS: readonly ["dictation", "talk"];
|
|
70
72
|
export type TutorExtra = (typeof TUTOR_EXTRAS)[number];
|
|
73
|
+
/**
|
|
74
|
+
* 老师卡上常驻的三块方砖(设计稿 https://claude.ai/artifact/NpPFoyvuF17dbRSEUuRJfW):先后就是牌上的先后。
|
|
75
|
+
* id 记在消息的 via.button;activity 是老师家里同名的文件(agents/<老师>/activities/<名>.md),孩子看到的字也是它。
|
|
76
|
+
* 家里有那个文件才出那一块(三门主科出厂都有);cotutor.json 的 tiles 只记关掉的,升级来的老 workspace 自动就有
|
|
77
|
+
*/
|
|
78
|
+
export declare const HOME_TILES: readonly [{
|
|
79
|
+
readonly id: "preview";
|
|
80
|
+
readonly activity: "预习";
|
|
81
|
+
}, {
|
|
82
|
+
readonly id: "check";
|
|
83
|
+
readonly activity: "检查作业";
|
|
84
|
+
}, {
|
|
85
|
+
readonly id: "review";
|
|
86
|
+
readonly activity: "复习";
|
|
87
|
+
}];
|
|
88
|
+
export type HomeTileId = (typeof HOME_TILES)[number]['id'];
|
|
89
|
+
export declare const HOME_TILE_IDS: [HomeTileId, ...HomeTileId[]];
|
|
71
90
|
export declare const TutorSchema: z.ZodObject<{
|
|
72
91
|
display: z.ZodString;
|
|
73
92
|
subject: z.ZodOptional<z.ZodString>;
|
|
@@ -119,6 +138,11 @@ export declare const TutorSchema: z.ZodObject<{
|
|
|
119
138
|
dictation: "dictation";
|
|
120
139
|
talk: "talk";
|
|
121
140
|
}>>>;
|
|
141
|
+
tiles: z.ZodOptional<z.ZodObject<{
|
|
142
|
+
check: z.ZodOptional<z.ZodBoolean>;
|
|
143
|
+
preview: z.ZodOptional<z.ZodBoolean>;
|
|
144
|
+
review: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
}, z.core.$strict>>;
|
|
122
146
|
}, z.core.$strip>;
|
|
123
147
|
export type Tutor = z.infer<typeof TutorSchema>;
|
|
124
148
|
/** 学科的标准名:三门主科的别名归一(语文 / 数学 / 英语),别的原样(去两头空白);没写 null */
|
|
@@ -129,16 +153,11 @@ export declare function subjectLang(subject: string | null | undefined): 'zh' |
|
|
|
129
153
|
* 这位老师卡上的常驻入口:配了照配的;没配按学科——语文 = 听写;英语 = 口语课,要口语课开着(talkOn = talk.enabled)
|
|
130
154
|
*/
|
|
131
155
|
export declare function tutorExtras(t: Pick<Tutor, 'extras' | 'subject'>, talkOn?: boolean): TutorExtra[];
|
|
156
|
+
/** 这位老师开着的方砖(牌上的先后);家里有没有那个 activity 文件不在这里管 */
|
|
157
|
+
export declare function tutorTiles(t: Pick<Tutor, 'tiles'>): HomeTileId[];
|
|
132
158
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* 板书写法的两种预载({boardFile} 板书技能 SKILL.md 的绝对路径,给能从文件追加系统提示的 CLI;{systemBody} 老师正文 + 板书写法,给只收一段系统提示文字的 CLI):
|
|
136
|
-
* 模板里用了其中一个,应用就当板书写法已在系统提示里;都没用,应用在话题第一条消息里注入 <cotutor-board>(任何 CLI 都成立的退路)。
|
|
137
|
-
* {effort} 这位老师的政策 effort(low / medium / high),qwen 由适配器写入思考量配置。
|
|
138
|
-
* 带不带工具由适配器展开(src/brain/cli/):{tools} 填一串工具名,独占一个参数的 {toolArgs} 展开成几个参数。
|
|
139
|
-
* 政策旋钮(预算、轮数、模型)写进模板,换 agent 只换运行时。
|
|
140
|
-
* stdin: "stream-json":消息不进命令行,写进 stdin(`--input-format stream-json`),
|
|
141
|
-
* 模板里就不写 {prompt};argv 与消息无关,应用把下一轮的进程提前起好等着。
|
|
159
|
+
* 旧的命令行运行时:一组 {run, resume} 命令模板(起 Qwen Code;更早是 Claude CLI)。2026-11-02 起不再支持,
|
|
160
|
+
* 留着这个形状只为旧 cotutor.json 读得进来:doctor 报出来,用到它的老师发不出去(src/brain/runtime.ts)。
|
|
142
161
|
*/
|
|
143
162
|
export declare const CliRuntimeSchema: z.ZodObject<{
|
|
144
163
|
cli: z.ZodOptional<z.ZodString>;
|
|
@@ -148,8 +167,8 @@ export declare const CliRuntimeSchema: z.ZodObject<{
|
|
|
148
167
|
}, z.core.$strip>;
|
|
149
168
|
export type CliRuntime = z.infer<typeof CliRuntimeSchema>;
|
|
150
169
|
/**
|
|
151
|
-
*
|
|
152
|
-
* 现在只有百炼(OpenAI 兼容的 Chat Completions
|
|
170
|
+
* API 运行时(2026-10-29;2026-11-02 起只有它):应用自己直连模型(src/brain/api.ts)。
|
|
171
|
+
* 现在只有百炼(OpenAI 兼容的 Chat Completions);凭证是机器级的 ~/.config/cotutor/(家长端「首页」填的按量 key 与业务空间 id,开发机还有 Token Plan)。
|
|
153
172
|
* 系统提示 = 老师正文 + 板书写法;同一话题的模型历史由应用存;effort 映射成思考开关与思考量;工具只有只读的三样。
|
|
154
173
|
*/
|
|
155
174
|
export declare const ApiRuntimeSchema: z.ZodObject<{
|
|
@@ -244,6 +263,12 @@ export declare const LiveSchema: z.ZodObject<{
|
|
|
244
263
|
export type LiveConfig = z.infer<typeof LiveSchema>;
|
|
245
264
|
/** 这位老师的嘴用哪个音色 */
|
|
246
265
|
export declare const mouthVoice: (live: Pick<LiveConfig, "voices">, tutor: string) => string;
|
|
266
|
+
/** 卡的配音(点读、单词卡、听写念词)的缺省:出厂三位各一个(2026-11-03 照 Ray 那里用着的),不写进 cotutor.json;别的老师没配就不配音 */
|
|
267
|
+
export declare const SHIPPED_CARD_VOICES: Readonly<Record<string, string>>;
|
|
268
|
+
/** 这位老师卡的配音用哪个音色:家长挑的,没挑用出厂的;都没有 undefined(不配音) */
|
|
269
|
+
export declare const cardVoice: (tutors: Readonly<Record<string, {
|
|
270
|
+
voice?: string;
|
|
271
|
+
}>>, tutor: string) => string | undefined;
|
|
247
272
|
/**
|
|
248
273
|
* 听写:拍照认词表用哪个模型。挑词的要求在听写 activity 文件「认字收到什么」;
|
|
249
274
|
* 下面两份是出厂那节的原文,workspace 里没有听写 activity 文件时照它。回什么格式(一行一段 JSON)由应用填(src/scenes/dictation/recognize.ts 的 FORMAT)。
|
|
@@ -378,6 +403,11 @@ export declare const CotutorConfigSchema: z.ZodObject<{
|
|
|
378
403
|
dictation: "dictation";
|
|
379
404
|
talk: "talk";
|
|
380
405
|
}>>>;
|
|
406
|
+
tiles: z.ZodOptional<z.ZodObject<{
|
|
407
|
+
check: z.ZodOptional<z.ZodBoolean>;
|
|
408
|
+
preview: z.ZodOptional<z.ZodBoolean>;
|
|
409
|
+
review: z.ZodOptional<z.ZodBoolean>;
|
|
410
|
+
}, z.core.$strict>>;
|
|
381
411
|
}, z.core.$strip>>>;
|
|
382
412
|
runtimes: z.ZodObject<{
|
|
383
413
|
default: z.ZodString;
|
|
@@ -437,19 +467,10 @@ export interface TutorView {
|
|
|
437
467
|
policy: Policy;
|
|
438
468
|
/** 老师卡上的常驻入口(已按学科补缺省) */
|
|
439
469
|
extras: TutorExtra[];
|
|
470
|
+
/** 开着的方砖(没管家里有没有那个文件) */
|
|
471
|
+
tiles: HomeTileId[];
|
|
440
472
|
}
|
|
441
473
|
/** 老师列表(带有效政策);kidOnly = 只要孩子端能看到的(enabled 且不 hidden) */
|
|
442
474
|
export declare function listTutors(config: CotutorConfig, opts?: {
|
|
443
475
|
kidOnly?: boolean;
|
|
444
476
|
}): TutorView[];
|
|
445
|
-
/** 运行时模板填占位符;{tools} 填适配器给的工具名单(src/brain/cli/ 的 fillArgs 先算好,没给就填空);{agentBody} / {systemBody} / {boardFile} 只在给了值时替换,否则原样留着(doctor 会报);{effort} 没给用出厂缺省(课文件排版这类不认老师政策的调用) */
|
|
446
|
-
export declare function fillRuntime(argv: readonly string[], vars: {
|
|
447
|
-
agent: string;
|
|
448
|
-
prompt: string;
|
|
449
|
-
session?: string;
|
|
450
|
-
agentBody?: string;
|
|
451
|
-
systemBody?: string;
|
|
452
|
-
boardFile?: string;
|
|
453
|
-
effort?: Policy['effort'];
|
|
454
|
-
tools?: string;
|
|
455
|
-
}): string[];
|