@artflo-ai/artflo-openclaw-plugin 0.0.3 → 0.0.5
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/README.md +1 -0
- package/dist/index.js +16 -1
- package/dist/src/constants.js +1 -0
- package/dist/src/core/api/api-base.js +2 -12
- package/dist/src/core/api/api-paths.js +31 -0
- package/dist/src/core/api/upload-file.js +2 -3
- package/dist/src/core/canvas/canvas-websocket-client.js +2 -2
- package/dist/src/core/canvas/create-canvas.js +3 -13
- package/dist/src/core/config/fetch-client-params.js +2 -1
- package/dist/src/core/config/fetch-vip-info.js +2 -1
- package/dist/src/services/manifest-service.js +37 -0
- package/dist/src/tools/register-tools.js +45 -10
- package/package.json +1 -1
- package/skills/artflo-canvas/SKILL.md +4 -3
- package/skills/artflo-canvas/references/planning-guide.md +2 -2
package/README.md
CHANGED
|
@@ -72,6 +72,7 @@ are auto-detected or fetched from the Artflo API at startup.
|
|
|
72
72
|
- `artflo_canvas_wait_for_completion` — Wait for node completion
|
|
73
73
|
- `artflo_canvas_execute_plan` — Execute a structured workflow plan
|
|
74
74
|
- `artflo_canvas_create` — Create a new canvas
|
|
75
|
+
- `artflo_canvas_get_last` — Get last used canvas id
|
|
75
76
|
- `artflo_canvas_list_models` — List available models with pricing
|
|
76
77
|
- `artflo_upload_file` — Upload files to Artflo storage
|
|
77
78
|
- `artflo_set_api_key` — Save API Key to config (used in chat onboarding)
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ const plugin = {
|
|
|
36
36
|
console.log(`[artflo] API Key not configured. Skipping initialization.`);
|
|
37
37
|
const sessions = createSessionRegistryService(config);
|
|
38
38
|
api.registerService(sessions.service);
|
|
39
|
-
registerArtfloTools(api, { config, sessions: sessions.registry });
|
|
39
|
+
registerArtfloTools(api, { config, workspaceDir: api.rootDir, sessions: sessions.registry });
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
// ── 自动获取 appKey / vipAppId / vipGroup ─────────────────────────
|
|
@@ -66,8 +66,23 @@ const plugin = {
|
|
|
66
66
|
api.registerService(sessions.service);
|
|
67
67
|
registerArtfloTools(api, {
|
|
68
68
|
config,
|
|
69
|
+
workspaceDir: api.rootDir,
|
|
69
70
|
sessions: sessions.registry,
|
|
70
71
|
});
|
|
72
|
+
// ── 每次 prompt 构建时注入 Artflo 配置状态 ──────────────────────
|
|
73
|
+
api.on('before_prompt_build', () => {
|
|
74
|
+
const masked = config.apiKey
|
|
75
|
+
? `****${config.apiKey.slice(-4)}`
|
|
76
|
+
: '未配置';
|
|
77
|
+
return {
|
|
78
|
+
appendSystemContext: [
|
|
79
|
+
`[Artflo Plugin] env=${config.env}, apiKey=${masked}`,
|
|
80
|
+
config.apiKey
|
|
81
|
+
? 'API Key 已配置,可直接使用画布功能。'
|
|
82
|
+
: 'API Key 未配置,请引导用户通过 artflo_set_api_key 设置。',
|
|
83
|
+
].join(' | '),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
71
86
|
},
|
|
72
87
|
};
|
|
73
88
|
export default plugin;
|
package/dist/src/constants.js
CHANGED
|
@@ -29,6 +29,7 @@ export const ARTFLO_TOOL_NAMES = {
|
|
|
29
29
|
waitForCompletion: 'artflo_canvas_wait_for_completion',
|
|
30
30
|
executePlan: 'artflo_canvas_execute_plan',
|
|
31
31
|
createCanvas: 'artflo_canvas_create',
|
|
32
|
+
getLastCanvas: 'artflo_canvas_get_last',
|
|
32
33
|
listModels: 'artflo_canvas_list_models',
|
|
33
34
|
uploadFile: 'artflo_upload_file',
|
|
34
35
|
setApiKey: 'artflo_set_api_key',
|
|
@@ -1,12 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
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
|
-
}
|
|
1
|
+
// Re-export from api-paths for backward compatibility
|
|
2
|
+
export { buildApiUrl, buildWsUrl, buildVipUrl, buildWebUrl } from './api-paths.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All Artflo API paths in one place.
|
|
3
|
+
* Every HTTP/WS request in this plugin MUST use a path from here.
|
|
4
|
+
* Do NOT construct API URLs manually elsewhere.
|
|
5
|
+
*/
|
|
6
|
+
/** WebSocket */
|
|
7
|
+
export const WS_CANVAS = '/canvas/ws';
|
|
8
|
+
/** apiBaseUrl paths */
|
|
9
|
+
export const API_CREATE_CANVAS = '/canvas';
|
|
10
|
+
export const API_UPLOAD_FILE = '/storage/obs/starii';
|
|
11
|
+
/** vipApiBaseUrl paths */
|
|
12
|
+
export const API_VIP_INFO = '/h5/user/vip_info_by_group.json';
|
|
13
|
+
/** webApiBaseUrl paths */
|
|
14
|
+
export const API_CLIENT_PARAMS = '/api/subscribe-client-params';
|
|
15
|
+
// ── Helper builders ─────────────────────────────────────────────────
|
|
16
|
+
function strip(url) {
|
|
17
|
+
return url.replace(/\/+$/, '');
|
|
18
|
+
}
|
|
19
|
+
export function buildApiUrl(config, path) {
|
|
20
|
+
return `${strip(config.apiBaseUrl)}${path}`;
|
|
21
|
+
}
|
|
22
|
+
export function buildVipUrl(config, path) {
|
|
23
|
+
return `${strip(config.vipApiBaseUrl)}${path}`;
|
|
24
|
+
}
|
|
25
|
+
export function buildWebUrl(config, path) {
|
|
26
|
+
return `${strip(config.webApiBaseUrl)}${path}`;
|
|
27
|
+
}
|
|
28
|
+
export function buildWsUrl(config) {
|
|
29
|
+
const hostname = new URL(config.apiBaseUrl).hostname;
|
|
30
|
+
return `wss://${hostname}${WS_CANVAS}`;
|
|
31
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import { basename } from 'node:path';
|
|
10
|
-
import {
|
|
10
|
+
import { buildApiUrl, API_UPLOAD_FILE } from '../api/api-paths.js';
|
|
11
11
|
/**
|
|
12
12
|
* Upload a local file or a remote URL to Artflo OBS.
|
|
13
13
|
*
|
|
@@ -37,8 +37,7 @@ export async function uploadFile(config, source, filename) {
|
|
|
37
37
|
}
|
|
38
38
|
const formData = new FormData();
|
|
39
39
|
formData.append('file', fileBlob, resolvedFilename);
|
|
40
|
-
const
|
|
41
|
-
const response = await fetch(`${baseUrl}/storage/obs/starii`, {
|
|
40
|
+
const response = await fetch(buildApiUrl(config, API_UPLOAD_FILE), {
|
|
42
41
|
method: 'POST',
|
|
43
42
|
headers: {
|
|
44
43
|
'X-API-Key': config.apiKey,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
|
-
import {
|
|
3
|
+
import { buildWsUrl } from '../api/api-paths.js';
|
|
4
4
|
import { writeWsTrace } from './ws-trace.js';
|
|
5
5
|
import { WS_ERROR, isJoinErrorMessage, isJoinSuccessMessage, isPushErrorMessage, isPushSuccessMessage, isSyncPushMessage, } from './types.js';
|
|
6
6
|
export class CanvasWebSocketClient extends EventEmitter {
|
|
@@ -33,7 +33,7 @@ export class CanvasWebSocketClient extends EventEmitter {
|
|
|
33
33
|
queryParams.set('country_code', params.countryCode || this.config.countryCode);
|
|
34
34
|
queryParams.set('api_key', params.apiKey || this.config.apiKey);
|
|
35
35
|
queryParams.set('canvas_id', params.canvasId);
|
|
36
|
-
return `${
|
|
36
|
+
return `${buildWsUrl(this.config)}?${queryParams.toString()}`;
|
|
37
37
|
}
|
|
38
38
|
async connect(params) {
|
|
39
39
|
if (this.connected) {
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Create a new Artflo canvas via the REST API.
|
|
3
|
-
*
|
|
4
|
-
* Derives the HTTP API base URL from the configured WebSocket URL:
|
|
5
|
-
* wss://prewebapi.artflo.ai/canvas/ws → https://prewebapi.artflo.ai
|
|
6
|
-
* wss://webapi.artflo.ai/canvas/ws → https://webapi.artflo.ai
|
|
7
3
|
*/
|
|
8
|
-
import {
|
|
4
|
+
import { buildApiUrl, buildWebUrl, API_CREATE_CANVAS } from '../api/api-paths.js';
|
|
9
5
|
export async function createCanvas(config, name = 'Untitled') {
|
|
10
|
-
const
|
|
11
|
-
const response = await fetch(`${baseUrl}/canvas`, {
|
|
6
|
+
const response = await fetch(buildApiUrl(config, API_CREATE_CANVAS), {
|
|
12
7
|
method: 'POST',
|
|
13
8
|
headers: {
|
|
14
9
|
'Content-Type': 'application/json',
|
|
@@ -24,14 +19,9 @@ export async function createCanvas(config, name = 'Untitled') {
|
|
|
24
19
|
throw new Error(`Create canvas failed: code=${body.code}`);
|
|
25
20
|
}
|
|
26
21
|
const canvasId = body.data.id;
|
|
27
|
-
// Derive project URL from API host: prewebapi → pre.artflo.ai, webapi → artflo.ai
|
|
28
|
-
const hostname = new URL(baseUrl).hostname;
|
|
29
|
-
const projectHost = hostname.startsWith('prewebapi')
|
|
30
|
-
? 'pre.artflo.ai'
|
|
31
|
-
: 'artflo.ai';
|
|
32
22
|
return {
|
|
33
23
|
id: canvasId,
|
|
34
24
|
name: body.data.name ?? name,
|
|
35
|
-
url:
|
|
25
|
+
url: `${buildWebUrl(config, '/project/' + canvasId)}`,
|
|
36
26
|
};
|
|
37
27
|
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Fetch appKey, vipAppId, vipGroup from the subscribe-client-params API.
|
|
3
3
|
* GET {webApiBaseUrl}/api/subscribe-client-params
|
|
4
4
|
*/
|
|
5
|
+
import { API_CLIENT_PARAMS } from '../api/api-paths.js';
|
|
5
6
|
export async function fetchClientParams(webApiBaseUrl) {
|
|
6
|
-
const url = `${webApiBaseUrl.replace(/\/+$/, '')}
|
|
7
|
+
const url = `${webApiBaseUrl.replace(/\/+$/, '')}${API_CLIENT_PARAMS}`;
|
|
7
8
|
const response = await fetch(url);
|
|
8
9
|
if (!response.ok) {
|
|
9
10
|
throw new Error(`fetchClientParams failed: HTTP ${response.status}`);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fetch user VIP / subscription status from the Artflo subscription API.
|
|
3
3
|
*/
|
|
4
|
+
import { buildVipUrl, API_VIP_INFO } from '../api/api-paths.js';
|
|
4
5
|
export async function fetchVipInfo(config) {
|
|
5
|
-
const url = new URL(
|
|
6
|
+
const url = new URL(buildVipUrl(config, API_VIP_INFO));
|
|
6
7
|
url.searchParams.set('app_id', config.vipAppId);
|
|
7
8
|
url.searchParams.set('vip_group', config.vipGroup);
|
|
8
9
|
const response = await fetch(url.toString(), {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const ARTFLO_DIR = '.artflo';
|
|
4
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
5
|
+
function getManifestPath(workspaceDir) {
|
|
6
|
+
return path.join(workspaceDir, ARTFLO_DIR, MANIFEST_FILE);
|
|
7
|
+
}
|
|
8
|
+
async function ensureArtfloDir(workspaceDir) {
|
|
9
|
+
const artfloDir = path.join(workspaceDir, ARTFLO_DIR);
|
|
10
|
+
try {
|
|
11
|
+
await fs.mkdir(artfloDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function readManifest(workspaceDir) {
|
|
18
|
+
const manifestPath = getManifestPath(workspaceDir);
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeManifest(workspaceDir, manifest) {
|
|
28
|
+
await ensureArtfloDir(workspaceDir);
|
|
29
|
+
const manifestPath = getManifestPath(workspaceDir);
|
|
30
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
export async function setProjectId(workspaceDir, id) {
|
|
33
|
+
await writeManifest(workspaceDir, { project: id });
|
|
34
|
+
}
|
|
35
|
+
export async function getProjectId(workspaceDir) {
|
|
36
|
+
return (await readManifest(workspaceDir)).project;
|
|
37
|
+
}
|
|
@@ -22,6 +22,7 @@ import { executePlan as runExecutePlan } from '../core/executor/execute-plan.js'
|
|
|
22
22
|
import { ExecutionTraceWriter } from '../core/executor/execution-trace.js';
|
|
23
23
|
import { parsePlanV2, validateAndFixPlan } from '../core/plan/validate-plan.js';
|
|
24
24
|
import { writeToolTrace } from './tool-trace.js';
|
|
25
|
+
import { getProjectId, setProjectId } from '../services/manifest-service.js';
|
|
25
26
|
export function registerArtfloTools(api, context) {
|
|
26
27
|
api.registerTool({
|
|
27
28
|
name: ARTFLO_TOOL_NAMES.getNode,
|
|
@@ -417,6 +418,9 @@ export function registerArtfloTools(api, context) {
|
|
|
417
418
|
return runWithToolTrace(ARTFLO_TOOL_NAMES.createCanvas, params, async () => {
|
|
418
419
|
const name = params.name || 'Untitled';
|
|
419
420
|
const result = await createCanvas(context.config, name);
|
|
421
|
+
if (context.workspaceDir) {
|
|
422
|
+
await setProjectId(context.workspaceDir, result.id);
|
|
423
|
+
}
|
|
420
424
|
return jsonResult({
|
|
421
425
|
ok: true,
|
|
422
426
|
label: ARTFLO_TOOL_NAMES.createCanvas,
|
|
@@ -429,6 +433,27 @@ export function registerArtfloTools(api, context) {
|
|
|
429
433
|
});
|
|
430
434
|
},
|
|
431
435
|
});
|
|
436
|
+
api.registerTool({
|
|
437
|
+
name: ARTFLO_TOOL_NAMES.getLastCanvas,
|
|
438
|
+
label: 'Artflo Canvas Get Last',
|
|
439
|
+
description: 'Get the last used canvas id. Use this when user requests last used canvas.',
|
|
440
|
+
parameters: Type.Object({}),
|
|
441
|
+
async execute(_id, params) {
|
|
442
|
+
return runWithToolTrace(ARTFLO_TOOL_NAMES.getLastCanvas, params, async () => {
|
|
443
|
+
let canvasId = '';
|
|
444
|
+
if (context.workspaceDir) {
|
|
445
|
+
canvasId = (await getProjectId(context.workspaceDir)) ?? '';
|
|
446
|
+
}
|
|
447
|
+
return jsonResult({
|
|
448
|
+
ok: true,
|
|
449
|
+
label: ARTFLO_TOOL_NAMES.createCanvas,
|
|
450
|
+
data: {
|
|
451
|
+
canvasId: canvasId,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
});
|
|
432
457
|
api.registerTool({
|
|
433
458
|
name: ARTFLO_TOOL_NAMES.listModels,
|
|
434
459
|
label: 'Artflo Canvas List Models',
|
|
@@ -559,17 +584,30 @@ export function registerArtfloTools(api, context) {
|
|
|
559
584
|
});
|
|
560
585
|
api.registerTool({
|
|
561
586
|
name: ARTFLO_TOOL_NAMES.setApiKey,
|
|
562
|
-
label: 'Artflo Set
|
|
563
|
-
description: 'Save
|
|
587
|
+
label: 'Artflo Set Config',
|
|
588
|
+
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.',
|
|
564
589
|
parameters: Type.Object({
|
|
565
|
-
apiKey: Type.String({ minLength: 1, description: '
|
|
590
|
+
apiKey: Type.Optional(Type.String({ minLength: 1, description: 'Artflo API Key.' })),
|
|
591
|
+
env: Type.Optional(Type.String({ enum: ['release', 'test'], description: 'Environment: release or test.' })),
|
|
566
592
|
}),
|
|
567
593
|
async execute(_id, params) {
|
|
568
|
-
const { apiKey } = params;
|
|
594
|
+
const { apiKey, env } = params;
|
|
595
|
+
if (!apiKey && !env) {
|
|
596
|
+
return jsonResult({
|
|
597
|
+
ok: false,
|
|
598
|
+
label: ARTFLO_TOOL_NAMES.setApiKey,
|
|
599
|
+
error: 'At least one of apiKey or env must be provided.',
|
|
600
|
+
});
|
|
601
|
+
}
|
|
569
602
|
try {
|
|
570
603
|
const cfg = api.runtime.config.loadConfig();
|
|
571
604
|
const pluginEntry = cfg.plugins?.entries?.['artflo-openclaw-plugin'] ?? {};
|
|
572
605
|
const existingConfig = pluginEntry.config ?? {};
|
|
606
|
+
const updatedConfig = { ...existingConfig };
|
|
607
|
+
if (apiKey)
|
|
608
|
+
updatedConfig.apiKey = apiKey;
|
|
609
|
+
if (env)
|
|
610
|
+
updatedConfig.env = env;
|
|
573
611
|
const nextConfig = {
|
|
574
612
|
...cfg,
|
|
575
613
|
plugins: {
|
|
@@ -579,10 +617,7 @@ export function registerArtfloTools(api, context) {
|
|
|
579
617
|
'artflo-openclaw-plugin': {
|
|
580
618
|
...pluginEntry,
|
|
581
619
|
enabled: true,
|
|
582
|
-
config:
|
|
583
|
-
...existingConfig,
|
|
584
|
-
apiKey,
|
|
585
|
-
},
|
|
620
|
+
config: updatedConfig,
|
|
586
621
|
},
|
|
587
622
|
},
|
|
588
623
|
},
|
|
@@ -592,8 +627,8 @@ export function registerArtfloTools(api, context) {
|
|
|
592
627
|
ok: true,
|
|
593
628
|
label: ARTFLO_TOOL_NAMES.setApiKey,
|
|
594
629
|
data: {
|
|
595
|
-
message: '
|
|
596
|
-
saved: true,
|
|
630
|
+
message: 'Config saved. Gateway will auto-reload.',
|
|
631
|
+
saved: { apiKey: apiKey ? true : false, env: env || null },
|
|
597
632
|
},
|
|
598
633
|
});
|
|
599
634
|
}
|
package/package.json
CHANGED
|
@@ -34,8 +34,7 @@ metadata:
|
|
|
34
34
|
> 请登录 [artflo.ai](https://artflo.ai),在设置页面获取 API Key,然后直接发给我,我帮你配置好。
|
|
35
35
|
|
|
36
36
|
3. 当用户发来 API Key 后,调用 `artflo_set_api_key` 工具保存到配置中。
|
|
37
|
-
4.
|
|
38
|
-
5. 重启后即可正常使用所有画布功能。
|
|
37
|
+
4. 保存成功后,gateway 会自动重新加载配置,稍等片刻即可正常使用所有画布功能。
|
|
39
38
|
|
|
40
39
|
## 操作策略
|
|
41
40
|
|
|
@@ -48,6 +47,8 @@ metadata:
|
|
|
48
47
|
- 规划时不要臆造底层画布数字节点类型。应使用 plan 中的节点类型,例如 `input`、`refine`、`process`、`selector`、`batch`、`crop`。
|
|
49
48
|
- 在假设画布运行时已就绪之前,先读取连接状态。
|
|
50
49
|
- 将插件配置视为 WebSocket 凭证和连接默认值的唯一真实来源。
|
|
50
|
+
- **禁止编造 API URL**:不要自行拼接或猜测 Artflo API 路径。所有 API 交互必须通过插件提供的 tool 完成(如 `artflo_canvas_get_last`、`artflo_canvas_create`、`artflo_upload_file` 等)。不要使用 `fetch` 或 `curl` 直接调用 Artflo API。
|
|
51
|
+
- **资源保存路径**:画布生成完成后需要下载保存的资源(图片、视频等),必须保存到 `/artflo/outbound` 目录。不要使用 `/tmp` 或其他临时目录。
|
|
51
52
|
- 当插件布局引擎可用时,不要手工编造节点坐标。
|
|
52
53
|
|
|
53
54
|
## 默认编辑规则
|
|
@@ -63,7 +64,7 @@ metadata:
|
|
|
63
64
|
|
|
64
65
|
## 推荐流程
|
|
65
66
|
|
|
66
|
-
1. 检查是否有可用的 canvas id。如果没有,调用 `artflo_canvas_create` 创建新画布,并将返回的 URL 分享给用户。
|
|
67
|
+
1. 检查是否有可用的 canvas id。如果没有,调用 `artflo_canvas_get_last` 获取上次使用的画布,没有的话调用 `artflo_canvas_create` 创建新画布,并将返回的 URL 分享给用户。
|
|
67
68
|
2. 调用 `artflo_canvas_connection_status` 检查连接状态。
|
|
68
69
|
3. 如果未连接,使用目标 canvas id 调用 `artflo_canvas_connect`。
|
|
69
70
|
4. **调用 `artflo_canvas_get_config` 获取可用模型列表、订阅状态和 Prompt Refine 分类。** 或者调用 `artflo_canvas_list_models` 获取更详细的模型信息(含分辨率价格、订阅要求等),适合需要向用户展示模型选择时使用。
|
|
@@ -7,8 +7,8 @@ Read `references/node-schema.json` first for node type constraints, then use thi
|
|
|
7
7
|
|
|
8
8
|
Before executing any workflow, ensure you have a canvas:
|
|
9
9
|
|
|
10
|
-
1. **No canvas id available**:
|
|
11
|
-
2. **Canvas id available**: Use the existing canvas. On the first task of each day, ask the user whether to create a new canvas or reuse the existing one.
|
|
10
|
+
1. **No canvas id available**: First try `artflo_canvas_get_last` to retrieve the user's most recent canvas. If found, use its `canvasId` for subsequent operations. Only call `artflo_canvas_create` if `artflo_canvas_get_last` returns no canvas or fails. Share the canvas `url` with the user.
|
|
11
|
+
2. **Canvas id available**: Use the existing canvas. On the first task of each day, ask the user whether to create a new canvas or reuse the existing one. The canvas URL is returned by `artflo_canvas_create` or `artflo_canvas_get_last`.
|
|
12
12
|
3. **User requests new canvas**: Call `artflo_canvas_create` immediately.
|
|
13
13
|
|
|
14
14
|
## Core Principle
|