dsh-plugin-lookatstudy 0.19.0 → 0.20.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/README.md +28 -10
- package/README.zh-CN.md +121 -0
- package/lib/client.js +528 -577
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +1 -1
- package/lib/index.mjs +56 -32
- package/package.json +1 -1
package/lib/index.d.mts
CHANGED
|
@@ -32,7 +32,7 @@ declare const Config: z<Config>;
|
|
|
32
32
|
declare const name = "lookatstudy-plugin";
|
|
33
33
|
declare const inject: string[];
|
|
34
34
|
/**
|
|
35
|
-
* Register the activation-gated study surface: the
|
|
35
|
+
* Register the activation-gated study surface: the 31 `study_*` tools (kept
|
|
36
36
|
* unregistered while dormant), the tutor persona (stable core + soul), the
|
|
37
37
|
* dynamic learner-snapshot context, and the `/study` command — every prompt
|
|
38
38
|
* text renders empty while inactive, and empty sections are dropped at
|
package/lib/index.mjs
CHANGED
|
@@ -135,7 +135,6 @@ const ZH = {
|
|
|
135
135
|
"note.src.learner": "我的笔记",
|
|
136
136
|
"error.send": "消息没有送达——请稍后再试一次。",
|
|
137
137
|
"error.action": "操作没有完成——请稍后再试。",
|
|
138
|
-
"thread.label": "「{title}」的对话",
|
|
139
138
|
"thread.streamingBadge": "AI 正在输出",
|
|
140
139
|
"map.search.label": "搜索",
|
|
141
140
|
"map.review.label": "复习",
|
|
@@ -152,15 +151,6 @@ const ZH = {
|
|
|
152
151
|
"quiz.submit": "提交",
|
|
153
152
|
"proposal.applied": "《{lesson}》已采纳提前毕业提议",
|
|
154
153
|
"proposal.rejected": "《{lesson}》提议已忽略,继续保持练习",
|
|
155
|
-
"companion.poke": "戳一戳伴学伙伴",
|
|
156
|
-
"companion.poked": "伴学伙伴向你眨了眨眼",
|
|
157
|
-
"companion.form.ember": "小焰",
|
|
158
|
-
"companion.form.frost": "霜绒",
|
|
159
|
-
"companion.form.moss": "苔芽",
|
|
160
|
-
"companion.form.star": "星尘",
|
|
161
|
-
"companion.form.ink": "墨墨",
|
|
162
|
-
"settings.companion": "伴学伙伴",
|
|
163
|
-
"settings.companion.hint": "选择学习面板右下角的伴学生物形态。",
|
|
164
154
|
"quiz.msg.explain-wrong": "我刚才有题答错了,帮我讲讲为什么错、正确的思路是什么。",
|
|
165
155
|
"quiz.msg.retry": "再来一组类似的题巩固一下。",
|
|
166
156
|
"quiz.msg.go-deeper": "这组我答得不错,帮我深入讲讲背后的原理和容易混淆的地方。",
|
|
@@ -219,9 +209,15 @@ const ZH = {
|
|
|
219
209
|
"palette.study": "开始学习",
|
|
220
210
|
"palette.kind.action": "操作",
|
|
221
211
|
"palette.empty": "没有匹配项",
|
|
212
|
+
"threads.open": "{n} 条对话",
|
|
213
|
+
"threads.now": "当前",
|
|
222
214
|
"threads.label": "课时线程",
|
|
223
215
|
"meter.label": "上下文用量",
|
|
224
|
-
"meter.
|
|
216
|
+
"meter.aria": "上下文已用 {pct}%",
|
|
217
|
+
"meter.brk.system": "系统提示",
|
|
218
|
+
"meter.brk.tools": "工具",
|
|
219
|
+
"meter.brk.messages": "对话",
|
|
220
|
+
"meter.estimate.note": "按事件字符估算",
|
|
225
221
|
"model.unknown": "当前模型",
|
|
226
222
|
"model.switched": "已切换到 {model}",
|
|
227
223
|
"model.switchFailed": "切换失败——请看宿主模型设置",
|
|
@@ -287,7 +283,7 @@ const ZH = {
|
|
|
287
283
|
"quiz.answer": "选 {letter}:{text}",
|
|
288
284
|
"tutor.dormant": "学习模式已关闭 — 在下方对话区点「开始学习」即可唤醒导师。",
|
|
289
285
|
"tutor.empty": "对话会出现在这里",
|
|
290
|
-
"tutor.empty.hint": "
|
|
286
|
+
"tutor.empty.hint": "和导师聊聊这一课",
|
|
291
287
|
"proposal.text": "导师提议你已掌握「{lesson}」:{rationale}",
|
|
292
288
|
"proposal.accept": "接受",
|
|
293
289
|
"proposal.decline": "再练练",
|
|
@@ -482,7 +478,6 @@ const EN = {
|
|
|
482
478
|
"note.src.learner": "My note",
|
|
483
479
|
"error.send": "The message did not go through — please try again.",
|
|
484
480
|
"error.action": "The action did not complete — please try again.",
|
|
485
|
-
"thread.label": "Conversation · {title}",
|
|
486
481
|
"thread.streamingBadge": "AI is responding",
|
|
487
482
|
"map.search.label": "Search",
|
|
488
483
|
"map.review.label": "Review",
|
|
@@ -499,15 +494,6 @@ const EN = {
|
|
|
499
494
|
"quiz.submit": "Submit",
|
|
500
495
|
"proposal.applied": "Graduation proposal for \"{lesson}\" accepted",
|
|
501
496
|
"proposal.rejected": "Proposal for \"{lesson}\" declined — keep practicing",
|
|
502
|
-
"companion.poke": "Poke the companion",
|
|
503
|
-
"companion.poked": "The companion winks at you",
|
|
504
|
-
"companion.form.ember": "Ember",
|
|
505
|
-
"companion.form.frost": "Frost",
|
|
506
|
-
"companion.form.moss": "Moss",
|
|
507
|
-
"companion.form.star": "Stardust",
|
|
508
|
-
"companion.form.ink": "Ink",
|
|
509
|
-
"settings.companion": "Companion",
|
|
510
|
-
"settings.companion.hint": "Choose the companion creature in the study panel corner.",
|
|
511
497
|
"quiz.msg.explain-wrong": "I got some questions wrong — walk me through why and the right line of thinking.",
|
|
512
498
|
"quiz.msg.retry": "Give me another similar set to consolidate.",
|
|
513
499
|
"quiz.msg.go-deeper": "I did well on this set — go deeper into the principles and common confusions.",
|
|
@@ -567,8 +553,14 @@ const EN = {
|
|
|
567
553
|
"palette.kind.action": "Action",
|
|
568
554
|
"palette.empty": "No matches",
|
|
569
555
|
"threads.label": "Lesson threads",
|
|
556
|
+
"threads.open": "{n} threads",
|
|
557
|
+
"threads.now": "current",
|
|
570
558
|
"meter.label": "Context usage",
|
|
571
|
-
"meter.
|
|
559
|
+
"meter.aria": "{pct}% of context used",
|
|
560
|
+
"meter.brk.system": "System prompt",
|
|
561
|
+
"meter.brk.tools": "Tools",
|
|
562
|
+
"meter.brk.messages": "Messages",
|
|
563
|
+
"meter.estimate.note": "Char-volume estimate",
|
|
572
564
|
"model.unknown": "Current model",
|
|
573
565
|
"model.switched": "Switched to {model}",
|
|
574
566
|
"model.switchFailed": "Switch failed — check the host model settings",
|
|
@@ -2634,11 +2626,13 @@ function sendJson(res, status, value) {
|
|
|
2634
2626
|
/**
|
|
2635
2627
|
* Read one JSON body, answering 400 on malformed or oversized input so the
|
|
2636
2628
|
* handler never throws into the HTTP layer.
|
|
2629
|
+
* @param maxBytes - per-route body ceiling; defaults to the tight 64 kB every
|
|
2630
|
+
* small-json route wants (issue #4: only the attachment intake lifts it).
|
|
2637
2631
|
* @returns the parsed value, or undefined when the response is already sent.
|
|
2638
2632
|
*/
|
|
2639
|
-
async function readJsonBodySafe(req, res) {
|
|
2633
|
+
async function readJsonBodySafe(req, res, maxBytes = 65536) {
|
|
2640
2634
|
try {
|
|
2641
|
-
return await readJsonBody(req);
|
|
2635
|
+
return await readJsonBody(req, maxBytes);
|
|
2642
2636
|
} catch (error) {
|
|
2643
2637
|
sendJson(res, 400, {
|
|
2644
2638
|
ok: false,
|
|
@@ -2647,13 +2641,13 @@ async function readJsonBodySafe(req, res) {
|
|
|
2647
2641
|
return;
|
|
2648
2642
|
}
|
|
2649
2643
|
}
|
|
2650
|
-
/** Read one JSON request body with a hard 64 kB
|
|
2651
|
-
function readJsonBody(req) {
|
|
2644
|
+
/** Read one JSON request body with a hard cap (64 kB unless the route lifts it); malformed bodies reject. */
|
|
2645
|
+
function readJsonBody(req, maxBytes = 65536) {
|
|
2652
2646
|
return new Promise((resolve, reject) => {
|
|
2653
2647
|
const chunks = [];
|
|
2654
2648
|
req.on("data", (chunk) => {
|
|
2655
2649
|
chunks.push(chunk);
|
|
2656
|
-
if (chunks.reduce((n, c) => n + c.length, 0) >
|
|
2650
|
+
if (chunks.reduce((n, c) => n + c.length, 0) > maxBytes) {
|
|
2657
2651
|
reject(/* @__PURE__ */ new Error("request body too large"));
|
|
2658
2652
|
return;
|
|
2659
2653
|
}
|
|
@@ -2675,6 +2669,16 @@ function readJsonBody(req) {
|
|
|
2675
2669
|
* @returns the disposer removing every route.
|
|
2676
2670
|
*/
|
|
2677
2671
|
function registerDashboard(webServer, deps) {
|
|
2672
|
+
let modelCache = null;
|
|
2673
|
+
const modelInfo = async () => {
|
|
2674
|
+
if (modelCache !== null && Date.now() - modelCache.at < 3e4) return modelCache.value;
|
|
2675
|
+
const value = await deps.modelInfo().catch(() => null);
|
|
2676
|
+
modelCache = {
|
|
2677
|
+
at: Date.now(),
|
|
2678
|
+
value
|
|
2679
|
+
};
|
|
2680
|
+
return value;
|
|
2681
|
+
};
|
|
2678
2682
|
const disposeRoutes = webServer.register({
|
|
2679
2683
|
kind: "prefix",
|
|
2680
2684
|
path: "/lookatstudy",
|
|
@@ -2684,7 +2688,8 @@ function registerDashboard(webServer, deps) {
|
|
|
2684
2688
|
sendJson(res, 200, {
|
|
2685
2689
|
...workbenchState(deps.store.get(), /* @__PURE__ */ new Date()),
|
|
2686
2690
|
statePath: deps.statePath,
|
|
2687
|
-
version: pluginVersion()
|
|
2691
|
+
version: pluginVersion(),
|
|
2692
|
+
model: await modelInfo()
|
|
2688
2693
|
});
|
|
2689
2694
|
return;
|
|
2690
2695
|
}
|
|
@@ -3137,7 +3142,7 @@ function registerDashboard(webServer, deps) {
|
|
|
3137
3142
|
return;
|
|
3138
3143
|
}
|
|
3139
3144
|
if (req.method === "POST" && pathname === "/lookatstudy/api/attachment") {
|
|
3140
|
-
const body = await readJsonBodySafe(req, res);
|
|
3145
|
+
const body = await readJsonBodySafe(req, res, 29360128);
|
|
3141
3146
|
if (body === void 0) return;
|
|
3142
3147
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
3143
3148
|
const data = typeof body.dataBase64 === "string" ? body.dataBase64 : "";
|
|
@@ -8840,7 +8845,7 @@ function createStudySurface(registry, store) {
|
|
|
8840
8845
|
const name = "lookatstudy-plugin";
|
|
8841
8846
|
const inject = ["tools", "systemPrompt"];
|
|
8842
8847
|
/**
|
|
8843
|
-
* Register the activation-gated study surface: the
|
|
8848
|
+
* Register the activation-gated study surface: the 31 `study_*` tools (kept
|
|
8844
8849
|
* unregistered while dormant), the tutor persona (stable core + soul), the
|
|
8845
8850
|
* dynamic learner-snapshot context, and the `/study` command — every prompt
|
|
8846
8851
|
* text renders empty while inactive, and empty sections are dropped at
|
|
@@ -8883,6 +8888,24 @@ function apply(ctx, config) {
|
|
|
8883
8888
|
});
|
|
8884
8889
|
cmdCtx.effect(() => disposeCommand, "lookatstudy.studyCommand()");
|
|
8885
8890
|
});
|
|
8891
|
+
let resolveModelInfo = null;
|
|
8892
|
+
ctx.inject(["agentDefaultModel", "llm"], (modelCtx) => {
|
|
8893
|
+
const faces = modelCtx;
|
|
8894
|
+
resolveModelInfo = async () => {
|
|
8895
|
+
try {
|
|
8896
|
+
const selection = faces.agentDefaultModel.currentSelection();
|
|
8897
|
+
const info = await faces.llm.resolveModelInfo(selection.provider, selection.model);
|
|
8898
|
+
return {
|
|
8899
|
+
id: info.id,
|
|
8900
|
+
provider: selection.provider,
|
|
8901
|
+
contextWindow: info.context?.contextWindow ?? null
|
|
8902
|
+
};
|
|
8903
|
+
} catch {
|
|
8904
|
+
return null;
|
|
8905
|
+
}
|
|
8906
|
+
};
|
|
8907
|
+
});
|
|
8908
|
+
const modelInfo = async () => resolveModelInfo === null ? null : resolveModelInfo();
|
|
8886
8909
|
ctx.inject(["webServer"], (webCtx) => {
|
|
8887
8910
|
const studyAreaPath = join(dirname(statePath), "study-area");
|
|
8888
8911
|
mkdirSync(studyAreaPath, { recursive: true });
|
|
@@ -8890,7 +8913,8 @@ function apply(ctx, config) {
|
|
|
8890
8913
|
store,
|
|
8891
8914
|
studyAreaPath,
|
|
8892
8915
|
statePath,
|
|
8893
|
-
onActiveChange: surface.sync
|
|
8916
|
+
onActiveChange: surface.sync,
|
|
8917
|
+
modelInfo
|
|
8894
8918
|
});
|
|
8895
8919
|
webCtx.effect(() => disposeDashboard, "lookatstudy.dashboard()");
|
|
8896
8920
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-lookatstudy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|