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/app/mock/mock.js
CHANGED
|
@@ -14,14 +14,14 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'no
|
|
|
14
14
|
import { readdir } from 'node:fs/promises';
|
|
15
15
|
import { createServer as createHttp } from 'node:http';
|
|
16
16
|
import { createServer as createHttps } from 'node:https';
|
|
17
|
-
import {
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { parseBoard } from "../../board/parse.js";
|
|
20
20
|
import { cardAssets, parseCardState, stripSecrets, DictationPropsSchema } from "../../board/cards/index.js";
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { bundleAsset, stageAsset, stageIndex, stageVersion } from "../http/stage.js";
|
|
23
23
|
import { sendFile } from "../http/send-file.js";
|
|
24
|
-
import { subjectLang, tutorExtras } from "../../schema/index.js";
|
|
24
|
+
import { HOME_TILES, HOME_TILE_IDS, canonicalSubject, subjectLang, tutorExtras } from "../../schema/index.js";
|
|
25
25
|
import { NEUTRAL_COLOR, tutorColors } from "../../ui/shared/tutor-color.js";
|
|
26
26
|
import { boardMarkOf } from "../../ui/kid/board-mark.js";
|
|
27
27
|
import { backTutorOf } from "../../ui/kid/tutor-face.js";
|
|
@@ -67,15 +67,15 @@ import { wavOfPcm } from "../../shared/wav.js";
|
|
|
67
67
|
import { buildReel, playRecordOk, reelHolds, reelMissRuns } from "../../ui/kid/reel.js";
|
|
68
68
|
import { weekOf } from "../../shared/time.js";
|
|
69
69
|
import {} from "../../schema/index.js";
|
|
70
|
-
import { lanAddresses, listenInfo } from "../cli/serve.js";
|
|
71
|
-
import { qrPage } from "../../ui/
|
|
70
|
+
import { lanAddresses, listenInfo, localName, siteUrl } from "../cli/serve.js";
|
|
71
|
+
import { qrPage } from "../../ui/parent/qr-page.js";
|
|
72
72
|
import { USER_CERT_DIR } from "../workspace.js";
|
|
73
73
|
import { kidThreads } from "../../runtime/kid-view.js";
|
|
74
74
|
import { ICON_SIZES, appIconPng, webManifest } from "../../ui/shared/icon.js";
|
|
75
75
|
import { faviconResponse } from "../../ui/shared/favicon.js";
|
|
76
76
|
import { kidPage } from "../../ui/kid/kid-page.js";
|
|
77
77
|
import { parentPage } from "../../ui/parent/parent-page.js";
|
|
78
|
-
import { arrangeHome, homeOwners, kidButtons, parseHome } from "../../runtime/home/home.js";
|
|
78
|
+
import { arrangeHome, homeOwners, kidButtons, kidTiles, parseHome } from "../../runtime/home/home.js";
|
|
79
79
|
import { packageTheme } from "../cli/themes.js";
|
|
80
80
|
import { cardsCss } from "../../board/cards/docs.js";
|
|
81
81
|
import { VOICE_GALLERY_PAGE } from "../../ui/dev/voice-gallery-page.js";
|
|
@@ -533,7 +533,9 @@ export function createMock(opts = {}) {
|
|
|
533
533
|
// mock 的话题都是新话题进去的:今天孩子问过的那个,挂在新话题按钮上(空白板上的胶囊)
|
|
534
534
|
const resumes = asked ? new Map([['free', { thread: asked.thread, title: (asked.question ?? '').slice(0, 10), at: list[list.length - 1]?.at ?? '' }]]) : undefined;
|
|
535
535
|
const alive = (date, thread) => (date === yesterday() && (past.get(name) ?? []).some((m) => m.thread === thread)) || (date === localDate(d) && list.some((m) => m.thread === thread));
|
|
536
|
-
|
|
536
|
+
// 方砖:三门主科出厂都有预习、检查作业、复习(真服务看老师家里有没有那个 activity 文件)
|
|
537
|
+
const main = ['语文', '数学', '英语'].includes(canonicalSubject(MOCK_TUTORS.find((t) => t.name === name)?.subject) ?? '');
|
|
538
|
+
return { kind: 'tutor', props: { tutor: name, tiles: main ? kidTiles(HOME_TILE_IDS, { usable: () => true }) : [], buttons: kidButtons((c.props.buttons ?? []), { resumes, alive, lecture: (id) => Boolean(mockLecture(id)), today: localDate(d) }).map((b) => { const l = b.kind === 'lecture' ? mockLecture(b.bundle) : null; return l ? { ...b, title: l.title, ms: l.ms, ...(l.video ? { video: true } : {}) } : b; }) } };
|
|
537
539
|
});
|
|
538
540
|
// figshot:和配了 figshot 的 workspace 一样给端口;这台电脑上 figshot 没开着,页面照样藏着这张卡
|
|
539
541
|
return { title, date: localDate(d), tutors: tutorsJson(), home: mockHomeId(), cards, figshot: { port: 8477 }, hand: 'right', ready: scenario !== 'nokey' };
|
|
@@ -579,6 +581,9 @@ export function createMock(opts = {}) {
|
|
|
579
581
|
return null;
|
|
580
582
|
if (via.button === 'new' || via.button === 'recent')
|
|
581
583
|
return via.button;
|
|
584
|
+
const tile = HOME_TILES.find((t) => t.id === via.button);
|
|
585
|
+
if (tile)
|
|
586
|
+
return { kind: 'activity', label: tile.activity, activity: tile.activity, params: {} };
|
|
582
587
|
if (via.home !== mockHomeId() || typeof via.button !== 'number')
|
|
583
588
|
return null;
|
|
584
589
|
const card = parseHome(mockHomeMd(localDate(now()), yesterday())).cards.find((c) => c.kind === 'tutor' && c.props.tutor === name);
|
|
@@ -626,13 +631,14 @@ export function createMock(opts = {}) {
|
|
|
626
631
|
setTimeout(reveal, tick);
|
|
627
632
|
});
|
|
628
633
|
};
|
|
629
|
-
|
|
634
|
+
/** internal = 实时那条连接交来的孩子发的一条(kidSend);HTTP 上不收(同真服务,2026-11-04 删了 HTTP 的发) */
|
|
635
|
+
const routeIn = async (method, path, body, internal = false) => {
|
|
630
636
|
await lecturesLoaded;
|
|
631
637
|
const url = new URL(path, 'http://x');
|
|
632
638
|
const p = url.pathname;
|
|
633
639
|
if (p === '/')
|
|
634
640
|
return { status: 200, html: kidPage(title, {}, await stageVersion()) };
|
|
635
|
-
if (p === '/
|
|
641
|
+
if (p === '/parent/qr')
|
|
636
642
|
return mock.listen ? { status: 200, html: qrPage(title, mock.listen(), url.searchParams.get('via') === 'ip' ? 'ip' : 'name', url.searchParams.get('to') === 'parent' ? 'parent' : 'kid') } : { status: 404, json: { error: 'not_listening' } };
|
|
637
643
|
if (p === '/dev/voice')
|
|
638
644
|
return { status: 200, html: VOICE_GALLERY_PAGE };
|
|
@@ -884,7 +890,7 @@ export function createMock(opts = {}) {
|
|
|
884
890
|
const out = { tutor: name, date: tail, messages: await Promise.all(old.map(kidMessage)), pending: null, thread: old.length ? old[old.length - 1].thread : null };
|
|
885
891
|
return { status: 200, json: out };
|
|
886
892
|
}
|
|
887
|
-
if (tail === 'messages' && method === 'POST') {
|
|
893
|
+
if (tail === 'messages' && method === 'POST' && internal) {
|
|
888
894
|
if (!isObj(body) || typeof body.text !== 'string')
|
|
889
895
|
return { status: 400, json: { error: 'bad_request' } };
|
|
890
896
|
const action = body.action === 'submit' ? 'submit' : undefined;
|
|
@@ -1085,7 +1091,9 @@ export function createMock(opts = {}) {
|
|
|
1085
1091
|
})().catch((err) => { res.writeHead(500, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'internal', message: String(err) })); });
|
|
1086
1092
|
};
|
|
1087
1093
|
const settleJob = async (job) => { await inflight.get(job); };
|
|
1088
|
-
const
|
|
1094
|
+
const route = (method, path, body) => routeIn(method, path, body);
|
|
1095
|
+
const kidSend = (tutor, body) => routeIn('POST', `/api/kid/conversations/${tutor}/messages`, isObj(body) ? { ...body, live: true } : body, true);
|
|
1096
|
+
const mock = { route, kidSend, settle, settleJob, handler, listen: null };
|
|
1089
1097
|
return mock;
|
|
1090
1098
|
}
|
|
1091
1099
|
export async function serveMock(opts = {}) {
|
|
@@ -1111,7 +1119,7 @@ export async function serveMock(opts = {}) {
|
|
|
1111
1119
|
});
|
|
1112
1120
|
const port = server.address().port;
|
|
1113
1121
|
const scheme = tls ? 'https' : 'http';
|
|
1114
|
-
const urls = [
|
|
1122
|
+
const urls = [localName(), ...lanAddresses()].map((h) => siteUrl(scheme, h, port));
|
|
1115
1123
|
mock.listen = () => listenInfo(Boolean(tls), port);
|
|
1116
|
-
return { server, mock, port, https: Boolean(tls), urls, qrPage:
|
|
1124
|
+
return { server, mock, port, https: Boolean(tls), urls, qrPage: siteUrl(scheme, 'localhost', port, '/parent/qr') };
|
|
1117
1125
|
}
|
package/dist/app/replay.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { readFile } from 'node:fs/promises';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { cardLabel } from "../board/cards/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import { parseLogLine } from "../brain/index.js";
|
|
14
14
|
import { UsageError } from "../shared/errors.js";
|
|
15
|
-
import { conversationFiles, threads } from "../schema/index.js";
|
|
15
|
+
import { conversationFiles, threads, viaSendsLabel } from "../schema/index.js";
|
|
16
16
|
import { compactDiff, diffLines } from "./diff.js";
|
|
17
17
|
import { tokenText } from "../shared/tokens.js";
|
|
18
18
|
import { toolCalls } from "../brain/transcript.js";
|
|
@@ -61,7 +61,7 @@ export async function startReplay(ws, tutor, date, job, opts = {}) {
|
|
|
61
61
|
replayOf: job,
|
|
62
62
|
runtime: opts.runtime,
|
|
63
63
|
...(m.via ? { via: m.via } : {}),
|
|
64
|
-
...(m.via &&
|
|
64
|
+
...(m.via && viaSendsLabel(m.via) ? { home: { button: m.via.label, ...(button?.brief ? { brief: button.brief } : {}) } } : {}),
|
|
65
65
|
...(m.continues && continued ? { continues: { ...m.continues, pack: continued } } : {}),
|
|
66
66
|
...(activity ? { activity } : {}),
|
|
67
67
|
// 原轮是实时的:回放也按实时写(上下文包带 live:、不补提问卡);嘴不在,接住与打断的 caught / cut 不带
|
|
@@ -119,7 +119,7 @@ async function toolsOf(ws, tutor, date, m) {
|
|
|
119
119
|
if (m.tools)
|
|
120
120
|
return m.tools;
|
|
121
121
|
const text = await readFile(conversationFiles(ws.dirs.conversations, tutor, date).log(m.job), 'utf8').catch(() => '');
|
|
122
|
-
return toolCalls(text,
|
|
122
|
+
return toolCalls(text, parseLogLine);
|
|
123
123
|
}
|
|
124
124
|
export async function compareReplay(ws, tutor, date, evalJob) {
|
|
125
125
|
const evalWs = evalWorkspace(ws);
|
|
@@ -35,7 +35,7 @@ export function homeSyntaxDoc(dir = PACKAGE_CARDS_DIR) {
|
|
|
35
35
|
- **第一个 \`## \` 起是给家长的**(「## 为什么这么排」),孩子看不到。
|
|
36
36
|
- 围栏外的普通行首页不显示(检查会提醒),要给孩子看的写进卡里。
|
|
37
37
|
|
|
38
|
-
应用保证的:每位孩子端露出的老师都有一张老师卡(没写就补一张只有「新话题」的)
|
|
38
|
+
应用保证的:每位孩子端露出的老师都有一张老师卡(没写就补一张只有「新话题」的);「新话题」永远是第一个按钮;老师家里有预习、检查作业、复习这几个 activity 的,新话题下面常驻这几块方砖(不用写,家长端能关,不占按钮的名额);应用不加「接着刚才的」——今天从同一个入口聊过的,孩子进老师页后在空白板上接(新话题是一个胶囊,activity、小课堂先问接着那个还是重新开始);引用坏了(话题找不到)的按钮不出现。
|
|
39
39
|
|
|
40
40
|
老师卡的按钮最多 ${TUTOR_BUTTONS_MAX} 个,每个最多 ${BUTTON_LABEL_MAX} 个字。
|
|
41
41
|
|
package/dist/app/workspace.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConfigError, UsageError } from '../shared/errors.ts';
|
|
2
2
|
import { redactDeep, redactHome } from '../shared/home.ts';
|
|
3
|
-
import { type RootSource, type Workspace } from '../schema/workspace.ts';
|
|
3
|
+
import { type RootSource, type SandboxMark, type Workspace } from '../schema/workspace.ts';
|
|
4
4
|
import { type CotutorConfig } from '../schema/index.ts';
|
|
5
5
|
export { ConfigError, UsageError, redactDeep, redactHome };
|
|
6
6
|
export type { RootSource, Workspace };
|
|
@@ -29,6 +29,8 @@ export declare function resolveRoot(override?: string, opts?: ResolveOptions): R
|
|
|
29
29
|
/** 校验 cotutor.json 的形状;失败 → ConfigError,信息是逐条修复指南 */
|
|
30
30
|
export declare function parseConfig(raw: unknown, file: string): CotutorConfig;
|
|
31
31
|
export declare function resolvePaths(root: string, paths: Record<string, string>): Workspace['paths'];
|
|
32
|
+
/** 根下的 sandbox 标记(.cotutor/sandbox.json):没有 → null;坏了响亮报错(不当成真 workspace 静默往下跑) */
|
|
33
|
+
export declare function readSandboxMark(root: string): SandboxMark | null;
|
|
32
34
|
export declare function assembleWorkspace(root: string, source: RootSource, config: CotutorConfig): Workspace;
|
|
33
35
|
/** 定根 + 读配置 + 校验 + 展开角色。cotutor.json 缺失也是错(它是必需的)。 */
|
|
34
36
|
export declare function loadWorkspace(override?: string, opts?: ResolveOptions): Workspace;
|
|
@@ -51,8 +53,11 @@ export declare function deepMerge(base: unknown, patch: unknown): unknown;
|
|
|
51
53
|
export declare function patchConfig(ws: Workspace, patch: Record<string, unknown>, opts?: {
|
|
52
54
|
also?: readonly string[];
|
|
53
55
|
}): Promise<CotutorConfig>;
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 文件内容变了(页面写的、或家长在编辑器改的)就重读;坏了抛 ConfigError,调用方决定是留旧的还是响。
|
|
58
|
+
* 比原文不比修改时间:Windows 上写 .tmp 再 rename 连写两次,修改时间常常一模一样(10-31 实测 200 次里 105 次);文件只有几 K,每次读一遍不贵。
|
|
59
|
+
*/
|
|
60
|
+
export declare function reloadIfChanged(ws: Workspace, lastText: string | null): Promise<{
|
|
56
61
|
ws: Workspace;
|
|
57
|
-
|
|
62
|
+
text: string;
|
|
58
63
|
}>;
|
package/dist/app/workspace.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* 配置"在但解析不了"响亮报错,不静默走缺省(沿 drawtell 的教训)。
|
|
8
8
|
*/
|
|
9
9
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
10
|
-
import { writeFile, rename
|
|
10
|
+
import { readFile, writeFile, rename } from 'node:fs/promises';
|
|
11
11
|
import { ConfigError, UsageError } from "../shared/errors.js";
|
|
12
12
|
import { redactDeep, redactHome } from "../shared/home.js";
|
|
13
|
-
import { SKILLS_DIR } from "../schema/workspace.js";
|
|
13
|
+
import { SANDBOX_MARK, SKILLS_DIR, SandboxMarkSchema } from "../schema/workspace.js";
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
16
16
|
import { CotutorConfigSchema, PATH_ROLES, ROLE_DEFAULTS, WORKSPACE_ROLES, explainIssues, } from "../schema/index.js";
|
|
@@ -136,11 +136,24 @@ export function resolvePaths(root, paths) {
|
|
|
136
136
|
out[role] = expandPath(value, vault);
|
|
137
137
|
return out;
|
|
138
138
|
}
|
|
139
|
+
/** 根下的 sandbox 标记(.cotutor/sandbox.json):没有 → null;坏了响亮报错(不当成真 workspace 静默往下跑) */
|
|
140
|
+
export function readSandboxMark(root) {
|
|
141
|
+
const file = join(root, SANDBOX_MARK);
|
|
142
|
+
const raw = readJson(file);
|
|
143
|
+
if (raw === null)
|
|
144
|
+
return null;
|
|
145
|
+
const r = SandboxMarkSchema.safeParse(raw);
|
|
146
|
+
if (!r.success)
|
|
147
|
+
throw new ConfigError(file, explainIssues(r.error.issues).map((l) => ` - ${l}`).join('\n'));
|
|
148
|
+
return r.data;
|
|
149
|
+
}
|
|
139
150
|
export function assembleWorkspace(root, source, config) {
|
|
151
|
+
const sandbox = readSandboxMark(root);
|
|
140
152
|
return {
|
|
141
153
|
root,
|
|
142
154
|
source,
|
|
143
155
|
config,
|
|
156
|
+
...(sandbox ? { sandbox } : {}),
|
|
144
157
|
paths: resolvePaths(root, config.paths),
|
|
145
158
|
dirs: {
|
|
146
159
|
agents: join(root, 'agents'),
|
|
@@ -178,6 +191,7 @@ export function workspaceReport(ws) {
|
|
|
178
191
|
vault: ws.paths.vault,
|
|
179
192
|
tutors: Object.keys(ws.config.tutors),
|
|
180
193
|
runtime: ws.config.runtimes.default,
|
|
194
|
+
...(ws.sandbox ? { sandbox: { from: ws.sandbox.from, forkedAt: ws.sandbox.forkedAt } } : {}),
|
|
181
195
|
});
|
|
182
196
|
}
|
|
183
197
|
// ---- cotutor.json 的补丁写回与热重载(从 runtime/journal/store.ts 搬来,2026-10-29) ----
|
|
@@ -233,13 +247,22 @@ export async function patchConfig(ws, patch, opts = {}) {
|
|
|
233
247
|
await rename(tmp, ws.files.config);
|
|
234
248
|
return r.data;
|
|
235
249
|
}
|
|
236
|
-
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
250
|
+
/**
|
|
251
|
+
* 文件内容变了(页面写的、或家长在编辑器改的)就重读;坏了抛 ConfigError,调用方决定是留旧的还是响。
|
|
252
|
+
* 比原文不比修改时间:Windows 上写 .tmp 再 rename 连写两次,修改时间常常一模一样(10-31 实测 200 次里 105 次);文件只有几 K,每次读一遍不贵。
|
|
253
|
+
*/
|
|
254
|
+
export async function reloadIfChanged(ws, lastText) {
|
|
255
|
+
const text = await readFile(ws.files.config, 'utf8').catch(() => null);
|
|
256
|
+
if (text === null)
|
|
243
257
|
throw new ConfigError(ws.files.config, '不存在了');
|
|
244
|
-
|
|
258
|
+
if (text === lastText)
|
|
259
|
+
return { ws, text };
|
|
260
|
+
let raw;
|
|
261
|
+
try {
|
|
262
|
+
raw = JSON.parse(text);
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
throw new ConfigError(ws.files.config, `不是合法 JSON:${err instanceof Error ? err.message : String(err)}`);
|
|
266
|
+
}
|
|
267
|
+
return { ws: assembleWorkspace(ws.root, ws.source, parseConfig(raw, ws.files.config)), text };
|
|
245
268
|
}
|
package/dist/app/ws/route.d.ts
CHANGED
|
@@ -18,8 +18,14 @@ export interface LiveRouteDeps {
|
|
|
18
18
|
echoWaitMs?: number;
|
|
19
19
|
ringMs?: number;
|
|
20
20
|
stallMs?: number;
|
|
21
|
+
idleCutMs?: number;
|
|
22
|
+
maxTakeMs?: number;
|
|
21
23
|
onFinished?(tutor: string, why: string): void;
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
/** 接升级的函数;openCalls = 现在开着几通(serve 看它定这台电脑能不能睡) */
|
|
26
|
+
export type LiveUpgrade = ((req: IncomingMessage, socket: Duplex, head: Buffer, ctx: LiveApp) => Promise<boolean>) & {
|
|
27
|
+
openCalls(): number;
|
|
28
|
+
};
|
|
29
|
+
export declare function makeLiveUpgrade(deps?: LiveRouteDeps): LiveUpgrade;
|
|
24
30
|
/** 探针:COTUTOR_LIVE_ENDPOINT 指到假百炼 */
|
|
25
|
-
export declare const liveUpgrade:
|
|
31
|
+
export declare const liveUpgrade: LiveUpgrade;
|
package/dist/app/ws/route.js
CHANGED
|
@@ -3,7 +3,8 @@ import { acceptWebSocket, isUpgrade } from "./ws.js";
|
|
|
3
3
|
import { cookieOf } from "../http/access.js";
|
|
4
4
|
import { DEVICE_COOKIE } from "../../schema/kid-device.js";
|
|
5
5
|
export function makeLiveUpgrade(deps = {}) {
|
|
6
|
-
|
|
6
|
+
const open = new Set();
|
|
7
|
+
const liveUpgrade = async function liveUpgrade(req, socket, head, ctx) {
|
|
7
8
|
const m = /^\/api\/live\/([a-z0-9][a-z0-9-]*)\/ws(?:\?.*)?$/.exec(req.url ?? '');
|
|
8
9
|
if (!m || !isUpgrade(req))
|
|
9
10
|
return false;
|
|
@@ -20,10 +21,13 @@ export function makeLiveUpgrade(deps = {}) {
|
|
|
20
21
|
onClose: () => call?.onSocketClose(),
|
|
21
22
|
});
|
|
22
23
|
const listen = new URL(req.url ?? '', 'http://x').searchParams.get('listen') === 'open' ? 'open' : 'hold';
|
|
23
|
-
call = new LiveCall(sock, { ctx, tutor, deviceId: cookieOf(req.headers.cookie, DEVICE_COOKIE), listen, endpoint: deps.endpoint, env: deps.env, heartbeatMs: deps.heartbeatMs, keepAliveMs: deps.keepAliveMs, swapTokens: deps.swapTokens, idleSwapMs: deps.idleSwapMs, echoWaitMs: deps.echoWaitMs, ringMs: deps.ringMs, stallMs: deps.stallMs,
|
|
24
|
+
call = new LiveCall(sock, { ctx, tutor, deviceId: cookieOf(req.headers.cookie, DEVICE_COOKIE), listen, endpoint: deps.endpoint, env: deps.env, heartbeatMs: deps.heartbeatMs, keepAliveMs: deps.keepAliveMs, swapTokens: deps.swapTokens, idleSwapMs: deps.idleSwapMs, echoWaitMs: deps.echoWaitMs, ringMs: deps.ringMs, stallMs: deps.stallMs, idleCutMs: deps.idleCutMs, maxTakeMs: deps.maxTakeMs, onFinished: (why) => { if (call)
|
|
25
|
+
open.delete(call); deps.onFinished?.(tutor, why); } });
|
|
26
|
+
open.add(call);
|
|
24
27
|
void call.start();
|
|
25
28
|
return true;
|
|
26
29
|
};
|
|
30
|
+
return Object.assign(liveUpgrade, { openCalls: () => open.size });
|
|
27
31
|
}
|
|
28
32
|
/** 探针:COTUTOR_LIVE_ENDPOINT 指到假百炼 */
|
|
29
|
-
export const liveUpgrade = makeLiveUpgrade(process.env.COTUTOR_LIVE_ENDPOINT ? { endpoint: process.env.COTUTOR_LIVE_ENDPOINT
|
|
33
|
+
export const liveUpgrade = makeLiveUpgrade(process.env.COTUTOR_LIVE_ENDPOINT ? { endpoint: process.env.COTUTOR_LIVE_ENDPOINT } : {});
|
package/dist/board/parse.d.ts
CHANGED
|
@@ -78,8 +78,6 @@ export declare function playsLecture(line: {
|
|
|
78
78
|
cues?: readonly BoardCue[];
|
|
79
79
|
}, cards: readonly BoardCard[]): boolean;
|
|
80
80
|
export declare function parseBoard(text: string, opts?: ParseBoardOptions): ParsedBoard;
|
|
81
|
-
/** 回复停在写成正文的工具调用上(后面只剩空行):老师在等工具的结果,这一轮其实没写完(runner 据此 resume 一次) */
|
|
82
|
-
export declare function endsWithToolCall(text: string): boolean;
|
|
83
81
|
/** 原文一行的角色:解析器把它当什么了 */
|
|
84
82
|
export type SourceRole =
|
|
85
83
|
/** 讲稿一句(念出来) */
|
package/dist/board/parse.js
CHANGED
|
@@ -247,12 +247,6 @@ export function parseBoard(text, opts = {}) {
|
|
|
247
247
|
},
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
|
-
/** 回复停在写成正文的工具调用上(后面只剩空行):老师在等工具的结果,这一轮其实没写完(runner 据此 resume 一次) */
|
|
251
|
-
export function endsWithToolCall(text) {
|
|
252
|
-
const { spans } = parseBoard(text);
|
|
253
|
-
const last = spans.toolCalls[spans.toolCalls.length - 1];
|
|
254
|
-
return Boolean(last) && text.split('\n').slice(last[1] + 1).every((l) => !l.trim());
|
|
255
|
-
}
|
|
256
250
|
const SECTION_HEAD = /^##\s+(.+?)\s*$/;
|
|
257
251
|
/**
|
|
258
252
|
* 家长端「看原文」用:把老师原文的每一行标上解析器怎么读的。
|
|
@@ -152,7 +152,7 @@ export interface BoardMessage {
|
|
|
152
152
|
reply: string | null;
|
|
153
153
|
pending: boolean;
|
|
154
154
|
section?: BoardSection | null;
|
|
155
|
-
/** 孩子这条带的作业照片(相对 workspace
|
|
155
|
+
/** 孩子这条带的作业照片(相对 workspace 根):答拍给我看卡的进那张卡;别的由老师用 image 卡放上板 */
|
|
156
156
|
photos?: string[];
|
|
157
157
|
/** 看完小课堂后的第一条:这一节前面是小课堂卡 */
|
|
158
158
|
lecture?: KidLecture;
|
|
@@ -163,14 +163,14 @@ export interface BoardMessage {
|
|
|
163
163
|
/** 按住说话的(索引里带 voice):板上去口头禅、一行截断 */
|
|
164
164
|
/** 首页按钮发的:按钮上的字,不是孩子说的 */
|
|
165
165
|
fromHome?: boolean;
|
|
166
|
-
/** 实时的一轮(
|
|
166
|
+
/** 实时的一轮(讲稿嘴照念):板上不看它,一律照实时的画(2026-11-04);只给字幕重放、录像用 */
|
|
167
167
|
live?: boolean;
|
|
168
168
|
}
|
|
169
169
|
/** 板上孩子的一句话:text 是显示的(语音去了口头禅),full 是原话 */
|
|
170
170
|
export interface KidSaid {
|
|
171
171
|
text: string;
|
|
172
172
|
full: string;
|
|
173
|
-
/**
|
|
173
|
+
/** 答拍给我看卡时拍来的照片:放大放在那张卡里(2026-11-04 之前缩在卡下沿) */
|
|
174
174
|
photos?: string[];
|
|
175
175
|
}
|
|
176
176
|
export interface BoardEntry extends BoardSection {
|
|
@@ -178,27 +178,23 @@ export interface BoardEntry extends BoardSection {
|
|
|
178
178
|
at?: string;
|
|
179
179
|
/** 老师还在说:卡只增不改;前 ready 拍就绪了就能播(2026-09-13 之前是整轮跑完才播);整轮跑完换成正式的一节 */
|
|
180
180
|
partial?: boolean;
|
|
181
|
-
/** 孩子问这节时拍的照片(节头上回显) */
|
|
182
|
-
photos?: string[];
|
|
183
181
|
/** 这一节答的是看完小课堂后的第一问:节前画小课堂卡 */
|
|
184
182
|
lecture?: KidLecture;
|
|
185
183
|
/** 这一节答的是孩子从纸上交的画:节前一张孩子的画卡 */
|
|
186
184
|
drawing?: KidDrawing;
|
|
187
|
-
/** 孩子引出这一节的那句话;saidOn =
|
|
185
|
+
/** 孩子引出这一节的那句话;saidOn = 它答的是哪一节的哪张问题卡。节头不是它(看 head):它给左栏用,答拍给我看卡的还进那张卡 */
|
|
188
186
|
said?: KidSaid;
|
|
189
187
|
saidOn?: {
|
|
190
188
|
job: string;
|
|
191
189
|
card: number;
|
|
192
190
|
};
|
|
193
|
-
/**
|
|
194
|
-
live?: true;
|
|
195
|
-
/** 实时:这一轮开头是脑写的节头(只有一行 # 标题的文字卡),从这里起新的一件事,head 是它的标题(liveHead) */
|
|
191
|
+
/** 这一轮开头是脑写的节头(只有一行 # 标题的文字卡),从这里起新的一件事,head 是它的标题(liveHead) */
|
|
196
192
|
head?: string;
|
|
197
|
-
/**
|
|
193
|
+
/** 这一轮开头没写节头,接着上一节画 */
|
|
198
194
|
cont?: true;
|
|
199
195
|
}
|
|
200
196
|
/**
|
|
201
|
-
*
|
|
197
|
+
* 一节怎么接(板上只有实时一种画法,2026-11-04;以前的话题也这样画):新一节的开头由脑定,不再机械是孩子那句。
|
|
202
198
|
* 这一轮第一张卡是节头 → head;没写(只有讲稿、开头是别的卡、还没写出卡)→ cont,接着上一节。一轮中间的节头卡,是从那张卡起新一节
|
|
203
199
|
*/
|
|
204
200
|
export declare function liveHead(s: Pick<BoardSection, 'cards'>): {
|
|
@@ -225,7 +221,7 @@ export declare function kidSaid(messages: readonly BoardMessage[]): Map<string,
|
|
|
225
221
|
}>;
|
|
226
222
|
/**
|
|
227
223
|
* 孩子端条目 → 板书节(没有 section 的条目不出节);
|
|
228
|
-
* 孩子的话挂在它引出的那一节上(said / saidOn,kidSaid);还在跑的只有带 partial 板书(
|
|
224
|
+
* 孩子的话挂在它引出的那一节上(said / saidOn,kidSaid);还在跑的只有带 partial 板书(流式,已有卡或已有句)才出节且标 partial,其余不出。
|
|
229
225
|
*/
|
|
230
226
|
export declare function sectionsFromMessages(messages: readonly BoardMessage[]): BoardEntry[];
|
|
231
227
|
/** 一节在目录里的名字:小节标题 > 有名字的文字卡 > 第一张有字的卡 > 第一句讲稿;截到 14 个字 */
|
|
@@ -269,13 +265,11 @@ export type PlayerEvent = {
|
|
|
269
265
|
type: 'fresh';
|
|
270
266
|
sections: number[];
|
|
271
267
|
};
|
|
272
|
-
/** stop 只停声音(卡的点读、单词);paint = 把某节的标注画齐;
|
|
268
|
+
/** stop 只停声音(卡的点读、单词);paint = 把某节的标注画齐;scrollLast = 滚到最后一节。板上不描「念到哪张」(卡和讲稿不同步,2026-11-04 起以前的话题也这样) */
|
|
273
269
|
export type PlayerEffect = {
|
|
274
270
|
kind: 'stop';
|
|
275
271
|
} | {
|
|
276
272
|
kind: 'render';
|
|
277
|
-
} | {
|
|
278
|
-
kind: 'showNow';
|
|
279
273
|
} | {
|
|
280
274
|
kind: 'paint';
|
|
281
275
|
section: number;
|
|
@@ -433,7 +433,7 @@ export function isQuestion(text) {
|
|
|
433
433
|
return t.endsWith('?') || t.endsWith('?');
|
|
434
434
|
}
|
|
435
435
|
/**
|
|
436
|
-
*
|
|
436
|
+
* 一节怎么接(板上只有实时一种画法,2026-11-04;以前的话题也这样画):新一节的开头由脑定,不再机械是孩子那句。
|
|
437
437
|
* 这一轮第一张卡是节头 → head;没写(只有讲稿、开头是别的卡、还没写出卡)→ cont,接着上一节。一轮中间的节头卡,是从那张卡起新一节
|
|
438
438
|
*/
|
|
439
439
|
export function liveHead(s) {
|
|
@@ -488,16 +488,16 @@ export function kidSaid(messages) {
|
|
|
488
488
|
}
|
|
489
489
|
/**
|
|
490
490
|
* 孩子端条目 → 板书节(没有 section 的条目不出节);
|
|
491
|
-
* 孩子的话挂在它引出的那一节上(said / saidOn,kidSaid);还在跑的只有带 partial 板书(
|
|
491
|
+
* 孩子的话挂在它引出的那一节上(said / saidOn,kidSaid);还在跑的只有带 partial 板书(流式,已有卡或已有句)才出节且标 partial,其余不出。
|
|
492
492
|
*/
|
|
493
493
|
export function sectionsFromMessages(messages) {
|
|
494
494
|
const out = [];
|
|
495
495
|
const says = kidSaid(messages);
|
|
496
496
|
for (const m of messages) {
|
|
497
497
|
const k = says.get(m.job);
|
|
498
|
-
const at = { ...(m.at ? { at: m.at } : {}), ...(m.
|
|
498
|
+
const at = { ...(m.at ? { at: m.at } : {}), ...(m.lecture ? { lecture: m.lecture } : {}), ...(m.drawing ? { drawing: m.drawing } : {}), ...(k ? { said: k.said, ...(k.on ? { saidOn: k.on } : {}) } : {}), ...liveHead(m.section ?? { cards: [] }) };
|
|
499
499
|
if (m.pending) {
|
|
500
|
-
if (m.section && m.section.partial && (m.section.cards.length ||
|
|
500
|
+
if (m.section && m.section.partial && (m.section.cards.length || m.section.lines.length))
|
|
501
501
|
out.push({ job: m.job, ...at, cards: m.section.cards, lines: m.section.lines, partial: true, ready: m.section.ready ?? 0, ...(m.section.layout ? { layout: m.section.layout } : {}) });
|
|
502
502
|
continue;
|
|
503
503
|
}
|
|
@@ -558,7 +558,7 @@ export function step(model, ev, ctx) {
|
|
|
558
558
|
case 'liveStart':
|
|
559
559
|
fx.push({ kind: 'paint', section: ev.section });
|
|
560
560
|
m = { state: { section: ev.section, line: -1, status: 'thinking' } };
|
|
561
|
-
fx.push({ kind: 'render' }
|
|
561
|
+
fx.push({ kind: 'render' });
|
|
562
562
|
break;
|
|
563
563
|
case 'liveBeat':
|
|
564
564
|
if (m.state.section !== ev.section || m.state.status !== 'thinking')
|
|
@@ -569,12 +569,10 @@ export function step(model, ev, ctx) {
|
|
|
569
569
|
fx.push({ kind: 'paint', section: ev.section });
|
|
570
570
|
if (m.state.section !== ev.section)
|
|
571
571
|
break;
|
|
572
|
-
if (m.state.status !== 'thinking')
|
|
573
|
-
fx.push({ kind: 'showNow' });
|
|
572
|
+
if (m.state.status !== 'thinking')
|
|
574
573
|
break;
|
|
575
|
-
}
|
|
576
574
|
m = { state: playerAtEnd(secs) };
|
|
577
|
-
fx.push({ kind: 'render' }
|
|
575
|
+
fx.push({ kind: 'render' });
|
|
578
576
|
break;
|
|
579
577
|
case 'liveDropped':
|
|
580
578
|
fx.push({ kind: 'stop' });
|
|
@@ -587,7 +585,7 @@ export function step(model, ev, ctx) {
|
|
|
587
585
|
for (const i of ev.sections)
|
|
588
586
|
fx.push({ kind: 'paint', section: i });
|
|
589
587
|
m = { state: playerAtEnd(secs) };
|
|
590
|
-
fx.push({ kind: 'render' }, { kind: '
|
|
588
|
+
fx.push({ kind: 'render' }, { kind: 'scrollLast' });
|
|
591
589
|
break;
|
|
592
590
|
}
|
|
593
591
|
return { model: m, effects: fx };
|
package/dist/brain/api-tools.js
CHANGED
|
@@ -137,8 +137,9 @@ export function readOnlyTools(roots, opts = {}) {
|
|
|
137
137
|
const relOf = async (p) => {
|
|
138
138
|
for (const root of await realRoots()) {
|
|
139
139
|
const rel = relative(root, p);
|
|
140
|
+
// 模型给的通配按 / 写(如 随便/*期中*.md),Windows 上 relative 给的是反斜杠
|
|
140
141
|
if (!outside(rel))
|
|
141
|
-
return rel;
|
|
142
|
+
return rel.split(sep).join('/');
|
|
142
143
|
}
|
|
143
144
|
return p;
|
|
144
145
|
};
|
package/dist/brain/api.d.ts
CHANGED
|
@@ -1,57 +1,52 @@
|
|
|
1
1
|
import { type ModelMessage } from 'ai';
|
|
2
|
-
import type
|
|
3
|
-
import { type RunPlan } from './cli/run-plan.ts';
|
|
2
|
+
import { type RunPlan } from './runtime.ts';
|
|
4
3
|
import { type ApiRuntime, type CotutorConfig, type Policy } from '../schema/index.ts';
|
|
5
|
-
import type { BrainHooks, BrainRequest, BrainRun
|
|
4
|
+
import type { BrainHooks, BrainRequest, BrainRun } from './index.ts';
|
|
6
5
|
/** 计划里记的 cli 名:API 的轮次按它分流 */
|
|
7
6
|
export declare const API_CLI = "api";
|
|
8
7
|
/**
|
|
9
8
|
* 出厂的 API 运行时(2026-10-31 起新装缺省 api,Qwen Code 那两条留作备用):模型同那两条——api 对 qwen(qwen3.8-max),api-flash 对 qwen-flash(qwen3.8-flash)
|
|
10
9
|
*/
|
|
11
10
|
export declare const FACTORY_API_RUNTIMES: Record<string, ApiRuntime>;
|
|
12
|
-
/** .log 的一行 → 统一事件(API 轮次的日志就是一行一条统一事件);不是这种行回 [] */
|
|
13
|
-
export declare function parseEventLine(line: string): CliEvent[];
|
|
14
11
|
/** 政策 effort → 思考开关与思考量(同 qwen:low 不想,孩子在等;带工具的轮次至少想一点,不然照片题连资料都不查) */
|
|
15
12
|
export declare function thinkingOf(effort: Policy['effort'] | undefined, tools: BrainRequest['tools']): {
|
|
16
13
|
enableThinking: boolean;
|
|
17
14
|
thinkingBudget?: number;
|
|
18
15
|
};
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
17
|
+
* 有思考档的模型发 reasoning_effort(照当时 Qwen Code 的档):想一点 = low、不限 = xhigh;不想或没有档 → null,照 thinkingOf 发。
|
|
21
18
|
* low 是它自己看着想:闲聊想一两百 token,认照片题想得多;thinking_budget 是死卡,1024 时有照片的话题每轮都想满(2026-10-31)
|
|
22
19
|
*/
|
|
23
20
|
export declare function effortOf(model: string, effort: Policy['effort'] | undefined, tools: BrainRequest['tools']): 'low' | 'xhigh' | null;
|
|
24
21
|
/** AI SDK 的 alibaba 只认 thinking_budget:在 fetch 这一层把请求体里的换成 reasoning_effort(SDK 给的中间件口子) */
|
|
25
22
|
export declare function effortFetch(effort: 'low' | 'xhigh', base?: typeof fetch): typeof fetch;
|
|
26
|
-
/**
|
|
27
|
-
* 百炼 Chat Completions 的端点:知道业务空间 id 就走它的域名(原型验过,同 realtime);只有按量 key 就走公网域名(qwen 那边按量的线也走它,2026-10-29 真跑通)
|
|
28
|
-
*/
|
|
29
|
-
export declare function bailianBase(workspaceId: string | null): string;
|
|
30
23
|
/**
|
|
31
24
|
* 已经结束的一轮,拼回历史时的老师那条:正文照原样,思考不带,工具往返换成一句「(用了 …,回了 N 字;结果这里不再带,要看再查)」。
|
|
32
25
|
* 只剩一条纯文字的回复,历史里永远没有配不上对的工具消息
|
|
33
26
|
*/
|
|
34
27
|
export declare function slimTurn(messages: readonly ModelMessage[]): string;
|
|
28
|
+
/** 这个模型发到哪、带哪把 key(src/shared/qianwen.ts 的 compatibleLine);null = 没配 */
|
|
29
|
+
export type BrainLine = (model: string) => {
|
|
30
|
+
baseURL: string;
|
|
31
|
+
key: string;
|
|
32
|
+
plan: boolean;
|
|
33
|
+
} | null;
|
|
35
34
|
export declare class ApiBrain {
|
|
36
35
|
private readonly workspace;
|
|
37
|
-
private readonly
|
|
38
|
-
/** 测试与探针:…/compatible-mode/v1(假百炼)
|
|
36
|
+
private readonly line;
|
|
37
|
+
/** 测试与探针:…/compatible-mode/v1(假百炼);不给按模型选线 */
|
|
39
38
|
private readonly endpoint;
|
|
40
39
|
constructor(opts: {
|
|
41
40
|
workspace: () => {
|
|
42
41
|
config: CotutorConfig;
|
|
43
42
|
root: string;
|
|
44
43
|
};
|
|
45
|
-
|
|
46
|
-
key: string;
|
|
47
|
-
workspaceId: string | null;
|
|
48
|
-
} | null;
|
|
44
|
+
line: BrainLine;
|
|
49
45
|
endpoint?: string;
|
|
50
46
|
});
|
|
51
47
|
plan(req: BrainRequest): RunPlan;
|
|
52
48
|
start(req: BrainRequest, plan: RunPlan, ctx: {
|
|
53
49
|
tutor: string;
|
|
54
|
-
key: WarmKey;
|
|
55
50
|
files: {
|
|
56
51
|
log: string;
|
|
57
52
|
err: string;
|