@fre4x/comfyui 1.0.65 → 1.1.0-beta.1

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.
Files changed (3) hide show
  1. package/README.md +24 -5
  2. package/dist/index.js +476 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -60,6 +60,9 @@ If you want a single call that submits and waits, pass `await: true` with a
60
60
  `timeout` value in seconds to `comfyui_workflow_run`. For lower-level control,
61
61
  use `comfyui_wait_for_workflow` with `prompt_id` and `timeout`.
62
62
 
63
+ `comfyui_workflow_run` accepts either a stored workflow reference
64
+ (`workflow_id` / `workflow_file_path`) or an inline `workflow` JSON object.
65
+
63
66
  ## Stored Workflow Reuse
64
67
 
65
68
  The stored-workflow tools let you avoid re-sending large API JSON blobs and give
@@ -71,20 +74,32 @@ agents a stable local edit loop.
71
74
  4. Reuse it with `comfyui_workflow_run`
72
75
 
73
76
  `comfyui_workflow_run` and `comfyui_save_workflow` both accept dot-notation
74
- overrides in this form:
77
+ overrides in canonical `node.inputs.field` form, plus shorter aliases like
78
+ `node.seed`, `node.text`, `node.positive_prompt`, and, for Web UI workflows,
79
+ `node.widgets[index]`.
75
80
 
76
81
  ```json
77
82
  {
78
83
  "workflow_id": "pony-portrait-v1",
79
84
  "overrides": {
80
- "2.inputs.seed": 67890,
81
- "6.inputs.text": "cinematic dragon portrait"
85
+ "2.seed": 67890,
86
+ "6.positive_prompt": "cinematic dragon portrait"
82
87
  },
83
88
  "await": true,
84
89
  "timeout": 90
85
90
  }
86
91
  ```
87
92
 
93
+ If your client serializes nested arguments first, `overrides` may also be sent
94
+ as a JSON string:
95
+
96
+ ```json
97
+ {
98
+ "workflow_id": "pony-portrait-v1",
99
+ "overrides": "{\"2.seed\":67890,\"6.positive_prompt\":\"cinematic dragon portrait\"}"
100
+ }
101
+ ```
102
+
88
103
  To inspect editable inputs and semantic hints for agent editing:
89
104
 
90
105
  ```json
@@ -94,14 +109,18 @@ To inspect editable inputs and semantic hints for agent editing:
94
109
  }
95
110
  ```
96
111
 
112
+ `comfyui_get_workflow` also returns a copy-ready `override_examples` object in
113
+ both `content.text` and `structuredContent` so agents can paste a valid payload
114
+ without rebuilding the shape by hand.
115
+
97
116
  To save a new or edited workflow locally:
98
117
 
99
118
  ```json
100
119
  {
101
120
  "workflow_file_path": "pony-portrait-v1.json",
102
121
  "overrides": {
103
- "6.inputs.text": "studio lighting, ultra detailed",
104
- "7.inputs.filename_prefix": "pony-portrait-agent"
122
+ "6.text": "studio lighting, ultra detailed",
123
+ "7.filename_prefix": "pony-portrait-agent"
105
124
  },
106
125
  "output_file_name": "pony-portrait-agent.json",
107
126
  "overwrite": true
package/dist/index.js CHANGED
@@ -30637,8 +30637,9 @@ function normalizeWidgetValue(value, typeName, extraInfo) {
30637
30637
  }
30638
30638
  return value;
30639
30639
  }
30640
- function convertWebUIToAPI(webUIJson, objectInfo) {
30640
+ function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
30641
30641
  const prompt = {};
30642
+ const widgetPathMap = {};
30642
30643
  const links = webUIJson.links || [];
30643
30644
  const nodes = webUIJson.nodes || [];
30644
30645
  const serverNodeIds = /* @__PURE__ */ new Set();
@@ -30703,14 +30704,16 @@ function convertWebUIToAPI(webUIJson, objectInfo) {
30703
30704
  }
30704
30705
  continue;
30705
30706
  }
30707
+ const widgetSourceIndex = widgetIndex;
30706
30708
  const value = normalizeWidgetValue(
30707
- widgets[widgetIndex],
30709
+ widgets[widgetSourceIndex],
30708
30710
  typeName,
30709
30711
  inputExtraInfo
30710
30712
  );
30711
30713
  widgetIndex++;
30712
30714
  if (!isConnectedWidget) {
30713
30715
  promptNode.inputs[inputName] = value;
30716
+ widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
30714
30717
  }
30715
30718
  }
30716
30719
  };
@@ -30725,7 +30728,10 @@ function convertWebUIToAPI(webUIJson, objectInfo) {
30725
30728
  }
30726
30729
  prompt[nodeIdStr] = promptNode;
30727
30730
  }
30728
- return prompt;
30731
+ return {
30732
+ prompt,
30733
+ widgetPathMap
30734
+ };
30729
30735
  }
30730
30736
 
30731
30737
  // src/index.ts
@@ -30749,6 +30755,11 @@ var WorkflowInputError = class extends Error {
30749
30755
  this.name = "WorkflowInputError";
30750
30756
  }
30751
30757
  };
30758
+ var INPUT_SEGMENT = "inputs";
30759
+ var OverrideRecordSchema = z3.record(z3.string(), z3.unknown());
30760
+ var OverridesSchema = z3.union([OverrideRecordSchema, z3.string()]).describe(
30761
+ 'Dot-notation overrides as an object or JSON string. e.g. {"6.seed": 7}'
30762
+ );
30752
30763
  function isRecord2(value) {
30753
30764
  return typeof value === "object" && value !== null;
30754
30765
  }
@@ -31022,6 +31033,22 @@ function isConnectionValue(value) {
31022
31033
  async function getObjectInfo() {
31023
31034
  return IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson("/object_info");
31024
31035
  }
31036
+ function summarizeErrorMessage(error48) {
31037
+ return error48 instanceof Error ? error48.message : String(error48);
31038
+ }
31039
+ async function tryGetObjectInfo() {
31040
+ try {
31041
+ return {
31042
+ objectInfo: await getObjectInfo()
31043
+ };
31044
+ } catch (error48) {
31045
+ return {
31046
+ warning: `Node schema details unavailable from ComfyUI: ${summarizeErrorMessage(
31047
+ error48
31048
+ )}`
31049
+ };
31050
+ }
31051
+ }
31025
31052
  async function resolveWorkflowReference(args) {
31026
31053
  if (args.workflow_file_path) {
31027
31054
  const targetPath = args.workflow_file_path;
@@ -31097,7 +31124,7 @@ async function resolveWorkflowReference(args) {
31097
31124
  "workflow_id"
31098
31125
  );
31099
31126
  }
31100
- async function normalizeWorkflowPrompt(workflow) {
31127
+ async function normalizeWorkflowPrompt(workflow, objectInfo) {
31101
31128
  if (!isRecord2(workflow)) {
31102
31129
  throw new WorkflowInputError(
31103
31130
  "Workflow must be a JSON object.",
@@ -31105,13 +31132,22 @@ async function normalizeWorkflowPrompt(workflow) {
31105
31132
  );
31106
31133
  }
31107
31134
  let sourceFormat = "api";
31135
+ let widgetPathMap = {};
31108
31136
  let normalizedWorkflow = workflow;
31109
31137
  if (isWebUIFormat(normalizedWorkflow)) {
31138
+ if (!objectInfo) {
31139
+ throw new WorkflowInputError(
31140
+ "Web UI workflow normalization requires ComfyUI node definitions. Start the ComfyUI server or save the workflow in API format first.",
31141
+ "workflow"
31142
+ );
31143
+ }
31110
31144
  sourceFormat = "webui";
31111
- normalizedWorkflow = convertWebUIToAPI(
31145
+ const converted = convertWebUIToAPIWithMetadata(
31112
31146
  normalizedWorkflow,
31113
- await getObjectInfo()
31147
+ objectInfo
31114
31148
  );
31149
+ normalizedWorkflow = converted.prompt;
31150
+ widgetPathMap = converted.widgetPathMap;
31115
31151
  }
31116
31152
  const promptCandidate = isRecord2(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
31117
31153
  if (!isRecord2(promptCandidate)) {
@@ -31122,10 +31158,11 @@ async function normalizeWorkflowPrompt(workflow) {
31122
31158
  }
31123
31159
  return {
31124
31160
  prompt: extractRunnablePromptNodes(promptCandidate),
31125
- sourceFormat
31161
+ sourceFormat,
31162
+ widgetPathMap
31126
31163
  };
31127
31164
  }
31128
- async function loadWorkflow(args) {
31165
+ async function loadWorkflow(args, objectInfo) {
31129
31166
  const { targetPath, content } = await resolveWorkflowReference(args);
31130
31167
  let parsed;
31131
31168
  try {
@@ -31136,11 +31173,12 @@ async function loadWorkflow(args) {
31136
31173
  args.workflow_file_path ? "workflow_file_path" : "workflow_id"
31137
31174
  );
31138
31175
  }
31139
- const { prompt, sourceFormat } = await normalizeWorkflowPrompt(parsed);
31176
+ const { prompt, sourceFormat, widgetPathMap } = await normalizeWorkflowPrompt(parsed, objectInfo);
31140
31177
  return {
31141
31178
  targetPath,
31142
31179
  prompt,
31143
- sourceFormat
31180
+ sourceFormat,
31181
+ widgetPathMap
31144
31182
  };
31145
31183
  }
31146
31184
  function inferEditableRole(nodeId, classType, inputName, consumerMap) {
@@ -31197,7 +31235,278 @@ function formatValuePreview(value) {
31197
31235
  return String(value);
31198
31236
  }
31199
31237
  }
31200
- function analyzeEditableInputs(prompt) {
31238
+ function getInputDefinition(objectInfo, prompt, nodeId, inputName) {
31239
+ if (!objectInfo) {
31240
+ return void 0;
31241
+ }
31242
+ const node = prompt[nodeId];
31243
+ if (!node) {
31244
+ return void 0;
31245
+ }
31246
+ const nodeDefinition = objectInfo[node.class_type];
31247
+ if (!nodeDefinition) {
31248
+ return void 0;
31249
+ }
31250
+ return nodeDefinition.input?.required?.[inputName] ?? nodeDefinition.input?.optional?.[inputName];
31251
+ }
31252
+ function getExpectedTypeName(inputDefinition) {
31253
+ if (!inputDefinition) {
31254
+ return void 0;
31255
+ }
31256
+ const [typeData] = inputDefinition;
31257
+ return Array.isArray(typeData) ? "COMBO" : String(typeData).toUpperCase();
31258
+ }
31259
+ function isPlainObject3(value) {
31260
+ return isRecord2(value) && !Array.isArray(value);
31261
+ }
31262
+ function getValueTypeName(value) {
31263
+ if (Array.isArray(value)) {
31264
+ return "array";
31265
+ }
31266
+ if (value === null) {
31267
+ return "null";
31268
+ }
31269
+ return typeof value;
31270
+ }
31271
+ function validateOverrideValueType(path2, value, inputDefinition) {
31272
+ if (!inputDefinition) {
31273
+ return;
31274
+ }
31275
+ const [typeData] = inputDefinition;
31276
+ if (Array.isArray(typeData)) {
31277
+ if (!typeData.some((option) => option === value)) {
31278
+ throw new WorkflowInputError(
31279
+ `Invalid override for '${path2}': expected one of ${typeData.map((option) => JSON.stringify(option)).join(", ")}, received ${JSON.stringify(value)}.`,
31280
+ "overrides"
31281
+ );
31282
+ }
31283
+ return;
31284
+ }
31285
+ const typeName = String(typeData).toUpperCase();
31286
+ switch (typeName) {
31287
+ case "INT":
31288
+ if (typeof value !== "number" || !Number.isInteger(value)) {
31289
+ throw new WorkflowInputError(
31290
+ `Invalid override for '${path2}': expected INT, received ${getValueTypeName(
31291
+ value
31292
+ )}.`,
31293
+ "overrides"
31294
+ );
31295
+ }
31296
+ return;
31297
+ case "FLOAT":
31298
+ if (typeof value !== "number") {
31299
+ throw new WorkflowInputError(
31300
+ `Invalid override for '${path2}': expected FLOAT, received ${getValueTypeName(
31301
+ value
31302
+ )}.`,
31303
+ "overrides"
31304
+ );
31305
+ }
31306
+ return;
31307
+ case "STRING":
31308
+ if (typeof value !== "string") {
31309
+ throw new WorkflowInputError(
31310
+ `Invalid override for '${path2}': expected STRING, received ${getValueTypeName(
31311
+ value
31312
+ )}.`,
31313
+ "overrides"
31314
+ );
31315
+ }
31316
+ return;
31317
+ case "BOOLEAN":
31318
+ if (typeof value !== "boolean") {
31319
+ throw new WorkflowInputError(
31320
+ `Invalid override for '${path2}': expected BOOLEAN, received ${getValueTypeName(
31321
+ value
31322
+ )}.`,
31323
+ "overrides"
31324
+ );
31325
+ }
31326
+ return;
31327
+ default:
31328
+ return;
31329
+ }
31330
+ }
31331
+ function getEditableAliases(canonicalPath, nodeId, inputName, role, widgetPathMap) {
31332
+ const aliases = /* @__PURE__ */ new Set([`${nodeId}.${inputName}`]);
31333
+ if (role) {
31334
+ aliases.add(`${nodeId}.${role}`);
31335
+ }
31336
+ for (const [widgetPath, resolvedPath] of Object.entries(widgetPathMap)) {
31337
+ if (resolvedPath === canonicalPath) {
31338
+ aliases.add(widgetPath);
31339
+ }
31340
+ }
31341
+ aliases.delete(canonicalPath);
31342
+ return [...aliases];
31343
+ }
31344
+ function buildEditableInputIndex(editableInputs) {
31345
+ const entriesByPath = /* @__PURE__ */ new Map();
31346
+ const aliasToPath = /* @__PURE__ */ new Map();
31347
+ const ambiguousAliases = /* @__PURE__ */ new Map();
31348
+ const pathsByNode = /* @__PURE__ */ new Map();
31349
+ for (const entry of editableInputs) {
31350
+ entriesByPath.set(entry.path, entry);
31351
+ const nodeEntries = pathsByNode.get(entry.node_id) ?? [];
31352
+ nodeEntries.push(entry);
31353
+ pathsByNode.set(entry.node_id, nodeEntries);
31354
+ for (const alias of [entry.path, ...entry.aliases]) {
31355
+ const existing = aliasToPath.get(alias);
31356
+ if (!existing) {
31357
+ aliasToPath.set(alias, entry.path);
31358
+ continue;
31359
+ }
31360
+ if (existing === entry.path) {
31361
+ continue;
31362
+ }
31363
+ const ambiguous = new Set(
31364
+ ambiguousAliases.get(alias) ?? [existing]
31365
+ );
31366
+ ambiguous.add(entry.path);
31367
+ ambiguousAliases.set(alias, [...ambiguous].sort());
31368
+ aliasToPath.delete(alias);
31369
+ }
31370
+ }
31371
+ return {
31372
+ entriesByPath,
31373
+ aliasToPath,
31374
+ ambiguousAliases,
31375
+ pathsByNode
31376
+ };
31377
+ }
31378
+ function getPathSegmentHelp(prompt, path2) {
31379
+ const parts = path2.split(".");
31380
+ if (parts.length === 0 || parts[0].length === 0) {
31381
+ return void 0;
31382
+ }
31383
+ const nodeId = parts[0];
31384
+ return {
31385
+ nodeId,
31386
+ nodeExists: isRecord2(prompt[nodeId])
31387
+ };
31388
+ }
31389
+ function describeAvailableNodePaths(editableIndex, nodeId) {
31390
+ if (!editableIndex) {
31391
+ return "";
31392
+ }
31393
+ const entries = editableIndex.pathsByNode.get(nodeId) ?? [];
31394
+ if (entries.length === 0) {
31395
+ return "";
31396
+ }
31397
+ return entries.map((entry) => {
31398
+ const aliasSuffix = entry.aliases.length > 0 ? ` (aliases: ${entry.aliases.join(", ")})` : "";
31399
+ return `${entry.path}${aliasSuffix}`;
31400
+ }).join("\n");
31401
+ }
31402
+ function getPreferredOverridePath(entry) {
31403
+ const roleAlias = entry.role ? `${entry.node_id}.${entry.role}` : void 0;
31404
+ if (roleAlias && entry.aliases.includes(roleAlias)) {
31405
+ return roleAlias;
31406
+ }
31407
+ const inputAlias = `${entry.node_id}.${entry.input_name}`;
31408
+ if (entry.aliases.includes(inputAlias)) {
31409
+ return inputAlias;
31410
+ }
31411
+ const nonWidgetAlias = entry.aliases.find(
31412
+ (alias) => !alias.includes(".widgets[")
31413
+ );
31414
+ return nonWidgetAlias ?? entry.path;
31415
+ }
31416
+ function buildOverrideExamples(editableInputs, limit = 5) {
31417
+ const examples = {};
31418
+ const preferredPaths = [];
31419
+ for (const entry of editableInputs.slice(0, limit)) {
31420
+ const preferredPath = getPreferredOverridePath(entry);
31421
+ examples[preferredPath] = entry.value;
31422
+ preferredPaths.push(preferredPath);
31423
+ }
31424
+ return {
31425
+ examples,
31426
+ preferred_paths: preferredPaths
31427
+ };
31428
+ }
31429
+ function formatOverrideExamplesText(examples) {
31430
+ return JSON.stringify(examples, null, 2);
31431
+ }
31432
+ function resolveOverridePath(prompt, rawPath, editableIndex) {
31433
+ if (!editableIndex) {
31434
+ return rawPath;
31435
+ }
31436
+ if (editableIndex.entriesByPath.has(rawPath)) {
31437
+ return rawPath;
31438
+ }
31439
+ const ambiguous = editableIndex.ambiguousAliases.get(rawPath);
31440
+ if (ambiguous) {
31441
+ throw new WorkflowInputError(
31442
+ `Ambiguous override path '${rawPath}'. It could refer to: ${ambiguous.join(
31443
+ ", "
31444
+ )}. Use a full '<node>.inputs.<field>' path instead.`,
31445
+ "overrides"
31446
+ );
31447
+ }
31448
+ const resolved = editableIndex.aliasToPath.get(rawPath);
31449
+ if (resolved) {
31450
+ return resolved;
31451
+ }
31452
+ const rawParts = rawPath.split(".");
31453
+ if (rawParts.length >= 3 && rawParts[1] === INPUT_SEGMENT) {
31454
+ const pathHelp2 = getPathSegmentHelp(prompt, rawPath);
31455
+ if (pathHelp2?.nodeId && pathHelp2.nodeExists) {
31456
+ const validPaths = describeAvailableNodePaths(
31457
+ editableIndex,
31458
+ pathHelp2.nodeId
31459
+ );
31460
+ if (validPaths.length > 0) {
31461
+ throw new WorkflowInputError(
31462
+ `Unsupported override path '${rawPath}'. Only detected editable inputs can be overridden. Valid editable paths for node ${pathHelp2.nodeId}:
31463
+ ${validPaths}`,
31464
+ "overrides"
31465
+ );
31466
+ }
31467
+ }
31468
+ }
31469
+ const pathHelp = getPathSegmentHelp(prompt, rawPath);
31470
+ if (pathHelp?.nodeId && pathHelp.nodeExists) {
31471
+ const validPaths = describeAvailableNodePaths(
31472
+ editableIndex,
31473
+ pathHelp.nodeId
31474
+ );
31475
+ if (validPaths.length > 0) {
31476
+ throw new WorkflowInputError(
31477
+ `Unknown override path '${rawPath}'. Valid editable paths for node ${pathHelp.nodeId}:
31478
+ ${validPaths}`,
31479
+ "overrides"
31480
+ );
31481
+ }
31482
+ }
31483
+ return rawPath;
31484
+ }
31485
+ function parseOverrides(rawOverrides) {
31486
+ if (rawOverrides === void 0) {
31487
+ return void 0;
31488
+ }
31489
+ if (typeof rawOverrides !== "string") {
31490
+ return rawOverrides;
31491
+ }
31492
+ let parsed;
31493
+ try {
31494
+ parsed = JSON.parse(rawOverrides);
31495
+ } catch (error48) {
31496
+ throw new WorkflowInputError(
31497
+ `Overrides must be a valid JSON object string: ${error48 instanceof Error ? error48.message : String(error48)}`,
31498
+ "overrides"
31499
+ );
31500
+ }
31501
+ if (!isPlainObject3(parsed)) {
31502
+ throw new WorkflowInputError(
31503
+ "Overrides JSON string must decode to an object.",
31504
+ "overrides"
31505
+ );
31506
+ }
31507
+ return parsed;
31508
+ }
31509
+ function analyzeEditableInputs(prompt, objectInfo, widgetPathMap = {}) {
31201
31510
  const consumerMap = /* @__PURE__ */ new Map();
31202
31511
  for (const [nodeId, node] of Object.entries(prompt)) {
31203
31512
  for (const [inputName, value] of Object.entries(node.inputs)) {
@@ -31232,11 +31541,24 @@ function analyzeEditableInputs(prompt) {
31232
31541
  node.class_type,
31233
31542
  inputName,
31234
31543
  consumerMap
31544
+ ),
31545
+ aliases: [],
31546
+ expected_type: getExpectedTypeName(
31547
+ getInputDefinition(objectInfo, prompt, nodeId, inputName)
31235
31548
  )
31236
31549
  });
31237
31550
  }
31238
31551
  }
31239
- return editableInputs.sort(
31552
+ return editableInputs.map((entry) => ({
31553
+ ...entry,
31554
+ aliases: getEditableAliases(
31555
+ entry.path,
31556
+ entry.node_id,
31557
+ entry.input_name,
31558
+ entry.role,
31559
+ widgetPathMap
31560
+ )
31561
+ })).sort(
31240
31562
  (left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
31241
31563
  );
31242
31564
  }
@@ -31247,7 +31569,9 @@ function formatEditableInputsText(editableInputs) {
31247
31569
  return formatListItems(editableInputs, (entry) => {
31248
31570
  const role = entry.role ? ` [${entry.role}]` : "";
31249
31571
  const classType = entry.class_type ? ` (${entry.class_type})` : "";
31250
- return `${entry.path}${role}${classType}: ${entry.value_preview}`;
31572
+ const expectedType = entry.expected_type ? ` <${entry.expected_type}>` : "";
31573
+ const aliasSuffix = entry.aliases.length > 0 ? ` | aliases: ${entry.aliases.join(", ")}` : "";
31574
+ return `${entry.path}${role}${classType}${expectedType}: ${entry.value_preview}${aliasSuffix}`;
31251
31575
  });
31252
31576
  }
31253
31577
  function deriveSavedWorkflowFileName(sourcePath, requestedName) {
@@ -31259,21 +31583,39 @@ function deriveSavedWorkflowFileName(sourcePath, requestedName) {
31259
31583
  }
31260
31584
  return `workflow-${Date.now()}.json`;
31261
31585
  }
31262
- function applyOverrides(prompt, overrides) {
31263
- for (const [key, value] of Object.entries(overrides)) {
31586
+ function applyOverrides(prompt, overrides, options = {}) {
31587
+ for (const [rawKey, value] of Object.entries(overrides)) {
31588
+ const key = resolveOverridePath(prompt, rawKey, options.editableIndex);
31264
31589
  const parts = key.split(".");
31265
31590
  let current = prompt;
31266
31591
  for (let i = 0; i < parts.length - 1; i++) {
31267
31592
  const part = parts[i];
31268
31593
  const next = current[part];
31269
31594
  if (!isRecord2(next)) {
31595
+ const pathHelp = getPathSegmentHelp(prompt, key);
31596
+ const validPaths = pathHelp?.nodeId && pathHelp.nodeExists ? describeAvailableNodePaths(
31597
+ options.editableIndex,
31598
+ pathHelp.nodeId
31599
+ ) : "";
31600
+ const validPathSuffix = validPaths.length > 0 ? `
31601
+ Valid editable paths for node ${pathHelp?.nodeId}:
31602
+ ${validPaths}` : "";
31270
31603
  throw new WorkflowInputError(
31271
- `Invalid override path: '${key}'. The segment '${part}' does not exist or is not an object. Please check the editable inputs using 'comfyui_get_workflow' to find the correct path.`,
31604
+ `Invalid override path: '${rawKey}'. The resolved path '${key}' failed at segment '${part}'.${validPathSuffix}`,
31272
31605
  "overrides"
31273
31606
  );
31274
31607
  }
31275
31608
  current = next;
31276
31609
  }
31610
+ if (parts.length === 3 && parts[1] === INPUT_SEGMENT && typeof parts[0] === "string" && typeof parts[2] === "string") {
31611
+ const inputDefinition = getInputDefinition(
31612
+ options.objectInfo,
31613
+ prompt,
31614
+ parts[0],
31615
+ parts[2]
31616
+ );
31617
+ validateOverrideValueType(key, value, inputDefinition);
31618
+ }
31277
31619
  current[parts[parts.length - 1]] = value;
31278
31620
  }
31279
31621
  }
@@ -31289,13 +31631,12 @@ var DiscoverWorkflowsSchema = paginationSchema.extend({
31289
31631
  directory_path: z3.string().optional().describe("Custom directory path to scan for workflows.")
31290
31632
  });
31291
31633
  var WorkflowRunSchema = z3.object({
31634
+ workflow: z3.unknown().optional().describe("Workflow JSON object to execute immediately."),
31292
31635
  workflow_file_path: z3.string().optional().describe(
31293
31636
  "Path or filename of workflow JSON. Scans default dirs if simple name provided."
31294
31637
  ),
31295
31638
  workflow_id: z3.string().optional().describe("Workflow identifier returned by discover_workflows."),
31296
- overrides: z3.record(z3.string(), z3.unknown()).optional().describe(
31297
- 'Key-value pairs to override using dot-notation. e.g. {"6.inputs.text": "dragon"}'
31298
- ),
31639
+ overrides: OverridesSchema.optional(),
31299
31640
  await: z3.boolean().default(true).describe("Wait for completion and return native images."),
31300
31641
  timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
31301
31642
  });
@@ -31308,7 +31649,7 @@ var SaveWorkflowSchema = z3.object({
31308
31649
  workflow: z3.unknown().optional().describe("Workflow JSON object to save locally."),
31309
31650
  workflow_file_path: z3.string().optional().describe("Existing workflow path or filename to save."),
31310
31651
  workflow_id: z3.string().optional().describe("Existing workflow identifier to save."),
31311
- overrides: z3.record(z3.string(), z3.unknown()).optional().describe("Dot-notation overrides to persist before saving."),
31652
+ overrides: OverridesSchema.optional(),
31312
31653
  output_file_name: z3.string().optional().describe("Filename to write locally. (default: anchored to ./)"),
31313
31654
  overwrite: z3.boolean().default(false).describe("Replace an existing local file when true."),
31314
31655
  include_prompt: z3.boolean().default(false).describe("Include saved API JSON in structuredContent.")
@@ -31552,21 +31893,43 @@ async function handleWaitForWorkflow(args) {
31552
31893
  }
31553
31894
  async function handleGetWorkflow(args) {
31554
31895
  try {
31555
- const loadedWorkflow = await loadWorkflow(args);
31556
- const editableInputs = analyzeEditableInputs(loadedWorkflow.prompt);
31896
+ const objectInfoAvailability = await tryGetObjectInfo();
31897
+ const loadedWorkflow = await loadWorkflow(
31898
+ args,
31899
+ objectInfoAvailability.objectInfo
31900
+ );
31901
+ const editableInputs = analyzeEditableInputs(
31902
+ loadedWorkflow.prompt,
31903
+ objectInfoAvailability.objectInfo,
31904
+ loadedWorkflow.widgetPathMap
31905
+ );
31906
+ const overrideExamples = buildOverrideExamples(editableInputs);
31557
31907
  const workflowId = getWorkflowId(loadedWorkflow.targetPath);
31558
- const text = [
31908
+ const textSections = [
31559
31909
  `Workflow ${workflowId} loaded from ${loadedWorkflow.targetPath}.`,
31560
- loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format.",
31910
+ loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format."
31911
+ ];
31912
+ if (objectInfoAvailability.warning) {
31913
+ textSections.push(objectInfoAvailability.warning);
31914
+ }
31915
+ textSections.push(
31561
31916
  `Editable inputs (${editableInputs.length}):`,
31562
- formatEditableInputsText(editableInputs)
31563
- ].join("\n");
31917
+ formatEditableInputsText(editableInputs),
31918
+ "Copy-ready override example:",
31919
+ formatOverrideExamplesText(overrideExamples.examples)
31920
+ );
31921
+ const text = textSections.join("\n");
31564
31922
  const structuredContent = {
31565
31923
  workflow_id: workflowId,
31566
31924
  path: loadedWorkflow.targetPath,
31567
31925
  source_format: loadedWorkflow.sourceFormat,
31568
- editable_inputs: editableInputs
31926
+ editable_inputs: editableInputs,
31927
+ override_examples: overrideExamples.examples,
31928
+ preferred_override_paths: overrideExamples.preferred_paths
31569
31929
  };
31930
+ if (objectInfoAvailability.warning) {
31931
+ structuredContent.warnings = [objectInfoAvailability.warning];
31932
+ }
31570
31933
  if (args.include_prompt) {
31571
31934
  structuredContent.prompt = loadedWorkflow.prompt;
31572
31935
  }
@@ -31581,10 +31944,17 @@ async function handleGetWorkflow(args) {
31581
31944
  async function handleSaveWorkflow(args) {
31582
31945
  try {
31583
31946
  let prompt;
31947
+ let widgetPathMap = {};
31584
31948
  let sourcePath;
31949
+ const parsedOverrides = parseOverrides(args.overrides);
31950
+ const objectInfoAvailability = await tryGetObjectInfo();
31585
31951
  if (args.workflow !== void 0) {
31586
- const normalized = await normalizeWorkflowPrompt(args.workflow);
31952
+ const normalized = await normalizeWorkflowPrompt(
31953
+ args.workflow,
31954
+ objectInfoAvailability.objectInfo
31955
+ );
31587
31956
  prompt = normalized.prompt;
31957
+ widgetPathMap = normalized.widgetPathMap;
31588
31958
  } else {
31589
31959
  if (!args.workflow_file_path && !args.workflow_id) {
31590
31960
  return createValidationError(
@@ -31592,12 +31962,25 @@ async function handleSaveWorkflow(args) {
31592
31962
  "Provide workflow JSON or an existing workflow reference."
31593
31963
  );
31594
31964
  }
31595
- const loadedWorkflow = await loadWorkflow(args);
31965
+ const loadedWorkflow = await loadWorkflow(
31966
+ args,
31967
+ objectInfoAvailability.objectInfo
31968
+ );
31596
31969
  prompt = loadedWorkflow.prompt;
31597
31970
  sourcePath = loadedWorkflow.targetPath;
31971
+ widgetPathMap = loadedWorkflow.widgetPathMap;
31598
31972
  }
31599
- if (args.overrides) {
31600
- applyOverrides(prompt, args.overrides);
31973
+ const editableInputs = analyzeEditableInputs(
31974
+ prompt,
31975
+ objectInfoAvailability.objectInfo,
31976
+ widgetPathMap
31977
+ );
31978
+ const editableIndex = buildEditableInputIndex(editableInputs);
31979
+ if (parsedOverrides) {
31980
+ applyOverrides(prompt, parsedOverrides, {
31981
+ editableIndex,
31982
+ objectInfo: objectInfoAvailability.objectInfo
31983
+ });
31601
31984
  }
31602
31985
  const outputFileName = deriveSavedWorkflowFileName(
31603
31986
  sourcePath,
@@ -31630,25 +32013,43 @@ async function handleSaveWorkflow(args) {
31630
32013
  `,
31631
32014
  "utf8"
31632
32015
  );
31633
- const editableInputs = analyzeEditableInputs(prompt);
32016
+ const updatedEditableInputs = analyzeEditableInputs(
32017
+ prompt,
32018
+ objectInfoAvailability.objectInfo,
32019
+ widgetPathMap
32020
+ );
32021
+ const overrideExamples = buildOverrideExamples(updatedEditableInputs);
31634
32022
  const workflowId = getWorkflowId(outputPath);
31635
32023
  const structuredContent = {
31636
32024
  workflow_id: workflowId,
31637
32025
  path: outputPath,
31638
32026
  source_path: sourcePath,
31639
32027
  overwritten: existedBeforeSave,
31640
- editable_inputs: editableInputs
32028
+ editable_inputs: updatedEditableInputs,
32029
+ override_examples: overrideExamples.examples,
32030
+ preferred_override_paths: overrideExamples.preferred_paths
31641
32031
  };
32032
+ if (objectInfoAvailability.warning) {
32033
+ structuredContent.warnings = [objectInfoAvailability.warning];
32034
+ }
31642
32035
  if (args.include_prompt) {
31643
32036
  structuredContent.prompt = prompt;
31644
32037
  }
31645
- const persistedChanges = args.overrides && Object.keys(args.overrides).length > 0 ? `Persisted ${Object.keys(args.overrides).length} override(s).` : "No overrides were applied.";
31646
- const text = [
32038
+ const persistedChanges = parsedOverrides && Object.keys(parsedOverrides).length > 0 ? `Persisted ${Object.keys(parsedOverrides).length} override(s).` : "No overrides were applied.";
32039
+ const textSections = [
31647
32040
  `Saved workflow ${workflowId} to ${outputPath}.`,
31648
- persistedChanges,
31649
- `Editable inputs (${editableInputs.length}):`,
31650
- formatEditableInputsText(editableInputs)
31651
- ].join("\n");
32041
+ persistedChanges
32042
+ ];
32043
+ if (objectInfoAvailability.warning) {
32044
+ textSections.push(objectInfoAvailability.warning);
32045
+ }
32046
+ textSections.push(
32047
+ `Editable inputs (${updatedEditableInputs.length}):`,
32048
+ formatEditableInputsText(updatedEditableInputs),
32049
+ "Copy-ready override example:",
32050
+ formatOverrideExamplesText(overrideExamples.examples)
32051
+ );
32052
+ const text = textSections.join("\n");
31652
32053
  return {
31653
32054
  content: [{ type: "text", text }],
31654
32055
  structuredContent
@@ -31659,10 +32060,43 @@ async function handleSaveWorkflow(args) {
31659
32060
  }
31660
32061
  async function handleWorkflowRun(args) {
31661
32062
  try {
31662
- const { prompt } = await loadWorkflow(args);
32063
+ let loadedWorkflow;
32064
+ let objectInfo;
32065
+ if (args.workflow !== void 0) {
32066
+ objectInfo = await getObjectInfo();
32067
+ const normalized = await normalizeWorkflowPrompt(
32068
+ args.workflow,
32069
+ objectInfo
32070
+ );
32071
+ loadedWorkflow = {
32072
+ targetPath: "[inline workflow]",
32073
+ prompt: normalized.prompt,
32074
+ sourceFormat: normalized.sourceFormat,
32075
+ widgetPathMap: normalized.widgetPathMap
32076
+ };
32077
+ } else {
32078
+ if (!args.workflow_file_path && !args.workflow_id) {
32079
+ return createValidationError(
32080
+ "workflow",
32081
+ "Provide workflow JSON or an existing workflow reference."
32082
+ );
32083
+ }
32084
+ objectInfo = await getObjectInfo();
32085
+ loadedWorkflow = await loadWorkflow(args, objectInfo);
32086
+ }
32087
+ const { prompt } = loadedWorkflow;
31663
32088
  const promptObj = { ...prompt };
31664
- if (args.overrides) {
31665
- applyOverrides(promptObj, args.overrides);
32089
+ const parsedOverrides = parseOverrides(args.overrides);
32090
+ if (parsedOverrides) {
32091
+ const editableInputs = analyzeEditableInputs(
32092
+ prompt,
32093
+ objectInfo,
32094
+ loadedWorkflow.widgetPathMap
32095
+ );
32096
+ applyOverrides(promptObj, parsedOverrides, {
32097
+ editableIndex: buildEditableInputIndex(editableInputs),
32098
+ objectInfo
32099
+ });
31666
32100
  }
31667
32101
  const clientId = `mcp-${Date.now()}`;
31668
32102
  const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson("/prompt", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fre4x/comfyui",
3
- "version": "1.0.65",
3
+ "version": "1.1.0-beta.1",
4
4
  "description": "MCP server for ComfyUI. Execute workflows and probe server state remotely.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",