openyida 2026.10.23-2 → 2026.10.23-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 +22 -3
- package/bin/yida-pre-beta.js +11 -0
- package/bin/yida.js +10 -5
- package/lib/app/services/canvas-page-schema-builder.js +1 -43
- package/lib/auth/qwenwork-client.js +190 -0
- package/lib/auth/qwenwork-desktop.js +140 -0
- package/lib/auth/qwenwork.js +37 -0
- package/lib/auth/token-store.js +1 -0
- package/lib/core/agent-capabilities.js +35 -5
- package/lib/core/command-manifest.js +9 -36
- package/lib/core/env-manager.js +3 -1
- package/lib/core/locales/en.js +16 -14
- package/lib/core/locales/zh.js +16 -14
- package/lib/core/task-center.js +4 -32
- package/lib/samples/openyida-scaffold/canvas-navigation.jsx +2 -6
- package/lib/task-center/contract.js +1 -26
- package/lib/task-center/loader.js +0 -5
- package/lib/task-center/scaffold.js +0 -9
- package/package.json +13 -5
- package/scripts/install-qwenwork-pre-beta.js +40 -0
- package/yida-skills/SKILL.md +7 -2
- package/yida-skills/references/qwenwork-desktop-login.md +33 -0
- package/yida-skills/references/setup-and-env.md +4 -0
- package/yida-skills/references/yida-api.md +1 -3
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +2 -4
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +0 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +0 -2
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +2 -2
- package/yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md +1 -29
- package/yida-skills/skills/yida-create-page/SKILL.md +0 -1
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/references/application-style-library.md +0 -2
- package/yida-skills/skills/yida-design/references/application-theme-consistency.md +0 -4
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +44 -27
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +1 -3
- package/yida-skills/skills/yida-login/SKILL.md +18 -0
- package/yida-skills/skills/yida-task-center/SKILL.md +1 -3
- package/yida-skills/skills-index.json +0 -13
- package/lib/app/form-message.js +0 -352
- package/lib/navigation/page-navigation.js +0 -62
- package/lib/task-center/navigation.js +0 -41
- package/yida-skills/skills/yida-form-message/SKILL.md +0 -53
- package/yida-skills/skills/yida-form-message/references/guide.md +0 -111
package/README.md
CHANGED
|
@@ -143,6 +143,25 @@ openyida login --check-only --json
|
|
|
143
143
|
openyida auth status
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
openyida auth qwenwork user-info --json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
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.
|
|
153
|
+
|
|
154
|
+
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.
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const { exchangeQwenWorkAgentToken, getQwenWorkUserInfo } = require('openyida/lib/auth/qwenwork-client');
|
|
158
|
+
async function readCloudUser() {
|
|
159
|
+
const credential = await exchangeQwenWorkAgentToken();
|
|
160
|
+
// The result contains QwenWork identifiers, not YiDA IDs.
|
|
161
|
+
return getQwenWorkUserInfo(credential);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
146
165
|
To reuse an existing shared login profile for the current project, switch first instead of starting a new OAuth login:
|
|
147
166
|
|
|
148
167
|
```bash
|
|
@@ -458,8 +477,9 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
458
477
|
| Command | Description |
|
|
459
478
|
|---------|-------------|
|
|
460
479
|
| `openyida login [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]` | Login with OAuth token mode |
|
|
480
|
+
| `openyida login-qwenwork [--corp-id <corpId>] [--endpoint <url>] [--json]` | Log in to YiDA with a one-time QwenWork exchange ticket |
|
|
461
481
|
| `openyida logout` | Logout / unbind current project auth |
|
|
462
|
-
| `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch
|
|
482
|
+
| `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch\|qwenwork user-info> [--json]` | Token login state and profile management |
|
|
463
483
|
| `openyida org <list\|switch> [--json] [--corp-id <corpId>]` | Organization management (list / switch existing profiles first) |
|
|
464
484
|
| `openyida env [--json\|setup\|list\|show\|switch\|add\|remove] [options]` | Detect AI tool environment & token login state |
|
|
465
485
|
|
|
@@ -516,7 +536,6 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
516
536
|
| `openyida print-template <list\|inspect\|fields\|build\|create\|copy\|save\|restore\|settings\|status\|order\|render\|files> <appType> <formUuid> [templateId] [--config <file>] [--scene <order\|quotation\|delivery\|receipt\|approval\|codes>] [--bindings <file>] [--name <title>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--status START\|STOP] [--instance <id>] [--page <n>] [--size <n>] [--allow-permission-change] [--json] \| print-template <list\|render> --detail-url <url> [--template-id <id>] [--json]` | Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs |
|
|
517
537
|
| `openyida form-view <list\|inspect\|save\|create\|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]` | Native form views: list, inspect and configure columns, widths, frozen columns and filters |
|
|
518
538
|
| `openyida custom-button <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native custom buttons: inspect and configure form edits, automation flows and page links |
|
|
519
|
-
| `openyida form-message <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [messageId] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native form notifications: inspect triggers, recipients and templates, then safely maintain bindings |
|
|
520
539
|
| `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
|
|
521
540
|
| `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check resources, frontend menu hiding and application theme delivery |
|
|
522
541
|
| `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
|
|
@@ -534,7 +553,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
534
553
|
| Command | Description |
|
|
535
554
|
|---------|-------------|
|
|
536
555
|
| `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\|
|
|
556
|
+
| `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
557
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
539
558
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
540
559
|
| `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const config = require('../package.json').openyidaLocalBeta;
|
|
6
|
+
// PRE beta 消费包内测试默认值,真实凭据仍由原认证模块管理。
|
|
7
|
+
process.env.OPENYIDA_ENDPOINT ||= config.baseUrl;
|
|
8
|
+
process.env.OPENYIDA_ENV ||= config.environment;
|
|
9
|
+
process.env.OPENYIDA_AUTH_DIR ||= path.join(os.homedir(), '.openyida', config.authDirectory);
|
|
10
|
+
process.env.OPENYIDA_SKIP_UPDATE_CHECK = '1';
|
|
11
|
+
require('./yida.js');
|
package/bin/yida.js
CHANGED
|
@@ -296,6 +296,7 @@ function getFirstPositionalArg(cliArgs, startIndex = 0) {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
const UNSUPPORTED_LEGACY_LOGIN_FLAGS = new Set([
|
|
299
|
+
'--qwenwork',
|
|
299
300
|
'--qr',
|
|
300
301
|
'--qr-code',
|
|
301
302
|
'--agent-qr',
|
|
@@ -594,6 +595,11 @@ async function main() {
|
|
|
594
595
|
});
|
|
595
596
|
assertManagedInvocation(command, rawArgs);
|
|
596
597
|
applyQuietFlag();
|
|
598
|
+
// 一次性票据只在当前进程消费,不能先自更新并重启命令。
|
|
599
|
+
if (command === 'login-qwenwork') {
|
|
600
|
+
await require('../lib/auth/qwenwork-desktop').run(args);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
597
603
|
applyGlobalEnvironmentFlags();
|
|
598
604
|
|
|
599
605
|
const { maybeAutoUpdate } = require('../lib/core/update');
|
|
@@ -714,6 +720,10 @@ async function main() {
|
|
|
714
720
|
|
|
715
721
|
case 'auth': {
|
|
716
722
|
const subCommand = args[0];
|
|
723
|
+
if (subCommand === 'qwenwork') {
|
|
724
|
+
await require('../lib/auth/qwenwork').run(args.slice(1));
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
717
727
|
const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
|
|
718
728
|
const { getAuthStatus } = require('../lib/core/utils');
|
|
719
729
|
const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
|
|
@@ -837,11 +847,6 @@ async function main() {
|
|
|
837
847
|
await run(args);
|
|
838
848
|
break;
|
|
839
849
|
}
|
|
840
|
-
case 'form-message': {
|
|
841
|
-
const { run } = require('../lib/app/form-message');
|
|
842
|
-
await run(args);
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
850
|
case 'print-template': {
|
|
846
851
|
const { run } = require('../lib/print-template/print-template');
|
|
847
852
|
await run(args);
|
|
@@ -10,16 +10,7 @@ const {
|
|
|
10
10
|
findEmojiInValue,
|
|
11
11
|
} = require('../../core/no-emoji-detector');
|
|
12
12
|
|
|
13
|
-
const
|
|
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 = [
|
|
13
|
+
const CANVAS_YIDA_API_BRIDGE_SOURCE = `var OPENYIDA_YIDA_FORM_API_METHODS = [
|
|
23
14
|
'saveFormData',
|
|
24
15
|
'updateFormData',
|
|
25
16
|
'searchFormDataIds',
|
|
@@ -248,15 +239,8 @@ function openyidaCreateRouterBridge(utils) {
|
|
|
248
239
|
|
|
249
240
|
function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
250
241
|
var utils = context && context.utils;
|
|
251
|
-
var navigation = createPageNavigation(window);
|
|
252
|
-
var taskNavigation = createTaskCenterNavigation();
|
|
253
|
-
var taskCounts = createTaskCenterLoader(createTaskCenterContract(), function() { return yidaBridge; });
|
|
254
242
|
var methodNames = openyidaCollectUtilsMethodNames(utils);
|
|
255
243
|
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
|
-
}
|
|
260
244
|
if (!utils || typeof utils[methodName] !== 'function') {
|
|
261
245
|
throw new Error('this.utils.' + methodName + ' is not available');
|
|
262
246
|
}
|
|
@@ -267,8 +251,6 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
267
251
|
apiGroups: {
|
|
268
252
|
utility: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
269
253
|
router: ['push'],
|
|
270
|
-
navigation: ['openPage', 'taskCenterLink', 'openTaskCenter'],
|
|
271
|
-
tasks: ['taskCenterCount'],
|
|
272
254
|
yida: OPENYIDA_KNOWN_YIDA_API_METHODS.slice()
|
|
273
255
|
},
|
|
274
256
|
knownMethods: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
@@ -277,33 +259,9 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
277
259
|
return !utils || typeof utils[methodName] !== 'function';
|
|
278
260
|
}),
|
|
279
261
|
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
|
-
},
|
|
300
262
|
router: openyidaCreateRouterBridge(utils),
|
|
301
263
|
yida: yidaBridge
|
|
302
264
|
};
|
|
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'; });
|
|
307
265
|
methodNames.forEach(function(methodName) {
|
|
308
266
|
if (!bridge[methodName]) {
|
|
309
267
|
bridge[methodName] = function() {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CliError } = require('../core/cli-error');
|
|
4
|
+
const { t } = require('../core/i18n');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
7
|
+
const MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
function failure(code, message, stage, details = {}) {
|
|
10
|
+
return new CliError(t(`qwenwork.${message}`), {
|
|
11
|
+
code: `QWENWORK_${code}`,
|
|
12
|
+
details: { stage, ...details },
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pick(env, names) {
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
const value = String(env[name] || '').trim();
|
|
19
|
+
if (value) {
|
|
20
|
+
return { name, value };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeBaseUrl(value) {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(value);
|
|
29
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
30
|
+
if ((url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) ||
|
|
31
|
+
url.username || url.password || url.search || url.hash) {
|
|
32
|
+
throw new Error('invalid_url');
|
|
33
|
+
}
|
|
34
|
+
return url.href.replace(/\/+$/, '');
|
|
35
|
+
} catch {
|
|
36
|
+
throw failure('INVALID_GATEWAY', 'invalid_url', 'resolve_credentials');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isObject(value) {
|
|
41
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Do not include upstream bodies, headers or exception messages in diagnostics:
|
|
45
|
+
// an authentication endpoint may echo credentials even in an error response.
|
|
46
|
+
async function requestJson(method, url, accessToken, options, stage) {
|
|
47
|
+
const timeoutMs = options.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : options.timeoutMs;
|
|
48
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
49
|
+
throw failure('INVALID_TIMEOUT', 'request_failed', stage);
|
|
50
|
+
}
|
|
51
|
+
const controller = new globalThis.AbortController();
|
|
52
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
const response = await globalThis.fetch(url, {
|
|
55
|
+
method,
|
|
56
|
+
headers: { accept: 'application/json', authorization: `Bearer ${accessToken}` },
|
|
57
|
+
redirect: 'manual',
|
|
58
|
+
signal: controller.signal,
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
if (response.body) {
|
|
62
|
+
await response.body.cancel();
|
|
63
|
+
}
|
|
64
|
+
throw failure('HTTP_ERROR', 'request_failed', stage, { status_code: response.status });
|
|
65
|
+
}
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let size = 0;
|
|
68
|
+
if (response.body) {
|
|
69
|
+
const reader = response.body.getReader();
|
|
70
|
+
let next = await reader.read();
|
|
71
|
+
while (!next.done) {
|
|
72
|
+
const { value } = next;
|
|
73
|
+
size += value.byteLength;
|
|
74
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
75
|
+
await reader.cancel();
|
|
76
|
+
throw failure('RESPONSE_TOO_LARGE', 'invalid_response', stage);
|
|
77
|
+
}
|
|
78
|
+
chunks.push(Buffer.from(value));
|
|
79
|
+
next = await reader.read();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let payload;
|
|
83
|
+
try {
|
|
84
|
+
payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
85
|
+
} catch {
|
|
86
|
+
throw failure('INVALID_RESPONSE', 'invalid_response', stage);
|
|
87
|
+
}
|
|
88
|
+
if (!isObject(payload) || (payload.code !== undefined && payload.code !== 'ok')) {
|
|
89
|
+
throw failure('INVALID_RESPONSE', 'invalid_response', stage);
|
|
90
|
+
}
|
|
91
|
+
return payload;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error && error.isCliError) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
throw controller.signal.aborted
|
|
97
|
+
? failure('TIMEOUT', 'request_timeout', stage)
|
|
98
|
+
: failure('NETWORK_ERROR', 'request_failed', stage);
|
|
99
|
+
} finally {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function decodeClaims(token) {
|
|
105
|
+
try {
|
|
106
|
+
const value = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
|
|
107
|
+
return isObject(value) ? value : {};
|
|
108
|
+
} catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Exchange a cloud Agent credential for an in-memory scoped user credential.
|
|
114
|
+
* This does not issue or persist an OpenYida session.
|
|
115
|
+
*/
|
|
116
|
+
async function exchangeQwenWorkAgentToken(options = {}) {
|
|
117
|
+
const env = options.env || process.env;
|
|
118
|
+
const agent = pick(env, ['QWENWORK_AGENT_TOKEN', 'MULE_AGENT_TOKEN', 'AGENT_TOKEN']);
|
|
119
|
+
if (!agent || /[\r\n]/.test(agent.value)) {
|
|
120
|
+
throw failure('CREDENTIAL_MISSING', 'credential_missing', 'resolve_credentials');
|
|
121
|
+
}
|
|
122
|
+
const gateway = pick(env, ['QWENWORK_GATEWAY_URL', 'MULE_GATEWAY_URL']);
|
|
123
|
+
if (!gateway) {
|
|
124
|
+
throw failure('GATEWAY_MISSING', 'gateway_missing', 'resolve_credentials');
|
|
125
|
+
}
|
|
126
|
+
const baseUrl = normalizeBaseUrl(gateway.value);
|
|
127
|
+
const response = await requestJson('POST', `${baseUrl}/api/agent-gw/token/exchange`, agent.value, options, 'exchange');
|
|
128
|
+
// The current cloud endpoint returns {token, scope, ttl} without an envelope.
|
|
129
|
+
const payload = isObject(response.data) ? response.data : response;
|
|
130
|
+
if (typeof payload.token !== 'string' || !payload.token.trim() || /\s/.test(payload.token) ||
|
|
131
|
+
typeof payload.scope !== 'string' || !Number.isFinite(payload.ttl) || payload.ttl <= 0) {
|
|
132
|
+
throw failure('INVALID_RESPONSE', 'invalid_response', 'exchange');
|
|
133
|
+
}
|
|
134
|
+
const decoded = decodeClaims(payload.token);
|
|
135
|
+
if (typeof decoded.exp === 'number' && decoded.exp * 1000 <= Date.now()) {
|
|
136
|
+
throw failure('TOKEN_EXPIRED', 'expired_token', 'exchange');
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
access_token: payload.token,
|
|
140
|
+
base_url: baseUrl,
|
|
141
|
+
credential_source: agent.name,
|
|
142
|
+
scope: payload.scope,
|
|
143
|
+
expires_in: payload.ttl,
|
|
144
|
+
expires_at: typeof decoded.exp === 'number' ? decoded.exp * 1000 : Date.now() + payload.ttl * 1000,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Query the credential's own origin; never infer a YiDA endpoint or organization. */
|
|
149
|
+
async function getQwenWorkUserInfo(credential, options = {}) {
|
|
150
|
+
const accessToken = credential && credential.access_token;
|
|
151
|
+
if (typeof accessToken !== 'string' || !accessToken || /[\r\n]/.test(accessToken)) {
|
|
152
|
+
throw failure('CREDENTIAL_MISSING', 'credential_missing', 'user_info');
|
|
153
|
+
}
|
|
154
|
+
const baseUrl = normalizeBaseUrl(credential.base_url);
|
|
155
|
+
const response = await requestJson('GET', `${baseUrl}/user/info`, accessToken, options, 'user_info');
|
|
156
|
+
if (response.code !== 'ok' || !isObject(response.data) ||
|
|
157
|
+
typeof response.data.user_id !== 'string' || !response.data.user_id.trim()) {
|
|
158
|
+
throw failure('INVALID_RESPONSE', 'invalid_response', 'user_info');
|
|
159
|
+
}
|
|
160
|
+
const env = options.env || process.env;
|
|
161
|
+
const decoded = decodeClaims(accessToken);
|
|
162
|
+
// Decoding is not local signature verification. The authenticated /user/info
|
|
163
|
+
// response establishes identity; these comparisons catch context drift.
|
|
164
|
+
for (const expected of [env.QWENWORK_USER_ID, env.MULE_USER_ID, decoded.user_id, decoded.sub]) {
|
|
165
|
+
if (expected && String(expected) !== response.data.user_id) {
|
|
166
|
+
throw failure('IDENTITY_MISMATCH', 'identity_mismatch', 'user_info');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const orgId = response.data.biz && response.data.biz.org_id;
|
|
170
|
+
if (orgId && decoded.biz_org_id && orgId !== decoded.biz_org_id) {
|
|
171
|
+
throw failure('IDENTITY_MISMATCH', 'identity_mismatch', 'user_info');
|
|
172
|
+
}
|
|
173
|
+
return response.data;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Safe composition for identity consumers: the returned object has no credential. */
|
|
177
|
+
async function getQwenWorkIdentity(options = {}) {
|
|
178
|
+
const credential = await exchangeQwenWorkAgentToken(options);
|
|
179
|
+
const profile = await getQwenWorkUserInfo(credential, options);
|
|
180
|
+
return {
|
|
181
|
+
base_url: credential.base_url,
|
|
182
|
+
credential_source: credential.credential_source,
|
|
183
|
+
scope: credential.scope,
|
|
184
|
+
expires_in: credential.expires_in,
|
|
185
|
+
expires_at: credential.expires_at,
|
|
186
|
+
profile,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { exchangeQwenWorkAgentToken, getQwenWorkUserInfo, getQwenWorkIdentity };
|
|
@@ -0,0 +1,140 @@
|
|
|
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 { saveTokenSession, loadTokenSession, isEnvTokenAuthMode } = require('./token-store');
|
|
7
|
+
const { readManagedContext } = require('../agent/managed-context');
|
|
8
|
+
|
|
9
|
+
const TICKET_ENV = 'OPENYIDA_QWENWORK_EXCHANGE_TICKET';
|
|
10
|
+
const TICKET_CONTEXT_ENV = 'OPENYIDA_QWENWORK_EXCHANGE_TICKET_CONTEXT';
|
|
11
|
+
const CONSUME_PATH = '/openapi/cli/v1/auth/qwenwork/desktop/consume';
|
|
12
|
+
const MAX_RESPONSE_BYTES = 65536;
|
|
13
|
+
|
|
14
|
+
function fail(code) {
|
|
15
|
+
return new CliError(t('qwenwork.desktop_login_failed'), { code: `QWENWORK_DESKTOP_${code}` });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseTicket(raw, context) {
|
|
19
|
+
try {
|
|
20
|
+
if (typeof raw !== 'string' || !raw || /\s/.test(raw) || raw.length > 32768 || typeof context !== 'string' || context.length > 8192) { throw new Error(); }
|
|
21
|
+
const value = JSON.parse(context);
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
23
|
+
!Number.isSafeInteger(value.expires_at) || value.expires_at <= Date.now() || value.expires_at > Date.now() + 300000 ||
|
|
24
|
+
typeof value.corp_id !== 'string' || !value.corp_id || typeof value.user_id !== 'string' || !value.user_id ||
|
|
25
|
+
typeof value.exchange_id !== 'string' || !value.exchange_id ||
|
|
26
|
+
'access_token' in value || 'refresh_token' in value || 'accessToken' in value || 'refreshToken' in value) { throw new Error(); }
|
|
27
|
+
return { ...value, exchange_ticket: raw };
|
|
28
|
+
} catch {
|
|
29
|
+
throw fail('TICKET_MISSING_OR_INVALID');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function origin(value) {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(value);
|
|
36
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/') { throw new Error(); }
|
|
37
|
+
return url.origin;
|
|
38
|
+
} catch {
|
|
39
|
+
throw fail('ENDPOINT_INVALID');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function requestJson(url, init, options) {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs || 15000);
|
|
46
|
+
try {
|
|
47
|
+
const response = await (options.fetch || globalThis.fetch)(url, { ...init, redirect: 'error', signal: controller.signal });
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
if (response.body) { await response.body.cancel(); }
|
|
50
|
+
throw fail('REQUEST_REJECTED');
|
|
51
|
+
}
|
|
52
|
+
const reader = response.body && response.body.getReader();
|
|
53
|
+
if (!reader) { throw fail('RESPONSE_INVALID'); }
|
|
54
|
+
const chunks = [];
|
|
55
|
+
let bytes = 0;
|
|
56
|
+
try {
|
|
57
|
+
for (let next = await reader.read(); !next.done; next = await reader.read()) {
|
|
58
|
+
bytes += next.value.byteLength;
|
|
59
|
+
if (bytes > MAX_RESPONSE_BYTES) { await reader.cancel(); throw fail('RESPONSE_INVALID'); }
|
|
60
|
+
chunks.push(next.value);
|
|
61
|
+
}
|
|
62
|
+
} finally {
|
|
63
|
+
reader.releaseLock();
|
|
64
|
+
}
|
|
65
|
+
const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
66
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) { throw fail('RESPONSE_INVALID'); }
|
|
67
|
+
return value;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// 服务端和网络异常可能回显凭据,只透传本模块生成的固定错误。
|
|
70
|
+
if (error instanceof CliError && /^QWENWORK_DESKTOP_/.test(error.code)) { throw error; }
|
|
71
|
+
throw fail(controller.signal.aborted ? 'REQUEST_TIMED_OUT' : 'REQUEST_FAILED');
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 专用免登入口:只消费环境中的一次性兑换票,不调用或重启普通 login。 */
|
|
78
|
+
async function loginQwenWork(options = {}) {
|
|
79
|
+
const env = options.env || process.env;
|
|
80
|
+
const raw = env[TICKET_ENV];
|
|
81
|
+
const context = env[TICKET_CONTEXT_ENV];
|
|
82
|
+
delete env[TICKET_ENV];
|
|
83
|
+
delete env[TICKET_CONTEXT_ENV];
|
|
84
|
+
if (isEnvTokenAuthMode(env) || readManagedContext(env)) { throw fail('MANAGED_AUTH_CONFLICT'); }
|
|
85
|
+
const ticket = parseTicket(raw, context);
|
|
86
|
+
const baseUrl = origin(ticket.base_url);
|
|
87
|
+
const expectedEndpoint = options.endpoint || env.OPENYIDA_ENDPOINT;
|
|
88
|
+
if (expectedEndpoint && origin(expectedEndpoint) !== baseUrl) { throw fail('ENDPOINT_MISMATCH'); }
|
|
89
|
+
if (options.corpId && options.corpId !== ticket.corp_id) { throw fail('IDENTITY_MISMATCH'); }
|
|
90
|
+
const startedAt = Date.now();
|
|
91
|
+
const value = await requestJson(`${baseUrl}${CONSUME_PATH}`, {
|
|
92
|
+
method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json', authorization: `Bearer ${ticket.exchange_ticket}` },
|
|
93
|
+
body: '{}',
|
|
94
|
+
}, options);
|
|
95
|
+
const expiresAt = startedAt + Number(value.expires_in) * 1000;
|
|
96
|
+
if (value.status !== 'ok' || value.auth_mode !== 'token' || value.can_auto_use !== true ||
|
|
97
|
+
value.token_type !== 'Bearer' || value.credential_source !== 'qwenwork_desktop' ||
|
|
98
|
+
'refresh_token' in value || 'refreshToken' in value || 'exchange_ticket' in value ||
|
|
99
|
+
typeof value.access_token !== 'string' || !value.access_token || /\s/.test(value.access_token) || value.access_token.length > 32768 ||
|
|
100
|
+
!Number.isInteger(value.expires_in) || value.expires_in < 1 || value.expires_in > 1800 ||
|
|
101
|
+
value.corp_id !== ticket.corp_id || value.user_id !== ticket.user_id || value.exchange_id !== ticket.exchange_id ||
|
|
102
|
+
(value.base_url && origin(value.base_url) !== baseUrl)) { throw fail('CREDENTIAL_INVALID'); }
|
|
103
|
+
const verified = await requestJson(`${baseUrl}/openapi/cli/v1/auth/status`, {
|
|
104
|
+
method: 'GET', headers: { authorization: `Bearer ${value.access_token}`, accept: 'application/json' },
|
|
105
|
+
}, options);
|
|
106
|
+
if (verified.status !== 'ok' || verified.auth_mode !== 'token' || verified.can_auto_use !== true ||
|
|
107
|
+
verified.corp_id !== value.corp_id || verified.user_id !== value.user_id || expiresAt <= Date.now()) { throw fail('IDENTITY_MISMATCH'); }
|
|
108
|
+
const session = saveTokenSession({
|
|
109
|
+
auth_mode: 'token', token_type: 'Bearer', access_token: value.access_token,
|
|
110
|
+
expires_at: expiresAt, base_url: baseUrl, client_id: DINGTALK_OAUTH_CLIENT_ID,
|
|
111
|
+
corp_id: value.corp_id, user_id: value.user_id, credential_source: 'qwenwork_desktop',
|
|
112
|
+
}, options);
|
|
113
|
+
const restored = loadTokenSession(options);
|
|
114
|
+
if (!['user', 'project'].includes(session.persistence_scope) || !restored ||
|
|
115
|
+
restored.access_token !== value.access_token || restored.base_url !== baseUrl ||
|
|
116
|
+
restored.corp_id !== value.corp_id || restored.user_id !== value.user_id) { throw fail('PERSISTENCE_FAILED'); }
|
|
117
|
+
// 身份、环境和存储细节仅用于内部校验,不进入模型可见的登录结果。
|
|
118
|
+
return { ok: true, status: 'ok', can_auto_use: true };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function run(args, options = {}) {
|
|
122
|
+
if (args.includes('--help') || args.includes('-h')) { console.log(t('qwenwork.desktop_login_usage')); return; }
|
|
123
|
+
const parsed = {};
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (let index = 0; index < args.length; index++) {
|
|
126
|
+
const flag = args[index];
|
|
127
|
+
if (seen.has(flag)) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2 }); }
|
|
128
|
+
seen.add(flag);
|
|
129
|
+
if (['--json', '--quiet'].includes(flag)) { continue; }
|
|
130
|
+
const key = flag === '--corp-id' ? 'corpId' : flag === '--endpoint' ? 'endpoint' : null;
|
|
131
|
+
const value = args[++index];
|
|
132
|
+
if (!key || !value || value.startsWith('--')) { throw new CliError(t('qwenwork.desktop_login_usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2 }); }
|
|
133
|
+
parsed[key] = value;
|
|
134
|
+
}
|
|
135
|
+
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)); }
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { TICKET_ENV, TICKET_CONTEXT_ENV, loginQwenWork, run };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CliError } = require('../core/cli-error');
|
|
4
|
+
const { t } = require('../core/i18n');
|
|
5
|
+
const { redactSensitive } = require('../core/redact');
|
|
6
|
+
const { getQwenWorkIdentity } = require('./qwenwork-client');
|
|
7
|
+
|
|
8
|
+
async function run(args, options = {}) {
|
|
9
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
10
|
+
console.log(t('qwenwork.usage'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (args[0] !== 'user-info' || args.slice(1).some(arg => arg !== '--json')) {
|
|
14
|
+
throw new CliError(t('qwenwork.usage'), { code: 'CLI_USAGE_ERROR', exitCode: 2 });
|
|
15
|
+
}
|
|
16
|
+
const identity = await getQwenWorkIdentity(options);
|
|
17
|
+
const { profile, ...metadata } = identity;
|
|
18
|
+
const result = redactSensitive({
|
|
19
|
+
ok: true,
|
|
20
|
+
provider: 'qwenwork',
|
|
21
|
+
...metadata,
|
|
22
|
+
user: {
|
|
23
|
+
user_id: profile.user_id,
|
|
24
|
+
nickname: profile.nickname,
|
|
25
|
+
username: profile.username,
|
|
26
|
+
email: profile.email,
|
|
27
|
+
is_biz: profile.is_biz,
|
|
28
|
+
biz_org_id: profile.biz && profile.biz.org_id,
|
|
29
|
+
biz_org_name: profile.biz && profile.biz.org_name,
|
|
30
|
+
},
|
|
31
|
+
yida_session_created: false,
|
|
32
|
+
});
|
|
33
|
+
console.log(JSON.stringify(result, null, 2));
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { run };
|
package/lib/auth/token-store.js
CHANGED
|
@@ -276,6 +276,7 @@ function normalizeTokenSession(session = {}) {
|
|
|
276
276
|
);
|
|
277
277
|
const normalized = {
|
|
278
278
|
auth_mode: 'token',
|
|
279
|
+
credential_source: session.credential_source,
|
|
279
280
|
token_type: session.token_type || session.tokenType || 'Bearer',
|
|
280
281
|
access_token: session.access_token || session.accessToken,
|
|
281
282
|
refresh_token: session.refresh_token || session.refreshToken,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { version } = require('../../package.json');
|
|
6
|
+
const { version, openyidaLocalBeta: localBeta } = require('../../package.json');
|
|
7
7
|
const { t } = require('./i18n');
|
|
8
8
|
const { buildCommandManifest } = require('./command-manifest');
|
|
9
9
|
const { buildEnvironmentSnapshot } = require('./env');
|
|
@@ -195,7 +195,7 @@ function forbiddenAliasBriefs(manifest) {
|
|
|
195
195
|
}));
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function buildAuthPath(login, env = process.env) {
|
|
198
|
+
function buildAuthPath(login, env = process.env, runtime = null) {
|
|
199
199
|
const authSource = login.auth_source || 'token_session';
|
|
200
200
|
const envTokenSession = authSource === 'env' || login.failure_reason === 'env_token_missing';
|
|
201
201
|
const authPath = {
|
|
@@ -230,10 +230,30 @@ function buildAuthPath(login, env = process.env) {
|
|
|
230
230
|
? 'STOP_AND_REQUEST_ENV_TOKEN'
|
|
231
231
|
: 'RUN_OPENYIDA_LOGIN_IF_USER_APPROVES',
|
|
232
232
|
};
|
|
233
|
+
// 桌面换票仅由当前活跃宿主和发行配置共同启用,不影响其他宿主的认证路径。
|
|
234
|
+
if (localBeta?.desktopTicketLogin && runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_desktop' && !envTokenSession) {
|
|
235
|
+
authPath.auth_runtime = 'qwenwork_desktop_exchange_ticket';
|
|
236
|
+
authPath.interactive_login_allowed = false;
|
|
237
|
+
authPath.missing_token_action = 'RUN_OPENYIDA_LOGIN_QWENWORK';
|
|
238
|
+
for (const key of ['auth_profile', 'corp_id', 'corp_name', 'user_id', 'user_name', 'store', 'persistence_scope', 'warning']) { delete authPath[key]; }
|
|
239
|
+
authPath.next_step = authPath.can_auto_use
|
|
240
|
+
? 'Silently continue the requested business operation. Do not announce login or identity details.'
|
|
241
|
+
: 'Silently run standalone openyida login-qwenwork --json, then continue the requested business operation. Do not narrate login, add shell operators, start OAuth, or edit environment files.';
|
|
242
|
+
authPath.next_step_commands = authPath.can_auto_use ? [] : ['openyida login-qwenwork --json'];
|
|
243
|
+
}
|
|
233
244
|
return authPath;
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
function buildInteractiveLogin(auth, runtime) {
|
|
248
|
+
if (auth.auth_runtime === 'qwenwork_desktop_exchange_ticket' && runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop') {
|
|
249
|
+
return {
|
|
250
|
+
mode: 'desktop_ticket', browser_default: 'not_required', browser_owner: 'none',
|
|
251
|
+
recommended_command: auth.can_auto_use ? null : 'openyida login-qwenwork --json',
|
|
252
|
+
agent_action: auth.can_auto_use ? 'continue_business_command' : 'run_standalone_login_qwenwork_and_wait',
|
|
253
|
+
user_visible: false, success_action: 'continue_business_command_without_message',
|
|
254
|
+
reason: 'qwenwork_desktop_pre_beta', completion_signal: 'process_exit_and_final_json', playwright_required: false,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
237
257
|
const base = {
|
|
238
258
|
suppress_flag: '--no-browser',
|
|
239
259
|
suppress_env: 'OPENYIDA_NO_BROWSER',
|
|
@@ -293,9 +313,9 @@ function buildInteractiveLogin(auth, runtime) {
|
|
|
293
313
|
}
|
|
294
314
|
|
|
295
315
|
function buildBuilderPath(login, projectRoot, manifest, env = process.env, runtimeSnapshot = null) {
|
|
296
|
-
const auth = buildAuthPath(login, env);
|
|
297
|
-
const canTrustSummaryPreflight = auth.can_auto_use === true;
|
|
298
316
|
const runtime = runtimeSnapshot || buildRuntimeSnapshot(projectRoot);
|
|
317
|
+
const auth = buildAuthPath(login, env, runtime);
|
|
318
|
+
const canTrustSummaryPreflight = auth.can_auto_use === true;
|
|
299
319
|
|
|
300
320
|
return {
|
|
301
321
|
schema_version: 1,
|
|
@@ -311,6 +331,13 @@ function buildBuilderPath(login, projectRoot, manifest, env = process.env, runti
|
|
|
311
331
|
auth,
|
|
312
332
|
runtime,
|
|
313
333
|
interactive_login: buildInteractiveLogin(auth, runtime),
|
|
334
|
+
communication: runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop' ? {
|
|
335
|
+
preparation_visibility: 'silent',
|
|
336
|
+
completion_content: 'business_result_and_business_link_only',
|
|
337
|
+
disclose_authentication_identity_environment: false,
|
|
338
|
+
report_preparation_only_when_user_action_is_required: true,
|
|
339
|
+
bundled_skill_path: path.resolve(__dirname, '../../yida-skills'),
|
|
340
|
+
} : undefined,
|
|
314
341
|
environment_check_simplification: {
|
|
315
342
|
contract_role: 'machine_readable_builder_runtime_not_yida_agent_business_logic',
|
|
316
343
|
minimal_probe_commands: [
|
|
@@ -432,6 +459,7 @@ function compactBuilderPath(builderPath) {
|
|
|
432
459
|
auth,
|
|
433
460
|
runtime: builderPath.runtime,
|
|
434
461
|
interactive_login: builderPath.interactive_login,
|
|
462
|
+
communication: builderPath.communication,
|
|
435
463
|
environment_check_simplification: {
|
|
436
464
|
minimal_probe_commands: environment.minimal_probe_commands,
|
|
437
465
|
can_skip_default_exploration_when_summary_ok: environment.can_skip_default_exploration_when_summary_ok,
|
|
@@ -601,7 +629,9 @@ function buildAgentCapabilitiesSummary() {
|
|
|
601
629
|
schema_version: 1,
|
|
602
630
|
name: 'openyida-agent-capabilities-summary',
|
|
603
631
|
version,
|
|
604
|
-
login:
|
|
632
|
+
login: builderPath.auth.auth_runtime === 'qwenwork_desktop_exchange_ticket'
|
|
633
|
+
? { status: loginStatus.status, can_auto_use: loginStatus.can_auto_use === true }
|
|
634
|
+
: compactLogin(loginStatus),
|
|
605
635
|
workdir: projectRoot,
|
|
606
636
|
workdir_exists: !!projectResolution.exists,
|
|
607
637
|
workdir_source: projectResolution.source,
|