weaver-work-cli 0.1.8 → 0.1.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.
- package/README.md +25 -17
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +435 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +34 -19
- package/dist/internal/e10/auth/session.js +74 -2
- package/dist/internal/e10/auth/xiaoe.js +359 -94
- package/dist/internal/e10/context.js +18 -6
- package/dist/internal/e10/request-runtime.js +8 -1
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +1227 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +1024 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +698 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/manifest.js +16 -9
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/hrm/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/operations/resolve.js +449 -0
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +11 -4
- package/dist/shortcuts/workflow/operations/form.js +1016 -590
- package/dist/shortcuts/workflow/operations/read.js +913 -231
- package/dist/shortcuts/workflow/operations/shared.js +21 -0
- package/dist/shortcuts/workflow/operations/write.js +750 -52
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +4 -2
- package/docs/hrm.md +53 -11
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +1 -1
- package/scripts/package-skill.mjs +32 -20
- package/scripts/xiaoe_env.py +118 -0
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/hrm.md +2 -1
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +217 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +173 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +122 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +440 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5694 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +104 -9
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +114 -5
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +167 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +42 -34
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +16 -43
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +41 -46
- package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +83 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +40 -27
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +74 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { meetingConfirmationError, meetingPartialError, meetingValidationError } from '../../errors.js';
|
|
3
|
+
import { meetingSuccess, SCHEMA_ID } from '../types.js';
|
|
4
|
+
import { assertDateTimeMinute, toIdString } from '../shared.js';
|
|
5
|
+
import { isWriteKind } from './catalog.js';
|
|
6
|
+
/** EB(eBuilder formdata)通用路径前缀(资料核实)。 */
|
|
7
|
+
export const EB_BASE = '/api/ebuilder/form/formdata/v2';
|
|
8
|
+
const EB_READ_OP = { list: 'getFormDataList', detail: 'getFormDataByPk', get: 'getFormDataList' };
|
|
9
|
+
const EB_WRITE_OP = { create: 'saveFormData', update: 'updateFormData', delete: 'deleteFormData' };
|
|
10
|
+
const CONTINUATION_TTL_SECONDS = 600;
|
|
11
|
+
/**
|
|
12
|
+
* EB 响应成功判定。
|
|
13
|
+
* EB 的响应体与标准版不同:成功时顶层常为 `{datajson:{...}}`(可能不带 status/code),
|
|
14
|
+
* 失败时返回 `code=100000/100001`;写操作以 `datajson.status` 表示成败。
|
|
15
|
+
* 因此不能直接套用标准版的 `isBusinessSuccess`(会误判成功响应为业务失败)。
|
|
16
|
+
*/
|
|
17
|
+
export function ebBusinessSuccess(value) {
|
|
18
|
+
if (!value || typeof value !== 'object')
|
|
19
|
+
return false;
|
|
20
|
+
if (value.code !== undefined && value.code !== null)
|
|
21
|
+
return Number(value.code) === 200;
|
|
22
|
+
if (value.datajson && typeof value.datajson === 'object') {
|
|
23
|
+
const status = value.datajson.status;
|
|
24
|
+
return status !== false;
|
|
25
|
+
}
|
|
26
|
+
return value.status === true;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 稳定序列化:对象递归**按键名排序**。
|
|
30
|
+
*
|
|
31
|
+
* `fingerprint` 用于判断「apply 的提交内容是否与 prepare 快照一致」,必须**只对内容敏感、
|
|
32
|
+
* 对键顺序不敏感** —— 否则同一份 payload 只要键顺序不同就会被误判为 `target_changed`。
|
|
33
|
+
* 2026-09-20 真机首次 apply 即踩到:prepare 与 apply 两条路径产出的键顺序不同
|
|
34
|
+
* (`hrm_members` 位置不同),字段与值完全相同却被拒绝,导致 EB 建会无法完成。
|
|
35
|
+
*/
|
|
36
|
+
function stableStringify(value) {
|
|
37
|
+
if (Array.isArray(value))
|
|
38
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
39
|
+
if (value && typeof value === 'object') {
|
|
40
|
+
const entries = Object.entries(value)
|
|
41
|
+
.filter(([, item]) => item !== undefined)
|
|
42
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
43
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(',')}}`;
|
|
44
|
+
}
|
|
45
|
+
return JSON.stringify(value ?? null);
|
|
46
|
+
}
|
|
47
|
+
function fingerprint(value) {
|
|
48
|
+
return createHash('sha256').update(stableStringify(value)).digest('hex').slice(0, 32);
|
|
49
|
+
}
|
|
50
|
+
function requireConfirm(args, action) {
|
|
51
|
+
if (args.confirm !== true) {
|
|
52
|
+
throw meetingConfirmationError(action, { hint: '重新调用时传入 confirm=true 与 prepare 返回的 continuation' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function verifyLease(args, action, env) {
|
|
56
|
+
const lease = await env.continuation.verify(args.continuation, action);
|
|
57
|
+
const session = await env.host.getSession();
|
|
58
|
+
if (lease.baseUrl !== session.baseUrl || lease.profile !== session.profile || lease.userId !== session.userId) {
|
|
59
|
+
throw meetingValidationError('context_mismatch', '确认上下文已变化(租户或登录账号不同),请重新执行 prepare');
|
|
60
|
+
}
|
|
61
|
+
return lease;
|
|
62
|
+
}
|
|
63
|
+
function isNetworkError(error) {
|
|
64
|
+
return error instanceof Error && error.type === 'network';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 查询接口 body(list/count):必须带 datajson,筛选走 URL query。
|
|
68
|
+
*
|
|
69
|
+
* ⚠️ `mainTable` 节点**必须存在**(空对象即可):缺失时 EB 服务端直接返回
|
|
70
|
+
* “mainTable解析异常,请参照文档修正数据格式”(2026-09-20 真机实测)。
|
|
71
|
+
* 该缺陷曾导致 CLI 的 EB 读操作全线失败,并连带使 `meeting.env.detect` 的 EB 冒烟失败、
|
|
72
|
+
* 环境被误判为 standard 版。
|
|
73
|
+
*/
|
|
74
|
+
export function queryBody(userId) {
|
|
75
|
+
return { datajson: { mainTable: {}, header: { operator: userId } } };
|
|
76
|
+
}
|
|
77
|
+
/** 详情接口 body(getFormDataByPk):顶层 mainTable 传 id。 */
|
|
78
|
+
function detailBody(userId, id) {
|
|
79
|
+
return { datajson: { mainTable: { id }, header: { operator: userId } } };
|
|
80
|
+
}
|
|
81
|
+
/** 写操作 body:datas 数组包裹 mainTable(漏用 datas 会报 datas 节点未找到)。 */
|
|
82
|
+
function writeBody(userId, mainTable) {
|
|
83
|
+
return {
|
|
84
|
+
datajson: {
|
|
85
|
+
operationinfo: { printCost: false, asyncPostProcess: false, asyncMark: false },
|
|
86
|
+
datas: [{ mainTable }],
|
|
87
|
+
header: { operator: userId },
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** 从 EB 响应中取数据行数组(兼容 datajson.datas / data.displayData / datajson.data)。 */
|
|
92
|
+
export function extractEbRows(response) {
|
|
93
|
+
const value = response;
|
|
94
|
+
const dj = value?.datajson ?? value?.data ?? {};
|
|
95
|
+
const raw = dj?.datas ?? dj?.data ?? dj?.displayData ?? (Array.isArray(dj) ? dj : undefined);
|
|
96
|
+
if (Array.isArray(raw)) {
|
|
97
|
+
const total = typeof dj?.total === 'number' ? dj.total : typeof value?.total === 'number' ? value.total : undefined;
|
|
98
|
+
return { rows: raw, ...(total === undefined ? {} : { total }) };
|
|
99
|
+
}
|
|
100
|
+
return { rows: [] };
|
|
101
|
+
}
|
|
102
|
+
/** 从 count 响应中取总数(datajson.datas 为数值)。 */
|
|
103
|
+
export function extractEbCount(response) {
|
|
104
|
+
const value = response;
|
|
105
|
+
const dj = value?.datajson ?? value?.data ?? {};
|
|
106
|
+
const raw = dj?.datas ?? dj?.total ?? value?.total;
|
|
107
|
+
if (typeof raw === 'number')
|
|
108
|
+
return raw;
|
|
109
|
+
if (typeof raw === 'string' && /^\d+$/u.test(raw))
|
|
110
|
+
return Number(raw);
|
|
111
|
+
if (Array.isArray(raw))
|
|
112
|
+
return raw.length;
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
function buildReadInputSchema(endpoint) {
|
|
116
|
+
const properties = {};
|
|
117
|
+
if (endpoint.kind === 'detail') {
|
|
118
|
+
properties.id = { ...SCHEMA_ID, description: '数据主键 id' };
|
|
119
|
+
}
|
|
120
|
+
else if (endpoint.kind === 'list') {
|
|
121
|
+
for (const param of endpoint.queryParams ?? []) {
|
|
122
|
+
properties[param] = { type: 'string', description: `EB 筛选参数(URL 占位符名 ${param})` };
|
|
123
|
+
}
|
|
124
|
+
properties.pageNo = { type: 'integer', minimum: 1, description: '页码,默认 1' };
|
|
125
|
+
properties.pageSize = { type: 'integer', minimum: 1, maximum: 200, description: '每页条数,默认 20' };
|
|
126
|
+
if (endpoint.countPk) {
|
|
127
|
+
properties.withCount = { type: 'boolean', description: '是否先查总数(默认 true,条件与列表一致)' };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
type: 'object',
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties,
|
|
134
|
+
...(endpoint.kind === 'detail' ? { required: ['id'] } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function buildReadOperation(endpoint) {
|
|
138
|
+
const readOp = EB_READ_OP[endpoint.kind];
|
|
139
|
+
const capability = `eb.${endpoint.domain}`;
|
|
140
|
+
return {
|
|
141
|
+
name: endpoint.operation,
|
|
142
|
+
resource: 'eb',
|
|
143
|
+
method: endpoint.operation,
|
|
144
|
+
action: endpoint.operation,
|
|
145
|
+
summary: endpoint.summary,
|
|
146
|
+
capability,
|
|
147
|
+
risk: 'read',
|
|
148
|
+
inputSchema: buildReadInputSchema(endpoint),
|
|
149
|
+
handler: async (args, env) => {
|
|
150
|
+
const session = await env.host.getSession();
|
|
151
|
+
const acceptBusiness = ebBusinessSuccess;
|
|
152
|
+
const path = `${EB_BASE}/${readOp}/${endpoint.interfacePk}`;
|
|
153
|
+
if (endpoint.kind === 'detail') {
|
|
154
|
+
const id = toIdString(args.id, 'id');
|
|
155
|
+
const response = await env.host.request({
|
|
156
|
+
method: 'POST',
|
|
157
|
+
path,
|
|
158
|
+
operation: endpoint.operation,
|
|
159
|
+
body: detailBody(session.userId, id),
|
|
160
|
+
acceptBusiness,
|
|
161
|
+
});
|
|
162
|
+
const data = response?.datajson?.mainTable ?? response?.datajson ?? response;
|
|
163
|
+
return meetingSuccess(endpoint.operation, { id, data }, endpoint.notes ? { meta: { notes: endpoint.notes } } : {});
|
|
164
|
+
}
|
|
165
|
+
const query = {};
|
|
166
|
+
for (const param of endpoint.queryParams ?? []) {
|
|
167
|
+
const value = args[param];
|
|
168
|
+
if (value !== undefined && value !== null && value !== '')
|
|
169
|
+
query[param] = value;
|
|
170
|
+
}
|
|
171
|
+
if (endpoint.kind === 'get') {
|
|
172
|
+
const response = await env.host.request({
|
|
173
|
+
method: 'POST',
|
|
174
|
+
path,
|
|
175
|
+
operation: endpoint.operation,
|
|
176
|
+
body: queryBody(session.userId),
|
|
177
|
+
acceptBusiness,
|
|
178
|
+
});
|
|
179
|
+
const { rows } = extractEbRows(response);
|
|
180
|
+
const data = rows[0] ?? response?.datajson ?? null;
|
|
181
|
+
return meetingSuccess(endpoint.operation, { data, count: rows.length }, endpoint.notes ? { meta: { notes: endpoint.notes } } : {});
|
|
182
|
+
}
|
|
183
|
+
const pageNo = typeof args.pageNo === 'number' ? args.pageNo : 1;
|
|
184
|
+
const pageSize = typeof args.pageSize === 'number' ? args.pageSize : 20;
|
|
185
|
+
let total;
|
|
186
|
+
if (endpoint.countPk && args.withCount !== false) {
|
|
187
|
+
const countResponse = await env.host.request({
|
|
188
|
+
method: 'POST',
|
|
189
|
+
path: `${EB_BASE}/getFormDataCount/${endpoint.countPk}`,
|
|
190
|
+
operation: endpoint.operation,
|
|
191
|
+
query,
|
|
192
|
+
body: queryBody(session.userId),
|
|
193
|
+
acceptBusiness,
|
|
194
|
+
});
|
|
195
|
+
total = extractEbCount(countResponse);
|
|
196
|
+
}
|
|
197
|
+
const response = await env.host.request({
|
|
198
|
+
method: 'POST',
|
|
199
|
+
path,
|
|
200
|
+
operation: endpoint.operation,
|
|
201
|
+
query: { ...query, pageNo, pageSize },
|
|
202
|
+
body: queryBody(session.userId),
|
|
203
|
+
acceptBusiness,
|
|
204
|
+
});
|
|
205
|
+
const { rows, total: listTotal } = extractEbRows(response);
|
|
206
|
+
return meetingSuccess(endpoint.operation, {
|
|
207
|
+
total: total ?? listTotal ?? rows.length,
|
|
208
|
+
pageNo,
|
|
209
|
+
pageSize,
|
|
210
|
+
count: rows.length,
|
|
211
|
+
rows,
|
|
212
|
+
filters: query,
|
|
213
|
+
}, endpoint.notes ? { meta: { notes: endpoint.notes } } : {});
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** 计算小时差(支持小数,如 1.5),用于 create_meeting 的 mt_hours。 */
|
|
218
|
+
function diffHours(begin, end) {
|
|
219
|
+
const parse = (text) => {
|
|
220
|
+
const matched = text.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/u);
|
|
221
|
+
if (!matched)
|
|
222
|
+
return undefined;
|
|
223
|
+
const [, y, mo, d, h, mi] = matched;
|
|
224
|
+
return Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi));
|
|
225
|
+
};
|
|
226
|
+
const from = parse(begin);
|
|
227
|
+
const to = parse(end);
|
|
228
|
+
if (from === undefined || to === undefined)
|
|
229
|
+
return undefined;
|
|
230
|
+
const hours = (to - from) / 3_600_000;
|
|
231
|
+
return hours > 0 ? Math.round(hours * 100) / 100 : undefined;
|
|
232
|
+
}
|
|
233
|
+
/** 写操作字段归一化:白名单过滤 + 日期格式校验 + 必填校验 + 会议建会特化默认值。 */
|
|
234
|
+
function normalizeEbWrite(endpoint, args, userId) {
|
|
235
|
+
const fields = endpoint.kind === 'delete' ? ['id'] : endpoint.fields ?? [];
|
|
236
|
+
const out = {};
|
|
237
|
+
for (const field of fields) {
|
|
238
|
+
const value = args[field];
|
|
239
|
+
if (value === undefined || value === null || value === '')
|
|
240
|
+
continue;
|
|
241
|
+
if ((endpoint.dateFields ?? []).includes(field)) {
|
|
242
|
+
out[field] = assertDateTimeMinute(value, field);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
out[field] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (endpoint.interfacePk === 'create_meeting') {
|
|
249
|
+
if (out.caller === undefined)
|
|
250
|
+
out.caller = userId;
|
|
251
|
+
if (out.contacter === undefined)
|
|
252
|
+
out.contacter = userId;
|
|
253
|
+
const hours = typeof out.begin_datetime === 'string' && typeof out.end_datetime === 'string'
|
|
254
|
+
? diffHours(out.begin_datetime, out.end_datetime)
|
|
255
|
+
: undefined;
|
|
256
|
+
if (hours !== undefined)
|
|
257
|
+
out.mt_hours = hours;
|
|
258
|
+
}
|
|
259
|
+
const required = endpoint.kind === 'delete' ? ['id'] : endpoint.requiredFields ?? [];
|
|
260
|
+
const missing = required.filter((field) => out[field] === undefined);
|
|
261
|
+
if (missing.length > 0) {
|
|
262
|
+
throw meetingValidationError('missing_required_field', `缺少必填字段:${missing.join(', ')}`, { missing });
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function buildWriteInputSchema(endpoint, includeConfirm) {
|
|
267
|
+
const properties = {};
|
|
268
|
+
if (includeConfirm) {
|
|
269
|
+
properties.confirm = { const: true };
|
|
270
|
+
properties.continuation = { type: 'string', minLength: 1 };
|
|
271
|
+
}
|
|
272
|
+
if (endpoint.kind === 'delete') {
|
|
273
|
+
properties.id = { ...SCHEMA_ID, description: '数据主键 id(必填)' };
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
for (const field of endpoint.fields ?? []) {
|
|
277
|
+
const isDate = (endpoint.dateFields ?? []).includes(field);
|
|
278
|
+
const required = (endpoint.requiredFields ?? []).includes(field);
|
|
279
|
+
properties[field] = isDate
|
|
280
|
+
? { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$', description: `${field}(yyyy-MM-dd HH:mm,不带秒)` }
|
|
281
|
+
: { type: 'string', description: `${field}${required ? '(必填)' : ''}` };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const requiredBase = endpoint.kind === 'delete' ? ['id'] : endpoint.requiredFields ?? [];
|
|
285
|
+
return {
|
|
286
|
+
type: 'object',
|
|
287
|
+
additionalProperties: false,
|
|
288
|
+
properties,
|
|
289
|
+
required: includeConfirm ? ['confirm', 'continuation', ...requiredBase] : requiredBase,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function buildWriteOperations(endpoint, preflight) {
|
|
293
|
+
const prepareName = `${endpoint.operation}.prepare`;
|
|
294
|
+
const applyName = `${endpoint.operation}.apply`;
|
|
295
|
+
const writeOp = EB_WRITE_OP[endpoint.kind];
|
|
296
|
+
const path = `${EB_BASE}/${writeOp}/${endpoint.interfacePk}`;
|
|
297
|
+
const capability = `eb.${endpoint.domain}`;
|
|
298
|
+
const acceptBusiness = ebBusinessSuccess;
|
|
299
|
+
const prepare = {
|
|
300
|
+
name: prepareName,
|
|
301
|
+
resource: 'eb',
|
|
302
|
+
method: prepareName,
|
|
303
|
+
action: prepareName,
|
|
304
|
+
summary: `准备${endpoint.summary}(生成确认预览,不写入)`,
|
|
305
|
+
capability,
|
|
306
|
+
risk: 'read-before-write',
|
|
307
|
+
requiresConfirmation: true,
|
|
308
|
+
inputSchema: buildWriteInputSchema(endpoint, false),
|
|
309
|
+
handler: async (args, env) => {
|
|
310
|
+
const session = await env.host.getSession();
|
|
311
|
+
const payload = normalizeEbWrite(endpoint, args, session.userId);
|
|
312
|
+
// EB 版建会缺少强制冲突检测会重复占用会议室/参会人撞会,故 prepare 阶段由钩子补齐。
|
|
313
|
+
const warnings = preflight ? await preflight({ endpoint, args, payload, env }) : [];
|
|
314
|
+
const token = await env.continuation.issue({
|
|
315
|
+
operation: applyName,
|
|
316
|
+
baseUrl: session.baseUrl,
|
|
317
|
+
profile: session.profile,
|
|
318
|
+
userId: session.userId,
|
|
319
|
+
payload,
|
|
320
|
+
fingerprint: fingerprint(payload),
|
|
321
|
+
}, CONTINUATION_TTL_SECONDS * 1000);
|
|
322
|
+
return meetingSuccess(prepareName, {
|
|
323
|
+
status: 'AWAITING_CONFIRMATION',
|
|
324
|
+
preview: {
|
|
325
|
+
interfacePk: endpoint.interfacePk,
|
|
326
|
+
kind: endpoint.kind,
|
|
327
|
+
body: payload,
|
|
328
|
+
path,
|
|
329
|
+
notes: endpoint.notes ?? [],
|
|
330
|
+
},
|
|
331
|
+
continuation: token,
|
|
332
|
+
expiresInSeconds: CONTINUATION_TTL_SECONDS,
|
|
333
|
+
}, warnings.length > 0 ? { warnings } : {});
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
const apply = {
|
|
337
|
+
name: applyName,
|
|
338
|
+
resource: 'eb',
|
|
339
|
+
method: applyName,
|
|
340
|
+
action: applyName,
|
|
341
|
+
summary: `提交${endpoint.summary}(${writeOp},body 用 datas 数组);必须在用户明确确认后调用`,
|
|
342
|
+
capability,
|
|
343
|
+
risk: 'high-risk-write',
|
|
344
|
+
requiresConfirmation: true,
|
|
345
|
+
inputSchema: buildWriteInputSchema(endpoint, true),
|
|
346
|
+
handler: async (args, env) => {
|
|
347
|
+
requireConfirm(args, applyName);
|
|
348
|
+
const lease = await verifyLease(args, applyName, env);
|
|
349
|
+
const session = await env.host.getSession();
|
|
350
|
+
const payload = normalizeEbWrite(endpoint, args, session.userId);
|
|
351
|
+
if (lease.fingerprint !== fingerprint(payload)) {
|
|
352
|
+
// 带上两侧快照:`target_changed` 否则无从判断到底哪个字段变了(排查成本极高)。
|
|
353
|
+
throw meetingValidationError('target_changed', '提交内容与 prepare 时的快照不一致,请重新执行 prepare', { expected: lease.payload, actual: payload });
|
|
354
|
+
}
|
|
355
|
+
let response;
|
|
356
|
+
try {
|
|
357
|
+
response = await env.host.request({
|
|
358
|
+
method: 'POST',
|
|
359
|
+
path,
|
|
360
|
+
operation: applyName,
|
|
361
|
+
body: writeBody(session.userId, payload),
|
|
362
|
+
acceptBusiness,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (isNetworkError(error)) {
|
|
367
|
+
const written = endpoint.kind === 'delete' ? '删除' : '写入';
|
|
368
|
+
throw meetingPartialError(`${endpoint.interfacePk} ${written}请求发送后连接中断,是否已生效不确定,禁止自动重试,请回查确认后再决定`, { attempted: { interfacePk: endpoint.interfacePk, body: payload } }, { stage: endpoint.operation });
|
|
369
|
+
}
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
const dj = response?.datajson ?? {};
|
|
373
|
+
if (dj.status === false) {
|
|
374
|
+
throw meetingValidationError('eb_write_not_applied', `${endpoint.interfacePk} 返回 status=false,服务端未生效,请核对字段与权限后重试`);
|
|
375
|
+
}
|
|
376
|
+
return meetingSuccess(applyName, {
|
|
377
|
+
status: 'COMPLETE',
|
|
378
|
+
interfacePk: endpoint.interfacePk,
|
|
379
|
+
dataIds: dj.dataIds ?? dj.ids ?? undefined,
|
|
380
|
+
result: response,
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
return [prepare, apply];
|
|
385
|
+
}
|
|
386
|
+
/** 把声明式目录转换为可注册的 operation 列表(读=1 个,写=prepare+apply 2 个)。 */
|
|
387
|
+
export function buildEbOperations(endpoints, options = {}) {
|
|
388
|
+
return endpoints.flatMap((endpoint) => (isWriteKind(endpoint.kind) ? buildWriteOperations(endpoint, options.preflight) : [buildReadOperation(endpoint)]));
|
|
389
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EB_ENDPOINTS } from './catalog.js';
|
|
2
|
+
import { buildEbOperations } from './factory.js';
|
|
3
|
+
import { ebMeetingWritePreflight, meetingEbConflictOperations } from './conflict.js';
|
|
4
|
+
/**
|
|
5
|
+
* 会议 EB 版(eBuilder)operations。
|
|
6
|
+
*
|
|
7
|
+
* 目录 `catalog.ts` 覆盖 EB 版全部 interfacePk(会议 6.x / 会议室 2.x / 占用 5.x /
|
|
8
|
+
* 签到 1.x / 回执 4.x / 纪要 3.x / 会议类型 3.5 / 议题类型 8.x / 会议议题 9.x / 应用设置 7.x),
|
|
9
|
+
* 读操作 1 个 interfacePk → 1 个 operation,写操作 → prepare + apply 两个 operation
|
|
10
|
+
* (沿用标准版的确认链约定)。
|
|
11
|
+
*
|
|
12
|
+
* 冲突检测(2026-09-20 补齐):
|
|
13
|
+
* - `ebMeetingWritePreflight` 挂到 prepare 阶段 → 建会/变更前强制执行会议室与参会人冲突检测
|
|
14
|
+
* (与标准版 create.prepare 的 runMandatoryConflictChecks 语义对齐:命中禁止提交直接拒绝、
|
|
15
|
+
* 仅提醒只给 warnings、检测失败视为冲突);
|
|
16
|
+
* - `meetingEbConflictOperations` 另提供「只查不建」的 `meeting.eb.conflict.room` /
|
|
17
|
+
* `meeting.eb.conflict.member`(后者为 2 次请求方案,无 N+1)。
|
|
18
|
+
*/
|
|
19
|
+
export const meetingEbOperations = [
|
|
20
|
+
...buildEbOperations(EB_ENDPOINTS, { preflight: ebMeetingWritePreflight }),
|
|
21
|
+
...meetingEbConflictOperations,
|
|
22
|
+
];
|
|
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
import { meetingValidationError } from '../errors.js';
|
|
6
6
|
import { meetingSuccess, SCHEMA_ID } from './types.js';
|
|
7
|
+
import { ebBusinessSuccess } from './eb/factory.js';
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// 环境模式探测(EB 版 / 标准版)——按 baseUrl 固化缓存,对齐 standalone 行为
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
@@ -87,7 +88,13 @@ const meetingEnvDetect = {
|
|
|
87
88
|
method: 'POST',
|
|
88
89
|
path: EB_SMOKE_PATH,
|
|
89
90
|
operation: 'meeting.env.detect',
|
|
90
|
-
|
|
91
|
+
// EB list 的 datajson 内必须出现 mainTable 节点(空对象即可):缺失时服务端报
|
|
92
|
+
// “mainTable解析异常,请参照文档修正数据格式”,冒烟会**假失败**并把 EB 环境误判为
|
|
93
|
+
// 标准版(2026-09-20 真机复现)。
|
|
94
|
+
body: { datajson: { mainTable: {}, header: { operator: session.userId } } },
|
|
95
|
+
// EB 响应不含 code/status(形如 {datajson:{datas:[...]}}),默认的 isBusinessSuccess
|
|
96
|
+
// 会判为 false → 冒烟假失败 → EB 环境被误判为标准版。
|
|
97
|
+
acceptBusiness: ebBusinessSuccess,
|
|
91
98
|
});
|
|
92
99
|
const warning = await writeCache(ctx, session.baseUrl, 'eb', EB_SMOKE_PATH);
|
|
93
100
|
return meetingSuccess('meeting.env.detect', { mode: 'eb', smokePath: EB_SMOKE_PATH, fromCache: false }, warning ? { warnings: [warning] } : undefined);
|
|
@@ -9,6 +9,7 @@ import { meetingCreateOperations } from './create.js';
|
|
|
9
9
|
import { meetingLifecycleOperations } from './lifecycle.js';
|
|
10
10
|
import { meetingReceiptOperations } from './receipts.js';
|
|
11
11
|
import { meetingSignOperations } from './signs.js';
|
|
12
|
+
import { meetingEbOperations } from './eb/index.js';
|
|
12
13
|
function buildRegistry() {
|
|
13
14
|
const registry = new Map();
|
|
14
15
|
const definitions = [
|
|
@@ -22,6 +23,7 @@ function buildRegistry() {
|
|
|
22
23
|
...meetingChangeOperations,
|
|
23
24
|
...meetingReceiptOperations,
|
|
24
25
|
...meetingSignOperations,
|
|
26
|
+
...meetingEbOperations,
|
|
25
27
|
];
|
|
26
28
|
for (const definition of definitions) {
|
|
27
29
|
if (registry.has(definition.name)) {
|