dsh-comfyui 0.5.1 → 0.5.3

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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { isAbsolute, join } from 'node:path';
3
- import { Config } from './config.js';
3
+ import { Config, resolveConfig } from './config.js';
4
4
  import { ComfyUIClient, CLIENT_ID } from './comfyui.js';
5
5
  import { ComfyUIStore } from './store.js';
6
6
  import { QueueTracker } from './queue.js';
7
- import { convertGraphToApi } from './convert.js';
7
+ import { convertGraphToApi, flattenDynamicCombos } from './convert.js';
8
8
  import { analyzeGraph } from './analyze.js';
9
9
  import { ProgressTracker } from './progress.js';
10
10
  import { COMFYUI_SKILL } from './skill.js';
@@ -66,23 +66,10 @@ function defaultDataDir() {
66
66
  * (defaults applied), then hands the resolved object to apply.
67
67
  */
68
68
  export async function apply(ctx, entryConfig) {
69
- const resolved = {
70
- baseUrl: entryConfig.baseUrl ?? 'http://127.0.0.1:8188',
71
- apiKeyEnv: entryConfig.apiKeyEnv ?? 'COMFYUI_API_KEY',
72
- connectTimeoutMs: entryConfig.connectTimeoutMs ?? 10_000,
73
- timeoutMs: entryConfig.timeoutMs ?? 900_000,
74
- pollIntervalMs: entryConfig.pollIntervalMs ?? 1_000,
75
- maxMediaItems: entryConfig.maxMediaItems ?? 12,
76
- maxMediaBytes: entryConfig.maxMediaBytes ?? 64 * 1024 * 1024,
77
- dataDir: entryConfig.dataDir !== undefined && entryConfig.dataDir !== '' ? entryConfig.dataDir : defaultDataDir(),
78
- maxAssets: entryConfig.maxAssets ?? 200,
79
- skillsDir: entryConfig.skillsDir ?? '',
80
- mediaHost: entryConfig.mediaHost ?? '',
81
- outputDir: entryConfig.outputDir ?? '',
82
- comfyuiDirs: Array.isArray(entryConfig.comfyuiDirs)
83
- ? entryConfig.comfyuiDirs.filter((dir) => typeof dir === 'string' && dir.trim() !== '')
84
- : [],
85
- };
69
+ // Volatile fields arrive as references on dsh 0.1.7+ (plain values on older
70
+ // hosts); `resolved` is the one plain object every consumer reads, refreshed
71
+ // in place when the Loader commits a settings-page change.
72
+ const resolved = resolveConfig(entryConfig, defaultDataDir());
86
73
  const store = new ComfyUIStore(resolved.dataDir, resolved.maxAssets);
87
74
  await store.init();
88
75
  // Per-workflow skill packs default to `<dataDir>/skills`; `skillsDir` moves
@@ -113,11 +100,27 @@ export async function apply(ctx, entryConfig) {
113
100
  // only broadcasts them to the submitting client. Node's global WebSocket
114
101
  // (undici) cannot set auth headers, so a remote server behind an
115
102
  // authenticating proxy simply shows no progress.
103
+ const progressUrl = () => resolved.baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:').replace(/\/$/, '') + `/ws?clientId=${CLIENT_ID}`;
104
+ let attachedUrl = progressUrl();
116
105
  ctx.effect(() => {
117
- const wsUrl = resolved.baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:').replace(/\/$/, '') + `/ws?clientId=${CLIENT_ID}`;
118
- progress.attach(wsUrl);
106
+ progress.attach(attachedUrl);
119
107
  return () => progress.dispose();
120
108
  }, 'dsh-comfyui: progress');
109
+ /** Pick up a new config in place; a new baseUrl re-points the progress socket. */
110
+ const refreshConfig = (next) => {
111
+ Object.assign(resolved, next);
112
+ const url = progressUrl();
113
+ if (url !== attachedUrl) {
114
+ attachedUrl = url;
115
+ progress.dispose();
116
+ progress.attach(url);
117
+ }
118
+ };
119
+ // dsh 0.1.7+: a settings-page save commits volatile values into the same
120
+ // references without restarting the plugin, then notifies this fiber.
121
+ ctx.on('loader/volatile-update', (() => {
122
+ refreshConfig(resolveConfig(entryConfig, defaultDataDir()));
123
+ }));
121
124
  const hostHint = createHostHint();
122
125
  const runtime = {
123
126
  getConfig: () => resolved,
@@ -164,7 +167,8 @@ export async function apply(ctx, entryConfig) {
164
167
  },
165
168
  queue: async (workflow, meta) => {
166
169
  const client = runtime.createClient(await resolveApiKey(ctx, resolved.apiKeyEnv));
167
- let prompt = workflow;
170
+ // Heals library entries saved with the pre-0.5.2 wrapped DynamicCombo shape (Issue #10).
171
+ let prompt = flattenDynamicCombos(workflow);
168
172
  if (meta.parameters !== undefined && meta.parameters.length > 0) {
169
173
  const objectInfo = await objectInfoCached(client);
170
174
  const slots = await store.loadSlots();
@@ -322,18 +326,25 @@ export async function apply(ctx, entryConfig) {
322
326
  let source = () => resolved;
323
327
  ctx.inject(['settings'], (settingsCtx) => {
324
328
  const settings = settingsCtx.settings;
325
- if (typeof settings.installSection !== 'function') {
326
- ctx.logger.warn('comfyui: settings service lacks installSection — settings page stays read-only, entry config stands');
329
+ if (typeof settings.installSection === 'function') {
330
+ // dsh 0.1.2–0.1.5: the section registry keeps its own copy of the values.
331
+ settings.installSection(ctx, COMFYUI_NS, Config, resolved, {
332
+ setSource: (current) => {
333
+ source = current;
334
+ },
335
+ onChange: () => {
336
+ refreshConfig(resolveConfig(source(), defaultDataDir()));
337
+ },
338
+ });
327
339
  return;
328
340
  }
329
- settings.installSection(ctx, COMFYUI_NS, Config, resolved, {
330
- setSource: (current) => {
331
- source = current;
332
- },
333
- onChange: () => {
334
- Object.assign(resolved, source());
335
- },
336
- });
341
+ // dsh 0.1.7+: forms are projected from the volatile Config fields and a
342
+ // save arrives through loader/volatile-update above. This plugin ships its
343
+ // own settings page, so opt out of any auto-generated one.
344
+ if (typeof settings.configure === 'function') {
345
+ const configure = settings.configure.bind(settings);
346
+ settingsCtx.effect(() => configure({ auto: false }, ctx.fiber), 'dsh-comfyui: settings page policy');
347
+ }
337
348
  });
338
349
  ctx.effect(() => {
339
350
  const disposers = registerComfyUITools(ctx, runtime);
package/lib/routes.js CHANGED
@@ -250,7 +250,14 @@ export function mountComfyUIRoutes(ctx, runtime) {
250
250
  });
251
251
  }
252
252
  catch (error) {
253
- sendJson(response, 200, { ok: false, error: errorMessage(error), latencyMs: Date.now() - startedAt });
253
+ // Name the probed address: a save that never persisted leaves the
254
+ // default 127.0.0.1:8188 in force, and a bare "fetch failed" reads
255
+ // like a network problem with the address the user typed (Issue #11).
256
+ sendJson(response, 200, {
257
+ ok: false,
258
+ error: `${errorMessage(error)}(探测地址 ${runtime.getConfig().baseUrl})`,
259
+ latencyMs: Date.now() - startedAt,
260
+ });
254
261
  }
255
262
  }),
256
263
  }));
@@ -1428,7 +1435,12 @@ export function mountComfyUIRoutes(ctx, runtime) {
1428
1435
  const client = runtime.createClient(await runtime.getApiKey());
1429
1436
  const entry = await client.getHistory(promptId);
1430
1437
  if (entry === undefined) {
1431
- sendJson(response, 200, { ok: true, status: 'unknown' });
1438
+ // No history entry: still waiting in ComfyUI's queue, or evicted.
1439
+ // Tell the two apart so the background card keeps polling a long
1440
+ // queue instead of timing it out as "gone".
1441
+ const queue = await client.getQueue();
1442
+ const waiting = [...queue.queue_running, ...queue.queue_pending].some((item) => item.prompt_id === promptId);
1443
+ sendJson(response, 200, { ok: true, status: waiting ? 'queued' : 'unknown' });
1432
1444
  return;
1433
1445
  }
1434
1446
  const statusStr = entry.status?.status_str;
package/lib/tools.js CHANGED
@@ -263,7 +263,7 @@ function summarizeFields(fields, required) {
263
263
  else if (typeof typeOrList === 'string') {
264
264
  entry.type = typeOrList;
265
265
  }
266
- if (optionsRecord !== undefined && 'default' in optionsRecord) {
266
+ if (optionsRecord !== undefined && optionsRecord.default !== undefined) {
267
267
  entry.default = optionsRecord.default;
268
268
  }
269
269
  out.push(entry);
@@ -307,16 +307,23 @@ function objectInfoDefinition(runtime) {
307
307
  : entries.filter(([name]) => name.toLowerCase().includes(filter));
308
308
  const nodes = filtered.slice(0, 60).map(([classType, def]) => ({
309
309
  class_type: classType,
310
- display_name: def.display_name,
310
+ // Omitted rather than assigned undefined: a key holding undefined makes the
311
+ // whole result fail DSH's lossless-JSON validation. The render type already
312
+ // declares display_name optional.
313
+ ...(def.display_name !== undefined ? { display_name: def.display_name } : {}),
311
314
  description: (def.description ?? '').slice(0, 200),
312
315
  required: summarizeFields(def.input?.required, true),
313
316
  optional: summarizeFields(def.input?.optional, false),
314
317
  }));
318
+ const hint = filtered.length > nodes.length
319
+ ? `filter matched ${filtered.length} nodes, showing first ${nodes.length} — narrow the filter for more`
320
+ : undefined;
315
321
  return {
316
322
  total: entries.length,
317
323
  shown: nodes.length,
318
324
  filter: filter ?? null,
319
- hint: filtered.length > nodes.length ? `filter matched ${filtered.length} nodes, showing first ${nodes.length} — narrow the filter for more` : undefined,
325
+ // Omitted rather than assigned undefined, same reason as display_name.
326
+ ...(hint !== undefined ? { hint } : {}),
320
327
  nodes,
321
328
  };
322
329
  },
@@ -409,12 +416,13 @@ function workflowDefinition(runtime, ctx) {
409
416
  lines.push(` 技能包${skill.required ? '(运行前必读)' : ''}: ${skill.summary}${extra} — 运行前先 action: skill { id: "${workflow.id}" }`);
410
417
  }
411
418
  for (const param of workflow.parameters ?? []) {
412
- const def = typeof param.default === 'string' ? `"${param.default}"` : String(param.default);
419
+ const label = param.label ?? param.name;
420
+ const def = param.default === undefined ? '' : `,默认 ${typeof param.default === 'string' ? `"${param.default}"` : String(param.default)}`;
413
421
  const options = Array.isArray(param.options) && param.options.length > 0 ? `,可选: ${param.options.join(' / ')}` : '';
414
422
  const upload = param.upload !== undefined
415
423
  ? `,上传类型: ${param.upload}${param.upload === 'media' ? `(${param.subfolder ?? ''}/,空值=移除该参考位)` : ''}`
416
424
  : '';
417
- lines.push(` ${param.name}(${param.label}${param.random === true ? ',随机' : ''},默认 ${def}${options}${upload})`);
425
+ lines.push(` ${param.name}(${label}${param.random === true ? ',随机' : ''}${def}${options}${upload})`);
418
426
  }
419
427
  }
420
428
  const loadArea = data.loadArea;
@@ -506,7 +514,13 @@ function workflowDefinition(runtime, ctx) {
506
514
  parameters: (parameters ?? []).map(({ name: pname, label, type, default: def, random, numberKind, options, upload }) => {
507
515
  // DSH validates tool output as lossless JSON: JSON.stringify drops
508
516
  // undefined keys, so omit optional fields instead of passing undefined.
509
- const entry = { name: pname, label, type, default: def };
517
+ const entry = { name: pname };
518
+ if (label !== undefined)
519
+ entry.label = label;
520
+ if (type !== undefined)
521
+ entry.type = type;
522
+ if (def !== undefined)
523
+ entry.default = def;
510
524
  if (random !== undefined)
511
525
  entry.random = random;
512
526
  // Tells the model whether decimals are accepted; absent means the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-comfyui",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Let the DeepSeek Harness agent smartly drive a local or remote ComfyUI to generate anything, with workflow and asset management panels, per-workflow skill packs, a companion skill and a same-origin media proxy. / 让 DeepSeek Harness 的 Agent 智能驱动本地或远程 ComfyUI 生成任何内容。附带工作流、资产管理面板与技能包管理挂载。配套 skill 与同源媒体代理。",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,12 +49,12 @@
49
49
  "node": ">=22.19"
50
50
  },
51
51
  "dependencies": {
52
- "@deepseek-ai/schemastery": "^3.18.0",
52
+ "@deepseek-ai/schemastery": "^3.18.4",
53
53
  "fflate": "^0.8.3"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@deepseek-ai/cordis": "^4.0.1",
57
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4"
57
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@deepseek-ai/cordis": "^4.0.1",