dsh-plugin-lookatstudy 0.20.1 → 0.21.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/lib/client.js +289 -15
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +172 -49
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -201,6 +201,9 @@ const ZH = {
|
|
|
201
201
|
"note.retryRender": "重试渲染",
|
|
202
202
|
"zoom.in": "放大面板字号",
|
|
203
203
|
"zoom.out": "缩小面板字号",
|
|
204
|
+
"pane.rail.aria": "调整课程栏宽度",
|
|
205
|
+
"pane.chat.aria": "调整对话栏宽度",
|
|
206
|
+
"pane.tip": "拖拽调宽,双击重置",
|
|
204
207
|
"palette.label": "命令面板",
|
|
205
208
|
"palette.placeholder": "搜索课时/课程,或选择动作…",
|
|
206
209
|
"palette.lessons": "课时",
|
|
@@ -226,6 +229,7 @@ const ZH = {
|
|
|
226
229
|
"settings.budget.on": "开启历史预算",
|
|
227
230
|
"settings.budget.off": "关闭历史预算",
|
|
228
231
|
"attach.sent": "我发了一个文件:{name}(工作区路径 {path})——请读取它再继续。",
|
|
232
|
+
"attach.remove": "移除待发附件",
|
|
229
233
|
"attach.fail": "附件上传失败",
|
|
230
234
|
"attach.label": "附件(≤20MiB,存入学习工作区)",
|
|
231
235
|
"rail.mastered": "毕业 {mastered}/{total} 课",
|
|
@@ -544,6 +548,9 @@ const EN = {
|
|
|
544
548
|
"note.retryRender": "Retry render",
|
|
545
549
|
"zoom.in": "Increase panel font size",
|
|
546
550
|
"zoom.out": "Decrease panel font size",
|
|
551
|
+
"pane.rail.aria": "Resize the course pane width",
|
|
552
|
+
"pane.chat.aria": "Resize the chat pane width",
|
|
553
|
+
"pane.tip": "Drag to resize, double-click to reset",
|
|
547
554
|
"palette.label": "Command palette",
|
|
548
555
|
"palette.placeholder": "Search lessons/courses or pick an action…",
|
|
549
556
|
"palette.lessons": "Lesson",
|
|
@@ -569,6 +576,7 @@ const EN = {
|
|
|
569
576
|
"settings.budget.on": "Enable history budget",
|
|
570
577
|
"settings.budget.off": "Disable history budget",
|
|
571
578
|
"attach.sent": "I attached a file: {name} (workspace path {path}) — read it before continuing.",
|
|
579
|
+
"attach.remove": "Remove pending attachment",
|
|
572
580
|
"attach.fail": "Attachment upload failed",
|
|
573
581
|
"attach.label": "Attach (≤20MiB, lands in the study workspace)",
|
|
574
582
|
"rail.mastered": "{mastered}/{total} lessons graduated",
|
|
@@ -772,10 +780,13 @@ function escapeHtml(text) {
|
|
|
772
780
|
}
|
|
773
781
|
/** Render inline markup (code, bold, italic, links, images) over escaped text.
|
|
774
782
|
* Image src allowlist: https?:// or data:image/ — folder imports inline local
|
|
775
|
-
* images as data URLs (capped), GitHub imports reference jsDelivr;
|
|
776
|
-
*
|
|
783
|
+
* images as data URLs (capped), GitHub imports reference jsDelivr; the
|
|
784
|
+
* plugin's own attachment route (/lookatstudy/api/attachment/<name>, issue #7
|
|
785
|
+
* — same-origin, server-side validated) renders workspace attachments;
|
|
786
|
+
* anything else stays literal text (no broken relative images, no exotic
|
|
787
|
+
* schemes). */
|
|
777
788
|
function inline(escaped) {
|
|
778
|
-
return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+|data:image\/[^)\s]+)\)/g, (_m, alt, src) => `<img src="${src}" alt="${alt}" loading="lazy">`).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>");
|
|
789
|
+
return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+|data:image\/[^)\s]+|\/lookatstudy\/api\/attachment\/[^)\s]+)\)/g, (_m, alt, src) => `<img src="${src}" alt="${alt}" loading="lazy">`).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>");
|
|
779
790
|
}
|
|
780
791
|
/** Interleave one body with its translation, paragraph by paragraph (upstream
|
|
781
792
|
* translation-layout "microsoft" semantics: original paragraph then its
|
|
@@ -1485,18 +1496,23 @@ function freshLesson(title, anchor, body, kind = "study") {
|
|
|
1485
1496
|
* @param parsed - course tree from an importer.
|
|
1486
1497
|
* @param source - import origin.
|
|
1487
1498
|
* @param sourceRef - markdown/folder/repo reference for display.
|
|
1499
|
+
* @param languageTarget - the taught language (BCP-47) when the course IS a
|
|
1500
|
+
* language course (design-apply path only; markdown imports stay undefined).
|
|
1488
1501
|
* @returns the imported (or pre-existing) course.
|
|
1489
1502
|
*/
|
|
1490
|
-
function importCourse(state, parsed, source, sourceRef) {
|
|
1491
|
-
const
|
|
1492
|
-
const
|
|
1493
|
-
if (
|
|
1503
|
+
function importCourse(state, parsed, source, sourceRef, languageTarget) {
|
|
1504
|
+
const base = slugify(parsed.title);
|
|
1505
|
+
const sameTitle = state.courses.find((c) => c.id === base && c.title === parsed.title);
|
|
1506
|
+
if (sameTitle !== void 0) return sameTitle;
|
|
1507
|
+
let id = base;
|
|
1508
|
+
for (let n = 2; state.courses.some((c) => c.id === id); n++) id = `${base}-${String(n)}`;
|
|
1494
1509
|
const course = {
|
|
1495
1510
|
id,
|
|
1496
1511
|
title: parsed.title,
|
|
1497
1512
|
source,
|
|
1498
1513
|
sourceRef,
|
|
1499
1514
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1515
|
+
languageTarget: languageTarget ?? null,
|
|
1500
1516
|
sections: parsed.sections.map((section) => {
|
|
1501
1517
|
const lessons = section.lessons.map((lesson) => (() => {
|
|
1502
1518
|
const st = freshLesson(lesson.title, lesson.anchor, lesson.body, lesson.world === "practice" ? "practice" : "study");
|
|
@@ -3179,6 +3195,36 @@ function registerDashboard(webServer, deps) {
|
|
|
3179
3195
|
}
|
|
3180
3196
|
return;
|
|
3181
3197
|
}
|
|
3198
|
+
if (req.method === "GET" && pathname.startsWith("/lookatstudy/api/attachment/")) {
|
|
3199
|
+
let name = "";
|
|
3200
|
+
try {
|
|
3201
|
+
name = decodeURIComponent(pathname.slice(28));
|
|
3202
|
+
} catch {
|
|
3203
|
+
name = "";
|
|
3204
|
+
}
|
|
3205
|
+
if (name === "" || name.includes("..") || /[\\/]/.test(name) || name.length > 250) {
|
|
3206
|
+
sendJson(res, 404, {
|
|
3207
|
+
ok: false,
|
|
3208
|
+
error: "not found"
|
|
3209
|
+
});
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
try {
|
|
3213
|
+
const buf = readFileSync(join(deps.studyAreaPath, "attachments", name));
|
|
3214
|
+
const ext = name.slice(name.lastIndexOf(".") + 1).toLowerCase();
|
|
3215
|
+
const mime = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : ext === "bmp" ? "image/bmp" : "application/octet-stream";
|
|
3216
|
+
res.writeHead(200, {
|
|
3217
|
+
"content-type": mime,
|
|
3218
|
+
"cache-control": "public, max-age=31536000, immutable"
|
|
3219
|
+
}).end(buf);
|
|
3220
|
+
} catch {
|
|
3221
|
+
sendJson(res, 404, {
|
|
3222
|
+
ok: false,
|
|
3223
|
+
error: "not found"
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3182
3228
|
sendJson(res, 404, {
|
|
3183
3229
|
ok: false,
|
|
3184
3230
|
error: "not found"
|
|
@@ -5045,8 +5091,13 @@ function renderDesignBrief(pending, part = 1) {
|
|
|
5045
5091
|
lines.push("- if the directory layout is already clear (e.g. lessons/N-Topic/), keep its sections; do not over-reorganize.");
|
|
5046
5092
|
lines.push(`- anchor is the full heading text used to slice the body; omit it for whole-file lessons.${shown.length > 80 ? " This part is large: design at file granularity (omit anchors, one lesson per file) to keep the JSON manageable." : ""}`);
|
|
5047
5093
|
lines.push("");
|
|
5094
|
+
lines.push("### Language-course check (decide while designing)");
|
|
5095
|
+
lines.push("Is this course teaching a language ITSELF (vocabulary, grammar, reading/writing/listening of English/Japanese/Korean/Chinese/…), rather than a knowledge/tech course that merely happens to be written in some language? Typical tells: vocab lists, grammar-point explanations, dialogues/readings, sentence-translation pairs, level markers (N5/N1, TOEFL/IELTS, HSK, JLPT), pronunciation content.");
|
|
5096
|
+
lines.push("- Yes → add \"languageTarget\": \"<BCP-47 of the TAUGHT language>\" (teaching English → \"en\", Japanese → \"ja\"). The writing language is NOT the taught language — a Chinese-written English-textbook repo has languageTarget \"en\".");
|
|
5097
|
+
lines.push("- No, or unsure → omit it entirely (a normal course misflagged as a language course is worse than the reverse).");
|
|
5098
|
+
lines.push("");
|
|
5048
5099
|
lines.push("Now design the course and call study_apply_design with:");
|
|
5049
|
-
lines.push("{ \"sections\": [ { \"title\": \"...\", \"lessons\": [ { \"title\": \"...\", \"file\": \"<exact path from this brief>\", \"anchor\": \"<optional full heading text>\", \"world\": \"study\" | \"practice\" } ] } ] }");
|
|
5100
|
+
lines.push("{ \"languageTarget\": \"<only for language courses>\", \"sections\": [ { \"title\": \"...\", \"lessons\": [ { \"title\": \"...\", \"file\": \"<exact path from this brief>\", \"anchor\": \"<optional full heading text>\", \"world\": \"study\" | \"practice\" } ] } ] }");
|
|
5050
5101
|
lines.push("Use ONLY file paths that appear in this brief — anything else is dropped. Apply directly, then walk the learner through the course map.");
|
|
5051
5102
|
return lines.join("\n");
|
|
5052
5103
|
}
|
|
@@ -6126,50 +6177,56 @@ function studyTools(store, deps = {}) {
|
|
|
6126
6177
|
const applyDesign = defineTool({
|
|
6127
6178
|
name: "study_apply_design",
|
|
6128
6179
|
description: "Apply the tutor-designed course structure to the pending import (the one study_import_github or study_import_folder returned design_required for). Every lesson's file must come from the design brief — unknown paths are dropped (anti-hallucination); lesson bodies are sliced by their anchor heading and the course is imported. On a validation or fetch error the tutor fixes the design and simply calls again.",
|
|
6129
|
-
parameters: {
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6180
|
+
parameters: {
|
|
6181
|
+
languageTarget: {
|
|
6182
|
+
type: "string",
|
|
6183
|
+
description: "Set ONLY when this course teaches a language ITSELF (vocabulary, grammar, reading/writing of English/Japanese/Chinese/…) rather than being a knowledge/tech course that merely happens to be written in some language: the taught language as a BCP-47 tag (\"en\", \"ja\", \"zh-CN\", …). The writing language is NOT the taught language — a Chinese-written English-textbook repo has languageTarget \"en\". Typical tells: vocab lists, grammar points, dialogues/readings, sentence-translation pairs, level markers (N5/N1, TOEFL, HSK, JLPT), pronunciation content. When unsure, omit it — a normal course misflagged as a language course is worse than the reverse."
|
|
6184
|
+
},
|
|
6185
|
+
sections: {
|
|
6186
|
+
type: "array",
|
|
6187
|
+
required: true,
|
|
6188
|
+
description: "Designed sections in learning order.",
|
|
6189
|
+
items: {
|
|
6190
|
+
type: "object",
|
|
6191
|
+
additionalProperties: false,
|
|
6192
|
+
properties: {
|
|
6193
|
+
title: {
|
|
6194
|
+
type: "string",
|
|
6195
|
+
required: true,
|
|
6196
|
+
description: "Section title (in the learner's language)."
|
|
6197
|
+
},
|
|
6198
|
+
lessons: {
|
|
6199
|
+
type: "array",
|
|
6200
|
+
required: true,
|
|
6201
|
+
items: {
|
|
6202
|
+
type: "object",
|
|
6203
|
+
additionalProperties: false,
|
|
6204
|
+
properties: {
|
|
6205
|
+
title: {
|
|
6206
|
+
type: "string",
|
|
6207
|
+
required: true,
|
|
6208
|
+
description: "Lesson title."
|
|
6209
|
+
},
|
|
6210
|
+
file: {
|
|
6211
|
+
type: "string",
|
|
6212
|
+
required: true,
|
|
6213
|
+
description: "Exact file path from the design brief."
|
|
6214
|
+
},
|
|
6215
|
+
anchor: {
|
|
6216
|
+
type: "string",
|
|
6217
|
+
description: "Full H2/H3 heading text the lesson body starts at; omit for whole-file lessons."
|
|
6218
|
+
},
|
|
6219
|
+
world: {
|
|
6220
|
+
type: "string",
|
|
6221
|
+
description: "\"study\" (explanation) or \"practice\" (exercise/lab/notebook); anything else is treated as study."
|
|
6222
|
+
}
|
|
6166
6223
|
}
|
|
6167
6224
|
}
|
|
6168
6225
|
}
|
|
6169
6226
|
}
|
|
6170
6227
|
}
|
|
6171
6228
|
}
|
|
6172
|
-
}
|
|
6229
|
+
},
|
|
6173
6230
|
output: {
|
|
6174
6231
|
schema: {
|
|
6175
6232
|
type: "object",
|
|
@@ -6202,12 +6259,21 @@ function studyTools(store, deps = {}) {
|
|
|
6202
6259
|
droppedLessons: {
|
|
6203
6260
|
type: "integer",
|
|
6204
6261
|
required: true
|
|
6262
|
+
},
|
|
6263
|
+
created: {
|
|
6264
|
+
type: "boolean",
|
|
6265
|
+
required: true,
|
|
6266
|
+
description: "False when an existing course short-circuited the import (same title) — the design did NOT land as a new course."
|
|
6267
|
+
},
|
|
6268
|
+
languageTarget: {
|
|
6269
|
+
type: "string",
|
|
6270
|
+
description: "Present only when the course teaches a language itself (BCP-47); the tutor then keeps quiz material in the target language."
|
|
6205
6271
|
}
|
|
6206
6272
|
}
|
|
6207
6273
|
},
|
|
6208
6274
|
render: (_args, value) => [{
|
|
6209
6275
|
type: "text",
|
|
6210
|
-
text: `Imported designed course “${value.title}” (${value.sections} sections, ${value.lessons} lessons${value.droppedLessons > 0 ? `, ${value.droppedLessons} hallucinated lesson(s) dropped` : ""}). First lesson: “${value.firstLessonTitle}” (id ${value.firstLessonId}). Present the course map to the learner.`
|
|
6276
|
+
text: value.created === false ? `“${value.title}” (id ${value.courseId}) was ALREADY imported — the design did not create a new course. Continue with the existing course, or delete it first (study_delete_course) to rebuild from this design.` : `Imported designed course “${value.title}” (${value.sections} sections, ${value.lessons} lessons${value.droppedLessons > 0 ? `, ${value.droppedLessons} hallucinated lesson(s) dropped` : ""})${typeof value.languageTarget === "string" ? ` — language course (${value.languageTarget}): keep reading passages, example sentences, and quiz language material in the target language` : ""}. First lesson: “${value.firstLessonTitle}” (id ${value.firstLessonId}). Present the course map to the learner.`
|
|
6211
6277
|
}]
|
|
6212
6278
|
},
|
|
6213
6279
|
async execute(args, exec) {
|
|
@@ -6236,12 +6302,20 @@ function studyTools(store, deps = {}) {
|
|
|
6236
6302
|
}
|
|
6237
6303
|
const parsed = buildCourseFromDesign(pd.part !== void 0 && pd.partCount !== void 0 && pd.partCount > 1 ? `${pd.courseTitle} (part ${pd.part})` : pd.courseTitle, validated, contents, pd.translations);
|
|
6238
6304
|
requireParsedLessons(parsed);
|
|
6239
|
-
const
|
|
6305
|
+
const languageTarget = typeof args.languageTarget === "string" && args.languageTarget.trim() !== "" ? args.languageTarget.trim().slice(0, 35) : null;
|
|
6306
|
+
const value = mutate((state) => {
|
|
6307
|
+
const before = state.courses.length;
|
|
6308
|
+
return {
|
|
6309
|
+
...toImportValue(importCourse(state, parsed, pd.source, pd.url, languageTarget)),
|
|
6310
|
+
created: state.courses.length > before
|
|
6311
|
+
};
|
|
6312
|
+
});
|
|
6240
6313
|
pendingDesign = null;
|
|
6241
6314
|
pendingPart = 1;
|
|
6242
6315
|
return {
|
|
6243
6316
|
...value,
|
|
6244
|
-
droppedLessons: validated.droppedLessons
|
|
6317
|
+
droppedLessons: validated.droppedLessons,
|
|
6318
|
+
...languageTarget !== null ? { languageTarget } : {}
|
|
6245
6319
|
};
|
|
6246
6320
|
},
|
|
6247
6321
|
timeoutMs: 18e4,
|
|
@@ -8690,6 +8764,45 @@ function studyTools(store, deps = {}) {
|
|
|
8690
8764
|
];
|
|
8691
8765
|
}
|
|
8692
8766
|
//#endregion
|
|
8767
|
+
//#region src/vendor/locale-names.ts
|
|
8768
|
+
/**
|
|
8769
|
+
* Vendored from LookatStudy shared/locales.ts (MIT License,
|
|
8770
|
+
* https://github.com/Kaiji-Z/LookatStudy), upstream v0.33.2 — only the
|
|
8771
|
+
* name-map + lookup function (the language-course round's BCP-47 → display
|
|
8772
|
+
* name need). Unmodified except this provenance header.
|
|
8773
|
+
*
|
|
8774
|
+
* locales —— BCP-47 → 人类可读语言名的共享映射(纯函数)。
|
|
8775
|
+
* 未知 locale 一律原样返回(如 "pt" → "pt"),不猜名字。
|
|
8776
|
+
*/
|
|
8777
|
+
/** locale → 该语言的自称名(尽量用母语写法,语言学习者认得出) */
|
|
8778
|
+
const LOCALE_LANGUAGE_NAMES = {
|
|
8779
|
+
en: "English",
|
|
8780
|
+
"zh-CN": "中文",
|
|
8781
|
+
"zh-TW": "繁體中文",
|
|
8782
|
+
ja: "日本語",
|
|
8783
|
+
ko: "한국어",
|
|
8784
|
+
fr: "Français",
|
|
8785
|
+
de: "Deutsch",
|
|
8786
|
+
es: "Español",
|
|
8787
|
+
"pt-BR": "Português",
|
|
8788
|
+
ru: "Русский",
|
|
8789
|
+
it: "Italiano",
|
|
8790
|
+
ar: "العربية",
|
|
8791
|
+
hi: "हिन्दी",
|
|
8792
|
+
tr: "Türkçe",
|
|
8793
|
+
pl: "Polski",
|
|
8794
|
+
nl: "Nederlands",
|
|
8795
|
+
id: "Indonesia",
|
|
8796
|
+
vi: "Tiếng Việt",
|
|
8797
|
+
th: "ไทย",
|
|
8798
|
+
sv: "Svenska",
|
|
8799
|
+
fi: "Suomi"
|
|
8800
|
+
};
|
|
8801
|
+
/** BCP-47 → 语言名;未映射的 locale 原样返回 */
|
|
8802
|
+
function localeToLanguageName(locale) {
|
|
8803
|
+
return LOCALE_LANGUAGE_NAMES[locale] ?? locale;
|
|
8804
|
+
}
|
|
8805
|
+
//#endregion
|
|
8693
8806
|
//#region src/surface.ts
|
|
8694
8807
|
/**
|
|
8695
8808
|
* Stable tutor core (ported from LookatStudy's BASE_AGENT_PROMPT plus its
|
|
@@ -8800,6 +8913,16 @@ function snapshotSectionText(state) {
|
|
|
8800
8913
|
if (memory.length > 0) lines.push(`记忆: ${memory.join(" | ")}`);
|
|
8801
8914
|
if (snap.dueCount > 0) lines.push(`今日待复习: ${snap.dueCount} 项`);
|
|
8802
8915
|
if (snap.pendingProposal !== null) lines.push(`待决提案 ${snap.pendingProposal.id}: ${snap.pendingProposal.rationale}(等学习者表态)`);
|
|
8916
|
+
const focusCourse = state.courses.find((c) => c.id === snap.focus?.courseId);
|
|
8917
|
+
if (focusCourse?.languageTarget != null && focusCourse.languageTarget !== "") {
|
|
8918
|
+
const t = localeToLanguageName(focusCourse.languageTarget);
|
|
8919
|
+
lines.push("");
|
|
8920
|
+
lines.push(`【语言教学姿态】这门课程教的是${t}——教学语言(讲解、指令、反馈)跟随学习者的界面语言,${t}是学习对象:`);
|
|
8921
|
+
lines.push(`- 课文引用、例句、题目素材保持${t}原文,考察目标语言本身——不要把目标语言材料翻译成教学语言来讲解或出题(生词首次出现可附教学语言注释,注释是辅助不是替代);`);
|
|
8922
|
+
lines.push(`- 出题双轴:题干和选项中的语言素材(句子/单词/短语/语法点)用${t}原文,指令性文字和解析用教学语言;`);
|
|
8923
|
+
lines.push(`- 学习者用${t}造句或表达时,先肯定再纠正其中的语言错误(语法/用词/拼写),用教学语言讲清错在哪;`);
|
|
8924
|
+
lines.push(`- 随掌握度上升,渐进提高${t}素材的占比,把学习者往"直接用${t}读"带。`);
|
|
8925
|
+
}
|
|
8803
8926
|
if (state.historyBudget === true) lines.push("历史预算已开启:当会话变长时,主动概括旧轮次的要点并基于概要继续,避免逐字复读历史。");
|
|
8804
8927
|
return lines.join("\n");
|
|
8805
8928
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-lookatstudy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"packageManager": "pnpm@11.7.0",
|
|
5
5
|
"description": "Turn any markdown, local folder, or GitHub learning repo into a guided course inside DeepSeek Harness: gated skill-tree progression, BKT mastery tracking, SM-2 spaced repetition.",
|
|
6
6
|
"type": "module",
|