dsh-image-gen 0.8.5 → 0.8.6

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.en.md CHANGED
@@ -401,8 +401,13 @@ pnpm run typecheck
401
401
  pnpm test
402
402
  pnpm run build
403
403
  pnpm run pack:check
404
+ pnpm run test:package
404
405
  ```
405
406
 
407
+ Saving settings on modern DSH requires the actually loaded `@deepseek-ai/schemastery >=3.18.4 <4`. The card distinguishes loading, missing host forms, remote connections that cannot persist, and incompatible schemas. Legacy DSH settings interfaces are retained. Check the dependencies resolved in the plugin profile when updating, as well as those in the source checkout.
408
+
409
+ `test:package` requires network access. It installs the current build's tarball and pinned published DSH 0.2 settings services in a temporary directory, then uses real React rendering to verify card state, the bundled save handler, profile file persistence, a fresh form, host restart, and actionable refusal with old schemastery. Image APIs, browser DOM, and RPC transport are outside this test. CI runs it on Windows and Linux.
410
+
406
411
  Feedback is welcome through [Issues](https://github.com/shanliuling/dsh-image-gen/issues). Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a Pull Request.
407
412
 
408
413
  ## License
package/README.md CHANGED
@@ -401,8 +401,13 @@ pnpm run typecheck
401
401
  pnpm test
402
402
  pnpm run build
403
403
  pnpm run pack:check
404
+ pnpm run test:package
404
405
  ```
405
406
 
407
+ 新 DSH 的设置保存需要实际加载的 `@deepseek-ai/schemastery >=3.18.4 <4`。设置卡会区分加载中、宿主未提供设置、远程连接不可保存及依赖不兼容;旧 DSH 的设置接口仍保留。更新依赖时应检查插件所在 profile 的实际依赖,而不只检查源码目录。
408
+
409
+ `test:package` 需要网络,会在临时目录安装当前构建的插件 tarball 和指定版本的 DSH 0.2 设置服务,通过真实 React 渲染验证卡片状态和保存处理、配置文件写入、重新打开表单和宿主重启后的读取,并验证旧 schemastery 的明确拒绝提示。图片 API、浏览器 DOM 和 RPC 传输不属于此测试;CI 同时在 Windows 和 Linux 执行。
410
+
406
411
  欢迎通过 [Issues](https://github.com/shanliuling/dsh-image-gen/issues) 反馈问题,或阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 后提交 Pull Request。
407
412
 
408
413
  ## License
package/lib/client.js CHANGED
@@ -87,6 +87,8 @@ window.__ModuleLoader__.load({
87
87
  const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
88
88
  /** Browser route the settings card probes provider connectivity through. */
89
89
  const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
90
+ /** Non-secret settings capability probe, available even when the host cannot serve a form. */
91
+ const SETTINGS_HEALTH_ROUTE = "/plugins/dsh-image-gen/settings-health";
90
92
  /** Browser route the workbench infinite canvas pushes live state through. */
91
93
  const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
92
94
  /** Upload a selected canvas original to the host attachment store. */
@@ -246,6 +248,38 @@ window.__ModuleLoader__.load({
246
248
  "google-sub": ""
247
249
  };
248
250
  //#endregion
251
+ //#region lib/types/client/settings-state.js
252
+ /** Preserve modern form states while accepting legacy scopes without status/mode. */
253
+ function settingsState(snapshot, health) {
254
+ if (snapshot.mode === "memory") return "remote";
255
+ if (health?.settings === "live" && !health.liveSchema) return "incompatible";
256
+ if (snapshot.status === "loading") return "loading";
257
+ if (snapshot.status === "unavailable") return "unavailable";
258
+ return snapshot.writable ? "ready" : "readOnly";
259
+ }
260
+ /** A failed probe cannot identify the cause; keep the host's own form state. */
261
+ async function fetchSettingsHealth(signal) {
262
+ try {
263
+ const response = await fetch(SETTINGS_HEALTH_ROUTE, {
264
+ cache: "no-store",
265
+ signal
266
+ });
267
+ if (!response.ok) return void 0;
268
+ const health = await response.json();
269
+ if (health === null || typeof health.liveSchema !== "boolean" || ![
270
+ "live",
271
+ "legacy",
272
+ "unavailable"
273
+ ].includes(health.settings ?? "")) return void 0;
274
+ return {
275
+ settings: health.settings,
276
+ liveSchema: health.liveSchema
277
+ };
278
+ } catch {
279
+ return;
280
+ }
281
+ }
282
+ //#endregion
249
283
  //#region lib/types/comfyui-workflow.js
250
284
  /** Pure ComfyUI API-workflow validation and placeholder injection. */
251
285
  const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
@@ -124783,7 +124817,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124783
124817
  `;
124784
124818
  //#endregion
124785
124819
  //#region lib/types/client/index.js
124786
- console.info(`[dsh-image-gen] canvas bundle 2026-09-30T06:41:54.565Z`);
124820
+ console.info(`[dsh-image-gen] canvas bundle 2026-10-08T04:58:18.947Z`);
124787
124821
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
124788
124822
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
124789
124823
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -124795,6 +124829,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124795
124829
  defaultProvider: "默认 Provider",
124796
124830
  defaultProviderHint: "Agent 生图默认使用;Studio 与工具调用可临时指定其他 Provider。",
124797
124831
  settingsReadOnly: "设置由配置文件提供,只读;如需修改请编辑对应的配置来源。",
124832
+ settingsLoading: "正在加载图像生成设置,请稍候。",
124833
+ settingsRemote: "当前连接不支持保存到宿主,请在 DSH 本机页面修改设置。",
124834
+ settingsUnavailable: "宿主尚未提供图像生成设置,请检查插件是否已启用及其加载日志。",
124835
+ settingsIncompatible: "当前加载的设置库不支持保存。请将插件所在环境的 @deepseek-ai/schemastery 更新到 3.18.4 或兼容的 3.x 版本,并重启 DSH。",
124798
124836
  providerGoogle: "Google Gemini",
124799
124837
  providerOpenAI: "OpenAI",
124800
124838
  providerOpenAICompat: "OpenAI 兼容(中转站)",
@@ -124944,6 +124982,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
124944
124982
  defaultProvider: "Default provider",
124945
124983
  defaultProviderHint: "Used by the Agent by default; the Studio and tool calls can switch per call.",
124946
124984
  settingsReadOnly: "Settings come from a profile file and are read-only; edit that source to change them.",
124985
+ settingsLoading: "Settings are loading; please wait.",
124986
+ settingsRemote: "This connection cannot save settings to the host. Edit them from the local DSH page.",
124987
+ settingsUnavailable: "The host has not provided image generation settings. Check that the plugin is enabled and review its load logs.",
124988
+ settingsIncompatible: "The loaded settings library cannot save these settings. Update @deepseek-ai/schemastery in the plugin environment to 3.18.4 or a compatible 3.x version, then restart DSH.",
124947
124989
  providerGoogle: "Google Gemini",
124948
124990
  providerOpenAI: "OpenAI",
124949
124991
  providerOpenAICompat: "OpenAI-compatible (relay)",
@@ -125397,7 +125439,8 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125397
125439
  return {
125398
125440
  getSnapshot: () => ({
125399
125441
  value: void 0,
125400
- writable: false
125442
+ writable: false,
125443
+ status: "unavailable"
125401
125444
  }),
125402
125445
  subscribe: () => () => {},
125403
125446
  set: async () => false
@@ -125737,6 +125780,9 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125737
125780
  function ImageGenerationSettingsCard(props) {
125738
125781
  const [open, setOpen] = (0, react.useState)(false);
125739
125782
  const [snapshot, setSnapshot] = (0, react.useState)(() => props.scope.getSnapshot());
125783
+ const [health, setHealth] = (0, react.useState)();
125784
+ const state = settingsState(snapshot, health);
125785
+ const settingsWritable = state === "ready";
125740
125786
  const [lang, setLang] = (0, react.useState)(() => props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
125741
125787
  const [defaultProvider, setDefaultProvider] = (0, react.useState)(() => props.scope.getSnapshot().value?.provider ?? "google");
125742
125788
  const [providerMessage, setProviderMessage] = (0, react.useState)("");
@@ -125758,9 +125804,28 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125758
125804
  const [subTick, setSubTick] = (0, react.useState)(0);
125759
125805
  const [subBusy, setSubBusy] = (0, react.useState)({});
125760
125806
  const [subPending, setSubPending] = (0, react.useState)([]);
125761
- (0, react.useEffect)(() => props.scope.subscribe(() => {
125762
- setSnapshot(props.scope.getSnapshot());
125763
- }), [props.scope]);
125807
+ (0, react.useEffect)(() => {
125808
+ const sync = () => {
125809
+ setSnapshot(props.scope.getSnapshot());
125810
+ };
125811
+ const unsubscribe = props.scope.subscribe(sync);
125812
+ sync();
125813
+ return unsubscribe;
125814
+ }, [props.scope]);
125815
+ (0, react.useEffect)(() => {
125816
+ if (!open || snapshot.mode === "memory") return void 0;
125817
+ const controller = new AbortController();
125818
+ fetchSettingsHealth(controller.signal).then((value) => {
125819
+ if (!controller.signal.aborted) setHealth(value);
125820
+ });
125821
+ return () => {
125822
+ controller.abort();
125823
+ };
125824
+ }, [
125825
+ open,
125826
+ snapshot.status,
125827
+ snapshot.mode
125828
+ ]);
125764
125829
  (0, react.useEffect)(() => {
125765
125830
  return props.locale?.subscribe?.(() => {
125766
125831
  setLang(props.locale?.getSnapshot?.()?.active?.startsWith("en") ? "en" : "zh");
@@ -125912,7 +125977,17 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
125912
125977
  if (params) for (const [k, v] of Object.entries(params)) text = text.replace(`{${k}}`, v);
125913
125978
  return text;
125914
125979
  };
125915
- const saveSetting = (field, value) => writeSetting(props.scope, field, value, t("settingsWriteRejected"));
125980
+ const settingsMessage = state === "ready" ? void 0 : t({
125981
+ loading: "settingsLoading",
125982
+ remote: "settingsRemote",
125983
+ unavailable: "settingsUnavailable",
125984
+ readOnly: "settingsReadOnly",
125985
+ incompatible: "settingsIncompatible"
125986
+ }[state]);
125987
+ const saveSetting = async (field, value) => {
125988
+ if (settingsMessage !== void 0) throw new Error(settingsMessage);
125989
+ await writeSetting(props.scope, field, value, t("settingsWriteRejected"));
125990
+ };
125916
125991
  const providerLabels = {
125917
125992
  google: t("providerGoogle"),
125918
125993
  openai: t("providerOpenAI"),
@@ -126445,7 +126520,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126445
126520
  updateRow(provider, { keyInput: event.target.value });
126446
126521
  },
126447
126522
  placeholder: row.keyStatus === "configured" ? t("apiKeyPlaceholder") : "",
126448
- disabled: !snapshot.writable || keyReadOnly || keyUnavailable
126523
+ disabled: !settingsWritable || keyReadOnly || keyUnavailable
126449
126524
  }),
126450
126525
  (0, react_jsx_runtime.jsx)("span", {
126451
126526
  className: "dsh-ig-hint",
@@ -126470,7 +126545,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126470
126545
  updateRow(provider, { baseURL: event.target.value });
126471
126546
  },
126472
126547
  required: true,
126473
- disabled: !snapshot.writable
126548
+ disabled: !settingsWritable
126474
126549
  }), provider !== "openai-compat" ? (0, react_jsx_runtime.jsx)("button", {
126475
126550
  type: "button",
126476
126551
  className: "dsh-ig-btn-reset",
@@ -126478,7 +126553,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126478
126553
  onClick: () => {
126479
126554
  updateRow(provider, { baseURL: DEFAULT_BASE_URLS[provider] });
126480
126555
  },
126481
- disabled: !snapshot.writable,
126556
+ disabled: !settingsWritable,
126482
126557
  children: t("reset")
126483
126558
  }) : null]
126484
126559
  }),
@@ -126506,7 +126581,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126506
126581
  },
126507
126582
  list: `dsh-ig-${provider}-model-options`,
126508
126583
  required: provider !== "openai-compat",
126509
- disabled: !snapshot.writable
126584
+ disabled: !settingsWritable
126510
126585
  }),
126511
126586
  (0, react_jsx_runtime.jsx)("datalist", {
126512
126587
  id: `dsh-ig-${provider}-model-options`,
@@ -126515,7 +126590,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126515
126590
  (0, react_jsx_runtime.jsx)("button", {
126516
126591
  type: "button",
126517
126592
  className: "dsh-ig-btn-secondary",
126518
- disabled: row.fetchingModels || row.saving || !snapshot.writable,
126593
+ disabled: row.fetchingModels || row.saving || !settingsWritable,
126519
126594
  onClick: () => {
126520
126595
  fetchProviderModels(provider);
126521
126596
  },
@@ -126529,7 +126604,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126529
126604
  updateRow(provider, { model: event.target.value });
126530
126605
  },
126531
126606
  required: true,
126532
- disabled: !snapshot.writable
126607
+ disabled: !settingsWritable
126533
126608
  }),
126534
126609
  modelPullSupported(provider) ? row.modelFetchMessage.length > 0 ? (0, react_jsx_runtime.jsx)("span", {
126535
126610
  className: `dsh-ig-hint${row.modelFetchIsError ? " dsh-ig-hint-error" : ""}`,
@@ -126554,7 +126629,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126554
126629
  onChange: (event) => {
126555
126630
  updateRow(provider, { editFormat: parseEditFormat(event.target.value) });
126556
126631
  },
126557
- disabled: !snapshot.writable,
126632
+ disabled: !settingsWritable,
126558
126633
  children: [
126559
126634
  (0, react_jsx_runtime.jsx)("option", {
126560
126635
  value: "multipart",
@@ -126590,7 +126665,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126590
126665
  onChange: (event) => {
126591
126666
  updateRow(provider, { outputFormat: event.target.value === "png" ? "png" : "jpeg" });
126592
126667
  },
126593
- disabled: !snapshot.writable,
126668
+ disabled: !settingsWritable,
126594
126669
  children: [(0, react_jsx_runtime.jsx)("option", {
126595
126670
  value: "jpeg",
126596
126671
  children: t("arkOutputFormatJpeg")
@@ -126618,7 +126693,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126618
126693
  onChange: (event) => {
126619
126694
  updateRow(provider, { watermark: event.target.value === "on" });
126620
126695
  },
126621
- disabled: !snapshot.writable,
126696
+ disabled: !settingsWritable,
126622
126697
  children: [(0, react_jsx_runtime.jsx)("option", {
126623
126698
  value: "on",
126624
126699
  children: t("arkWatermarkOn")
@@ -126646,7 +126721,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126646
126721
  onChange: (event) => {
126647
126722
  updateRow(provider, { background: event.target.value === "transparent" ? "transparent" : "opaque" });
126648
126723
  },
126649
- disabled: !snapshot.writable,
126724
+ disabled: !settingsWritable,
126650
126725
  children: [(0, react_jsx_runtime.jsx)("option", {
126651
126726
  value: "opaque",
126652
126727
  children: t("arkBackgroundOpaque")
@@ -126677,7 +126752,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126677
126752
  },
126678
126753
  placeholder: t("editExtraPlaceholder"),
126679
126754
  rows: 2,
126680
- disabled: !snapshot.writable
126755
+ disabled: !settingsWritable
126681
126756
  }),
126682
126757
  (0, react_jsx_runtime.jsx)("span", {
126683
126758
  className: "dsh-ig-hint",
@@ -126715,7 +126790,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126715
126790
  (0, react_jsx_runtime.jsx)("button", {
126716
126791
  className: "dsh-ig-save",
126717
126792
  type: "submit",
126718
- disabled: row.saving || row.testing || row.fetchingModels || !snapshot.writable,
126793
+ disabled: row.saving || row.testing || row.fetchingModels || !settingsWritable,
126719
126794
  children: row.saving ? t("saving") : t("save")
126720
126795
  })
126721
126796
  ]
@@ -126752,7 +126827,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126752
126827
  updateRow("comfyui", { baseURL: event.target.value });
126753
126828
  },
126754
126829
  required: true,
126755
- disabled: !snapshot.writable
126830
+ disabled: !settingsWritable
126756
126831
  }), (0, react_jsx_runtime.jsx)("button", {
126757
126832
  type: "button",
126758
126833
  className: "dsh-ig-btn-reset",
@@ -126760,7 +126835,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126760
126835
  onClick: () => {
126761
126836
  updateRow("comfyui", { baseURL: DEFAULT_BASE_URLS.comfyui });
126762
126837
  },
126763
- disabled: !snapshot.writable,
126838
+ disabled: !settingsWritable,
126764
126839
  children: t("reset")
126765
126840
  })]
126766
126841
  }),
@@ -126866,7 +126941,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126866
126941
  updateRow("comfyui", { timeoutSeconds: Number(event.target.value) });
126867
126942
  },
126868
126943
  required: true,
126869
- disabled: !snapshot.writable
126944
+ disabled: !settingsWritable
126870
126945
  }),
126871
126946
  (0, react_jsx_runtime.jsx)("span", {
126872
126947
  className: "dsh-ig-hint",
@@ -126893,7 +126968,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
126893
126968
  }), (0, react_jsx_runtime.jsx)("button", {
126894
126969
  className: "dsh-ig-save",
126895
126970
  type: "submit",
126896
- disabled: row.saving || !snapshot.writable || row.workflows.length === 0,
126971
+ disabled: row.saving || !settingsWritable || row.workflows.length === 0,
126897
126972
  children: row.saving ? t("saving") : t("save")
126898
126973
  })]
126899
126974
  })]
@@ -127014,7 +127089,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127014
127089
  }), (0, react_jsx_runtime.jsx)("button", {
127015
127090
  className: "dsh-ig-save",
127016
127091
  type: "submit",
127017
- disabled: row.saving || !snapshot.writable,
127092
+ disabled: row.saving || !settingsWritable,
127018
127093
  children: row.saving ? t("saving") : t("save")
127019
127094
  })]
127020
127095
  })]
@@ -127103,10 +127178,10 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127103
127178
  }), open ? (0, react_jsx_runtime.jsxs)("div", {
127104
127179
  className: "dsh-ig-body",
127105
127180
  children: [
127106
- !snapshot.writable ? (0, react_jsx_runtime.jsx)("p", {
127181
+ settingsMessage !== void 0 ? (0, react_jsx_runtime.jsx)("p", {
127107
127182
  className: "dsh-ig-status dsh-ig-status-readonly",
127108
127183
  role: "note",
127109
- children: t("settingsReadOnly")
127184
+ children: settingsMessage
127110
127185
  }) : null,
127111
127186
  (0, react_jsx_runtime.jsxs)("div", {
127112
127187
  className: "dsh-ig-field",
@@ -127128,7 +127203,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127128
127203
  onChange: () => {
127129
127204
  selectDefaultProvider(provider);
127130
127205
  },
127131
- disabled: !snapshot.writable
127206
+ disabled: !settingsWritable
127132
127207
  }), providerLabels[provider]]
127133
127208
  }, provider))
127134
127209
  }),
@@ -127164,7 +127239,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127164
127239
  onChange: (event) => {
127165
127240
  toggleSaveToWorkspace(event.target.checked);
127166
127241
  },
127167
- disabled: !snapshot.writable
127242
+ disabled: !settingsWritable
127168
127243
  }), (0, react_jsx_runtime.jsx)("span", {
127169
127244
  className: "dsh-ig-label",
127170
127245
  children: t("saveToWorkspace")
@@ -127194,7 +127269,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127194
127269
  if (event.key === "Enter") commitWorkspaceFolder();
127195
127270
  },
127196
127271
  placeholder: "dsh-image-gen",
127197
- disabled: !snapshot.writable
127272
+ disabled: !settingsWritable
127198
127273
  }),
127199
127274
  (0, react_jsx_runtime.jsx)("span", {
127200
127275
  className: "dsh-ig-hint",
@@ -127225,7 +127300,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
127225
127300
  onChange: (event) => {
127226
127301
  toggleShowPill(event.target.checked);
127227
127302
  },
127228
- disabled: !snapshot.writable
127303
+ disabled: !settingsWritable
127229
127304
  }), (0, react_jsx_runtime.jsx)("span", {
127230
127305
  className: "dsh-ig-label",
127231
127306
  children: t("showPill")
package/lib/index.js CHANGED
@@ -5596,6 +5596,8 @@ const INSPIRATION_ROUTE = "/plugins/dsh-image-gen/inspiration";
5596
5596
  const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
5597
5597
  /** Browser route the settings card probes provider connectivity through. */
5598
5598
  const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
5599
+ /** Non-secret settings capability probe, available even when the host cannot serve a form. */
5600
+ const SETTINGS_HEALTH_ROUTE = "/plugins/dsh-image-gen/settings-health";
5599
5601
  /** Browser route the workbench infinite canvas pushes live state through. */
5600
5602
  const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
5601
5603
  /** Upload a selected canvas original to the host attachment store. */
@@ -28245,6 +28247,27 @@ function jsonError(res, status, code) {
28245
28247
  json(res, status, { error: code });
28246
28248
  }
28247
28249
  //#endregion
28250
+ //#region lib/types/settings-health.js
28251
+ function describeSettingsHealth(settings, schema) {
28252
+ const service = settings;
28253
+ const fields = Object.values(schema.dict ?? {});
28254
+ return {
28255
+ settings: typeof service?.installSection === "function" ? "legacy" : typeof service?.describe === "function" ? "live" : "unavailable",
28256
+ liveSchema: fields.length > 0 && fields.every((field) => field.meta.volatile === true)
28257
+ };
28258
+ }
28259
+ function serveSettingsHealth(req, res, health) {
28260
+ res.setHeader("cache-control", "no-store");
28261
+ if (req.method !== "GET") {
28262
+ res.statusCode = 405;
28263
+ res.setHeader("allow", "GET");
28264
+ res.end();
28265
+ return;
28266
+ }
28267
+ res.setHeader("content-type", "application/json; charset=utf-8");
28268
+ res.end(JSON.stringify(health));
28269
+ }
28270
+ //#endregion
28248
28271
  //#region lib/types/index.js
28249
28272
  const name = "dsh-image-gen";
28250
28273
  const inject = [
@@ -28270,12 +28293,17 @@ function apply(ctx, config = {}) {
28270
28293
  const canvasMirror = new CanvasMirror();
28271
28294
  const subscriptionManager = new SubscriptionManager(ctx);
28272
28295
  registerSubscriptionRoutes(ctx, subscriptionManager);
28273
- installImageSettings(ctx, current(), {
28296
+ const settingsHealth = installImageSettings(ctx, current(), {
28274
28297
  setSource: (source) => {
28275
28298
  current = () => migrateOpenAICompatConfig(plainConfig(source()));
28276
28299
  },
28277
28300
  onChange: () => {}
28278
28301
  });
28302
+ ctx.effect(() => ctx.webServer.register({
28303
+ kind: "exact",
28304
+ path: SETTINGS_HEALTH_ROUTE,
28305
+ handler: (req, res) => serveSettingsHealth(req, res, settingsHealth())
28306
+ }), "dsh-image-gen: settings health");
28279
28307
  ctx.effect(() => ctx.webServer.register({
28280
28308
  kind: "exact",
28281
28309
  path: IMAGE_ROUTE,
@@ -29240,14 +29268,22 @@ function imagePresentation(result) {
29240
29268
  * on the service, so the namespace registers through it; DSH 0.1.7 replaced it
29241
29269
  * with schema-derived forms served by the loader itself (keyed by entry id),
29242
29270
  * so its service carries no installer and a host without any settings service
29243
- * simply never runs the callback — both stay silent instead of warning.
29271
+ * simply never runs the callback. A modern host with an unmarked schema is
29272
+ * incompatible and must be diagnosed, while old hosts keep their installer.
29244
29273
  */
29245
29274
  function installImageSettings(ctx, config, hooks) {
29275
+ let service;
29246
29276
  ctx.inject(["settings"], (settingsCtx) => {
29247
29277
  const settings = settingsCtx.settings;
29248
- if (typeof settings.installSection !== "function") return;
29249
- settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
29278
+ service = settings;
29279
+ const health = describeSettingsHealth(settings, Config);
29280
+ if (health.settings === "live" && !health.liveSchema) ctx.logger.warn("dsh-image-gen: the loaded schema lacks volatile fields required by this DSH settings service. Install @deepseek-ai/schemastery >=3.18.4 <4 in the plugin profile and check which copy the plugin resolves; settings cannot be saved with this schema.");
29281
+ if (typeof settings.installSection === "function") settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
29282
+ return () => {
29283
+ service = void 0;
29284
+ };
29250
29285
  });
29286
+ return () => describeSettingsHealth(service, Config);
29251
29287
  }
29252
29288
  //#endregion
29253
29289
  export { CANVAS_STATE_ROUTE, Config, DELETE_ROUTE, IMAGE_ROUTE, IMPORT_ROUTE, INSPIRATION_ROUTE, SAVE_WORKSPACE_ROUTE, STUDIO_ROUTE, TEST_CONNECTION_ROUTE, apply, imageAttachmentFromMeta, inject, name };
@@ -1,9 +1,7 @@
1
1
  import './tl/css-supports-shim.js';
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
- export interface SettingsScopeSnapshot<T> {
4
- value: T | undefined;
5
- writable: boolean;
6
- }
3
+ import { type SettingsScopeSnapshot } from './settings-state.js';
4
+ export type { SettingsScopeSnapshot } from './settings-state.js';
7
5
  export interface SettingsScope<T> {
8
6
  getSnapshot(): SettingsScopeSnapshot<T>;
9
7
  subscribe(listener: () => void): () => void;
@@ -116,4 +114,3 @@ export declare function GeneratedImageCard(props: ImageCardProps): import("react
116
114
  export declare function GeneratedImagesCard(props: ImageCardProps): import("react").JSX.Element;
117
115
  /** Render modern image artifacts as final conversation output instead of Tool process content. */
118
116
  export declare function PromotedImageResultNode(props: ImageResultNodeProps): import("react").JSX.Element;
119
- export {};
@@ -0,0 +1,12 @@
1
+ /** Preserve modern form states while accepting legacy scopes without status/mode. */
2
+ import { type SettingsHealth } from '../shared.js';
3
+ export interface SettingsScopeSnapshot<T> {
4
+ value: T | undefined;
5
+ writable: boolean;
6
+ status?: 'loading' | 'ready' | 'unavailable';
7
+ mode?: 'host' | 'memory';
8
+ }
9
+ export type SettingsState = 'ready' | 'loading' | 'remote' | 'unavailable' | 'readOnly' | 'incompatible';
10
+ export declare function settingsState(snapshot: SettingsScopeSnapshot<unknown>, health?: SettingsHealth): SettingsState;
11
+ /** A failed probe cannot identify the cause; keep the host's own form state. */
12
+ export declare function fetchSettingsHealth(signal: AbortSignal): Promise<SettingsHealth | undefined>;
@@ -0,0 +1,6 @@
1
+ /** Diagnose the actual host service and schema without reading any settings values. */
2
+ import type z from '@deepseek-ai/schemastery';
3
+ import type { IncomingMessage, ServerResponse } from 'node:http';
4
+ import type { SettingsHealth } from './shared.js';
5
+ export declare function describeSettingsHealth(settings: unknown, schema: z): SettingsHealth;
6
+ export declare function serveSettingsHealth(req: IncomingMessage, res: ServerResponse, health: SettingsHealth): void;
@@ -14,6 +14,12 @@ export declare const INSPIRATION_ROUTE = "/plugins/dsh-image-gen/inspiration";
14
14
  export declare const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
15
15
  /** Browser route the settings card probes provider connectivity through. */
16
16
  export declare const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
17
+ /** Non-secret settings capability probe, available even when the host cannot serve a form. */
18
+ export declare const SETTINGS_HEALTH_ROUTE = "/plugins/dsh-image-gen/settings-health";
19
+ export interface SettingsHealth {
20
+ settings: 'unavailable' | 'legacy' | 'live';
21
+ liveSchema: boolean;
22
+ }
17
23
  /** Browser route the workbench infinite canvas pushes live state through. */
18
24
  export declare const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
19
25
  /** Upload a selected canvas original to the host attachment store. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-image-gen",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "Bring ChatGPT-like image generation to DeepSeek Harness — Gemini, OpenAI, Seedream, DashScope, local ComfyUI & more.",
5
5
  "type": "module",
6
6
  "author": "shanliuling",
@@ -60,6 +60,7 @@
60
60
  "build": "tsc && tsdown",
61
61
  "prepare": "pnpm run build",
62
62
  "test": "vitest run",
63
+ "test:package": "node scripts/check-package.mjs",
63
64
  "pack:check": "pnpm pack --dry-run"
64
65
  },
65
66
  "engines": {
@@ -90,7 +91,7 @@
90
91
  ],
91
92
  "peerDependencies": {
92
93
  "@deepseek-ai/cordis": ">=4.0.0 <5",
93
- "@deepseek-ai/schemastery": ">=3.18.0 <4",
94
+ "@deepseek-ai/schemastery": ">=3.18.4 <4",
94
95
  "react": ">=18.2.0 <19"
95
96
  },
96
97
  "peerDependenciesMeta": {