openyida 2026.10.15 → 2026.10.16
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/agent/cmd.js +1 -1
- package/lib/agent/frozen-bundle.js +4 -4
- package/lib/agent/private-directory.js +9 -2
- package/lib/agent/provider-preflight.js +10 -3
- package/lib/agent/runtime-catalog.json +15 -12
- package/lib/agent/stdio.js +7 -1
- package/lib/auth/token-auth.js +27 -1
- package/lib/auth/token-store.js +29 -3
- package/lib/core/command-manifest.js +2 -1
- package/lib/core/locales/en.js +7 -0
- package/lib/core/locales/zh.js +7 -0
- package/lib/core/utils.js +5 -0
- package/lib/design-plan/design-plan.js +3 -3
- package/lib/design-plan/patch.js +9 -2
- package/package.json +5 -5
- package/yida-skills/references/local-agent-preflight.md +5 -2
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +10 -0
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +3 -3
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +3 -2
package/lib/agent/cmd.js
CHANGED
|
@@ -285,7 +285,7 @@ async function run(argv = [], options = {}) {
|
|
|
285
285
|
// Inventory includes signed-out CLIs. Runtime reports their auth status;
|
|
286
286
|
// the control plane independently enforces task readiness.
|
|
287
287
|
config.providers = providerCheck.providers.filter(p => p.registrable).map(p => ({
|
|
288
|
-
profileId: 'openyida.' + p.provider, provider: p.provider, executable: p.executable,
|
|
288
|
+
profileId: 'openyida.' + p.provider, provider: p.provider, executable: p.executable, ...(p.version ? { version: p.version } : {}),
|
|
289
289
|
}));
|
|
290
290
|
}
|
|
291
291
|
if (managedConnections && args.command === 'connect') {
|
|
@@ -285,11 +285,11 @@ function materializeBundle(runtime, config, options = {}) {
|
|
|
285
285
|
catch (error) {
|
|
286
286
|
if (!['EEXIST', 'ENOTEMPTY', 'EACCES', 'EPERM'].includes(error.code) || !fs.existsSync(root)) {throw error;}
|
|
287
287
|
}
|
|
288
|
-
} else {
|
|
289
|
-
// Never trust a version or mtime cache. Check source stability and every
|
|
290
|
-
// byte of the existing bundle, including unexpected entries, on reuse.
|
|
291
|
-
verifySources();
|
|
292
288
|
}
|
|
289
|
+
// On reuse, source bytes were hashed to compute this exact manifest ID.
|
|
290
|
+
// We execute only the existing bundle, whose complete inventory and bytes
|
|
291
|
+
// are verified below. Reopening the source snapshot cannot make that bundle
|
|
292
|
+
// more trustworthy. New publication still checks source stability above.
|
|
293
293
|
if (published) {fs.chmodSync(root, 0o500);}
|
|
294
294
|
verifyBundle(root, id);
|
|
295
295
|
const bundle = { schemaVersion: 1, id, root, manifestPath: path.join(root, 'bundle-manifest.json'), manifestSha256: id,
|
|
@@ -51,8 +51,15 @@ foreach ($sid in @($identity.Value, 'S-1-5-18', 'S-1-5-32-544')) {
|
|
|
51
51
|
foreach ($directory in ($reader.ReadToEnd() | ConvertFrom-Json)) {
|
|
52
52
|
# Concurrent launchers may create the same parent. Never rewrite a winner;
|
|
53
53
|
# accept it only when its actual owner and grants meet the same private policy.
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
# Extended-length paths avoid the .NET Framework MAX_PATH limit. Paths
|
|
55
|
+
# are already absolute and validated by the Node caller.
|
|
56
|
+
$nativePath = $directory
|
|
57
|
+
if (-not $directory.StartsWith('\\\\?\\')) {
|
|
58
|
+
if ($directory.StartsWith('\\\\')) { $nativePath = '\\\\?\\UNC\\' + $directory.Substring(2) }
|
|
59
|
+
else { $nativePath = '\\\\?\\' + $directory }
|
|
60
|
+
}
|
|
61
|
+
[System.IO.Directory]::CreateDirectory($nativePath, $acl) | Out-Null
|
|
62
|
+
$actual = (New-Object System.IO.DirectoryInfo($nativePath)).GetAccessControl()
|
|
56
63
|
if ($actual.GetOwner([System.Security.Principal.SecurityIdentifier]).Value -ne $identity.Value) {throw 'Private directory owner differs'}
|
|
57
64
|
foreach ($rule in $actual.Access) {
|
|
58
65
|
$sid = $rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
@@ -186,7 +186,14 @@ async function checkProvider(provider, executable, options) {
|
|
|
186
186
|
if (version.status !== 'passed') { return { status: version.status, cliDetected: false }; }
|
|
187
187
|
// Installation evidence is independent of authentication/protocol readiness.
|
|
188
188
|
// Apply this consistently to every enabled provider, including future adapters.
|
|
189
|
-
|
|
189
|
+
// Reuse only a bounded, recognizable version line from this exact probe.
|
|
190
|
+
// Arbitrary provider banners/stderr never become launch metadata.
|
|
191
|
+
const versionLine = String(version.output || '').split(/\r?\n/).map(line => line.trim()).find(line =>
|
|
192
|
+
line.length <= 64 && /^(?:(?:codex(?:-cli)?|qoder(?:cli| cli)?|opencode)\s+)?v?\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)?(?:\s+\([0-9a-z ._-]+\))?$/i.test(line));
|
|
193
|
+
const detected = { cliDetected: true, ...(versionLine ? { version: versionLine } : {}) };
|
|
194
|
+
// Connect/run only establish installation inventory. The runtime discovers live
|
|
195
|
+
// protocol, login and models for EVERY registered provider; doctor probes them here.
|
|
196
|
+
if (options.inventoryOnly) { return { ...detected, status: 'cli_detected', authKnown: false, protocolProbe: 'not_run' }; }
|
|
190
197
|
if (provider === 'opencode') { return { ...detected, status: 'available', authKnown: false, protocolProbe: 'not_run' }; }
|
|
191
198
|
const protocol = await execute(executable, provider === 'qoder' ? ['--acp'] : ['app-server', '--listen', 'stdio://'], provider, options);
|
|
192
199
|
if (protocol.status === 'login_required') {
|
|
@@ -230,9 +237,9 @@ async function preflight(args, options = {}) {
|
|
|
230
237
|
};
|
|
231
238
|
for (const candidate of entry.candidates.slice(0, 4)) {
|
|
232
239
|
executable = candidate;
|
|
233
|
-
result = await checkProvider(entry.provider, executable, { ...options, probe: boundedProbe });
|
|
240
|
+
result = await checkProvider(entry.provider, executable, { ...options, inventoryOnly: ['connect', 'run'].includes(args.command), probe: boundedProbe });
|
|
234
241
|
if (result.cliDetected && !discovered) { discovered = { result, executable }; }
|
|
235
|
-
if (Date.now() >= deadline || ['ready', 'available', 'login_required', 'probe_cancelled'].includes(result.status)) { break; }
|
|
242
|
+
if (Date.now() >= deadline || ['ready', 'available', 'cli_detected', 'login_required', 'probe_cancelled'].includes(result.status)) { break; }
|
|
236
243
|
}
|
|
237
244
|
if (!result.cliDetected && result.status !== 'probe_cancelled' && discovered) {
|
|
238
245
|
({ result, executable } = discovered);
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
"keys": {
|
|
4
4
|
"openyida-runtime-public-beta-20260923-1": {
|
|
5
5
|
"publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEApl2Q+64rX997KxEQUY2yU8Xnc7sM3NRQ8Nf0bAi9Hck=\n-----END PUBLIC KEY-----\n"
|
|
6
|
+
},
|
|
7
|
+
"openyida-runtime-public-beta-20261012-1": {
|
|
8
|
+
"publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAKaA5RBJXWVhQa+9ehmR3x7WQCAlVGxbJULG9T17qmRc=\n-----END PUBLIC KEY-----\n"
|
|
6
9
|
}
|
|
7
10
|
},
|
|
8
11
|
"releases": [
|
|
@@ -10,33 +13,33 @@
|
|
|
10
13
|
"platform": "darwin",
|
|
11
14
|
"arch": "arm64",
|
|
12
15
|
"packageName": "@openyida/local-agent-runtime-darwin-arm64",
|
|
13
|
-
"packageVersion": "0.1.
|
|
14
|
-
"runtimeVersion": "0.1.
|
|
15
|
-
"manifestSha256": "
|
|
16
|
+
"packageVersion": "0.1.3",
|
|
17
|
+
"runtimeVersion": "0.1.3",
|
|
18
|
+
"manifestSha256": "1a9f5e148491c350457a41f095de9598775a2a349d872c43877c9fad63c09e28"
|
|
16
19
|
},
|
|
17
20
|
{
|
|
18
21
|
"platform": "darwin",
|
|
19
22
|
"arch": "x64",
|
|
20
23
|
"packageName": "@openyida/local-agent-runtime-darwin-x64",
|
|
21
|
-
"packageVersion": "0.1.
|
|
22
|
-
"runtimeVersion": "0.1.
|
|
23
|
-
"manifestSha256": "
|
|
24
|
+
"packageVersion": "0.1.3",
|
|
25
|
+
"runtimeVersion": "0.1.3",
|
|
26
|
+
"manifestSha256": "46918d51efd40b73a645487525f2f7bdca94124ffb3ad6cdae6e9641f28b3963"
|
|
24
27
|
},
|
|
25
28
|
{
|
|
26
29
|
"platform": "win32",
|
|
27
30
|
"arch": "x64",
|
|
28
31
|
"packageName": "@openyida/local-agent-runtime-win32-x64",
|
|
29
|
-
"packageVersion": "0.1.
|
|
30
|
-
"runtimeVersion": "0.1.
|
|
31
|
-
"manifestSha256": "
|
|
32
|
+
"packageVersion": "0.1.3",
|
|
33
|
+
"runtimeVersion": "0.1.3",
|
|
34
|
+
"manifestSha256": "64d471a37c54650e25c214e08d7ca7fa8dde068decfcbe71bd8abcbb2b7e74c6"
|
|
32
35
|
},
|
|
33
36
|
{
|
|
34
37
|
"platform": "win32",
|
|
35
38
|
"arch": "arm64",
|
|
36
39
|
"packageName": "@openyida/local-agent-runtime-win32-arm64",
|
|
37
|
-
"packageVersion": "0.1.
|
|
38
|
-
"runtimeVersion": "0.1.
|
|
39
|
-
"manifestSha256": "
|
|
40
|
+
"packageVersion": "0.1.3",
|
|
41
|
+
"runtimeVersion": "0.1.3",
|
|
42
|
+
"manifestSha256": "7b1ff527dc54285084eac0773d273fdfb892ce5ca352a810767c64eefc8e59c0"
|
|
40
43
|
}
|
|
41
44
|
]
|
|
42
45
|
}
|
package/lib/agent/stdio.js
CHANGED
|
@@ -127,7 +127,13 @@ function launchRuntime(runtime, config, options = {}) {
|
|
|
127
127
|
diagnostic('error', 'runtime_action_failed', {
|
|
128
128
|
command: config.command, component: 'node', errorCode: code, retryable: event.retryable === true,
|
|
129
129
|
});
|
|
130
|
-
|
|
130
|
+
const enrollmentMessages = {
|
|
131
|
+
ENROLLMENT_EXPIRED: 'agent.enrollment_expired',
|
|
132
|
+
ENROLLMENT_REJECTED: 'agent.enrollment_rejected',
|
|
133
|
+
ENROLLMENT_CONFLICT: 'agent.enrollment_conflict',
|
|
134
|
+
ENROLLMENT_REQUEST_FAILED: 'agent.enrollment_request_failed',
|
|
135
|
+
};
|
|
136
|
+
failure = new CliError(t(enrollmentMessages[code] || 'agent.runtime_failed'), { code, details: { retryable: event.retryable === true } });
|
|
131
137
|
stop('SIGTERM');
|
|
132
138
|
return;
|
|
133
139
|
}
|
package/lib/auth/token-auth.js
CHANGED
|
@@ -229,6 +229,26 @@ function createTokenAuthRequiredError(reason, sourceError) {
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
function createAuthProfileSelectionError(status) {
|
|
233
|
+
const reason = status.failure_reason || status.status;
|
|
234
|
+
if (!['profile_required', 'profile_not_found'].includes(reason)) {return null;}
|
|
235
|
+
const { CliError } = require('../core/cli-error');
|
|
236
|
+
const { t } = require('../core/i18n');
|
|
237
|
+
return new CliError(t(`common.auth_${reason}`), {
|
|
238
|
+
code: reason === 'profile_required' ? 'AUTH_PROFILE_REQUIRED' : 'AUTH_PROFILE_NOT_FOUND',
|
|
239
|
+
details: {
|
|
240
|
+
authMode: 'token',
|
|
241
|
+
status: reason,
|
|
242
|
+
failure_reason: reason,
|
|
243
|
+
auth_profile: status.auth_profile,
|
|
244
|
+
candidate_count: status.candidate_count,
|
|
245
|
+
candidates: status.candidates,
|
|
246
|
+
next_step: status.next_step,
|
|
247
|
+
next_step_commands: status.next_step_commands,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
232
252
|
function createTokenRefreshFailedError(reason, sourceError) {
|
|
233
253
|
const { CliError } = require('../core/cli-error');
|
|
234
254
|
return new CliError(
|
|
@@ -630,7 +650,12 @@ async function tokenLogout(options = {}) {
|
|
|
630
650
|
}
|
|
631
651
|
|
|
632
652
|
async function getAccessToken(options = {}) {
|
|
633
|
-
|
|
653
|
+
const resolution = resolveTokenSession(options);
|
|
654
|
+
let session = resolution.session;
|
|
655
|
+
if (!session) {
|
|
656
|
+
const selectionError = createAuthProfileSelectionError(resolution);
|
|
657
|
+
if (selectionError) {throw selectionError;}
|
|
658
|
+
}
|
|
634
659
|
// Do not reject a token from local expiry metadata. The business endpoint is
|
|
635
660
|
// the authority: requestWithAutoLogin refreshes only after it rejects the
|
|
636
661
|
// access token, then retries with the newly persisted local session.
|
|
@@ -675,4 +700,5 @@ module.exports = {
|
|
|
675
700
|
resolveTokenBaseUrl,
|
|
676
701
|
isRefreshAuthRequired,
|
|
677
702
|
createTokenRefreshFailedError,
|
|
703
|
+
createAuthProfileSelectionError,
|
|
678
704
|
};
|
package/lib/auth/token-store.js
CHANGED
|
@@ -472,12 +472,31 @@ function saveAuthProfilePointer(session, options = {}) {
|
|
|
472
472
|
return pointer;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
function
|
|
476
|
-
const
|
|
477
|
-
|
|
475
|
+
function getWorkspaceAuthProfilePointerFilePath(options = {}) {
|
|
476
|
+
const root = path.resolve(resolveProjectRoot(options));
|
|
477
|
+
const workspace = path.dirname(root);
|
|
478
|
+
if (isEnvTokenAuthMode(options.env || process.env) || path.basename(root) !== 'project' ||
|
|
479
|
+
fs.existsSync(path.join(workspace, 'config.json'))) {
|
|
478
480
|
return null;
|
|
479
481
|
}
|
|
480
482
|
try {
|
|
483
|
+
// Only bridge the conventional workspace -> workspace/project transition.
|
|
484
|
+
// A symlinked project belongs to its own workspace, not its lexical parent.
|
|
485
|
+
if (fs.realpathSync(root) !== path.join(fs.realpathSync(workspace), 'project')) {return null;}
|
|
486
|
+
return getAuthProfilePointerFilePath({ ...options, projectRoot: workspace, envName: resolveEnvName(options) });
|
|
487
|
+
} catch {return null;}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function loadAuthProfilePointer(options = {}) {
|
|
491
|
+
try {
|
|
492
|
+
let pointerFile = getAuthProfilePointerFilePath(options);
|
|
493
|
+
// An explicit child selection (including an unbound/corrupt pointer) wins.
|
|
494
|
+
// Reading inherited metadata never copies credentials or writes a pointer.
|
|
495
|
+
if (!fs.existsSync(pointerFile)) {
|
|
496
|
+
if (fs.existsSync(getTokenFilePath(options))) {return null;}
|
|
497
|
+
pointerFile = getWorkspaceAuthProfilePointerFilePath(options);
|
|
498
|
+
}
|
|
499
|
+
if (!pointerFile || !fs.existsSync(pointerFile)) {return null;}
|
|
481
500
|
const pointer = JSON.parse(fs.readFileSync(pointerFile, 'utf8'));
|
|
482
501
|
if (!pointer || typeof pointer !== 'object' || !pointer.auth_profile) {
|
|
483
502
|
return null;
|
|
@@ -491,6 +510,13 @@ function loadAuthProfilePointer(options = {}) {
|
|
|
491
510
|
function clearAuthProfilePointer(options = {}) {
|
|
492
511
|
try {
|
|
493
512
|
const pointerFile = getAuthProfilePointerFilePath(options);
|
|
513
|
+
const workspacePointerFile = getWorkspaceAuthProfilePointerFilePath(options);
|
|
514
|
+
if (workspacePointerFile && fs.existsSync(workspacePointerFile)) {
|
|
515
|
+
// Detaching this project must not revive its inherited workspace choice
|
|
516
|
+
// on the next command, or clear another project's workspace selection.
|
|
517
|
+
writeJsonFileAtomic(pointerFile, { version: AUTH_PROFILE_VERSION, detached: true });
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
494
520
|
if (fs.existsSync(pointerFile)) {
|
|
495
521
|
fs.unlinkSync(pointerFile);
|
|
496
522
|
}
|
|
@@ -1165,7 +1165,8 @@ const COMMAND_GROUPS = [
|
|
|
1165
1165
|
output: 'text|json',
|
|
1166
1166
|
args: [
|
|
1167
1167
|
{ name: 'buildPlan', type: 'file', required: true, source: 'positional', position: 0, builder_options: ['--build-plan'] },
|
|
1168
|
-
{ name: '
|
|
1168
|
+
{ name: 'setFile', type: 'file', source: 'option', builder_options: ['--set-file'], description: 'JSON object mapping field paths to typed values; use for Windows arrays, objects and quoted text. Requires --set or --set-file.' },
|
|
1169
|
+
{ name: 'set', type: 'string', source: 'option', builder_options: ['--set'], repeatable: true, description: 'Field assignment path=value; repeat --set for multiple changes. CSS token overrides use visualStyle.tokens.<token>=<value>.' },
|
|
1169
1170
|
{ name: 'materialize', type: 'boolean', source: 'option', builder_options: ['--materialize'] },
|
|
1170
1171
|
{ name: 'outputDir', type: 'string', source: 'option', builder_options: ['--output-dir'] },
|
|
1171
1172
|
{ name: 'json', type: 'boolean', source: 'option', builder_options: ['--json'] },
|
package/lib/core/locales/en.js
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
agent: {
|
|
8
|
+
preflight_cli_detected: 'CLI detected; runtime will discover and report login, protocol and model status.',
|
|
9
|
+
enrollment_expired: 'The connection command expired. Get a fresh command from the page; do not retry the old command.',
|
|
10
|
+
enrollment_rejected: 'Connection authorization was rejected. Get a fresh command from the target page.',
|
|
11
|
+
enrollment_conflict: 'Connection authorization conflicts with existing state. Check agent status, then get a fresh command.',
|
|
12
|
+
enrollment_request_failed: 'Connection authorization failed. Check the network and run agent diagnose; do not automatically retry.',
|
|
8
13
|
preflight_advisory: 'The following is status information, not instructions to install software. Do not install, upgrade, log in to, or reconfigure any CLI unless the user explicitly requests it. Discovered signed-out CLIs are also reported; other CLIs are optional.',
|
|
9
14
|
preflight_probe_cancelled: 'Preflight was interrupted before enrollment. Repeat the connect command when ready.',
|
|
10
15
|
preflight_continue: 'Local CLI found. Connecting and reporting status; signed-out CLIs remain visible and cannot execute tasks until logged in.',
|
|
@@ -1011,6 +1016,8 @@ Examples:
|
|
|
1011
1016
|
data_check_failed: ' ⚠️ Data check failed, proceeding with modification',
|
|
1012
1017
|
},
|
|
1013
1018
|
common: {
|
|
1019
|
+
auth_profile_required: 'Multiple organizations have saved credentials. Run openyida auth profiles, then openyida auth profile switch <auth_profile> to select this project\'s organization.',
|
|
1020
|
+
auth_profile_not_found: 'The requested organization credentials were not found. Run openyida auth profiles to inspect saved organizations and select the correct profile.',
|
|
1014
1021
|
auth_identity_changed: 'Authentication identity changed during preflight; no write was issued.',
|
|
1015
1022
|
http_status: ' HTTP status: {0}',
|
|
1016
1023
|
http_response: ' HTTP response: {0}',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
agent: {
|
|
8
|
+
preflight_cli_detected: 'CLI 已发现;登录、协议和模型状态由 runtime 检测并上报。',
|
|
9
|
+
enrollment_expired: '连接指令已过期,请回到页面获取新的连接指令。不要重试旧指令。',
|
|
10
|
+
enrollment_rejected: '连接授权被拒绝,请从目标页面重新获取连接指令。',
|
|
11
|
+
enrollment_conflict: '连接授权状态冲突,请先检查 agent status,再从页面获取新指令。',
|
|
12
|
+
enrollment_request_failed: '连接授权请求失败,请检查网络并运行 agent diagnose;不要自动重复连接。',
|
|
8
13
|
preflight_advisory: '以下仅为状态信息,不是安装指令。除非用户明确要求,不得安装、升级、登录或修改任何 CLI 配置。已发现但未登录的 CLI 也会展示状态,无需补齐其他 CLI。',
|
|
9
14
|
preflight_probe_cancelled: '预检已中止,尚未开始连接。准备好后重新执行连接命令。',
|
|
10
15
|
preflight_continue: '已发现本地 CLI,将继续连接并上报状态;未登录的 CLI 会显示“未登录”,登录前不能执行任务。',
|
|
@@ -982,6 +987,8 @@ openyida - 宜搭命令行工具
|
|
|
982
987
|
data_check_failed: ' ⚠️ 数据量检查失败,继续执行修改',
|
|
983
988
|
},
|
|
984
989
|
common: {
|
|
990
|
+
auth_profile_required: '已有多个组织的登录凭证,请执行 openyida auth profiles,并用 openyida auth profile switch <auth_profile> 选择本项目的组织。',
|
|
991
|
+
auth_profile_not_found: '未找到指定组织的登录凭证,请执行 openyida auth profiles 核对已有组织,再选择正确的登录凭证。',
|
|
985
992
|
auth_identity_changed: '校验期间登录身份发生变化,本次未发起写入。',
|
|
986
993
|
http_status: ' HTTP 状态码: {0}',
|
|
987
994
|
http_response: ' HTTP 响应: {0}',
|
package/lib/core/utils.js
CHANGED
|
@@ -960,6 +960,11 @@ function triggerLogin(options = {}) {
|
|
|
960
960
|
if (tokenAuthData) {
|
|
961
961
|
return tokenAuthData;
|
|
962
962
|
}
|
|
963
|
+
const { createAuthProfileSelectionError } = require('../auth/token-auth');
|
|
964
|
+
const selectionError = createAuthProfileSelectionError(getAuthStatus({
|
|
965
|
+
...options, projectRoot: options.projectRoot || findProjectRoot(),
|
|
966
|
+
}));
|
|
967
|
+
if (selectionError) {throw selectionError;}
|
|
963
968
|
const { CliError } = require('./cli-error');
|
|
964
969
|
throw new CliError(
|
|
965
970
|
'not_logged_in: token auth is unavailable. Run openyida login first.',
|
|
@@ -14,7 +14,7 @@ function usage() {
|
|
|
14
14
|
' openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]',
|
|
15
15
|
' openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]',
|
|
16
16
|
' openyida design-plan materialize <build-plan.json> [--from-preview | --business-file <json> --visual-file <json>] [--output-dir <dir>] [--rebase-parts] [--check] [--json]',
|
|
17
|
-
' openyida design-plan patch <build-plan.json> --set <path=value> [--set <
|
|
17
|
+
' openyida design-plan patch <build-plan.json> [--set <path=value> ...] [--set-file <changes.json>] [--materialize] [--output-dir <dir>] [--json]',
|
|
18
18
|
].join('\n');
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -56,7 +56,7 @@ const OPTION_SCHEMA = Object.freeze({
|
|
|
56
56
|
init: { flags: ['--json'], values: ['--theme-id', '--output-dir'] },
|
|
57
57
|
preview: { flags: ['--json'], values: ['--part-file'] },
|
|
58
58
|
materialize: { flags: ['--from-preview', '--rebase-parts', '--check', '--json'], values: ['--business-file', '--visual-file', '--output-dir'] },
|
|
59
|
-
patch: { flags: ['--materialize', '--json'], values: ['--set', '--output-dir'], inlineValues: ['--set'] },
|
|
59
|
+
patch: { flags: ['--materialize', '--json'], values: ['--set', '--set-file', '--output-dir'], inlineValues: ['--set'] },
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
function assertKnownOptions(subcommand, args) {
|
|
@@ -126,7 +126,7 @@ async function run(args) {
|
|
|
126
126
|
? preview(input, { partFile: optionValue(options, '--part-file') })
|
|
127
127
|
: subcommand === 'materialize'
|
|
128
128
|
? materialize(input, { outputDir, check: options.includes('--check'), rebaseParts: options.includes('--rebase-parts'), fromPreview: options.includes('--from-preview'), businessFile: optionValue(options, '--business-file'), visualFile: optionValue(options, '--visual-file') })
|
|
129
|
-
: patchPlan(input, setValues(options), { outputDir, materialize: options.includes('--materialize') });
|
|
129
|
+
: patchPlan(input, setValues(options), { outputDir, setFile: optionValue(options, '--set-file'), materialize: options.includes('--materialize') });
|
|
130
130
|
} catch (error) {
|
|
131
131
|
const files = [input, ...['--business-file', '--visual-file', '--part-file'].map(flag => optionValue(options, flag))];
|
|
132
132
|
if (subcommand !== 'init') {
|
package/lib/design-plan/patch.js
CHANGED
|
@@ -156,9 +156,16 @@ function isAssetProgressOnly(original, patched) {
|
|
|
156
156
|
|
|
157
157
|
function patchPlan(inputPath, setExpressions, options = {}) {
|
|
158
158
|
const resolvedInput = path.resolve(inputPath);
|
|
159
|
-
const expressions = Array.isArray(setExpressions) ? setExpressions : [];
|
|
159
|
+
const expressions = Array.isArray(setExpressions) ? [...setExpressions] : [];
|
|
160
|
+
if (options.setFile) {
|
|
161
|
+
const changes = readJson(path.resolve(options.setFile));
|
|
162
|
+
if (!changes || Array.isArray(changes) || typeof changes !== 'object') {
|
|
163
|
+
throw new CliError('--set-file requires a JSON object mapping field paths to values', { code: 'DESIGN_PLAN_INVALID_SET_FILE' });
|
|
164
|
+
}
|
|
165
|
+
for (const [field, value] of Object.entries(changes)) { expressions.push(field + '=' + JSON.stringify(value)); }
|
|
166
|
+
}
|
|
160
167
|
if (expressions.length === 0) {
|
|
161
|
-
throw new CliError('design-plan patch 至少需要一个 --set path=value', {
|
|
168
|
+
throw new CliError('design-plan patch 至少需要一个 --set path=value 或 --set-file changes.json', {
|
|
162
169
|
code: 'DESIGN_PLAN_SET_REQUIRED',
|
|
163
170
|
});
|
|
164
171
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openyida",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.16",
|
|
4
4
|
"description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openyida": "bin/yida.js",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"endpoint": "https://www.aliwork.com/openyida/agent-api"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@openyida/local-agent-runtime-darwin-arm64": "0.1.
|
|
118
|
-
"@openyida/local-agent-runtime-darwin-x64": "0.1.
|
|
119
|
-
"@openyida/local-agent-runtime-win32-x64": "0.1.
|
|
120
|
-
"@openyida/local-agent-runtime-win32-arm64": "0.1.
|
|
117
|
+
"@openyida/local-agent-runtime-darwin-arm64": "0.1.3",
|
|
118
|
+
"@openyida/local-agent-runtime-darwin-x64": "0.1.3",
|
|
119
|
+
"@openyida/local-agent-runtime-win32-x64": "0.1.3",
|
|
120
|
+
"@openyida/local-agent-runtime-win32-arm64": "0.1.3"
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -29,7 +29,9 @@ openyida agent doctor --provider codex --json
|
|
|
29
29
|
|
|
30
30
|
桌面版是否存在与 CLI 是否可用是两个独立事实。Qoder 桌面程序不会作为 CLI 启动;Codex 内置程序被发现后仍需通过检查。
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
连接和重连先检查所有已安装 CLI 的版本,全部可上报 CLI 都会交给 Runtime,不会因第一个 CLI 可用而跳过其他 CLI。登录、协议和模型状态由 Runtime 检测并持续上报;预检成功仅表示已安装,不表示已登录。版本输出符合严格格式时,通过已有 version 字段复用,避免 Runtime 再次启动同一版本查询。
|
|
33
|
+
|
|
34
|
+
完整 `doctor` 诊断仍检查:Qoder 版本、ACP 初始化和 CLI 模型列表;Codex 版本、app-server 初始化和登录状态。不发送模型 prompt、不创建业务对象、不自动安装程序、不发起登录、不复制其他应用的凭据。Windows 单次子进程检查超时为 45 秒,每个 Provider 总预算为 135 秒;其他平台分别为 15 秒与 30 秒。总预算耗尽即停止探测,不把超时当作未安装。
|
|
33
35
|
|
|
34
36
|
当前接入范围为 Qoder 和 Codex。
|
|
35
37
|
|
|
@@ -37,6 +39,7 @@ Qoder 检查版本、ACP 初始化和 CLI 模型列表;Codex 检查版本、ap
|
|
|
37
39
|
|
|
38
40
|
| status | 含义 | 下一步 |
|
|
39
41
|
| --- | --- | --- |
|
|
42
|
+
| cli_detected | 连接预检已确认 CLI 可执行,登录/协议尚未检查 | 继续注册,等待 Runtime 上报实际状态;不要重复连接 |
|
|
40
43
|
| ready | CLI 登录和协议检查通过 | 继续连接 |
|
|
41
44
|
| available | 可启动,但有能力尚未验证 | 继续现有接入流程,保留未验证标记 |
|
|
42
45
|
| cli_not_found | 未找到 CLI | 仅报告缺失;用户明确要求后才协助安装或指定路径 |
|
|
@@ -47,7 +50,7 @@ Qoder 检查版本、ACP 初始化和 CLI 模型列表;Codex 检查版本、ap
|
|
|
47
50
|
| probe_cancelled | 用户中止预检 | 清理本轮检查进程,停止连接 |
|
|
48
51
|
| probe_failed | 命令检查失败,原因不能可靠归类 | 直接运行 CLI 检查配置、账号或网络 |
|
|
49
52
|
|
|
50
|
-
`--json` 返回 `schemaVersion=1`、逐 provider 状态、`desktopDetected`、`cliDetected`、`usable`、`registrable`
|
|
53
|
+
`--json` 返回 `schemaVersion=1`、逐 provider 状态、`desktopDetected`、`cliDetected`、`usable`、`registrable` 和按需提供的 `remediation`;识别到严格格式版本时附带 `version`。诊断信息标记 INFORMATION_ONLY、requiresExplicitUserRequest=true 和 automaticActionAllowed=false,提供文档、路径选项及复查命令,不输出安装或登录命令;不包含原始 Provider 输出、用户标识或凭据。
|
|
51
54
|
|
|
52
55
|
`doctor.providerReady` 表示本地 CLI 检查结果,`runtime.installed` 表示 Runtime 制品校验结果。两者通过仍不等于宜搭连接已授权或真实业务 E2E 通过,因此 doctor 的 `ready` 不表示连接在线。`cliDetected` 表示 CLI 版本探测成功,`registrable` 表示已发现且可上报,登录或协议检查失败不会删除安装清单(用户取消除外);`usable` 表示执行就绪。`readyForEnrollment` 只要求至少一个可上报 CLI 且用户未取消。`connect` 和 `run` 将这些 CLI 交给 Runtime,未登录状态由 Runtime 上报,前端显示“未登录”,服务端拒绝任务执行。即使全部已发现 CLI 都未登录,也允许连接电脑并展示状态。
|
|
53
56
|
|
|
@@ -73,3 +73,13 @@ build-plan.json(业务和视觉的源事实)
|
|
|
73
73
|
如果已经改动主计划并出现 DESIGN_PLAN_STALE_PART,在原 materialize 命令增加 --rebase-parts。CLI 使用 init 保存的 .build-plan-base.json 做三方核对,保留已完成 facts;冲突按 details.conflicts 明确选择后重试。基线缺失或无法核实时告知阻塞并恢复可信文件,不手填 digest、不删除重建、不重新 init。方案确认后调整一律走 patch --materialize;确认前重复物化仍可用 --rebase-parts 与基线核对,但改动内容以当前 build-plan.json 为准。
|
|
74
74
|
|
|
75
75
|
本地校验失败统一按[本地校验修复](../../../../references/source-repair.md)处理:先修正并回读源文件,未变化不重跑,连续两次修复无进展时保留产物并报告阻塞。
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Windows 参数传递
|
|
79
|
+
|
|
80
|
+
修改方案中的数组、对象或包含双引号的文本时,先用文件写入工具保存 UTF-8 JSON 文件,再执行
|
|
81
|
+
|
|
82
|
+
`openyida design-plan patch prd/build-plan.json --set-file prd/changes.json --materialize --json`
|
|
83
|
+
|
|
84
|
+
changes.json 是字段路径到值的映射,例如 {"dataModels[0].fields":[{"name":"物品名称","type":"TextField","required":true}]}。
|
|
85
|
+
不要把整个 JSON 内联到 PowerShell 或 cmd 参数中;文件输入仍经过相同字段保护、方案校验和确认失效处理。
|
package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css
CHANGED
|
@@ -106,8 +106,8 @@
|
|
|
106
106
|
--corner-3: 2px;
|
|
107
107
|
--corner-4: 2px;
|
|
108
108
|
--corner-5: 2px;
|
|
109
|
-
--corner-circle:
|
|
110
|
-
--corner-semicircle:
|
|
109
|
+
--corner-circle: 999px;
|
|
110
|
+
--corner-semicircle: var(--corner-2);
|
|
111
111
|
|
|
112
112
|
/* 阴影 */
|
|
113
113
|
--shadow-1: 0px 2px 4px 0px rgba(0, 0, 0, 0.12);
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
/* 卡片 */
|
|
159
159
|
--pod-card-padding: 20px;
|
|
160
160
|
--pod-card-border-radius: var(--pod-xl-border-radius, 20px);
|
|
161
|
-
--pod-card-border:
|
|
161
|
+
--pod-card-border: 1px solid var(--color-line1-1);
|
|
162
162
|
--pod-card-spacing: var(--pod-md-gap-pad-margin);
|
|
163
163
|
|
|
164
164
|
/* 导航:顶导与侧导布局 */
|
package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md
CHANGED
|
@@ -11,6 +11,7 @@ tokens:
|
|
|
11
11
|
"--pod-app-root-bg-image": "none" # 仅作用于根节点;本主题不使用背景图
|
|
12
12
|
"--pod-page-bg-color": "#FFFFFF" # neutral-gray;白色内容画布
|
|
13
13
|
"--pod-card-bg-color": "#FFFFFF" # neutral-gray;一级内容表面
|
|
14
|
+
"--pod-card-border": "1px solid var(--color-line1-1)"
|
|
14
15
|
"--pod-table-cell-color": "var(--pod-card-bg-color)" # 表格正文跟随卡片表面
|
|
15
16
|
"--pod-shell-padding": "0px"
|
|
16
17
|
navigation:
|
|
@@ -112,8 +113,8 @@ tokens:
|
|
|
112
113
|
"--corner-3": 2px # 气泡、浮层、弹窗外轮廓
|
|
113
114
|
"--corner-4": 2px # 小型嵌入摘要与内部组合容器
|
|
114
115
|
"--corner-5": 2px # 卡片、独立面板与抽屉外轮廓
|
|
115
|
-
"--corner-circle":
|
|
116
|
-
"--corner-semicircle":
|
|
116
|
+
"--corner-circle": 999px # 等宽高头像、图例圆点
|
|
117
|
+
"--corner-semicircle": "var(--corner-2)" # 胶囊按钮、计数标记、进度条圆头
|
|
117
118
|
shadow:
|
|
118
119
|
"--shadow-1": "0px 2px 4px 0px rgba(0, 0, 0, 0.12)"
|
|
119
120
|
"--shadow-2": "0px 4px 20px -1px rgba(0, 0, 0, 0.16)"
|