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/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 25 `study_*` tools (kept
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.estimate": " {n} tok(按事件字符估算)",
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.estimate": "~{n} tok (char-volume estimate)",
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 cap; malformed bodies reject. */
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) > 65536) {
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 25 `study_*` tools (kept
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.19.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",