cotutor 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/LICENSE +9 -7
  2. package/NOTICE +3 -3
  3. package/README.md +36 -13
  4. package/avatars/chinese.webp +0 -0
  5. package/avatars/english.webp +0 -0
  6. package/avatars/math.webp +0 -0
  7. package/bin/cotutor.js +13 -3
  8. package/cards/photo/card.css +10 -3
  9. package/cards/photo/card.md +2 -2
  10. package/cards/tutor/card.css +13 -0
  11. package/cards/tutor/card.md +2 -1
  12. package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +1 -1
  13. package/dist/app/activity-eval.js +3 -2
  14. package/dist/app/cli/activities.d.ts +8 -1
  15. package/dist/app/cli/activities.js +42 -4
  16. package/dist/app/cli/cert.d.ts +5 -0
  17. package/dist/app/cli/cert.js +20 -7
  18. package/dist/app/cli/chalk/browser.d.ts +2 -2
  19. package/dist/app/cli/chalk/browser.js +22 -7
  20. package/dist/app/cli/chalk/main.js +3 -2
  21. package/dist/app/cli/doctor.d.ts +6 -5
  22. package/dist/app/cli/doctor.js +107 -171
  23. package/dist/app/cli/firewall.d.ts +24 -0
  24. package/dist/app/cli/firewall.js +79 -0
  25. package/dist/app/cli/init.js +8 -5
  26. package/dist/app/cli/main.js +347 -24
  27. package/dist/app/cli/migrate.d.ts +2 -11
  28. package/dist/app/cli/migrate.js +1 -59
  29. package/dist/app/cli/port-holder.d.ts +16 -0
  30. package/dist/app/cli/port-holder.js +34 -0
  31. package/dist/app/cli/port.d.ts +37 -0
  32. package/dist/app/cli/port.js +116 -0
  33. package/dist/app/cli/sandbox.d.ts +25 -0
  34. package/dist/app/cli/sandbox.js +166 -0
  35. package/dist/app/cli/serve-log.d.ts +13 -0
  36. package/dist/app/cli/serve-log.js +90 -0
  37. package/dist/app/cli/serve.d.ts +18 -4
  38. package/dist/app/cli/serve.js +78 -8
  39. package/dist/app/cli/service-windows.d.ts +50 -0
  40. package/dist/app/cli/service-windows.js +117 -0
  41. package/dist/app/cli/service.d.ts +138 -0
  42. package/dist/app/cli/service.js +429 -0
  43. package/dist/app/cli/skeleton.d.ts +1 -1
  44. package/dist/app/cli/skeleton.js +13 -13
  45. package/dist/app/cli/skills.d.ts +5 -1
  46. package/dist/app/cli/skills.js +14 -4
  47. package/dist/app/cli/tray-icons.d.ts +11 -0
  48. package/dist/app/cli/tray-icons.js +87 -0
  49. package/dist/app/cli/tray-ids.d.ts +6 -0
  50. package/dist/app/cli/tray-ids.js +6 -0
  51. package/dist/app/cli/tray-mac.d.ts +8 -0
  52. package/dist/app/cli/tray-mac.js +140 -0
  53. package/dist/app/cli/tray-run.d.ts +1 -0
  54. package/dist/app/cli/tray-run.js +33 -0
  55. package/dist/app/cli/tray-windows.d.ts +5 -0
  56. package/dist/app/cli/tray-windows.js +166 -0
  57. package/dist/app/cli/tray.d.ts +205 -0
  58. package/dist/app/cli/tray.js +618 -0
  59. package/dist/app/cli/usage.d.ts +1 -1
  60. package/dist/app/cli/usage.js +11 -6
  61. package/dist/app/dictation/route.js +3 -2
  62. package/dist/app/http/app.d.ts +5 -10
  63. package/dist/app/http/app.js +45 -98
  64. package/dist/app/http/avatars.d.ts +17 -0
  65. package/dist/app/http/avatars.js +35 -0
  66. package/dist/app/http/dev-tutor-files.js +1 -1
  67. package/dist/app/http/parent-home.d.ts +14 -0
  68. package/dist/app/http/parent-home.js +69 -6
  69. package/dist/app/http/parent-look.d.ts +2 -0
  70. package/dist/app/http/parent-look.js +151 -0
  71. package/dist/app/http/raw-view.js +3 -3
  72. package/dist/app/http/voice-preview.d.ts +4 -0
  73. package/dist/app/http/voice-preview.js +34 -0
  74. package/dist/app/machine/bailian.d.ts +0 -2
  75. package/dist/app/machine/bailian.js +5 -6
  76. package/dist/app/mock/mock-live.js +1 -1
  77. package/dist/app/mock/mock.d.ts +4 -2
  78. package/dist/app/mock/mock.js +20 -12
  79. package/dist/app/replay.js +4 -4
  80. package/dist/app/skills/home-doc.js +1 -1
  81. package/dist/app/workspace.d.ts +9 -4
  82. package/dist/app/workspace.js +33 -10
  83. package/dist/app/ws/route.d.ts +8 -2
  84. package/dist/app/ws/route.js +7 -3
  85. package/dist/board/parse.d.ts +0 -2
  86. package/dist/board/parse.js +0 -6
  87. package/dist/board/presentation.d.ts +9 -15
  88. package/dist/board/presentation.js +8 -10
  89. package/dist/brain/api-tools.js +2 -1
  90. package/dist/brain/api.d.ts +12 -17
  91. package/dist/brain/api.js +16 -33
  92. package/dist/brain/index.d.ts +23 -71
  93. package/dist/brain/index.js +6 -69
  94. package/dist/brain/log.d.ts +5 -0
  95. package/dist/brain/log.js +22 -0
  96. package/dist/brain/runtime.d.ts +30 -0
  97. package/dist/brain/runtime.js +23 -0
  98. package/dist/brain/{cli/stream-json.d.ts → stream-json.d.ts} +2 -4
  99. package/dist/brain/{cli/stream-json.js → stream-json.js} +0 -4
  100. package/dist/brain/stream.d.ts +5 -5
  101. package/dist/brain/stream.js +7 -7
  102. package/dist/brain/transcript.d.ts +3 -3
  103. package/dist/brain/transcript.js +6 -6
  104. package/dist/brain/{cli/types.d.ts → types.d.ts} +3 -48
  105. package/dist/brain/types.js +14 -0
  106. package/dist/chalk/glyphs/skeleton.d.ts +3 -3
  107. package/dist/chalk/glyphs/skeleton.js +1 -1
  108. package/dist/lib/index.d.ts +1 -1
  109. package/dist/lib/index.js +1 -1
  110. package/dist/memory/diary.js +3 -3
  111. package/dist/realtime/bailian.d.ts +1 -1
  112. package/dist/realtime/bailian.js +3 -2
  113. package/dist/realtime/preview.js +1 -1
  114. package/dist/realtime/voices.d.ts +2 -0
  115. package/dist/realtime/voices.js +57 -57
  116. package/dist/runtime/activities.d.ts +13 -1
  117. package/dist/runtime/activities.js +28 -2
  118. package/dist/runtime/activity-state.js +2 -2
  119. package/dist/runtime/agent-file.d.ts +3 -3
  120. package/dist/runtime/agent-file.js +3 -3
  121. package/dist/runtime/context/tutor-rules.d.ts +2 -2
  122. package/dist/runtime/context/tutor-rules.js +3 -3
  123. package/dist/runtime/home/home.d.ts +12 -1
  124. package/dist/runtime/home/home.js +8 -1
  125. package/dist/runtime/home/store.d.ts +1 -1
  126. package/dist/runtime/home/store.js +36 -21
  127. package/dist/runtime/journal/mix.d.ts +15 -6
  128. package/dist/runtime/journal/mix.js +60 -19
  129. package/dist/runtime/journal/recorder.d.ts +58 -13
  130. package/dist/runtime/journal/recorder.js +106 -16
  131. package/dist/runtime/journal/store.d.ts +4 -4
  132. package/dist/runtime/journal/store.js +6 -6
  133. package/dist/runtime/kid-input.js +2 -3
  134. package/dist/runtime/live/call.d.ts +12 -0
  135. package/dist/runtime/live/call.js +51 -18
  136. package/dist/runtime/live/turns.js +1 -1
  137. package/dist/runtime/runner.d.ts +9 -55
  138. package/dist/runtime/runner.js +52 -173
  139. package/dist/scenes/paint/painter.js +4 -3
  140. package/dist/schema/config.d.ts +43 -22
  141. package/dist/schema/config.js +45 -47
  142. package/dist/schema/conversation.d.ts +6 -0
  143. package/dist/schema/home.d.ts +11 -1
  144. package/dist/schema/home.js +7 -2
  145. package/dist/schema/workspace.d.ts +27 -0
  146. package/dist/schema/workspace.js +28 -0
  147. package/dist/shared/default-port.d.ts +2 -0
  148. package/dist/shared/default-port.js +2 -0
  149. package/dist/shared/qianwen.d.ts +14 -10
  150. package/dist/shared/qianwen.js +26 -22
  151. package/dist/shared/site-url.d.ts +2 -0
  152. package/dist/shared/site-url.js +5 -0
  153. package/dist/ui/dev/dev-page.js +25 -252
  154. package/dist/ui/dev/dev-theme.d.ts +1 -1
  155. package/dist/ui/dev/dev-theme.js +1 -1
  156. package/dist/ui/dev/voice-gallery-page.js +1 -1
  157. package/dist/ui/kid/board-mark.d.ts +2 -2
  158. package/dist/ui/kid/board-mark.js +2 -2
  159. package/dist/ui/kid/home-tiles.d.ts +14 -0
  160. package/dist/ui/kid/home-tiles.js +44 -0
  161. package/dist/ui/kid/kid-page.js +68 -103
  162. package/dist/ui/kid/voice-ui.d.ts +1 -1
  163. package/dist/ui/kid/voice-ui.js +1 -1
  164. package/dist/ui/kid/word-emoji.js +5 -3
  165. package/dist/ui/parent/parent-home-page.js +315 -7
  166. package/dist/ui/parent/parent-page.js +6 -2
  167. package/dist/ui/parent/parent-settings-page.d.ts +1 -0
  168. package/dist/ui/parent/parent-settings-page.js +133 -0
  169. package/dist/ui/{dev → parent}/qr-page.d.ts +2 -2
  170. package/dist/ui/{dev → parent}/qr-page.js +19 -14
  171. package/dist/ui/shared/brand.d.ts +3 -1
  172. package/dist/ui/shared/brand.js +3 -1
  173. package/grades/grade-2/README.md +1 -1
  174. package/grades/grade-2/VALIDATION.md +1 -1
  175. package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +1 -1
  176. 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
  177. 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
  178. package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +1 -1
  179. package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +1 -1
  180. 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
  181. 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
  182. package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +1 -1
  183. package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +1 -1
  184. 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
  185. 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
  186. package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +1 -1
  187. package/package.json +2 -1
  188. package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +2 -2
  189. package/skills/cotutor-board/references/photo.md +2 -2
  190. package/skills/cotutor-home/SKILL.md +1 -0
  191. package/skills/cotutor-home/references/tutor.md +2 -1
  192. package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +2 -1
  193. package/skills/cotutor-tune/references//345/255/227/346/256/265.md +6 -3
  194. package/skills/cotutor-tutor/SKILL.md +5 -13
  195. package/dist/brain/cli/adapters.d.ts +0 -60
  196. package/dist/brain/cli/adapters.js +0 -91
  197. package/dist/brain/cli/qwen.d.ts +0 -11
  198. package/dist/brain/cli/qwen.js +0 -141
  199. package/dist/brain/cli/run-plan.d.ts +0 -59
  200. package/dist/brain/cli/run-plan.js +0 -71
  201. package/dist/brain/cli/run.d.ts +0 -35
  202. package/dist/brain/cli/run.js +0 -256
  203. package/dist/brain/cli/types.js +0 -3
  204. package/dist/brain/cli/warm.d.ts +0 -53
  205. package/dist/brain/cli/warm.js +0 -103
@@ -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
- /** 文件改了(页面写的、或家长在编辑器改的)就重读;坏了抛 ConfigError,调用方决定是留旧的还是响 */
55
- export declare function reloadIfChanged(ws: Workspace, lastMtime: number): Promise<{
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
- mtime: number;
62
+ text: string;
58
63
  }>;
@@ -7,10 +7,10 @@
7
7
  * 配置"在但解析不了"响亮报错,不静默走缺省(沿 drawtell 的教训)。
8
8
  */
9
9
  import { readFileSync, readdirSync, statSync } from 'node:fs';
10
- import { writeFile, rename, stat } from 'node:fs/promises';
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
- /** 文件改了(页面写的、或家长在编辑器改的)就重读;坏了抛 ConfigError,调用方决定是留旧的还是响 */
237
- export async function reloadIfChanged(ws, lastMtime) {
238
- const st = await stat(ws.files.config);
239
- if (st.mtimeMs === lastMtime)
240
- return { ws, mtime: lastMtime };
241
- const raw = readJson(ws.files.config);
242
- if (raw === null)
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
- return { ws: assembleWorkspace(ws.root, ws.source, parseConfig(raw, ws.files.config)), mtime: st.mtimeMs };
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
  }
@@ -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
- export declare function makeLiveUpgrade(deps?: LiveRouteDeps): (req: IncomingMessage, socket: Duplex, head: Buffer, ctx: LiveApp) => Promise<boolean>;
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: (req: IncomingMessage, socket: Duplex, head: Buffer, ctx: LiveApp) => Promise<boolean>;
31
+ export declare const liveUpgrade: LiveUpgrade;
@@ -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
- return async function liveUpgrade(req, socket, head, ctx) {
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, onFinished: (why) => deps.onFinished?.(tutor, why) });
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, env: { ...process.env, DASHSCOPE_API_KEY: process.env.DASHSCOPE_API_KEY ?? 'sk-probe' } } : {});
33
+ export const liveUpgrade = makeLiveUpgrade(process.env.COTUTOR_LIVE_ENDPOINT ? { endpoint: process.env.COTUTOR_LIVE_ENDPOINT } : {});
@@ -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
  /** 讲稿一句(念出来) */
@@ -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 根;R5):节头上回显缩略图 */
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 = 它答的是哪一节的哪张问题卡(写进那张卡的答案栏),没有 = 在这一节开头单独一行。实时的节头不是它(看 head),它只给左栏用 */
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 板书(流式,已有卡或已有配好的句;实时的有句就算)才出节且标 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 = 把某节的标注画齐;showNow = 选中态照现在的位置;scrollLast = 滚到最后一节 */
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 板书(流式,已有卡或已有配好的句;实时的有句就算)才出节且标 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.photos?.length && !k?.said.photos ? { photos: m.photos } : {}), ...(m.lecture ? { lecture: m.lecture } : {}), ...(m.drawing ? { drawing: m.drawing } : {}), ...(k ? { said: k.said, ...(k.on ? { saidOn: k.on } : {}) } : {}), ...(m.live ? { live: true, ...liveHead(m.section ?? { cards: [] }) } : {}) };
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 || (m.live && m.section.lines.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' }, { kind: 'showNow' });
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' }, { kind: 'showNow' });
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: 'showNow' }, { kind: 'scrollLast' });
588
+ fx.push({ kind: 'render' }, { kind: 'scrollLast' });
591
589
  break;
592
590
  }
593
591
  return { model: m, effects: fx };
@@ -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
  };
@@ -1,57 +1,52 @@
1
1
  import { type ModelMessage } from 'ai';
2
- import type { CliEvent } from './cli/types.ts';
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, WarmKey } from './index.ts';
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
- * 有思考档的模型照 qwen 运行时发 reasoning_effort(src/brain/cli/qwen.ts 的 EFFORT):想一点 = low、不限 = xhigh;不想或没有档 → null,照 thinkingOf 发。
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 auth;
38
- /** 测试与探针:…/compatible-mode/v1(假百炼);不给按业务空间 id 拼 */
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
- auth: () => {
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;
package/dist/brain/api.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * 脑执行的 API 实现(2026-10-29):不起 CLI,应用在服务进程里直连百炼的 Chat Completions(AI SDK + @ai-sdk/alibaba,cotutor-api-prototype 验过这条路)。
3
+ * - 发到哪按模型选(src/shared/qianwen.ts 的 compatibleLine):Token Plan 有这个模型、本机有 token-plan.json 就走 Token Plan(2026-11-02 起),否则百炼按量
3
4
  * - 系统提示 = runner 给的 systemBody(老师正文或 activity + 板书写法);消息 = 上下文包 + 孩子的话,本轮的照片直接附成图,不靠老师自己去读
4
5
  * - 同一话题的模型历史应用自己存:<日期>.<话题>.brain.jsonl,一行一轮(会话 id、模型、这轮的用户消息、模型回的全部消息、成没成);
5
6
  * 接着话题时把同一会话的那几行拼回去。照片只记相对 workspace 根的路径,发的时候再编码。没回完的一轮只留写出来的正文,下一条消息前头注明
@@ -20,11 +21,11 @@ import { isStepCount, streamText } from 'ai';
20
21
  import { randomUUID } from 'node:crypto';
21
22
  import { createWriteStream } from 'node:fs';
22
23
  import { appendFile, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
23
- import { dirname, extname, isAbsolute, join, relative } from 'node:path';
24
- import { getRuntime } from "./cli/run-plan.js";
24
+ import { dirname, extname, isAbsolute, join, relative, sep } from 'node:path';
25
+ import { getRuntime } from "./runtime.js";
26
+ import { parseEventLine } from "./log.js";
25
27
  import { createPartialReader } from "./stream.js";
26
28
  import { parseTranscript, toolCalls, toolSummary } from "./transcript.js";
27
- import { compatibleBase, DASHSCOPE_HOST } from "../shared/qianwen.js";
28
29
  import { isApiRuntime } from "../schema/index.js";
29
30
  import { readOnlyTools } from "./api-tools.js";
30
31
  /** 计划里记的 cli 名:API 的轮次按它分流 */
@@ -39,7 +40,7 @@ export const FACTORY_API_RUNTIMES = {
39
40
  const DEFAULT_STEPS = 6;
40
41
  /** 想一点:没有思考档的模型思考最多多少 token(有档的发 reasoning_effort,见 effortOf) */
41
42
  const THINK_LITTLE = 1024;
42
- /** 有思考档的模型:同 Qwen Code 里的 qwen3.8 系列(src/brain/cli/qwen.ts 的 tiers),百炼兼容接口认 reasoning_effort */
43
+ /** 有思考档的模型:qwen3.8 系列(2026-10-31 照当时 Qwen Code 的档对过),百炼兼容接口认 reasoning_effort */
43
44
  const TIERED = /^qwen3\.8-/;
44
45
  /** 一张照片最大多少字节(百炼按 base64 收,太大的不附,记一条提醒) */
45
46
  const IMAGE_MAX = 10 * 1024 * 1024;
@@ -48,18 +49,6 @@ const MIME = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
48
49
  const CODEC = 'ai-sdk@7';
49
50
  /** 上一轮没回完:接在下一条消息前头 */
50
51
  const UNFINISHED = '(应用注:上一轮你没有回完,上面是写出来的那部分;孩子可能听到了一些,也可能没有,别当它已经说完。)';
51
- /** .log 的一行 → 统一事件(API 轮次的日志就是一行一条统一事件);不是这种行回 [] */
52
- export function parseEventLine(line) {
53
- if (!line.startsWith('{"kind":'))
54
- return [];
55
- try {
56
- const e = JSON.parse(line);
57
- return typeof e.kind === 'string' ? [e] : [];
58
- }
59
- catch {
60
- return [];
61
- }
62
- }
63
52
  /** 政策 effort → 思考开关与思考量(同 qwen:low 不想,孩子在等;带工具的轮次至少想一点,不然照片题连资料都不查) */
64
53
  export function thinkingOf(effort, tools) {
65
54
  if (effort === 'high')
@@ -69,7 +58,7 @@ export function thinkingOf(effort, tools) {
69
58
  return { enableThinking: false };
70
59
  }
71
60
  /**
72
- * 有思考档的模型照 qwen 运行时发 reasoning_effort(src/brain/cli/qwen.ts 的 EFFORT):想一点 = low、不限 = xhigh;不想或没有档 → null,照 thinkingOf 发。
61
+ * 有思考档的模型发 reasoning_effort(照当时 Qwen Code 的档):想一点 = low、不限 = xhigh;不想或没有档 → null,照 thinkingOf 发。
73
62
  * low 是它自己看着想:闲聊想一两百 token,认照片题想得多;thinking_budget 是死卡,1024 时有照片的话题每轮都想满(2026-10-31)
74
63
  */
75
64
  export function effortOf(model, effort, tools) {
@@ -87,12 +76,6 @@ export function effortFetch(effort, base = fetch) {
87
76
  return base(input, { ...init, body: JSON.stringify({ ...body, reasoning_effort: effort }) });
88
77
  };
89
78
  }
90
- /**
91
- * 百炼 Chat Completions 的端点:知道业务空间 id 就走它的域名(原型验过,同 realtime);只有按量 key 就走公网域名(qwen 那边按量的线也走它,2026-10-29 真跑通)
92
- */
93
- export function bailianBase(workspaceId) {
94
- return workspaceId ? `https://${workspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` : compatibleBase(DASHSCOPE_HOST);
95
- }
96
79
  /** 报错只说状态码:SDK 的异常里带着整个请求 */
97
80
  function failure(err) {
98
81
  const status = err && typeof err === 'object' && 'statusCode' in err ? err.statusCode : undefined;
@@ -213,12 +196,12 @@ async function pastOf(file, session, root, current) {
213
196
  }
214
197
  export class ApiBrain {
215
198
  workspace;
216
- auth;
217
- /** 测试与探针:…/compatible-mode/v1(假百炼);不给按业务空间 id 拼 */
199
+ line;
200
+ /** 测试与探针:…/compatible-mode/v1(假百炼);不给按模型选线 */
218
201
  endpoint;
219
202
  constructor(opts) {
220
203
  this.workspace = opts.workspace;
221
- this.auth = opts.auth;
204
+ this.line = opts.line;
222
205
  this.endpoint = opts.endpoint;
223
206
  }
224
207
  plan(req) {
@@ -247,11 +230,11 @@ export class ApiBrain {
247
230
  const session = plan.session ?? `api-${randomUUID()}`;
248
231
  event({ kind: 'session', id: session });
249
232
  const images = req.images ?? [];
250
- const stored = images.map((p) => { const rel = relative(root, p); return rel.startsWith('..') || isAbsolute(rel) ? p : rel; });
233
+ const stored = images.map((p) => { const rel = relative(root, p); return rel.startsWith('..') || isAbsolute(rel) ? p : rel.split(sep).join('/'); });
251
234
  const record = { v: 1, session, at: new Date().toISOString(), model: runtime.model, codec: CODEC, user: { text: req.prompt, images: stored }, messages: [], ok: false };
252
235
  let final;
253
- const auth = this.auth();
254
- if (!auth && !this.endpoint) {
236
+ const line = this.line(runtime.model);
237
+ if (!line && !this.endpoint) {
255
238
  final = { ok: false, text: null, reason: 'auth:没有百炼按量 key' };
256
239
  warnings.push('百炼还没配好(按量 key):家长端「首页」里填,这位老师的运行时是 API 的,没有它说不了话');
257
240
  }
@@ -267,7 +250,7 @@ export class ApiBrain {
267
250
  }
268
251
  const user = await userMessage(text, attach, root, warnings);
269
252
  const tier = effortOf(runtime.model, req.effort, req.tools);
270
- const alibaba = createAlibaba({ apiKey: auth?.key ?? 'sk-endpoint', baseURL: this.endpoint ?? bailianBase(auth.workspaceId), includeUsage: true, ...(tier ? { fetch: effortFetch(tier) } : {}) });
253
+ const alibaba = createAlibaba({ apiKey: line?.key ?? 'sk-endpoint', baseURL: this.endpoint ?? line.baseURL, includeUsage: true, ...(tier ? { fetch: effortFetch(tier) } : {}) });
271
254
  // 断流看门狗:stall.ms 没有一块流到就停(工具在本进程里跑,几毫秒的事,不另算)
272
255
  const abort = new AbortController();
273
256
  let stalled = false;
@@ -296,7 +279,7 @@ export class ApiBrain {
296
279
  try {
297
280
  const result = streamText({
298
281
  model: alibaba(runtime.model),
299
- ...(req.systemBody ?? req.agentBody ? { system: req.systemBody ?? req.agentBody } : {}),
282
+ ...(req.systemBody ? { system: req.systemBody } : {}),
300
283
  messages: [...past, user],
301
284
  ...(req.tools !== 'off' ? { tools: readOnlyTools(req.readDirs, { images }) } : {}),
302
285
  stopWhen: isStepCount(runtime.maxSteps ?? DEFAULT_STEPS),
@@ -399,13 +382,13 @@ export class ApiBrain {
399
382
  hooks.onExit(Date.now());
400
383
  await new Promise((r) => out.end(r));
401
384
  // 没配凭证的那轮连请求都没发:不进历史
402
- if (auth || this.endpoint)
385
+ if (line || this.endpoint)
403
386
  await appendFile(files.history, `${JSON.stringify(record)}\n`).catch(() => { });
404
387
  const logText = await readFile(files.log, 'utf8').catch(() => '');
405
388
  const transcript = parseTranscript(logText, parseEventLine);
406
389
  const tools = toolCalls(logText, parseEventLine).slice(0, 200);
407
390
  return { transcript: { ...transcript, final: transcript.final ?? final }, tools, warnings };
408
391
  };
409
- return { warm: false, text: () => reader.text(), done: go() };
392
+ return { text: () => reader.text(), done: go() };
410
393
  }
411
394
  }