poker-mcp 1.4.0 → 1.5.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.
package/README.md CHANGED
@@ -4,13 +4,37 @@ YAML-based input file management tool for radiation-shielding calculation code P
4
4
 
5
5
  ## 📋 クイック情報
6
6
 
7
- - **バージョン**: 1.4.0
7
+ - **バージョン**: 1.5.0
8
8
  - **プロトコル**: MCP (Model Context Protocol) 1.0.0 完全準拠
9
9
  - **メインサーバー**: `src/mcp_server_stdio_v4.js`
10
10
  - **データ保存**: `~/.poker-mcp/`(`POKER_MCP_HOME`環境変数で変更可)
11
11
  - **核種データ**: `${POKER_INSTALL_PATH}/LIB/ICRP-07.NDX` を直接参照
12
12
  - **実行方式**: STDIO通信(MCPプロトコル標準)
13
13
 
14
+ ## 🆕 バージョン1.5.0の新機能
15
+
16
+ ### 🖥️ POKER GUI を閉じずに表示を切り替え
17
+ `poker_openGui` で別の入力を指定すると、起動中の POKER ウィンドウの表示が
18
+ そのまま切り替わります。従来は先に POKER を閉じる必要がありました。
19
+
20
+ 未保存の編集があるときは POKER 側で保存確認が出るため、編集内容が
21
+ 黙って失われることはありません(キャンセルすると切り替わりません)。
22
+
23
+ > **POKER 2.1.1 以降が必要です。** 2.1.0 以前では従来どおり
24
+ > `POKER_ALREADY_RUNNING` を返すので、先に POKER を閉じてください。
25
+
26
+ ### 🐛 `poker_openGui` の偽の成功報告を修正
27
+ `spawn` の成否のみで判定していたため、POKER が二重起動で弾かれて即座に
28
+ 終了しても「起動しました」と PID 付きで成功を返していました。
29
+ 起動後の生存確認を追加し、実際に表示されたことを確認してから報告します。
30
+
31
+ ### 📖 ドキュメントの大幅整理
32
+ `ADMIN_GUIDE.md` を実運用に即した内容へ全面改訂しました(699行 → 248行)。
33
+ 乱数で「正常」を出力する監視スクリプトなど、実態と乖離した記述を削除し、
34
+ 検証可能な手順のみに整理しています。
35
+
36
+ 詳細は [CHANGELOG.md](./CHANGELOG.md) を参照。
37
+
14
38
  ## 🆕 バージョン1.4.0の新機能
15
39
 
16
40
  ### ☢️ 子孫核種の自動管理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "YAML-based input file management tool for radiation shielding calculation code POKER with MCP support - Enhanced with collision detection, daughter nuclide auto-completion, and environment variable support",
5
5
  "main": "src/mcp_server_stdio_v4.js",
6
6
  "type": "module",
@@ -5,6 +5,86 @@ import path from 'path';
5
5
  import { logger } from '../../utils/logger.js';
6
6
  import { TASKS_DIR, YAML_FILE } from '../../utils/paths.js';
7
7
 
8
+ /**
9
+ * POKER.exe のファイルバージョンを取得する(Windows専用)
10
+ *
11
+ * POKER 2.1.1 以降は二重起動時に入力ファイルパスを既存インスタンスへ
12
+ * 転送するため、起動中でもそのまま spawn してよい。
13
+ * 2.1.0 以前は転送機能がなく、メッセージボックスを出して終了する。
14
+ *
15
+ * @returns {Promise<string|null>} "2.1.1.1" 形式。取得できなければ null
16
+ */
17
+ async function getPokerFileVersion(exePath) {
18
+ if (process.platform !== 'win32') return null;
19
+
20
+ return new Promise(resolve => {
21
+ try {
22
+ const ps = spawn('powershell', [
23
+ '-NoProfile', '-NonInteractive', '-Command',
24
+ `try { [System.Diagnostics.FileVersionInfo]::GetVersionInfo('${exePath.replace(/'/g, "''")}').FileVersion } catch { '' }`
25
+ ], { windowsHide: true });
26
+
27
+ let out = '';
28
+ ps.stdout.on('data', d => { out += d.toString(); });
29
+ ps.on('error', () => resolve(null));
30
+ ps.on('close', () => {
31
+ const v = out.trim();
32
+ resolve(/^\d+(\.\d+)*$/.test(v) ? v : null);
33
+ });
34
+ setTimeout(() => resolve(null), 5000);
35
+ } catch {
36
+ resolve(null);
37
+ }
38
+ });
39
+ }
40
+
41
+ /** "2.1.1.1" >= "2.1.1" を判定する */
42
+ function isVersionAtLeast(version, minimum) {
43
+ if (!version) return false;
44
+ const a = version.split('.').map(Number);
45
+ const b = minimum.split('.').map(Number);
46
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
47
+ const x = a[i] || 0, y = b[i] || 0;
48
+ if (x !== y) return x > y;
49
+ }
50
+ return true;
51
+ }
52
+
53
+ /** 入力転送に対応する最小バージョン */
54
+ const FORWARDING_MIN_VERSION = '2.1.1';
55
+
56
+ /**
57
+ * POKER.exe が既に起動しているかを調べる(Windows専用)
58
+ *
59
+ * @returns {Promise<{detected: boolean, pids: number[]}>}
60
+ */
61
+ async function isPokerRunning() {
62
+ if (process.platform !== 'win32') return { detected: false, pids: [] };
63
+
64
+ return new Promise(resolve => {
65
+ try {
66
+ const p = spawn('tasklist', ['/FI', 'IMAGENAME eq POKER.exe', '/FO', 'CSV', '/NH'], {
67
+ windowsHide: true
68
+ });
69
+ let out = '';
70
+ p.stdout.on('data', d => { out += d.toString(); });
71
+ p.on('error', () => resolve({ detected: false, pids: [] }));
72
+ p.on('close', () => {
73
+ // 該当なしの場合 tasklist は「情報: ...」等を返す
74
+ const pids = [];
75
+ for (const line of out.split(/\r?\n/)) {
76
+ const m = line.match(/^"POKER\.exe","(\d+)"/i);
77
+ if (m) pids.push(Number(m[1]));
78
+ }
79
+ resolve({ detected: pids.length > 0, pids });
80
+ });
81
+ setTimeout(() => resolve({ detected: false, pids: [] }), 3000);
82
+ } catch {
83
+ resolve({ detected: false, pids: [] });
84
+ }
85
+ });
86
+ }
87
+
8
88
  /**
9
89
  * POKER.exe のパスを解決する
10
90
  * POKER_INSTALL_PATH 環境変数(デフォルト: C:/Poker)配下の POKER.exe を参照
@@ -81,6 +161,34 @@ export function createGuiHandlers(taskManager) {
81
161
  };
82
162
  }
83
163
 
164
+ // ── Step 3.5: 多重起動の扱い ────────────────────────────────
165
+ //
166
+ // POKER 2.1.1 以降は、起動中に別の入力で spawn すると、2つ目の
167
+ // プロセスがパスを既存インスタンスへ転送して終了する(表示が切替わる)。
168
+ // 2.1.0 以前は転送機能がなく、メッセージボックスを出して終了するため、
169
+ // 呼び出し側には「成功したのに表示が変わらない」と見える。
170
+ // そこでバージョンで分岐する。
171
+ const pokerVersion = await getPokerFileVersion(pokerExePath);
172
+ const supportsForwarding = isVersionAtLeast(pokerVersion, FORWARDING_MIN_VERSION);
173
+ const running = await isPokerRunning();
174
+
175
+ if (running.detected && !supportsForwarding) {
176
+ logger.warn('poker_openGui: 起動中かつ転送非対応版', {
177
+ pids: running.pids, version: pokerVersion
178
+ });
179
+ return {
180
+ success: false,
181
+ error: 'POKER.exe は既に起動しています。この版は二重起動時の入力転送に対応していません。',
182
+ error_type: 'POKER_ALREADY_RUNNING',
183
+ running_pids: running.pids,
184
+ poker_version: pokerVersion ?? '(取得不可)',
185
+ suggestion: `別の入力を表示するには、先に起動中の POKER ウィンドウを閉じてください。`
186
+ + `\nPOKER ${FORWARDING_MIN_VERSION} 以降に更新すると、閉じずに切り替えられます。`
187
+ };
188
+ }
189
+
190
+ const forwarding = running.detected && supportsForwarding;
191
+
84
192
  // ── Step 4: POKER.exe をデタッチ起動 ─────────────────────────
85
193
  try {
86
194
  const child = spawn(pokerExePath, [resolvedYaml], {
@@ -88,6 +196,54 @@ export function createGuiHandlers(taskManager) {
88
196
  stdio: 'ignore', // MCPサーバーの stdio を汚染しない
89
197
  shell: false
90
198
  });
199
+
200
+ // 起動直後に終了していないかを確認してから成功を報告する。
201
+ // spawn の成功はプロセス生成の成功でしかなく、GUI が実際に
202
+ // 表示されたことを意味しない。
203
+ //
204
+ // ただし転送時(既存インスタンスあり・2.1.1以降)は、2つ目の
205
+ // プロセスがパスを送って即座に終了するのが正常動作である。
206
+ const died = await new Promise(resolve => {
207
+ const timer = setTimeout(() => resolve(null), 1500);
208
+ child.once('exit', code => { clearTimeout(timer); resolve(code ?? -1); });
209
+ child.once('error', () => { clearTimeout(timer); resolve(-1); });
210
+ });
211
+
212
+ if (died !== null && !(forwarding && died === 0)) {
213
+ logger.error('poker_openGui: POKER.exe が起動直後に終了', {
214
+ exitCode: died, executable: pokerExePath, inputFile: resolvedYaml, forwarding
215
+ });
216
+ return {
217
+ success: false,
218
+ error: `POKER.exe が起動直後に終了しました (exit=${died})`,
219
+ error_type: 'POKER_EXITED_IMMEDIATELY',
220
+ input_file: resolvedYaml,
221
+ suggestion: '入力ファイルが POKER で読める形式か確認してください。'
222
+ };
223
+ }
224
+
225
+ if (forwarding) {
226
+ logger.info('poker_openGui: 既存インスタンスへ入力を転送', {
227
+ executable: pokerExePath, inputFile: resolvedYaml,
228
+ targetPids: running.pids, version: pokerVersion
229
+ });
230
+ return {
231
+ success: true,
232
+ message: '起動中の POKER に入力を転送しました。',
233
+ forwarded: true,
234
+ launched: {
235
+ executable: pokerExePath,
236
+ input_file: resolvedYaml,
237
+ target_pids: running.pids
238
+ },
239
+ poker_version: pokerVersion,
240
+ note: '転送の送信までを確認しています。読み込みに失敗した場合は'
241
+ + ' POKER のウィンドウにエラーが表示されます。'
242
+ + '\n未保存の編集があるときは POKER 側で保存確認が出ます'
243
+ + '(キャンセルすると切り替わりません)。'
244
+ };
245
+ }
246
+
91
247
  child.unref(); // MCPサーバーが POKER.exe の終了を待たない
92
248
 
93
249
  logger.info('poker_openGui: POKER.exe 起動成功', {