openyida 2026.9.11 → 2026.9.13

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 (82) hide show
  1. package/README.md +7 -5
  2. package/bin/yida.js +1 -0
  3. package/lib/ai/ai.js +2 -0
  4. package/lib/app/canvas-compile.js +29 -26
  5. package/lib/app/compile.js +39 -1
  6. package/lib/app/services/canvas-page-compiler.js +3 -3
  7. package/lib/app/update-app.js +17 -2
  8. package/lib/asset/ai-image.js +37 -65
  9. package/lib/asset/asset-cmd.js +151 -110
  10. package/lib/asset/asset-plan.js +105 -0
  11. package/lib/asset/asset-resolve.js +343 -225
  12. package/lib/asset/asset-status.js +20 -31
  13. package/lib/asset/attachment-upload.js +29 -0
  14. package/lib/asset/host-capabilities.js +91 -0
  15. package/lib/asset/image-metadata.js +104 -0
  16. package/lib/asset/url-verify.js +90 -130
  17. package/lib/core/agent-capabilities.js +11 -0
  18. package/lib/core/command-manifest.js +25 -11
  19. package/lib/core/locales/en.js +6 -2
  20. package/lib/core/locales/zh.js +6 -2
  21. package/lib/design-plan/materialize.js +7 -9
  22. package/lib/design-plan/normalize.js +11 -1
  23. package/lib/process/services/process-actions.js +145 -0
  24. package/lib/process/services/process-compiler.js +4 -30
  25. package/lib/process/services/process-view-verifier.js +31 -0
  26. package/package.json +1 -1
  27. package/scripts/postinstall.js +1 -1
  28. package/yida-skills/SKILL.md +3 -2
  29. package/yida-skills/skills/yida-app/SKILL.md +1 -1
  30. package/yida-skills/skills/yida-app/workflow/step-2-design.md +11 -1
  31. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +21 -1
  32. package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +11 -7
  33. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +4 -0
  34. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +5 -3
  35. package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +1 -1
  36. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +3 -27
  37. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +1 -1
  38. package/yida-skills/skills/yida-create-app/SKILL.md +4 -4
  39. package/yida-skills/skills/yida-create-process/SKILL.md +2 -0
  40. package/yida-skills/skills/yida-custom-page/references/assets-guide.md +9 -12
  41. package/yida-skills/skills/yida-design/SKILL.md +1 -0
  42. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +4 -4
  43. package/yida-skills/skills/yida-design/references/asset-workflow.md +14 -53
  44. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +1 -1
  45. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +21 -4
  46. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1 -2
  47. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +1 -1
  48. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +1 -1
  49. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +1 -1
  50. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +1 -1
  51. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +1 -1
  52. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +1 -1
  53. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +1 -1
  54. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +1 -1
  55. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +1 -1
  56. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +1 -1
  57. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +1 -1
  58. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +1 -1
  59. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +1 -1
  60. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +1 -1
  61. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +1 -1
  62. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +1 -1
  63. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +2 -2
  64. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +1 -1
  65. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +1 -1
  66. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +1 -1
  67. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +1 -1
  68. package/yida-skills/skills/yida-design/workflow/output-design.md +4 -0
  69. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +1 -1
  70. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +2 -1
  71. package/yida-skills/skills/yida-image-assets/SKILL.md +91 -0
  72. package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +87 -0
  73. package/yida-skills/skills/yida-image-assets/references/source-policy.md +19 -0
  74. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +1 -1
  75. package/yida-skills/skills/yida-prd/workflow/output-prd.md +2 -2
  76. package/yida-skills/skills/yida-process-rule/SKILL.md +6 -1
  77. package/yida-skills/skills/yida-process-rule/references/approval-actions.md +48 -0
  78. package/yida-skills/skills/yida-publish-page/SKILL.md +5 -4
  79. package/yida-skills/skills/yida-rechart/SKILL.md +1 -1
  80. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +1 -1
  81. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +22 -13
  82. package/yida-skills/skills-index.json +18 -1
package/README.md CHANGED
@@ -210,6 +210,8 @@ openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-fo
210
210
  openyida get-permission APP_XXX FORM_XXX
211
211
  ```
212
212
 
213
+ Append and forward permissions are configured in the process definition through `nodes[].actions.normalActions/appendActions`. Both `create-process` and `configure-process` compile these settings into matching designer and runtime properties. See [approval action configuration](yida-skills/skills/yida-process-rule/references/approval-actions.md) for the JSON example, defaults, and verification limits.
214
+
213
215
  `configure-process` 的流程 JSON 中,审批人可配置为发起人、指定成员、指定角色、部门主管或直属主管,例如:
214
216
 
215
217
  ```json
@@ -431,7 +433,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
431
433
  | `openyida create-page <appType> "<name>" [--mode dashboard] [--hide-nav] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page |
432
434
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build Yida-compatible page source |
433
435
  | `openyida check-page <src> [--compat]` | Check custom page standards |
434
- | `openyida compile <src>` | Compile custom page locally |
436
+ | `openyida compile <src> [--canvas] [--json]` | Compile custom page locally |
435
437
  | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--auto-nav-order] [--open\|--no-open]` | Compile and publish custom page |
436
438
  | `openyida update-form-config <appType> <formUuid> <true\|false\|keep> "<title>" [--locale zh_CN\|en_US\|ja_JP]` | Update form configuration |
437
439
  | `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
@@ -454,8 +456,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
454
456
 
455
457
  | Command | Description |
456
458
  |---------|-------------|
457
- | `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules |
458
- | `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one) |
459
+ | `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules; Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
460
+ | `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
459
461
  | `openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...` | Manage process form AI approval prompts |
460
462
  | `openyida process preview <appType> ...` | Preview process instance (visual flowchart) |
461
463
 
@@ -529,7 +531,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
529
531
  | `openyida batch <file>\|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]` | Run OpenYida commands in batch |
530
532
  | `openyida flash-to-prd --file <path> --name "<project>"` | Convert flash notes or meeting notes to a PRD prompt |
531
533
  | `openyida ai <text\|image> [options]` | Call Yida AI text and image recognition APIs |
532
- | `openyida asset <status\|resolve\|generate> [options]` | Detect asset capability / resolve materials |
534
+ | `openyida asset <status\|resolve\|sources> [options]` | Detect asset capability / resolve materials |
533
535
  | `openyida cdn-config [options]` | Configure CDN / OSS upload |
534
536
  | `openyida cdn-upload <image-path>` | Upload image to CDN |
535
537
  | `openyida cdn-refresh [options]` | Refresh CDN cache |
@@ -560,7 +562,7 @@ The runner rejects public generic echo services such as httpbin and example.com.
560
562
 
561
563
  The opt-in `node scripts/e2e-real/connector/action-update-runner.js` regression uses the owner-confirmed login-free `www.aliwork.com` fixture and a single owned NONE-auth connector containing a target action plus one preservation sentinel. Enable it with `OPENYIDA_E2E=1 OPENYIDA_E2E_CONNECTOR_ACTION_UPDATE=1`. It verifies isolated `currentPage`, `pageSize`, `userLanguage`, `searchFieldJson`, and dynamic `_stamp` edits, restores each field and the final baseline, and persists only response structure, data count, and SHA-256. It never stores response row values or auth/profile/corp identifiers, never retries unknown writes, and intentionally leaves the owned connector as a `cleanup_blocked` residual because no proven delete API exists.
562
564
 
563
- `openyida asset resolve --hero <path-or-url> --product <path-or-url> --require-hero --upload-assets --json` is the preferred preflight for homepage visuals. It handles local files and external image references in one flow, uploads or mirrors them when CDN is configured, and returns `materialStatus: final|draft|none` so agents do not claim an unfinished visual page is final.
565
+ `openyida asset resolve --input manifest-draft.json --manifest asset-manifest.json --json` validates every image slot and writes a traceable manifest. Search assets only accept Unsplash/Pexels; draft results exit with code 2. Use `--offline` to block URL checks and uploads.
564
566
 
565
567
  #### Environment and Localization
566
568
 
package/bin/yida.js CHANGED
@@ -496,6 +496,7 @@ const MANIFEST_HELP_PATHS = Object.freeze({
496
496
  data: ['data'],
497
497
  report: ['report'],
498
498
  'create-process': ['create-process'],
499
+ 'configure-process': ['configure-process'],
499
500
  'create-report': ['create-report'],
500
501
  'append-chart': ['append-chart'],
501
502
  'save-share-config': ['save-share-config'],
package/lib/ai/ai.js CHANGED
@@ -31,6 +31,7 @@ const IMAGE_MIME_TYPES = {
31
31
  '.gif': 'image/gif',
32
32
  '.webp': 'image/webp',
33
33
  '.bmp': 'image/bmp',
34
+ '.svg': 'image/svg+xml',
34
35
  };
35
36
 
36
37
  function printHelp() {
@@ -500,6 +501,7 @@ module.exports = {
500
501
  getAuthRef,
501
502
  callTextFromAI,
502
503
  uploadImageForAI,
504
+ inferAppType,
503
505
  invokeImageRecognition,
504
506
  normalizeImageResult,
505
507
  parseArgs,
@@ -1019,39 +1019,42 @@ function compileCanvas(source, options = {}) {
1019
1019
  try {
1020
1020
  resolve(compileCanvasLocal(source, options));
1021
1021
  } catch (compileError) {
1022
- if (isCliError(compileError)) {
1023
- reject(compileError);
1024
- return;
1025
- }
1026
- const detail = compileError && compileError.message ? compileError.message : String(compileError);
1027
- if (
1028
- compileError &&
1029
- /^OPENYIDA_/.test(String(compileError.code || '')) &&
1030
- compileError.details !== undefined
1031
- ) {
1032
- reject(new CliError(`Code Canvas 本地编译失败: ${detail}`, {
1033
- code: compileError.code,
1034
- details: compileError.details,
1035
- }));
1036
- return;
1037
- }
1038
- reject(new CliError(`Code Canvas 本地编译失败: ${detail}`, {
1039
- code: 'OPENYIDA_CANVAS_COMPILE_FAILED',
1040
- details: {
1041
- stage: 'canvas_compile',
1042
- sourcePath: options.sourcePath,
1043
- causeCode: compileError && compileError.code,
1044
- causeName: compileError && compileError.name,
1045
- loc: compileError && compileError.loc,
1046
- },
1047
- }));
1022
+ reject(normalizeCanvasCompileError(compileError, options));
1048
1023
  }
1049
1024
  });
1050
1025
  }
1051
1026
 
1027
+ function normalizeCanvasCompileError(compileError, options = {}) {
1028
+ if (isCliError(compileError)) {
1029
+ return compileError;
1030
+ }
1031
+ const detail = compileError && compileError.message ? compileError.message : String(compileError);
1032
+ if (
1033
+ compileError &&
1034
+ /^OPENYIDA_/.test(String(compileError.code || '')) &&
1035
+ compileError.details !== undefined
1036
+ ) {
1037
+ return new CliError(`Code Canvas 本地编译失败: ${detail}`, {
1038
+ code: compileError.code,
1039
+ details: compileError.details,
1040
+ });
1041
+ }
1042
+ return new CliError(`Code Canvas 本地编译失败: ${detail}`, {
1043
+ code: 'OPENYIDA_CANVAS_COMPILE_FAILED',
1044
+ details: {
1045
+ stage: 'canvas_compile',
1046
+ sourcePath: options.sourcePath,
1047
+ causeCode: compileError && compileError.code,
1048
+ causeName: compileError && compileError.name,
1049
+ loc: compileError && compileError.loc,
1050
+ },
1051
+ });
1052
+ }
1053
+
1052
1054
  module.exports = {
1053
1055
  compileCanvas,
1054
1056
  compileCanvasLocal,
1057
+ normalizeCanvasCompileError,
1055
1058
  extractImportedModules,
1056
1059
  resolveWindowAlias,
1057
1060
  shouldAllowUnsupportedBareImports,
@@ -3,6 +3,8 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { compileSource } = require('./page-compiler');
6
+ const { compileCanvasPageSource } = require('./services/canvas-page-compiler');
7
+ const { loadPageSource } = require('./services/page-source-loader');
6
8
  const { runLintCheck } = require('./page-linter');
7
9
  const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
8
10
  const { warnLargePageSource } = require('./page-size-guard');
@@ -23,7 +25,20 @@ async function run(args) {
23
25
 
24
26
  const skipLint = args.includes('--skip-lint');
25
27
  const compat = args.includes('--compat') || args.includes('--modern');
26
- const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--compat' && arg !== '--modern');
28
+ const canvas = args.includes('--canvas');
29
+ const json = args.includes('--json');
30
+ const filteredArgs = args.filter(arg => ![
31
+ '--skip-lint',
32
+ '--compat',
33
+ '--modern',
34
+ '--canvas',
35
+ '--json',
36
+ ].includes(arg));
37
+ if (filteredArgs.length < 1) {
38
+ throwUsage(t('cli.compile_usage'), t('cli.compile_example'), {
39
+ code: 'COMPILE_INVALID_ARGUMENTS',
40
+ });
41
+ }
27
42
  let sourcePath = path.resolve(filteredArgs[0]);
28
43
  if (!fs.existsSync(sourcePath)) {
29
44
  throwCommandError(t('publish.source_not_found', sourcePath), {
@@ -32,6 +47,29 @@ async function run(args) {
32
47
  });
33
48
  }
34
49
 
50
+ if (canvas || /\.canvas\.(?:jsx?|tsx?)$/i.test(sourcePath)) {
51
+ const workspaceRoot = process.cwd();
52
+ const relativeSourcePath = path.relative(workspaceRoot, sourcePath);
53
+ const loadedSource = loadPageSource(relativeSourcePath, { workspaceRoot });
54
+ const compiled = compileCanvasPageSource(loadedSource);
55
+ const payload = {
56
+ ok: true,
57
+ pageType: 'canvas',
58
+ sourcePath: fs.realpathSync(sourcePath),
59
+ profile: compiled.profile,
60
+ inputHash: compiled.inputHash,
61
+ sourceHash: compiled.sourceHash,
62
+ compiledHash: compiled.compiledHash,
63
+ importedModules: JSON.parse(compiled.importedModules),
64
+ };
65
+ if (json) {
66
+ console.log(JSON.stringify(payload));
67
+ } else {
68
+ info(JSON.stringify(payload, null, 2));
69
+ }
70
+ return payload;
71
+ }
72
+
35
73
  const initialSourceCode = fs.readFileSync(sourcePath, 'utf-8');
36
74
  warnLargePageSource(initialSourceCode, sourcePath);
37
75
  if (shouldBuildPageSource(initialSourceCode, sourcePath, { modern: compat })) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
- const { compileCanvasLocal } = require('../canvas-compile');
4
+ const { compileCanvasLocal, normalizeCanvasCompileError } = require('../canvas-compile');
5
5
  const { assertNoEmojiInText } = require('../../core/no-emoji-guard');
6
6
  const { schemaError } = require('../../core/structured-error');
7
7
  const { isLoadedPageSource } = require('./page-source-loader');
@@ -36,8 +36,8 @@ function compileCanvasPageSource(loadedSource) {
36
36
  sourcePath: loadedSource.relativePath,
37
37
  });
38
38
  } catch (error) {
39
- compilerError('SCHEMA_PAGE_SOURCE_COMPILE_FAILED', 'Canvas page source could not be compiled.', {
40
- stage: 'canvas',
39
+ throw normalizeCanvasCompileError(error, {
40
+ sourcePath: loadedSource.relativePath,
41
41
  });
42
42
  }
43
43
 
@@ -221,6 +221,16 @@ function normalizeLayoutDirection(layoutDirection) {
221
221
  return normalized;
222
222
  }
223
223
 
224
+ /** 与 yc-utils 的应用配置归一化一致;不应用详情页 Shell 的 top_fold 覆盖。 */
225
+ function normalizeStoredLayoutDirection(layoutDirection, navType) {
226
+ if (SUPPORTED_LAYOUT_DIRECTIONS.includes(layoutDirection)) {return layoutDirection;}
227
+ if (layoutDirection === 'hoz') {return navType === 'top_side' ? 'l_shape' : 'top';}
228
+ if (layoutDirection === 'ver') {return 'side';}
229
+ if (navType === 'top_fold') {return 'top';}
230
+ if (navType === 'top_side') {return 'l_shape';}
231
+ return 'side';
232
+ }
233
+
224
234
  function pickAppField(currentApp, fieldName, configName) {
225
235
  if (currentApp[fieldName] !== undefined && currentApp[fieldName] !== null) {
226
236
  return currentApp[fieldName];
@@ -296,6 +306,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
296
306
  const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
297
307
  const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
298
308
  const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
309
+ const currentNavType = currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE);
310
+ const currentLayoutDirection = currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION);
299
311
  if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
300
312
  throw new Error(t('update_app.custom_theme_color_required'));
301
313
  }
@@ -319,9 +331,10 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
319
331
  mode: currentApp.mode || (currentApp.config && currentApp.config.APPMODE) || 'normal',
320
332
  type: currentApp.type || (currentApp.config && currentApp.config.APPTYPE) || 'single',
321
333
  navTheme: params.navTheme || currentApp.navTheme || (currentApp.config && currentApp.config.NAV_THEME) || 'light',
322
- navType: currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE) || 'top_side',
323
334
  navLayout: currentApp.navLayout || (currentApp.config && currentApp.config.NAVLAYOUT) || 'auto',
324
- layoutDirection: params.layoutDirection || currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION) || 'side',
335
+ layoutDirection: params.layoutDirection
336
+ ? normalizeLayoutDirection(params.layoutDirection)
337
+ : normalizeStoredLayoutDirection(currentLayoutDirection, currentNavType),
325
338
  homepageLogo: currentApp.homepageLogo || (currentApp.config && currentApp.config.HOMEPAGELOGO) || '',
326
339
  logoSource: params.logoSource || currentApp.logoSource || (currentApp.config && currentApp.config.LOGO_SOURCE) || 'appIcon',
327
340
  logoLink: currentApp.logoLink || (currentApp.config && currentApp.config.LOGOLINK) || '',
@@ -346,6 +359,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
346
359
  pageFooter: currentApp.pageFooter || '',
347
360
  };
348
361
 
362
+ // ThemeNavSetting 保存现有 navType;新布局由 layoutDirection 控制,不反写或补造旧字段。
363
+ if (currentNavType) {postDataObj.navType = currentNavType;}
349
364
  if (addWaterMark !== undefined && addWaterMark !== null) {
350
365
  postDataObj.addWaterMark = addWaterMark;
351
366
  }
@@ -1,89 +1,60 @@
1
1
 
2
- /**
3
- * ai-image.js - 图片素材来源能力检测(诚实降级 + 引导本地 agent)
4
- *
5
- * P3 素材工具化的关键澄清:openyida 本身没有内置「文生图」API,
6
- * `openyida ai` 只支持文生文与识图,不能凭空生成图片 URL。
7
- *
8
- * 因此本模块不假装能生成图片,而是:
9
- * 1. 诚实地检测「是否存在内置图片生成能力」(目前恒为 false);
10
- * 2. 把「生成/寻找素材」这件事交回给正在驱动本 CLI 的智能体(Claude/Codex 等),
11
- * 由 skill 文档引导它:能自己生成就生成,否则去免费素材库搜真实图片;
12
- * 3. 提供推荐的免费/可商用素材库清单与使用纪律,供 skill / agent 参考。
13
- *
14
- * 无论走哪条路,候选素材都交给 asset resolve 统一解析;有 CDN 时再转存,
15
- * 绝不把编造的 URL 直接写进页面。
16
- *
17
- * 用法:
18
- * const { detectImageGenerator, getFreeStockLibraries, getMaterialSourcingGuidance } = require('./ai-image');
19
- * const g = detectImageGenerator(); // { available:false, reason, delegateToAgent:true }
20
- */
21
-
22
2
  'use strict';
23
3
 
24
- /**
25
- * 推荐的免费 / 可商用图片素材库
26
- * 说明:这些站点提供免费可商用(多为 CC0 / 自有免费授权)的高质量图片,
27
- * 适合官网 Hero、产品图、场景配图。agent 应优先从这里取真实图片 URL,
28
- * 取到后交给 `openyida asset resolve` 统一处理。
29
- */
30
4
  const FREE_STOCK_LIBRARIES = [
31
5
  {
32
6
  name: 'Unsplash',
33
7
  site: 'https://unsplash.com',
34
- license: 'Unsplash License(免费可商用,无需署名)',
35
- bestFor: '高质量摄影级 Hero 大图、生活方式/场景图',
36
- directImageHint:
37
- 'images.unsplash.com/photo-... 形式的直链可直接用于 <img>;页面链接需先取得图片直链。',
8
+ license: '遵守 Unsplash License 和 API Guidelines',
9
+ bestFor: 'Hero、生活方式和场景图',
10
+ directImageHint: '使用 API 返回的 images.unsplash.com URL。',
11
+ deliveryPolicy: '下载授权图片后上传宜搭附件;保留署名和 download_location 记录。',
38
12
  },
39
13
  {
40
14
  name: 'Pexels',
41
15
  site: 'https://www.pexels.com',
42
- license: 'Pexels License(免费可商用,无需署名)',
43
- bestFor: '产品/办公/团队/自然等主题摄影图与短视频',
16
+ license: '遵守 Pexels License 和 API Guidelines',
17
+ bestFor: '产品、办公、人物和场景图',
44
18
  directImageHint: 'images.pexels.com/photos/... 形式为图片直链。',
45
- },
46
- {
47
- name: 'Pixabay',
48
- site: 'https://pixabay.com',
49
- license: 'Pixabay Content License(免费可商用)',
50
- bestFor: '插画、矢量、照片综合库',
51
- directImageHint: 'cdn.pixabay.com/photo/... 形式为图片直链。',
52
- },
53
- {
54
- name: 'Undraw',
55
- site: 'https://undraw.co/illustrations',
56
- license: 'unDraw open-source license(免费可商用,可改色)',
57
- bestFor: '扁平风品牌插画、空状态/引导插画(SVG,可随品牌色变量着色)',
58
- directImageHint: '导出 SVG 或使用官方 CDN SVG 直链。',
19
+ deliveryPolicy: '下载后上传宜搭附件;保留 Pexels 链接和摄影师信息。',
59
20
  },
60
21
  ];
61
22
 
62
23
  /**
63
24
  * 检测是否存在「内置图片生成能力」。
64
25
  *
65
- * 目前 openyida 无内置文生图连接器,恒返回 available:false,并显式声明
66
- * 由 agent 接管素材来源(避免任何模块据此伪造图片 URL)。
26
+ * openyida 无内置文生图连接器。宿主能力未确认时返回 available:null,
27
+ * 由 Agent 检查真实工具清单。
67
28
  *
68
29
  * @param {object} [options]
69
- * @param {boolean} [options.hasImageGenConnector] 预留:未来若配置了文生图连接器可置 true
70
- * @returns {{ available: boolean, reason: string, delegateToAgent: boolean }}
30
+ * @returns {{ status: string, available: boolean|null, reason: string, delegateToAgent: boolean }}
71
31
  */
72
32
  function detectImageGenerator(options = {}) {
73
- if (options.hasImageGenConnector) {
33
+ const capability = options.hostCapabilities && options.hostCapabilities.image_generation;
34
+ if (capability && capability.status === 'available') {
74
35
  return {
36
+ status: 'available',
75
37
  available: true,
76
- reason: '检测到已配置的文生图连接器,可由连接器生成图片后再转存 CDN。',
38
+ reason: '当前宿主可生成图片;生成后交给 asset resolve。',
39
+ delegateToAgent: true,
40
+ source: capability.source || 'host_capability',
41
+ };
42
+ }
43
+ if (capability && capability.status === 'unavailable') {
44
+ return {
45
+ status: 'unavailable',
46
+ available: false,
47
+ reason: '当前宿主不支持图片生成。',
77
48
  delegateToAgent: false,
49
+ source: capability.source,
78
50
  };
79
51
  }
80
52
  return {
81
- available: false,
82
- reason:
83
- 'openyida 无内置文生图能力(`openyida ai` 仅支持文生文/识图)。' +
84
- '图片素材应由智能体自行生成,或到免费素材库(Unsplash/Pexels 等)检索真实图片,' +
85
- '再交给 `openyida asset resolve` 解析、(有 CDN 时)转存 CDN 后回填 spec。',
53
+ status: 'unknown',
54
+ available: null,
55
+ reason: '当前宿主未确认图片生成能力。',
86
56
  delegateToAgent: true,
57
+ source: (capability && capability.source) || 'host_tool_inventory_required',
87
58
  };
88
59
  }
89
60
 
@@ -102,17 +73,18 @@ function getFreeStockLibraries() {
102
73
  function getMaterialSourcingGuidance() {
103
74
  return {
104
75
  steps: [
105
- '1. 判断页面是否需要真实图片(官网 Hero / 产品图 / 场景图通常需要)。',
106
- '2. 优先:若智能体自身具备图片生成能力,按品牌调性生成图片并保存到本地。',
107
- '3. 备选:到免费可商用素材库(见 libraries)检索与主题匹配的真实图片,拿到图片直链。',
108
- '4. 解析:把本地文件或外链交给 `openyida asset resolve` 统一处理并回填 spec.assets;已配置 CDN 时转存为稳定 URL。',
109
- '5. 兜底:若既无生成能力又找不到可用素材,交付「标注素材缺口」的低保真草稿,绝不编造图片 URL 或声称已上传。',
76
+ '1. 先用用户提供的素材。',
77
+ '2. 再从 Unsplash / Pexels 搜图。',
78
+ '3. 仍有缺口时使用宿主生图。',
79
+ '4. 最后使用中性占位并保持 draft。',
80
+ '5. 所有候选图通过 asset resolve 写入 manifest。',
110
81
  ],
111
82
  libraries: getFreeStockLibraries(),
112
83
  rules: [
113
- '绝不编造图片 URL;素材经 `openyida asset resolve` 统一处理后再写进页面。',
114
- ' CDN 时不得声称「已上传」或「已是最终版」。',
115
- '素材授权需为免费可商用;如不确定授权,改用文字排版/数据图示/插画(如 unDraw)替代。',
84
+ '不编造图片 URL;按 manifest 的页面状态继续,只使用当前页 materialStatus=final 的图片。',
85
+ '默认下载并上传宜搭图片附件;超过 20 MiB 保留原链接;不额外探测 URL,下载失败跳过并记录缺口。',
86
+ '联网搜图仅使用 Unsplash / Pexels。',
87
+ '来源不明时使用中性占位或宿主生成图。',
116
88
  ],
117
89
  };
118
90
  }