dsh-comfyui 0.3.0-beta.5 → 0.4.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/lib/store.d.ts CHANGED
@@ -14,6 +14,10 @@ export interface StoredWorkflow {
14
14
  parameters?: WorkflowParameter[];
15
15
  /** Classification tags (preset kinds like 图生图 plus user-defined). */
16
16
  tags?: string[];
17
+ /** Skill-pack directory name under `<dataDir>/skills/`; absent = no pack. */
18
+ skillDir?: string;
19
+ /** Refuse `action: run` until the agent has read this workflow's skill pack. */
20
+ requireSkill?: boolean;
17
21
  /** Where the workflow came from: 'user' (panel/tool) or 'comfyui' (imported). */
18
22
  source?: 'user' | 'comfyui';
19
23
  /** Original ComfyUI-side user-data file name when imported from the server. */
@@ -69,6 +73,8 @@ export declare class ComfyUIStore {
69
73
  private readonly mediaSizesPath;
70
74
  private readonly mediaHashesPath;
71
75
  private readonly currentImagePath;
76
+ /** Root of the per-workflow skill packs (one directory per pack). */
77
+ readonly skillsRoot: string;
72
78
  constructor(dir: string, maxAssets: number);
73
79
  /** Ensure the data directory exists. */
74
80
  init(): Promise<void>;
@@ -117,6 +123,13 @@ export declare class ComfyUIStore {
117
123
  ok: false;
118
124
  error: string;
119
125
  }>;
126
+ /** Patch the skill-pack fields of one workflow without touching its JSON or
127
+ * parameters. `skillDir: null` detaches the pack (the directory stays on
128
+ * disk until an explicit destroy). */
129
+ updateWorkflowSkill(id: string, patch: {
130
+ skillDir?: string | null;
131
+ requireSkill?: boolean;
132
+ }): Promise<StoredWorkflow | undefined>;
120
133
  /** Delete a workflow by id; false when it did not exist. */
121
134
  deleteWorkflow(id: string): Promise<boolean>;
122
135
  listAssets(): Promise<AssetRecord[]>;
package/lib/store.js CHANGED
@@ -51,8 +51,11 @@ export class ComfyUIStore {
51
51
  mediaSizesPath;
52
52
  mediaHashesPath;
53
53
  currentImagePath;
54
+ /** Root of the per-workflow skill packs (one directory per pack). */
55
+ skillsRoot;
54
56
  constructor(dir, maxAssets) {
55
57
  this.maxAssets = maxAssets;
58
+ this.skillsRoot = join(dir, 'skills');
56
59
  this.workflowsPath = join(dir, 'workflows.json');
57
60
  this.assetsPath = join(dir, 'assets.json');
58
61
  this.trackedPath = join(dir, 'tracked.json');
@@ -160,6 +163,10 @@ export class ComfyUIStore {
160
163
  ...list[index], name, description, workflow,
161
164
  parameters,
162
165
  tags: tags !== undefined ? tags : list[index].tags,
166
+ // Skill-pack fields are owned by the skill routes, not the workflow
167
+ // editor: a plain save must never drop a pack the user attached.
168
+ skillDir: list[index].skillDir,
169
+ requireSkill: list[index].requireSkill,
163
170
  source: input.source ?? list[index].source,
164
171
  comfyuiFile: input.comfyuiFile ?? list[index].comfyuiFile,
165
172
  updatedAt: now,
@@ -180,6 +187,35 @@ export class ComfyUIStore {
180
187
  await this.writeWorkflows(list);
181
188
  return { ok: true, workflow: created };
182
189
  }
190
+ /** Patch the skill-pack fields of one workflow without touching its JSON or
191
+ * parameters. `skillDir: null` detaches the pack (the directory stays on
192
+ * disk until an explicit destroy). */
193
+ async updateWorkflowSkill(id, patch) {
194
+ const list = await this.listWorkflows();
195
+ const index = list.findIndex((entry) => entry.id === id);
196
+ if (index === -1)
197
+ return undefined;
198
+ const current = list[index];
199
+ const next = { ...current, updatedAt: new Date().toISOString() };
200
+ if (patch.skillDir !== undefined) {
201
+ if (patch.skillDir === null) {
202
+ delete next.skillDir;
203
+ delete next.requireSkill;
204
+ }
205
+ else {
206
+ next.skillDir = patch.skillDir;
207
+ }
208
+ }
209
+ if (patch.requireSkill !== undefined) {
210
+ if (patch.requireSkill)
211
+ next.requireSkill = true;
212
+ else
213
+ delete next.requireSkill;
214
+ }
215
+ list[index] = next;
216
+ await this.writeWorkflows(list);
217
+ return next;
218
+ }
183
219
  /** Delete a workflow by id; false when it did not exist. */
184
220
  async deleteWorkflow(id) {
185
221
  const list = await this.listWorkflows();
package/lib/tools.d.ts CHANGED
@@ -14,6 +14,7 @@ import type { RunProgress } from './progress.js';
14
14
  import type { QueuedRun } from './queue.js';
15
15
  import { type WorkflowParameter } from './params.js';
16
16
  import type { HostHint } from './host-hint.js';
17
+ import { type WorkflowSkillPacks } from './skillpack.js';
17
18
  /** A workflow saved on the ComfyUI server (userdata/workflows), with extract status. */
18
19
  export interface ComfyUIComfyWorkflow {
19
20
  name: string;
@@ -82,6 +83,8 @@ export interface ComfyUIRuntime {
82
83
  }>;
83
84
  /** Delete a workflow from the library; false when it did not exist. */
84
85
  deleteWorkflow(id: string): Promise<boolean>;
86
+ /** Per-workflow skill packs (SKILL.md bundles under `<dataDir>/skills/`). */
87
+ skillPacks: WorkflowSkillPacks;
85
88
  /** Force TTS-Audio-Suite to rescan its voice library (best-effort; false
86
89
  * when the server has no such endpoint). Call before re-deriving parameter
87
90
  * snapshots so object_info reports newly added voices. */
package/lib/tools.js CHANGED
@@ -1,6 +1,34 @@
1
1
  import { collectMedia } from './comfyui.js';
2
2
  import { TEMPLATES, findTemplate, cloneWorkflow, applyTemplateInputs } from './templates.js';
3
3
  import { refreshParameterMetadata } from './params.js';
4
+ import { SKILL_MAIN, joinFrontmatter } from './skillpack.js';
5
+ /**
6
+ * Which skill packs each agent has already read, for the `requireSkill` gate.
7
+ *
8
+ * Keyed by the live `Agent` handle the tools registry passes as `exec.agent`:
9
+ * one per session and stable across turns, so a WeakMap entry lives exactly as
10
+ * long as the session and needs no eviction pass. An execution without an
11
+ * agent (a direct internal call) is never gated.
12
+ */
13
+ const skillPackReads = new WeakMap();
14
+ /** Upper bound on the text one `comfyui_skill action: read` returns. The pack
15
+ * caps files at 256 KB, which is still far more than a tool result should
16
+ * inject in one go; a longer file comes back truncated with a marker. */
17
+ const MAX_TOOL_READ_CHARS = 40_000;
18
+ function markSkillPackRead(agent, workflowId) {
19
+ if (typeof agent !== 'object' || agent === null)
20
+ return;
21
+ const seen = skillPackReads.get(agent);
22
+ if (seen === undefined)
23
+ skillPackReads.set(agent, new Set([workflowId]));
24
+ else
25
+ seen.add(workflowId);
26
+ }
27
+ function hasReadSkillPack(agent, workflowId) {
28
+ if (typeof agent !== 'object' || agent === null)
29
+ return true;
30
+ return skillPackReads.get(agent)?.has(workflowId) === true;
31
+ }
4
32
  const TOOL_TIMEOUT_MS = 3_600_000;
5
33
  function missing(args, name) {
6
34
  return args[name] === undefined || args[name] === null;
@@ -324,6 +352,32 @@ function workflowDefinition(runtime, ctx) {
324
352
  schema: { type: 'object' },
325
353
  render(_args, value) {
326
354
  const data = value;
355
+ if (data.action === 'skill') {
356
+ const pack = data.skill;
357
+ if (pack === undefined)
358
+ return [{ type: 'text', text: `ComfyUI workflow ${data.name ?? data.id} 没有技能包。` }];
359
+ // Same framing the host uses for its own skills: a named block, the
360
+ // resource base, then the body verbatim. The reference files stay on
361
+ // disk until the body sends the model after one of them.
362
+ const others = pack.files.filter((file) => file !== 'SKILL.md');
363
+ return [{
364
+ type: 'text',
365
+ text: [
366
+ `<skill_content name="${pack.workflowName}">`,
367
+ '<skill_resources>',
368
+ `Base directory for this skill: ${pack.resourceBase}`,
369
+ others.length > 0
370
+ ? `Files in this pack: ${others.join(', ')} — resolve them against the base directory and read one only when the instructions below point at it.`
371
+ : 'This pack has no reference files.',
372
+ '</skill_resources>',
373
+ '',
374
+ '<skill_instructions>',
375
+ pack.body,
376
+ '</skill_instructions>',
377
+ '</skill_content>',
378
+ ].join('\n'),
379
+ }];
380
+ }
327
381
  if (data.action === 'get') {
328
382
  return [{ type: 'text', text: `ComfyUI workflow ${data.name ?? data.id}: ${JSON.stringify(data.workflow)}` }];
329
383
  }
@@ -349,6 +403,11 @@ function workflowDefinition(runtime, ctx) {
349
403
  lines.push(`Saved ComfyUI workflows (${data.workflows?.length ?? 0}):`);
350
404
  for (const workflow of data.workflows ?? []) {
351
405
  lines.push(`- ${workflow.id} — ${workflow.name}${workflow.description !== '' ? `: ${workflow.description}` : ''}`);
406
+ const skill = workflow.skill;
407
+ if (skill !== undefined) {
408
+ const extra = skill.files > 1 ? `,另有 ${skill.files - 1} 篇参考文档` : '';
409
+ lines.push(` 技能包${skill.required ? '(运行前必读)' : ''}: ${skill.summary}${extra} — 运行前先 action: skill { id: "${workflow.id}" }`);
410
+ }
352
411
  for (const param of workflow.parameters ?? []) {
353
412
  const def = typeof param.default === 'string' ? `"${param.default}"` : String(param.default);
354
413
  const options = Array.isArray(param.options) && param.options.length > 0 ? `,可选: ${param.options.join(' / ')}` : '';
@@ -394,8 +453,8 @@ function workflowDefinition(runtime, ctx) {
394
453
  timeoutMs: TOOL_TIMEOUT_MS,
395
454
  async execute(args, exec) {
396
455
  const action = args.action;
397
- if (action !== 'list' && action !== 'run' && action !== 'get' && action !== 'refresh') {
398
- throw new Error(`comfyui_workflow: action must be list, run, get, or refresh, got ${String(action)}`);
456
+ if (action !== 'list' && action !== 'run' && action !== 'get' && action !== 'refresh' && action !== 'skill') {
457
+ throw new Error(`comfyui_workflow: action must be list, run, skill, get, or refresh, got ${String(action)}`);
399
458
  }
400
459
  if (action === 'list') {
401
460
  const [workflows, comfyui, slots] = await Promise.all([
@@ -407,6 +466,23 @@ function workflowDefinition(runtime, ctx) {
407
466
  // loader parameters take these files in slot order, so the model can
408
467
  // see how many references a run will pick up without asking.
409
468
  const loaded = slots.filter((slot) => slot !== null);
469
+ // Rung one of the skill-pack disclosure ladder: a workflow that has a
470
+ // pack contributes ONE summary line here, not its body. The model
471
+ // reaches for `action: skill` only after this listing points it at a
472
+ // specific workflow, so an unused pack costs nothing.
473
+ const packs = new Map();
474
+ await Promise.all(workflows
475
+ .filter((workflow) => workflow.skillDir !== undefined && workflow.skillDir !== '')
476
+ .map(async (workflow) => {
477
+ const pack = await runtime.skillPacks.infoFor(workflow).catch(() => undefined);
478
+ if (pack === undefined)
479
+ return;
480
+ packs.set(workflow.id, {
481
+ summary: pack.summary !== '' ? pack.summary : `${workflow.name} 的使用说明`,
482
+ files: pack.files.length,
483
+ required: pack.required,
484
+ });
485
+ }));
410
486
  return {
411
487
  action: 'list',
412
488
  // Local environment the model can rely on: the ComfyUI server
@@ -426,6 +502,7 @@ function workflowDefinition(runtime, ctx) {
426
502
  id,
427
503
  name,
428
504
  description,
505
+ ...(packs.has(id) ? { skill: packs.get(id) } : {}),
429
506
  parameters: (parameters ?? []).map(({ name: pname, label, type, default: def, random, numberKind, options, upload }) => {
430
507
  // DSH validates tool output as lossless JSON: JSON.stringify drops
431
508
  // undefined keys, so omit optional fields instead of passing undefined.
@@ -449,7 +526,7 @@ function workflowDefinition(runtime, ctx) {
449
526
  }
450
527
  const id = args.id;
451
528
  if (typeof id !== 'string' || id === '') {
452
- throw new Error('comfyui_workflow: id is required for action: run, get and refresh');
529
+ throw new Error('comfyui_workflow: id is required for action: run, get, refresh and skill');
453
530
  }
454
531
  const saved = await runtime.getWorkflow(id);
455
532
  if (saved === undefined) {
@@ -465,6 +542,26 @@ function workflowDefinition(runtime, ctx) {
465
542
  workflow: saved.workflow,
466
543
  };
467
544
  }
545
+ if (action === 'skill') {
546
+ const pack = await runtime.skillPacks.load(saved.id);
547
+ if (!pack.ok) {
548
+ throw new Error(`comfyui_workflow: ${pack.error}`);
549
+ }
550
+ // Reading the pack is what opens the `requireSkill` gate below.
551
+ markSkillPackRead(exec.agent, saved.id);
552
+ return {
553
+ action: 'skill',
554
+ id: saved.id,
555
+ name: saved.name,
556
+ skill: {
557
+ workflowName: pack.value.workflowName,
558
+ summary: pack.value.summary,
559
+ body: pack.value.body,
560
+ resourceBase: pack.value.resourceBase,
561
+ files: pack.value.files,
562
+ },
563
+ };
564
+ }
468
565
  if (action === 'refresh') {
469
566
  // The library snapshot is captured at save time and never re-reads
470
567
  // object_info, so a voice library that grew since then would reject
@@ -495,6 +592,12 @@ function workflowDefinition(runtime, ctx) {
495
592
  changed,
496
593
  };
497
594
  }
595
+ // The 必读 gate: workflows the user marked `requireSkill` refuse to run
596
+ // until this session has actually loaded their pack. The reminder in the
597
+ // listing is advisory; this is the part that holds.
598
+ if (saved.requireSkill === true && saved.skillDir !== undefined && !hasReadSkillPack(exec.agent, saved.id)) {
599
+ throw new Error(`comfyui_workflow: 工作流 "${saved.name}" 标记了运行前必读技能包 — 先调用 action: skill { id: "${saved.id}" } 读完再运行。`);
600
+ }
498
601
  const config = runtime.getConfig();
499
602
  const client = runtime.createClient(await runtime.getApiKey());
500
603
  const values = typeof args.parameters === 'object' && args.parameters !== null
@@ -561,6 +664,188 @@ function workflowDefinition(runtime, ctx) {
561
664
  },
562
665
  };
563
666
  }
667
+ /**
668
+ * `comfyui_skill`: read and write one workflow's skill pack.
669
+ *
670
+ * The pack is documentation the agent is expected to consult before running a
671
+ * workflow (`comfyui_workflow action: skill`), and this tool is the other half:
672
+ * the agent can also author it — record a pitfall it just hit, add a style
673
+ * reference, lay out its own folders — with the same validation, size caps, and
674
+ * path containment the panel goes through.
675
+ *
676
+ * Destroying a pack is deliberately absent. The files are hand-written by the
677
+ * user and have no other copy, so removing the whole thing stays a panel
678
+ * gesture behind an explicit confirmation; the agent can delete a file it owns
679
+ * but cannot wipe the directory.
680
+ */
681
+ function skillDefinition(runtime) {
682
+ return {
683
+ name: 'comfyui_skill',
684
+ description: [
685
+ "Read and write one workflow's skill pack: the SKILL.md the agent reads before running that workflow, plus its reference files, scripts, templates and assets.",
686
+ '`action: list` returns the pack listing (files, sub-directories, byte sizes) and its absolute directory.',
687
+ '`action: read` returns one file (path relative to the pack, e.g. `references/styles.md`); reading `SKILL.md` also satisfies the 必读 gate that blocks `comfyui_workflow action: run` for workflows marked required.',
688
+ '`action: write` creates or overwrites one file (`content`); pass `summary` alongside when writing SKILL.md to set the one-line summary the workflow listing shows. `action: append` adds to the end of an existing file instead — the right choice for recording a newly discovered pitfall without rewriting the document.',
689
+ '`action: mkdir` creates a sub-directory, `action: rename` moves a file within the pack, `action: delete` removes one file (SKILL.md cannot be renamed or deleted).',
690
+ '`action: enable` attaches a pack to a workflow that has none (seeding SKILL.md), and `action: require` toggles whether running that workflow demands the pack be read first.',
691
+ 'Write documentation the next agent run will need: when to use the workflow, which parameter values matter, what fails. Keep SKILL.md short and put bulk material in separate files — SKILL.md is loaded whole, the other files only when it points at them.',
692
+ ].join(' '),
693
+ parameters: {
694
+ type: 'object',
695
+ properties: {
696
+ action: {
697
+ type: 'string',
698
+ enum: ['list', 'read', 'write', 'append', 'mkdir', 'rename', 'delete', 'enable', 'require'],
699
+ description: 'list the pack; read/write/append/rename/delete one file; mkdir a sub-directory; enable a pack on a workflow; require toggles the read-before-run gate.',
700
+ },
701
+ workflow_id: { type: 'string', description: 'Workflow id from comfyui_workflow action: list.' },
702
+ path: { type: 'string', description: 'Pack-relative file path for read/write/append/rename/delete, e.g. "SKILL.md" or "references/styles.md". One level of sub-directory only.' },
703
+ content: { type: 'string', description: 'File text for write and append.' },
704
+ summary: { type: 'string', description: 'One-line summary stored in SKILL.md frontmatter; this is the line the workflow listing shows, so make it say when to use the workflow.' },
705
+ to: { type: 'string', description: 'New path for rename (a bare name keeps the current directory).' },
706
+ name: { type: 'string', description: 'Sub-directory name for mkdir (letters, digits, CJK, underscore, dash).' },
707
+ required: { type: 'boolean', description: 'For action: require — true refuses to run the workflow until the pack has been read in this session.' },
708
+ },
709
+ required: ['action', 'workflow_id'],
710
+ },
711
+ output: {
712
+ schema: { type: 'object' },
713
+ render(_args, value) {
714
+ const data = value;
715
+ if (data.action === 'read') {
716
+ return [{ type: 'text', text: `${data.path} (${data.workflowName}):\n${data.content ?? ''}${data.truncated === true ? '\n…(文件过大,已截断)' : ''}` }];
717
+ }
718
+ const lines = [];
719
+ if (data.action === 'list') {
720
+ lines.push(`技能包 ${data.workflowName}(${data.dir})${data.required === true ? ' — 运行前必读' : ''}`);
721
+ if (data.summary !== undefined && data.summary !== '')
722
+ lines.push(`摘要: ${data.summary}`);
723
+ for (const file of data.files ?? [])
724
+ lines.push(` ${file.path} (${file.size} B)`);
725
+ const empty = (data.dirs ?? []).filter((dir) => !(data.files ?? []).some((file) => file.path.startsWith(`${dir}/`)));
726
+ if (empty.length > 0)
727
+ lines.push(` 空目录: ${empty.map((dir) => `${dir}/`).join('、')}`);
728
+ return [{ type: 'text', text: lines.join('\n') }];
729
+ }
730
+ lines.push(`技能包 ${data.workflowName} 已更新(${data.action}${data.path !== undefined ? ` ${data.path}` : ''})`);
731
+ for (const file of data.files ?? [])
732
+ lines.push(` ${file.path} (${file.size} B)`);
733
+ return [{ type: 'text', text: lines.join('\n') }];
734
+ },
735
+ presentationMeta(_args, value) {
736
+ return value;
737
+ },
738
+ },
739
+ timeoutMs: TOOL_TIMEOUT_MS,
740
+ async execute(args, exec) {
741
+ const action = args.action;
742
+ const id = args.workflow_id;
743
+ if (typeof id !== 'string' || id === '') {
744
+ throw new Error('comfyui_skill: workflow_id is required');
745
+ }
746
+ const saved = await runtime.getWorkflow(id);
747
+ if (saved === undefined) {
748
+ throw new Error(`comfyui_skill: workflow "${id}" not found — run comfyui_workflow action: list first`);
749
+ }
750
+ const path = typeof args.path === 'string' ? args.path : '';
751
+ const content = typeof args.content === 'string' ? args.content : '';
752
+ const listing = async (label, extra) => {
753
+ const pack = await runtime.skillPacks.info(id);
754
+ if (pack === undefined)
755
+ throw new Error(`comfyui_skill: 工作流 "${saved.name}" 没有技能包 — 先用 action: enable 挂一个`);
756
+ return {
757
+ action: label,
758
+ workflowId: id,
759
+ workflowName: saved.name,
760
+ dir: pack.dir,
761
+ summary: pack.summary,
762
+ required: pack.required,
763
+ files: pack.files.map(({ path: file, size }) => ({ path: file, size })),
764
+ dirs: pack.dirs,
765
+ ...extra,
766
+ };
767
+ };
768
+ if (action === 'enable') {
769
+ const result = await runtime.skillPacks.enable(id);
770
+ if (!result.ok)
771
+ throw new Error(`comfyui_skill: ${result.error}`);
772
+ return listing('enable');
773
+ }
774
+ if (action === 'list')
775
+ return listing('list');
776
+ if (action === 'require') {
777
+ const result = await runtime.skillPacks.setRequired(id, args.required === true);
778
+ if (!result.ok)
779
+ throw new Error(`comfyui_skill: ${result.error}`);
780
+ return listing('require');
781
+ }
782
+ if (action === 'read') {
783
+ if (path === '')
784
+ throw new Error('comfyui_skill: path is required for action: read');
785
+ const file = await runtime.skillPacks.readFile(id, path);
786
+ if (!file.ok)
787
+ throw new Error(`comfyui_skill: ${file.error}`);
788
+ // Reading the main document is the same gesture `comfyui_workflow
789
+ // action: skill` performs, so it opens the run gate too.
790
+ if (path === SKILL_MAIN)
791
+ markSkillPackRead(exec.agent, id);
792
+ const truncated = file.value.length > MAX_TOOL_READ_CHARS;
793
+ return {
794
+ action: 'read',
795
+ workflowId: id,
796
+ workflowName: saved.name,
797
+ path,
798
+ content: truncated ? file.value.slice(0, MAX_TOOL_READ_CHARS) : file.value,
799
+ truncated,
800
+ };
801
+ }
802
+ if (action === 'write' || action === 'append') {
803
+ if (path === '')
804
+ throw new Error(`comfyui_skill: path is required for action: ${action}`);
805
+ let text = content;
806
+ if (action === 'append') {
807
+ const existing = await runtime.skillPacks.readFile(id, path);
808
+ const before = existing.ok ? existing.value : '';
809
+ text = before === '' ? content : `${before.replace(/\s*$/, '')}\n\n${content}`;
810
+ }
811
+ const summary = typeof args.summary === 'string' ? args.summary : undefined;
812
+ const result = path === SKILL_MAIN && summary !== undefined
813
+ ? await runtime.skillPacks.writeFile(id, path, joinFrontmatter(summary, text))
814
+ : await runtime.skillPacks.writeFile(id, path, text);
815
+ if (!result.ok)
816
+ throw new Error(`comfyui_skill: ${result.error}`);
817
+ return listing(action, { path });
818
+ }
819
+ if (action === 'mkdir') {
820
+ const name = typeof args.name === 'string' ? args.name : '';
821
+ const result = await runtime.skillPacks.makeDir(id, name);
822
+ if (!result.ok)
823
+ throw new Error(`comfyui_skill: ${result.error}`);
824
+ return listing('mkdir', { path: `${name}/` });
825
+ }
826
+ if (action === 'rename') {
827
+ const to = typeof args.to === 'string' ? args.to : '';
828
+ if (path === '' || to === '')
829
+ throw new Error('comfyui_skill: path and to are required for action: rename');
830
+ const bucket = path.includes('/') ? `${path.split('/')[0] ?? ''}/` : '';
831
+ const target = to.includes('/') ? to : `${bucket}${to}`;
832
+ const result = await runtime.skillPacks.renameFile(id, path, target);
833
+ if (!result.ok)
834
+ throw new Error(`comfyui_skill: ${result.error}`);
835
+ return listing('rename', { path: target });
836
+ }
837
+ if (action === 'delete') {
838
+ if (path === '')
839
+ throw new Error('comfyui_skill: path is required for action: delete');
840
+ const result = await runtime.skillPacks.deleteFile(id, path);
841
+ if (!result.ok)
842
+ throw new Error(`comfyui_skill: ${result.error}`);
843
+ return listing('delete', { path });
844
+ }
845
+ throw new Error(`comfyui_skill: unknown action ${String(action)}`);
846
+ },
847
+ };
848
+ }
564
849
  /** Register the plugin tools; returns disposers. */
565
850
  export function registerComfyUITools(ctx, runtime) {
566
851
  const tools = ctx.tools;
@@ -568,5 +853,6 @@ export function registerComfyUITools(ctx, runtime) {
568
853
  disposers.push(tools.register(runDefinition(runtime, ctx)));
569
854
  disposers.push(tools.register(objectInfoDefinition(runtime)));
570
855
  disposers.push(tools.register(workflowDefinition(runtime, ctx)));
856
+ disposers.push(tools.register(skillDefinition(runtime)));
571
857
  return disposers;
572
858
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-comfyui",
3
- "version": "0.3.0-beta.5",
4
- "description": "Drive ComfyUI from DeepSeek Harness: generate and process images and videos through agent tools, with a workflow/asset/queue panel, in-chat results and a settings page. / 让 DeepSeek Harness 的 Agent 直接驱动 ComfyUI 生成与处理图像、视频,带工作流/资产/队列面板。",
3
+ "version": "0.4.0",
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",
7
7
  "url": "git+https://github.com/fandc520/dsh-comfyui.git"
@@ -53,16 +53,11 @@
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@deepseek-ai/cordis": "^4.0.1",
56
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7"
57
- },
58
- "peerDependenciesMeta": {
59
- "@deepseek-ai/dsh-settings": {
60
- "optional": true
61
- }
56
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4"
62
57
  },
63
58
  "devDependencies": {
64
59
  "@deepseek-ai/cordis": "^4.0.1",
65
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
60
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
66
61
  "@types/node": "^24.0.0",
67
62
  "@types/react": "~18.3.1",
68
63
  "react": "^18.3.1",