openyida 2026.9.22-beta.2 → 2026.9.22-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,12 +10,19 @@ const FAILURE_STATUSES = new Set([
10
10
  'dws_identity_unmapped', 'dws_upstream_unavailable', 'dws_policy_rejected', 'dws_session_expired',
11
11
  ]);
12
12
 
13
- function failure(reason) {
13
+ function failure(reason, extra = {}) {
14
14
  return new CliError(`DWS sync failed: ${reason}`, {
15
- code: 'DWS_SYNC_FAILED', details: { reason },
15
+ code: 'DWS_SYNC_FAILED', details: { reason, ...extra },
16
16
  });
17
17
  }
18
18
 
19
+ function rateLimitFailure(response, headers = {}) {
20
+ const data = response && (response.data || response.content || response);
21
+ if (!data || !['dws_rate_limited', 'dws_rate_limit_unavailable'].includes(data.status)) { return null; }
22
+ const delay = Number(data.retry_after ?? headers['retry-after']);
23
+ return failure(data.status, { retry_after: Number.isInteger(delay) && delay > 0 && delay <= 60 ? delay : 1 });
24
+ }
25
+
19
26
  function readHandoff(input = process.stdin, timeoutMs = 10000) {
20
27
  return new Promise((resolve, reject) => {
21
28
  if (input.isTTY) { reject(failure('STDIN_REQUIRED')); return; }
@@ -80,11 +87,13 @@ async function syncDws(options = {}) {
80
87
  response = await requestJson('POST', `${target.origin}/openapi/cli/v1/auth/dws/token`, {
81
88
  ...(expectedCorp ? { corpId: expectedCorp } : {}), environment: handoff.environment,
82
89
  }, { Authorization: `Bearer ${handoff.accessToken}` }, { timeoutMs: 30000, maxResponseBytes: 65536 });
83
- } catch {
84
- throw failure('EXCHANGE_REQUEST_FAILED');
90
+ } catch (error) {
91
+ throw rateLimitFailure(error && error.payload, (error && error.headers) || {}) || failure('EXCHANGE_REQUEST_FAILED');
85
92
  } finally {
86
93
  delete handoff.accessToken;
87
94
  }
95
+ const limited = rateLimitFailure(response);
96
+ if (limited) { throw limited; }
88
97
  const data = response && (response.data || response.content || response);
89
98
  if (!data || response.success === false || response.ok === false || data.status !== 'ok') {
90
99
  throw failure(data && FAILURE_STATUSES.has(data.status) ? data.status : 'EXCHANGE_REJECTED');
@@ -96,7 +96,9 @@ function compactSkillsDiagnostics(skillsDiagnostics) {
96
96
  reason: candidate.reason,
97
97
  exists: candidate.exists,
98
98
  writable: candidate.writable,
99
+ readable: candidate.readable,
99
100
  usable: candidate.usable,
101
+ installable: candidate.installable,
100
102
  workspace_only: candidate.workspace_only,
101
103
  }
102
104
  : null;
@@ -105,6 +107,7 @@ function compactSkillsDiagnostics(skillsDiagnostics) {
105
107
  schema_version: skillsDiagnostics.schema_version,
106
108
  active_tool: skillsDiagnostics.active_tool,
107
109
  selected: compactCandidate(skillsDiagnostics.selected),
110
+ install_target: compactCandidate(skillsDiagnostics.install_target),
108
111
  candidates: (skillsDiagnostics.candidates || []).map(compactCandidate),
109
112
  diagnostics: skillsDiagnostics.diagnostics || {},
110
113
  };
package/lib/core/copy.js CHANGED
@@ -390,7 +390,7 @@ function run(args = process.argv.slice(3)) {
390
390
  // 若宿主用户级目录不可写,按 capabilities 诊断降级到工作区可发现目录。
391
391
  const activeResult = envResults.find((r) => r.isActive);
392
392
  const skillsDiagnostics = buildSkillsDiagnostics({ ...runtimeOptions, projectResolution });
393
- const selectedSkills = skillsDiagnostics.selected;
393
+ const selectedSkills = skillsDiagnostics.install_target;
394
394
 
395
395
  if (selectedSkills && selectedSkills.path) {
396
396
  assertCopyDestinationSafe(packageYidaSkillsDir, selectedSkills.path);
package/lib/core/utils.js CHANGED
@@ -670,6 +670,14 @@ function buildSkillsCandidate(destPath, source, scope, reason) {
670
670
  const parentDir = path.dirname(destPath);
671
671
  const nearestWritableBase = nearestExistingAncestor(parentDir);
672
672
  const writable = canWritePath(destPath);
673
+ const entryPath = path.join(destPath, 'SKILL.md');
674
+ let readable = false;
675
+ try {
676
+ readable = fs.statSync(entryPath).isFile();
677
+ fs.accessSync(entryPath, fs.constants.R_OK);
678
+ } catch {
679
+ readable = false;
680
+ }
673
681
  return {
674
682
  path: destPath,
675
683
  source,
@@ -680,7 +688,9 @@ function buildSkillsCandidate(destPath, source, scope, reason) {
680
688
  parent_exists: fs.existsSync(parentDir),
681
689
  nearest_existing_parent: nearestWritableBase,
682
690
  writable,
683
- usable: writable,
691
+ readable,
692
+ usable: readable,
693
+ installable: scope !== 'package' && writable,
684
694
  workspace_only: scope === 'workspace',
685
695
  };
686
696
  }
@@ -787,7 +797,13 @@ function buildSkillsDiagnostics(options = {}) {
787
797
  );
788
798
  }
789
799
 
790
- const selected = candidates.find((candidate) => candidate.usable) || candidates[0] || null;
800
+ // Bundled documentation is a read fallback, never an installation destination.
801
+ addCandidate(
802
+ options.bundledSkillsPath || path.resolve(__dirname, '../../yida-skills'),
803
+ 'package:yida-skills', 'package', 'bundled_skills_read_fallback'
804
+ );
805
+ const selected = candidates.find((candidate) => candidate.usable) || null;
806
+ const installTarget = candidates.find((candidate) => candidate.installable) || null;
791
807
  const diagnostics = {
792
808
  config_content_error: configSkillPaths.error,
793
809
  fallback: selected && selected.workspace_only
@@ -808,6 +824,7 @@ function buildSkillsDiagnostics(options = {}) {
808
824
  }
809
825
  : null,
810
826
  selected,
827
+ install_target: installTarget,
811
828
  candidates,
812
829
  diagnostics,
813
830
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.9.22-beta.2",
3
+ "version": "2026.9.22-beta.3",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",