@artflo-ai/artflo-openclaw-plugin 0.0.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 (59) hide show
  1. package/README.md +102 -0
  2. package/dist/index.js +73 -0
  3. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-00-216Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  4. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-00-217Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  5. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-05-727Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  6. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-30-218Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  7. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-30-218Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  8. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-35-728Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  9. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-00-218Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  10. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-00-219Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  11. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-05-729Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  12. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-30-220Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  13. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-30-220Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  14. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-35-729Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  15. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-00-221Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  16. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-00-221Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  17. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-05-730Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  18. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-30-222Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  19. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-30-222Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  20. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-35-731Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  21. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-00-223Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  22. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-00-223Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  23. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-05-732Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  24. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-30-223Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  25. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-30-223Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  26. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-35-734Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  27. package/dist/logs/ws-traffic-traces/2026-03-27T20-34-00-228Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  28. package/dist/logs/ws-traffic-traces/2026-03-27T20-34-00-229Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  29. package/dist/src/config.js +57 -0
  30. package/dist/src/constants.js +35 -0
  31. package/dist/src/core/api/api-base.js +12 -0
  32. package/dist/src/core/api/upload-file.js +59 -0
  33. package/dist/src/core/canvas/canvas-session-manager.js +189 -0
  34. package/dist/src/core/canvas/canvas-websocket-client.js +453 -0
  35. package/dist/src/core/canvas/create-canvas.js +37 -0
  36. package/dist/src/core/canvas/types.js +23 -0
  37. package/dist/src/core/canvas/ws-trace.js +42 -0
  38. package/dist/src/core/config/fetch-client-params.js +20 -0
  39. package/dist/src/core/config/fetch-vip-info.js +30 -0
  40. package/dist/src/core/config/model-config-transformer.js +104 -0
  41. package/dist/src/core/executor/element-builders.js +216 -0
  42. package/dist/src/core/executor/execute-plan.js +1221 -0
  43. package/dist/src/core/executor/execution-trace.js +34 -0
  44. package/dist/src/core/layout/layout-service.js +366 -0
  45. package/dist/src/core/plan/analyze-plan-groups.js +71 -0
  46. package/dist/src/core/plan/types.js +1 -0
  47. package/dist/src/core/plan/validate-plan.js +159 -0
  48. package/dist/src/paths.js +16 -0
  49. package/dist/src/services/canvas-session-registry.js +57 -0
  50. package/dist/src/tools/register-tools.js +669 -0
  51. package/dist/src/tools/tool-trace.js +19 -0
  52. package/openclaw.plugin.json +33 -0
  53. package/package.json +42 -0
  54. package/skills/artflo-canvas/SKILL.md +118 -0
  55. package/skills/artflo-canvas/references/graph-rules.md +53 -0
  56. package/skills/artflo-canvas/references/layout-notes.md +31 -0
  57. package/skills/artflo-canvas/references/node-schema.json +948 -0
  58. package/skills/artflo-canvas/references/node-schema.md +188 -0
  59. package/skills/artflo-canvas/references/planning-guide.md +321 -0
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Artflo OpenClaw Plugin
2
+
3
+ OpenClaw plugin for Artflo canvas operations over direct WebSocket.
4
+
5
+ ## Quick Start
6
+
7
+ ### Install
8
+
9
+ ```bash
10
+ openclaw plugins install @artflo-ai/artflo-openclaw-plugin
11
+ openclaw plugins enable artflo-openclaw-plugin
12
+ openclaw gateway restart
13
+ ```
14
+
15
+ ### Configure
16
+
17
+ Only one required parameter: `apiKey`.
18
+
19
+ The easiest way: just start chatting. The plugin will detect missing API Key
20
+ and guide you through setup in the conversation.
21
+
22
+ Or manually edit `~/.openclaw/openclaw.json`:
23
+
24
+ ```json
25
+ {
26
+ "artflo-openclaw-plugin": {
27
+ "enabled": true,
28
+ "config": {
29
+ "apiKey": "YOUR_API_KEY"
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Get your API Key from [artflo.ai](https://artflo.ai) settings page.
36
+
37
+ ### Switch Environment
38
+
39
+ Default is `release` (production). For test environment:
40
+
41
+ ```json
42
+ {
43
+ "config": {
44
+ "env": "test",
45
+ "apiKey": "YOUR_API_KEY"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ | Parameter | Required | Default | Description |
53
+ |---|---|---|---|
54
+ | `apiKey` | Yes | — | Artflo API Key |
55
+ | `env` | No | `release` | `release` (production) or `test` |
56
+ | `operSystem` | No | `7` | Client OS identifier |
57
+
58
+ Other parameters (`appKey`, `deviceId`, `timeZone`, `countryCode`, etc.)
59
+ are auto-detected or fetched from the Artflo API at startup.
60
+
61
+ ## Tools
62
+
63
+ - `artflo_canvas_connect` — Connect to canvas WebSocket
64
+ - `artflo_canvas_connection_status` — Check connection status
65
+ - `artflo_canvas_get_state` — Get canvas state
66
+ - `artflo_canvas_get_config` — Get available models and config
67
+ - `artflo_canvas_get_node` — Read a single node
68
+ - `artflo_canvas_find_nodes` — Search nodes
69
+ - `artflo_canvas_change_elements` — Modify nodes
70
+ - `artflo_canvas_delete_elements` — Delete nodes
71
+ - `artflo_canvas_run_nodes` — Execute nodes
72
+ - `artflo_canvas_wait_for_completion` — Wait for node completion
73
+ - `artflo_canvas_execute_plan` — Execute a structured workflow plan
74
+ - `artflo_canvas_create` — Create a new canvas
75
+ - `artflo_canvas_list_models` — List available models with pricing
76
+ - `artflo_upload_file` — Upload files to Artflo storage
77
+ - `artflo_set_api_key` — Save API Key to config (used in chat onboarding)
78
+
79
+ ## Local Development
80
+
81
+ ```bash
82
+ git clone <repo-url>
83
+ cd artflo-openclaw-ws-plugin
84
+ pnpm install
85
+ pnpm check # type check only
86
+ pnpm build # compile to dist/
87
+ ```
88
+
89
+ ### Link for local testing
90
+
91
+ ```bash
92
+ openclaw plugins install -l .
93
+ openclaw plugins enable artflo-openclaw-plugin
94
+ openclaw gateway restart
95
+ ```
96
+
97
+ ## Publish
98
+
99
+ ```bash
100
+ pnpm build
101
+ npm publish
102
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ import { createPluginConfig } from './src/config.js';
2
+ import { createSessionRegistryService } from './src/services/canvas-session-registry.js';
3
+ import { registerArtfloTools } from './src/tools/register-tools.js';
4
+ import { fetchClientParams } from './src/core/config/fetch-client-params.js';
5
+ import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './src/constants.js';
6
+ const pluginConfigSchema = {
7
+ type: 'object',
8
+ additionalProperties: false,
9
+ properties: {
10
+ env: {
11
+ type: 'string',
12
+ enum: ['release', 'test'],
13
+ default: 'release',
14
+ description: 'Environment: release (production) or test.',
15
+ },
16
+ apiKey: {
17
+ type: 'string',
18
+ description: 'Artflo API Key for authentication. Get it from https://artflo.ai/settings.',
19
+ },
20
+ operSystem: {
21
+ type: 'string',
22
+ default: '7',
23
+ description: 'Client oper_system identifier for WebSocket connections.',
24
+ },
25
+ },
26
+ };
27
+ const plugin = {
28
+ id: PLUGIN_ID,
29
+ name: PLUGIN_NAME,
30
+ description: PLUGIN_DESCRIPTION,
31
+ configSchema: pluginConfigSchema,
32
+ async register(api) {
33
+ const config = createPluginConfig(api);
34
+ // ── 没有 apiKey 时跳过所有初始化,只注册 tool(tool 内部会检查 apiKey)──
35
+ if (!config.apiKey) {
36
+ console.log(`[artflo] API Key not configured. Skipping initialization.`);
37
+ const sessions = createSessionRegistryService(config);
38
+ api.registerService(sessions.service);
39
+ registerArtfloTools(api, { config, sessions: sessions.registry });
40
+ return;
41
+ }
42
+ // ── 自动获取 appKey / vipAppId / vipGroup ─────────────────────────
43
+ if (!config.appKey || !config.vipAppId || !config.vipGroup) {
44
+ try {
45
+ const params = await fetchClientParams(config.webApiBaseUrl);
46
+ if (!config.appKey)
47
+ config.appKey = params.appKey;
48
+ if (!config.vipAppId)
49
+ config.vipAppId = params.vipAppId;
50
+ if (!config.vipGroup)
51
+ config.vipGroup = params.vipGroup;
52
+ console.log(`[artflo] client params fetched: appKey=${config.appKey}, env=${config.env}`);
53
+ }
54
+ catch (err) {
55
+ console.warn(`[artflo] Failed to fetch client params, using defaults:`, err instanceof Error ? err.message : String(err));
56
+ if (!config.appKey)
57
+ config.appKey = 'A40CB4D42E28F808';
58
+ if (!config.vipAppId)
59
+ config.vipAppId = '7029803307044000000';
60
+ if (!config.vipGroup)
61
+ config.vipGroup = 'group_artflo';
62
+ }
63
+ }
64
+ console.log(`[artflo] env=${config.env}, api=${config.apiBaseUrl}`);
65
+ const sessions = createSessionRegistryService(config);
66
+ api.registerService(sessions.service);
67
+ registerArtfloTools(api, {
68
+ config,
69
+ sessions: sessions.registry,
70
+ });
71
+ },
72
+ };
73
+ export default plugin;
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:00.217Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:00.217Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:05.728Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:30.218Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:30.218Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:30:35.728Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:00.219Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:00.219Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:05.729Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:30.220Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:30.220Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:31:35.730Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:00.222Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:00.221Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:05.730Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:30.222Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:30.222Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:32:35.731Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:00.223Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:00.223Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:05.732Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:30.223Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:30.223Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:33:35.734Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a",
6
+ "payload": {
7
+ "canvasId": "2c637466-6c6f-6172-400c-15145e021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:34:00.229Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a",
6
+ "payload": {
7
+ "canvasId": "2b637466-6c6f-6172-470c-151459021b0a"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "createdAt": "2026-03-27T12:34:00.229Z",
3
+ "direction": "lifecycle",
4
+ "event": "heartbeat:ping",
5
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a",
6
+ "payload": {
7
+ "canvasId": "2d637466-6c6f-6172-410c-15145f021b0a"
8
+ }
9
+ }
@@ -0,0 +1,57 @@
1
+ import { createHash } from 'node:crypto';
2
+ import os from 'node:os';
3
+ import { ENV_CONFIG, DEFAULT_MAX_RECONNECT_ATTEMPTS, DEFAULT_OPER_SYSTEM, DEFAULT_REQUEST_TIMEOUT_MS, } from './constants.js';
4
+ function readString(value, fallback) {
5
+ return typeof value === 'string' && value.length > 0 ? value : fallback;
6
+ }
7
+ function readNumber(value, fallback) {
8
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
9
+ }
10
+ function buildStableDefaultDeviceId() {
11
+ const fingerprint = createHash('sha1')
12
+ .update(`${os.hostname()}:${os.userInfo().username}`)
13
+ .digest('hex')
14
+ .slice(0, 8);
15
+ return `artflo-openclaw-${fingerprint}`;
16
+ }
17
+ function detectTimeZone() {
18
+ try {
19
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Asia/Shanghai';
20
+ }
21
+ catch {
22
+ return 'Asia/Shanghai';
23
+ }
24
+ }
25
+ function detectCountryCode() {
26
+ try {
27
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale || '';
28
+ const parts = locale.split('-');
29
+ return parts.length >= 2 ? parts[parts.length - 1].toUpperCase() : 'CN';
30
+ }
31
+ catch {
32
+ return 'CN';
33
+ }
34
+ }
35
+ export function createPluginConfig(api) {
36
+ const pluginConfig = api.pluginConfig ?? {};
37
+ const env = (typeof pluginConfig.env === 'string' && (pluginConfig.env === 'release' || pluginConfig.env === 'test')
38
+ ? pluginConfig.env
39
+ : 'release');
40
+ const urls = ENV_CONFIG[env];
41
+ return {
42
+ env,
43
+ apiBaseUrl: urls.apiBaseUrl,
44
+ vipApiBaseUrl: urls.vipApiBaseUrl,
45
+ webApiBaseUrl: urls.webApiBaseUrl,
46
+ apiKey: readString(pluginConfig.apiKey, ''),
47
+ appKey: readString(pluginConfig.appKey, ''),
48
+ vipAppId: readString(pluginConfig.vipAppId, ''),
49
+ vipGroup: readString(pluginConfig.vipGroup, ''),
50
+ deviceId: readString(pluginConfig.deviceId, buildStableDefaultDeviceId()),
51
+ timeZone: readString(pluginConfig.timeZone, detectTimeZone()),
52
+ countryCode: readString(pluginConfig.countryCode, detectCountryCode()),
53
+ operSystem: readString(pluginConfig.operSystem, DEFAULT_OPER_SYSTEM),
54
+ requestTimeoutMs: readNumber(pluginConfig.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS),
55
+ maxReconnectAttempts: readNumber(pluginConfig.maxReconnectAttempts, DEFAULT_MAX_RECONNECT_ATTEMPTS),
56
+ };
57
+ }
@@ -0,0 +1,35 @@
1
+ export const PLUGIN_ID = 'artflo-openclaw-plugin';
2
+ export const PLUGIN_NAME = 'Artflo OpenClaw Plugin';
3
+ export const PLUGIN_DESCRIPTION = 'Expose deterministic Artflo canvas tools over direct WebSocket execution.';
4
+ export const ENV_CONFIG = {
5
+ release: {
6
+ apiBaseUrl: 'https://webapi.artflo.ai',
7
+ vipApiBaseUrl: 'https://api-h5-sub.starii.com',
8
+ webApiBaseUrl: 'https://artflo.ai',
9
+ },
10
+ test: {
11
+ apiBaseUrl: 'https://testwebapi.artflo.ai',
12
+ vipApiBaseUrl: 'https://pre-api-h5-sub.starii.com',
13
+ webApiBaseUrl: 'https://test.artflo.ai',
14
+ },
15
+ };
16
+ export const DEFAULT_OPER_SYSTEM = '7';
17
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
18
+ export const DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
19
+ export const ARTFLO_TOOL_NAMES = {
20
+ connect: 'artflo_canvas_connect',
21
+ connectionStatus: 'artflo_canvas_connection_status',
22
+ getState: 'artflo_canvas_get_state',
23
+ getConfig: 'artflo_canvas_get_config',
24
+ findNodes: 'artflo_canvas_find_nodes',
25
+ getNode: 'artflo_canvas_get_node',
26
+ changeElements: 'artflo_canvas_change_elements',
27
+ deleteElements: 'artflo_canvas_delete_elements',
28
+ runNodes: 'artflo_canvas_run_nodes',
29
+ waitForCompletion: 'artflo_canvas_wait_for_completion',
30
+ executePlan: 'artflo_canvas_execute_plan',
31
+ createCanvas: 'artflo_canvas_create',
32
+ listModels: 'artflo_canvas_list_models',
33
+ uploadFile: 'artflo_upload_file',
34
+ setApiKey: 'artflo_set_api_key',
35
+ };
@@ -0,0 +1,12 @@
1
+ /** HTTP API base URL (trailing slash stripped). */
2
+ export function getApiBaseUrl(config) {
3
+ return config.apiBaseUrl.replace(/\/+$/, '');
4
+ }
5
+ /**
6
+ * Derive the WebSocket URL from apiBaseUrl.
7
+ * https://webapi.artflo.ai → wss://webapi.artflo.ai/canvas/ws
8
+ */
9
+ export function getCanvasWsUrl(config) {
10
+ const hostname = new URL(config.apiBaseUrl).hostname;
11
+ return `wss://${hostname}/canvas/ws`;
12
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Upload a file to Artflo OBS storage.
3
+ *
4
+ * POST /storage/obs/starii (multipart/form-data, field "file")
5
+ * Auth: X-API-Key header.
6
+ * Max 100 MiB; common image/video extensions.
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import { basename } from 'node:path';
10
+ import { getApiBaseUrl } from './api-base.js';
11
+ /**
12
+ * Upload a local file or a remote URL to Artflo OBS.
13
+ *
14
+ * @param source - Local file path or remote https URL.
15
+ * @param config - Plugin config (provides access token and WS URL for API base).
16
+ * @param filename - Optional override for the uploaded filename.
17
+ */
18
+ export async function uploadFile(config, source, filename) {
19
+ let fileBlob;
20
+ let resolvedFilename;
21
+ if (source.startsWith('http://') || source.startsWith('https://')) {
22
+ // Remote URL — download first
23
+ const resp = await fetch(source);
24
+ if (!resp.ok) {
25
+ throw new Error(`Failed to download ${source}: HTTP ${resp.status}`);
26
+ }
27
+ const buffer = await resp.arrayBuffer();
28
+ const contentType = resp.headers.get('content-type') || 'application/octet-stream';
29
+ fileBlob = new Blob([buffer], { type: contentType });
30
+ resolvedFilename = filename || basename(new URL(source).pathname) || 'upload';
31
+ }
32
+ else {
33
+ // Local file path
34
+ const buffer = await readFile(source);
35
+ fileBlob = new Blob([buffer]);
36
+ resolvedFilename = filename || basename(source);
37
+ }
38
+ const formData = new FormData();
39
+ formData.append('file', fileBlob, resolvedFilename);
40
+ const baseUrl = getApiBaseUrl(config);
41
+ const response = await fetch(`${baseUrl}/storage/obs/starii`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'X-API-Key': config.apiKey,
45
+ },
46
+ body: formData,
47
+ });
48
+ if (!response.ok) {
49
+ throw new Error(`Upload failed: HTTP ${response.status}`);
50
+ }
51
+ const body = (await response.json());
52
+ if (body.code !== 0 || !body.data?.url) {
53
+ throw new Error(`Upload failed: code=${body.code}`);
54
+ }
55
+ return {
56
+ url: body.data.url,
57
+ key: body.data.key ?? '',
58
+ };
59
+ }