openyida 2026.10.30 → 2026.10.31
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/app/app-theme-state.js +80 -0
- package/lib/app/create-app.js +31 -0
- package/lib/app/update-app.js +51 -47
- package/lib/auth/qwenwork-desktop.js +2 -1
- package/lib/auth/qwenwork-login.js +40 -5
- package/lib/core/agent-capabilities.js +48 -10
- package/lib/core/command-manifest.js +7 -3
- package/lib/core/form-edit-permissions.js +210 -0
- package/lib/core/locales/en.js +9 -1
- package/lib/core/locales/zh.js +9 -1
- package/lib/core/query-data.js +12 -1
- package/lib/core/qwenwork-login-guidance.js +4 -1
- package/lib/core/utils.js +15 -10
- package/lib/permission/permission-scope.js +27 -8
- package/lib/permission/save-permission.js +6 -4
- package/package.json +1 -1
- package/yida-skills/SKILL.md +6 -0
- package/yida-skills/references/execution-rules.md +11 -0
- package/yida-skills/references/qwenwork-cloud-login.md +2 -0
- package/yida-skills/references/qwenwork-desktop-login.md +2 -0
- package/yida-skills/references/setup-and-env.md +22 -6
- package/yida-skills/references/user-facing-summary.md +23 -3
- package/yida-skills/skills/yida-app/SKILL.md +6 -0
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +3 -3
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +1 -1
- package/yida-skills/skills/yida-create-app/SKILL.md +7 -1
- package/yida-skills/skills/yida-data-management/SKILL.md +4 -1
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +4 -0
- package/yida-skills/skills/yida-design/references/application-style-library.md +4 -2
- package/yida-skills/skills/yida-design/workflow/output-design.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +1 -1
- package/yida-skills/skills/yida-form-permission/SKILL.md +3 -22
- package/yida-skills/skills/yida-login/SKILL.md +11 -4
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +2 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { httpGet, requestWithAutoLogin } = require('../core/utils');
|
|
4
|
+
const { throwCommandError } = require('../core/command-errors');
|
|
5
|
+
const { t } = require('../core/i18n');
|
|
6
|
+
const { isAgentAppType } = require('./agent-app-type');
|
|
7
|
+
|
|
8
|
+
function themeContextFailure(reason) {
|
|
9
|
+
throwCommandError(t('upgrade_app_theme.context_failed'), {
|
|
10
|
+
code: 'APP_THEME_CONTEXT_UNVERIFIED', details: { reason },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The application API exposes persisted mode/provenance. These establish the
|
|
15
|
+
// configured theme, not proof that a browser loaded the stylesheet.
|
|
16
|
+
function resolveAppThemeState(app = {}) {
|
|
17
|
+
const config = app.config && typeof app.config === 'object' && !Array.isArray(app.config) ? app.config : {};
|
|
18
|
+
const mode = app.appThemeMode ?? config.APP_THEME_MODE;
|
|
19
|
+
const enabled = app.appThemeEnable;
|
|
20
|
+
const createdModern = config.CREATED_WITH_MODERN_THEME;
|
|
21
|
+
if (enabled === 'n' || mode === 'legacy' || createdModern === 'n') {
|
|
22
|
+
return { mode: 'legacy', source: 'server_settings' };
|
|
23
|
+
}
|
|
24
|
+
if (enabled === 'y' && mode === 'modern') {
|
|
25
|
+
return { mode: 'modern', source: 'server_theme_state' };
|
|
26
|
+
}
|
|
27
|
+
if (enabled !== undefined && enabled !== null && enabled !== 'y') {
|
|
28
|
+
return { mode: 'unknown', source: 'server_settings' };
|
|
29
|
+
}
|
|
30
|
+
if (createdModern === 'y' &&
|
|
31
|
+
(mode === 'modern' || ((mode === undefined || mode === null || mode === '') && isAgentAppType(app.agentAppType)))) {
|
|
32
|
+
return { mode: 'modern', source: 'server_creation_config' };
|
|
33
|
+
}
|
|
34
|
+
return { mode: 'unknown', source: 'server_settings' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchCurrentApp(appType, authRef, verifyIdentity = false) {
|
|
38
|
+
const target = { baseUrl: authRef.baseUrl, corpId: authRef.corpId, userId: authRef.userId };
|
|
39
|
+
if (verifyIdentity && (typeof appType !== 'string' || !/^[A-Za-z0-9_-]+$/.test(appType) ||
|
|
40
|
+
typeof target.corpId !== 'string' || !target.corpId || !target.baseUrl)) {
|
|
41
|
+
themeContextFailure('invalid_target');
|
|
42
|
+
}
|
|
43
|
+
const assertIdentity = auth => {
|
|
44
|
+
if (verifyIdentity && Object.keys(target).some(key => auth[key] !== target[key])) {
|
|
45
|
+
themeContextFailure('identity_changed');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const response = await requestWithAutoLogin((auth) => {
|
|
49
|
+
assertIdentity(auth);
|
|
50
|
+
return httpGet(
|
|
51
|
+
auth.baseUrl,
|
|
52
|
+
`/${encodeURIComponent(appType)}/query/app/getAppIncludingAecpInfo.json`,
|
|
53
|
+
{
|
|
54
|
+
_api: 'nattyFetch',
|
|
55
|
+
_mock: 'false',
|
|
56
|
+
appKey: appType,
|
|
57
|
+
_csrf_token: auth.csrfToken,
|
|
58
|
+
_stamp: Date.now(),
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}, authRef);
|
|
62
|
+
assertIdentity(authRef);
|
|
63
|
+
|
|
64
|
+
if (verifyIdentity) {
|
|
65
|
+
const app = response?.content;
|
|
66
|
+
if (response?.success !== true || !app || typeof app !== 'object' || Array.isArray(app)) {
|
|
67
|
+
themeContextFailure('app_query_failed');
|
|
68
|
+
}
|
|
69
|
+
if (app.appType !== appType || app.corpId !== target.corpId) {
|
|
70
|
+
themeContextFailure('app_identity_mismatch');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!response || !response.success) {
|
|
75
|
+
throw new Error(response ? response.errorMsg || '查询应用详情失败' : '查询应用详情失败');
|
|
76
|
+
}
|
|
77
|
+
return response.content || {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { fetchCurrentApp, resolveAppThemeState };
|
package/lib/app/create-app.js
CHANGED
|
@@ -22,6 +22,7 @@ const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff
|
|
|
22
22
|
const { throwCommandError } = require('../core/command-errors');
|
|
23
23
|
const { isPermissionGuidanceError } = require('../core/permission-guidance');
|
|
24
24
|
const { resolveBuilderAiSource } = require('./builder-ai-source');
|
|
25
|
+
const { fetchCurrentApp, resolveAppThemeState } = require('./app-theme-state');
|
|
25
26
|
|
|
26
27
|
const DEFAULT_APP_OPTIONS = {
|
|
27
28
|
iconColor: '#0089FF',
|
|
@@ -376,6 +377,35 @@ async function run(args) {
|
|
|
376
377
|
const appUrl = entryUrls.appUrl;
|
|
377
378
|
const corpId = authRef.corpId || (authRef.authData && authRef.authData.corp_id) || '';
|
|
378
379
|
|
|
380
|
+
let themeVerification;
|
|
381
|
+
try {
|
|
382
|
+
const app = await fetchCurrentApp(appType, authRef, true);
|
|
383
|
+
const theme = resolveAppThemeState(app);
|
|
384
|
+
if (theme.mode !== 'modern') {
|
|
385
|
+
throwCommandError(t('create_app.modern_theme_unverified'), {
|
|
386
|
+
code: 'APP_THEME_NOT_MODERN', details: { themeMode: theme.mode },
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
themeVerification = { verified: true, appThemeMode: 'modern', source: theme.source,
|
|
390
|
+
scope: 'configuration_readback', runtimeVerified: false };
|
|
391
|
+
} catch (error) {
|
|
392
|
+
const created = { appCreated: true, appType, corpId, url: appUrl, retryCreate: false,
|
|
393
|
+
partial: true, residual: { appType, corpId }, retrySafe: false, mutationPerformed: true,
|
|
394
|
+
sideEffectState: 'applied', readbackAllowed: true };
|
|
395
|
+
if (isPermissionGuidanceError(error)) {
|
|
396
|
+
error.details = { ...error.details, ...created };
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
throwCommandError(t('create_app.modern_theme_unverified'), {
|
|
400
|
+
code: 'APP_CREATED_THEME_UNVERIFIED', details: {
|
|
401
|
+
...created,
|
|
402
|
+
causeCode: error.code || 'APP_THEME_READ_FAILED',
|
|
403
|
+
...(error.details?.themeMode ? { themeMode: error.details.themeMode } : {}),
|
|
404
|
+
...(error.details?.reason ? { reason: error.details.reason } : {}),
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
379
409
|
chalkResult(true, t('create_app.success'), [
|
|
380
410
|
['appType', appType],
|
|
381
411
|
['Corp ID', corpId || t('common.unknown_error')],
|
|
@@ -391,6 +421,7 @@ async function run(args) {
|
|
|
391
421
|
appType,
|
|
392
422
|
appName,
|
|
393
423
|
corpId,
|
|
424
|
+
themeVerification,
|
|
394
425
|
url: appUrl,
|
|
395
426
|
...entryUrls,
|
|
396
427
|
},
|
package/lib/app/update-app.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
const querystring = require('querystring');
|
|
10
|
+
const { fetchCurrentApp, resolveAppThemeState } = require('./app-theme-state');
|
|
10
11
|
const {
|
|
11
|
-
httpGet,
|
|
12
12
|
httpPost,
|
|
13
13
|
requestWithAutoLogin,
|
|
14
14
|
} = require('../core/utils');
|
|
@@ -376,35 +376,24 @@ function preserveBasicSettings(payload, app) {
|
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
function requireAppStyleConfirmation(params,
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
function requireAppStyleConfirmation(params, app) {
|
|
380
|
+
const theme = resolveAppThemeState(app);
|
|
381
|
+
if (theme.mode !== 'modern' && params.confirmLegacyAppStyle !== true) {
|
|
382
|
+
const storedMode = pickAppField(app, 'appThemeMode', 'APP_THEME_MODE');
|
|
383
383
|
throwCommandError(t('update_app.legacy_style_confirm_required'), {
|
|
384
384
|
code: 'LEGACY_APP_CUSTOM_THEME_CONFIRMATION_REQUIRED',
|
|
385
|
-
details: { appType: params.appType, applicationSettingsWritten: false,
|
|
385
|
+
details: { appType: params.appType, applicationSettingsWritten: false,
|
|
386
|
+
themeCapability: theme.mode, storedThemeMode: typeof storedMode === 'string' ? storedMode : null,
|
|
387
|
+
nextAction: 'ask_user_for_application_style_injection' },
|
|
386
388
|
});
|
|
387
389
|
}
|
|
388
390
|
}
|
|
389
391
|
|
|
390
|
-
// Runtime capability is resolved by the server. Stored APP_THEME_MODE alone is insufficient.
|
|
391
392
|
async function checkAppStyleInjection(params, authRef) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
const identity = parseThemeContext(html, ['appType', 'corpId']);
|
|
397
|
-
if (identity.appType !== params.appType || !authRef.corpId || identity.corpId !== authRef.corpId) {
|
|
398
|
-
throwCommandError(t('upgrade_app_theme.context_failed'), { code: 'APP_THEME_CONTEXT_UNVERIFIED' });
|
|
399
|
-
}
|
|
400
|
-
let context;
|
|
401
|
-
try {
|
|
402
|
-
context = parseThemeContext(html, ['appType', 'corpId', 'appThemeEnable', 'appThemeMode']);
|
|
403
|
-
} catch (error) {
|
|
404
|
-
if (error.code !== 'APP_THEME_CONTEXT_UNVERIFIED') {throw error;}
|
|
405
|
-
return requireAppStyleConfirmation(params, {});
|
|
406
|
-
}
|
|
407
|
-
requireAppStyleConfirmation(params, context);
|
|
393
|
+
// AppRpc#getAppIncludingAecpInfo returns the application's own identity.
|
|
394
|
+
// Never open /admin or follow a browser login redirect to validate an API write.
|
|
395
|
+
const app = await fetchCurrentApp(params.appType, authRef, true);
|
|
396
|
+
requireAppStyleConfirmation(params, app);
|
|
408
397
|
}
|
|
409
398
|
|
|
410
399
|
function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
@@ -520,25 +509,6 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
520
509
|
return postDataObj;
|
|
521
510
|
}
|
|
522
511
|
|
|
523
|
-
async function fetchCurrentApp(appType, authRef) {
|
|
524
|
-
const response = await requestWithAutoLogin((auth) => httpGet(
|
|
525
|
-
auth.baseUrl,
|
|
526
|
-
`/${appType}/query/app/getAppIncludingAecpInfo.json`,
|
|
527
|
-
{
|
|
528
|
-
_api: 'nattyFetch',
|
|
529
|
-
_mock: 'false',
|
|
530
|
-
appKey: appType,
|
|
531
|
-
_csrf_token: auth.csrfToken,
|
|
532
|
-
_stamp: Date.now(),
|
|
533
|
-
}
|
|
534
|
-
), authRef);
|
|
535
|
-
|
|
536
|
-
if (!response || !response.success) {
|
|
537
|
-
throw new Error(response ? response.errorMsg || '查询应用详情失败' : '查询应用详情失败');
|
|
538
|
-
}
|
|
539
|
-
return response.content || {};
|
|
540
|
-
}
|
|
541
|
-
|
|
542
512
|
function getThemeSettings(app) {
|
|
543
513
|
const rawStyle = pickAppField(app, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
|
|
544
514
|
let customThemeStyle = rawStyle || null;
|
|
@@ -573,6 +543,32 @@ function themeReadbackEvidence(params, details) {
|
|
|
573
543
|
return { ...details, scope: 'configuration_readback', operation, runtimeVerified: false };
|
|
574
544
|
}
|
|
575
545
|
|
|
546
|
+
/** Configuration completion is separate from rendering. */
|
|
547
|
+
function getThemeCompletion(params, currentApp, app, themeVerified, navigationVerified) {
|
|
548
|
+
const before = resolveAppThemeState(currentApp);
|
|
549
|
+
const theme = resolveAppThemeState(app);
|
|
550
|
+
const operation = params.customThemeStyle ? 'application_css'
|
|
551
|
+
: params.disableCustomTheme ? 'disable'
|
|
552
|
+
: params.colour && params.colour !== 'custom' ? 'preset' : 'partial_update';
|
|
553
|
+
const actual = getThemeSettings(app);
|
|
554
|
+
const checks = {
|
|
555
|
+
themeModeVerified: theme.mode !== 'unknown' && theme.mode === before.mode,
|
|
556
|
+
settingsVerified: themeVerified === true,
|
|
557
|
+
navigationVerified: navigationVerified === true,
|
|
558
|
+
};
|
|
559
|
+
if (operation === 'application_css') {
|
|
560
|
+
checks.resourceBound = actual.colour === 'custom' && !!normalizeCssColorToHex(actual.themeColor) &&
|
|
561
|
+
actual.customThemeStyle?.enabled === true &&
|
|
562
|
+
typeof actual.customThemeStyle?.cssUrl === 'string' && !!actual.customThemeStyle.cssUrl.trim();
|
|
563
|
+
// Legacy overlays require scope approval.
|
|
564
|
+
if (theme.mode === 'legacy') {checks.legacyScopeConfirmed = params.confirmLegacyAppStyle === true;}
|
|
565
|
+
}
|
|
566
|
+
const complete = operation !== 'partial_update' && Object.values(checks).every(value => value === true);
|
|
567
|
+
return { scope: 'configuration_readback', operation, mode: theme.mode, modeSource: theme.source,
|
|
568
|
+
complete, status: complete ? 'complete' : operation === 'partial_update' ? 'partial' : 'incomplete',
|
|
569
|
+
checks, missing: Object.keys(checks).filter(key => !checks[key]), runtimeVerified: false };
|
|
570
|
+
}
|
|
571
|
+
|
|
576
572
|
function noThemeUpload(appType) {
|
|
577
573
|
return { requested: false, status: 'not_requested', uploadedThisRun: false, appType };
|
|
578
574
|
}
|
|
@@ -633,6 +629,7 @@ async function saveAppSettings(params, authRef) {
|
|
|
633
629
|
const navigationFields = Object.keys(expectedNavigation);
|
|
634
630
|
if (!verifyTheme && !navigationFields.length) {return response;}
|
|
635
631
|
let actual;
|
|
632
|
+
let actualApp = {};
|
|
636
633
|
let actualNavigation;
|
|
637
634
|
let readbackError;
|
|
638
635
|
let themeVerified = !verifyTheme;
|
|
@@ -642,13 +639,15 @@ async function saveAppSettings(params, authRef) {
|
|
|
642
639
|
if (attempt) { await new Promise((resolve) => setTimeout(resolve, attempt * 250)); }
|
|
643
640
|
try {
|
|
644
641
|
const app = await fetchCurrentApp(params.appType, authRef);
|
|
642
|
+
actualApp = app;
|
|
645
643
|
actual = getThemeSettings(app);
|
|
646
644
|
actualNavigation = getNavigationSettings(app, navigationFields, currentApp, params.disableCustomTheme);
|
|
647
645
|
readbackError = undefined;
|
|
648
646
|
themeVerified = !verifyTheme || themeSettingsMatch(expected, actual);
|
|
649
647
|
navigationVerified = navigationFields.every((name) => expectedNavigation[name] === actualNavigation[name]);
|
|
650
|
-
|
|
651
|
-
|
|
648
|
+
const themeCompletion = getThemeCompletion(params, currentApp, app, themeVerified, navigationVerified);
|
|
649
|
+
if (themeVerified && navigationVerified && (!params.customThemeStyle || themeCompletion.complete)) {
|
|
650
|
+
return { ...response, themeCompletion,
|
|
652
651
|
...(submittedFields ? { submittedFields } : {}),
|
|
653
652
|
...(verifyTheme ? { themeVerification: themeReadbackEvidence(params, { verified: true, ...actual }) } : {}),
|
|
654
653
|
...(navigationFields.length ? { navigationVerification: { verified: true, ...actualNavigation } } : {}),
|
|
@@ -662,9 +661,12 @@ async function saveAppSettings(params, authRef) {
|
|
|
662
661
|
}
|
|
663
662
|
return {
|
|
664
663
|
success: false,
|
|
664
|
+
themeCompletion: getThemeCompletion(params, currentApp, actualApp, themeVerified, navigationVerified),
|
|
665
665
|
...(submittedFields ? { submittedFields } : {}),
|
|
666
|
-
errorCode: themeVerified ? '
|
|
667
|
-
|
|
666
|
+
errorCode: !themeVerified ? 'APP_THEME_NOT_PERSISTED' : !navigationVerified
|
|
667
|
+
? 'APP_NAVIGATION_NOT_PERSISTED' : 'APP_THEME_CONFIGURATION_INCOMPLETE',
|
|
668
|
+
errorMsg: t(!themeVerified ? 'update_app.theme_not_persisted' : !navigationVerified
|
|
669
|
+
? 'update_app.navigation_not_persisted' : 'update_app.theme_configuration_incomplete'),
|
|
668
670
|
...(verifyTheme ? { themeVerification: themeReadbackEvidence(params, themeVerified
|
|
669
671
|
? { verified: true, ...actual } : { verified: false, expected, actual, readbackError }) } : {}),
|
|
670
672
|
...(navigationFields.length ? { navigationVerification: navigationVerified
|
|
@@ -813,6 +815,7 @@ async function run(args) {
|
|
|
813
815
|
success: true,
|
|
814
816
|
appType: params.appType,
|
|
815
817
|
themeVerification: response.themeVerification,
|
|
818
|
+
themeCompletion: response.themeCompletion,
|
|
816
819
|
themeUpload,
|
|
817
820
|
nameVerification,
|
|
818
821
|
navigationVerification: response.navigationVerification,
|
|
@@ -838,11 +841,12 @@ async function run(args) {
|
|
|
838
841
|
themeUpload,
|
|
839
842
|
runtimeVerification: { verified: false, status: 'not_checked' },
|
|
840
843
|
submittedFields: response && response.submittedFields,
|
|
844
|
+
themeCompletion: response && response.themeCompletion,
|
|
841
845
|
errorCode: response && response.errorCode, themeVerification: response && response.themeVerification,
|
|
842
846
|
navigationVerification: response && response.navigationVerification }));
|
|
843
847
|
throwCommandError(errorMsg, { code: response && response.errorCode,
|
|
844
848
|
details: response && { ...(response.errorCode === 'APP_NAVIGATION_NOT_PERSISTED' ? response.navigationVerification : response.themeVerification),
|
|
845
|
-
...themeFailureEvidence(themeUpload) } });
|
|
849
|
+
themeCompletion: response.themeCompletion, ...themeFailureEvidence(themeUpload) } });
|
|
846
850
|
}
|
|
847
851
|
}
|
|
848
852
|
|
|
@@ -17,7 +17,8 @@ const MAX_RESPONSE_BYTES = 65536;
|
|
|
17
17
|
function fail(code) {
|
|
18
18
|
return new CliError(t('qwenwork.desktop_login_failed'), {
|
|
19
19
|
code: `QWENWORK_DESKTOP_${code}`,
|
|
20
|
-
details: {
|
|
20
|
+
details: { stage: 'authentication', owner: 'openyida', retryable: false,
|
|
21
|
+
nextAction: 'retry_after_host_state_changes', output_visibility: 'internal', invocation: buildQwenWorkLoginInvocation() },
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -9,7 +9,13 @@ const { exchangeQwenWorkCloudSession } = require('./qwenwork-cloud');
|
|
|
9
9
|
const { tokenLogin, tokenRefresh, isRefreshAuthRequired } = require('./token-auth');
|
|
10
10
|
|
|
11
11
|
function failure(code) {
|
|
12
|
-
return new CliError(t('qwenwork.login_check_failed'), { code
|
|
12
|
+
return new CliError(t('qwenwork.login_check_failed'), { code, details: {
|
|
13
|
+
stage: 'authentication', owner: 'openyida',
|
|
14
|
+
retryable: false,
|
|
15
|
+
nextAction: code === 'QWENWORK_LOGIN_IDENTITY_MISMATCH'
|
|
16
|
+
? 'select_target_organization_in_host_then_retry' : 'retry_after_host_state_changes',
|
|
17
|
+
output_visibility: 'internal',
|
|
18
|
+
} });
|
|
13
19
|
}
|
|
14
20
|
function summary(ok, session = {}) { return { ok, status: ok ? 'ok' : 'not_logged_in', can_auto_use: ok, ...(ok ? sessionIdentity(session) : {}) }; }
|
|
15
21
|
|
|
@@ -72,7 +78,8 @@ async function verify(session, options = {}) {
|
|
|
72
78
|
throw failure('QWENWORK_LOGIN_CHECK_FAILED');
|
|
73
79
|
}
|
|
74
80
|
if (value.corp_id !== session.corp_id || value.user_id !== session.user_id ||
|
|
75
|
-
(options.corpId && value.corp_id !== options.corpId)
|
|
81
|
+
(options.corpId && value.corp_id !== options.corpId) ||
|
|
82
|
+
(options.userId && value.user_id !== options.userId)) { throw failure('QWENWORK_LOGIN_IDENTITY_MISMATCH'); }
|
|
76
83
|
return true;
|
|
77
84
|
} catch (error) {
|
|
78
85
|
if (error instanceof CliError) { throw error; }
|
|
@@ -81,7 +88,7 @@ async function verify(session, options = {}) {
|
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/** 网页态优先消费宿主凭据,认证失败回退普通 OAuth;其他宿主保持原流程。 */
|
|
84
|
-
async function
|
|
91
|
+
async function performQwenWorkLogin(options = {}, dependencies = {}) {
|
|
85
92
|
const env = options.env || process.env;
|
|
86
93
|
const runtime = options.runtime || detectRuntimeCapabilities({ env });
|
|
87
94
|
if (isQwenWorkWebRuntime(runtime) && !isEnvTokenAuthMode(env) && !hasEnvTokenCredential(env)) {
|
|
@@ -110,12 +117,26 @@ async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
|
|
|
110
117
|
return summary(true, session);
|
|
111
118
|
}
|
|
112
119
|
const resolution = resolveTokenSession(options);
|
|
113
|
-
if (resolution.status === 'profile_required') {
|
|
120
|
+
if (resolution.status === 'profile_required') {
|
|
121
|
+
// 受管桌面的身份由客户端绑定;历史 profile 歧义不能让状态检查跳过宿主签票。
|
|
122
|
+
// 只返回未就绪,不选择凭据、发起 OAuth 或放宽显式身份约束。
|
|
123
|
+
const hostCheck = options.checkOnly && runtime.tool === 'qwenwork' && runtime.subtype === 'qwenwork_desktop' &&
|
|
124
|
+
/^[a-f0-9]{64}$/.test(env.QWENWORK_CLIENT_OPENYIDA_IDENTITY || '') &&
|
|
125
|
+
![options.authProfile, options.profile, options.corpId, options.userId,
|
|
126
|
+
env.OPENYIDA_AUTH_PROFILE, env.OPENYIDA_AUTH_CORP_ID, env.OPENYIDA_AUTH_USER_ID].some(Boolean);
|
|
127
|
+
if (hostCheck) { return summary(false); }
|
|
128
|
+
throw failure('AUTH_PROFILE_REQUIRED');
|
|
129
|
+
}
|
|
130
|
+
if (runtime.tool === 'qwenwork' && resolution.status === 'profile_not_found' &&
|
|
131
|
+
(options.corpId || options.userId || env.OPENYIDA_AUTH_CORP_ID || env.OPENYIDA_AUTH_USER_ID)) {
|
|
132
|
+
throw failure('QWENWORK_LOGIN_IDENTITY_MISMATCH');
|
|
133
|
+
}
|
|
114
134
|
let session = loadTokenSession(options);
|
|
115
135
|
if (session && options.endpoint && new URL(session.base_url).origin !== new URL(options.endpoint).origin) {
|
|
116
136
|
session = null;
|
|
117
137
|
}
|
|
118
138
|
if (await verify(session, options)) { return summary(true, session); }
|
|
139
|
+
if (options.checkOnly) { return summary(false); }
|
|
119
140
|
if (session?.refresh_token) {
|
|
120
141
|
try {
|
|
121
142
|
const refreshed = await (dependencies.refresh || tokenRefresh)(options);
|
|
@@ -129,7 +150,6 @@ async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
|
|
|
129
150
|
}
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
|
-
if (options.checkOnly) { return summary(false); }
|
|
133
153
|
if (isEnvTokenAuthMode(env) || isQwenWorkWebRuntime(runtime)) { throw failure('QWENWORK_LOGIN_FAILED'); }
|
|
134
154
|
const result = await (dependencies.login || tokenLogin)(options);
|
|
135
155
|
if (!result || result.ok === false || !result.access_token || !await verify(result, options)) {
|
|
@@ -138,6 +158,21 @@ async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
|
|
|
138
158
|
return summary(true, result);
|
|
139
159
|
}
|
|
140
160
|
|
|
161
|
+
async function ensureQwenWorkLogin(options = {}, dependencies = {}) {
|
|
162
|
+
try {
|
|
163
|
+
const result = await performQwenWorkLogin(options, dependencies);
|
|
164
|
+
return result.ok && (options.corpId || options.userId) ? { ...result, target_match: 'matched' } : result;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const terminal = failure(error instanceof CliError ? error.code : 'QWENWORK_LOGIN_FAILED');
|
|
167
|
+
if (error instanceof CliError) {
|
|
168
|
+
error.details = { ...terminal.details, ...error.details,
|
|
169
|
+
retryable: false, nextAction: terminal.details.nextAction, output_visibility: 'internal' };
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
throw terminal;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
141
176
|
async function run(args, options = {}) {
|
|
142
177
|
const result = await ensureQwenWorkLogin({ ...options, checkOnly: args.includes('--check-only') });
|
|
143
178
|
if (args.includes('--json') && !args.includes('--quiet') && process.env.YIDA_QUIET !== '1') {
|
|
@@ -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
|
|
6
|
+
const { version } = require('../../package.json');
|
|
7
7
|
const { t } = require('./i18n');
|
|
8
8
|
const { buildCommandManifest, buildEntryDeliveryPolicy } = require('./command-manifest');
|
|
9
9
|
const { buildEnvironmentSnapshot } = require('./env');
|
|
@@ -231,25 +231,26 @@ 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 (runtime?.tool === 'qwenwork') {
|
|
235
|
+
for (const key of ['auth_profile', 'corp_id', 'corp_name', 'user_id', 'user_name', 'store', 'persistence_scope', 'warning']) { delete authPath[key]; }
|
|
236
|
+
}
|
|
234
237
|
if (isQwenWorkWebRuntime(runtime) && !envTokenSession) {
|
|
235
238
|
const invocation = buildQwenWorkLoginInvocation({ subtype: 'qwenwork_web' });
|
|
236
239
|
authPath.auth_runtime = 'qwenwork_cloud_exchange';
|
|
237
240
|
authPath.interactive_login_allowed = true;
|
|
238
241
|
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
242
|
authPath.next_step = authPath.can_auto_use
|
|
241
243
|
? 'Silently continue the requested business operation and report its business result.'
|
|
242
244
|
: 'Run the recommended login command and wait. The CLI first tries cloud credentials, then uses normal OAuth if authentication fails. If it prints an authorization URL, follow oauth_fallback browser guidance without starting another login process. Never inspect or display credentials.';
|
|
243
245
|
authPath.next_step_commands = authPath.can_auto_use ? [] : [invocation.command];
|
|
244
246
|
return authPath;
|
|
245
247
|
}
|
|
246
|
-
//
|
|
247
|
-
if (
|
|
248
|
+
// 公开入口已随正式包提供;宿主处理票据,模型只执行能力快照的命令。
|
|
249
|
+
if (runtime?.tool === 'qwenwork' && runtime?.subtype === 'qwenwork_desktop' && !envTokenSession) {
|
|
248
250
|
const invocation = buildQwenWorkLoginInvocation();
|
|
249
251
|
authPath.auth_runtime = 'qwenwork_desktop_exchange_ticket';
|
|
250
252
|
authPath.interactive_login_allowed = true;
|
|
251
253
|
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
254
|
authPath.next_step = authPath.can_auto_use
|
|
254
255
|
? 'Silently continue the requested business operation and report its business result.'
|
|
255
256
|
: `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.`;
|
|
@@ -277,7 +278,7 @@ function buildInteractiveLogin(auth, runtime) {
|
|
|
277
278
|
invocation: buildQwenWorkLoginInvocation(),
|
|
278
279
|
agent_action: auth.can_auto_use ? 'continue_business_command' : 'run_login_qwenwork_and_wait',
|
|
279
280
|
user_visible: false, success_action: 'continue_business_command_without_message',
|
|
280
|
-
reason: '
|
|
281
|
+
reason: 'qwenwork_desktop_host', completion_signal: 'process_exit_and_final_json', playwright_required: false,
|
|
281
282
|
};
|
|
282
283
|
}
|
|
283
284
|
const base = {
|
|
@@ -358,12 +359,44 @@ function buildBuilderPath(login, projectRoot, manifest, env = process.env, runti
|
|
|
358
359
|
runtime,
|
|
359
360
|
browser_verification: require('./browser-handoff').browserVerificationPolicy(runtime, env),
|
|
360
361
|
interactive_login: buildInteractiveLogin(auth, runtime),
|
|
361
|
-
communication:
|
|
362
|
+
communication: {
|
|
362
363
|
preparation_visibility: 'silent',
|
|
364
|
+
authentication_narration: 'forbidden_in_messages_progress_todos_cards_and_tool_titles',
|
|
365
|
+
readiness_announcement: 'never_announce_environment_login_or_organization_readiness',
|
|
366
|
+
preparation_success_action: 'continue_business_operation_without_transition_message',
|
|
367
|
+
tool_title_content: 'current_business_stage_only_preserve_raw_command_in_execution_record',
|
|
368
|
+
app_build_task: {
|
|
369
|
+
title_template: '在{organization_name}组织下搭建宜搭应用',
|
|
370
|
+
applies_to: 'controllable_task_title_and_start_message_conversation_title_when_host_supports_update',
|
|
371
|
+
organization_source: 'verified_current_host_organization_display_name_never_environment_label_domain_id_or_history',
|
|
372
|
+
unknown_organization_title: '在当前组织下搭建宜搭应用',
|
|
373
|
+
update_title_after_organization_is_verified: true,
|
|
374
|
+
append_authentication_or_preparation_status: false,
|
|
375
|
+
},
|
|
376
|
+
app_creation_notice: {
|
|
377
|
+
template: '当前在{organization_name}组织下为您创建{app_name}应用',
|
|
378
|
+
when: 'once_immediately_before_creating_new_app_not_preparation_retries_or_resume',
|
|
379
|
+
organization_source: 'verified_current_host_organization_display_name_never_environment_label_domain_id_or_history',
|
|
380
|
+
app_name_source: 'confirmed_app_name',
|
|
381
|
+
unknown_organization: '当前组织',
|
|
382
|
+
append_authentication_or_preparation_status: false,
|
|
383
|
+
},
|
|
363
384
|
completion_content: 'business_result_and_business_link_only',
|
|
364
385
|
disclose_authentication_identity_environment: false,
|
|
365
|
-
|
|
386
|
+
disclose_implementation_details: false,
|
|
387
|
+
progress_content: 'business_stage_only_never_authentication_or_internal_repair',
|
|
388
|
+
user_action_content: 'minimal_action_and_business_impact_without_technical_explanation',
|
|
389
|
+
report_preparation_only_when_user_action_is_required: false,
|
|
366
390
|
bundled_skill_path: path.resolve(__dirname, '../../yida-skills'),
|
|
391
|
+
},
|
|
392
|
+
host_context: runtime.tool === 'qwenwork' ? {
|
|
393
|
+
default_organization: 'current_host_organization',
|
|
394
|
+
ask_organization_by_default: false,
|
|
395
|
+
explicit_target_policy: 'preserve_user_target_and_stop_if_unmatched',
|
|
396
|
+
organization_switch: 'host_supported_flow_only_never_copy_profiles_or_rewrite_environment',
|
|
397
|
+
recovery: auth.auth_runtime === 'env_token_bootstrap'
|
|
398
|
+
? { max_attempts: 0, failure_next_step: 'stop_and_request_host_preparation' }
|
|
399
|
+
: buildQwenWorkLoginInvocation({ subtype: runtime.subtype }),
|
|
367
400
|
} : undefined,
|
|
368
401
|
environment_check_simplification: {
|
|
369
402
|
contract_role: 'machine_readable_builder_runtime_not_yida_agent_business_logic',
|
|
@@ -488,6 +521,7 @@ function compactBuilderPath(builderPath) {
|
|
|
488
521
|
browser_verification: builderPath.browser_verification,
|
|
489
522
|
interactive_login: builderPath.interactive_login,
|
|
490
523
|
communication: builderPath.communication,
|
|
524
|
+
host_context: builderPath.host_context,
|
|
491
525
|
environment_check_simplification: {
|
|
492
526
|
minimal_probe_commands: environment.minimal_probe_commands,
|
|
493
527
|
can_skip_default_exploration_when_summary_ok: environment.can_skip_default_exploration_when_summary_ok,
|
|
@@ -657,8 +691,12 @@ function buildAgentCapabilitiesSummary() {
|
|
|
657
691
|
schema_version: 1,
|
|
658
692
|
name: 'openyida-agent-capabilities-summary',
|
|
659
693
|
version,
|
|
660
|
-
login:
|
|
661
|
-
? { status: loginStatus.status, can_auto_use: loginStatus.can_auto_use === true
|
|
694
|
+
login: runtime.tool === 'qwenwork'
|
|
695
|
+
? { status: loginStatus.status, can_auto_use: loginStatus.can_auto_use === true,
|
|
696
|
+
...(builderPath.auth.auth_runtime === 'env_token_bootstrap' ? {
|
|
697
|
+
auth_mode: loginStatus.auth_mode, auth_source: loginStatus.auth_source,
|
|
698
|
+
failure_reason: loginStatus.failure_reason,
|
|
699
|
+
} : {}) }
|
|
662
700
|
: compactLogin(loginStatus),
|
|
663
701
|
workdir: projectRoot,
|
|
664
702
|
workdir_exists: !!projectResolution.exists,
|
|
@@ -1344,7 +1344,7 @@ const COMMAND_GROUPS = [
|
|
|
1344
1344
|
output: 'json',
|
|
1345
1345
|
}),
|
|
1346
1346
|
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' },
|
|
1347
|
-
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.'],
|
|
1347
|
+
notes: ['Creation always requests createWithModernTheme=y and verifies the new app identity and modern configuration through getAppIncludingAecpInfo.json before reporting success. APP_CREATED_THEME_UNVERIFIED preserves details.appCreated/appType/retryCreate=false; inspect that app and never recreate or silently downgrade it. themeVerification is configuration readback, not browser rendering proof.', '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.'],
|
|
1348
1348
|
}),
|
|
1349
1349
|
command('design-plan.catalog', ['design-plan', 'catalog'], 'design-plan catalog [--json]', 'help.cmd_design_plan_catalog', { managedRun: { scope: 'local' },
|
|
1350
1350
|
requiresLogin: false, output: 'json',
|
|
@@ -1419,12 +1419,13 @@ const COMMAND_GROUPS = [
|
|
|
1419
1419
|
{ name: 'showAppNav', type: 'boolean', source: 'option', builder_options: ['--show-app-nav'] },
|
|
1420
1420
|
],
|
|
1421
1421
|
notes: [
|
|
1422
|
-
'Before uploading or binding CSS,
|
|
1422
|
+
'Before uploading or binding CSS, verify app/corp identity through getAppIncludingAecpInfo.json, never an admin HTML route. Recognize modern when server appThemeEnable=y and appThemeMode=modern, or when CREATED_WITH_MODERN_THEME=y with explicit modern mode or an unset mode and server-derived AI provenance (including qwenwork). Explicit disabled/legacy facts take precedence. A mode name or host alone is insufficient; missing evidence remains unknown. Configuration readback never proves browser CSS rendering. Legacy or unknown capability requires asking the human about this exact application and reviewed stylesheet scope before --confirm-legacy-app-style; unavailable confirmation means stop before uploading. Prefer page-scoped styles for legacy applications. --disable-custom-theme retains the existing resource URL/name and sets enabled/iframePropagation to boolean false. Never submit an empty style or remove the resource URL. Inspect strict configuration readback. Checking that link#yida-app-custom-theme is absent and CSS is not requested follows builder_path.browser_verification; HTTP success alone is insufficient.',
|
|
1423
1423
|
'Upload application CSS with --theme-file; the primary color is read from CSS and saved with the theme resource. Edit navigation radius, three border states and selected shadow in design.md, regenerate CSS, then upload; these tokens are not separate update-app flags. Unknown options, missing values and combined --hide-app-nav/--show-app-nav are rejected.',
|
|
1424
1424
|
'themeUpload records this invocation: target app/environment, source name/bytes/SHA-256, themeColor and resource URL. resourceContentVerified=false means no remote byte comparison. Without --theme-file it is not_requested/uploadedThisRun=false. Unknown upload or failed binding needs readback before another upload; accepted uploads retain their receipt.',
|
|
1425
1425
|
'One design document and theme CSS cover navigation, application shell, native forms, custom pages and record details. tokens.application-global.appearance.navigation defines navigation surfaces, text and interaction states, logo, groups, search, popups, borders, radius and density. Platform-supported tokens are applied through the active navigation mode; page content consumes the same application theme.',
|
|
1426
1426
|
'Applications with appThemeEnable=n or appThemeMode=legacy persist side/top/l_shape as ver/hoz/ver and map unsupported white/gray navigation colors to legacy light. Existing legacy layouts and navType are preserved. Unknown theme capability keeps the existing save protocol.',
|
|
1427
1427
|
'navigationVerification checks requested navTheme/layoutDirection/hideAppNav/logoSource; mismatch is APP_NAVIGATION_NOT_PERSISTED, nonzero, with read-only retries. themeVerification.scope=configuration_readback; operation is bind_resource/update_theme/disable/preserve. preserve is not upload evidence. New themes need an uploaded receipt and matching enabled resource/color. runtimeVerification stays false/not_checked. Browser checks follow builder_path.browser_verification; only a local agent on the user machine may run them. Cloud/sandbox/remote/unknown keep not_checked internal and deliver confirmed results and entry URLs, without default user verification tasks.',
|
|
1428
|
+
'themeCompletion.complete requires stable proven modern mode, or legacy mode with scope confirmation, plus matching CSS/color and requested navigation. Unknown/changed mode fails with APP_THEME_CONFIGURATION_INCOMPLETE after read-only retries. Color/navigation-only is partial; preset/disable are separate operations. Never replace requested CSS with colors after a historical redirect. sample is optional; configuration is not rendering proof.',
|
|
1428
1429
|
'For generated design artifacts, pass themeProfile.navTheme from design.md to --nav-theme. Do not infer it from the business brief or omit it expecting a light default.',
|
|
1429
1430
|
'Platform preset keys are used independently of --theme-file and --theme-color. Choose --hide-app-nav or --show-app-nav to set navigation visibility.',
|
|
1430
1431
|
],
|
|
@@ -1899,6 +1900,7 @@ const COMMAND_GROUPS = [
|
|
|
1899
1900
|
output: 'json',
|
|
1900
1901
|
noteKeys: ['help.data_notes', 'query_data.approver_query_help'],
|
|
1901
1902
|
notes: [
|
|
1903
|
+
'Ordinary form updates read record edit permission and native edit-schema field behaviors before writing. Only verified NORMAL fields are accepted; readonly, hidden, disabled, unknown or dynamically unverifiable fields reject the entire update. Restricted subform replacements require the native editor. This CLI preflight does not replace server-side authorization.',
|
|
1902
1904
|
'Application tasks: data query tasks <appType> --type todo|created|done|cc [--normalized] [--transport app-v1|app-query]. submitted remains an alias of created. --normalized returns items/total/page/pageSize with native detail targets; an unknown total is null. app-v1 is the compatible default; only cc adds --process-codes and --instance-status. Explicit app-query fixes appKeys to the target app and supports the per-kind filters in task-center capabilities: --originators, --process-codes, --instance-create-from/to, --task-create-from/to, --task-finish-from/to, --carbon-create-from/to. Date values are Unix milliseconds. Unsupported filters and caller scope/identity overrides are rejected; transports never silently fall back.',
|
|
1903
1905
|
'Each condition uses key (field ID), componentName, type, operator and value. Query types: TextField/TextareaField/TableField TEXT; NumberField/RateField/DateField/CascadeDateField DOUBLE; RadioField/SelectField/CheckboxField/MultiSelectField/DepartmentSelectField ARRAY; EmployeeField/CascadeSelectField/CitySelectField STRING; ImageField/AttachmentField TEXT.',
|
|
1904
1906
|
'Ordinary form searchFormDatas accepts AND condition arrays via --search-json/--search-file. DateField: type DOUBLE, is/is_not, value ""; zero is filled. Query range endpoints as DateField with .start/.end: both empty means unfilled, both filled means complete.',
|
|
@@ -2332,7 +2334,9 @@ function summarizeLocalizedCommands(commands) {
|
|
|
2332
2334
|
reference: 'yida-skills/skills/yida-design/references/ask-human-interaction-contract.md',
|
|
2333
2335
|
audience: 'nontechnical_user',
|
|
2334
2336
|
wording: 'Use everyday language, concrete business names and short sentences. Lead with the result or action; explain necessary technical terms on first use.',
|
|
2335
|
-
progress: '
|
|
2337
|
+
progress: 'Report only the actual business stage and verified business results. Never announce environment, login or current-organization readiness.',
|
|
2338
|
+
preparation: 'Strictly internal: installation, environment checks, authentication, ticket exchange, refresh and recovery must never appear in messages, todos, cards or tool-call titles/descriptions. On success continue the business operation without a transition message. If user action is essential, state only the minimal action and business impact.',
|
|
2339
|
+
app_creation_notice: 'Immediately before creating a new app, say once: 当前在{组织名称}组织下为您创建{应用名称}应用. Use the verified current host organization display name and confirmed app name; never substitute an environment label, domain, organization ID or historical account. If the display name is unavailable, say 当前组织. Do not repeat on retries or existing-app continuation, or append authentication/preparation status.',
|
|
2336
2340
|
image_progress: 'Use 素材采集&应用创建 as the progress description while image collection and app creation are both running; otherwise use the active stage name. Keep the todo items separate. Record image counts, slots, subtasks and execution details internally.',
|
|
2337
2341
|
todo: {
|
|
2338
2342
|
templates: 'yida-skills/skills/yida-design/references/ask-human-interaction-contract.md#步骤列表与进度',
|