openyida 2026.10.23-beta.2 → 2026.10.23-beta.3
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/lib/auth/qwenwork-desktop.js +20 -8
- package/lib/auth/token-auth.js +8 -1
- package/lib/core/agent-capabilities.js +7 -4
- package/lib/core/env-manager.js +1 -3
- package/lib/core/locales/en.js +1 -0
- package/lib/core/locales/zh.js +1 -0
- package/lib/core/qwenwork-login-guidance.js +32 -0
- package/package.json +1 -1
- package/yida-skills/references/qwenwork-desktop-login.md +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { CliError } = require('../core/cli-error');
|
|
4
|
+
const { buildQwenWorkLoginInvocation } = require('../core/qwenwork-login-guidance');
|
|
4
5
|
const { t } = require('../core/i18n');
|
|
5
6
|
const { DINGTALK_OAUTH_CLIENT_ID } = require('../core/env-manager');
|
|
6
7
|
const { saveTokenSession, loadTokenSession, isEnvTokenAuthMode } = require('./token-store');
|
|
@@ -12,7 +13,10 @@ const CONSUME_PATH = '/openapi/cli/v1/auth/qwenwork/desktop/consume';
|
|
|
12
13
|
const MAX_RESPONSE_BYTES = 65536;
|
|
13
14
|
|
|
14
15
|
function fail(code) {
|
|
15
|
-
return new CliError(t('qwenwork.desktop_login_failed'), {
|
|
16
|
+
return new CliError(t('qwenwork.desktop_login_failed'), {
|
|
17
|
+
code: `QWENWORK_DESKTOP_${code}`,
|
|
18
|
+
details: { invocation: buildQwenWorkLoginInvocation() },
|
|
19
|
+
});
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
function parseTicket(raw, context) {
|
|
@@ -95,9 +99,12 @@ async function loginQwenWork(options = {}) {
|
|
|
95
99
|
const expiresAt = startedAt + Number(value.expires_in) * 1000;
|
|
96
100
|
if (value.status !== 'ok' || value.auth_mode !== 'token' || value.can_auto_use !== true ||
|
|
97
101
|
value.token_type !== 'Bearer' || value.credential_source !== 'qwenwork_desktop' ||
|
|
98
|
-
'
|
|
102
|
+
'refreshToken' in value || 'exchange_ticket' in value ||
|
|
99
103
|
typeof value.access_token !== 'string' || !value.access_token || /\s/.test(value.access_token) || value.access_token.length > 32768 ||
|
|
100
104
|
!Number.isInteger(value.expires_in) || value.expires_in < 1 || value.expires_in > 1800 ||
|
|
105
|
+
typeof value.refresh_token !== 'string' || !value.refresh_token || /\s/.test(value.refresh_token) || value.refresh_token.length > 32768 ||
|
|
106
|
+
!Number.isInteger(value.refresh_token_expires_in) || value.refresh_token_expires_in < 1 ||
|
|
107
|
+
(value.client_id && value.client_id !== DINGTALK_OAUTH_CLIENT_ID) ||
|
|
101
108
|
value.corp_id !== ticket.corp_id || value.user_id !== ticket.user_id || value.exchange_id !== ticket.exchange_id ||
|
|
102
109
|
(value.base_url && origin(value.base_url) !== baseUrl)) { throw fail('CREDENTIAL_INVALID'); }
|
|
103
110
|
const verified = await requestJson(`${baseUrl}/openapi/cli/v1/auth/status`, {
|
|
@@ -106,34 +113,39 @@ async function loginQwenWork(options = {}) {
|
|
|
106
113
|
if (verified.status !== 'ok' || verified.auth_mode !== 'token' || verified.can_auto_use !== true ||
|
|
107
114
|
verified.corp_id !== value.corp_id || verified.user_id !== value.user_id || expiresAt <= Date.now()) { throw fail('IDENTITY_MISMATCH'); }
|
|
108
115
|
const session = saveTokenSession({
|
|
109
|
-
auth_mode: 'token', token_type: 'Bearer', access_token: value.access_token,
|
|
116
|
+
auth_mode: 'token', token_type: 'Bearer', access_token: value.access_token, refresh_token: value.refresh_token,
|
|
110
117
|
expires_at: expiresAt, base_url: baseUrl, client_id: DINGTALK_OAUTH_CLIENT_ID,
|
|
111
118
|
corp_id: value.corp_id, user_id: value.user_id, credential_source: 'qwenwork_desktop',
|
|
112
119
|
}, options);
|
|
113
120
|
const restored = loadTokenSession(options);
|
|
114
121
|
if (!['user', 'project'].includes(session.persistence_scope) || !restored ||
|
|
115
|
-
restored.access_token !== value.access_token || restored.base_url !== baseUrl ||
|
|
122
|
+
restored.access_token !== value.access_token || restored.refresh_token !== value.refresh_token || restored.base_url !== baseUrl ||
|
|
116
123
|
restored.corp_id !== value.corp_id || restored.user_id !== value.user_id) { throw fail('PERSISTENCE_FAILED'); }
|
|
117
124
|
// 身份、环境和存储细节仅用于内部校验,不进入模型可见的登录结果。
|
|
118
125
|
return { ok: true, status: 'ok', can_auto_use: true };
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
async function run(args, options = {}) {
|
|
122
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
129
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
130
|
+
const invocation = buildQwenWorkLoginInvocation();
|
|
131
|
+
if (args.includes('--json')) { console.log(JSON.stringify({ usage: t('qwenwork.desktop_login_usage'), invocation })); }
|
|
132
|
+
else { console.log(`${t('qwenwork.desktop_login_usage')}\n${t('qwenwork.desktop_login_standalone', invocation.command)}`); }
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
123
135
|
const parsed = {};
|
|
124
136
|
const seen = new Set();
|
|
125
137
|
for (let index = 0; index < args.length; index++) {
|
|
126
138
|
const flag = args[index];
|
|
127
|
-
if (seen.has(flag)) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2 }); }
|
|
139
|
+
if (seen.has(flag)) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2, details: { invocation: buildQwenWorkLoginInvocation() } }); }
|
|
128
140
|
seen.add(flag);
|
|
129
141
|
if (['--json', '--quiet'].includes(flag)) { continue; }
|
|
130
142
|
const key = flag === '--corp-id' ? 'corpId' : flag === '--endpoint' ? 'endpoint' : null;
|
|
131
143
|
const value = args[++index];
|
|
132
|
-
if (!key || !value || value.startsWith('--')) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2 }); }
|
|
144
|
+
if (!key || !value || value.startsWith('--')) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2, details: { invocation: buildQwenWorkLoginInvocation() } }); }
|
|
133
145
|
parsed[key] = value;
|
|
134
146
|
}
|
|
135
147
|
const result = await loginQwenWork({ ...options, ...parsed });
|
|
136
|
-
if (args.includes('--json') && !args.includes('--quiet') && (options.env || process.env).YIDA_QUIET !== '1') { console.log(JSON.stringify(result)); }
|
|
148
|
+
if (args.includes('--json') && !args.includes('--quiet') && (options.env || process.env).YIDA_QUIET !== '1') { console.log(JSON.stringify({ ...result, next_action: 'continue_business_operation_silently', user_visible: false })); }
|
|
137
149
|
return result;
|
|
138
150
|
}
|
|
139
151
|
|
package/lib/auth/token-auth.js
CHANGED
|
@@ -175,6 +175,7 @@ function normalizeTokenResponse(response, baseUrl, clientId) {
|
|
|
175
175
|
);
|
|
176
176
|
return {
|
|
177
177
|
auth_mode: 'token',
|
|
178
|
+
credential_source: data.credential_source,
|
|
178
179
|
status: data.status || 'ok',
|
|
179
180
|
can_auto_use: true,
|
|
180
181
|
message: data.message || data.errorMsg || response.errorMsg,
|
|
@@ -486,7 +487,9 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
486
487
|
// Token exchange follows the active environment endpoint, just like the
|
|
487
488
|
// legacy login bootstrap. The response may return a different corp base_url
|
|
488
489
|
// for business requests and generated application links.
|
|
489
|
-
|
|
490
|
+
// 桌面会话沿用首次换票的可信 origin;刷新不受后续全局环境切换影响。
|
|
491
|
+
const desktopSession = session.credential_source === 'qwenwork_desktop';
|
|
492
|
+
const authBaseUrlValue = desktopSession ? session.base_url : resolveTokenBaseUrl({
|
|
490
493
|
projectRoot: options.projectRoot,
|
|
491
494
|
endpoint: options.endpoint,
|
|
492
495
|
});
|
|
@@ -499,6 +502,9 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
499
502
|
requireOkResponse(response);
|
|
500
503
|
const data = getTokenPayload(response);
|
|
501
504
|
const responseBusinessBaseUrl = data.base_url || data.baseUrl;
|
|
505
|
+
if (desktopSession && responseBusinessBaseUrl && normalizeBaseUrl(responseBusinessBaseUrl, null) !== session.base_url) {
|
|
506
|
+
throw createTokenAuthRequiredError('refresh_origin_mismatch');
|
|
507
|
+
}
|
|
502
508
|
const businessBaseUrl = session.base_url || authBaseUrlValue;
|
|
503
509
|
const normalized = normalizeTokenResponse({
|
|
504
510
|
...data,
|
|
@@ -507,6 +513,7 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
507
513
|
user_id: data.user_id || data.userId || session.user_id,
|
|
508
514
|
user_name: data.user_name || data.userName || session.user_name,
|
|
509
515
|
refresh_token: data.refresh_token || data.refreshToken || session.refresh_token,
|
|
516
|
+
credential_source: data.credential_source || session.credential_source,
|
|
510
517
|
}, businessBaseUrl, clientId);
|
|
511
518
|
if (!normalized.access_token) {
|
|
512
519
|
return {
|
|
@@ -9,6 +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
13
|
const { findProjectRoot, getAuthStatus } = coreUtils;
|
|
13
14
|
|
|
14
15
|
function redactLogin(login) {
|
|
@@ -232,14 +233,15 @@ function buildAuthPath(login, env = process.env, runtime = null) {
|
|
|
232
233
|
};
|
|
233
234
|
// 桌面换票仅由当前活跃宿主和发行配置共同启用,不影响其他宿主的认证路径。
|
|
234
235
|
if (localBeta?.desktopTicketLogin && runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_desktop' && !envTokenSession) {
|
|
236
|
+
const invocation = buildQwenWorkLoginInvocation();
|
|
235
237
|
authPath.auth_runtime = 'qwenwork_desktop_exchange_ticket';
|
|
236
238
|
authPath.interactive_login_allowed = false;
|
|
237
239
|
authPath.missing_token_action = 'RUN_OPENYIDA_LOGIN_QWENWORK';
|
|
238
240
|
for (const key of ['auth_profile', 'corp_id', 'corp_name', 'user_id', 'user_name', 'store', 'persistence_scope', 'warning']) { delete authPath[key]; }
|
|
239
241
|
authPath.next_step = authPath.can_auto_use
|
|
240
|
-
? 'Silently continue the requested business operation
|
|
241
|
-
:
|
|
242
|
-
authPath.next_step_commands = authPath.can_auto_use ? [] : [
|
|
242
|
+
? 'Silently continue the requested business operation and report its business result.'
|
|
243
|
+
: `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.`;
|
|
244
|
+
authPath.next_step_commands = authPath.can_auto_use ? [] : [invocation.command];
|
|
243
245
|
}
|
|
244
246
|
return authPath;
|
|
245
247
|
}
|
|
@@ -248,7 +250,8 @@ function buildInteractiveLogin(auth, runtime) {
|
|
|
248
250
|
if (auth.auth_runtime === 'qwenwork_desktop_exchange_ticket' && runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') {
|
|
249
251
|
return {
|
|
250
252
|
mode: 'desktop_ticket', browser_default: 'not_required', browser_owner: 'none',
|
|
251
|
-
recommended_command: auth.can_auto_use ? null :
|
|
253
|
+
recommended_command: auth.can_auto_use ? null : buildQwenWorkLoginInvocation().command,
|
|
254
|
+
invocation: buildQwenWorkLoginInvocation(),
|
|
252
255
|
agent_action: auth.can_auto_use ? 'continue_business_command' : 'run_standalone_login_qwenwork_and_wait',
|
|
253
256
|
user_visible: false, success_action: 'continue_business_command_without_message',
|
|
254
257
|
reason: 'qwenwork_desktop_pre_beta', completion_signal: 'process_exit_and_final_json', playwright_required: false,
|
package/lib/core/env-manager.js
CHANGED
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const { findProjectRoot } = require('./utils');
|
|
27
|
-
const localBeta = require('../../package.json').openyidaLocalBeta;
|
|
28
27
|
|
|
29
28
|
const DEFAULT_BASE_URL = 'https://www.aliwork.com';
|
|
30
29
|
const DEFAULT_LOGIN_URL = 'https://www.aliwork.com/workPlatform';
|
|
@@ -108,7 +107,6 @@ const DEFAULT_INTERNATIONAL_ENV = {
|
|
|
108
107
|
};
|
|
109
108
|
|
|
110
109
|
const BUILTIN_ENVIRONMENTS = {
|
|
111
|
-
...(localBeta ? { [localBeta.environment]: { baseUrl: localBeta.baseUrl, loginUrl: localBeta.baseUrl + '/workPlatform', description: localBeta.description } } : {}),
|
|
112
110
|
public: DEFAULT_PUBLIC_ENV,
|
|
113
111
|
intl: DEFAULT_INTERNATIONAL_ENV,
|
|
114
112
|
alibaba: DEFAULT_ALIBABA_INTERNAL_ENV,
|
|
@@ -167,7 +165,7 @@ function cloneBuiltinEnvironments() {
|
|
|
167
165
|
|
|
168
166
|
function buildDefaultEnvsConfig() {
|
|
169
167
|
return {
|
|
170
|
-
current:
|
|
168
|
+
current: 'public',
|
|
171
169
|
environments: cloneBuiltinEnvironments(),
|
|
172
170
|
};
|
|
173
171
|
}
|
package/lib/core/locales/en.js
CHANGED
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
qwenwork: {
|
|
8
8
|
desktop_login_failed: 'QwenWork desktop login failed. Check the desktop identity and login handoff before retrying.',
|
|
9
9
|
desktop_login_usage: 'Usage: openyida login-qwenwork [--corp-id <corpId>] [--endpoint <url>] [--json]',
|
|
10
|
+
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.',
|
|
10
11
|
credential_missing: 'No cloud Agent credential is available. Run this command inside the QwenWork cloud execution environment.',
|
|
11
12
|
gateway_missing: 'QWENWORK_GATEWAY_URL or MULE_GATEWAY_URL is required.',
|
|
12
13
|
invalid_url: 'The QwenWork gateway must use HTTPS (HTTP is allowed only for loopback) and contain no credentials, query or fragment.',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
qwenwork: {
|
|
8
8
|
desktop_login_failed: '千问办公桌面免登失败,请确认桌面登录身份和免登通道后重试。',
|
|
9
9
|
desktop_login_usage: '用法:openyida login-qwenwork [--corp-id <corpId>] [--endpoint <url>] [--json]',
|
|
10
|
+
desktop_login_standalone: '请直接执行推荐的登录命令:{0}。无需额外处理 PATH 和输出;成功后静默继续业务,失败时按提示修正调用。',
|
|
10
11
|
credential_missing: '未发现云端 Agent 凭证,请在千问办公云端执行环境中运行此命令。',
|
|
11
12
|
gateway_missing: '缺少 QWENWORK_GATEWAY_URL 或 MULE_GATEWAY_URL。',
|
|
12
13
|
invalid_url: '千问网关必须使用 HTTPS(仅回环地址允许 HTTP),且不能包含账号密码、查询参数或片段。',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
/** 从当前调用或同一安装前缀定位入口,不依赖模型重写 PATH。 */
|
|
6
|
+
function resolveExecutable(invoked = process.argv[1], packageRoot = path.resolve(__dirname, '../..')) {
|
|
7
|
+
const usable = value => typeof value === 'string' && /^(?:\/|[a-z]:[\\/])/i.test(value)
|
|
8
|
+
&& /^[\w./:\\-]+$/.test(value) && /^openyida(?:\.cmd|\.exe)?$/i.test(path.basename(value.replace(/\\/g, '/')));
|
|
9
|
+
if (usable(invoked)) { return invoked.replace(/\\/g, '/'); }
|
|
10
|
+
const candidates = [path.resolve(packageRoot, '../../../bin/openyida'), path.resolve(packageRoot, '../../openyida.cmd')];
|
|
11
|
+
for (const candidate of candidates) {
|
|
12
|
+
if (!usable(candidate) || !fs.existsSync(candidate)) { continue; }
|
|
13
|
+
try {
|
|
14
|
+
if (fs.realpathSync(candidate).startsWith(`${fs.realpathSync(packageRoot)}${path.sep}`)) { return candidate.replace(/\\/g, '/'); }
|
|
15
|
+
} catch { /* 不可用入口不扩展到其他安装目录。 */ }
|
|
16
|
+
}
|
|
17
|
+
return 'openyida';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildQwenWorkLoginInvocation(options = {}) {
|
|
21
|
+
const executable = resolveExecutable(options.executablePath, options.packageRoot);
|
|
22
|
+
return {
|
|
23
|
+
command: `${executable} login-qwenwork --json`,
|
|
24
|
+
required_runtime: { tool: 'qwenwork', subtype: 'qwenwork_desktop' },
|
|
25
|
+
execution: 'standalone_command',
|
|
26
|
+
preserve_command_verbatim: true,
|
|
27
|
+
output_visibility: 'internal',
|
|
28
|
+
success_next_step: 'continue_business_operation_silently',
|
|
29
|
+
failure_next_step: 'follow_returned_invocation_guidance',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
module.exports = { buildQwenWorkLoginInvocation };
|
package/package.json
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
只以当前 `openyida agent-capabilities --summary-json` 的 `runtime.tool=qwenwork`、`runtime.subtype=qwenwork_desktop` 和 `builder_path.interactive_login.mode=desktop_ticket` 判断桌面换票能力。安装目录、产品名称和历史会话不能替代当前快照。
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
快照显示登录可用时直接执行用户请求;登录不可用且上述条件全部成立时,原样执行 `builder_path.interactive_login.recommended_command`。该命令优先使用当前 CLI 的绝对路径,后续复用同一入口,不自行补 PATH。执行规则以 `builder_path.interactive_login.invocation` 为准;成功后直接继续业务操作。下面仅为命令形式示例:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
openyida login-qwenwork --json
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
直接使用推荐的完整命令即可,无需额外处理 PATH 和输出;执行工具会提供结果和退出状态。调用格式不匹配时,按返回的推荐命令修正后继续。专用命令只消费本次私有通道的短期一次性兑换票,不调用或递归进入普通 `login`。
|
|
28
28
|
|
|
29
29
|
真实宜搭凭据仅由 CLI 校验并持久化,不读取、填写或展示任何令牌、兑换票和认证文件。保持包内配置及实际认证 origin,不根据历史记忆猜测环境等价或切换到其他环境、组织。凭据过期时重新执行专用命令;票据缺失、过期、重放、签发或消费失败时停止,不回退 OAuth、直接凭据或手改环境文件。
|
|
30
30
|
|