openyida 2026.10.23 → 2026.10.25

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 (31) hide show
  1. package/README.md +2 -1
  2. package/bin/yida.js +10 -0
  3. package/lib/app/services/canvas-page-schema-builder.js +43 -1
  4. package/lib/core/command-manifest.js +43 -3
  5. package/lib/core/locales/en.js +15 -0
  6. package/lib/core/locales/zh.js +15 -0
  7. package/lib/core/task-center.js +32 -4
  8. package/lib/navigation/page-navigation.js +62 -0
  9. package/lib/samples/openyida-scaffold/canvas-navigation.jsx +6 -2
  10. package/lib/solution-template/solution-template.js +666 -0
  11. package/lib/task-center/contract.js +26 -1
  12. package/lib/task-center/loader.js +5 -0
  13. package/lib/task-center/navigation.js +41 -0
  14. package/lib/task-center/scaffold.js +9 -0
  15. package/package.json +1 -1
  16. package/yida-skills/SKILL.md +5 -1
  17. package/yida-skills/references/yida-api.md +3 -1
  18. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +4 -2
  19. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +2 -0
  20. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +2 -0
  21. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +2 -2
  22. package/yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md +29 -1
  23. package/yida-skills/skills/yida-create-page/SKILL.md +1 -0
  24. package/yida-skills/skills/yida-design/SKILL.md +1 -1
  25. package/yida-skills/skills/yida-design/references/application-style-library.md +2 -0
  26. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +4 -0
  27. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +27 -44
  28. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +3 -1
  29. package/yida-skills/skills/yida-solution-template/SKILL.md +98 -0
  30. package/yida-skills/skills/yida-task-center/SKILL.md +3 -1
  31. package/yida-skills/skills-index.json +13 -0
package/README.md CHANGED
@@ -473,6 +473,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
473
473
  | `openyida app-entry get <appType> [--json]` | Read frontend and management runtime entries |
474
474
  | `openyida app-entry set <appType> [--frontend <url>\|--frontend-page <formUuid>] [--management <url>\|--management-page <formUuid>] [--clear-frontend] [--clear-management] [--json]` | Register application entries with partial update and readback |
475
475
  | `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
476
+ | `openyida solution-template <search\|detail\|prepare\|ai-build\|enable\|status> [templateId\|requestId] [options] [--json]` | Solution templates for independent local agents: discovery, cloud AI handoff and direct enable |
476
477
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
477
478
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
478
479
  | `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
@@ -534,7 +535,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
534
535
  | Command | Description |
535
536
  |---------|-------------|
536
537
  | `openyida data <query\|get\|create\|update\|transfer> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
537
- | `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
538
+ | `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|link\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
538
539
  | `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
539
540
  | `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
540
541
  | `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
package/bin/yida.js CHANGED
@@ -588,6 +588,10 @@ async function main() {
588
588
  }
589
589
  return;
590
590
  }
591
+ // Template-only preflight; managed runs retain their existing guard contract.
592
+ if (command === 'solution-template' && !hasHelpFlag(rawArgs) && !readManagedContext()) {
593
+ require('../lib/solution-template/solution-template').assertIndependentLocalAgent();
594
+ }
591
595
  commandReceipt = beginCommandReceipt({ command, args: rawArgs });
592
596
  process.once('exit', code => {
593
597
  try {commandReceipt.finish(code);} catch {process.exitCode = 1;}
@@ -790,6 +794,12 @@ async function main() {
790
794
  break;
791
795
  }
792
796
 
797
+ case 'solution-template': {
798
+ const { run } = require('../lib/solution-template/solution-template');
799
+ await run(args);
800
+ break;
801
+ }
802
+
793
803
  case 'corp-efficiency': {
794
804
  const { run } = require('../lib/corp-efficiency/corp-efficiency');
795
805
  await run(args);
@@ -10,7 +10,16 @@ const {
10
10
  findEmojiInValue,
11
11
  } = require('../../core/no-emoji-detector');
12
12
 
13
- const CANVAS_YIDA_API_BRIDGE_SOURCE = `var OPENYIDA_YIDA_FORM_API_METHODS = [
13
+ const { createPageNavigation } = require('../../navigation/page-navigation');
14
+ const { createTaskCenterContract } = require('../../task-center/contract');
15
+ const { createTaskCenterLoader } = require('../../task-center/loader');
16
+ const { createTaskCenterNavigation } = require('../../task-center/navigation');
17
+
18
+ const CANVAS_YIDA_API_BRIDGE_SOURCE = `${createPageNavigation.toString()}
19
+ ${createTaskCenterNavigation.toString()}
20
+ ${createTaskCenterContract.toString()}
21
+ ${createTaskCenterLoader.toString()}
22
+ var OPENYIDA_YIDA_FORM_API_METHODS = [
14
23
  'saveFormData',
15
24
  'updateFormData',
16
25
  'searchFormDataIds',
@@ -239,8 +248,15 @@ function openyidaCreateRouterBridge(utils) {
239
248
 
240
249
  function openyidaCreateUtilsBridge(context, yidaBridge) {
241
250
  var utils = context && context.utils;
251
+ var navigation = createPageNavigation(window);
252
+ var taskNavigation = createTaskCenterNavigation();
253
+ var taskCounts = createTaskCenterLoader(createTaskCenterContract(), function() { return yidaBridge; });
242
254
  var methodNames = openyidaCollectUtilsMethodNames(utils);
243
255
  function invoke(methodName) {
256
+ if (methodName === 'openPage') { return navigation.open.apply(null, Array.prototype.slice.call(arguments, 1)); }
257
+ if (['taskCenterCount', 'taskCenterLink', 'openTaskCenter'].indexOf(methodName) !== -1) {
258
+ return bridge[methodName].apply(null, Array.prototype.slice.call(arguments, 1));
259
+ }
244
260
  if (!utils || typeof utils[methodName] !== 'function') {
245
261
  throw new Error('this.utils.' + methodName + ' is not available');
246
262
  }
@@ -251,6 +267,8 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
251
267
  apiGroups: {
252
268
  utility: OPENYIDA_UTIL_API_METHODS.slice(),
253
269
  router: ['push'],
270
+ navigation: ['openPage', 'taskCenterLink', 'openTaskCenter'],
271
+ tasks: ['taskCenterCount'],
254
272
  yida: OPENYIDA_KNOWN_YIDA_API_METHODS.slice()
255
273
  },
256
274
  knownMethods: OPENYIDA_UTIL_API_METHODS.slice(),
@@ -259,9 +277,33 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
259
277
  return !utils || typeof utils[methodName] !== 'function';
260
278
  }),
261
279
  invoke: invoke,
280
+ openPage: navigation.open,
281
+ taskCenterLink: function(options) {
282
+ return taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
283
+ },
284
+ taskCenterCount: async function(options) {
285
+ if (!options || typeof options !== 'object' || Array.isArray(options)
286
+ || Object.keys(options).some(function(key) { return ['scope', 'appType', 'kind', 'filters'].indexOf(key) === -1; })) {
287
+ var error = new Error('TASK_FILTER_INVALID: options');
288
+ error.code = 'TASK_FILTER_INVALID';
289
+ throw error;
290
+ }
291
+ var target = taskNavigation.target({ scope: options.scope, appType: options.appType, kind: options.kind, baseUrl: window.location.origin });
292
+ var filters = createTaskCenterContract().normalizeFilters(target.kind, options.filters === undefined ? {} : options.filters, 'app-query');
293
+ return taskCounts.count(target.kind, Object.assign({}, filters, { transport: 'app-query' },
294
+ target.scope === 'corp' ? { scope: 'corp' } : { appType: target.appType }));
295
+ },
296
+ openTaskCenter: function(options) {
297
+ var target = taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
298
+ return navigation.open(target.pageUrl, { mode: target.mode });
299
+ },
262
300
  router: openyidaCreateRouterBridge(utils),
263
301
  yida: yidaBridge
264
302
  };
303
+ ['openPage', 'taskCenterLink', 'openTaskCenter', 'taskCenterCount'].forEach(function(methodName) {
304
+ if (bridge.availableMethods.indexOf(methodName) === -1) { bridge.availableMethods.push(methodName); }
305
+ });
306
+ bridge.missingKnownMethods = bridge.missingKnownMethods.filter(function(name) { return name !== 'openPage'; });
265
307
  methodNames.forEach(function(methodName) {
266
308
  if (!bridge[methodName]) {
267
309
  bridge[methodName] = function() {
@@ -556,6 +556,13 @@ const COMMAND_SIDE_EFFECTS = new Map([
556
556
  mutating_actions: ['create', 'update', 'delete'],
557
557
  })],
558
558
  ['cool-card', sideEffect('mixed', { mutates_yida: true, mutates_local: true, read_actions: ['list', 'templates', 'get'], mutating_actions: ['create'] })],
559
+ ['solution-template', sideEffect('mixed', {
560
+ mutates_yida: true,
561
+ mutates_local: true,
562
+ read_actions: ['search', 'detail', 'prepare', 'ai-build', 'status'],
563
+ mutating_actions: ['enable'],
564
+ note: 'Direct enable writes a durable local request receipt before the one-shot platform copy. AI build only validates the template and returns the native builder handoff.',
565
+ })],
559
566
  ['data-card', sideEffect('mixed', { mutates_yida: true, mutates_local: true, read_actions: ['list', 'get', 'probe', 'release-check', 'refs'], mutating_actions: ['create', 'save', 'publish', 'release-get', 'release-save', 'release-test'], note: 'release-get reads broadcast settings but the native backend may initialize the application cool app; classify it conservatively as a possible mutation.' })],
560
567
  ['print-template', sideEffect('mixed', printTemplateEffects)],
561
568
  ['form-view', sideEffect('mixed', {
@@ -683,7 +690,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
683
690
  ['task-center', sideEffect('mixed', {
684
691
  mutates_yida: false,
685
692
  mutates_local: true,
686
- read_actions: ['todo', 'created', 'done', 'cc', 'proxy', 'capabilities', 'options', 'inspect', 'count'],
693
+ read_actions: ['todo', 'created', 'done', 'cc', 'proxy', 'capabilities', 'link', 'options', 'inspect', 'count'],
687
694
  mutating_actions: ['scaffold'],
688
695
  })],
689
696
  ]);
@@ -855,6 +862,7 @@ const COMMAND_PERMISSIONS = new Map([
855
862
 
856
863
  ...permissionEntries(['print-template'], actionDependentPermission(printTemplatePermission)),
857
864
  ...permissionEntries(['cool-card'], actionDependentPermission({ preauthorized_actions: ['create'] })),
865
+ ...permissionEntries(['solution-template'], actionDependentPermission({ ask_actions: ['enable'] })),
858
866
  ...permissionEntries(['data-card'], actionDependentPermission({ preauthorized_actions: ['create', 'save', 'publish', 'release-get'], ask_actions: ['release-save', 'release-test'] })),
859
867
  ...permissionEntries([
860
868
  'form-view',
@@ -1249,6 +1257,34 @@ const COMMAND_GROUPS = [
1249
1257
  command('app-list', ['app-list'], 'app-list [--type managed|created] [--page N] [--size N]', 'help.cmd_app_list', { managedRun: {'scope':'denied','reason':'scope_not_audited'},
1250
1258
  notes: ['Successful results include appUrl/workbenchUrl for the business workspace and adminUrl for developer administration. Complete application delivery retains adminUrl regardless of auth runtime; route construction does not verify recipient access. Preserve the frontend URL from its own page result.'],
1251
1259
  }),
1260
+ command('solution-template', ['solution-template'], 'solution-template <search|detail|prepare|ai-build|enable|status> [templateId|requestId] [options] [--json]', 'solution_template.description', {
1261
+ managedRun: { scope: 'denied', reason: 'creates_new_application_or_requires_native_builder_handoff' },
1262
+ output: 'json',
1263
+ args: [
1264
+ { name: 'action', type: 'string', source: 'positional', position: 0, required: true, choices: ['search', 'detail', 'prepare', 'ai-build', 'enable', 'status'] },
1265
+ { name: 'identity', type: 'string', source: 'positional', position: 1, description: 'TPL_ template ID for detail/prepare/ai-build/enable; durable request ID for status.' },
1266
+ { name: 'query', type: 'string', source: 'option', builder_options: ['--query'] },
1267
+ { name: 'scene', type: 'string', source: 'option', builder_options: ['--scene'] },
1268
+ { name: 'industry', type: 'string', source: 'option', builder_options: ['--industry'] },
1269
+ { name: 'name', type: 'string', source: 'option', builder_options: ['--name'] },
1270
+ { name: 'withData', type: 'string', source: 'option', builder_options: ['--with-data'], choices: ['y', 'n'] },
1271
+ { name: 'requestId', type: 'string', source: 'option', builder_options: ['--request-id'] },
1272
+ { name: 'retry', type: 'boolean', source: 'option', builder_options: ['--retry'], description: 'Explicitly retry a platform-rejected enable attempt; never replay an uncertain request.' },
1273
+ { name: 'wait', type: 'string', source: 'option', builder_options: ['--wait'], description: 'Readback wait budget, 0-60 seconds. Default: enable 10, status 0.' },
1274
+ { name: 'page', type: 'string', source: 'option', builder_options: ['--page'] },
1275
+ { name: 'size', type: 'string', source: 'option', builder_options: ['--size'] },
1276
+ { name: 'json', type: 'boolean', source: 'option', builder_options: ['--json'] },
1277
+ ],
1278
+ examples: [
1279
+ 'openyida solution-template search --query "合同" --json',
1280
+ 'openyida solution-template detail TPL_XXX --json',
1281
+ 'openyida solution-template prepare TPL_XXX --name "合同管理" --with-data n --json',
1282
+ 'openyida solution-template ai-build TPL_XXX --json',
1283
+ 'openyida solution-template enable TPL_XXX --name "合同管理" --with-data y --json',
1284
+ 'openyida solution-template status st-xxxxxxxx --json',
1285
+ ],
1286
+ notes: ['Independent local agents only: reject cloud/bound contexts, managed local runs and unknown environments before auth, HTTP or receipt writes. Help and command discovery remain available. Cloud native templateReference/PRD building continues inside the bound app and must not use this command. ai-build only hands the user a cloud builder URL; it does not run template AI generation locally. Search currently excludes SaaS templates.', 'Use only for an explicit user request to operate on Solution Center templates or to continue an already confirmed template task within its authorized scope. Ordinary app creation stays with yida-app; do not automatically search, recommend, enable or fall back to a template. A query, reference link, generic creation permission or AI handoff is not consent to enable. Enable, retry after rejection and an additional copy each require explicit user authorization. search/detail/prepare are read-only. prepare summarizes the organization/environment, template version, name, sample data and existing apps/requests; source availability is not permission to enable. Permissions and entitlements are decided exclusively by the platform on submit. ai-build only returns the native builder handoff. enable writes a durable receipt before a one-shot copy and waits up to 10 seconds for app-list readback; --wait 0-60 controls this budget. Rejected attempts retain the platform error, never claim later same-name apps, and require explicit --retry to submit again. Unknown outcomes only allow readback, including with --retry. Repeating a completed request returns its app without re-copying; use a distinct request ID only for an explicitly requested additional copy. applicationVerified reports current readback, while previouslyVerified/lastVerifiedAt retain historical creation evidence. No percentage progress or background polling.'],
1287
+ }),
1252
1288
  command('corp-efficiency', ['corp-efficiency'], 'corp-efficiency [overview|details|detail|groups|notify] [options] [--open|--no-open]', 'help.cmd_corp_efficiency', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
1253
1289
  output: 'json',
1254
1290
  }),
@@ -1821,10 +1857,11 @@ const COMMAND_GROUPS = [
1821
1857
  'openyida data transfer task APP_XXX --process-inst-id PINST_XXX --form-uuid FORM_XXX --to-user-id USER_ID --expect-form-name "Approval form" --expect-form-type process --json',
1822
1858
  ],
1823
1859
  }),
1824
- command('task-center', ['task-center'], 'task-center <todo|created|done|cc|proxy|capabilities|scaffold|options|inspect|count> [--spec <file.json> --output <file.canvas.jsx>] [options]', 'help.cmd_task_center', {
1860
+ command('task-center', ['task-center'], 'task-center <todo|created|done|cc|proxy|capabilities|link|scaffold|options|inspect|count> [--spec <file.json> --output <file.canvas.jsx>] [options]', 'help.cmd_task_center', {
1825
1861
  requiresLogin: false,
1826
1862
  managedRun: { scope: 'actions', actions: {
1827
1863
  capabilities: { scope: 'local', allowedFlags: ['--json'] },
1864
+ link: { scope: 'local', allowedFlags: ['--scope', '--type', '--base-url', '--mode', '--json'] },
1828
1865
  scaffold: { scope: 'local' },
1829
1866
  options: { scope: 'app', appPosition: 1 },
1830
1867
  inspect: { scope: 'app', appPosition: 1 },
@@ -1833,13 +1870,16 @@ const COMMAND_GROUPS = [
1833
1870
  output: 'json',
1834
1871
  examples: [
1835
1872
  'openyida task-center capabilities --json',
1873
+ 'openyida task-center link APP_XXX --scope app --type todo --base-url https://tenant.aliwork.com --json',
1874
+ 'openyida task-center link --scope corp --type cc --base-url https://tenant.aliwork.com --json',
1875
+ 'openyida task-center count --scope corp --type created --json',
1836
1876
  'openyida task-center options APP_XXX --type originators --keyword 张 --json',
1837
1877
  'openyida task-center inspect APP_XXX --process-inst-id INSTANCE_REPLACE --form-uuid FORM_REPLACE --json',
1838
1878
  'openyida task-center inspect APP_XXX --process-inst-id INSTANCE_REPLACE --history --task-id TASK_REPLACE --json',
1839
1879
  'openyida task-center count APP_XXX --type todo --transport app-query --filters-json \'{"keyword":"采购"}\' --json',
1840
1880
  'openyida task-center scaffold --spec workflow-spec.json --output project/pages/src/progress.canvas.jsx',
1841
1881
  ],
1842
- notes: ['capabilities describes application task kinds, filters, lookup options, workflow read models and scaffold specs. inspect <appType> --process-inst-id ID [--history] [--task-id ID] reads authorized process progress and optional history; taskResult matches a task, without asserting visitor ownership. count <appType> --type KIND [--transport app-query] [--filters-json JSON] reads one list record and its server total; unknown totals stay null. Neither read command requires a custom-page choice. The tasks scaffold selects fields, filterFields and optional expandProcess; the progress view exports ProcessProgressSection for a real linked instance. options <appType> --type originators|processes|forms --keyword TEXT --size N --json reads current-visitor lookup candidates. scaffold creates a new local Canvas page only after an explicit custom-page request or affirmative ask_human choice, recorded as customPage.source and confirmed=true. A generic task-center request or Fast mode does not provide that choice. Pages use the current visitor and existing application theme; CLI credentials are never embedded. Global queries remain unavailable in application-bound managed runs.'],
1882
+ notes: ['link generates a native app or corporation task-center pageUrl and DingTalk AppLink without opening a browser or writing platform resources. --scope app|corp is required; app requires appType, corp rejects appType. --base-url allows offline URL generation; otherwise the active auth environment supplies the origin. --mode defaults to auto: browser pages stay in the browser and DingTalk pages use native openLink with ddtab=true; generating a link cannot detect a future page visitor environment. Explicit dingtalk mode adds ddtab=true. count --scope corp uses app-query with appKeys=[]; explicit --scope app defaults to app-query, while legacy count without --scope retains app-v1 compatibility. Scope must be clarified with the user when ambiguous. Published utilities expose openPage(url, {mode: auto|dingtalk|browser}), taskCenterLink and openTaskCenter. DingTalk mode never silently falls back to a browser. capabilities describes application task kinds, filters, lookup options, workflow read models and scaffold specs. inspect <appType> --process-inst-id ID [--history] [--task-id ID] reads authorized process progress and optional history; taskResult matches a task, without asserting visitor ownership. count <appType> --type KIND [--transport app-query] [--filters-json JSON] reads one list record and its server total; unknown totals stay null. Neither read command requires a custom-page choice. The tasks scaffold selects fields, filterFields and optional expandProcess; the progress view exports ProcessProgressSection for a real linked instance. options <appType> --type originators|processes|forms --keyword TEXT --size N --json reads current-visitor lookup candidates. scaffold creates a new local Canvas page only after an explicit custom-page request or affirmative ask_human choice, recorded as customPage.source and confirmed=true. A generic task-center request or Fast mode does not provide that choice. Pages use the current visitor and existing application theme; CLI credentials are never embedded. Global queries remain unavailable in application-bound managed runs.'],
1843
1883
  }),
1844
1884
  command('basic-info', ['basic-info'], 'basic-info <overview|commodity|grant|capacity|quota|abs-path|dataflow|i18n|domain>', 'help.cmd_basic_info', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
1845
1885
  output: 'json',
@@ -32,6 +32,21 @@ module.exports = {
32
32
  },
33
33
  dws_external: {'note': 'dws is an independent external tool. OpenYida only shows installation instructions; it does not download or install it.', 'install_guide': 'Show official DingTalk CLI installation instructions'},
34
34
  cool_card: {'unsupported_environment': 'The Alibaba Group edition does not support building cool cards.', 'description': 'Create and read native YiDA cool cards (interactive and pinned)', 'usage': 'Usage: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': 'Native cool card operation stopped: {0}'},
35
+ solution_template: {
36
+ description: 'Solution templates for independent local agents: discovery, cloud AI handoff and direct enable',
37
+ local_agent_only: 'Template commands require a recognized independent local agent. Cloud template AI keeps its native workflow.',
38
+ usage: 'Usage: openyida solution-template search [--query <text>] [--scene <scene>] [--industry <industry>] [--page <n>] [--size <n>] [--json]\n openyida solution-template detail <TPL_...> [--json]\n openyida solution-template prepare <TPL_...> --name <appName> [--with-data y|n] [--request-id <id>] [--json]\n openyida solution-template ai-build <TPL_...> [--json]\n openyida solution-template enable <TPL_...> --name <appName> [--with-data y|n] [--request-id <id>] [--retry] [--wait <0-60>] [--json]\n openyida solution-template status <requestId> [--wait <0-60>] [--json]',
39
+ failed: 'Solution Center template operation stopped: {0}',
40
+ ai_build_handoff: 'The template supports native AI build. Add customization requirements and submit them in the native builder. This receipt does not mean an app exists.',
41
+ no_progress_api: 'The platform currently exposes no pollable percentage progress. The request receipt and app-list readback are preserved, but resubmission is still unsafe.',
42
+ platform_decides: 'Permissions and entitlements are decided by the platform when enabling. Template source availability does not confirm authorization.',
43
+ rejected: 'The platform rejected this attempt. The platform reason is preserved. A new submission requires an explicit retry; OpenYida does not change permissions or entitlements.',
44
+ pending: 'The application is not visible yet. The request is preserved; check its status to continue without submitting again.',
45
+ accepted: 'The platform accepted the request. Checking for the application entry.',
46
+ waiting: 'Waiting for the application to appear. Only checking status; no new copy request is sent.',
47
+ no_longer_visible: 'This application was verified previously but is not visible in the current query. Creation history is preserved; current access is not confirmed.',
48
+ readback_failed: 'The application query failed. The request and platform reason are preserved; check status again without resubmitting.',
49
+ },
35
50
  data_card: {'description':'Create, configure, publish and distribute native YiDA data cards','usage':'Usage: openyida data-card <list|create|get|save|publish|probe|release-get|release-save|release-check|release-test|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]','failed':'Native data card operation stopped: {0}','conflict':'The draft changed. Read it again before saving or publishing.','mismatch':'Readback does not match the expected schema or published state. Keep the backup and inspect the card before retrying.'},
36
51
  app_settings: {'description':'Read theme, frontend/backend, native data card and cool card management links','usage':'Usage: openyida app-settings <appType> [--section theme|basic|data-card|cool-card] [--json]','failed':'Application settings could not be read: {0}'},
37
52
  form_view: {
@@ -32,6 +32,21 @@ module.exports = {
32
32
  },
33
33
  dws_external: {'note': 'dws 是独立的外部工具;OpenYida 仅提供安装指引,不下载或安装它。', 'install_guide': '显示钉钉 CLI 官方安装指引'},
34
34
  cool_card: {'unsupported_environment': '集团版不支持酷卡片搭建。', 'description': '创建和读取宜搭原生酷卡片(交互卡片与置顶卡片)', 'usage': '用法: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': '原生酷卡片操作已停止:{0}'},
35
+ solution_template: {
36
+ description: '独立本地 Agent 的方案模板查询、云端 AI 搭建入口与直接启用',
37
+ local_agent_only: '请在已识别的独立本地 Agent 中使用模板命令;云端继续平台原生模板 AI 搭。',
38
+ usage: '用法: openyida solution-template search [--query <文本>] [--scene <场景>] [--industry <行业>] [--page <n>] [--size <n>] [--json]\n openyida solution-template detail <TPL_...> [--json]\n openyida solution-template prepare <TPL_...> --name <应用名> [--with-data y|n] [--request-id <id>] [--json]\n openyida solution-template ai-build <TPL_...> [--json]\n openyida solution-template enable <TPL_...> --name <应用名> [--with-data y|n] [--request-id <id>] [--retry] [--wait <0-60>] [--json]\n openyida solution-template status <requestId> [--wait <0-60>] [--json]',
39
+ failed: '方案中心模板操作已停止:{0}',
40
+ ai_build_handoff: '已验证模板支持 AI 搭建;请在原生搭建页补充定制需求并提交。此回执不表示应用已创建。',
41
+ no_progress_api: '平台当前未提供可轮询的百分比进度;已保留请求记录并完成应用列表回读,仍不能安全重提。',
42
+ platform_decides: '权限和权益由平台在启用时判断;模板有可复制版本不代表当前账号已获授权。',
43
+ rejected: '平台已拒绝本次启用,已保留平台原因。再次提交需要显式重试;OpenYida 不修改权限或权益。',
44
+ pending: '应用暂未出现在列表,已保留请求记录;可继续查询状态,无需重复提交。',
45
+ accepted: '平台已受理启用请求,正在确认应用入口。',
46
+ waiting: '正在等待应用出现在列表,仅查询状态,不重复创建。',
47
+ no_longer_visible: '此应用曾确认创建,但本次查询暂不可见;保留创建记录,当前访问状态尚未确认。',
48
+ readback_failed: '应用查询失败,已保留请求记录和平台原因;请继续查询状态,勿重复提交。',
49
+ },
35
50
  data_card: {'description':'创建、配置、发布原生数据卡片并管理投放播报','usage':'用法: openyida data-card <list|create|get|save|publish|probe|release-get|release-save|release-check|release-test|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]','failed':'原生数据卡片操作已停止:{0}','conflict':'卡片草稿已发生变化,请重新读取后再保存或发布。','mismatch':'回读配置或发布状态与预期不一致,请保留备份并检查卡片后再重试。'},
36
51
  app_settings: {'description':'获取主题、业务前后台、原生数据卡片和酷卡片管理入口','usage':'用法: openyida app-settings <appType> [--section theme|basic|data-card|cool-card] [--json]','failed':'无法读取应用设置:{0}'},
37
52
  form_view: {
@@ -50,11 +50,13 @@ Usage:
50
50
  --keyword TEXT 搜索关键词
51
51
  --no-detail 不返回详情(仅 created 类型支持)
52
52
 
53
+ openyida task-center link [appType] --scope app|corp --type todo|created|done|cc [--base-url URL] [--mode dingtalk|auto|browser] --json
54
+ openyida task-center count --scope corp --type todo|created|done|cc [--filters-json JSON] --json
53
55
  openyida task-center capabilities --json
54
56
  openyida task-center scaffold --spec <file.json> --output <file.canvas.jsx>
55
57
  openyida task-center options <appType> --type <originators|processes|forms> [--keyword TEXT] [--size N] --json
56
58
  openyida task-center inspect <appType> --process-inst-id <id> [--form-uuid <id>] [--history] [--task-id <id>] --json
57
- openyida task-center count <appType> --type <todo|created|done|cc> [--transport app-v1|app-query] [--filters-json <json>] --json
59
+ openyida task-center count <appType> [--scope app] --type <todo|created|done|cc> [--transport app-v1|app-query] [--filters-json <json>] --json
58
60
  `;
59
61
 
60
62
  // ── 工具函数 ──────────────────────────────────────────
@@ -217,6 +219,26 @@ async function run(args) {
217
219
  return result;
218
220
  }
219
221
 
222
+ if (type === 'link') {
223
+ const allowed = ['scope', 'type', 'base_url', 'mode', 'json'];
224
+ const invalid = Object.keys(options).find(key => !allowed.includes(key));
225
+ if (invalid || positionals.length > 2 || options.json !== undefined && options.json !== true) {
226
+ fail(t('task_center.invalid_spec', invalid || 'arguments'));
227
+ }
228
+ if (!['app', 'corp'].includes(options.scope)) { fail(t('task_center.invalid_spec', 'scope')); }
229
+ const baseUrl = options.base_url === undefined ? (await ensureSession()).baseUrl : options.base_url;
230
+ const target = require('../task-center/navigation').target({
231
+ scope: options.scope, kind: options.type, baseUrl, mode: options.mode,
232
+ ...(positionals[1] !== undefined ? { appType: positionals[1] } : {}),
233
+ });
234
+ const nativeTarget = require('../task-center/navigation').target({ scope: options.scope, kind: options.type, baseUrl, mode: 'dingtalk',
235
+ ...(positionals[1] !== undefined ? { appType: positionals[1] } : {}) });
236
+ const dingtalkUrl = require('../dingtalk/dingtalk-link').buildDingTalkPageLink({ url: nativeTarget.pageUrl });
237
+ const result = { ...target, dingtalkUrl, opened: false };
238
+ console.log(JSON.stringify(result, null, 2));
239
+ return result;
240
+ }
241
+
220
242
  if (type === 'options') {
221
243
  const invalid = Object.keys(options).find(key => !['type', 'keyword', 'size', 'json'].includes(key));
222
244
  if (invalid || positionals.length !== 2) { fail(t('task_center.invalid_spec', invalid || 'arguments')); }
@@ -234,9 +256,9 @@ async function run(args) {
234
256
  }
235
257
 
236
258
  if (type === 'inspect' || type === 'count') {
237
- const allowed = type === 'inspect' ? ['process_inst_id', 'form_uuid', 'task_id', 'history', 'json'] : ['type', 'transport', 'filters_json', 'json'];
259
+ const allowed = type === 'inspect' ? ['process_inst_id', 'form_uuid', 'task_id', 'history', 'json'] : ['type', 'transport', 'filters_json', 'scope', 'json'];
238
260
  const invalid = Object.keys(options).find(key => !allowed.includes(key));
239
- if (invalid || positionals.length !== 2 || options.json !== undefined && options.json !== true || options.history !== undefined && options.history !== true) {
261
+ if (invalid || (positionals.length !== 2 && !(type === 'count' && options.scope === 'corp' && positionals.length === 1)) || options.json !== undefined && options.json !== true || options.history !== undefined && options.history !== true) {
240
262
  fail(t('task_center.invalid_spec', invalid || 'arguments'));
241
263
  }
242
264
  const tasks = require('../task-center/contract');
@@ -251,7 +273,13 @@ async function run(args) {
251
273
  if (options.filters_json !== undefined) {
252
274
  try { filters = JSON.parse(options.filters_json); } catch { fail(t('task_center.invalid_spec', 'filters-json')); }
253
275
  }
254
- input = { ...tasks.normalizeFilters(options.type, filters, options.transport), appType: tasks.appOf(positionals[1]), transport: tasks.transportOf(options.transport) };
276
+ if (options.scope !== undefined && !['app', 'corp'].includes(options.scope)) { fail(t('task_center.invalid_spec', 'scope')); }
277
+ if (options.scope === 'corp' && positionals[1] !== undefined) { fail(t('task_center.invalid_spec', 'appType')); }
278
+ const transport = options.transport || (options.scope !== undefined ? 'app-query' : undefined);
279
+ input = { ...tasks.normalizeFilters(options.type, filters, transport),
280
+ ...(options.scope === 'corp' ? { scope: 'corp' } : { appType: tasks.appOf(positionals[1]) }),
281
+ transport: tasks.transportOf(transport) };
282
+ if (input.scope === 'corp' && input.transport !== 'app-query') { fail(t('task_center.invalid_spec', 'transport')); }
255
283
  }
256
284
  const client = createYidaClient({ authRef: await ensureSession() });
257
285
  const request = descriptor => client.get(descriptor.path, { _api: 'nattyFetch', _mock: 'false', ...descriptor.params });
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ // Portable: publish injects this exact implementation into the page utility bridge.
4
+ function createPageNavigation(win) {
5
+ function fail(code) {
6
+ const error = new Error(code);
7
+ error.code = code;
8
+ throw error;
9
+ }
10
+ function normalize(input, options) {
11
+ const value = input && typeof input === 'object' && !Array.isArray(input) ? input.url : input;
12
+ if (typeof value !== 'string' || !value.trim()) { fail('PAGE_URL_INVALID'); }
13
+ if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) { fail('PAGE_OPEN_OPTIONS_INVALID'); }
14
+ const mode = (options && options.mode) || 'auto';
15
+ if (!['auto', 'dingtalk', 'browser'].includes(mode)) { fail('PAGE_OPEN_MODE_INVALID'); }
16
+ let url;
17
+ try { url = new URL(value, win.location.href || win.location.origin); } catch { fail('PAGE_URL_INVALID'); }
18
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) { fail('PAGE_URL_INVALID'); }
19
+ const inDingTalk = /dingtalk/i.test((win.navigator && win.navigator.userAgent) || '');
20
+ const useDingTalk = mode === 'dingtalk' || (mode === 'auto' && inDingTalk);
21
+ if (useDingTalk) { url.searchParams.set('ddtab', 'true'); }
22
+ return { url: url.href, mode, inDingTalk, useDingTalk };
23
+ }
24
+ function invokeNative(invoke, url) {
25
+ return new Promise((resolve, reject) => {
26
+ function success(value) { clearTimeout(timer); resolve(value); }
27
+ function rejectOpen(reason) {
28
+ clearTimeout(timer);
29
+ const error = new Error('DINGTALK_OPEN_FAILED');
30
+ error.code = 'DINGTALK_OPEN_FAILED';
31
+ error.cause = reason;
32
+ reject(error);
33
+ }
34
+ const timer = setTimeout(() => rejectOpen('timeout'), 15000);
35
+ try {
36
+ const result = invoke({ url, onSuccess: success, onFail: rejectOpen });
37
+ if (result && typeof result.then === 'function') { result.then(success, rejectOpen); }
38
+ } catch (error) { rejectOpen(error); }
39
+ });
40
+ }
41
+ async function open(input, options) {
42
+ const target = normalize(input, options);
43
+ if (!target.useDingTalk) {
44
+ win.open(target.url, '_blank', 'noopener,noreferrer');
45
+ return;
46
+ }
47
+ if (!target.inDingTalk) { fail('DINGTALK_ENV_REQUIRED'); }
48
+ // Use the publishing host's bridge. Do not scan parent DOM or cross-origin windows.
49
+ const dd = win.dd;
50
+ if (dd && dd.env && dd.env.platform === 'notInDingTalk') { fail('DINGTALK_API_UNAVAILABLE'); }
51
+ if (dd && dd.biz && dd.biz.util && typeof dd.biz.util.openLink === 'function') {
52
+ return invokeNative(options => dd.biz.util.openLink(options), target.url);
53
+ }
54
+ if (win.Ali && typeof win.Ali.pushWindow === 'function') {
55
+ return invokeNative(options => win.Ali.pushWindow(options), target.url);
56
+ }
57
+ fail('DINGTALK_API_UNAVAILABLE');
58
+ }
59
+ return { normalize, open };
60
+ }
61
+
62
+ module.exports = { createPageNavigation };
@@ -46,8 +46,12 @@ function navigateCanvasPage(entry, context = {}) {
46
46
  }
47
47
  const href = buildCanvasPageUrl(entry, context);
48
48
  const utils = context.utils || window.__OPENYIDA_UTILS__;
49
- if (entry.openMode === 'new-tab' || entry.targetType === 'url') {
50
- if (typeof utils?.openPage === 'function') return utils.openPage({ url: href });
49
+ if (entry.openMode === 'dingtalk' || entry.openMode === 'new-tab' || entry.targetType === 'url') {
50
+ if (typeof utils?.openPage === 'function') return entry.openMode === 'dingtalk'
51
+ ? utils.openPage(href, { mode: 'dingtalk' }) : utils.openPage(href);
52
+ if (entry.openMode === 'dingtalk' || /dingtalk/i.test(window.navigator?.userAgent || '')) {
53
+ throw new Error('DINGTALK_API_UNAVAILABLE');
54
+ }
51
55
  window.open(href, '_blank', 'noopener,noreferrer');
52
56
  return;
53
57
  }