設計ノート
Harness自体のプラグイン化について
結論: 大部分は可能で、実行時の中核はすでにDriver側に吸収済み。残る外側は「構造上プロセスの外にしか置けないブートストラップ」だけ。
つまり「プラグイン化できないもの」の一覧が、そのまま
Harnessの定義(=最小のHarness) になる。
現在のHarnessが含む LivePage・MCPアダプタ・ensure/reloadの
オーケストレーション等は不可還元ではなく、その不可還元な核の
周りに集まった便益層である(/api/*・/mcp越しに任意言語で
再実装できる)。Harnessは「消せない外側+便利だから置いた外側」の
2層でできている、と読むと正確。
すでにプラグイン側にあるもの
コマンドdispatch・ファイルJSONL・REST・MCP・レジストリ・stdio shimは
全て qgis_live_driver 内にあるため、輸送層としての外側スクリプト
(live-drive.mjs/qgis-mcp.mjs相当)は原理的には不要。
さらに以下はnamedコマンドとしてDriverに実装済み:
| コマンド | 吸収した元の機能 |
|---|---|
reload_plugin |
qgis-session.mjs がクライアント側で生成していたreloadコード(sys.modules掃除+reloadPlugin+open_dialog)。自己リロードも可 |
open_plugin |
startup.py+WEBENGINE_AUTOOPEN によるダイアログ自動オープン(コマンド呼び出し型に置換) |
bridge_call |
JS経由 BridgeClient.callJson のbridge呼出しをプロセス内直接呼出しに。WebChannelのwedge/飢餓の影響を受けない |
status |
pyeval("1") による間接的な生存probeを正式コマンド化 |
plugin_id ルーティング |
eval/pyeval/screenshot/devreload を他プラグインのドライバへ転送。Driverのlive dir/HTTPポート1本が全プラグインの統一制御点になる |
さらに内側へ移せるもの
| 機能 | 方法 |
|---|---|
| 他プラグインのdeploy(ファイル配置) | pyevalでファイル配置・junction・QGIS4.iniまで完結可能(reload側は reload_plugin 化済み)。ただし現行の規約ではdeploy系の書込みはUI操作のみと定めているため、導入する場合もその制約を引き継ぐ |
| LivePage相当のクライアント | /api/*を叩くだけのstdlibクライアントに置換可能(mcp_stdio.pyがほぼそれ) |
QTWEBENGINE_CHROMIUM_FLAGSの設定 |
classFactory時点ではQWebEngine未初期化のためプラグイン内でos.environ設定できる可能性(他が先に初期化すると破綻。要検証) |
WEBENGINE_LIVE_DIR のゲート |
QgsSettingsや ~/.qgis-live/ の設定ファイルで有効化判定すればenv無し起動QGISへもアタッチ可能(任意コード実行の常時有効化になるためopt-in必須) |
構造上外に残るもの(不可逆)
| 残るもの | 理由 | 最小化策 |
|---|---|---|
| プラグイン自身の初回インストール | 自己deployの鶏卵問題 | 通常のプラグインzip/プラグインマネージャ配布 |
| QGISプロセスの起動 | プラグインは起動済みQGIS内でしか動かない | envを永続化するかlive dir既定パス化すれば「普通に起動」で済む |
| クラッシュ監視・再起動 | ホスト死亡時にドライバも死ぬ(自己監視不可) | クライアント側タイムアウト+レジストリstale検出で代替 |
| L1(Playwright)/pytest | QGIS無しでの検証が目的なので外が正しい | そのまま(Nodeは任意依存に退化) |
QGIS_PLUGINPATHをrepoの qgis-plugin/ に恒久的に向ければ
deploy自体も不要になる可能性がある(QGIS 4.0.1での動作は要検証)。
決定: ハイブリッド構造を固定方針とする
上記の分析から「全部プラグインにできる」わけだが、実運用の安定性を 考えて「プラグイン内の薄いプリミティブ + 外側のオーケストレーション」の ハイブリッド構造を固定方針とした。
- プラグイン内: pyeval / status / open_plugin / reload_plugin /
screenshot / bridge_call 等の即応答の実行プリミティブのみ。
各コマンドはGUIスレッドで即時実行し、例外は
{error: ...}として返す - 外側: 順序制御・待機・リトライ・タイムアウト・結果集約。
REST(
/api/*)/MCP(/mcp)/ファイルJSONL越しに 任意言語の薄いクライアントで行う
理由(いずれも実測): dispatchはGUIスレッドで動くため、待機や多段処理を インプロセスに入れるとQGIS UIをブロックしてドライバ自身も無応答になる (pyeval内のdriver.submit自己デッドロック等)。加えて、検証側の障害で QGISを巻き込まないクラッシュ分離、実行中コマンドを打ち切る手段が クライアント側の放棄しかない、という理由から、タイムアウトと リトライは構造上外側でしか制御できない。
エラー処理の契約もこの境界で固定する: プリミティブは失敗を
{error} で返して例外を漏らさない、境界のHTTP/接続エラーは
クライアント側で例外化する、オーケストレーションは個別失敗を集約して
続行する(test:liveのcheck()/WARNパターン)。新コマンドは
「即応答・error返却・GUIブロック禁止」を満たすこととし、長時間処理は
状態付きコマンド(start→poll→result)に分割して待機を外側に置く。
留保
- GUIスレッド同期実行のため、モーダルダイアログやGUIブロック中は ドライバも沈黙する。プラグイン化で悪化も改善もしない固有の制約
eval/devreload/UIスクショは対象プラグインのWebEngine viewが必要。 DriverだけではJS側を駆動できず「器ごとのlive_driver」という2層構造は残る (ただしplugin_idルーティングでDriver経由の一窓操作は可能)pyeval=任意コード実行なので、配布可能なプラグインにするなら envゲートを設定UI等に置き換える配慮が必要