openyida 2026.5.25 → 2026.5.26

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 CHANGED
@@ -297,7 +297,7 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
297
297
  | `openyida app-list [--size N]` | List Yida applications |
298
298
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
299
299
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create an application and output `appType` |
300
- | `openyida update-app <appType> --name "..."` | Update application metadata |
300
+ | `openyida update-app <appType> [--name "..."] [--layout slide\|ver] [--theme deepBlue]` | Update application metadata and theme/layout fields |
301
301
  | `openyida nav-group <list\|create\|rename\|delete\|move\|hide\|show> <appType> ...` | Manage sidebar navigation groups and move pages between groups |
302
302
  | `openyida app-permission <get\|set\|add\|remove\|search-user> ...` | Manage app primary admins, data admins, and developer members |
303
303
  | `openyida i18n <overview\|config\|languages\|list\|upsert\|delete\|translate\|translate-all\|upgrade> <appType> ...` | Manage app multilingual copy and language configuration |
@@ -330,7 +330,7 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
330
330
 
331
331
  | Command | Description |
332
332
  |---------|-------------|
333
- | `openyida data <action> <resource> [args]` | Unified data management for forms, processes, tasks, and subforms |
333
+ | `openyida data <action> <resource> [args]` | Unified data management for forms, processes, tasks, and subforms; form queries support `--all` and `--form-uuid` subform hydration |
334
334
  | `openyida data check <appType> <formUuid> <rules.json>` | Detect anomalous process-form records |
335
335
  | `openyida task-center <type> [options]` | Query todo, created, processed, CC, or proxy-submitted tasks |
336
336
  | `openyida agent-center <sub-command>` | Manage Yida process delegation and departure delegation |
@@ -372,6 +372,7 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
372
372
  | `openyida sample [--list]` | Emit sample templates |
373
373
  | `openyida bridge start [--token <pair-token>] [--port 6736] [--open]` | Start the local OpenYida web bridge for `https://demo.aliwork.com/s/openyida` |
374
374
  | `openyida doctor [--fix]` | Diagnose and repair environment issues |
375
+ | `openyida db-seq-fix [--fix]` | Detect and repair PostgreSQL sequence drift |
375
376
  | `openyida formula evaluate <formula\|file> [--schema file]` | Static-check formula syntax and field references |
376
377
  | `openyida update` | Update OpenYida through npm |
377
378
  | `openyida export-conversation [options]` | Export AI conversation history |
package/bin/yida.js CHANGED
@@ -306,7 +306,7 @@ function shouldUsePlaywrightFallbackInAgentLogin() {
306
306
  const { detectActiveTool } = require('../lib/core/utils');
307
307
  const activeTool = detectActiveTool();
308
308
  if (activeTool && activeTool.tool === 'wukong') {
309
- return true;
309
+ return false;
310
310
  }
311
311
  return process.env.OPENYIDA_AGENT_PLAYWRIGHT_FALLBACK === '1';
312
312
  }
@@ -477,6 +477,14 @@ async function main() {
477
477
  if (cachedResult.status === 'ok') {
478
478
  printLoginResult(cachedResult);
479
479
  } else {
480
+ const { detectActiveTool } = require('../lib/core/utils');
481
+ const activeTool = detectActiveTool();
482
+ if (activeTool && activeTool.tool === 'wukong') {
483
+ const { codexLogin } = require('../lib/auth/codex-login');
484
+ const result = await codexLogin({ tool: 'wukong' });
485
+ printLoginResult(result);
486
+ break;
487
+ }
480
488
  const { interactiveLogin } = require('../lib/auth/login');
481
489
  const browserResult = interactiveLogin({
482
490
  playwrightFallback: shouldUsePlaywrightFallbackInAgentLogin(),
@@ -486,8 +494,6 @@ async function main() {
486
494
  } else {
487
495
  // CDP/Playwright 失败后的兜底策略:
488
496
  // QoderWork 有 in-app browser,优先使用 browser handoff;其余走终端二维码
489
- const { detectActiveTool } = require('../lib/core/utils');
490
- const activeTool = detectActiveTool();
491
497
  if (activeTool && activeTool.tool === 'qoderwork') {
492
498
  const { codexLogin } = require('../lib/auth/codex-login');
493
499
  const result = await codexLogin({ tool: 'qoderwork' });
@@ -1065,6 +1071,12 @@ async function main() {
1065
1071
  break;
1066
1072
  }
1067
1073
 
1074
+ case 'db-seq-fix': {
1075
+ const { run: runDbSeqFix } = require('../lib/db/db-seq-fix');
1076
+ await runDbSeqFix(args);
1077
+ break;
1078
+ }
1079
+
1068
1080
  case 'update': {
1069
1081
  const { runUpdate } = require('../lib/core/update');
1070
1082
  await runUpdate(currentVersion);
@@ -604,6 +604,16 @@ function lintYidaSource(sourceCode, filePath) {
604
604
  pushIssue(warnings, lineNumber, 'native-select-ui', t('publish.lint_native_select_ui'), disableMap);
605
605
  }
606
606
 
607
+ const iframeSelfNavigationMatch = line.match(/<a\b(?=[^>]*(?:aliwork\.com|yidaapps\.com|\/preview\/|\/workbench))(?!(?=[^>]*\btarget=(['"]?)_top\1))(?!(?=[^>]*\btarget=(['"]?)_blank\2))[^>]*>/i);
608
+ if (iframeSelfNavigationMatch && !isInCommentOrString(line, iframeSelfNavigationMatch.index)) {
609
+ pushIssue(warnings, lineNumber, 'iframe-self-navigation', t('publish.lint_iframe_self_navigation'), disableMap);
610
+ }
611
+
612
+ const topLocationMatch = line.match(/window\.location\.href\s*=\s*[^;\n]*(?:aliwork\.com|yidaapps\.com|\/preview\/|\/workbench)/i);
613
+ if (topLocationMatch && !isInCommentOrString(line, topLocationMatch.index) && !line.includes('window.top.location')) {
614
+ pushIssue(warnings, lineNumber, 'iframe-self-navigation', t('publish.lint_iframe_self_navigation'), disableMap);
615
+ }
616
+
607
617
  const pageSizeMatch = line.match(/\bpageSize\s*:\s*(\d+)/);
608
618
  if (pageSizeMatch && Number(pageSizeMatch[1]) > 100 && !isInCommentOrString(line, pageSizeMatch.index)) {
609
619
  pushIssue(errors, lineNumber, 'page-size-limit', t('publish.lint_page_size_limit', pageSizeMatch[1]), disableMap);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * update-app.js - 更新宜搭应用信息
3
3
  *
4
- * 用法:openyida update-app <appType> --name "新名称" [--desc "描述"] [--icon "图标"]
4
+ * 用法:openyida update-app <appType> --name "新名称" [--desc "描述"] [--icon "图标"] [--layout slide|ver]
5
5
  */
6
6
 
7
7
  'use strict';
@@ -29,6 +29,9 @@ function parseArgs(args) {
29
29
  desc: null,
30
30
  icon: null,
31
31
  iconColor: null,
32
+ colour: null,
33
+ navTheme: null,
34
+ layoutDirection: null,
32
35
  };
33
36
 
34
37
  // 第一个参数是 appType
@@ -69,6 +72,28 @@ function parseArgs(args) {
69
72
  i++;
70
73
  }
71
74
  break;
75
+ case '--theme':
76
+ case '--colour':
77
+ if (nextArg) {
78
+ result.colour = nextArg;
79
+ i++;
80
+ }
81
+ break;
82
+ case '--nav-theme':
83
+ case '--navTheme':
84
+ if (nextArg) {
85
+ result.navTheme = nextArg;
86
+ i++;
87
+ }
88
+ break;
89
+ case '--layout':
90
+ case '--layout-direction':
91
+ case '--layoutDirection':
92
+ if (nextArg) {
93
+ result.layoutDirection = nextArg;
94
+ i++;
95
+ }
96
+ break;
72
97
  }
73
98
  }
74
99
 
@@ -88,7 +113,7 @@ async function run(args) {
88
113
  const params = parseArgs(args);
89
114
 
90
115
  // 验证必填参数
91
- const { c, banner, step, label, info, success: chalkSuccess, result: chalkResult, error: chalkError } = require('../core/chalk');
116
+ const { c, banner, step, label, info, warn, success: chalkSuccess, result: chalkResult, error: chalkError } = require('../core/chalk');
92
117
 
93
118
  if (!params.appType) {
94
119
  chalkError(t('update_app.missing_app_type'), { exit: false });
@@ -96,7 +121,7 @@ async function run(args) {
96
121
  process.exit(1);
97
122
  }
98
123
 
99
- if (!params.name && !params.desc && !params.icon) {
124
+ if (!params.name && !params.desc && !params.icon && !params.colour && !params.navTheme && !params.layoutDirection) {
100
125
  chalkError(t('update_app.missing_update_field'), { exit: false });
101
126
  printUsage();
102
127
  process.exit(1);
@@ -107,6 +132,9 @@ async function run(args) {
107
132
  if (params.name) {label('Name', params.name);}
108
133
  if (params.desc) {label('Desc', params.desc);}
109
134
  if (params.icon) {label('Icon', `${params.icon} ${c.dim}(${params.iconColor || '#0089FF'})${c.reset}`);}
135
+ if (params.colour || params.navTheme || params.layoutDirection) {
136
+ label('Theme', `${params.colour || '-'} / ${params.navTheme || '-'} / ${params.layoutDirection || '-'}`);
137
+ }
110
138
 
111
139
  // Step 1: 读取登录态
112
140
  step(1, t('common.step_login', 1));
@@ -158,6 +186,13 @@ async function run(args) {
158
186
  postDataObj.iconUrl = iconValue;
159
187
  }
160
188
 
189
+ if (params.colour) {postDataObj.colour = params.colour;}
190
+ if (params.navTheme) {postDataObj.navTheme = params.navTheme;}
191
+ if (params.layoutDirection) {
192
+ postDataObj.layoutDirection = params.layoutDirection;
193
+ warn(t('update_app.layout_notice'));
194
+ }
195
+
161
196
  const postData = querystring.stringify(postDataObj);
162
197
 
163
198
  const response = await requestWithAutoLogin((auth) => {
@@ -182,6 +217,9 @@ async function run(args) {
182
217
  name: params.name || undefined,
183
218
  desc: params.desc || undefined,
184
219
  icon: params.icon || undefined,
220
+ colour: params.colour || undefined,
221
+ navTheme: params.navTheme || undefined,
222
+ layoutDirection: params.layoutDirection || undefined,
185
223
  },
186
224
  }));
187
225
  } else {
@@ -45,7 +45,7 @@ const COMMAND_GROUPS = [
45
45
  output: 'json',
46
46
  }),
47
47
  command('create-app', ['create-app'], 'create-app "<name>"|--name <name> [options] [--locale zh_CN|en_US|ja_JP] [--open|--no-open]', 'help.cmd_create_app'),
48
- command('update-app', ['update-app'], 'update-app <appType> --name "..."', 'help.cmd_update_app'),
48
+ command('update-app', ['update-app'], 'update-app <appType> [--name "..."] [--layout slide|ver] [--theme deepBlue]', 'help.cmd_update_app'),
49
49
  command('nav-group', ['nav-group'], 'nav-group <list|create|rename|delete|move|hide|show> <appType> ...', 'help.cmd_nav_group', {
50
50
  output: 'json',
51
51
  }),
@@ -175,6 +175,7 @@ const COMMAND_GROUPS = [
175
175
  command('copy', ['copy'], 'copy [--force]', 'help.cmd_copy', { requiresLogin: false }),
176
176
  command('sample', ['sample'], 'sample [--list]', 'help.cmd_sample', { requiresLogin: false }),
177
177
  command('doctor', ['doctor'], 'doctor [--fix]', 'help.cmd_doctor', { requiresLogin: false }),
178
+ command('db-seq-fix', ['db-seq-fix'], 'db-seq-fix [--fix]', 'help.cmd_db_seq_fix'),
178
179
  command('formula.evaluate', ['formula', 'evaluate'], 'formula evaluate <formula|file> [--schema file]', 'help.cmd_formula_evaluate', {
179
180
  requiresLogin: false,
180
181
  output: 'text|json',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Output machine-readable command manifest',
82
82
  cmd_a2a: 'Start local read-only A2A adapter or print Agent Card',
83
83
  cmd_bridge: 'Start OpenYida local web bridge service',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'نسخ دليل عمل project',
85
86
  cmd_sample: 'إخراج أمثلة/قوالب الكود',
86
87
  cmd_doctor: 'تشخيص البيئة والإصلاح التلقائي',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' التعرف على الاجتماع: {0} حقول بيانات وصفية، {1} أقسام A1، {2} متحدثين',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Maschinenlesbares Command Manifest ausgeben',
82
82
  cmd_a2a: 'Lokalen schreibgeschützten A2A-Adapter starten oder Agent Card ausgeben',
83
83
  cmd_bridge: 'Lokalen OpenYida Web-Bridge-Dienst starten',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'project-Arbeitsverzeichnis kopieren',
85
86
  cmd_sample: 'Codebeispiele/Vorlagen ausgeben',
86
87
  cmd_doctor: 'Umgebungsdiagnose & automatische Reparatur',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Meeting-Erkennung: {0} Metadaten, {1} A1-Abschnitte, {2} Sprecher',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -87,6 +87,7 @@ module.exports = {
87
87
  cmd_copy: 'Copy project working directory',
88
88
  cmd_sample: 'Output code samples/templates',
89
89
  cmd_doctor: 'Environment diagnostics & auto-fix',
90
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
90
91
  cmd_formula_evaluate: 'Static-check Yida formula syntax and field refs',
91
92
  cmd_update: 'Check and update to latest version',
92
93
  cmd_export_conversation: 'Export AI conversation records',
@@ -802,11 +803,11 @@ Examples:
802
803
 
803
804
  // ── lib/update-app.js ─────────────────────────────
804
805
  update_app: {
805
- usage: 'Usage: openyida update-app <appType> --name "New Name" [--desc "Description"] [--icon "icon-name"] [--icon-color "color"]',
806
- example: 'Example: openyida update-app APP_XXX --name "New App Name" --desc "New Description"',
807
- options: 'Options:\n --name, -n App name (required or at least one update field)\n --desc, -d App description\n --icon Icon name (e.g. xian-yingyong)\n --icon-color Icon color (default: #0089FF)',
806
+ usage: 'Usage: openyida update-app <appType> [--name "New Name"] [--desc "Description"] [--layout slide|ver] [--theme deepBlue]',
807
+ example: 'Example: openyida update-app APP_XXX --name "New App Name" --layout ver --theme deepBlue',
808
+ options: 'Options:\n --name, -n App name (specify at least one update field)\n --desc, -d App description\n --icon Icon name (e.g. xian-yingyong)\n --icon-color Icon color (default: #0089FF)\n --theme App theme colour\n --nav-theme Navigation theme navTheme\n --layout Layout direction layoutDirection (slide or ver)',
808
809
  missing_app_type: 'Error: missing appType argument',
809
- missing_update_field: 'Error: at least one update field must be specified (--name, --desc, --icon)',
810
+ missing_update_field: 'Error: at least one update field must be specified (--name, --desc, --icon, --theme, --nav-theme, --layout)',
810
811
  title: ' update-app - Yida App Info Update Tool',
811
812
  app_type: '\n App ID: {0}',
812
813
  new_name: ' New name: {0}',
@@ -817,6 +818,7 @@ Examples:
817
818
  app_type_label: ' appType: {0}',
818
819
  name_label: ' App name: {0}',
819
820
  failed: ' ❌ Update failed: {0}',
821
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
820
822
  },
821
823
 
822
824
  // ── lib/update-form-config.js ──────────────────────
@@ -960,6 +962,7 @@ Examples:
960
962
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
961
963
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
962
964
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
965
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
963
966
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
964
967
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
965
968
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Emitir manifiesto de comandos legible por máquina',
82
82
  cmd_a2a: 'Iniciar adaptador A2A local de solo lectura o imprimir Agent Card',
83
83
  cmd_bridge: 'Iniciar servicio local OpenYida web bridge',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'Copiar directorio de trabajo project',
85
86
  cmd_sample: 'Generar ejemplos/plantillas de código',
86
87
  cmd_doctor: 'Diagnóstico de entorno y reparación automática',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Reconocimiento de reunion: {0} metadatos, {1} secciones A1, {2} ponentes',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Afficher le manifeste des commandes lisible par machine',
82
82
  cmd_a2a: 'Démarrer l’adaptateur A2A local en lecture seule ou afficher l’Agent Card',
83
83
  cmd_bridge: 'Démarrer le service web bridge local OpenYida',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'Copier le répertoire de travail project',
85
86
  cmd_sample: 'Exporter des exemples/modèles de code',
86
87
  cmd_doctor: 'Diagnostic d\'environnement et réparation auto',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Reconnaissance reunion : {0} metadonnees, {1} sections A1, {2} intervenants',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Output machine-readable command manifest',
82
82
  cmd_a2a: 'Start local read-only A2A adapter or print Agent Card',
83
83
  cmd_bridge: 'Start OpenYida local web bridge service',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'project कार्य निर्देशिका कॉपी करें',
85
86
  cmd_sample: 'कोड सैंपल/टेम्पलेट आउटपुट करें',
86
87
  cmd_doctor: 'वातावरण निदान और स्वचालित मरम्मत',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Meeting recognition: {0} metadata, {1} A1 sections, {2} speakers',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -83,6 +83,7 @@ module.exports = {
83
83
  cmd_commands: '機械可読コマンド manifest を出力',
84
84
  cmd_a2a: 'ローカル読み取り専用 A2A Adapter を起動、または Agent Card を出力',
85
85
  cmd_bridge: 'OpenYida ローカル Web ブリッジサービスを起動',
86
+ cmd_db_seq_fix: 'PostgreSQL Sequence ドリフトを検出・修復',
86
87
  cmd_copy: 'project 作業ディレクトリをコピー',
87
88
  cmd_sample: 'コードサンプル/テンプレートを出力',
88
89
  cmd_doctor: '環境診断と自動修復',
@@ -882,6 +883,7 @@ openyida - Yida CLI ツール
882
883
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
883
884
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
884
885
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
886
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
885
887
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
886
888
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
887
889
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -1067,6 +1069,9 @@ openyida - Yida CLI ツール
1067
1069
  done_meeting: ' 会議認識:メタ情報 {0} 項目、A1 要約 {1} セクション、発言者 {2} 名',
1068
1070
  },
1069
1071
  },
1072
+ update_app: {
1073
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
1074
+ },
1070
1075
  codex_login: {
1071
1076
  title: ' openyida login {0} - {1} Login Mode',
1072
1077
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Output machine-readable command manifest',
82
82
  cmd_a2a: 'Start local read-only A2A adapter or print Agent Card',
83
83
  cmd_bridge: 'Start OpenYida local web bridge service',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'project 작업 디렉토리 복사',
85
86
  cmd_sample: '코드 샘플/템플릿 출력',
86
87
  cmd_doctor: '환경 진단 및 자동 수정',
@@ -464,6 +465,7 @@ module.exports = {
464
465
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
465
466
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
466
467
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
468
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
467
469
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
468
470
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
469
471
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -677,6 +679,9 @@ module.exports = {
677
679
  done_meeting: ' 회의 인식: 메타정보 {0}개, A1 요약 {1}개, 발언자 {2}명',
678
680
  },
679
681
  },
682
+ update_app: {
683
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
684
+ },
680
685
  codex_login: {
681
686
  title: ' openyida login {0} - {1} Login Mode',
682
687
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Emitir manifesto de comandos legível por máquina',
82
82
  cmd_a2a: 'Iniciar adaptador A2A local somente leitura ou imprimir Agent Card',
83
83
  cmd_bridge: 'Iniciar serviço local OpenYida web bridge',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'Copiar diretório de trabalho project',
85
86
  cmd_sample: 'Gerar exemplos/modelos de código',
86
87
  cmd_doctor: 'Diagnóstico de ambiente e reparo automático',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Reconhecimento de reuniao: {0} metadados, {1} secoes A1, {2} palestrantes',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -81,6 +81,7 @@ module.exports = {
81
81
  cmd_commands: 'Output machine-readable command manifest',
82
82
  cmd_a2a: 'Start local read-only A2A adapter or print Agent Card',
83
83
  cmd_bridge: 'Start OpenYida local web bridge service',
84
+ cmd_db_seq_fix: 'Detect and repair PostgreSQL sequence drift',
84
85
  cmd_copy: 'Sao chép thư mục làm việc project',
85
86
  cmd_sample: 'Xuất mẫu ví dụ/mẫu mã',
86
87
  cmd_doctor: 'Chẩn đoán môi trường và sửa tự động',
@@ -462,6 +463,7 @@ module.exports = {
462
463
  lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
463
464
  lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
464
465
  lint_native_select_ui: 'Found a native select. User-facing custom page dropdowns should use a Tailwind-styled custom dropdown component for consistent visual quality',
466
+ lint_iframe_self_navigation: 'Yida custom pages run in an iframe. Use target="_top"/target="_blank" or window.top.location for Yida page navigation to avoid nested pages.',
465
467
  lint_page_size_limit: 'pageSize={0} exceeds the Yida API limit of 100; use 100 or less',
466
468
  lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
467
469
  lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
@@ -675,6 +677,9 @@ module.exports = {
675
677
  done_meeting: ' Nhan dang cuoc hop: {0} thong tin, {1} phan A1, {2} nguoi phat bieu',
676
678
  },
677
679
  },
680
+ update_app: {
681
+ layout_notice: 'Note: layoutDirection is consumed by the Yida app shell during creation/refresh. If the top action bar does not recover immediately after a backend switch, reopen the workbench or recreate the app with the target layout.',
682
+ },
678
683
  codex_login: {
679
684
  title: ' openyida login {0} - {1} Login Mode',
680
685
  not_codex: 'Current environment is not detected as Codex / Qoder / Wukong; returning an in-app browser login handoff only.',
@@ -83,6 +83,7 @@ module.exports = {
83
83
  cmd_commands: '輸出機器可讀命令清單',
84
84
  cmd_a2a: '啟動本機唯讀 A2A Adapter 或輸出 Agent Card',
85
85
  cmd_bridge: '啟動 OpenYida 本機網頁橋接服務',
86
+ cmd_db_seq_fix: 'PostgreSQL Sequence 漂移偵測與修復',
86
87
  cmd_copy: '複製 project 工作目錄',
87
88
  cmd_sample: '輸出程式碼範例/模板',
88
89
  cmd_doctor: '環境診斷與自動修復',
@@ -793,6 +794,7 @@ openyida - 宜搭命令列工具
793
794
  lint_foreach_callback_function: '.forEach(function ...) 可能導致回調內 this 遺失,建議改為 .forEach((item) => ...)',
794
795
  lint_controlled_input: 'input 使用了 value 受控模式,宜搭頁面應使用 defaultValue + onChange 寫入 _customState',
795
796
  lint_native_select_ui: '偵測到原生 select。面向用戶的自訂頁面下拉互動應使用 Tailwind 風格的自訂下拉組件,避免瀏覽器原生控件觀感不一致',
797
+ lint_iframe_self_navigation: '宜搭自訂頁面運行在 iframe 中,跳轉宜搭頁面時請使用 target="_top"/target="_blank" 或 window.top.location,避免頁面套娃',
796
798
  lint_page_size_limit: 'pageSize={0} 超過宜搭 API 上限 100,請改為 100 或更小',
797
799
  lint_yida_api_catch: 'this.utils.yida API 調用未偵測到 .catch(),請補充錯誤處理並 toast 給用戶',
798
800
  lint_echarts_legacy_map_china: 'ECharts 5 已廢棄 echarts/map/js/china.js,請載入 DataV GeoJSON 後調用 echarts.registerMap("china", geoJson)',
@@ -1103,6 +1105,9 @@ openyida - 宜搭命令列工具
1103
1105
  node_end: '流程結束',
1104
1106
  node_approval: '審批節點',
1105
1107
  },
1108
+ update_app: {
1109
+ layout_notice: '提示:layoutDirection 由宜搭應用外殼在建立/重新整理時消費;若後台切換後頂部操作欄未立即恢復,請重新開啟工作台或使用目標 layout 重新建立應用。',
1110
+ },
1106
1111
  codex_login: {
1107
1112
  title: ' openyida login {0} - {1} 登入模式',
1108
1113
  not_codex: '目前環境未偵測為 Codex / Qoder / 悟空,僅返回內建瀏覽器登入 handoff。',
@@ -87,6 +87,7 @@ module.exports = {
87
87
  cmd_copy: '复制 project 工作目录',
88
88
  cmd_sample: '输出代码示例/模板',
89
89
  cmd_doctor: '环境诊断与自动修复',
90
+ cmd_db_seq_fix: 'PostgreSQL Sequence 漂移检测与修复',
90
91
  cmd_formula_evaluate: '静态检查宜搭公式语法和字段引用',
91
92
  cmd_update: '检查并更新到最新版本',
92
93
  cmd_export_conversation: '导出 AI 对话记录',
@@ -739,11 +740,11 @@ openyida - 宜搭命令行工具
739
740
 
740
741
  // ── lib/update-app.js ─────────────────────────────
741
742
  update_app: {
742
- usage: '用法: openyida update-app <appType> --name "新名称" [--desc "描述"] [--icon "图标"] [--icon-color "颜色"]',
743
- example: '示例: openyida update-app APP_XXX --name "新应用名称" --desc "新描述"',
744
- options: '选项:\n --name, -n 应用名称(必填或至少一个更新字段)\n --desc, -d 应用描述\n --icon 图标名称(如: xian-yingyong)\n --icon-color 图标颜色(默认: #0089FF)',
743
+ usage: '用法: openyida update-app <appType> [--name "新名称"] [--desc "描述"] [--layout slide|ver] [--theme deepBlue]',
744
+ example: '示例: openyida update-app APP_XXX --name "新应用名称" --layout ver --theme deepBlue',
745
+ options: '选项:\n --name, -n 应用名称(至少指定一个更新字段)\n --desc, -d 应用描述\n --icon 图标名称(如: xian-yingyong)\n --icon-color 图标颜色(默认: #0089FF)\n --theme 应用主题 colour\n --nav-theme 导航主题 navTheme\n --layout 布局方向 layoutDirection(slide 或 ver)',
745
746
  missing_app_type: '错误: 缺少 appType 参数',
746
- missing_update_field: '错误: 至少需要指定一个更新字段(--name, --desc, --icon)',
747
+ missing_update_field: '错误: 至少需要指定一个更新字段(--name, --desc, --icon, --theme, --nav-theme, --layout)',
747
748
  title: ' update-app - 宜搭应用信息更新工具',
748
749
  app_type: '\n 应用 ID: {0}',
749
750
  new_name: ' 新名称: {0}',
@@ -754,6 +755,7 @@ openyida - 宜搭命令行工具
754
755
  app_type_label: ' appType: {0}',
755
756
  name_label: ' 应用名称: {0}',
756
757
  failed: ' ❌ 更新失败: {0}',
758
+ layout_notice: '提示:layoutDirection 由宜搭应用外壳在创建/刷新时消费;若后台切换后顶部操作栏未立即恢复,请重新打开工作台或使用目标 layout 重新创建应用。',
757
759
  },
758
760
 
759
761
  // ── lib/process/create-process.js ─────────────────
@@ -962,6 +964,7 @@ openyida - 宜搭命令行工具
962
964
  lint_foreach_callback_function: '.forEach(function ...) 可能导致回调内 this 丢失,建议改为 .forEach((item) => ...)',
963
965
  lint_controlled_input: 'input 使用了 value 受控模式,宜搭页面应使用 defaultValue + onChange 写入 _customState',
964
966
  lint_native_select_ui: '检测到原生 select。面向用户的自定义页面下拉交互应使用 Tailwind 风格的自定义下拉组件,避免浏览器原生控件观感不一致',
967
+ lint_iframe_self_navigation: '宜搭自定义页面运行在 iframe 中,跳转宜搭页面时请使用 target="_top"/target="_blank" 或 window.top.location,避免页面套娃',
965
968
  lint_page_size_limit: 'pageSize={0} 超过宜搭接口上限 100,请改为 100 或更小',
966
969
  lint_yida_api_catch: 'this.utils.yida API 调用未检测到 .catch(),请补充错误处理并 toast 给用户',
967
970
  lint_echarts_legacy_map_china: 'ECharts 5 已废弃 echarts/map/js/china.js,请加载 DataV GeoJSON 后调用 echarts.registerMap("china", geoJson)',
@@ -29,8 +29,8 @@ const { warn } = require('./chalk');
29
29
  const USAGE = `openyida data - Unified Yida data CLI
30
30
 
31
31
  Usage:
32
- openyida data query form <appType> <formUuid> [--page N] [--size N] [--search-json JSON|--search-file .cache/openyida/search.json] [--inst-id ID]
33
- openyida data get form <appType> --inst-id <formInstId>
32
+ openyida data query form <appType> <formUuid> [--page N] [--size N] [--all] [--max-pages N] [--search-json JSON|--search-file .cache/openyida/search.json] [--inst-id ID] [--no-hydrate-subforms]
33
+ openyida data get form <appType> --inst-id <formInstId> [--form-uuid <formUuid>] [--no-hydrate-subforms]
34
34
  openyida data create form <appType> <formUuid> (--data-json <JSON>|--data-file .cache/openyida/data.json) [--dept-id ID]
35
35
  openyida data update form <appType> --inst-id <formInstId> (--data-json <JSON>|--data-file .cache/openyida/data.json) [--use-latest-version y]
36
36
  openyida data query subform <appType> <formUuid> --inst-id <formInstId> --table-field-id <fieldId> [--page N] [--size N]
@@ -113,6 +113,17 @@ function clampPageSize(options, defaultSize = 20) {
113
113
  options.page = page;
114
114
  }
115
115
 
116
+ function parsePositiveInt(value, defaultValue, label) {
117
+ const parsed = Number.parseInt(value || `${defaultValue}`, 10);
118
+ if (!Number.isFinite(parsed) || parsed <= 0) {
119
+ if (label) {
120
+ parseError(`${label} 必须是正整数`);
121
+ }
122
+ return defaultValue;
123
+ }
124
+ return parsed;
125
+ }
126
+
116
127
  function requirePositionals(positionals, count, names) {
117
128
  if (positionals.length < count) {
118
129
  parseError(`缺少必填参数 ${names.join(' ')}`);
@@ -230,6 +241,138 @@ function normalizeFormDatasResult(result) {
230
241
  return result;
231
242
  }
232
243
 
244
+ function getResultDataList(result) {
245
+ if (!result) {return [];}
246
+ if (Array.isArray(result.data)) {return result.data;}
247
+ if (result.content && Array.isArray(result.content.data)) {return result.content.data;}
248
+ if (result.content && Array.isArray(result.content.dataList)) {return result.content.dataList;}
249
+ return [];
250
+ }
251
+
252
+ function getResultTotalCount(result) {
253
+ if (!result) {return 0;}
254
+ if (result.totalCount !== undefined) {return Number(result.totalCount) || 0;}
255
+ if (result.content && result.content.totalCount !== undefined) {return Number(result.content.totalCount) || 0;}
256
+ return 0;
257
+ }
258
+
259
+ function getFormDataContainer(result) {
260
+ if (!result || !result.success) {return null;}
261
+ const candidates = [
262
+ result.content,
263
+ result.data,
264
+ result.content && result.content.data,
265
+ result.content && result.content.formData,
266
+ ];
267
+
268
+ for (const candidate of candidates) {
269
+ if (!candidate || typeof candidate !== 'object') {continue;}
270
+ if (candidate.formData && typeof candidate.formData === 'object') {
271
+ return candidate.formData;
272
+ }
273
+ }
274
+
275
+ return null;
276
+ }
277
+
278
+ function normalizeSubformResult(result) {
279
+ const normalized = normalizeFormDatasResult(result);
280
+ return {
281
+ ...normalized,
282
+ data: getResultDataList(normalized),
283
+ totalCount: getResultTotalCount(normalized),
284
+ };
285
+ }
286
+
287
+ async function fetchAllPages(fetchPage, options, defaultSize = 100) {
288
+ const pageSize = Math.min(parsePositiveInt(options.size, defaultSize), 100);
289
+ const maxPages = parsePositiveInt(options.max_pages, 1000);
290
+ const firstPage = parsePositiveInt(options.page, 1);
291
+ const allData = [];
292
+ let page = firstPage;
293
+ let lastResult = null;
294
+ let pagesFetched = 0;
295
+
296
+ while (pagesFetched < maxPages) {
297
+ const result = normalizeFormDatasResult(await fetchPage(page, pageSize));
298
+ lastResult = result;
299
+ const data = getResultDataList(result);
300
+ allData.push(...data);
301
+ pagesFetched++;
302
+
303
+ const totalCount = getResultTotalCount(result);
304
+ if (totalCount && allData.length >= totalCount) {break;}
305
+ if (!data.length || data.length < pageSize) {break;}
306
+ page++;
307
+ }
308
+
309
+ return {
310
+ ...(lastResult || { success: true }),
311
+ data: allData,
312
+ totalCount: getResultTotalCount(lastResult) || allData.length,
313
+ currentPage: firstPage,
314
+ pageSize,
315
+ pagesFetched,
316
+ };
317
+ }
318
+
319
+ async function fetchAllSubformRows(session, appType, formUuid, formInstId, tableFieldId, options = {}) {
320
+ const result = await fetchAllPages((page, size) => sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/listTableDataByFormInstIdAndTableId.json`, {
321
+ formUuid,
322
+ formInstanceId: formInstId,
323
+ tableFieldId,
324
+ currentPage: String(page),
325
+ pageSize: String(size),
326
+ }), {
327
+ page: 1,
328
+ size: options.size || 100,
329
+ max_pages: options.max_pages || 1000,
330
+ }, 100);
331
+ return normalizeSubformResult(result);
332
+ }
333
+
334
+ async function hydrateTruncatedSubforms(result, context) {
335
+ if (!context || !context.formUuid || !context.formInstId || context.disabled) {
336
+ return result;
337
+ }
338
+
339
+ const formData = getFormDataContainer(result);
340
+ if (!formData) {return result;}
341
+
342
+ const hydrated = [];
343
+ const entries = Object.entries(formData);
344
+ for (const [fieldId, value] of entries) {
345
+ if (!/^tableField_/.test(fieldId)) {continue;}
346
+ if (!Array.isArray(value) || value.length < 50) {continue;}
347
+ if (value.length > 0 && !value.every(item => item && typeof item === 'object')) {continue;}
348
+
349
+ const subformResult = await fetchAllSubformRows(
350
+ context.session,
351
+ context.appType,
352
+ context.formUuid,
353
+ context.formInstId,
354
+ fieldId,
355
+ context.options || {}
356
+ );
357
+ const rows = subformResult.data || [];
358
+ if (rows.length > value.length) {
359
+ formData[fieldId] = rows;
360
+ hydrated.push({
361
+ fieldId,
362
+ originalCount: value.length,
363
+ hydratedCount: rows.length,
364
+ });
365
+ }
366
+ }
367
+
368
+ if (hydrated.length > 0) {
369
+ const target = result.content && typeof result.content === 'object' ? result.content : result;
370
+ target._openyidaHydratedSubforms = hydrated;
371
+ }
372
+
373
+ return result;
374
+ }
375
+
233
376
  function printResult(result) {
234
377
  const errorCode = result && result.errorCode;
235
378
  const hasErrorCode = errorCode !== undefined && errorCode !== null && errorCode !== '' && errorCode !== 0 && errorCode !== '0';
@@ -250,13 +393,41 @@ function printFormDatasResult(result) {
250
393
  async function queryForm(positionals, options, session) {
251
394
  requirePositionals(positionals, 2, ['appType', 'formUuid']);
252
395
  const [appType, formUuid] = positionals;
253
- clampPageSize(options);
396
+ clampPageSize(options, options.all ? 100 : 20);
254
397
 
255
398
  let result;
256
399
  if (options.inst_id) {
257
400
  result = await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/getFormDataById.json`, {
258
401
  formInstId: options.inst_id,
259
402
  });
403
+ result = await hydrateTruncatedSubforms(result, {
404
+ session,
405
+ appType,
406
+ formUuid,
407
+ formInstId: options.inst_id,
408
+ disabled: !!options.no_hydrate_subforms,
409
+ options,
410
+ });
411
+ } else if (options.all) {
412
+ const searchJson = readJsonOption(options, 'search_json', 'search_file', '查询条件');
413
+ result = await fetchAllPages((page, size) => {
414
+ const params = {
415
+ formUuid,
416
+ appType,
417
+ currentPage: String(page),
418
+ pageSize: String(size),
419
+ };
420
+ if (searchJson) {
421
+ params.searchFieldJson = searchJson;
422
+ }
423
+ for (const key of ['originator_id', 'create_from', 'create_to', 'modified_from', 'modified_to', 'dynamic_order']) {
424
+ if (options[key]) {params[snakeToCamel(key)] = options[key];}
425
+ }
426
+ const requestPath = options.ids_only
427
+ ? `/dingtalk/web/${appType}/v1/form/searchFormDataIds.json`
428
+ : `/dingtalk/web/${appType}/v1/form/searchFormDatas.json`;
429
+ return sendGet(session, appType, requestPath, params);
430
+ }, options, 100);
260
431
  } else {
261
432
  const params = {
262
433
  formUuid,
@@ -284,8 +455,16 @@ async function getForm(positionals, options, session) {
284
455
  requirePositionals(positionals, 1, ['appType']);
285
456
  requireOption(options, 'inst_id');
286
457
  const [appType] = positionals;
287
- printResult(await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/getFormDataById.json`, {
458
+ const result = await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/getFormDataById.json`, {
459
+ formInstId: options.inst_id,
460
+ });
461
+ printResult(await hydrateTruncatedSubforms(result, {
462
+ session,
463
+ appType,
464
+ formUuid: options.form_uuid,
288
465
  formInstId: options.inst_id,
466
+ disabled: !!options.no_hydrate_subforms,
467
+ options,
289
468
  }));
290
469
  }
291
470
 
@@ -299,7 +478,7 @@ async function createForm(positionals, options, session) {
299
478
  formDataJson: dataJson,
300
479
  };
301
480
  if (options.dept_id) {params.deptId = options.dept_id;}
302
- printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/form/saveFormData.json`, params));
481
+ printResult(await sendPost(session, appType, `/alibaba/web/${appType}/_/saveFormData.json`, params));
303
482
  }
304
483
 
305
484
  async function updateForm(positionals, options, session) {
package/lib/core/utils.js CHANGED
@@ -433,6 +433,10 @@ function isCsrfTokenExpired(responseJson) {
433
433
  );
434
434
  }
435
435
 
436
+ function isHttpRedirectStatus(statusCode) {
437
+ return [301, 302, 303, 307, 308].includes(Number(statusCode));
438
+ }
439
+
436
440
  // ── base_url 解析 ─────────────────────────────────────
437
441
 
438
442
  /**
@@ -460,6 +464,16 @@ function resolveBaseUrl(cookieData, defaultBaseUrl) {
460
464
 
461
465
  // ── HTTP 请求工具 ─────────────────────────────────────
462
466
 
467
+ function collectResponseText(res, onEnd) {
468
+ const chunks = [];
469
+ res.on('data', (chunk) => {
470
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
471
+ });
472
+ res.on('end', () => {
473
+ onEnd(Buffer.concat(chunks).toString('utf8'));
474
+ });
475
+ }
476
+
463
477
  /**
464
478
  * 发送 HTTP POST 请求(application/x-www-form-urlencoded)
465
479
  * @param {string} baseUrl
@@ -508,12 +522,18 @@ function httpPost(baseUrl, requestPath, postData, cookies, optionsOverride = {})
508
522
  };
509
523
 
510
524
  const req = requestModule.request(options, (res) => {
511
- let data = '';
512
- res.on('data', (chunk) => { data += chunk; });
513
- res.on('end', () => {
525
+ collectResponseText(res, (data) => {
514
526
  if (!optionsOverride.silentStatus) {
515
527
  warn(t('common.http_status', res.statusCode));
516
528
  }
529
+ if (isHttpRedirectStatus(res.statusCode)) {
530
+ resolve({
531
+ __needLogin: true,
532
+ __httpStatus: res.statusCode,
533
+ __location: res.headers.location || '',
534
+ });
535
+ return;
536
+ }
517
537
  try {
518
538
  const parsed = JSON.parse(data);
519
539
  if (isLoginExpired(parsed)) {
@@ -593,12 +613,18 @@ function httpGet(baseUrl, requestPath, queryParams, cookies, optionsOverride = {
593
613
  };
594
614
 
595
615
  const req = requestModule.request(options, (res) => {
596
- let data = '';
597
- res.on('data', (chunk) => { data += chunk; });
598
- res.on('end', () => {
616
+ collectResponseText(res, (data) => {
599
617
  if (!optionsOverride.silentStatus) {
600
618
  warn(t('common.http_status', res.statusCode));
601
619
  }
620
+ if (isHttpRedirectStatus(res.statusCode)) {
621
+ resolve({
622
+ __needLogin: true,
623
+ __httpStatus: res.statusCode,
624
+ __location: res.headers.location || '',
625
+ });
626
+ return;
627
+ }
602
628
  try {
603
629
  const parsed = JSON.parse(data);
604
630
  if (isLoginExpired(parsed)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.5.25",
3
+ "version": "2026.5.26",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -17,6 +17,7 @@ const SKILL_COVERAGE = {
17
17
  'yida-business-rule': { level: 'opt-in', reason: 'business association rules mutate form event configuration; validate in a dedicated real-form/UI stage before adding to deterministic shared E2E' },
18
18
  'yida-chart': { level: 'real-e2e', stages: ['report', 'dashboard'], tests: ['report chart config generation'] },
19
19
  'yida-connector': { level: 'offline', stages: ['connector-local'], commands: ['connector gen-template', 'connector parse-api'] },
20
+ 'yida-connector-safe-actions': { level: 'offline', stages: ['connector-local'], commands: ['connector parse-api', 'connector test --action <operationId>'], reason: 'skill documents conservative HTTP connector action generation and repair workflow; shared E2E should validate local parsing without mutating tenant connectors' },
20
21
  'yida-corp-efficiency': { level: 'offline-unit', tests: ['tests/corp-efficiency.test.js'], reason: 'enterprise efficiency queries and notify mutations are not safe for shared real org E2E' },
21
22
  'yida-corp-manager': { level: 'offline-unit', tests: ['tests/corp-manager.test.js'], reason: 'enterprise admin mutations are not safe for shared real org E2E' },
22
23
  'yida-create-app': { level: 'real-e2e', stages: ['app'], commands: ['create-app'] },
@@ -186,6 +186,7 @@ openyida copy
186
186
  | `yida-integration` | `skills/yida-integration/SKILL.md` | 集成自动化逻辑流(创建/列表/启停) | `openyida integration <create\|list\|enable\|disable> ...` |
187
187
  | `yida-business-rule` | `skills/yida-business-rule/SKILL.md` | 表单业务关联规则高级函数(INSERT/UPDATE/DELETE/UPSERT) | 详见 SKILL.md |
188
188
  | `yida-connector` | `skills/yida-connector/SKILL.md` | HTTP 连接器创建、测试与动作管理 | `openyida connector smart-create <配置>` |
189
+ | `yida-connector-safe-actions` | `skills/yida-connector-safe-actions/SKILL.md` | 从前后端接口安全生成 HTTP 连接器执行动作,并修复测试后动作消失问题 | `openyida connector add-action --operations <动作JSON> --connector-id <id> --confirm` |
189
190
  | `sls-log-workbench` | `skills/sls-log-workbench/SKILL.md` | SLS 日志查询工作台排查(内部技术支持) | 详见 SKILL.md |
190
191
  | `yida-dashboard` | `skills/yida-dashboard/SKILL.md` | 经营看板/驾驶舱/数据大屏完整产品化交付(单屏控制塔+宜搭待办连接器真实钉钉待办闭环+卡片截图+组织内短链) | 详见 SKILL.md |
191
192
  | `yida-chart` | `skills/yida-chart/SKILL.md` | 报表可视化(ECharts 图表 + 数据聚合) | 详见 SKILL.md |
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: yida-connector-safe-actions
3
+ description: 宜搭 HTTP 连接器执行动作安全生成与修复。适用于从前端 API 文件、后端 Controller/接口定义生成 OpenYida 连接器操作,或修复“点击测试后报错、所有操作消失”的连接器动作配置问题。不适用于创建连接器本体(应使用 yida-connector),也不适用于配置集成自动化逻辑流(应使用 yida-integration)。
4
+ ---
5
+
6
+ # 宜搭 HTTP 连接器执行动作安全生成
7
+
8
+ ## 适用场景
9
+
10
+ 当用户需要把已有系统接口接入宜搭 HTTP 连接器时使用本技能,尤其适用于:
11
+
12
+ - 已有连接器,需要继续添加“执行动作”
13
+ - 用户提供前端 API 文件和后端 Controller/接口定义文件
14
+ - 从 Vue/React API wrapper、ASP.NET Controller、Spring Controller 等代码中提取接口
15
+ - 点击宜搭连接器测试面板时报错,刷新后动作列表为空
16
+ - `openyida connector list-actions <connector-id>` 返回 0 个动作,但连接器仍存在
17
+ - 需要说明“如何正确使用 OpenYida 生成 HTTP 连接器动作”
18
+
19
+ 如果只是创建连接器、配置鉴权、管理连接器账号,优先使用 `yida-connector`。
20
+
21
+ ## 核心原则
22
+
23
+ - 先读源码再生成动作,不要凭空编造接口路径、参数或 action-id。
24
+ - 默认只生成前端 API 文件实际暴露/调用的接口,除非用户明确要求覆盖后端全部接口。
25
+ - 对未知响应结构保持保守,先只输出根对象 `Response`,避免宜搭测试面板解析复杂输出结构时报错。
26
+ - Windows PowerShell 读取中文 JSON 时必须显式使用 UTF-8。
27
+ - 修改后必须执行“添加动作 -> 列表验证 -> CLI 测试 -> 再次列表验证”的闭环。
28
+ - 如果一次错误配置导致动作被清空,应重建完整动作列表,而不是只追加缺失动作。
29
+
30
+ ## 推荐流程
31
+
32
+ ### 1. 读取接口来源
33
+
34
+ 同时读取用户提供的前端 API 文件和后端接口定义文件。
35
+
36
+ 前端 API 文件用于确认“哪些接口真的要暴露给宜搭连接器”;后端文件用于确认 method、route、query/path/body 参数和默认值。
37
+
38
+ ### 2. 查看连接器状态
39
+
40
+ ```bash
41
+ openyida connector detail <connector-id>
42
+ openyida connector list-actions <connector-id>
43
+ ```
44
+
45
+ 记录以下信息:
46
+
47
+ - 连接器 ID
48
+ - 连接器域名、协议、基础路径、鉴权方式
49
+ - 当前已有动作列表
50
+ - 是否已经出现动作被清空
51
+
52
+ ### 3. 生成动作 JSON 文件
53
+
54
+ 建议放在当前项目:
55
+
56
+ ```text
57
+ .cache/openyida/connector-actions/<业务名>-actions.json
58
+ ```
59
+
60
+ 动作 ID 建议使用顺序编号:
61
+
62
+ ```json
63
+ "id": "operation-1"
64
+ ```
65
+
66
+ 动作调用名使用前端函数名或后端 Action 名:
67
+
68
+ ```json
69
+ "operationId": "getUserDtuSns"
70
+ ```
71
+
72
+ ### 4. 校验 JSON 编码
73
+
74
+ Windows PowerShell 必须加 `-Encoding UTF8`:
75
+
76
+ ```powershell
77
+ Get-Content -Raw -Encoding UTF8 .cache\openyida\connector-actions\<业务名>-actions.json | ConvertFrom-Json | Out-Null
78
+ ```
79
+
80
+ 不要使用默认 `Get-Content` 校验中文 JSON,默认编码可能导致误判或乱码。
81
+
82
+ ### 5. 添加动作
83
+
84
+ ```bash
85
+ openyida connector add-action --operations .cache/openyida/connector-actions/<业务名>-actions.json --connector-id <connector-id> --confirm
86
+ ```
87
+
88
+ ### 6. 验证动作存在
89
+
90
+ ```bash
91
+ openyida connector list-actions <connector-id>
92
+ ```
93
+
94
+ ### 7. 用 CLI 测试动作
95
+
96
+ CLI 测试时 `--action` 使用 `operationId`,不是顺序编号 `operation-1`:
97
+
98
+ ```bash
99
+ openyida connector test --connector-id <connector-id> --action <operationId>
100
+ ```
101
+
102
+ 测试后再次查询动作列表,确认动作没有被清空:
103
+
104
+ ```bash
105
+ openyida connector list-actions <connector-id>
106
+ ```
107
+
108
+ ## 安全动作格式
109
+
110
+ ### 无参数 GET 动作
111
+
112
+ ```json
113
+ {
114
+ "id": "operation-1",
115
+ "operationId": "getAccessToken",
116
+ "summary": "获取三色灯 Token",
117
+ "description": "获取三色灯 Token",
118
+ "url": "api/TriColorLamp/GetAccessToken",
119
+ "method": "get",
120
+ "inputs": [],
121
+ "parameters": {},
122
+ "responses": {
123
+ "type": "object",
124
+ "properties": {}
125
+ },
126
+ "outputs": [
127
+ {
128
+ "defaultValue": "{}",
129
+ "desc": "响应体结构",
130
+ "name": "Response",
131
+ "paramType": "Object",
132
+ "required": false
133
+ }
134
+ ],
135
+ "origin": true
136
+ }
137
+ ```
138
+
139
+ ### 带 Query 参数的 GET 动作
140
+
141
+ ```json
142
+ {
143
+ "id": "operation-2",
144
+ "operationId": "getDtuSnData",
145
+ "summary": "获取单设备三色灯数据",
146
+ "description": "根据 dtuSn 和日期获取三色灯数据",
147
+ "url": "api/TriColorLamp/GetDtuSnData",
148
+ "method": "get",
149
+ "inputs": [
150
+ {
151
+ "childList": [
152
+ {
153
+ "componentName": "TextField",
154
+ "desc": "设备 dtuSn",
155
+ "name": "dtuSn",
156
+ "queryDefaultValue": {
157
+ "paramType": "fixedValue",
158
+ "defaultValue": ""
159
+ },
160
+ "required": true
161
+ },
162
+ {
163
+ "componentName": "TextField",
164
+ "desc": "查询日期,可为空",
165
+ "name": "date",
166
+ "queryDefaultValue": {
167
+ "paramType": "fixedValue",
168
+ "defaultValue": ""
169
+ },
170
+ "required": false
171
+ }
172
+ ],
173
+ "desc": "请求参数",
174
+ "name": "Query",
175
+ "paramType": "Object",
176
+ "required": false
177
+ }
178
+ ],
179
+ "parameters": {
180
+ "query": [
181
+ {
182
+ "name": "dtuSn",
183
+ "type": "string",
184
+ "required": true,
185
+ "description": "设备 dtuSn",
186
+ "queryDefaultValue": {
187
+ "paramType": "fixedValue",
188
+ "defaultValue": ""
189
+ }
190
+ },
191
+ {
192
+ "name": "date",
193
+ "type": "string",
194
+ "required": false,
195
+ "description": "查询日期,可为空",
196
+ "queryDefaultValue": {
197
+ "paramType": "fixedValue",
198
+ "defaultValue": ""
199
+ }
200
+ }
201
+ ]
202
+ },
203
+ "responses": {
204
+ "type": "object",
205
+ "properties": {}
206
+ },
207
+ "outputs": [
208
+ {
209
+ "defaultValue": "{}",
210
+ "desc": "响应体结构",
211
+ "name": "Response",
212
+ "paramType": "Object",
213
+ "required": false
214
+ }
215
+ ],
216
+ "origin": true
217
+ }
218
+ ```
219
+
220
+ ## 字段规则
221
+
222
+ | 字段 | 推荐写法 |
223
+ | --- | --- |
224
+ | `id` | 使用 `operation-1`、`operation-2` 等顺序编号 |
225
+ | `operationId` | 使用前端函数名或后端 Action 名,例如 `getDtuSnData` |
226
+ | `summary` | 中文短名称,用于宜搭界面展示 |
227
+ | `description` | 一句话说明动作用途 |
228
+ | `url` | 不带域名,只写连接器域名后的相对路径 |
229
+ | `method` | 小写,例如 `get`、`post`、`put` |
230
+ | `inputs` | GET 参数只放 `Query`,不要放 `Body` |
231
+ | `parameters.query` | 与 `inputs[].childList[]` 中的 query 参数保持一致 |
232
+ | `queryDefaultValue` | Query 参数建议在 `inputs` 和 `parameters` 两处都写 |
233
+ | `outputs` | 修复或首次生成时只保留根对象 `Response` |
234
+
235
+ ## 避免测试面板崩溃
236
+
237
+ 以下写法容易导致宜搭连接器测试面板解析异常,应谨慎使用:
238
+
239
+ - 未确认平台兼容时,在输出字段里展开复杂 `Code`、`Message`、`Data` 子字段
240
+ - 给输入/输出叶子节点添加非必要的 `label`
241
+ - GET 动作配置 `Body`
242
+ - `inputs` 与 `parameters` 中的 query 参数不一致
243
+ - 中文 JSON 未按 UTF-8 读取或保存
244
+ - 只追加部分动作,遗漏原有 Token/Test 动作,导致重建后动作列表不完整
245
+
246
+ 当用户反馈“点击测试后操作都不见了”,优先按修复流程处理,不要继续追加同一份风险 JSON。
247
+
248
+ ## ASP.NET Controller 映射规则
249
+
250
+ 对于 ASP.NET Controller:
251
+
252
+ ```csharp
253
+ [Route("api/[controller]/[action]")]
254
+ public class TriColorLampController : ControllerBase
255
+ ```
256
+
257
+ 路径映射为:
258
+
259
+ ```text
260
+ api/TriColorLamp/<ActionName>
261
+ ```
262
+
263
+ 规则:
264
+
265
+ - `[HttpGet]`、`[HttpPost]`、`[HttpPut]` 映射到对应小写 method
266
+ - `[FromQuery]` 参数映射为 `Query`
267
+ - 有默认值或可空参数,例如 `string date = null`,映射为 `required: false`
268
+ - 无默认值的必填参数映射为 `required: true`
269
+
270
+ ## 故障修复流程
271
+
272
+ 当连接器动作被清空时:
273
+
274
+ 1. 执行 `openyida connector detail <connector-id>`,确认连接器仍存在。
275
+ 2. 执行 `openyida connector list-actions <connector-id>`,确认动作是否为 0。
276
+ 3. 从前端 API 文件和后端 Controller 重新生成完整动作列表。
277
+ 4. 如果原来有 Token/Test 动作,也要一起放回 JSON。
278
+ 5. 使用保守输出结构,不展开复杂响应字段。
279
+ 6. 执行 `add-action --confirm` 重建动作。
280
+ 7. 执行 `list-actions` 验证动作数量。
281
+ 8. 用 `connector test --action <operationId>` 测试至少一个无参数动作。
282
+ 9. 测试后再次执行 `list-actions`,确认动作没有再次消失。