openyida 2026.10.28-beta.2 → 2026.10.28

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
@@ -143,27 +143,6 @@ openyida login --check-only --json
143
143
  openyida auth status
144
144
  ```
145
145
 
146
- In QwenWork web, `openyida login --qwenwork --json` (or ordinary `login`) sends the sandbox Agent Token and gateway origin directly to the selected YiDA authentication endpoint. YiDA validates the identity through the trusted gateway’s fixed `/api/agent-gw/yida/user_info` endpoint, then issues its own short-lived session. This login flow no longer calls the legacy exchange or `/user/info` endpoints. Login preparation stays silent; failures stop without browser OAuth. Desktop and other hosts retain their existing authentication paths.
147
-
148
- For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:
149
-
150
- ```bash
151
- openyida auth qwenwork user-info --json
152
- ```
153
-
154
- This command exchanges the injected `QWENWORK_AGENT_TOKEN` (legacy: `MULE_AGENT_TOKEN` / `AGENT_TOKEN`) at `QWENWORK_GATEWAY_URL` (legacy: `MULE_GATEWAY_URL`), then queries `/user/info` on the same origin. It reports identity and scope metadata without printing or persisting either credential. It does not create an OpenYida login session, and QwenWork organization IDs are not YiDA `corpId` values. Existing OAuth/profile behavior is unchanged.
155
-
156
- Programmatic callers can use `exchangeQwenWorkAgentToken`, `getQwenWorkUserInfo` and `getQwenWorkIdentity` from `lib/auth/qwenwork-client.js`. The exchange function returns an in-memory `access_token` credential for the identity query or a future server-side login exchange; callers must not log or persist it.
157
-
158
- ```js
159
- const { exchangeQwenWorkAgentToken, getQwenWorkUserInfo } = require('openyida/lib/auth/qwenwork-client');
160
- async function readCloudUser() {
161
- const credential = await exchangeQwenWorkAgentToken();
162
- // The result contains QwenWork identifiers, not YiDA IDs.
163
- return getQwenWorkUserInfo(credential);
164
- }
165
- ```
166
-
167
146
  To reuse an existing shared login profile for the current project, switch first instead of starting a new OAuth login:
168
147
 
169
148
  ```bash
@@ -478,9 +457,9 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
478
457
 
479
458
  | Command | Description |
480
459
  |---------|-------------|
481
- | `openyida login [--qwenwork] [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]` | Login with OAuth token mode |
460
+ | `openyida login [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]` | Login with OAuth token mode |
482
461
  | `openyida logout` | Logout / unbind current project auth |
483
- | `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch\|qwenwork user-info> [--json]` | Token login state and profile management |
462
+ | `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch>` | Token login state and profile management |
484
463
  | `openyida org <list\|switch> [--json] [--corp-id <corpId>]` | Organization management (list / switch existing profiles first) |
485
464
  | `openyida env [--json\|setup\|list\|show\|switch\|add\|remove] [options]` | Detect AI tool environment & token login state |
486
465
 
package/bin/yida.js CHANGED
@@ -598,23 +598,7 @@ async function main() {
598
598
  });
599
599
  assertManagedInvocation(command, rawArgs);
600
600
  applyQuietFlag();
601
- // 一次性票据只在当前进程消费,不能先自更新并重启命令。
602
- switch (command) {
603
- case 'login-qwenwork':
604
- await require('../lib/auth/qwenwork-desktop').run(args);
605
- return;
606
- }
607
601
  applyGlobalEnvironmentFlags();
608
- // 网页态由 CLI 消费宿主凭据;桌面态继续消费宿主准备的登录态。
609
- const webLogin = (command === 'login' || (command === 'auth' && args[0] === 'login')) &&
610
- require('../lib/core/qwenwork-login-guidance').isQwenWorkWebRuntime(require('../lib/core/utils').detectRuntimeCapabilities());
611
- if ((command === 'login' && args.includes('--qwenwork')) || webLogin) {
612
- const loginArgs = applyLoginEnvironmentFlags(command === 'auth' ? args.slice(1) : args, { inferTargetUrl: true });
613
- if (hasHelpFlag(loginArgs)) { printLoginHelp(); return; }
614
- assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
615
- await require('../lib/auth/qwenwork-login').run(loginArgs, buildTokenLoginOptions(loginArgs));
616
- return;
617
- }
618
602
 
619
603
  const { maybeAutoUpdate } = require('../lib/core/update');
620
604
  const updateResult = await maybeAutoUpdate({
@@ -734,10 +718,6 @@ async function main() {
734
718
 
735
719
  case 'auth': {
736
720
  const subCommand = args[0];
737
- if (subCommand === 'qwenwork') {
738
- await require('../lib/auth/qwenwork').run(args.slice(1));
739
- break;
740
- }
741
721
  const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
742
722
  const { getAuthStatus } = require('../lib/core/utils');
743
723
  const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
@@ -20,6 +20,7 @@ const { buildApplicationEntryUrls } = require('./application-entry-urls');
20
20
  const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
21
21
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
22
22
  const { throwCommandError } = require('../core/command-errors');
23
+ const { isPermissionGuidanceError } = require('../core/permission-guidance');
23
24
  const { resolveBuilderAiSource } = require('./builder-ai-source');
24
25
 
25
26
  const DEFAULT_APP_OPTIONS = {
@@ -358,6 +359,7 @@ async function run(args) {
358
359
  if (corpConfig.content.forcePhysicalColumn === 'y') {openPhysicColumn = 'y';}
359
360
  }
360
361
  } catch (err) {
362
+ if (isPermissionGuidanceError(err)) { throw err; }
361
363
  // 查询失败时使用默认值,不影响主流程
362
364
  }
363
365
 
@@ -21,6 +21,7 @@ const { t } = require('../core/i18n');
21
21
  const { buildYidaI18n, resolveContentLocale } = require('../core/yida-i18n');
22
22
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
23
23
  const { throwUsage } = require('../core/command-errors');
24
+ const { isPermissionGuidanceError } = require('../core/permission-guidance');
24
25
  const { getSchemaServerRevision } = require('../core/server-revision');
25
26
  const { resolveBuilderAiSource } = require('./builder-ai-source');
26
27
 
@@ -243,6 +244,7 @@ async function run(args) {
243
244
  try {
244
245
  newAppType = await createApp(appName, authRef);
245
246
  } catch (err) {
247
+ if (isPermissionGuidanceError(err)) { throw err; }
246
248
  error(err.message);
247
249
  }
248
250
  success(t('import.app_created', newAppType));
@@ -272,6 +274,7 @@ async function run(args) {
272
274
  newFormUuid = await createBlankForm(newAppType, formName, authRef, formType);
273
275
  success(t('import.blank_form_created', newFormUuid));
274
276
  } catch (err) {
277
+ if (isPermissionGuidanceError(err)) { throw err; }
275
278
  fail(t('import.create_form_failed', err.message));
276
279
  migrationReport.forms.push({
277
280
  oldFormUuid,
@@ -175,7 +175,6 @@ function normalizeTokenResponse(response, baseUrl, clientId) {
175
175
  );
176
176
  return {
177
177
  auth_mode: 'token',
178
- credential_source: data.credential_source,
179
178
  status: data.status || 'ok',
180
179
  can_auto_use: true,
181
180
  message: data.message || data.errorMsg || response.errorMsg,
@@ -487,9 +486,7 @@ async function tokenRefreshUnlocked(options, session) {
487
486
  // Token exchange follows the active environment endpoint, just like the
488
487
  // legacy login bootstrap. The response may return a different corp base_url
489
488
  // for business requests and generated application links.
490
- // 桌面会话沿用首次换票的可信 origin;刷新不受后续全局环境切换影响。
491
- const desktopSession = session.credential_source === 'qwenwork_desktop';
492
- const authBaseUrlValue = desktopSession ? session.base_url : resolveTokenBaseUrl({
489
+ const authBaseUrlValue = resolveTokenBaseUrl({
493
490
  projectRoot: options.projectRoot,
494
491
  endpoint: options.endpoint,
495
492
  });
@@ -502,13 +499,6 @@ async function tokenRefreshUnlocked(options, session) {
502
499
  requireOkResponse(response);
503
500
  const data = getTokenPayload(response);
504
501
  const responseBusinessBaseUrl = data.base_url || data.baseUrl;
505
- if (desktopSession && responseBusinessBaseUrl !== undefined) {
506
- try {
507
- const value = new URL(responseBusinessBaseUrl);
508
- if (typeof responseBusinessBaseUrl !== 'string' || responseBusinessBaseUrl.length > 2048 || /\s/.test(responseBusinessBaseUrl) ||
509
- value.protocol !== 'https:' || value.username || value.password || value.pathname !== '/' || value.search || value.hash) { throw new Error(); }
510
- } catch { throw createTokenAuthRequiredError('refresh_origin_mismatch'); }
511
- }
512
502
  const businessBaseUrl = session.base_url || authBaseUrlValue;
513
503
  const normalized = normalizeTokenResponse({
514
504
  ...data,
@@ -517,7 +507,6 @@ async function tokenRefreshUnlocked(options, session) {
517
507
  user_id: data.user_id || data.userId || session.user_id,
518
508
  user_name: data.user_name || data.userName || session.user_name,
519
509
  refresh_token: data.refresh_token || data.refreshToken || session.refresh_token,
520
- credential_source: data.credential_source || session.credential_source,
521
510
  }, businessBaseUrl, clientId);
522
511
  if (!normalized.access_token) {
523
512
  return {
@@ -276,7 +276,6 @@ function normalizeTokenSession(session = {}) {
276
276
  );
277
277
  const normalized = {
278
278
  auth_mode: 'token',
279
- credential_source: session.credential_source,
280
279
  token_type: session.token_type || session.tokenType || 'Bearer',
281
280
  access_token: session.access_token || session.accessToken,
282
281
  refresh_token: session.refresh_token || session.refreshToken,
@@ -3,13 +3,12 @@
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { version, openyidaLocalBeta: localBeta } = require('../../package.json');
6
+ const { version } = require('../../package.json');
7
7
  const { t } = require('./i18n');
8
8
  const { buildCommandManifest } = require('./command-manifest');
9
9
  const { buildEnvironmentSnapshot } = require('./env');
10
10
  const coreUtils = require('./utils');
11
11
  const { buildHostAssetCapabilities } = require('../asset/host-capabilities');
12
- const { buildQwenWorkLoginInvocation, isQwenWorkWebRuntime } = require('./qwenwork-login-guidance');
13
12
  const { findProjectRoot, getAuthStatus } = coreUtils;
14
13
 
15
14
  function redactLogin(login) {
@@ -196,7 +195,7 @@ function forbiddenAliasBriefs(manifest) {
196
195
  }));
197
196
  }
198
197
 
199
- function buildAuthPath(login, env = process.env, runtime = null) {
198
+ function buildAuthPath(login, env = process.env) {
200
199
  const authSource = login.auth_source || 'token_session';
201
200
  const envTokenSession = authSource === 'env' || login.failure_reason === 'env_token_missing';
202
201
  const authPath = {
@@ -231,54 +230,10 @@ function buildAuthPath(login, env = process.env, runtime = null) {
231
230
  ? 'STOP_AND_REQUEST_ENV_TOKEN'
232
231
  : 'RUN_OPENYIDA_LOGIN_IF_USER_APPROVES',
233
232
  };
234
- if (isQwenWorkWebRuntime(runtime) && !envTokenSession) {
235
- const invocation = buildQwenWorkLoginInvocation({ subtype: 'qwenwork_web' });
236
- authPath.auth_runtime = 'qwenwork_cloud_exchange';
237
- authPath.interactive_login_allowed = true;
238
- authPath.missing_token_action = 'RUN_OPENYIDA_LOGIN_QWENWORK';
239
- for (const key of ['auth_profile', 'corp_id', 'corp_name', 'user_id', 'user_name', 'store', 'persistence_scope', 'warning']) { delete authPath[key]; }
240
- authPath.next_step = authPath.can_auto_use
241
- ? 'Silently continue the requested business operation and report its business result.'
242
- : 'Run the recommended login command silently. The CLI consumes cloud runtime credentials; do not inspect credentials, open a browser, or fall back to OAuth on failure.';
243
- authPath.next_step_commands = authPath.can_auto_use ? [] : [invocation.command];
244
- return authPath;
245
- }
246
- // 桌面换票仅由当前活跃宿主和发行配置共同启用,不影响其他宿主的认证路径。
247
- if (localBeta?.desktopTicketLogin && runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_desktop' && !envTokenSession) {
248
- const invocation = buildQwenWorkLoginInvocation();
249
- authPath.auth_runtime = 'qwenwork_desktop_exchange_ticket';
250
- authPath.interactive_login_allowed = true;
251
- authPath.missing_token_action = 'RUN_OPENYIDA_LOGIN_QWENWORK';
252
- for (const key of ['auth_profile', 'corp_id', 'corp_name', 'user_id', 'user_name', 'store', 'persistence_scope', 'warning']) { delete authPath[key]; }
253
- authPath.next_step = authPath.can_auto_use
254
- ? 'Silently continue the requested business operation and report its business result.'
255
- : `Run the recommended login command as returned: ${invocation.command}. No extra PATH or output setup is needed. After success silently continue the requested business operation. If it fails, follow the returned invocation guidance.`;
256
- authPath.next_step_commands = authPath.can_auto_use ? [] : [invocation.command];
257
- }
258
233
  return authPath;
259
234
  }
260
235
 
261
236
  function buildInteractiveLogin(auth, runtime) {
262
- if (auth.auth_runtime === 'qwenwork_cloud_exchange' && isQwenWorkWebRuntime(runtime)) {
263
- return {
264
- mode: 'cloud_token', browser_default: 'not_required', browser_owner: 'none',
265
- recommended_command: auth.can_auto_use ? null : buildQwenWorkLoginInvocation({ subtype: 'qwenwork_web' }).command,
266
- invocation: buildQwenWorkLoginInvocation({ subtype: 'qwenwork_web' }),
267
- agent_action: auth.can_auto_use ? 'continue_business_command' : 'run_login_qwenwork_and_wait',
268
- user_visible: false, success_action: 'continue_business_command_without_message',
269
- reason: 'qwenwork_web_cloud_credentials', completion_signal: 'process_exit_and_final_json', playwright_required: false,
270
- };
271
- }
272
- if (auth.auth_runtime === 'qwenwork_desktop_exchange_ticket' && runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') {
273
- return {
274
- mode: 'desktop_ticket', browser_default: 'not_required', browser_owner: 'cli_if_auth_required',
275
- recommended_command: auth.can_auto_use ? null : buildQwenWorkLoginInvocation().command,
276
- invocation: buildQwenWorkLoginInvocation(),
277
- agent_action: auth.can_auto_use ? 'continue_business_command' : 'run_login_qwenwork_and_wait',
278
- user_visible: false, success_action: 'continue_business_command_without_message',
279
- reason: 'qwenwork_desktop_pre_beta', completion_signal: 'process_exit_and_final_json', playwright_required: false,
280
- };
281
- }
282
237
  const base = {
283
238
  suppress_flag: '--no-browser',
284
239
  suppress_env: 'OPENYIDA_NO_BROWSER',
@@ -338,9 +293,9 @@ function buildInteractiveLogin(auth, runtime) {
338
293
  }
339
294
 
340
295
  function buildBuilderPath(login, projectRoot, manifest, env = process.env, runtimeSnapshot = null) {
341
- const runtime = runtimeSnapshot || buildRuntimeSnapshot(projectRoot);
342
- const auth = buildAuthPath(login, env, runtime);
296
+ const auth = buildAuthPath(login, env);
343
297
  const canTrustSummaryPreflight = auth.can_auto_use === true;
298
+ const runtime = runtimeSnapshot || buildRuntimeSnapshot(projectRoot);
344
299
 
345
300
  return {
346
301
  schema_version: 1,
@@ -356,13 +311,6 @@ function buildBuilderPath(login, projectRoot, manifest, env = process.env, runti
356
311
  auth,
357
312
  runtime,
358
313
  interactive_login: buildInteractiveLogin(auth, runtime),
359
- communication: isQwenWorkWebRuntime(runtime) || (runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') ? {
360
- preparation_visibility: 'silent',
361
- completion_content: 'business_result_and_business_link_only',
362
- disclose_authentication_identity_environment: false,
363
- report_preparation_only_when_user_action_is_required: true,
364
- bundled_skill_path: path.resolve(__dirname, '../../yida-skills'),
365
- } : undefined,
366
314
  environment_check_simplification: {
367
315
  contract_role: 'machine_readable_builder_runtime_not_yida_agent_business_logic',
368
316
  minimal_probe_commands: [
@@ -484,7 +432,6 @@ function compactBuilderPath(builderPath) {
484
432
  auth,
485
433
  runtime: builderPath.runtime,
486
434
  interactive_login: builderPath.interactive_login,
487
- communication: builderPath.communication,
488
435
  environment_check_simplification: {
489
436
  minimal_probe_commands: environment.minimal_probe_commands,
490
437
  can_skip_default_exploration_when_summary_ok: environment.can_skip_default_exploration_when_summary_ok,
@@ -654,9 +601,7 @@ function buildAgentCapabilitiesSummary() {
654
601
  schema_version: 1,
655
602
  name: 'openyida-agent-capabilities-summary',
656
603
  version,
657
- login: ['qwenwork_desktop_exchange_ticket', 'qwenwork_cloud_exchange'].includes(builderPath.auth.auth_runtime)
658
- ? { status: loginStatus.status, can_auto_use: loginStatus.can_auto_use === true }
659
- : compactLogin(loginStatus),
604
+ login: compactLogin(loginStatus),
660
605
  workdir: projectRoot,
661
606
  workdir_exists: !!projectResolution.exists,
662
607
  workdir_source: projectResolution.source,
@@ -101,7 +101,8 @@ function shouldUseStructuredErrorOutput(error, args = []) {
101
101
  && details.mutationPerformed === false
102
102
  );
103
103
  const navigationOrderFailure = /^NAV_ORDER_/.test(error.code || '');
104
- return ownedPartial || mutationOutcomeUnknown || deterministicPreflightFailure || navigationOrderFailure;
104
+ const permissionRequired = !!details && details.status === 'permission_required';
105
+ return ownedPartial || mutationOutcomeUnknown || deterministicPreflightFailure || navigationOrderFailure || permissionRequired;
105
106
  }
106
107
 
107
108
  module.exports = {
@@ -420,7 +420,6 @@ const COMMAND_SIDE_EFFECTS = new Map([
420
420
  'export-conversation',
421
421
  'flash-to-prd',
422
422
  'login',
423
- 'login-qwenwork',
424
423
  'logout',
425
424
  'sample',
426
425
  'update',
@@ -588,7 +587,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
588
587
  mutates_yida: false,
589
588
  mutates_local: true,
590
589
  read_actions: ['status', 'profiles', 'profile list'],
591
- mutating_actions: ['login', 'refresh', 'logout', 'profile switch', 'qwenwork user-info'],
590
+ mutating_actions: ['login', 'refresh', 'logout', 'profile switch'],
592
591
  })],
593
592
  ['batch', sideEffect('mixed', {
594
593
  mutates_yida: true,
@@ -813,7 +812,6 @@ const COMMAND_PERMISSIONS = new Map([
813
812
 
814
813
  ...permissionEntries([
815
814
  'login',
816
- 'login-qwenwork',
817
815
  'logout',
818
816
  'update',
819
817
  ], permission('allow', 'external', {
@@ -926,7 +924,7 @@ const COMMAND_PERMISSIONS = new Map([
926
924
  ...permissionEntries([
927
925
  'auth',
928
926
  ], actionDependentPermission({
929
- preauthorized_actions: ['login', 'refresh', 'logout', 'profile switch', 'qwenwork user-info'],
927
+ preauthorized_actions: ['login', 'refresh', 'logout', 'profile switch'],
930
928
  })),
931
929
 
932
930
  ...permissionEntries([
@@ -1161,13 +1159,12 @@ const COMMAND_GROUPS = [
1161
1159
  id: 'auth',
1162
1160
  titleKey: 'help.group_auth',
1163
1161
  commands: [
1164
- command('login', ['login'], 'login [--qwenwork] [target-url] [--env <name>|--intl|--overseas|--global|--yidaapps|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]', 'help.cmd_login', { managedRun: { scope: 'local', allowedFlags: ['--check-only', '--json', '--quiet'], requiredFlags: ['--check-only'] },
1162
+ command('login', ['login'], 'login [target-url] [--env <name>|--intl|--overseas|--global|--yidaapps|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]', 'help.cmd_login', { managedRun: { scope: 'local', allowedFlags: ['--check-only', '--json', '--quiet'], requiredFlags: ['--check-only'] },
1165
1163
  requiresLogin: false,
1166
1164
  output: 'json',
1167
1165
  }),
1168
- command('login-qwenwork', ['login-qwenwork'], 'login-qwenwork --exchange-ticket <ticket> --ticket-context <json> [--json]', 'help.cmd_login_qwenwork', { managedRun: { scope: 'denied', reason: 'identity_exchange' }, requiresLogin: false, output: 'json', hidden: true }),
1169
1166
  command('logout', ['logout'], 'logout', 'help.cmd_logout', { managedRun: { scope: 'denied', reason: 'scope_not_audited' }, requiresLogin: false }),
1170
- command('auth', ['auth'], 'auth <status|login|refresh|logout|profiles|profile switch|qwenwork user-info> [--json]', 'help.cmd_auth', { managedRun: { scope: 'actions', actions: { status: { scope: 'local', allowedFlags: ['--json', '--quiet'] }, qwenwork: { scope: 'denied', reason: 'identity_exchange' } } }, requiresLogin: false }),
1167
+ command('auth', ['auth'], 'auth <status|login|refresh|logout|profiles|profile switch>', 'help.cmd_auth', { managedRun: { scope: 'actions', actions: { status: { scope: 'local', allowedFlags: ['--json', '--quiet'] } } }, requiresLogin: false }),
1171
1168
  command('org', ['org'], 'org <list|switch> [--json] [--corp-id <corpId>]', 'help.cmd_org', { managedRun: { scope: 'denied', reason: 'scope_not_audited' } }),
1172
1169
  command('env', ['env'], 'env [--json|setup|list|show|switch|add|remove] [options]', 'help.cmd_env', { managedRun: { scope: 'local', allowedFlags: ['--json', '--quiet'] },
1173
1170
  requiresLogin: false,
@@ -1292,7 +1289,7 @@ const COMMAND_GROUPS = [
1292
1289
  output: 'json',
1293
1290
  }),
1294
1291
  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', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
1295
- 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.'],
1292
+ 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.', 'In QwenWork, permission failures return a nonzero exit and structured status=permission_required. YIDA_PLATFORM_ACCESS_REQUIRED and YIDA_APP_CREATE_PERMISSION_REQUIRED include details.permissionLabel/permissionUrl and nextAction.label/url; YIDA_PERMISSION_REQUIRED preserves an unclassified permission reason without inventing a link. Explain the missing permission, display the semantic label as a Markdown link to its paired URL (or reuse nextStep), and pause resource writes; do not automatically open it, send an application, switch spaces, log in again or retry creation. Resume only after the user requests continuation and the host identity, organization and permissions are verified.'],
1296
1293
  }),
1297
1294
  command('design-plan.catalog', ['design-plan', 'catalog'], 'design-plan catalog [--json]', 'help.cmd_design_plan_catalog', { managedRun: { scope: 'local' },
1298
1295
  requiresLogin: false, output: 'json',
@@ -4,21 +4,6 @@
4
4
  'use strict';
5
5
 
6
6
  module.exports = {
7
- qwenwork: {
8
- login_check_failed: 'Unable to verify the login session. Please retry.',
9
- desktop_login_failed: 'QwenWork desktop login failed. Check the desktop identity and login handoff before retrying.',
10
- desktop_login_usage: 'Usage: openyida login --qwenwork [--json] [--check-only]',
11
- desktop_login_standalone: 'Run the recommended login command directly: {0}. No extra PATH or output setup is needed. After success continue the business operation silently; on failure follow the returned guidance.',
12
- credential_missing: 'No cloud Agent credential is available. Run this command inside the QwenWork cloud execution environment.',
13
- gateway_missing: 'QWENWORK_GATEWAY_URL or MULE_GATEWAY_URL is required.',
14
- invalid_url: 'The QwenWork gateway must use HTTPS (HTTP is allowed only for loopback) and contain no credentials, query or fragment.',
15
- request_failed: 'The QwenWork request failed. Check the stage and HTTP status in the diagnostic details.',
16
- request_timeout: 'The QwenWork request timed out.',
17
- invalid_response: 'QwenWork returned an invalid authentication or identity response.',
18
- identity_mismatch: 'The returned QwenWork identity does not match the cloud execution context or exchanged credential.',
19
- expired_token: 'QwenWork returned an expired user credential.',
20
- usage: 'Usage: openyida auth qwenwork user-info [--json]',
21
- },
22
7
  print_template: {
23
8
  'description': 'Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs',
24
9
  'error': 'Print template operation failed: {0}',
@@ -191,7 +176,6 @@ module.exports = {
191
176
  alias: 'Alias:',
192
177
  group_auth: 'Auth & Environment',
193
178
  cmd_login: 'Login with OAuth token mode',
194
- cmd_login_qwenwork: 'Log in to YiDA with a one-time QwenWork exchange ticket',
195
179
  cmd_logout: 'Logout / unbind current project auth',
196
180
  cmd_auth: 'Token login state and profile management',
197
181
  cmd_agent: 'Connect a local Agent (Go runtime, development preview)',
@@ -520,10 +504,10 @@ Examples:
520
504
  save_permission_usage: 'Usage: openyida save-permission <appType> <formUuid> [--package-uuid <packageUuid>] [--data-permission <json>] [--action-permission <json>] [--package-type <FORM_PACKAGE_VIEW|FORM_PACKAGE_START>]',
521
505
  save_permission_example: "Example: openyida save-permission APP_XXX FORM-XXX --data-permission '{\"role\":\"DEFAULT\",\"dataRange\":\"SELF\"}'",
522
506
  exec_failed: '\n❌ Execution failed: {0}',
523
- login_usage: 'Usage: openyida login [--qwenwork] [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
507
+ login_usage: 'Usage: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
524
508
  login_example: 'Examples:\n openyida login # Automatically open the browser via OAuth loopback login\n openyida login --no-browser # Let the caller handle the authorization URL\n openyida login --check-only --json # Check token auth status only\n openyida login --intl # Login against the international environment\n OPENYIDA_NO_BROWSER=1 openyida login # Suppress auto-opening the browser with an environment variable\n openyida auth login # Login alias',
525
509
  login_unsupported_option: 'Removed legacy login option is no longer supported: {0}. Use token/OAuth login; env token mode reads only OPENYIDA_* tokens.',
526
- auth_usage: 'Usage: openyida auth <status|login|refresh|logout|profiles|profile switch|qwenwork user-info> [--json]',
510
+ auth_usage: 'Usage: openyida auth <status|login|refresh|logout|profiles|profile switch>',
527
511
  auth_example: 'Examples:\n openyida auth status # View login status\n openyida auth profiles # List existing login profiles\n openyida auth profile switch <auth_profile> # Switch current project to an existing profile\n openyida auth login # Add a profile when the target does not exist\n openyida auth refresh # Refresh login session\n openyida auth logout # Unbind current project auth\n openyida auth logout --profile <auth_profile> # Delete a shared profile explicitly',
528
512
  org_usage: 'Usage: openyida org <list|switch> [--json] [--corp-id <corpId>]',
529
513
  org_example: 'Examples:\n openyida org list --json # List accessible organizations\n openyida org switch --corp-id dingXXX # Switch existing profiles first; login to add one if missing',
@@ -1178,7 +1162,7 @@ Examples:
1178
1162
 
1179
1163
  import_example2: ' openyida import ./yida-export.json "Quality Traceability System (Production)"',
1180
1164
  exec_failed: '\n❌ Execution failed: {0}',
1181
- auth_usage: 'Usage: openyida auth <status|login|refresh|logout|profiles|profile switch|qwenwork user-info> [--json]',
1165
+ auth_usage: 'Usage: openyida auth <status|login|refresh|logout|profiles|profile switch>',
1182
1166
  auth_example: 'Examples:\n openyida auth status # View login status\n openyida auth profiles # List existing login profiles\n openyida auth profile switch <auth_profile> # Switch current project to an existing profile\n openyida auth login # Add a profile when the target does not exist\n openyida auth refresh # Refresh login session\n openyida auth logout # Unbind current project auth\n openyida auth logout --profile <auth_profile> # Delete a shared profile explicitly',
1183
1167
  org_usage: 'Usage: openyida org <list|switch> [--json] [--corp-id <corpId>]',
1184
1168
  org_example: 'Examples:\n openyida org list --json # List accessible organizations\n openyida org switch --corp-id dingXXX # Switch existing profiles first; login to add one if missing',
@@ -2609,3 +2593,12 @@ module.exports.feedback_github = {
2609
2593
  };
2610
2594
 
2611
2595
  module.exports.process_dynamic_actors = { invalid: 'Invalid process actor configuration. Check details.reason, selection range and actual member fields.' };
2596
+
2597
+ module.exports.permission_guidance = {
2598
+ platform_access_link: 'Request YiDA access',
2599
+ create_app_link: 'Request permission to create YiDA applications',
2600
+ platform_access: 'The current account is not authorized to use YiDA in this organization.',
2601
+ create_app: 'The current account cannot create applications in this organization.',
2602
+ unknown: 'Permission denied; the permission type is unconfirmed. Contact an administrator and pause this operation.',
2603
+ next_step: 'Open the permission request page: [{0}]({1}). After completing it, verify the current organization and permissions before continuing.',
2604
+ };
@@ -4,21 +4,6 @@
4
4
  'use strict';
5
5
 
6
6
  module.exports = {
7
- qwenwork: {
8
- login_check_failed: '登录状态暂时无法确认,请重试。',
9
- desktop_login_failed: '千问办公桌面免登失败,请确认桌面登录身份和免登通道后重试。',
10
- desktop_login_usage: '用法:openyida login --qwenwork [--json] [--check-only]',
11
- desktop_login_standalone: '请直接执行推荐的登录命令:{0}。无需额外处理 PATH 和输出;成功后静默继续业务,失败时按提示修正调用。',
12
- credential_missing: '未发现云端 Agent 凭证,请在千问办公云端执行环境中运行此命令。',
13
- gateway_missing: '缺少 QWENWORK_GATEWAY_URL 或 MULE_GATEWAY_URL。',
14
- invalid_url: '千问网关必须使用 HTTPS(仅回环地址允许 HTTP),且不能包含账号密码、查询参数或片段。',
15
- request_failed: '千问接口请求失败,请查看诊断信息中的阶段和 HTTP 状态码。',
16
- request_timeout: '千问接口请求超时。',
17
- invalid_response: '千问返回的认证或身份响应格式不正确。',
18
- identity_mismatch: '千问返回的用户身份与云端执行上下文或兑换凭证不一致。',
19
- expired_token: '千问返回的用户凭证已过期。',
20
- usage: '用法:openyida auth qwenwork user-info [--json]',
21
- },
22
7
  print_template: {
23
8
  'description': '管理宜搭原生打印模板、按字段生成多种单据、备份恢复与生成PDF',
24
9
  'error': '打印模板操作失败:{0}',
@@ -191,7 +176,6 @@ module.exports = {
191
176
  alias: '别名:',
192
177
  group_auth: '环境 & 认证',
193
178
  cmd_login: '登录(OAuth token 模式)',
194
- cmd_login_qwenwork: '使用千问办公的一次性兑换票登录宜搭',
195
179
  cmd_logout: '退出登录 / 解绑当前项目登录态',
196
180
  cmd_auth: 'token 登录态与 profile 管理',
197
181
  cmd_agent: '连接本地 Agent(Go runtime,开发预览)',
@@ -520,7 +504,7 @@ openyida - 宜搭命令行工具
520
504
  save_permission_usage: '用法: openyida save-permission <appType> <formUuid> [--package-uuid <packageUuid>] [--data-permission <json>] [--action-permission <json>] [--package-type <FORM_PACKAGE_VIEW|FORM_PACKAGE_START>]',
521
505
  save_permission_example: "示例: openyida save-permission APP_XXX FORM-XXX --data-permission '{\"role\":\"DEFAULT\",\"dataRange\":\"SELF\"}'",
522
506
  exec_failed: '\n❌ 执行失败: {0}',
523
- login_usage: '用法: openyida login [--qwenwork] [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
507
+ login_usage: '用法: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
524
508
  login_example: '示例:\n openyida login # 通过 OAuth loopback 自动打开浏览器登录\n openyida login --no-browser # 不自动打开浏览器,由调用方接管授权链接\n openyida login --check-only --json # 只检查 token 登录态\n openyida login --intl # 使用国际站环境登录\n OPENYIDA_NO_BROWSER=1 openyida login # 通过环境变量抑制自动打开浏览器\n openyida auth login # 登录入口别名',
525
509
  login_unsupported_option: '已删除的旧登录参数不再支持: {0}。请使用 token/OAuth 登录;env token 模式下只读取 OPENYIDA_* token。',
526
510
  first_run_title: ' 🤖 OpenYida - AI 问答模式已开启! ',
@@ -1150,7 +1134,7 @@ openyida - 宜搭命令行工具
1150
1134
  // ── lib/import-app.js ──────────────────────────────
1151
1135
  import_example2: ' openyida import ./yida-export.json "质量追溯系统(生产环境)"',
1152
1136
  exec_failed: '\n❌ 执行失败: {0}',
1153
- auth_usage: '用法: openyida auth <status|login|refresh|logout|profiles|profile switch|qwenwork user-info> [--json]',
1137
+ auth_usage: '用法: openyida auth <status|login|refresh|logout|profiles|profile switch>',
1154
1138
  auth_example: '示例:\n openyida auth status # 查看登录状态\n openyida auth profiles # 列出已有登录 profile\n openyida auth profile switch <auth_profile> # 切换当前项目到已有 profile\n openyida auth login # 目标 profile 不存在时登录新增\n openyida auth refresh # 刷新登录态\n openyida auth logout # 解绑当前项目登录态\n openyida auth logout --profile <auth_profile> # 删除指定共享 profile',
1155
1139
  org_usage: '用法: openyida org <list|switch> [--json] [--corp-id <corpId>]',
1156
1140
  org_example: '示例:\n openyida org list --json # 列出可访问的组织\n openyida org switch --corp-id dingXXX # 优先切换已有 profile;不存在时登录新增',
@@ -2563,3 +2547,12 @@ module.exports.feedback_github = {
2563
2547
  };
2564
2548
 
2565
2549
  module.exports.process_dynamic_actors = { invalid: '流程人员来源配置无效,请按 details.reason 检查来源、范围与真实成员字段。' };
2550
+
2551
+ module.exports.permission_guidance = {
2552
+ platform_access_link: '申请宜搭使用授权',
2553
+ create_app_link: '申请宜搭应用创建权限',
2554
+ platform_access: '当前没有此组织的宜搭使用权限,可申请宜搭使用授权。',
2555
+ create_app: '当前没有此组织的宜搭应用创建权限,可申请应用创建权限。',
2556
+ unknown: '权限不足,具体权限类型尚未确认。请联系管理员核实,暂不重试当前操作。',
2557
+ next_step: '请打开申请页面:[{0}]({1})。处理完成后确认当前组织和权限,再继续搭建。',
2558
+ };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const { CliError } = require('./cli-error');
4
+ const { t } = require('./i18n');
5
+ const { redactString } = require('./redact');
6
+
7
+ const TYPES = { unauth: 'platform_access', noAppCreationAuth: 'create_app' };
8
+ const CODES = {
9
+ platform_access: 'YIDA_PLATFORM_ACCESS_REQUIRED',
10
+ create_app: 'YIDA_APP_CREATE_PERMISSION_REQUIRED',
11
+ unknown: 'YIDA_PERMISSION_REQUIRED',
12
+ };
13
+ const AUTH_CODES = new Set([
14
+ 'not_logged_in', 'invalid_access_token', 'expired_access_token',
15
+ 'access_token_expired', 'missing_access_token',
16
+ ]);
17
+
18
+ function normalizeMessage(value) {
19
+ return String(value || '').replace(/[\s,,。.!!]/g, '');
20
+ }
21
+
22
+ // 仅接受当前宜搭域名的两类申请页;重建查询参数,避免传递凭据或任意回跳地址。
23
+ function resolvePermissionPage(location, baseUrl) {
24
+ if (!location || !baseUrl) { return null; }
25
+ try {
26
+ const base = new URL(baseUrl);
27
+ const target = new URL(location, base);
28
+ if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password
29
+ || target.origin !== base.origin || target.username || target.password
30
+ || target.pathname !== '/defaultPage.html') { return null; }
31
+ const values = target.searchParams.getAll('type');
32
+ if (values.length !== 1 || !Object.hasOwn(TYPES, values[0])) { return null; }
33
+ const url = new URL('/defaultPage.html', base);
34
+ url.searchParams.set('type', values[0]);
35
+ for (const key of ['corpid', 'appType']) {
36
+ const value = target.searchParams.get(key);
37
+ if (value && /^[A-Za-z0-9_-]{1,200}$/.test(value)) { url.searchParams.set(key, value); }
38
+ }
39
+ return { permissionType: TYPES[values[0]], url: url.href };
40
+ } catch { return null; }
41
+ }
42
+
43
+ function createPermissionError(response, options = {}) {
44
+ if (options.tool !== 'qwenwork') { return null; }
45
+ let payload = response;
46
+ if (typeof payload === 'string') {
47
+ try { payload = JSON.parse(payload); } catch { payload = {}; }
48
+ }
49
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { payload = {}; }
50
+ const content = payload.content && typeof payload.content === 'object' && !Array.isArray(payload.content)
51
+ ? payload.content : {};
52
+ const code = String(payload.errorCode || payload.code || content.errorCode || content.code || '');
53
+ const status = String(payload.status || content.status || '').toLowerCase();
54
+ const message = String(payload.errorMsg || payload.message || content.errorMsg || content.message || '');
55
+ const httpStatus = Number(options.httpStatus || payload.__httpStatus || 0);
56
+ // 明确的凭据失效继续由原有认证链路处理。
57
+ if (httpStatus === 401 || AUTH_CODES.has(code.toLowerCase()) || AUTH_CODES.has(status)
58
+ || /not_logged_in|invalid_access_token|expired_access_token|access_token_expired|access token is invalid or expired/i.test(message)) { return null; }
59
+ const failed = payload.success === false || payload.__needLogin || httpStatus >= 300;
60
+ if (!failed) { return null; }
61
+ let page = resolvePermissionPage(options.location || payload.__location, options.baseUrl);
62
+ const legacyDenied = /^(?:TIANSHU_)?000006$/.test(code);
63
+ if (!page && legacyDenied) {
64
+ const normalized = normalizeMessage(message);
65
+ if (normalized === '已升级宜搭你未在授权范围内因此无法使用如要使用请联系管理员授权') {
66
+ page = resolvePermissionPage('/defaultPage.html?type=unauth', options.baseUrl);
67
+ } else if (normalized === '管理员暂未为您分配权限请联系管理员授权'
68
+ && /^\/query\/app\/registerApp(?:\.json)?(?:\?|$)/.test(options.requestPath || '')) {
69
+ // 000006 被多个权限检查共用,只有已核实的创建接口及文案组合才可归类。
70
+ page = resolvePermissionPage('/defaultPage.html?type=noAppCreationAuth', options.baseUrl);
71
+ }
72
+ }
73
+ if (!page && !legacyDenied && httpStatus !== 403 && code !== '403' && status !== '403') { return null; }
74
+ const permissionType = page ? page.permissionType : 'unknown';
75
+ const label = page ? t(`permission_guidance.${permissionType}_link`) : '';
76
+ const details = {
77
+ status: 'permission_required', permissionType,
78
+ platformErrorCode: code || undefined, httpStatus: httpStatus || undefined,
79
+ platformErrorMessage: message ? redactString(message) : undefined,
80
+ retryable: false,
81
+ nextStep: page ? t('permission_guidance.next_step', label, page.url) : t('permission_guidance.unknown'),
82
+ };
83
+ if (page) {
84
+ details.permissionLabel = label;
85
+ details.permissionUrl = page.url;
86
+ details.nextAction = { type: 'open_url', label, url: page.url };
87
+ }
88
+ const displayMessage = page || !message ? t(`permission_guidance.${permissionType}`) : redactString(message);
89
+ return new CliError(displayMessage, {
90
+ code: CODES[permissionType], details,
91
+ });
92
+ }
93
+
94
+ function isPermissionGuidanceError(error) {
95
+ return !!error && error.isCliError === true && Object.values(CODES).includes(error.code);
96
+ }
97
+
98
+ module.exports = { createPermissionError, isPermissionGuidanceError };