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
|
@@ -167,19 +167,15 @@ description: cotutor 有脸的老师共同的守则:上下文包怎么看、三
|
|
|
167
167
|
|
|
168
168
|
实时的板是这个话题的笔记和练习。孩子听完、说完,回头看的是它。
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
孩子拍来古诗练习,圈了第一题。先讲,问他:
|
|
171
171
|
|
|
172
172
|
````
|
|
173
|
-
```text
|
|
174
|
-
# 补读音:瀑、布、穷、尽
|
|
175
|
-
```
|
|
176
|
-
|
|
177
173
|
这一题给四个字补读音,都在《望庐山瀑布》里。
|
|
178
174
|
先看「瀑」。山上冲下来的大水,叫瀑布。
|
|
179
175
|
你读读看,「瀑」怎么念?
|
|
180
176
|
````
|
|
181
177
|
|
|
182
|
-
|
|
178
|
+
四个字他都补好了。把定下来的记上:
|
|
183
179
|
|
|
184
180
|
````
|
|
185
181
|
```text
|
|
@@ -189,12 +185,8 @@ description: cotutor 有脸的老师共同的守则:上下文包怎么看、三
|
|
|
189
185
|
四个都补好了,这一题做完了。
|
|
190
186
|
````
|
|
191
187
|
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- 拍来新的一道题,也先写节头,作业那张卡跟在它后面。
|
|
195
|
-
<!-- 为什么:回放 10-20 2113-13(拍来第二题)两次都没写节头:板书写法「作业照片」说第一张卡放作业,压过了这条。 -->
|
|
196
|
-
- 节头一行说清这件事,不抄孩子的原话。
|
|
197
|
-
- 还是这件事,就不写节头。你的卡接在上一节后面。
|
|
188
|
+
<!-- 为什么没有节头的规矩:10-21 加过「新的一件事,第一张卡写节头」,到 11-04 一共 12 轮实时,0 轮照做(该写的 6 轮)。
|
|
189
|
+
它还和板书写法「作业照片:第一张卡放作业」打架。11-04 去掉。老师自己写了只有一行 # 标题的 text 卡,板上照样起新一节(presentation.ts 的 liveHead)。 -->
|
|
198
190
|
- 讲完的、孩子答过的,记成一张笔记卡。
|
|
199
191
|
- 要孩子做的出成练习,照「一步一步教」。
|
|
200
192
|
- 板上不写这一步要他自己想的答案,只放题和提示。
|
|
@@ -204,7 +196,7 @@ description: cotutor 有脸的老师共同的守则:上下文包怎么看、三
|
|
|
204
196
|
- 卡写在哪句前后都行,不用一张卡讲三句。
|
|
205
197
|
- 讲稿里的 `[词]` 照旧在卡上标出来。
|
|
206
198
|
- 笔记可以比讲稿写得全。孩子打断了,板上的还在。
|
|
207
|
-
<!--
|
|
199
|
+
<!-- 为什么:实时的板不配音、卡总晚于嘴,不用和讲稿一拍一拍对上。板上原来多半是应用拼的:孩子的原话当节头,末句的问话补成提问卡(10-20 晚语文 9 张文字卡 7 张是补的)。 -->
|
|
208
200
|
|
|
209
201
|
{{有 lecture}}
|
|
210
202
|
## 想要小课堂
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { type CliRuntime, type Policy } from '../../schema/index.ts';
|
|
2
|
-
import type { CliAdapter, CliEvent, ToolKind, ToolSet } from './types.ts';
|
|
3
|
-
export { textsOf, toolsOf, type AssistantPart, type CliAdapter, type CliEvent, type CliFinal, type ToolKind, type ToolResult, type ToolSet, type ToolUse } from './types.ts';
|
|
4
|
-
/** 登记在这里的才认;接一个新 CLI 加一行 */
|
|
5
|
-
export declare const ADAPTERS: readonly CliAdapter[];
|
|
6
|
-
/** 自定义壳脚本与测试进程的 stream-json 协议,不准备 Qwen 的家目录与认证。 */
|
|
7
|
-
export declare const STREAM_JSON: CliAdapter;
|
|
8
|
-
/** 运行时的 CLI 名:cli 字段,或 run[0] 的文件名 */
|
|
9
|
-
export declare function runtimeCli(runtime: {
|
|
10
|
-
run: readonly string[];
|
|
11
|
-
cli?: string;
|
|
12
|
-
}): string;
|
|
13
|
-
export declare function adapterNamed(name: string): CliAdapter | null;
|
|
14
|
-
/** 旧配置仍可读取,但退役的运行器不能通过通用协议兜底启动。 */
|
|
15
|
-
export declare function runtimeIssue(runtime: {
|
|
16
|
-
run: readonly string[];
|
|
17
|
-
resume?: readonly string[];
|
|
18
|
-
cli?: string;
|
|
19
|
-
}): string | null;
|
|
20
|
-
export declare function adapterFor(runtime: {
|
|
21
|
-
run: readonly string[];
|
|
22
|
-
resume?: readonly string[];
|
|
23
|
-
cli?: string;
|
|
24
|
-
}): CliAdapter;
|
|
25
|
-
/** 工具名属于哪一类:问遍登记的适配器(各家工具名不重),都不认就是 other。老数据只存了名字,也能认 */
|
|
26
|
-
export declare function toolKind(name: string): ToolKind;
|
|
27
|
-
/** 模板管得了这轮带不带工具吗:用了 `{tools}` 或 `{toolArgs}`。管不了的,应用也就不替它省掉上下文包里只有工具才用得上的路径 */
|
|
28
|
-
export declare function controlsTools(runtime: CliRuntime): boolean;
|
|
29
|
-
/** 出厂运行时:各适配器的模板合在一起(default 由 skeleton 定) */
|
|
30
|
-
export declare function factoryRuntimes(): Record<string, CliRuntime>;
|
|
31
|
-
export interface FillVars {
|
|
32
|
-
agent: string;
|
|
33
|
-
prompt: string;
|
|
34
|
-
session?: string;
|
|
35
|
-
agentBody?: string;
|
|
36
|
-
systemBody?: string;
|
|
37
|
-
boardFile?: string;
|
|
38
|
-
effort?: Policy['effort'];
|
|
39
|
-
/** 这轮带不带工具;不给 = 带 */
|
|
40
|
-
tools?: ToolSet;
|
|
41
|
-
/** 这轮该读得到的目录(workspace 根、vault) */
|
|
42
|
-
readDirs?: readonly string[];
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* 填模板:独占一个参数的 `{toolArgs}` 换成适配器给的几个参数(可以是零个),`{tools}` 换成适配器的工具名单,其余占位照 schema 的 fillRuntime。
|
|
46
|
-
*/
|
|
47
|
-
export declare function fillArgs(adapter: CliAdapter, argv: readonly string[], vars: FillVars): string[];
|
|
48
|
-
/** 起老师进程的环境:基础环境 + 这轮计划里适配器定的变量,再交给适配器补齐认证环境 */
|
|
49
|
-
export declare function processEnv(plan: {
|
|
50
|
-
cli: string;
|
|
51
|
-
env: Record<string, string>;
|
|
52
|
-
}, base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
53
|
-
/** 按计划里记的适配器名读输出(老数据没有就按 stream-json) */
|
|
54
|
-
export declare function parserOf(cli: string | undefined): (line: string) => CliEvent[];
|
|
55
|
-
/** 按运行时名读输出(落盘的老轮次只记了运行时名;运行时后来删了或改了就按 stream-json)。API 运行时的日志不归这里读,见 src/brain/index.ts 的 logParser */
|
|
56
|
-
export declare function parserForRuntime(runtimes: Readonly<Record<string, unknown>>, name: string | undefined): (line: string) => CliEvent[];
|
|
57
|
-
/** 起进程前让这个 CLI 的适配器备好 workspace 里的机器文件(qwen 的隔离家目录);没有这一步的 CLI 什么都不做 */
|
|
58
|
-
export declare function prepareCli(cli: string, root: string): Promise<void>;
|
|
59
|
-
/** 进程起来报的工具表里,不该有的那几样(适配器不核对就是空) */
|
|
60
|
-
export declare function unexpectedTools(cli: string, set: ToolSet, tools: readonly string[]): string[];
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 适配器注册表:运行时写了 `cli` 就按它找,没写按 run[0] 的文件名。
|
|
3
|
-
* 都对不上(测试的假 CLI 是 `node …_fake-cli.ts`、自家套的壳脚本没写 cli)就按通用的 stream-json 读,
|
|
4
|
-
* 只复用流协议与 Qwen 的工具参数,不设置 Qwen 的运行环境。
|
|
5
|
-
*/
|
|
6
|
-
import { basename } from 'node:path';
|
|
7
|
-
import { POLICY_DEFAULTS, fillRuntime } from "../../schema/index.js";
|
|
8
|
-
import { parseStreamJson, streamJsonUserMessage } from "./stream-json.js";
|
|
9
|
-
import { qwen } from "./qwen.js";
|
|
10
|
-
export { textsOf, toolsOf } from "./types.js";
|
|
11
|
-
/** 登记在这里的才认;接一个新 CLI 加一行 */
|
|
12
|
-
export const ADAPTERS = [qwen];
|
|
13
|
-
/** 自定义壳脚本与测试进程的 stream-json 协议,不准备 Qwen 的家目录与认证。 */
|
|
14
|
-
export const STREAM_JSON = {
|
|
15
|
-
name: 'stream-json',
|
|
16
|
-
parse: parseStreamJson,
|
|
17
|
-
stdinMessage: streamJsonUserMessage,
|
|
18
|
-
toolList: qwen.toolList,
|
|
19
|
-
toolArgs: qwen.toolArgs,
|
|
20
|
-
toolKind: qwen.toolKind,
|
|
21
|
-
env: (env) => env,
|
|
22
|
-
runtimes: () => ({}),
|
|
23
|
-
};
|
|
24
|
-
/** 历史对话里的工具名仍要能展示读过的文件,不用于启动旧 CLI。 */
|
|
25
|
-
const LEGACY_TOOL_KINDS = { Read: 'read', Bash: 'shell', Grep: 'search', Glob: 'search', Edit: 'edit', Write: 'edit', NotebookEdit: 'edit', MultiEdit: 'edit', Skill: 'skill' };
|
|
26
|
-
/** 运行时的 CLI 名:cli 字段,或 run[0] 的文件名 */
|
|
27
|
-
export function runtimeCli(runtime) {
|
|
28
|
-
return runtime.cli ?? basename(runtime.run[0] ?? '');
|
|
29
|
-
}
|
|
30
|
-
export function adapterNamed(name) {
|
|
31
|
-
return ADAPTERS.find((a) => a.name === name) ?? null;
|
|
32
|
-
}
|
|
33
|
-
/** 旧配置仍可读取,但退役的运行器不能通过通用协议兜底启动。 */
|
|
34
|
-
export function runtimeIssue(runtime) {
|
|
35
|
-
const retired = [runtime.cli, basename(runtime.run[0] ?? ''), basename(runtime.resume?.[0] ?? '')].some((name) => name && /^claude(?:\.exe|\.cmd)?$/i.test(name));
|
|
36
|
-
return retired ? 'Claude CLI 已移除;先 cotutor upgrade --config 补齐出厂运行时,把 runtimes.default 和 tutors.*.runtime 改为 api 或 api-flash(应用直连百炼;用 Qwen Code 就改为 qwen 或 qwen-flash),再删除旧运行时条目' : null;
|
|
37
|
-
}
|
|
38
|
-
export function adapterFor(runtime) {
|
|
39
|
-
const issue = runtimeIssue(runtime);
|
|
40
|
-
if (issue)
|
|
41
|
-
throw new Error(issue);
|
|
42
|
-
return adapterNamed(runtimeCli(runtime)) ?? STREAM_JSON;
|
|
43
|
-
}
|
|
44
|
-
/** 工具名属于哪一类:问遍登记的适配器(各家工具名不重),都不认就是 other。老数据只存了名字,也能认 */
|
|
45
|
-
export function toolKind(name) {
|
|
46
|
-
for (const a of ADAPTERS) {
|
|
47
|
-
const k = a.toolKind(name);
|
|
48
|
-
if (k !== 'other')
|
|
49
|
-
return k;
|
|
50
|
-
}
|
|
51
|
-
return LEGACY_TOOL_KINDS[name] ?? 'other';
|
|
52
|
-
}
|
|
53
|
-
/** 模板管得了这轮带不带工具吗:用了 `{tools}` 或 `{toolArgs}`。管不了的,应用也就不替它省掉上下文包里只有工具才用得上的路径 */
|
|
54
|
-
export function controlsTools(runtime) {
|
|
55
|
-
return [...runtime.run, ...runtime.resume].some((a) => a.includes('{tools}') || a === '{toolArgs}');
|
|
56
|
-
}
|
|
57
|
-
/** 出厂运行时:各适配器的模板合在一起(default 由 skeleton 定) */
|
|
58
|
-
export function factoryRuntimes() {
|
|
59
|
-
return Object.assign({}, ...ADAPTERS.map((a) => a.runtimes()));
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* 填模板:独占一个参数的 `{toolArgs}` 换成适配器给的几个参数(可以是零个),`{tools}` 换成适配器的工具名单,其余占位照 schema 的 fillRuntime。
|
|
63
|
-
*/
|
|
64
|
-
export function fillArgs(adapter, argv, vars) {
|
|
65
|
-
const set = vars.tools ?? 'on';
|
|
66
|
-
const spread = argv.flatMap((a) => (a === '{toolArgs}' ? adapter.toolArgs(set, { readDirs: vars.readDirs ?? [] }) : [a]));
|
|
67
|
-
return fillRuntime(spread, { ...vars, effort: vars.effort ?? POLICY_DEFAULTS.effort, tools: adapter.toolList(set) });
|
|
68
|
-
}
|
|
69
|
-
/** 起老师进程的环境:基础环境 + 这轮计划里适配器定的变量,再交给适配器补齐认证环境 */
|
|
70
|
-
export function processEnv(plan, base) {
|
|
71
|
-
const adapter = adapterNamed(plan.cli) ?? STREAM_JSON;
|
|
72
|
-
return adapter.env({ ...base, ...plan.env });
|
|
73
|
-
}
|
|
74
|
-
/** 按计划里记的适配器名读输出(老数据没有就按 stream-json) */
|
|
75
|
-
export function parserOf(cli) {
|
|
76
|
-
return (cli ? adapterNamed(cli) : null)?.parse ?? STREAM_JSON.parse;
|
|
77
|
-
}
|
|
78
|
-
/** 按运行时名读输出(落盘的老轮次只记了运行时名;运行时后来删了或改了就按 stream-json)。API 运行时的日志不归这里读,见 src/brain/index.ts 的 logParser */
|
|
79
|
-
export function parserForRuntime(runtimes, name) {
|
|
80
|
-
const r = name && name !== 'default' ? runtimes[name] : undefined;
|
|
81
|
-
return r && typeof r === 'object' && Array.isArray(r.run) ? (adapterNamed(runtimeCli(r)) ?? STREAM_JSON).parse : STREAM_JSON.parse;
|
|
82
|
-
}
|
|
83
|
-
/** 起进程前让这个 CLI 的适配器备好 workspace 里的机器文件(qwen 的隔离家目录);没有这一步的 CLI 什么都不做 */
|
|
84
|
-
export async function prepareCli(cli, root) {
|
|
85
|
-
await adapterNamed(cli)?.prepare?.(root);
|
|
86
|
-
}
|
|
87
|
-
/** 进程起来报的工具表里,不该有的那几样(适配器不核对就是空) */
|
|
88
|
-
export function unexpectedTools(cli, set, tools) {
|
|
89
|
-
const want = adapterNamed(cli)?.expectedTools?.(set);
|
|
90
|
-
return want ? tools.filter((t) => !want.includes(t)) : [];
|
|
91
|
-
}
|
package/dist/brain/cli/qwen.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { CliAdapter } from './types.ts';
|
|
2
|
-
/** workspace 里归 qwen 的目录(机器文件,prepare 每次对一遍) */
|
|
3
|
-
export declare const QWEN_DIR = ".cotutor/qwen";
|
|
4
|
-
/** 出厂模型(2026-10-09 五个模型 7 道真题对比:不想时 2–8 秒,讲得准也细;3.7-plus 快但浅,3.8-flash 不想时把盘算写进讲稿——
|
|
5
|
-
* 2026-10-13 英语老师三问没再见到,出厂 qwen-flash 运行时给英语老师) */
|
|
6
|
-
export declare const QWEN_DEFAULT_MODEL = "qwen3.8-max";
|
|
7
|
-
/** 隔离的家里那份 settings.json(没有 key,只写环境变量名) */
|
|
8
|
-
export declare function qwenHomeSettings(): Record<string, unknown>;
|
|
9
|
-
/** 这几份机器文件:绝对路径 → 内容 */
|
|
10
|
-
export declare function qwenFiles(root: string): Record<string, string>;
|
|
11
|
-
export declare const qwen: CliAdapter;
|
package/dist/brain/cli/qwen.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Qwen Code(`qwen`,0.25 起)的适配器。输出读法在 stream-json.ts,运行约定:
|
|
3
|
-
* - **隔离的家**:不用 `--bare`(它不读任何配置、不认 `--core-tools`、自带 read_file / edit / run_shell_command 一套)。
|
|
4
|
-
* `QWEN_HOME` 指 workspace 的 `.cotutor/qwen/home/`(不改 HOME),里面的 settings.json 由这里生成:模型供应商(key 只写环境变量名)、
|
|
5
|
-
* 关掉它自己的记忆整理(不然一轮跑完在后台用 write_file 写文件)、使用统计、遥测、防休眠。会话落在 `.cotutor/qwen/runtime/`。
|
|
6
|
-
* - **工具**:`{toolArgs}` 展开成 `--core-tools` + `--exclude-tools`;不带 = 工具表为空,只读 = read_file / glob / grep_search。
|
|
7
|
-
* 模板带 `--approval-mode default`:shell、写、读 workspace 外都被拒(`--yolo` 会放行 workspace 外的读)。
|
|
8
|
-
* - **思考量**:没有旗标;按 effort 指一份系统配置(`QWEN_CODE_SYSTEM_SETTINGS_PATH`,`model.reasoningEffort`)。
|
|
9
|
-
* - **key 与端点**:settings.json 只写 `${COTUTOR_QWEN_BASE_URL}` 与环境变量名 `COTUTOR_QWEN_API_KEY`(qwen 读配置时展开);起进程时按 `src/shared/qianwen.ts`
|
|
10
|
-
* 定走 Token Plan 还是百炼按量,把这两个填进子进程的环境,不落盘。出厂的几个模型 Token Plan 都有,有它的 key 就走它。
|
|
11
|
-
* - 没有钱数,只报 token;超时(退出码 55)不吐 result;每轮一条 goal_state 事件(读法里不出事件)。
|
|
12
|
-
*/
|
|
13
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
14
|
-
import { dirname, join } from 'node:path';
|
|
15
|
-
import { compatibleBase, qianwenKeys, qianwenLine } from "../../shared/qianwen.js";
|
|
16
|
-
import { parseStreamJson, streamJsonUserMessage } from "./stream-json.js";
|
|
17
|
-
const KINDS = {
|
|
18
|
-
read_file: 'read',
|
|
19
|
-
read_many_files: 'read',
|
|
20
|
-
zoom_image: 'read',
|
|
21
|
-
list_directory: 'read',
|
|
22
|
-
run_shell_command: 'shell',
|
|
23
|
-
glob: 'search',
|
|
24
|
-
grep_search: 'search',
|
|
25
|
-
edit: 'edit',
|
|
26
|
-
write_file: 'edit',
|
|
27
|
-
notebook_edit: 'edit',
|
|
28
|
-
skill: 'skill',
|
|
29
|
-
};
|
|
30
|
-
/** 只读那轮给的核心工具(读文件含图片、按名找、按内容找) */
|
|
31
|
-
const READ_TOOLS = ['read_file', 'glob', 'grep_search'];
|
|
32
|
-
/** `--core-tools` 管不到的非核心工具,要一个个排除(0.25 的 system/init 里出现过的;ask_user_question 与计划模式那两个是不加 --bare 时才有的,2026-10-09 真跑里核对出来) */
|
|
33
|
-
const NON_CORE = ['agent', 'skill', 'list_agents', 'task_stop', 'send_message', 'record_artifact', 'get_goal', 'update_goal', 'tool_call', 'tool_search', 'report_findings', 'enter_worktree', 'exit_worktree', 'ask_user_question', 'enter_plan_mode', 'exit_plan_mode'];
|
|
34
|
-
/** workspace 里归 qwen 的目录(机器文件,prepare 每次对一遍) */
|
|
35
|
-
export const QWEN_DIR = '.cotutor/qwen';
|
|
36
|
-
const homeOf = (root) => join(root, QWEN_DIR, 'home');
|
|
37
|
-
const effortFile = (root, level) => join(root, QWEN_DIR, `effort-${level}.json`);
|
|
38
|
-
/** 政策 effort → 模型的思考档:low 不想(孩子在等);qwen3.7 系列只有开关,none 以外都是开 */
|
|
39
|
-
const EFFORT = { low: 'none', medium: 'low', high: 'xhigh' };
|
|
40
|
-
/**
|
|
41
|
-
* 带工具的轮次(作业照片、记账)至少想一点:2026-10-09 真跑,同一道照片题 none 那次 read_file 都没调、请孩子说是哪页;
|
|
42
|
-
* low 那次读了图、认出题目(11 秒)。孩子随口的话不带工具,不想照旧(3–6 秒)
|
|
43
|
-
*/
|
|
44
|
-
const levelOf = (effort, tools) => (tools !== 'off' && EFFORT[effort] === 'none' ? 'low' : EFFORT[effort]);
|
|
45
|
-
/** settings.json 里写的占位;qwen 读配置时拿子进程环境展开 */
|
|
46
|
-
const BASE_ENV = 'COTUTOR_QWEN_BASE_URL';
|
|
47
|
-
const KEY_ENV = 'COTUTOR_QWEN_API_KEY';
|
|
48
|
-
const toggle = { thinking: true, toggleOnly: true, disableField: 'enable_thinking' };
|
|
49
|
-
const tiers = { thinking: true, efforts: ['low', 'medium', 'xhigh'], defaultEffort: 'xhigh', disableField: 'reasoning_effort' };
|
|
50
|
-
/** 出厂模型(2026-10-09 五个模型 7 道真题对比:不想时 2–8 秒,讲得准也细;3.7-plus 快但浅,3.8-flash 不想时把盘算写进讲稿——
|
|
51
|
-
* 2026-10-13 英语老师三问没再见到,出厂 qwen-flash 运行时给英语老师) */
|
|
52
|
-
export const QWEN_DEFAULT_MODEL = 'qwen3.8-max';
|
|
53
|
-
/** 千问上能用的几个(Token Plan 与百炼按量都有);运行时模板的 -m 选其中一个 */
|
|
54
|
-
const MODELS = [
|
|
55
|
-
{ id: 'qwen3.7-plus', capabilities: { reasoning: toggle }, generationConfig: { contextWindowSize: 1_000_000, modalities: { image: true } } },
|
|
56
|
-
{ id: 'qwen3.7-max', capabilities: { reasoning: toggle }, generationConfig: { contextWindowSize: 1_000_000 } },
|
|
57
|
-
{ id: 'qwen3.8-flash', capabilities: { reasoning: tiers }, generationConfig: { contextWindowSize: 1_000_000, modalities: { image: true, video: true } } },
|
|
58
|
-
{ id: 'qwen3.8-max', capabilities: { reasoning: tiers }, generationConfig: { contextWindowSize: 1_000_000, modalities: { image: true, video: true } } },
|
|
59
|
-
];
|
|
60
|
-
/** 隔离的家里那份 settings.json(没有 key,只写环境变量名) */
|
|
61
|
-
export function qwenHomeSettings() {
|
|
62
|
-
return {
|
|
63
|
-
$version: 4,
|
|
64
|
-
security: { auth: { selectedType: 'openai' } },
|
|
65
|
-
model: { name: QWEN_DEFAULT_MODEL },
|
|
66
|
-
modelProviders: { openai: MODELS.map((m) => ({ ...m, baseUrl: `\${${BASE_ENV}}`, envKey: KEY_ENV })) },
|
|
67
|
-
memory: { enableManagedAutoMemory: false, enableManagedAutoDream: false },
|
|
68
|
-
privacy: { usageStatisticsEnabled: false },
|
|
69
|
-
telemetry: { enabled: false },
|
|
70
|
-
general: { disableAutoUpdate: true, disableUpdateNag: true, preventSystemSleep: false },
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/** 这几份机器文件:绝对路径 → 内容 */
|
|
74
|
-
export function qwenFiles(root) {
|
|
75
|
-
const files = {
|
|
76
|
-
// 整个目录不进 git:会话记录(runtime/)是对话原文,同 conversations/ 只留本机;其余都是这里每次重新生成的
|
|
77
|
-
[join(root, QWEN_DIR, '.gitignore')]: '# qwen 的隔离家目录与会话记录(cotutor 生成,对话原文只留本机)\n*\n',
|
|
78
|
-
[join(homeOf(root), 'settings.json')]: `${JSON.stringify(qwenHomeSettings(), null, 2)}\n`,
|
|
79
|
-
};
|
|
80
|
-
for (const level of new Set(Object.values(EFFORT)))
|
|
81
|
-
files[effortFile(root, level)] = `${JSON.stringify({ $version: 4, model: { reasoningEffort: level } }, null, 2)}\n`;
|
|
82
|
-
return files;
|
|
83
|
-
}
|
|
84
|
-
/** 别处的设置会盖掉家里选的模型与供应商,起进程时去掉 */
|
|
85
|
-
const FOREIGN = ['OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL', 'QWEN_MODEL'];
|
|
86
|
-
export const qwen = {
|
|
87
|
-
name: 'qwen',
|
|
88
|
-
parse: parseStreamJson,
|
|
89
|
-
stdinMessage: streamJsonUserMessage,
|
|
90
|
-
// qwen 的带工具只有只读一档(read / on 一样):shell 与写本来就不给
|
|
91
|
-
toolList: (set) => (set !== 'off' ? READ_TOOLS.join(',') : ''),
|
|
92
|
-
// --core-tools 是白名单,不带时也要给一个(给空等于不限),再把它排除掉;非核心工具不归 --core-tools 管,总要排除。
|
|
93
|
-
// 只读那轮把 workspace 根与 vault 加进可读范围:会话 cwd 是老师目录,作业照片(captures/)与教材在它外面,读外面要征得同意——
|
|
94
|
-
// 消息走 stdin 时它就停在那儿等,一直等到 --max-wall-time(2026-10-09 真跑,开了思考的那次读照片挂住)
|
|
95
|
-
toolArgs: (set, { readDirs }) => set !== 'off'
|
|
96
|
-
? ['--core-tools', READ_TOOLS.join(','), '--exclude-tools', NON_CORE.join(','), ...readDirs.flatMap((d) => ['--include-directories', d])]
|
|
97
|
-
: ['--core-tools', 'read_file', '--exclude-tools', ['read_file', ...NON_CORE].join(',')],
|
|
98
|
-
toolKind: (name) => KINDS[name] ?? 'other',
|
|
99
|
-
expectedTools: (set) => (set !== 'off' ? READ_TOOLS : []),
|
|
100
|
-
planEnv: ({ root, effort, tools }) => root ? { QWEN_HOME: homeOf(root), QWEN_RUNTIME_DIR: join(root, QWEN_DIR, 'runtime'), QWEN_CODE_SYSTEM_SETTINGS_PATH: effortFile(root, levelOf(effort, tools)) } : {},
|
|
101
|
-
async prepare(root) {
|
|
102
|
-
for (const [file, text] of Object.entries(qwenFiles(root))) {
|
|
103
|
-
if ((await readFile(file, 'utf8').catch(() => null)) === text)
|
|
104
|
-
continue;
|
|
105
|
-
await mkdir(dirname(file), { recursive: true });
|
|
106
|
-
await writeFile(file, text);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
env: (env) => {
|
|
110
|
-
const out = { ...env, QWEN_USAGE_STATISTICS_ENABLED: 'false', QWEN_CODE_MODELS_DEV: 'off', QWEN_CODE_SUPPRESS_YOLO_WARNING: '1' };
|
|
111
|
-
for (const k of FOREIGN)
|
|
112
|
-
delete out[k];
|
|
113
|
-
const line = qianwenLine(QWEN_DEFAULT_MODEL, qianwenKeys(out));
|
|
114
|
-
if (line) {
|
|
115
|
-
out[BASE_ENV] = compatibleBase(line.host);
|
|
116
|
-
out[KEY_ENV] = line.key;
|
|
117
|
-
}
|
|
118
|
-
return out;
|
|
119
|
-
},
|
|
120
|
-
check: ({ version, env }) => {
|
|
121
|
-
const v = /(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
122
|
-
const old = !v || Number(v[1]) === 0 && Number(v[2]) < 25;
|
|
123
|
-
const line = qianwenLine(QWEN_DEFAULT_MODEL, qianwenKeys(env));
|
|
124
|
-
return [
|
|
125
|
-
{ name: 'version', ok: !old, detail: old ? `qwen ${version || '?'}:要 0.25 起(关工具的旗标、隔离的家、思考量配置都按 0.25 定的)` : `qwen ${version}`, fix: old ? 'qwen update(或 brew upgrade qwen-code)' : undefined },
|
|
126
|
-
line
|
|
127
|
-
? { name: 'key', ok: true, detail: line.plan ? '走千问 Token Plan(QWENCLOUD_TOKEN_PLAN_API_KEY)' : '走百炼按量(DASHSCOPE_API_KEY,或本机 qwen / voxtell 的配置)' }
|
|
128
|
-
: { name: 'key', ok: false, detail: '千问的 key 一把都拿不到:老师进程起来就会 401', fix: '在起 serve 的 shell 里 export QWENCLOUD_TOKEN_PLAN_API_KEY(Token Plan,sk-sp-)或 DASHSCOPE_API_KEY(百炼按量)' },
|
|
129
|
-
];
|
|
130
|
-
},
|
|
131
|
-
runtimes: () => {
|
|
132
|
-
// 消息走 stdin(预热能用;数组旗标 --core-tools 之类会吞掉跟在后面的消息,不进 argv 正好避开);-m 选模型(家里那份 settings.json 列着的);
|
|
133
|
-
// {systemBody} = 老师正文 + 板书写法,整份换掉 qwen 自己的系统提示;{toolArgs} 由适配器展开;--approval-mode default:shell、写、读 workspace 外都拒
|
|
134
|
-
// qwen-flash 只差模型(2026-10-13 英语老师出厂走它:Token Plan 上便宜、开口 2.5–4 秒)
|
|
135
|
-
const of = (model) => {
|
|
136
|
-
const flags = ['-m', model, '--system-prompt', '{systemBody}', '--input-format', 'stream-json', '--output-format', 'stream-json', '--include-partial-messages', '--approval-mode', 'default', '{toolArgs}', '--max-wall-time', '10m'];
|
|
137
|
-
return { stdin: 'stream-json', run: ['qwen', ...flags], resume: ['qwen', '--resume', '{session}', ...flags] };
|
|
138
|
-
};
|
|
139
|
-
return { qwen: of(QWEN_DEFAULT_MODEL), 'qwen-flash': of('qwen3.8-flash') };
|
|
140
|
-
},
|
|
141
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { streamJsonUserMessage } from './stream-json.ts';
|
|
2
|
-
import type { ToolSet } from './types.ts';
|
|
3
|
-
import { type ConversationIndex, type CotutorConfig, type Policy, type Runtime } from '../../schema/index.ts';
|
|
4
|
-
export interface RunPlan {
|
|
5
|
-
/** 运行时名(runtimes 里的键) */
|
|
6
|
-
runtime: string;
|
|
7
|
-
/** 适配器名(src/brain/cli/;认不出的是 stream-json) */
|
|
8
|
-
cli: string;
|
|
9
|
-
/** 这轮带不带工具(模板管不了工具的运行时也照记,只是没用上) */
|
|
10
|
-
tools: ToolSet;
|
|
11
|
-
/** 完整命令行,argv[0] 是可执行文件 */
|
|
12
|
-
argv: string[];
|
|
13
|
-
/** 这次是 resume 还是新开 */
|
|
14
|
-
resume: boolean;
|
|
15
|
-
/** resume 的会话 id */
|
|
16
|
-
session: string | null;
|
|
17
|
-
/** 运行时 stdin: "stream-json":要写进 stdin 的那一行(用户消息,带换行);消息不在 argv 里 */
|
|
18
|
-
stdin?: string;
|
|
19
|
-
/** 适配器按这轮的计划定的环境变量(qwen 的思考量配置之类);和 argv 一起进预热的比对 */
|
|
20
|
-
env: Record<string, string>;
|
|
21
|
-
}
|
|
22
|
-
export declare class RuntimeError extends Error {
|
|
23
|
-
}
|
|
24
|
-
export declare function getRuntime(config: CotutorConfig, name?: string): {
|
|
25
|
-
name: string;
|
|
26
|
-
runtime: Runtime;
|
|
27
|
-
};
|
|
28
|
-
/** 模板里有没有用到某个占位符(决定要不要去读老师正文)。API 运行时没有模板:系统提示由应用给,算用了 {systemBody} */
|
|
29
|
-
export declare function runtimeUses(runtime: Runtime, placeholder: string): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* 这个运行时把板书写法预载进系统提示了吗:模板用了 {boardFile} / {systemBody},或者(2026-09-19 头一版)写死了 cotutor-board/SKILL.md 的路径。
|
|
32
|
-
* 没有 → 应用在话题第一条注入 <cotutor-board>。
|
|
33
|
-
*/
|
|
34
|
-
export declare function boardPreloaded(runtime: Runtime): boolean;
|
|
35
|
-
export declare function planRun(config: CotutorConfig, index: Pick<ConversationIndex, 'session'>, vars: {
|
|
36
|
-
agent: string;
|
|
37
|
-
prompt: string;
|
|
38
|
-
agentBody?: string;
|
|
39
|
-
systemBody?: string;
|
|
40
|
-
boardFile?: string;
|
|
41
|
-
runtime?: string;
|
|
42
|
-
effort?: Policy['effort'];
|
|
43
|
-
tools?: ToolSet;
|
|
44
|
-
root?: string;
|
|
45
|
-
readDirs?: readonly string[];
|
|
46
|
-
}): RunPlan;
|
|
47
|
-
/** stream-json 输入的一条用户消息(Qwen 的 `--input-format stream-json` 一行一条;别的 CLI 看它的适配器) */
|
|
48
|
-
export declare const stdinMessage: typeof streamJsonUserMessage;
|
|
49
|
-
/**
|
|
50
|
-
* 断流后接着跑的那条消息(policy.stall):resume 同一个会话发给老师。被杀的那段回复只流到了 stdout,没进会话,
|
|
51
|
-
* 所以断在半截的正文(open)原样递回去,让它从断处接着写;应用把 open 拼在这次的收尾正文前面。
|
|
52
|
-
* open 为空 = 这一段一个字没出来,已经做完的工具调用与整段都在会话里,接着把这一轮做完。
|
|
53
|
-
*/
|
|
54
|
-
export declare function stallPrompt(open: string): string;
|
|
55
|
-
/**
|
|
56
|
-
* 老师把工具调用写成了正文、停在那里等结果(这轮没给它那个工具,qwen 照自己的格式写出来;板书解析器把那块剥掉了):
|
|
57
|
-
* resume 同一个会话,告诉它不会有结果,接着直接回孩子。那块前面写的话已经进了会话,应用拼在这次的前面
|
|
58
|
-
*/
|
|
59
|
-
export declare function toolLeakPrompt(): string;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 一次运行的命令规划(纯函数):按运行时选 run 还是 resume、填占位符、定 cwd 相对路径。
|
|
3
|
-
* 规则:当天索引里已有会话**且**是同一运行时跑出来的 → resume;否则新开(跨天索引本来就是新的,零点后第一条自然不带 --resume;
|
|
4
|
-
* 换了运行时也新开——不同运行时的会话不混用)。{agentBody} 只给模板里真用到它的运行时读老师正文。
|
|
5
|
-
*/
|
|
6
|
-
import { adapterFor, fillArgs } from "./adapters.js";
|
|
7
|
-
import { streamJsonUserMessage } from "./stream-json.js";
|
|
8
|
-
import { POLICY_DEFAULTS, isApiRuntime } from "../../schema/index.js";
|
|
9
|
-
export class RuntimeError extends Error {
|
|
10
|
-
}
|
|
11
|
-
export function getRuntime(config, name) {
|
|
12
|
-
const n = name ?? config.runtimes.default;
|
|
13
|
-
const p = config.runtimes[n];
|
|
14
|
-
if (n === 'default' || !p || typeof p === 'string') {
|
|
15
|
-
const known = Object.keys(config.runtimes).filter((k) => k !== 'default');
|
|
16
|
-
throw new RuntimeError(`运行时 "${n}" 不存在;cotutor.json 的 runtimes 里有:${known.join('、')}`);
|
|
17
|
-
}
|
|
18
|
-
return { name: n, runtime: p };
|
|
19
|
-
}
|
|
20
|
-
/** 模板里有没有用到某个占位符(决定要不要去读老师正文)。API 运行时没有模板:系统提示由应用给,算用了 {systemBody} */
|
|
21
|
-
export function runtimeUses(runtime, placeholder) {
|
|
22
|
-
if (isApiRuntime(runtime))
|
|
23
|
-
return placeholder === '{systemBody}';
|
|
24
|
-
return [...runtime.run, ...runtime.resume].some((a) => a.includes(placeholder));
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* 这个运行时把板书写法预载进系统提示了吗:模板用了 {boardFile} / {systemBody},或者(2026-09-19 头一版)写死了 cotutor-board/SKILL.md 的路径。
|
|
28
|
-
* 没有 → 应用在话题第一条注入 <cotutor-board>。
|
|
29
|
-
*/
|
|
30
|
-
export function boardPreloaded(runtime) {
|
|
31
|
-
return runtimeUses(runtime, '{boardFile}') || runtimeUses(runtime, '{systemBody}') || runtimeUses(runtime, 'cotutor-board/SKILL.md');
|
|
32
|
-
}
|
|
33
|
-
export function planRun(config, index, vars) {
|
|
34
|
-
const { name, runtime } = getRuntime(config, vars.runtime);
|
|
35
|
-
if (isApiRuntime(runtime))
|
|
36
|
-
throw new RuntimeError(`运行时 "${name}" 是 API 运行时,不起命令行(src/brain/api.ts)`);
|
|
37
|
-
const adapter = adapterFor(runtime);
|
|
38
|
-
const session = index.session && index.session.runtime === name ? index.session.id : null;
|
|
39
|
-
const template = session ? runtime.resume : runtime.run;
|
|
40
|
-
const effort = vars.effort ?? POLICY_DEFAULTS.effort;
|
|
41
|
-
return {
|
|
42
|
-
runtime: name,
|
|
43
|
-
cli: adapter.name,
|
|
44
|
-
tools: vars.tools ?? 'on',
|
|
45
|
-
argv: fillArgs(adapter, template, { agent: vars.agent, prompt: vars.prompt, session: session ?? undefined, agentBody: vars.agentBody, systemBody: vars.systemBody, boardFile: vars.boardFile, effort: vars.effort, tools: vars.tools, readDirs: vars.readDirs }),
|
|
46
|
-
resume: session !== null,
|
|
47
|
-
session,
|
|
48
|
-
...(runtime.stdin ? { stdin: adapter.stdinMessage(vars.prompt) } : {}),
|
|
49
|
-
env: adapter.planEnv?.({ root: vars.root, effort, tools: vars.tools ?? 'on' }) ?? {},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/** stream-json 输入的一条用户消息(Qwen 的 `--input-format stream-json` 一行一条;别的 CLI 看它的适配器) */
|
|
53
|
-
export const stdinMessage = streamJsonUserMessage;
|
|
54
|
-
/**
|
|
55
|
-
* 断流后接着跑的那条消息(policy.stall):resume 同一个会话发给老师。被杀的那段回复只流到了 stdout,没进会话,
|
|
56
|
-
* 所以断在半截的正文(open)原样递回去,让它从断处接着写;应用把 open 拼在这次的收尾正文前面。
|
|
57
|
-
* open 为空 = 这一段一个字没出来,已经做完的工具调用与整段都在会话里,接着把这一轮做完。
|
|
58
|
-
*/
|
|
59
|
-
export function stallPrompt(open) {
|
|
60
|
-
const rule = '不道歉,不提断了,不重复前面的。';
|
|
61
|
-
return open
|
|
62
|
-
? `(应用注)你刚才那段回复传到一半网络断了。孩子那边已经看到、听到的就是下面 <<< 与 >>> 之间这些:\n<<<\n${open}\n>>>\n从断的地方直接接着写,字要接得上;${rule}`
|
|
63
|
-
: `(应用注)你刚才那段回复网络断了,一个字没传出来。已经做完的工具调用和写完的段都还在,接着把这一轮做完;${rule}`;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* 老师把工具调用写成了正文、停在那里等结果(这轮没给它那个工具,qwen 照自己的格式写出来;板书解析器把那块剥掉了):
|
|
67
|
-
* resume 同一个会话,告诉它不会有结果,接着直接回孩子。那块前面写的话已经进了会话,应用拼在这次的前面
|
|
68
|
-
*/
|
|
69
|
-
export function toolLeakPrompt() {
|
|
70
|
-
return '(应用注)你刚才写的工具调用没有执行,孩子也看不到。这一轮不用工具,接着直接回孩子;不提工具,不道歉,不重复前面的。';
|
|
71
|
-
}
|
package/dist/brain/cli/run.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { type RunPlan } from './run-plan.ts';
|
|
2
|
-
import type { CliRuntime, CotutorConfig, Policy } from '../../schema/index.ts';
|
|
3
|
-
import type { BrainExecutor, BrainHooks, BrainRequest, BrainRun, WarmKey } from '../index.ts';
|
|
4
|
-
import { type Slot } from './warm.ts';
|
|
5
|
-
export declare class CliBrain implements BrainExecutor {
|
|
6
|
-
private readonly spares;
|
|
7
|
-
private readonly workspace;
|
|
8
|
-
/** 起子进程用的环境(测试注入);缺省 process.env */
|
|
9
|
-
private readonly env;
|
|
10
|
-
constructor(opts: {
|
|
11
|
-
workspace: () => {
|
|
12
|
-
config: CotutorConfig;
|
|
13
|
-
root: string;
|
|
14
|
-
};
|
|
15
|
-
env?: NodeJS.ProcessEnv;
|
|
16
|
-
warmIdleMs?: number;
|
|
17
|
-
});
|
|
18
|
-
plan(req: BrainRequest): RunPlan;
|
|
19
|
-
prepare(plan: RunPlan): Promise<void>;
|
|
20
|
-
canWarm(runtime: CliRuntime): boolean;
|
|
21
|
-
warm(tutor: string, slot: Slot, plan: RunPlan, key: WarmKey): void;
|
|
22
|
-
warmed(tutor: string, slot?: Slot): boolean;
|
|
23
|
-
dropWarm(tutor: string, slot?: Slot): void;
|
|
24
|
-
close(): void;
|
|
25
|
-
private processEnv;
|
|
26
|
-
start(req: BrainRequest, plan: RunPlan, ctx: {
|
|
27
|
-
tutor: string;
|
|
28
|
-
key: WarmKey;
|
|
29
|
-
files: {
|
|
30
|
-
log: string;
|
|
31
|
-
err: string;
|
|
32
|
-
};
|
|
33
|
-
stall: Policy['stall'];
|
|
34
|
-
}, hooks: BrainHooks): BrainRun;
|
|
35
|
-
}
|