openyida 2026.7.19 → 2026.7.21

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.
Files changed (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,589 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { schemaError } = require('./errors');
7
+ const { createDefaultRegistry } = require('./resource-registry');
8
+ const { compareCodePoints } = require('./sort');
9
+ const { canonicalizeKey, stableStringify } = require('./normalize-manifest');
10
+
11
+ const STATE_KIND = 'openyida_resource_state';
12
+ const STATE_CONTRACT_VERSION = 1;
13
+ const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
14
+ const ATOMIC_NONCE_PATTERN = /^[a-f0-9]{24}$/;
15
+ const ENVIRONMENT_SCOPE_TOKEN = 'openyida-schema-unscoped-internal';
16
+
17
+ const SENSITIVE_STATE_KEY_CANONICALS = new Set([
18
+ 'apikey',
19
+ 'authorization',
20
+ 'clientsecret',
21
+ 'cookie',
22
+ 'cookies',
23
+ 'credential',
24
+ 'credentials',
25
+ 'csrf',
26
+ 'csrftoken',
27
+ 'header',
28
+ 'headers',
29
+ 'internalpath',
30
+ 'internalurl',
31
+ 'password',
32
+ 'refreshtoken',
33
+ 'secret',
34
+ 'token',
35
+ ]);
36
+
37
+ const REAL_ID_KEY_CANONICALS = new Set([
38
+ 'apptype',
39
+ 'fieldid',
40
+ 'formuuid',
41
+ 'nodeid',
42
+ 'processcode',
43
+ 'processid',
44
+ 'processversion',
45
+ ]);
46
+
47
+ function resolveDefaultStatePath(cwd) {
48
+ return path.join(cwd || process.cwd(), '.cache', 'openyida', 'state.v1.json');
49
+ }
50
+
51
+ function createSha256(value) {
52
+ return 'sha256:' + crypto
53
+ .createHash('sha256')
54
+ .update(String(value === undefined || value === null ? '' : value))
55
+ .digest('hex');
56
+ }
57
+
58
+ function hashStable(value) {
59
+ return createSha256(stableStringify(value));
60
+ }
61
+
62
+ function createEnvironmentIdentity(input = {}, options = {}) {
63
+ if (input && input.environmentKey && input.corpIdHash) {
64
+ assertHash(input.environmentKey, 'environmentKey', '/environment/environmentKey');
65
+ assertHash(input.corpIdHash, 'corpIdHash', '/environment/corpIdHash');
66
+ return {
67
+ environmentKey: input.environmentKey,
68
+ corpIdHash: input.corpIdHash,
69
+ };
70
+ }
71
+
72
+ if (options.allowUnscopedEnvironment === true) {
73
+ return {
74
+ environmentKey: createSha256(`${ENVIRONMENT_SCOPE_TOKEN}:environment`),
75
+ corpIdHash: createSha256(`${ENVIRONMENT_SCOPE_TOKEN}:corp`),
76
+ };
77
+ }
78
+
79
+ const endpoint = input && (input.endpoint !== undefined ? input.endpoint : input.baseUrl);
80
+ const corpId = input && input.corpId;
81
+ if (!hasNonEmptyString(endpoint) || !hasNonEmptyString(corpId)) {
82
+ throw schemaError('SCHEMA_STATE_ENVIRONMENT_REQUIRED', 'State operations require endpoint/environmentKey and corpId/corpIdHash.', {
83
+ path: '/environment',
84
+ });
85
+ }
86
+
87
+ return {
88
+ environmentKey: createSha256(normalizeEndpoint(endpoint)),
89
+ corpIdHash: createSha256(corpId),
90
+ };
91
+ }
92
+
93
+ function createEmptyState(environmentInput, options = {}) {
94
+ const environment = createEnvironmentIdentity(environmentInput, options);
95
+ const state = {
96
+ kind: STATE_KIND,
97
+ contractVersion: STATE_CONTRACT_VERSION,
98
+ revision: 0,
99
+ environment,
100
+ resources: {},
101
+ };
102
+ if (options.manifestHash !== undefined) {
103
+ assertHash(options.manifestHash, 'manifestHash', '/manifestHash');
104
+ state.manifestHash = options.manifestHash;
105
+ }
106
+ validateStateShape(state, {
107
+ registry: options.registry,
108
+ });
109
+ return state;
110
+ }
111
+
112
+ function readState(statePath, options = {}) {
113
+ const fsImpl = options.fsImpl || fs;
114
+ const environment = createEnvironmentIdentity(options.environment, options);
115
+
116
+ let exists;
117
+ try {
118
+ exists = fsImpl.existsSync(statePath);
119
+ } catch (error) {
120
+ throw schemaError('SCHEMA_STATE_READ_FAILED', 'State file could not be read.', {
121
+ details: { operation: 'exists' },
122
+ });
123
+ }
124
+
125
+ if (!exists) {
126
+ return createEmptyState(environment, {
127
+ manifestHash: options.manifestHash,
128
+ registry: options.registry,
129
+ });
130
+ }
131
+
132
+ let raw;
133
+ try {
134
+ raw = fsImpl.readFileSync(statePath, 'utf8');
135
+ } catch (error) {
136
+ throw schemaError('SCHEMA_STATE_READ_FAILED', 'State file could not be read.', {
137
+ details: { operation: 'read' },
138
+ });
139
+ }
140
+
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(raw);
144
+ } catch (error) {
145
+ throw schemaError('SCHEMA_STATE_INVALID', 'State file is not valid JSON.', {
146
+ details: { reason: 'invalid_json' },
147
+ });
148
+ }
149
+
150
+ validateState(parsed, {
151
+ environment,
152
+ registry: options.registry,
153
+ });
154
+ return parsed;
155
+ }
156
+
157
+ function writeStateAtomic(statePath, state, options = {}) {
158
+ const fsImpl = options.fsImpl || fs;
159
+ const environment = createEnvironmentIdentity(options.environment, options);
160
+ const nextState = prepareStateForWrite(state, {
161
+ environment,
162
+ incrementRevision: options.incrementRevision,
163
+ registry: options.registry,
164
+ });
165
+ const dir = path.dirname(statePath);
166
+ const data = stableStringify(nextState) + '\n';
167
+ let fd;
168
+ let tmpPath;
169
+ let tempCreated = false;
170
+
171
+ try {
172
+ tmpPath = createAtomicTempPath(statePath, options);
173
+ fsImpl.mkdirSync(dir, { recursive: true, mode: 0o700 });
174
+ fd = fsImpl.openSync(tmpPath, 'wx', 0o600);
175
+ tempCreated = true;
176
+ fsImpl.writeFileSync(fd, data, 'utf8');
177
+ if (typeof fsImpl.fsyncSync === 'function') {
178
+ fsImpl.fsyncSync(fd);
179
+ }
180
+ fsImpl.closeSync(fd);
181
+ fd = undefined;
182
+ fsImpl.renameSync(tmpPath, statePath);
183
+ fsyncDirectoryBestEffort(fsImpl, dir);
184
+ return nextState;
185
+ } catch (error) {
186
+ if (fd !== undefined) {
187
+ try {
188
+ fsImpl.closeSync(fd);
189
+ } catch (closeError) {
190
+ // Best effort cleanup.
191
+ }
192
+ }
193
+ try {
194
+ if (tempCreated && tmpPath && fsImpl.existsSync(tmpPath)) {
195
+ fsImpl.unlinkSync(tmpPath);
196
+ }
197
+ } catch (unlinkError) {
198
+ // Best effort cleanup.
199
+ }
200
+ throw schemaError('SCHEMA_STATE_WRITE_FAILED', 'State file could not be written atomically.', {
201
+ details: { operation: 'atomic_write' },
202
+ });
203
+ }
204
+ }
205
+
206
+ function createAtomicTempPath(targetPath, options = {}) {
207
+ const dir = path.dirname(targetPath);
208
+ const base = path.basename(targetPath);
209
+ const pid = options.atomicProcessId === undefined ? process.pid : options.atomicProcessId;
210
+ const now = options.atomicNow === undefined ? Date.now() : options.atomicNow;
211
+ const nonce = options.atomicNonce || crypto.randomBytes(12).toString('hex');
212
+ if (
213
+ !Number.isInteger(pid) || pid <= 0 ||
214
+ !Number.isSafeInteger(now) || now < 0 ||
215
+ !ATOMIC_NONCE_PATTERN.test(nonce)
216
+ ) {
217
+ throw new Error('invalid atomic temp identity');
218
+ }
219
+ return path.join(dir, `.${base}.${pid}.${now}.${nonce}.tmp`);
220
+ }
221
+
222
+ function prepareStateForWrite(state, options = {}) {
223
+ const nextState = clonePlain(state);
224
+ validateState(nextState, {
225
+ environment: options.environment,
226
+ registry: options.registry,
227
+ });
228
+ nextState.revision += options.incrementRevision === false ? 0 : 1;
229
+ validateState(nextState, {
230
+ environment: options.environment,
231
+ registry: options.registry,
232
+ });
233
+ return nextState;
234
+ }
235
+
236
+ function validateState(state, options = {}) {
237
+ const environment = options.environment
238
+ ? createEnvironmentIdentity(options.environment, options)
239
+ : createEnvironmentIdentity(options.environmentInput, options);
240
+ validateStateShape(state, {
241
+ registry: options.registry,
242
+ });
243
+ assertEnvironmentMatches(state.environment, environment);
244
+ }
245
+
246
+ function validateStateShape(state, options = {}) {
247
+ const registry = options.registry || createDefaultRegistry();
248
+ if (!state || typeof state !== 'object' || Array.isArray(state)) {
249
+ throw schemaError('SCHEMA_STATE_INVALID', 'State file must be a JSON object.');
250
+ }
251
+ validateForbiddenStateKeys(state);
252
+ assertAllowedKeys(state, ['kind', 'contractVersion', 'revision', 'environment', 'manifestHash', 'resources'], '/');
253
+
254
+ if (state.kind !== STATE_KIND) {
255
+ throw schemaError('SCHEMA_STATE_INVALID', 'State kind must be "openyida_resource_state".', {
256
+ path: '/kind',
257
+ });
258
+ }
259
+ if (state.contractVersion !== STATE_CONTRACT_VERSION) {
260
+ throw schemaError('SCHEMA_STATE_VERSION_UNSUPPORTED', 'State contractVersion is not supported.', {
261
+ path: '/contractVersion',
262
+ details: { contractVersion: state.contractVersion },
263
+ });
264
+ }
265
+ if (!Number.isInteger(state.revision) || state.revision < 0) {
266
+ throw schemaError('SCHEMA_STATE_INVALID', 'State revision must be a non-negative integer.', {
267
+ path: '/revision',
268
+ });
269
+ }
270
+ validateEnvironmentShape(state.environment);
271
+ if (state.manifestHash !== undefined) {
272
+ assertHash(state.manifestHash, 'manifestHash', '/manifestHash');
273
+ }
274
+ validateResourcesShape(state.resources, registry);
275
+ }
276
+
277
+ function upsertResourceState(state, resourceState, options = {}) {
278
+ const nextState = clonePlain(state);
279
+ validateStateShape(nextState, { registry: options.registry });
280
+
281
+ const resourceType = resourceState && resourceState.resourceType;
282
+ const key = resourceState && resourceState.key;
283
+ if (!resourceType || !key) {
284
+ throw schemaError('SCHEMA_STATE_INVALID', 'Resource state must include resourceType and key.');
285
+ }
286
+ if (!nextState.resources[resourceType]) {
287
+ nextState.resources[resourceType] = {};
288
+ }
289
+ nextState.resources[resourceType][key] = buildPersistedResourceState(resourceState);
290
+ validateStateShape(nextState, { registry: options.registry });
291
+ return nextState;
292
+ }
293
+
294
+ function getResourceState(state, resourceType, key) {
295
+ return state &&
296
+ state.resources &&
297
+ state.resources[resourceType] &&
298
+ state.resources[resourceType][key] || null;
299
+ }
300
+
301
+ function buildPersistedResourceState(resourceState) {
302
+ const persisted = {
303
+ adapterVersion: resourceState.adapterVersion,
304
+ bindings: clonePlain(resourceState.bindings || {}),
305
+ };
306
+ if (resourceState.lastAppliedHash !== undefined) {
307
+ assertHash(resourceState.lastAppliedHash, 'lastAppliedHash');
308
+ persisted.lastAppliedHash = resourceState.lastAppliedHash;
309
+ }
310
+ if (resourceState.observedManagedHash !== undefined) {
311
+ assertHash(resourceState.observedManagedHash, 'observedManagedHash');
312
+ persisted.observedManagedHash = resourceState.observedManagedHash;
313
+ }
314
+ if (resourceState.remoteSchemaHash !== undefined) {
315
+ assertHash(resourceState.remoteSchemaHash, 'remoteSchemaHash');
316
+ persisted.remoteSchemaHash = resourceState.remoteSchemaHash;
317
+ }
318
+ if (resourceState.lastApplied !== undefined) {
319
+ persisted.lastApplied = clonePlain(resourceState.lastApplied);
320
+ }
321
+ return persisted;
322
+ }
323
+
324
+ function validateResourcesShape(resources, registry) {
325
+ if (!resources || typeof resources !== 'object' || Array.isArray(resources)) {
326
+ throw schemaError('SCHEMA_STATE_INVALID', 'State resources must be an object.', {
327
+ path: '/resources',
328
+ });
329
+ }
330
+
331
+ for (const resourceType of Object.keys(resources)) {
332
+ const adapter = registry.get(resourceType);
333
+ const resourceMap = resources[resourceType];
334
+ const resourceMapPath = `/resources/${escapePointer(resourceType)}`;
335
+ if (!resourceMap || typeof resourceMap !== 'object' || Array.isArray(resourceMap)) {
336
+ throw schemaError('SCHEMA_STATE_INVALID', 'State resource type entries must be objects.', {
337
+ path: resourceMapPath,
338
+ });
339
+ }
340
+ for (const key of Object.keys(resourceMap)) {
341
+ validateResourceEntry(resourceMap[key], {
342
+ adapter,
343
+ key,
344
+ path: `${resourceMapPath}/${escapePointer(key)}`,
345
+ resourceType,
346
+ });
347
+ }
348
+ }
349
+ }
350
+
351
+ function validateResourceEntry(entry, context) {
352
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
353
+ throw schemaError('SCHEMA_STATE_INVALID', 'State resource entry must be an object.', {
354
+ path: context.path,
355
+ });
356
+ }
357
+ assertAllowedKeys(entry, [
358
+ 'adapterVersion',
359
+ 'bindings',
360
+ 'lastAppliedHash',
361
+ 'observedManagedHash',
362
+ 'remoteSchemaHash',
363
+ 'lastApplied',
364
+ ], context.path);
365
+
366
+ if (!Number.isInteger(entry.adapterVersion) || entry.adapterVersion < 1) {
367
+ throw schemaError('SCHEMA_STATE_INVALID', 'State resource adapterVersion must be a supported integer.', {
368
+ path: `${context.path}/adapterVersion`,
369
+ });
370
+ }
371
+ if (entry.adapterVersion !== context.adapter.adapterVersion) {
372
+ throw schemaError('SCHEMA_STATE_ADAPTER_VERSION_UNSUPPORTED', 'State resource adapterVersion is not supported by the current adapter.', {
373
+ path: `${context.path}/adapterVersion`,
374
+ details: {
375
+ resourceType: context.resourceType,
376
+ key: context.key,
377
+ stateAdapterVersion: entry.adapterVersion,
378
+ adapterVersion: context.adapter.adapterVersion,
379
+ },
380
+ });
381
+ }
382
+ if (!entry.bindings || typeof entry.bindings !== 'object' || Array.isArray(entry.bindings)) {
383
+ throw schemaError('SCHEMA_STATE_INVALID', 'State resource bindings must be an object.', {
384
+ path: `${context.path}/bindings`,
385
+ });
386
+ }
387
+ for (const hashKey of ['lastAppliedHash', 'observedManagedHash', 'remoteSchemaHash']) {
388
+ if (entry[hashKey] !== undefined) {
389
+ assertHash(entry[hashKey], hashKey, `${context.path}/${hashKey}`);
390
+ }
391
+ }
392
+ if (entry.lastApplied !== undefined && (!entry.lastApplied || typeof entry.lastApplied !== 'object' || Array.isArray(entry.lastApplied))) {
393
+ throw schemaError('SCHEMA_STATE_INVALID', 'State lastApplied must be a compact managed object.', {
394
+ path: `${context.path}/lastApplied`,
395
+ });
396
+ }
397
+ if (typeof context.adapter.validateStateResource === 'function') {
398
+ context.adapter.validateStateResource({
399
+ key: context.key,
400
+ resourceType: context.resourceType,
401
+ state: entry,
402
+ path: context.path,
403
+ });
404
+ }
405
+ }
406
+
407
+ function validateEnvironmentShape(environment) {
408
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment)) {
409
+ throw schemaError('SCHEMA_STATE_INVALID', 'State environment must be an object.', {
410
+ path: '/environment',
411
+ });
412
+ }
413
+ assertAllowedKeys(environment, ['environmentKey', 'corpIdHash'], '/environment');
414
+ assertHash(environment.environmentKey, 'environmentKey', '/environment/environmentKey');
415
+ assertHash(environment.corpIdHash, 'corpIdHash', '/environment/corpIdHash');
416
+ }
417
+
418
+ function validateForbiddenStateKeys(value, pathSegments = []) {
419
+ if (!value || typeof value !== 'object') {
420
+ return;
421
+ }
422
+ if (Array.isArray(value)) {
423
+ value.forEach((item, index) => validateForbiddenStateKeys(item, pathSegments.concat(String(index))));
424
+ return;
425
+ }
426
+
427
+ for (const [key, child] of Object.entries(value)) {
428
+ const canonical = canonicalizeKey(key);
429
+ if (SENSITIVE_STATE_KEY_CANONICALS.has(canonical) || isForbiddenRealIdLocation(pathSegments, key, canonical)) {
430
+ throw schemaError('SCHEMA_STATE_FORBIDDEN_FIELD', 'State must not contain credentials, internal paths, or remote IDs outside adapter bindings.', {
431
+ path: formatJsonPointer(pathSegments.concat(key)),
432
+ details: { key },
433
+ });
434
+ }
435
+ validateForbiddenStateKeys(child, pathSegments.concat(key));
436
+ }
437
+ }
438
+
439
+ function isForbiddenRealIdLocation(pathSegments, key, canonical) {
440
+ if (!REAL_ID_KEY_CANONICALS.has(canonical)) {
441
+ return false;
442
+ }
443
+ const segments = pathSegments.concat(key);
444
+ if (segments.length < 5 || segments[0] !== 'resources') {
445
+ return true;
446
+ }
447
+ const resourceType = segments[1];
448
+ if (canonical === 'apptype') {
449
+ return !(
450
+ segments.length === 5 &&
451
+ segments[3] === 'bindings' &&
452
+ segments[4] === key &&
453
+ (resourceType === 'app' || resourceType === 'form' || resourceType === 'page' || resourceType === 'process')
454
+ );
455
+ }
456
+ if (canonical === 'formuuid') {
457
+ return !(
458
+ segments.length === 5 &&
459
+ (resourceType === 'form' || resourceType === 'page' || resourceType === 'process') &&
460
+ segments[3] === 'bindings' &&
461
+ segments[4] === key
462
+ );
463
+ }
464
+ if (canonical === 'fieldid') {
465
+ return !(segments.length === 7 && resourceType === 'form' && segments[3] === 'bindings' && segments[4] === 'fieldBindings' && segments[6] === key);
466
+ }
467
+ if (canonical === 'processcode') {
468
+ return !(
469
+ segments.length === 5 &&
470
+ segments[3] === 'bindings' &&
471
+ segments[4] === key
472
+ );
473
+ }
474
+ if (canonical === 'processid' || canonical === 'processversion') {
475
+ return !(segments.length === 5 && segments[3] === 'bindings' && segments[4] === key);
476
+ }
477
+ if (canonical === 'nodeid') {
478
+ return !(
479
+ segments.length === 7 &&
480
+ segments[3] === 'bindings' &&
481
+ segments[4] === 'nodeBindings' &&
482
+ segments[6] === key
483
+ );
484
+ }
485
+ return true;
486
+ }
487
+
488
+ function assertEnvironmentMatches(actual, expected) {
489
+ if (
490
+ actual.environmentKey !== expected.environmentKey ||
491
+ actual.corpIdHash !== expected.corpIdHash
492
+ ) {
493
+ throw schemaError('SCHEMA_STATE_ENVIRONMENT_MISMATCH', 'State belongs to a different environment.', {
494
+ path: '/environment',
495
+ details: {
496
+ environmentKey: actual.environmentKey,
497
+ corpIdHash: actual.corpIdHash,
498
+ },
499
+ });
500
+ }
501
+ }
502
+
503
+ function assertAllowedKeys(value, allowedKeys, pointer) {
504
+ const allowed = new Set(allowedKeys);
505
+ for (const key of Object.keys(value || {})) {
506
+ if (!allowed.has(key)) {
507
+ throw schemaError('SCHEMA_STATE_INVALID', 'State contains an unknown property.', {
508
+ path: pointer === '/' ? `/${escapePointer(key)}` : `${pointer}/${escapePointer(key)}`,
509
+ details: { property: key },
510
+ });
511
+ }
512
+ }
513
+ }
514
+
515
+ function assertHash(value, name, pointer) {
516
+ if (typeof value !== 'string' || !HASH_PATTERN.test(value)) {
517
+ throw schemaError('SCHEMA_STATE_INVALID', `${name} must be a sha256 hash.`, {
518
+ path: pointer,
519
+ details: { field: name },
520
+ });
521
+ }
522
+ }
523
+
524
+ function hasNonEmptyString(value) {
525
+ return typeof value === 'string' && value.trim().length > 0;
526
+ }
527
+
528
+ function normalizeEndpoint(endpoint) {
529
+ return String(endpoint || '').trim().replace(/\/+$/, '').toLowerCase();
530
+ }
531
+
532
+ function fsyncDirectoryBestEffort(fsImpl, dir) {
533
+ if (typeof fsImpl.openSync !== 'function' || typeof fsImpl.fsyncSync !== 'function') {
534
+ return;
535
+ }
536
+ let dirFd;
537
+ try {
538
+ dirFd = fsImpl.openSync(dir, 'r');
539
+ fsImpl.fsyncSync(dirFd);
540
+ } catch (error) {
541
+ // Directory fsync is not portable across all filesystems.
542
+ } finally {
543
+ if (dirFd !== undefined) {
544
+ try {
545
+ fsImpl.closeSync(dirFd);
546
+ } catch (closeError) {
547
+ // Best effort cleanup.
548
+ }
549
+ }
550
+ }
551
+ }
552
+
553
+ function clonePlain(value) {
554
+ if (value === undefined || value === null || typeof value !== 'object') {
555
+ return value;
556
+ }
557
+ return JSON.parse(JSON.stringify(value));
558
+ }
559
+
560
+ function formatJsonPointer(segments) {
561
+ if (!segments || segments.length === 0) {
562
+ return '/';
563
+ }
564
+ return '/' + segments.map(escapePointer).join('/');
565
+ }
566
+
567
+ function escapePointer(value) {
568
+ return String(value).replace(/~/g, '~0').replace(/\//g, '~1');
569
+ }
570
+
571
+ function compareResourceIds(left, right) {
572
+ return compareCodePoints(`${left.resourceType}:${left.key}`, `${right.resourceType}:${right.key}`);
573
+ }
574
+
575
+ module.exports = {
576
+ STATE_KIND,
577
+ STATE_CONTRACT_VERSION,
578
+ compareResourceIds,
579
+ createEmptyState,
580
+ createEnvironmentIdentity,
581
+ createSha256,
582
+ getResourceState,
583
+ hashStable,
584
+ readState,
585
+ resolveDefaultStatePath,
586
+ upsertResourceState,
587
+ validateState,
588
+ writeStateAtomic,
589
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.7.19",
3
+ "version": "2026.7.21",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -85,7 +85,7 @@
85
85
  "homepage": "https://github.com/openyida/openyida#readme",
86
86
  "dependencies": {
87
87
  "@babel/standalone": "^7.15.1",
88
- "qrcode": "^1.5.4",
88
+ "ajv": "^8.20.0",
89
89
  "uglify-js": "^3.19.3"
90
90
  },
91
91
  "devDependencies": {
@@ -224,28 +224,24 @@ npm install -g openyida@latest
224
224
  若登录态无效,执行:
225
225
 
226
226
  \`\`\`bash
227
- openyida login --browser
227
+ openyida login
228
228
  \`\`\`
229
229
 
230
- 登录完成后再次运行 \`openyida login --check-only --json\` 验证缓存写入,再继续真实资源操作。
230
+ 登录完成后再次运行 \`openyida login --check-only --json\` 验证 token session,再继续真实资源操作。
231
231
 
232
232
  ## Codex Browser 边界
233
233
 
234
234
  Codex App 的 in-app browser / \`@Browser\` 适合打开本地开发服务器、file-backed preview 和无需登录的公开页面,用于截图、点击和检查渲染状态。
235
235
 
236
- 不要把 Codex in-app browser 用作宜搭登录 Cookie 来源:它不支持认证流程、登录态页面、普通浏览器 profile Cookie 导出。
236
+ 不要把 Codex in-app browser 用作宜搭登录 Cookie 来源:OpenYida CLI 登录使用 OAuth loopback + token session,不需要导出或手写 Cookie
237
237
 
238
- 需要登录并获得 CLI Cookie 时,优先运行:
238
+ 需要登录并获得 CLI token session 时,优先运行:
239
239
 
240
240
  \`\`\`bash
241
- openyida login --browser
241
+ openyida login
242
242
  \`\`\`
243
243
 
244
- \`--browser\` 会打开常规本地浏览器并在登录成功后将 Cookie 写入 OpenYida 缓存。只有在需要纯浏览器预览或检查公开页面时才使用 \`@Browser\`。
245
-
246
- 在 Codex 中如果 \`openyida login --codex-poll\` 返回 \`need_corp_selection\`,优先调用 OpenYida MCP 工具
247
- \`select_yida_login_organization\`,传入返回值里的 \`session_file\`。该工具会通过 MCP \`elicitation/create\`
248
- 展示 Codex 原生组织单选控件,并在用户选择后完成 \`openyida login --codex-select\`。
244
+ \`openyida login\` 会通过 OAuth loopback 打开常规本地浏览器;如需抑制自动打开浏览器,可设置环境变量 \`OPENYIDA_NO_BROWSER=1\`。只有在需要纯浏览器预览或检查公开页面时才使用 \`@Browser\`。
249
245
 
250
246
  ## 工作目录
251
247
 
@@ -279,8 +275,8 @@ fast_build 页面源码默认不得使用 \`this.dataSourceMap.*\`,除非本
279
275
  | \`yida-skills/analytics\` | 报表、统计、图表、ECharts、看板、驾驶舱 | \`yida-report\`, \`yida-chart\`, \`yida-dashboard\` |
280
276
  | \`yida-skills/integration\` | 连接器、外部 API、数据源、集成自动化 | \`yida-integration\`, \`yida-connector\`, \`yida-connector-safe-actions\`, \`yida-data-source-connectors\` |
281
277
  | \`yida-skills/access\` | 平台/应用/表单/页面权限、公开访问、分享 | \`yida-corp-manager\`, \`yida-app-permission\`, \`yida-form-permission\`, \`yida-page-config\` |
282
- | \`yida-skills/ops\` | SLS、日志、traceId、Sequence、VOC | \`sls-log-workbench\`, \`yida-db-seq-fix\`, \`yida-voc\` |
283
- | \`yida-skills/agent\` | 大文件写入、导出对话、会议纪要/闪记转 PRD | \`large-file-write\`, \`yida-export-conversation\`, \`yida-flash-note-to-prd\` |
278
+ | \`yida-skills/ops\` | Sequence、VOC | \`yida-db-seq-fix\`, \`yida-voc\` |
279
+ | \`yida-skills/agent\` | 导出对话、会议纪要/闪记转 PRD | \`yida-export-conversation\`, \`yida-flash-note-to-prd\` |
284
280
 
285
281
  ## 执行规则
286
282
 
@@ -12,7 +12,7 @@ const path = require('path');
12
12
  // per-file cap stays fixed to catch accidental large-blob embeds.
13
13
  const MAX_TARBALL_BYTES = 1792 * 1024;
14
14
  const MAX_UNPACKED_BYTES = 5632 * 1024;
15
- const MAX_ENTRY_COUNT = 420;
15
+ const MAX_ENTRY_COUNT = 433;
16
16
  const MAX_SINGLE_FILE_BYTES = 512 * 1024;
17
17
 
18
18
  function formatBytes(bytes) {