@artflo-ai/artflo-openclaw-plugin 0.0.10 → 0.0.12

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.
@@ -8,18 +8,54 @@ const VALID_NODE_TYPES = new Set([
8
8
  'video',
9
9
  'batch',
10
10
  ]);
11
+ /**
12
+ * Fallback map: canvas numeric node types → PlanDSL string types.
13
+ * LLMs sometimes emit raw canvas codes instead of the PlanDSL strings.
14
+ */
15
+ const NUMERIC_TYPE_MAP = {
16
+ 100000: 'process', // EDGE — unlikely but safe fallback
17
+ 110000: 'input',
18
+ 110001: 'image',
19
+ 110004: 'video',
20
+ 110006: 'selector',
21
+ 130000: 'process',
22
+ 130500: 'refine',
23
+ 190000: 'crop',
24
+ };
11
25
  function isRecord(value) {
12
26
  return typeof value === 'object' && value !== null;
13
27
  }
28
+ /**
29
+ * Attempt to coerce a raw node type (number or unknown string) into a valid
30
+ * PlanDSL NodeType. Returns the mapped type or `null` if unrecoverable.
31
+ */
32
+ function coerceNodeType(raw) {
33
+ if (typeof raw === 'string') {
34
+ const lower = raw.toLowerCase().trim();
35
+ if (VALID_NODE_TYPES.has(lower)) {
36
+ return lower;
37
+ }
38
+ // Try parsing stringified number ("110000")
39
+ const num = Number(lower);
40
+ if (!Number.isNaN(num) && NUMERIC_TYPE_MAP[num]) {
41
+ return NUMERIC_TYPE_MAP[num];
42
+ }
43
+ return null;
44
+ }
45
+ if (typeof raw === 'number' && NUMERIC_TYPE_MAP[raw]) {
46
+ return NUMERIC_TYPE_MAP[raw];
47
+ }
48
+ return null;
49
+ }
14
50
  export function parsePlanV2(value) {
15
51
  if (!isRecord(value)) {
16
- throw new Error('Plan must be an object');
52
+ throw new Error('Plan must be an object. See artflo_canvas SKILL.md for the PlanDSL V2 schema.');
17
53
  }
18
54
  if (!Array.isArray(value.nodes)) {
19
- throw new Error('Plan nodes must be an array');
55
+ throw new Error('Plan nodes must be an array. See artflo_canvas SKILL.md for the PlanDSL V2 schema.');
20
56
  }
21
57
  if (!Array.isArray(value.edges)) {
22
- throw new Error('Plan edges must be an array');
58
+ throw new Error('Plan edges must be an array. See artflo_canvas SKILL.md for the PlanDSL V2 schema.');
23
59
  }
24
60
  const nodes = value.nodes.map((node, index) => parseNodeSpec(node, index));
25
61
  const edges = value.edges.map((edge, index) => parseEdgeSpec(edge, index));
@@ -44,15 +80,25 @@ function parseNodeSpec(value, index) {
44
80
  if (!isRecord(value)) {
45
81
  throw new Error(`Node at index ${index} must be an object`);
46
82
  }
47
- if (typeof value.ref !== 'string' || value.ref.length === 0) {
83
+ // Accept "ref" or fall back to "id" (canvas format)
84
+ const ref = typeof value.ref === 'string' && value.ref.length > 0
85
+ ? value.ref
86
+ : typeof value.id === 'string' && value.id.length > 0
87
+ ? value.id
88
+ : '';
89
+ if (ref.length === 0) {
48
90
  throw new Error(`Node at index ${index} is missing ref`);
49
91
  }
50
- if (typeof value.type !== 'string' || !VALID_NODE_TYPES.has(value.type)) {
51
- throw new Error(`Node ${value.ref} has invalid type`);
92
+ const resolvedType = coerceNodeType(value.type);
93
+ if (!resolvedType) {
94
+ throw new Error(`Node ${ref} has invalid type "${String(value.type)}". ` +
95
+ `Valid types: ${[...VALID_NODE_TYPES].join(', ')}. ` +
96
+ `Do NOT use numeric canvas types (110000, 130000, etc.). ` +
97
+ `See artflo_canvas SKILL.md for the PlanDSL V2 schema.`);
52
98
  }
53
99
  return {
54
- ref: value.ref,
55
- type: value.type,
100
+ ref,
101
+ type: resolvedType,
56
102
  data: isRecord(value.data) ? value.data : {},
57
103
  dependsOn: typeof value.dependsOn === 'string' ? value.dependsOn : undefined,
58
104
  };
@@ -61,15 +107,31 @@ function parseEdgeSpec(value, index) {
61
107
  if (!isRecord(value)) {
62
108
  throw new Error(`Edge at index ${index} must be an object`);
63
109
  }
64
- if (typeof value.from !== 'string' || typeof value.to !== 'string') {
65
- throw new Error(`Edge at index ${index} must include from and to`);
110
+ // Accept "from"/"to" or fall back to "source"/"target" (canvas format)
111
+ const from = typeof value.from === 'string'
112
+ ? value.from
113
+ : typeof value.source === 'string'
114
+ ? value.source
115
+ : '';
116
+ const to = typeof value.to === 'string'
117
+ ? value.to
118
+ : typeof value.target === 'string'
119
+ ? value.target
120
+ : '';
121
+ if (from.length === 0 || to.length === 0) {
122
+ throw new Error(`Edge at index ${index} must include from and to. ` +
123
+ `See artflo_canvas SKILL.md for the PlanDSL V2 schema.`);
66
124
  }
67
125
  return {
68
126
  ref: typeof value.ref === 'string' ? value.ref : undefined,
69
- from: value.from,
70
- to: value.to,
71
- fromHandle: typeof value.fromHandle === 'string' ? value.fromHandle : undefined,
72
- toHandle: typeof value.toHandle === 'string' ? value.toHandle : undefined,
127
+ from,
128
+ to,
129
+ fromHandle: typeof value.fromHandle === 'string' ? value.fromHandle
130
+ : typeof value.sourceHandle === 'string' ? value.sourceHandle
131
+ : undefined,
132
+ toHandle: typeof value.toHandle === 'string' ? value.toHandle
133
+ : typeof value.targetHandle === 'string' ? value.targetHandle
134
+ : undefined,
73
135
  };
74
136
  }
75
137
  export function validateAndFixPlan(plan) {
@@ -27,7 +27,7 @@ export function registerArtfloTools(api, context) {
27
27
  api.registerTool({
28
28
  name: ARTFLO_TOOL_NAMES.getNode,
29
29
  label: 'Artflo Canvas Get Node',
30
- description: 'Read one node or edge from the current Artflo canvas session by id.',
30
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Read one node or edge from the current Artflo canvas session by id.',
31
31
  parameters: Type.Object({
32
32
  canvasId: Type.String({ minLength: 1 }),
33
33
  id: Type.String({ minLength: 1 }),
@@ -52,7 +52,7 @@ export function registerArtfloTools(api, context) {
52
52
  api.registerTool({
53
53
  name: ARTFLO_TOOL_NAMES.connect,
54
54
  label: 'Artflo Canvas Connect',
55
- description: 'Register or warm a direct Artflo canvas WebSocket session for a canvas id using preconfigured plugin credentials.',
55
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Register or warm a direct Artflo canvas WebSocket session for a canvas id using preconfigured plugin credentials.',
56
56
  parameters: Type.Object({
57
57
  canvasId: Type.String({ minLength: 1 }),
58
58
  }),
@@ -79,7 +79,7 @@ export function registerArtfloTools(api, context) {
79
79
  api.registerTool({
80
80
  name: ARTFLO_TOOL_NAMES.connectionStatus,
81
81
  label: 'Artflo Canvas Connection Status',
82
- description: 'Inspect current plugin-level Artflo WebSocket session state and config readiness.',
82
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Inspect current plugin-level Artflo WebSocket session state and config readiness.',
83
83
  parameters: Type.Object({
84
84
  canvasId: Type.Optional(Type.String()),
85
85
  }),
@@ -107,7 +107,7 @@ export function registerArtfloTools(api, context) {
107
107
  api.registerTool({
108
108
  name: ARTFLO_TOOL_NAMES.getState,
109
109
  label: 'Artflo Canvas State',
110
- description: 'Return scaffold plugin state and planned runtime capabilities for Artflo execution.',
110
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Return scaffold plugin state and planned runtime capabilities for Artflo execution.',
111
111
  parameters: Type.Object({
112
112
  canvasId: Type.Optional(Type.String()),
113
113
  }),
@@ -143,7 +143,7 @@ export function registerArtfloTools(api, context) {
143
143
  api.registerTool({
144
144
  name: ARTFLO_TOOL_NAMES.getConfig,
145
145
  label: 'Artflo Canvas Get Config',
146
- description: 'Return available models, subscription status, and prompt refine categories for the connected canvas. Call this before planning a workflow so you know which models and resolutions are available.',
146
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Return available models, subscription status, and prompt refine categories for the connected canvas. Call this before planning a workflow so you know which models and resolutions are available.',
147
147
  parameters: Type.Object({
148
148
  canvasId: Type.String({ minLength: 1 }),
149
149
  }),
@@ -179,7 +179,7 @@ export function registerArtfloTools(api, context) {
179
179
  api.registerTool({
180
180
  name: ARTFLO_TOOL_NAMES.findNodes,
181
181
  label: 'Artflo Canvas Find Nodes',
182
- description: 'Find nodes or edges in the connected Artflo canvas by ids, type, status, label text, or runnable state.',
182
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Find nodes or edges in the connected Artflo canvas by ids, type, status, label text, or runnable state.',
183
183
  parameters: Type.Object({
184
184
  canvasId: Type.String({ minLength: 1 }),
185
185
  ids: Type.Optional(Type.Array(Type.String())),
@@ -237,7 +237,7 @@ export function registerArtfloTools(api, context) {
237
237
  api.registerTool({
238
238
  name: ARTFLO_TOOL_NAMES.changeElements,
239
239
  label: 'Artflo Canvas Change Elements',
240
- description: 'Apply direct structured element changes to an existing Artflo canvas session.',
240
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Apply direct structured element changes to an existing Artflo canvas session. Do NOT use this tool to create new workflows from scratch — use artflo_canvas_execute_plan instead.',
241
241
  parameters: Type.Object({
242
242
  canvasId: Type.String({ minLength: 1 }),
243
243
  changes: Type.Array(Type.Any(), { minItems: 1 }),
@@ -264,7 +264,7 @@ export function registerArtfloTools(api, context) {
264
264
  api.registerTool({
265
265
  name: ARTFLO_TOOL_NAMES.deleteElements,
266
266
  label: 'Artflo Canvas Delete Elements',
267
- description: 'Delete nodes or edges from the current Artflo canvas session.',
267
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Delete nodes or edges from the current Artflo canvas session.',
268
268
  parameters: Type.Object({
269
269
  canvasId: Type.String({ minLength: 1 }),
270
270
  ids: Type.Array(Type.String(), { minItems: 1 }),
@@ -289,7 +289,7 @@ export function registerArtfloTools(api, context) {
289
289
  api.registerTool({
290
290
  name: ARTFLO_TOOL_NAMES.runNodes,
291
291
  label: 'Artflo Canvas Run Nodes',
292
- description: 'Run one or more executable Artflo nodes by id.',
292
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Run one or more executable Artflo nodes by id.',
293
293
  parameters: Type.Object({
294
294
  canvasId: Type.String({ minLength: 1 }),
295
295
  ids: Type.Array(Type.String(), { minItems: 1 }),
@@ -314,7 +314,7 @@ export function registerArtfloTools(api, context) {
314
314
  api.registerTool({
315
315
  name: ARTFLO_TOOL_NAMES.waitForCompletion,
316
316
  label: 'Artflo Canvas Wait For Completion',
317
- description: 'Wait for one or more executable nodes to reach completion status.',
317
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Wait for one or more executable nodes to reach completion status.',
318
318
  parameters: Type.Object({
319
319
  canvasId: Type.String({ minLength: 1 }),
320
320
  ids: Type.Array(Type.String(), { minItems: 1 }),
@@ -341,7 +341,7 @@ export function registerArtfloTools(api, context) {
341
341
  api.registerTool({
342
342
  name: ARTFLO_TOOL_NAMES.executePlan,
343
343
  label: 'Artflo Execute Plan',
344
- description: 'Execute a structured Artflo plan through deterministic runtime logic. In scaffold phase, validates request shape and returns the next integration milestone.',
344
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for the PlanDSL V2 schema and valid node types. Execute a structured Artflo plan through deterministic runtime logic. Node types must be strings: "input", "process", "refine", "selector", "crop", "image", "video", "batch". Edges must use "from"/"to" fields (not "source"/"target"). Do NOT use numeric canvas types (110000, 130000, etc.).',
345
345
  parameters: Type.Object({
346
346
  canvasId: Type.String({ minLength: 1 }),
347
347
  plan: Type.Object({
@@ -410,7 +410,7 @@ export function registerArtfloTools(api, context) {
410
410
  api.registerTool({
411
411
  name: ARTFLO_TOOL_NAMES.createCanvas,
412
412
  label: 'Artflo Canvas Create',
413
- description: 'Create a new Artflo canvas project. Returns the canvas id and project URL. Use this when no canvas id is available or the user requests a new canvas.',
413
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Create a new Artflo canvas project. Returns the canvas id and project URL. Use this when no canvas id is available or the user requests a new canvas.',
414
414
  parameters: Type.Object({
415
415
  name: Type.Optional(Type.String({ description: 'Canvas name, defaults to "Untitled"' })),
416
416
  }),
@@ -436,7 +436,7 @@ export function registerArtfloTools(api, context) {
436
436
  api.registerTool({
437
437
  name: ARTFLO_TOOL_NAMES.getLastCanvas,
438
438
  label: 'Artflo Canvas Get Last',
439
- description: 'Get the last used canvas id. Use this when user requests last used canvas.',
439
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Get the last used canvas id. Use this when user requests last used canvas.',
440
440
  parameters: Type.Object({}),
441
441
  async execute(_id, params) {
442
442
  return runWithToolTrace(ARTFLO_TOOL_NAMES.getLastCanvas, params, async () => {
@@ -457,7 +457,7 @@ export function registerArtfloTools(api, context) {
457
457
  api.registerTool({
458
458
  name: ARTFLO_TOOL_NAMES.listModels,
459
459
  label: 'Artflo Canvas List Models',
460
- description: 'List all available AI models. When presenting results to the user, you MUST show ALL of these fields in a table: ' +
460
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. List all available AI models. When presenting results to the user, you MUST show ALL of these fields in a table: ' +
461
461
  '1) Model name (use model_name, NEVER expose model_key to user) ' +
462
462
  '2) Credits (base price; if resolutionPricing exists, show per-resolution prices) ' +
463
463
  '3) Supported resolutions ' +
@@ -567,7 +567,7 @@ export function registerArtfloTools(api, context) {
567
567
  api.registerTool({
568
568
  name: ARTFLO_TOOL_NAMES.uploadFile,
569
569
  label: 'Artflo Upload File',
570
- description: 'Upload a local file or a remote URL to Artflo OBS storage. Returns the CDN URL and object key. Use this to upload reference images before creating workflows with image-to-image or image-to-video nodes.',
570
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Upload a local file or a remote URL to Artflo OBS storage. Returns the CDN URL and object key. Use this to upload reference images before creating workflows with image-to-image or image-to-video nodes.',
571
571
  parameters: Type.Object({
572
572
  source: Type.String({
573
573
  description: 'Local file path or remote https URL to upload.',
@@ -592,7 +592,7 @@ export function registerArtfloTools(api, context) {
592
592
  api.registerTool({
593
593
  name: ARTFLO_TOOL_NAMES.getUserStatus,
594
594
  label: 'Artflo Get User Status',
595
- description: 'Get the current user subscription plan and remaining credits. Does not require a canvas connection.',
595
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Get the current user subscription plan and remaining credits. Does not require a canvas connection.',
596
596
  parameters: Type.Object({}),
597
597
  async execute(_id, params) {
598
598
  return runWithToolTrace(ARTFLO_TOOL_NAMES.getUserStatus, params, async () => {
@@ -621,7 +621,7 @@ export function registerArtfloTools(api, context) {
621
621
  api.registerTool({
622
622
  name: ARTFLO_TOOL_NAMES.setApiKey,
623
623
  label: 'Artflo Set Config',
624
- description: 'Save Artflo plugin config (apiKey and/or env). Call this when the user provides their API Key or wants to switch environment. Both parameters are optional — only provided values will be updated.',
624
+ description: '⚠️ Before using this tool, you MUST read the artflo_canvas SKILL.md for usage rules and constraints. Save Artflo plugin config (apiKey and/or env). Call this when the user provides their API Key or wants to switch environment. Both parameters are optional — only provided values will be updated.',
625
625
  parameters: Type.Object({
626
626
  apiKey: Type.Optional(Type.String({ minLength: 1, description: 'Artflo API Key.' })),
627
627
  env: Type.Optional(Type.String({ enum: ['release', 'test'], description: 'Environment: release or test.' })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artflo-ai/artflo-openclaw-plugin",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin that connects directly to Artflo canvas WebSocket runtime.",
6
6
  "keywords": [
@@ -13,6 +13,7 @@
13
13
  "files": [
14
14
  "README.md",
15
15
  "dist",
16
+ "!dist/logs",
16
17
  "openclaw.plugin.json",
17
18
  "skills",
18
19
  "package.json"
@@ -58,7 +58,7 @@ metadata:
58
58
  - 将插件配置视为 WebSocket 凭证和连接默认值的唯一真实来源。
59
59
  - **禁止编造 API URL**:不要自行拼接或猜测 Artflo API 路径。所有 API 交互必须通过插件提供的 tool 完成(如 `artflo_canvas_get_last`、`artflo_canvas_create`、`artflo_upload_file` 等)。不要使用 `fetch` 或 `curl` 直接调用 Artflo API。
60
60
  - **model_key 保密**:`model_key`(如 `Praline_2`、`ToffeeV1-Lite`)是内部标识,仅供构建 plan 使用。向用户展示模型时只使用 `model_name`(显示名称),绝不向用户暴露 `model_key`。
61
- - **资源保存路径**:画布生成完成后需要下载保存的资源(图片、视频等),必须保存到 `/artflo/outbound` 目录。不要使用 `/tmp` 或其他临时目录。
61
+ - **资源保存路径**:画布生成完成后需要下载保存的资源(图片、视频等),必须保存到 `artflo/outbound` 目录。不要使用 `/tmp` 或其他临时目录。
62
62
  - **环境设置保密**:不要主动向用户提及或暴露环境切换功能(release/test)。只有当用户明确提到要切换环境、设置 test 环境时,才使用 `artflo_set_api_key` 的 `env` 参数。
63
63
  - **品牌名称规范**:产品名称是 **Artflo**(A 大写,其余小写)。不要写成 ArtFlo、ARTFLO、artFlo 或其他变体。所有面向用户的文字中必须使用正确的大小写。
64
64
  - **模型列表展示规范**:当用户查看模型列表时,必须展示以下字段,不要省略:
@@ -94,6 +94,7 @@ metadata:
94
94
  8. 调用 `artflo_canvas_execute_plan` 执行该 plan。
95
95
  9. 如果是检查现状,使用 `artflo_canvas_get_state`、`artflo_canvas_get_node` 或 `artflo_canvas_find_nodes`。
96
96
  10. 如果是定向修复,使用 `artflo_canvas_change_elements`、`artflo_canvas_delete_elements`、`artflo_canvas_run_nodes` 和 `artflo_canvas_wait_for_completion`。
97
+ 11. 生成成功必须告知用户源文件的资源链接,与生成文件分开发送
97
98
 
98
99
  ## 规划核心要点
99
100
 
@@ -137,19 +138,3 @@ metadata:
137
138
  4. 如果错误是 "invalid model"(error_code=402),说明选择的模型不可用。调用 `artflo_canvas_get_config` 确认可用模型列表,选择一个 `available: true` 的模型,然后 change + rerun。
138
139
  5. **绝对不要删除已成功执行的上游节点**。只修复出错的那个节点。
139
140
  6. 如果需要重建整个工作流分支,先向用户确认,不要自行决定。
140
-
141
- ## Channel 适配规则
142
-
143
- 向用户发送 Artflo 生成结果时,必须根据当前 Channel 的限制选择合适的发送方式。
144
-
145
- ### 企业微信(WeCom)
146
-
147
- | 资源大小 | 发送方式 |
148
- |---------|---------|
149
- | 图片 ≤ 2 MB | 以图片形式发送 |
150
- | 图片 > 2 MB 且 ≤ 20 MB | 以文件形式发送 |
151
- | 文件 > 20 MB | 直接发送图片/文件的 URL 链接 |
152
-
153
- ### 其他 Channel
154
-
155
- (待补充)