openyida 2026.10.26-beta.1 → 2026.10.26-beta.2

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,6 +143,8 @@ 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`) exchanges the cloud runtime credential for a verified YiDA session on the selected YiDA endpoint. Login preparation stays silent; failures stop without browser OAuth. Desktop and other hosts retain their existing authentication paths.
147
+
146
148
  For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:
147
149
 
148
150
  ```bash
package/bin/yida.js CHANGED
@@ -601,9 +601,11 @@ async function main() {
601
601
  return;
602
602
  }
603
603
  applyGlobalEnvironmentFlags();
604
- // 桌面准备完成后继续执行公开命令,由真实登录态决定结果。
605
- if (command === 'login' && args.includes('--qwenwork')) {
606
- const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
604
+ // 网页态由 CLI 消费宿主凭据;桌面态继续消费宿主准备的登录态。
605
+ const webLogin = (command === 'login' || (command === 'auth' && args[0] === 'login')) &&
606
+ require('../lib/core/qwenwork-login-guidance').isQwenWorkWebRuntime(require('../lib/core/utils').detectRuntimeCapabilities());
607
+ if ((command === 'login' && args.includes('--qwenwork')) || webLogin) {
608
+ const loginArgs = applyLoginEnvironmentFlags(command === 'auth' ? args.slice(1) : args, { inferTargetUrl: true });
607
609
  if (hasHelpFlag(loginArgs)) { printLoginHelp(); return; }
608
610
  assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
609
611
  await require('../lib/auth/qwenwork-login').run(loginArgs, buildTokenLoginOptions(loginArgs));
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const { CliError } = require('../core/cli-error');
4
+ const { t } = require('../core/i18n');
5
+ const { DINGTALK_OAUTH_CLIENT_ID } = require('../core/env-manager');
6
+ const { resolveTokenBaseUrl } = require('./token-auth');
7
+ const { exchangeQwenWorkAgentToken, getQwenWorkUserInfo } = require('./qwenwork-client');
8
+
9
+ function failure(code, details = {}) {
10
+ return new CliError(t('qwenwork.login_check_failed'), { code: `QWENWORK_CLOUD_${code}`, details });
11
+ }
12
+
13
+ function origin(value) {
14
+ try {
15
+ const url = new URL(value);
16
+ if (url.protocol !== 'https:' || url.username || url.password || url.pathname !== '/' || url.search || url.hash) { throw new Error(); }
17
+ return url.origin;
18
+ } catch { throw failure('ENDPOINT_INVALID'); }
19
+ }
20
+
21
+ async function exchangeSession(credential, baseUrl, options) {
22
+ const controller = new AbortController();
23
+ const timeoutMs = options.timeoutMs === undefined ? 15000 : options.timeoutMs;
24
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { throw failure('INVALID_TIMEOUT'); }
25
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26
+ try {
27
+ const response = await (options.fetch || globalThis.fetch)(`${baseUrl}/openapi/cli/v1/auth/qwenwork/token`, {
28
+ method: 'POST', redirect: 'error', signal: controller.signal,
29
+ headers: { authorization: `Bearer ${credential.access_token}`, accept: 'application/json', 'content-type': 'application/json' },
30
+ body: JSON.stringify(options.corpId ? { corp_id: options.corpId } : {}),
31
+ });
32
+ if (!response.ok) {
33
+ await response.body?.cancel();
34
+ throw failure('HTTP_ERROR', { stage: 'yida_exchange', status_code: response.status });
35
+ }
36
+ const reader = response.body?.getReader();
37
+ if (!reader) { throw failure('RESPONSE_INVALID'); }
38
+ const chunks = []; let bytes = 0;
39
+ try {
40
+ for (let part = await reader.read(); !part.done; part = await reader.read()) {
41
+ bytes += part.value.byteLength;
42
+ if (bytes > 65536) { await reader.cancel(); throw failure('RESPONSE_INVALID'); }
43
+ chunks.push(part.value);
44
+ }
45
+ } finally { reader.releaseLock(); }
46
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
47
+ } catch (error) {
48
+ // 上游正文和异常可能回显凭据,仅保留本模块生成的固定错误码。
49
+ if (error instanceof CliError && /^QWENWORK_CLOUD_/.test(error.code)) { throw error; }
50
+ throw failure(controller.signal.aborted ? 'REQUEST_TIMED_OUT' : 'REQUEST_FAILED');
51
+ } finally { clearTimeout(timer); }
52
+ }
53
+
54
+ /** 仅在内存中兑换千问凭据;返回宜搭会话供调用方校验后保存。 */
55
+ async function exchangeQwenWorkCloudSession(options = {}) {
56
+ const baseUrl = origin(options.endpoint || (options.env || process.env).OPENYIDA_ENDPOINT || resolveTokenBaseUrl(options));
57
+ const credential = await exchangeQwenWorkAgentToken(options);
58
+ try {
59
+ const profile = await getQwenWorkUserInfo(credential, options);
60
+ if (profile.is_biz !== true || !profile.biz?.org_id) { throw failure('ENTERPRISE_REQUIRED'); }
61
+ const startedAt = Date.now();
62
+ const value = await exchangeSession(credential, baseUrl, options);
63
+ if (!value || value.status !== 'ok' || value.auth_mode !== 'token' || value.can_auto_use !== true ||
64
+ value.token_type !== 'Bearer' || value.credential_source !== 'qwenwork_cloud' ||
65
+ typeof value.access_token !== 'string' || !value.access_token || /\s/.test(value.access_token) || value.access_token.length > 32768 ||
66
+ value.access_token === credential.access_token ||
67
+ ['QWENWORK_AGENT_TOKEN', 'MULE_AGENT_TOKEN', 'AGENT_TOKEN'].some(name => value.access_token === (options.env || process.env)[name]) ||
68
+ !Number.isInteger(value.expires_in) || value.expires_in <= 0 || value.expires_in > 1800 ||
69
+ typeof value.corp_id !== 'string' || !/^ding[0-9a-f]{32}$/.test(value.corp_id) ||
70
+ typeof value.user_id !== 'string' || !value.user_id || value.user_id.trim() !== value.user_id || value.user_id.length > 256 ||
71
+ (options.corpId && value.corp_id !== options.corpId) ||
72
+ (options.userId && value.user_id !== options.userId) ||
73
+ (value.base_url !== undefined && origin(value.base_url) !== baseUrl) ||
74
+ 'refresh_token' in value || 'refreshToken' in value || 'exchange_ticket' in value) { throw failure('RESPONSE_INVALID'); }
75
+ return {
76
+ auth_mode: 'token', credential_source: 'qwenwork_cloud', token_type: 'Bearer',
77
+ access_token: value.access_token, base_url: baseUrl,
78
+ client_id: options.clientId || DINGTALK_OAUTH_CLIENT_ID,
79
+ corp_id: value.corp_id, user_id: value.user_id,
80
+ expires_at: startedAt + value.expires_in * 1000,
81
+ };
82
+ } finally { credential.access_token = undefined; }
83
+ }
84
+
85
+ module.exports = { exchangeQwenWorkCloudSession };
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
  const { t } = require('../core/i18n');
3
3
  const { CliError } = require('../core/cli-error');
4
- const { loadTokenSession, resolveTokenSession, isEnvTokenAuthMode } = require('./token-store');
4
+ const { loadTokenSession, resolveTokenSession, isEnvTokenAuthMode, hasEnvTokenCredential, saveTokenSession } = require('./token-store');
5
+ const { detectRuntimeCapabilities } = require('../core/utils');
6
+ const { isQwenWorkWebRuntime } = require('../core/qwenwork-login-guidance');
7
+ const { exchangeQwenWorkCloudSession } = require('./qwenwork-cloud');
5
8
  const { tokenLogin, tokenRefresh, isRefreshAuthRequired } = require('./token-auth');
6
9
 
7
10
  function failure(code) {
@@ -45,8 +48,26 @@ async function verify(session, options = {}) {
45
48
  } finally { clearTimeout(timer); }
46
49
  }
47
50
 
48
- /** 公开入口复用、刷新登录态;确需授权时复用普通 login 的前台 OAuth 流程。 */
51
+ /** 网页态消费宿主凭据;其他宿主保留已有登录态与前台 OAuth 流程。 */
49
52
  async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
53
+ const env = options.env || process.env;
54
+ const runtime = options.runtime || detectRuntimeCapabilities({ env });
55
+ if (isQwenWorkWebRuntime(runtime) && !isEnvTokenAuthMode(env) && !hasEnvTokenCredential(env)) {
56
+ if (options.checkOnly) { return summary(await verify(loadTokenSession(options), options)); }
57
+ const selectedProfile = options.authProfile || options.profile || env.OPENYIDA_AUTH_PROFILE;
58
+ const selectedSession = selectedProfile ? loadTokenSession(options) : null;
59
+ if (selectedProfile && !selectedSession) { throw failure('AUTH_PROFILE_REQUIRED'); }
60
+ const cloudOptions = {
61
+ ...options,
62
+ endpoint: options.endpoint || env.OPENYIDA_ENDPOINT || selectedSession?.base_url,
63
+ corpId: options.corpId || env.OPENYIDA_AUTH_CORP_ID || selectedSession?.corp_id,
64
+ userId: options.userId || env.OPENYIDA_AUTH_USER_ID || selectedSession?.user_id,
65
+ };
66
+ const session = await exchangeQwenWorkCloudSession(cloudOptions);
67
+ if (!await verify(session, options)) { throw failure('QWENWORK_LOGIN_CHECK_FAILED'); }
68
+ saveTokenSession(session, options);
69
+ return summary(true);
70
+ }
50
71
  const resolution = resolveTokenSession(options);
51
72
  if (resolution.status === 'profile_required') { throw failure('AUTH_PROFILE_REQUIRED'); }
52
73
  let session = loadTokenSession(options);
@@ -68,7 +89,7 @@ async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
68
89
  }
69
90
  }
70
91
  if (options.checkOnly) { return summary(false); }
71
- if (isEnvTokenAuthMode(options.env || process.env)) { throw failure('QWENWORK_LOGIN_FAILED'); }
92
+ if (isEnvTokenAuthMode(env) || isQwenWorkWebRuntime(runtime)) { throw failure('QWENWORK_LOGIN_FAILED'); }
72
93
  const result = await (dependencies.login || tokenLogin)(options);
73
94
  if (!result || result.ok === false || !result.access_token || !await verify(result, options)) {
74
95
  throw failure('QWENWORK_LOGIN_FAILED');
@@ -9,7 +9,7 @@ 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 } = require('./qwenwork-login-guidance');
12
+ const { buildQwenWorkLoginInvocation, isQwenWorkWebRuntime } = require('./qwenwork-login-guidance');
13
13
  const { findProjectRoot, getAuthStatus } = coreUtils;
14
14
 
15
15
  function redactLogin(login) {
@@ -231,6 +231,18 @@ function buildAuthPath(login, env = process.env, runtime = null) {
231
231
  ? 'STOP_AND_REQUEST_ENV_TOKEN'
232
232
  : 'RUN_OPENYIDA_LOGIN_IF_USER_APPROVES',
233
233
  };
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
+ }
234
246
  // 桌面换票仅由当前活跃宿主和发行配置共同启用,不影响其他宿主的认证路径。
235
247
  if (localBeta?.desktopTicketLogin && runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_desktop' && !envTokenSession) {
236
248
  const invocation = buildQwenWorkLoginInvocation();
@@ -247,6 +259,16 @@ function buildAuthPath(login, env = process.env, runtime = null) {
247
259
  }
248
260
 
249
261
  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
+ }
250
272
  if (auth.auth_runtime === 'qwenwork_desktop_exchange_ticket' && runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') {
251
273
  return {
252
274
  mode: 'desktop_ticket', browser_default: 'not_required', browser_owner: 'cli_if_auth_required',
@@ -334,7 +356,7 @@ function buildBuilderPath(login, projectRoot, manifest, env = process.env, runti
334
356
  auth,
335
357
  runtime,
336
358
  interactive_login: buildInteractiveLogin(auth, runtime),
337
- communication: runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop' ? {
359
+ communication: isQwenWorkWebRuntime(runtime) || (runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') ? {
338
360
  preparation_visibility: 'silent',
339
361
  completion_content: 'business_result_and_business_link_only',
340
362
  disclose_authentication_identity_environment: false,
@@ -632,7 +654,7 @@ function buildAgentCapabilitiesSummary() {
632
654
  schema_version: 1,
633
655
  name: 'openyida-agent-capabilities-summary',
634
656
  version,
635
- login: builderPath.auth.auth_runtime === 'qwenwork_desktop_exchange_ticket'
657
+ login: ['qwenwork_desktop_exchange_ticket', 'qwenwork_cloud_exchange'].includes(builderPath.auth.auth_runtime)
636
658
  ? { status: loginStatus.status, can_auto_use: loginStatus.can_auto_use === true }
637
659
  : compactLogin(loginStatus),
638
660
  workdir: projectRoot,
@@ -21,12 +21,15 @@ function buildQwenWorkLoginInvocation(options = {}) {
21
21
  const executable = resolveExecutable(options.executablePath, options.packageRoot);
22
22
  return {
23
23
  command: `${executable} login --qwenwork --json`,
24
- required_runtime: { tool: 'qwenwork', subtype: 'qwenwork_desktop' },
24
+ required_runtime: { tool: 'qwenwork', subtype: options.subtype === 'qwenwork_web' ? 'qwenwork_web' : 'qwenwork_desktop' },
25
25
  execution: 'command',
26
26
  preserve_command_verbatim: true,
27
27
  output_visibility: 'internal',
28
28
  success_next_step: 'continue_business_operation_silently',
29
- failure_next_step: 'follow_returned_invocation_guidance',
29
+ failure_next_step: options.subtype === 'qwenwork_web' ? 'stop_and_report_blocker_without_browser_or_credentials' : 'follow_returned_invocation_guidance',
30
30
  };
31
31
  }
32
- module.exports = { buildQwenWorkLoginInvocation };
32
+ function isQwenWorkWebRuntime(runtime) {
33
+ return runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_web';
34
+ }
35
+ module.exports = { buildQwenWorkLoginInvocation, isQwenWorkWebRuntime };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.10.26-beta.1",
3
+ "version": "2026.10.26-beta.2",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida-pre-beta.js",
@@ -124,7 +124,7 @@
124
124
  "openyidaLocalBeta": {
125
125
  "environment": "pre",
126
126
  "baseUrl": "https://pre-yida-vpc.alibaba-inc.com",
127
- "description": "宜搭 PRE 一次性换票联调",
127
+ "description": "宜搭 PRE 千问办公免登联调",
128
128
  "authDirectory": "qwenwork-pre-beta",
129
129
  "desktopTicketLogin": true
130
130
  }
@@ -79,6 +79,10 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
79
79
 
80
80
  每项待办对应一个阶段,直接使用上方短标题。创建应用、表单、数据、图片、页面和发布分别展示。执行中保持标题与列表顺序稳定,完成后标记完成并保留记录。图片准备与表单、页面搭建可同时进行,列表顺序仅用于展示。业务细节写在进度消息中,任务编号、命令、路径、授权依据和耗时写入内部执行记录;用户要求的耗时统计集中放入报告。素材采集与应用创建同时进行时,进度描述统一为“素材采集&应用创建”;单独进行时使用对应阶段名称。图片数量、用途及任务详情归入内部执行记录。
81
81
 
82
+ ## 千问办公网页态的后台登录
83
+
84
+ 当前快照为 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_web` 且 `interactive_login.mode=cloud_token` 时,按[云端后台免登规则](references/qwenwork-cloud-login.md)执行快照推荐命令。CLI 自动消费沙箱凭据;成功后继续业务,失败不回退浏览器 OAuth,不读取或展示凭据。桌面态继续使用桌面换票。
85
+
82
86
  ## 千问办公桌面端的后台准备
83
87
 
84
88
  当前快照确认运行于千问办公桌面端时,安装、环境检查和自动登录按[后台准备规则](references/qwenwork-desktop-login.md)静默执行。用户只需要看到业务进度和业务结果。
@@ -100,6 +104,7 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
100
104
  | `openyida` 不可用 | 先安装或更新 `openyida`,不创建资源 |
101
105
  | 工作目录不存在 | 先执行 `openyida copy` 初始化工作目录 |
102
106
  | 登录态可用 | 进入 Step 2 |
107
+ | `interactive_login.mode=cloud_token` 且未登录 | 静默执行快照推荐的 `openyida login --qwenwork --json`;CLI 自动消费云端凭据,成功后继续业务;失败停止,不打开浏览器或回退 OAuth |
103
108
  | 登录态缺失 | 按快照提示补登录态;未恢复前停止资源写操作 |
104
109
 
105
110
  千问云端身份接入开发或宿主身份诊断,可显式运行 `openyida auth qwenwork user-info --json`;它只验证千问身份,不建立宜搭登录态,不能据此跳过上述宜搭 auth snapshot 检查。详细边界见 `yida-login`。
@@ -0,0 +1,8 @@
1
+ # 千问办公网页态后台免登
2
+
3
+ 1. 以当前能力快照的 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_web`、`builder_path.interactive_login.mode=cloud_token` 确认环境。
4
+ 2. 已登录直接继续业务;未登录时静默执行快照推荐的命令,通常为 `openyida login --qwenwork --json`,并等待结束。普通 `openyida login` 在该网页态也会自动走同一路径。
5
+ 3. CLI 自行消费沙箱提供的 Agent 凭据,兑换千问用户凭据并校验身份,再向当前宜搭环境换取登录态。不要让模型读取、复制、打印环境变量或认证文件。
6
+ 4. 成功后直接继续创建应用等用户任务,只展示业务结果和链接;不展示安装、登录、Token、用户、组织、环境或凭据路径。
7
+ 5. 缺少凭据、个人身份无企业组织、接口拒绝或网络失败时停止,只简短说明阻塞;不回退 OAuth,不启动浏览器或 Playwright,不要求用户粘贴 Token。
8
+ 6. 桌面态仍走桌面换票;其他宿主继续采用其快照中的认证规则;已注入宜搭 Token 的运行环境保持原规则。
@@ -1,3 +1,7 @@
1
+ ## 千问办公网页态的后台登录
2
+
3
+ 当前快照为 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_web` 且 `interactive_login.mode=cloud_token` 时,按[云端后台免登规则](qwenwork-cloud-login.md)执行快照推荐命令。CLI 自动消费沙箱凭据;成功后继续业务,失败不回退浏览器 OAuth,不读取或展示凭据。桌面态继续使用桌面换票。
4
+
1
5
  ## 千问办公桌面端
2
6
 
3
7
  当前快照确认运行于千问办公桌面端时,遵循[后台准备规则](qwenwork-desktop-login.md),静默完成下述准备,不向用户逐项播报。
@@ -23,7 +27,7 @@ openyida login --check-only --json
23
27
  - 不要根据 agent 名称、宿主产品、workspace 路径或猜测的环境变量推断认证模式。
24
28
  - 先执行 `openyida agent-capabilities --summary-json`;只有简版快照不可用或信息不足时,才降级执行 `openyida login --check-only --json`。
25
29
  - snapshot 返回 `login.auth_source=env` 或 `failure_reason=env_token_missing` 时,进入运行环境注入 token 模式。凭证只来自运行环境注入的 `OPENYIDA_ACCESS_TOKEN`、`OPENYIDA_REFRESH_TOKEN` 等环境变量。
26
- - 其他 `login.auth_mode=token` 场景使用默认 OAuth token session。
30
+ - 除上述千问办公网页态云端免登外,其他 `login.auth_mode=token` 场景使用默认 OAuth token session。
27
31
  - 不要从 `.cache/cookies*.json` 推断登录态。
28
32
  - OAuth 浏览器归属以 `builder_path.interactive_login.mode` 为准:`not_required` 不触发 OAuth;`cli_auto_open` 执行 `openyida login`;`caller_open_url` 执行 `openyida login --no-browser`,由 Agent 优先调用沙箱浏览器 / 内置 Browser 打开输出 URL 一次;`unsupported` 停止说明环境没有可用浏览器能力,不要默认安装 Playwright。
29
33
 
@@ -34,6 +38,7 @@ openyida login --check-only --json
34
38
  | command not found | 安装或更新 `openyida`;不要创建资源 |
35
39
  | `workdir_exists=false` 或 `active.projectRootExists=false` | 先执行 `openyida copy`;工作目录存在前不要创建资源 |
36
40
  | `auth_mode=token` 且 `status=ok` 或 `can_auto_use=true` | 继续执行 |
41
+ | `interactive_login.mode=cloud_token` 且未登录 | 静默执行快照推荐的 `openyida login --qwenwork --json`;CLI 自动消费云端凭据,成功后继续业务;失败停止,不打开浏览器或回退 OAuth |
37
42
  | `auth_mode=token` 且 `status=profile_required` | 不猜组织;执行 `openyida auth profiles`,再用 `openyida auth profile switch <auth_profile>` 切换已有 profile |
38
43
  | snapshot 返回 `auth_source=env` / `failure_reason=env_token_missing` | 进入运行环境注入 token 模式;缺 token 时停止,让 Codex、yida-agent 等宿主注入 `OPENYIDA_ACCESS_TOKEN` 或 `OPENYIDA_REFRESH_TOKEN`;不要执行 OAuth |
39
44
  | `auth_mode=token`,未登录,且 `interactive_login.mode=cli_auto_open` | 只执行一次 `openyida login`,等待该命令结束,并使用其最终 JSON 判断结果 |
@@ -11,11 +11,15 @@ description: 宜搭登录态管理。以 OpenYida auth snapshot 为准;默认
11
11
  - 不要根据 agent 名称、宿主产品、workspace 路径或猜测的环境变量推断认证模式。
12
12
  - 先读 `openyida agent-capabilities --summary-json`;只有需要更多信息时,才降级执行 `openyida login --check-only --json`。
13
13
  - snapshot 返回 `login.auth_source=env` 或 `failure_reason=env_token_missing` 时,进入运行环境注入 token 模式。凭证只来自运行环境注入的 `OPENYIDA_ACCESS_TOKEN`、`OPENYIDA_REFRESH_TOKEN` 等环境变量。
14
- - 其他 `auth_mode=token` 场景使用默认 OAuth token session。
14
+ - 除上述千问办公网页态云端免登外,其他 `auth_mode=token` 场景使用默认 OAuth token session。
15
15
  - 不要从 `.cache/cookies*.json` 推断登录态。
16
16
  - 浏览器归属以 `builder_path.interactive_login.mode` 为准:`not_required` 不触发 OAuth;`cli_auto_open` 执行 `openyida login` 并等待;`caller_open_url` 执行 `openyida login --no-browser`,由 Agent 优先调用沙箱浏览器 / 内置 Browser 打开 CLI 输出的授权 URL 一次;`unsupported` 停止并说明没有可用浏览器能力,不要默认安装 Playwright。
17
17
  - `caller_open_url` 模式下,Agent 不要只把 URL 贴给用户然后等待。只有当前宿主没有浏览器工具,或浏览器工具调用失败时,才退回让用户手动打开 URL。
18
18
 
19
+ ## 千问办公网页态的后台登录
20
+
21
+ 当前快照为 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_web` 且 `interactive_login.mode=cloud_token` 时,按[云端后台免登规则](../../references/qwenwork-cloud-login.md)执行快照推荐命令。CLI 自动消费沙箱凭据;成功后继续业务,失败不回退浏览器 OAuth,不读取或展示凭据。桌面态继续使用桌面换票。
22
+
19
23
  ## 千问办公桌面端的后台登录
20
24
 
21
25
  先以当前快照的 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_desktop` 和 `builder_path.interactive_login.mode=desktop_ticket` 确认桌面换票能力。满足条件后,按[后台准备规则](../../references/qwenwork-desktop-login.md)静默补齐登录态,随后继续业务操作。
@@ -42,6 +46,7 @@ openyida login --check-only --json
42
46
  | 状态 | 动作 |
43
47
  |---|---|
44
48
  | `auth_mode=token` 且 `status=ok` 或 `can_auto_use=true` | 继续执行业务命令 |
49
+ | `interactive_login.mode=cloud_token` 且未登录 | 静默执行快照推荐的 `openyida login --qwenwork --json`;CLI 自动消费云端凭据,成功后继续业务;失败停止,不打开浏览器或回退 OAuth |
45
50
  | `status=profile_required` 且返回 `candidates` / `next_step` | 不猜组织;先执行 `openyida auth profiles`,再用 `openyida auth profile switch <auth_profile>` 切到用户确认或目标匹配的已有 profile |
46
51
  | `auth_source=env` / `failure_reason=env_token_missing` | 进入运行环境注入 token 模式;缺 token 时停止,让 Codex、yida-agent 等宿主注入 `OPENYIDA_ACCESS_TOKEN` 或 `OPENYIDA_REFRESH_TOKEN`;不要执行 OAuth |
47
52
  | `auth_mode=token`,未登录,且 `interactive_login.mode=cli_auto_open` | 只执行一次 `openyida login`,等待该命令结束,并使用其最终 JSON 判断结果 |