dsh-deeptutor 0.1.10 → 0.1.11

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 CHANGED
@@ -54,10 +54,12 @@ study page, then archived to a notebook):
54
54
 
55
55
  ## Requirements
56
56
 
57
- - A working DeepSeek Harness (dsh) install, 0.1.0-rc.6 or any 0.2.x release
58
- (compatibility re-verified on dsh 0.2.1-rc.2 / 0.2.1-rc.3). Bundle
59
- auto-registration via `dsh plugin add` is verified on dsh CLI 0.1.0-rc.6 +
60
- pnpm 8.15.6.
57
+ - A working DeepSeek Harness (dsh) install, 0.1.0-rc.6 or newer. The manifest
58
+ declares a version floor rather than a ceiling, so a new dsh release is never
59
+ refused at install time; a scheduled probe boots the plugin against the
60
+ current npm `latest` and `next` hosts instead and opens an issue when
61
+ activation breaks. Bundle auto-registration via `dsh plugin add` was verified
62
+ on dsh CLI 0.1.0-rc.6, 0.2.1-rc.2 and 0.2.1-rc.3.
61
63
  - A DeepTutor deployment, either:
62
64
  - **Local** — `deeptutor serve` running on this machine, or the `deeptutor`
63
65
  CLI on `PATH` (used as fallback);
package/README.zh.md CHANGED
@@ -39,7 +39,7 @@
39
39
 
40
40
  ## 环境要求
41
41
 
42
- - 可用的 DeepSeek Harness (dsh) 安装,0.1.0-rc.6 或任意 0.2.x 版本(已在 dsh 0.2.1-rc.2 / 0.2.1-rc.3 上重新验证兼容性)。`dsh plugin add` 自动登记 bundle 已在 dsh CLI 0.1.0-rc.6 + pnpm 8.15.6 上验证。
42
+ - 可用的 DeepSeek Harness (dsh) 安装,0.1.0-rc.6 或更新版本。清单里只写下限不写上限,新版本 dsh 发布后不会再在安装时被拒绝;替代办法是定时拿 npm 上 `latest` 和 `next` 两个通道的宿主各冷启动一次,插件起不来就开 issue。`dsh plugin add` 自动登记 bundle 已在 dsh CLI 0.1.0-rc.6、0.2.1-rc.2 与 0.2.1-rc.3 上验证。
43
43
  - 一个 DeepTutor 部署,二选一:
44
44
  - **本地** —— 本机运行 `deeptutor serve`,或 PATH 上有 `deeptutor` CLI(作为回退);
45
45
  - **远程** —— DeepTutor 在服务器上,通过自动建立的 SSH 隧道访问(含 SSH CLI 回退)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-deeptutor",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Learning assistant extension for DeepSeek Harness (dsh): DeepTutor tutoring for your agent — deep explanations, self-test questions, learning paths, personal knowledge-base search (RAG), and note archiving (HTTP/WS first, CLI fallback; auto-adapts local/remote deployment).",
5
5
  "keywords": [
6
6
  "dsh",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=20.11",
25
- "dsh": "^0.1.0-rc.6 || ^0.2.0-rc.1"
25
+ "dsh": ">=0.1.0-rc.6"
26
26
  },
27
27
  "type": "module",
28
28
  "main": "./lib/index.js",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "peerDependencies": {
67
67
  "@deepseek-ai/cordis": "^4.0.1",
68
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1"
68
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.6"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@deepseek-ai/cordis": "^4.0.5",
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Boot-verify dsh-deeptutor against the `dsh` on PATH.
4
+ *
5
+ * Why this exists: the manifest no longer caps the supported dsh line (that is
6
+ * what issue #5 was about), so the compatibility gate is replaced by this
7
+ * probe. The scheduled `dsh-compat` workflow runs it against the npm `latest`
8
+ * and `next` releases; run it by hand after a dsh upgrade as well.
9
+ *
10
+ * What it does, against a throwaway DSH_HOME:
11
+ * 1. packs this repository (prepack builds + typechecks) unless --spec is given;
12
+ * 2. installs the package into a fresh `compat` profile;
13
+ * 3. cold-boots that profile with a dead model endpoint;
14
+ * 4. asserts the plugin activated (no `did not activate` / import failure).
15
+ *
16
+ * Usage:
17
+ * node scripts/check-dsh-compat.mjs [--spec <dir|tarball|npm-spec>] [--timeout 60]
18
+ * [--dsh-bin dsh] [--json] [--keep-workspace]
19
+ *
20
+ * Exit codes: 0 = the plugin activated, 1 = it did not, 2 = inconclusive.
21
+ * The probe only reads and writes inside its own temporary DSH_HOME.
22
+ */
23
+ import { spawnSync } from "node:child_process";
24
+ import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
25
+ import { tmpdir } from "node:os";
26
+ import { join, resolve } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+
29
+ const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
30
+ const argv = process.argv.slice(2);
31
+
32
+ function option(name, fallback) {
33
+ const index = argv.indexOf(name);
34
+ return index >= 0 && index + 1 < argv.length ? argv[index + 1] : fallback;
35
+ }
36
+
37
+ const dshBin = option("--dsh-bin", process.platform === "win32" ? "dsh.cmd" : "dsh");
38
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
39
+ const timeoutSeconds = Number(option("--timeout", "60"));
40
+ const jsonOutput = argv.includes("--json");
41
+ const keepWorkspace = argv.includes("--keep-workspace");
42
+ const specArgument = option("--spec", undefined);
43
+
44
+ const PROFILE = "compat";
45
+ const FAILURE = /did not activate|failed to import|plugin tree failed to load|Cannot find module|ERR_MODULE_NOT_FOUND/i;
46
+ const ACTIVATED = /\[dsh-deeptutor\] plugin loaded/;
47
+
48
+ function run(command, args, options = {}) {
49
+ return spawnSync(command, args, {
50
+ encoding: "utf8",
51
+ timeout: (options.timeoutSeconds ?? 180) * 1000,
52
+ killSignal: "SIGKILL",
53
+ input: "",
54
+ cwd: options.cwd ?? repoRoot,
55
+ env: { ...process.env, ...(options.env ?? {}) },
56
+ });
57
+ }
58
+
59
+ function tail(text, lines = 25) {
60
+ return String(text ?? "")
61
+ .trimEnd()
62
+ .split("\n")
63
+ .slice(-lines)
64
+ .join("\n");
65
+ }
66
+
67
+ /** The model/patch layer the boot probe needs: a real model name behind a dead port. */
68
+ function writeProbePatch(profileDir) {
69
+ mkdirSync(profileDir, { recursive: true });
70
+ writeFileSync(
71
+ join(profileDir, "cordis.patch.yml"),
72
+ [
73
+ "# generated by scripts/check-dsh-compat.mjs — real model name behind a dead endpoint",
74
+ "- id: llm-compat",
75
+ " config:",
76
+ " models:",
77
+ " - id: Qwen3.6-35B",
78
+ " contextWindow: 262144",
79
+ " maxTokens: 8192",
80
+ "- id: agent-default-model",
81
+ " config:",
82
+ " provider: deepseek-official",
83
+ " model: Qwen3.6-35B",
84
+ "- id: \"@deepseek-ai/cordis-plugin-hmr\"",
85
+ " disabled: true",
86
+ "",
87
+ ].join("\n"),
88
+ );
89
+ }
90
+
91
+ function main() {
92
+ const dshVersionProbe = run(dshBin, ["--version"], { timeoutSeconds: 30 });
93
+ if (dshVersionProbe.error !== undefined || dshVersionProbe.status !== 0) {
94
+ return {
95
+ verdict: "inconclusive",
96
+ stage: "dsh-version",
97
+ detail: `${dshBin} is not runnable: ${String(dshVersionProbe.error ?? dshVersionProbe.stderr)}`.trim(),
98
+ exitCode: 2,
99
+ };
100
+ }
101
+ const dshVersion = String(dshVersionProbe.stdout ?? "").trim().split("\n").pop() ?? "unknown";
102
+
103
+ const workspace = mkdtempSync(join(tmpdir(), "dsh-compat-"));
104
+ const dshHome = join(workspace, ".dsh");
105
+ const env = { DSH_HOME: dshHome };
106
+ const cleanup = () => {
107
+ if (!keepWorkspace) rmSync(workspace, { recursive: true, force: true });
108
+ };
109
+
110
+ try {
111
+ let spec = specArgument;
112
+ if (spec === undefined) {
113
+ // --cache: keep npm state inside the probe workspace, so a read-only or
114
+ // root-owned npm cache on the caller's machine cannot fail the probe.
115
+ const packed = run(npmBin, ["pack", "--pack-destination", workspace, "--cache", join(workspace, "npm-cache")], { timeoutSeconds: 300 });
116
+ if (packed.status !== 0) {
117
+ return { verdict: "inconclusive", stage: "pack", detail: tail(`${packed.stdout}\n${packed.stderr}`), exitCode: 2 };
118
+ }
119
+ const tarball = readdirSync(workspace).find((name) => name.endsWith(".tgz"));
120
+ if (tarball === undefined) {
121
+ return { verdict: "inconclusive", stage: "pack", detail: "npm pack produced no tarball", exitCode: 2 };
122
+ }
123
+ spec = join(workspace, tarball);
124
+ }
125
+
126
+ writeProbePatch(join(dshHome, "profiles", PROFILE));
127
+
128
+ // `-w`: dsh profiles are single-package pnpm workspaces and pnpm refuses
129
+ // to add to a workspace root without it (observed on pnpm 9 + dsh 0.2.1).
130
+ // Isolated pnpm store: a machine-wide store (store-dir in ~/.npmrc) can be
131
+ // read-only for this user, which would look like an install failure.
132
+ const install = run(
133
+ dshBin,
134
+ ["plugin", "--profile", PROFILE, "add", "-w", `--store-dir=${join(workspace, "pnpm-store")}`, spec],
135
+ { timeoutSeconds: 300, env },
136
+ );
137
+ if (install.status !== 0) {
138
+ return {
139
+ verdict: "fail",
140
+ stage: "install",
141
+ detail: tail(`${install.stdout}\n${install.stderr}`),
142
+ exitCode: 1,
143
+ };
144
+ }
145
+
146
+ const boot = run(dshBin, ["--profile", PROFILE, "ok"], {
147
+ timeoutSeconds,
148
+ env: {
149
+ ...env,
150
+ DEEPSEEK_BASE_URL: "http://127.0.0.1:9",
151
+ DEEPSEEK_API_KEY: "sk-compat-probe-000000000000000000000000",
152
+ DSH_PERMISSION_MODE: "danger-full-access",
153
+ },
154
+ cwd: dshHome,
155
+ });
156
+ const log = `${boot.stdout ?? ""}\n${boot.stderr ?? ""}`;
157
+ const timedOut = boot.error?.code === "ETIMEDOUT";
158
+ const failure = log.match(FAILURE);
159
+ if (failure !== null) {
160
+ return { verdict: "fail", stage: "boot", detail: tail(log), signature: failure[0], exitCode: 1 };
161
+ }
162
+ if (log.match(ACTIVATED) !== null) {
163
+ return { verdict: "pass", stage: "boot", detail: "plugin activation marker seen", exitCode: 0 };
164
+ }
165
+ if (timedOut) {
166
+ return { verdict: "pass", stage: "boot", detail: "boot stayed alive through the probe window without a failure signature", exitCode: 0 };
167
+ }
168
+ return { verdict: "inconclusive", stage: "boot", detail: tail(log), exitCode: 2 };
169
+ } finally {
170
+ cleanup();
171
+ }
172
+ }
173
+
174
+ const result = main();
175
+ const versionProbe = run(dshBin, ["--version"], { timeoutSeconds: 30 });
176
+ const report = {
177
+ dshBin,
178
+ dshVersion: String(versionProbe.stdout ?? "").trim().split("\n").pop() ?? "unavailable",
179
+ spec: specArgument ?? "(packed from the repository)",
180
+ ...result,
181
+ };
182
+
183
+ if (jsonOutput) {
184
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
185
+ } else {
186
+ const label = report.verdict === "pass" ? "PASS" : report.verdict === "fail" ? "FAIL" : "INCONCLUSIVE";
187
+ process.stdout.write(`[dsh-compat] ${label} · dsh ${report.dshVersion} · stage ${report.stage}\n`);
188
+ if (report.detail !== undefined && report.detail !== "") process.stdout.write(`${report.detail}\n`);
189
+ if (keepWorkspace) process.stdout.write(`[dsh-compat] workspace kept\n`);
190
+ }
191
+ process.exit(report.exitCode);