@xdxer/dingtalk-agent 0.1.5-beta.11 → 0.1.5-beta.13
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/CHANGELOG.md +16 -0
- package/dist/bin/dingtalk-agent.js +6 -0
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/multica-deploy.js +77 -5
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/predicate-registry.js +187 -0
- package/dist/src/predicate-registry.js.map +1 -0
- package/dist/src/promotion.js +1 -1
- package/dist/src/promotion.js.map +1 -1
- package/dist/src/schedule-plan.js +27 -3
- package/dist/src/schedule-plan.js.map +1 -1
- package/docs/schemas/multica-deployment-plan.schema.json +14 -1
- package/docs/schemas/multica-deployment-receipt.schema.json +14 -1
- package/lab/robot-eval/suite.json +1 -1
- package/package.json +1 -1
- package/skills/core/dta-agent-compose/SKILL.md +1 -1
- package/skills/core/dta-agent-compose/references/drive-and-schedules.md +3 -1
- package/skills/core/dta-basic-behavior/SKILL.md +1 -1
- package/skills/core/dta-basic-behavior/references/task-lifecycle.md +6 -1
- package/skills/platforms/multica-dingtalk/dta-ops-multica/SKILL.md +25 -17
- package/skills/platforms/multica-dingtalk/dta-ops-multica/scripts/multica_ext.py +74 -8
- package/dist/src/map.js +0 -157
- package/dist/src/map.js.map +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { digest, stableStringify } from './events.js';
|
|
2
|
+
import { PREDICATE_ID } from './development-workspace.js';
|
|
3
|
+
/**
|
|
4
|
+
* Completion predicate registry 的 **runtime 投影**(issue #46)。
|
|
5
|
+
*
|
|
6
|
+
* #24 把判据收进 manifest 的 `predicates` 注册表,声明侧与运行侧引用同一个 id;
|
|
7
|
+
* #40 却把「manifest 随 skill-push 进入 runtime」当成既成事实——它不是。`skill-push`
|
|
8
|
+
* 只上传技能目录,目标 runtime 既不保证有 manifest 也不保证有 `multica_ext.py`:运行拍
|
|
9
|
+
* 只能看到 description 里的 `[dta-completion-predicate] <id>`,拿不到判据正文。
|
|
10
|
+
* 于是「运行时读同一份注册表」是一条不可达路径,而不可达的合同不是合同。
|
|
11
|
+
*
|
|
12
|
+
* 本模块把注册表变成**受管 runtime artifact**:deploy 从 manifest 生成规范化投影,作为
|
|
13
|
+
* 归属 Skill 的 supporting file 走既有 skill-push 链路交付(不引入第二条传输通道),
|
|
14
|
+
* 投影 hash 进 Skill tree hash → deployment hash → Receipt,回读用现有 skill hash 对账
|
|
15
|
+
* 就能发现漂移。运行侧读的是随 skill 树物化的这同一份 JSON,不需要平台凭据或 Python。
|
|
16
|
+
*
|
|
17
|
+
* 三条不变量:
|
|
18
|
+
*
|
|
19
|
+
* 1. **单一来源**:投影只由 manifest 生成,没有手抄副本;`resolveFromRegistry` 是运行侧
|
|
20
|
+
* 唯一的解析入口,与声明侧共用 id 文法(`PREDICATE_ID`)。
|
|
21
|
+
* 2. **字节 ↔ hash 一一对应**:`serializeRegistry` 是投影的唯一序列化形式(稳定键序 +
|
|
22
|
+
* 末尾换行),所以「文件内容变了」与「hash 变了」不可能分叉。
|
|
23
|
+
* 3. **fail-closed**:schema 不符、hash 对不上、id 未注册、正文为空——一律响亮抛错。
|
|
24
|
+
* 解析不出判据时运行侧不得使用完成措辞(Basic Behavior 的完成闸门负责执行这条)。
|
|
25
|
+
*/
|
|
26
|
+
export const PREDICATE_REGISTRY_SCHEMA = 'dingtalk-agent/predicate-registry@1';
|
|
27
|
+
/** 投影在归属 Skill 内的相对路径——写入侧与运行侧读的是同一个常量。 */
|
|
28
|
+
export const PREDICATE_REGISTRY_PATH = 'references/predicate-registry.json';
|
|
29
|
+
/** 默认归属 Skill:所有钉钉员工都装的基础行为技能,判存/完成闸门也住在这里。 */
|
|
30
|
+
export const PREDICATE_REGISTRY_DEFAULT_OWNER = 'dta-basic-behavior';
|
|
31
|
+
/**
|
|
32
|
+
* 投影的归属 Skill:优先 `dta-basic-behavior`,否则取字典序第一个受管 Skill。
|
|
33
|
+
* 规则必须确定且两侧共用——deploy 按它注入文件,schedule 的唤醒提示词按它写路径,
|
|
34
|
+
* 猜错一边就等于交付了一份没人读的文件。
|
|
35
|
+
*/
|
|
36
|
+
export function predicateRegistryOwner(skills) {
|
|
37
|
+
const managed = [...new Set(skills.filter((name) => name.trim()))].sort();
|
|
38
|
+
if (!managed.length)
|
|
39
|
+
return '';
|
|
40
|
+
return managed.includes(PREDICATE_REGISTRY_DEFAULT_OWNER)
|
|
41
|
+
? PREDICATE_REGISTRY_DEFAULT_OWNER
|
|
42
|
+
: managed[0];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 从 manifest 生成投影。返回 null = manifest 根本没声明注册表(legacy 项目,维持现状);
|
|
46
|
+
* 显式空注册表 `{}` 仍然生成投影——项目已进入引用模式,运行侧应当读到「注册表是空的」
|
|
47
|
+
* 而不是「没有注册表所以随便判」。
|
|
48
|
+
*/
|
|
49
|
+
export function buildPredicateRegistry(project) {
|
|
50
|
+
if (project.predicates === undefined)
|
|
51
|
+
return null;
|
|
52
|
+
const predicates = {};
|
|
53
|
+
for (const id of Object.keys(project.predicates).sort()) {
|
|
54
|
+
if (!PREDICATE_ID.test(id))
|
|
55
|
+
throw new Error(`predicate registry id 非法: ${id}`);
|
|
56
|
+
const entry = project.predicates[id];
|
|
57
|
+
const description = (entry.description || '').trim();
|
|
58
|
+
if (!description)
|
|
59
|
+
throw new Error(`predicate registry ${id} 缺 description`);
|
|
60
|
+
const source = (entry.source || '').trim();
|
|
61
|
+
predicates[id] = source ? { description, source } : { description };
|
|
62
|
+
}
|
|
63
|
+
const schedules = project.scheduleRefs
|
|
64
|
+
.filter((item) => item.predicate)
|
|
65
|
+
.map((item) => ({ name: item.name, predicate: item.predicate }))
|
|
66
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
for (const item of schedules) {
|
|
68
|
+
if (!predicates[item.predicate]) {
|
|
69
|
+
throw new Error(`节律 ${item.name} 引用未注册的 predicate:${item.predicate}——投影不携带解析不出的引用`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const unsigned = {
|
|
73
|
+
$schema: PREDICATE_REGISTRY_SCHEMA,
|
|
74
|
+
project: project.name,
|
|
75
|
+
predicates,
|
|
76
|
+
schedules,
|
|
77
|
+
};
|
|
78
|
+
return { ...unsigned, hash: digest(stableStringify(unsigned)) };
|
|
79
|
+
}
|
|
80
|
+
/** 投影的唯一序列化形式:稳定键序 + 末尾换行。字节与 hash 一一对应。 */
|
|
81
|
+
export function serializeRegistry(projection) {
|
|
82
|
+
const { hash, ...unsigned } = projection;
|
|
83
|
+
return JSON.stringify({ ...unsigned, hash }, null, 2) + '\n';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 运行侧的唯一解析入口(issue #46 期望 3):读随 skill 树交付的投影,按 id 取判据正文。
|
|
87
|
+
*
|
|
88
|
+
* 每一种「读不出来」都是响亮失败,不是空判据:文件缺失/非 JSON/schema 不符/hash 与内容
|
|
89
|
+
* 对不上/id 未注册/description 为空。调用方(运行拍)解析失败时**不得使用完成措辞**——
|
|
90
|
+
* 判据读不到就没有判据,说「已完成」等于凭空宣称。
|
|
91
|
+
*/
|
|
92
|
+
export function resolveFromRegistry(text, id) {
|
|
93
|
+
const wanted = (id || '').trim();
|
|
94
|
+
if (!PREDICATE_ID.test(wanted)) {
|
|
95
|
+
throw new Error(`predicate id 文法非法: ${wanted || '<empty>'}`);
|
|
96
|
+
}
|
|
97
|
+
const projection = parseRegistry(text);
|
|
98
|
+
const entry = projection.predicates[wanted];
|
|
99
|
+
if (!entry) {
|
|
100
|
+
throw new Error(`predicate:${wanted} 不在交付的注册表投影里(registry hash ${projection.hash.slice(0, 12)})——` +
|
|
101
|
+
'判据解析不到,这一拍不得使用完成措辞');
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
id: wanted,
|
|
105
|
+
description: entry.description,
|
|
106
|
+
...(entry.source ? { source: entry.source } : {}),
|
|
107
|
+
registryHash: projection.hash,
|
|
108
|
+
scheduleNames: projection.schedules.filter((item) => item.predicate === wanted)
|
|
109
|
+
.map((item) => item.name),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** 结构 + 完整性校验。hash 是无密钥完整性校验:证「文件没被改半截」,不证签发方。 */
|
|
113
|
+
export function parseRegistry(text) {
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = JSON.parse(text);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw new Error(`predicate registry 投影不是合法 JSON: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
122
|
+
throw new Error('predicate registry 投影必须是 JSON object');
|
|
123
|
+
}
|
|
124
|
+
const value = raw;
|
|
125
|
+
const keys = Object.keys(value).sort();
|
|
126
|
+
const expected = ['$schema', 'hash', 'predicates', 'project', 'schedules'];
|
|
127
|
+
if (keys.length !== expected.length || expected.some((key, i) => keys[i] !== key)) {
|
|
128
|
+
throw new Error(`predicate registry 投影字段集必须恰为 ${expected.join('/')}`);
|
|
129
|
+
}
|
|
130
|
+
if (value.$schema !== PREDICATE_REGISTRY_SCHEMA) {
|
|
131
|
+
throw new Error('predicate registry 投影 schema 非法');
|
|
132
|
+
}
|
|
133
|
+
if (typeof value.project !== 'string' || !value.project.trim()) {
|
|
134
|
+
throw new Error('predicate registry 投影缺 project');
|
|
135
|
+
}
|
|
136
|
+
if (!value.predicates || typeof value.predicates !== 'object' || Array.isArray(value.predicates)) {
|
|
137
|
+
throw new Error('predicate registry 投影 predicates 必须是 object');
|
|
138
|
+
}
|
|
139
|
+
const predicates = {};
|
|
140
|
+
for (const [id, entry] of Object.entries(value.predicates)) {
|
|
141
|
+
if (!PREDICATE_ID.test(id))
|
|
142
|
+
throw new Error(`predicate registry 投影含非法 id: ${id}`);
|
|
143
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
144
|
+
throw new Error(`predicate registry 投影 ${id} 必须是 object`);
|
|
145
|
+
}
|
|
146
|
+
const extra = Object.keys(entry).filter((key) => key !== 'description' && key !== 'source');
|
|
147
|
+
if (extra.length) {
|
|
148
|
+
throw new Error(`predicate registry 投影 ${id} 含未知字段: ${extra.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
if (typeof entry.description !== 'string' || !entry.description.trim()) {
|
|
151
|
+
throw new Error(`predicate registry 投影 ${id} 的 description 为空`);
|
|
152
|
+
}
|
|
153
|
+
if (entry.source !== undefined && (typeof entry.source !== 'string' || !entry.source.trim())) {
|
|
154
|
+
throw new Error(`predicate registry 投影 ${id} 的 source 非法`);
|
|
155
|
+
}
|
|
156
|
+
predicates[id] = entry.source
|
|
157
|
+
? { description: entry.description, source: entry.source }
|
|
158
|
+
: { description: entry.description };
|
|
159
|
+
}
|
|
160
|
+
if (!Array.isArray(value.schedules)) {
|
|
161
|
+
throw new Error('predicate registry 投影 schedules 必须是数组');
|
|
162
|
+
}
|
|
163
|
+
const schedules = value.schedules.map((item) => {
|
|
164
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) ||
|
|
165
|
+
typeof item.name !== 'string' || !item.name.trim() ||
|
|
166
|
+
typeof item.predicate !== 'string' || !PREDICATE_ID.test(item.predicate)) {
|
|
167
|
+
throw new Error('predicate registry 投影 schedules 条目非法');
|
|
168
|
+
}
|
|
169
|
+
if (!predicates[item.predicate]) {
|
|
170
|
+
throw new Error(`predicate registry 投影里的节律引用了未注册 id: ${item.predicate}`);
|
|
171
|
+
}
|
|
172
|
+
return { name: item.name, predicate: item.predicate };
|
|
173
|
+
});
|
|
174
|
+
const projection = {
|
|
175
|
+
$schema: PREDICATE_REGISTRY_SCHEMA,
|
|
176
|
+
project: value.project,
|
|
177
|
+
predicates,
|
|
178
|
+
schedules,
|
|
179
|
+
hash: String(value.hash || ''),
|
|
180
|
+
};
|
|
181
|
+
const { hash, ...unsigned } = projection;
|
|
182
|
+
if (!hash || hash !== digest(stableStringify(unsigned))) {
|
|
183
|
+
throw new Error('predicate registry 投影 hash 与内容不一致——交付的判据被改过或截断,fail-closed');
|
|
184
|
+
}
|
|
185
|
+
return projection;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=predicate-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicate-registry.js","sourceRoot":"","sources":["../../src/predicate-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { digest, stableStringify } from './events.js'\nimport { PREDICATE_ID, type DeclaredPredicate } from './development-workspace.js'\n\n/**\n * Completion predicate registry 的 **runtime 投影**(issue #46)。\n *\n * #24 把判据收进 manifest 的 `predicates` 注册表,声明侧与运行侧引用同一个 id;\n * #40 却把「manifest 随 skill-push 进入 runtime」当成既成事实——它不是。`skill-push`\n * 只上传技能目录,目标 runtime 既不保证有 manifest 也不保证有 `multica_ext.py`:运行拍\n * 只能看到 description 里的 `[dta-completion-predicate] <id>`,拿不到判据正文。\n * 于是「运行时读同一份注册表」是一条不可达路径,而不可达的合同不是合同。\n *\n * 本模块把注册表变成**受管 runtime artifact**:deploy 从 manifest 生成规范化投影,作为\n * 归属 Skill 的 supporting file 走既有 skill-push 链路交付(不引入第二条传输通道),\n * 投影 hash 进 Skill tree hash → deployment hash → Receipt,回读用现有 skill hash 对账\n * 就能发现漂移。运行侧读的是随 skill 树物化的这同一份 JSON,不需要平台凭据或 Python。\n *\n * 三条不变量:\n *\n * 1. **单一来源**:投影只由 manifest 生成,没有手抄副本;`resolveFromRegistry` 是运行侧\n * 唯一的解析入口,与声明侧共用 id 文法(`PREDICATE_ID`)。\n * 2. **字节 ↔ hash 一一对应**:`serializeRegistry` 是投影的唯一序列化形式(稳定键序 +\n * 末尾换行),所以「文件内容变了」与「hash 变了」不可能分叉。\n * 3. **fail-closed**:schema 不符、hash 对不上、id 未注册、正文为空——一律响亮抛错。\n * 解析不出判据时运行侧不得使用完成措辞(Basic Behavior 的完成闸门负责执行这条)。\n */\n\nexport const PREDICATE_REGISTRY_SCHEMA = 'dingtalk-agent/predicate-registry@1'\n\n/** 投影在归属 Skill 内的相对路径——写入侧与运行侧读的是同一个常量。 */\nexport const PREDICATE_REGISTRY_PATH = 'references/predicate-registry.json'\n\n/** 默认归属 Skill:所有钉钉员工都装的基础行为技能,判存/完成闸门也住在这里。 */\nexport const PREDICATE_REGISTRY_DEFAULT_OWNER = 'dta-basic-behavior'\n\nexport interface PredicateRegistryEntry {\n description: string\n source?: string\n}\n\nexport interface PredicateRegistryProjection {\n $schema: typeof PREDICATE_REGISTRY_SCHEMA\n project: string\n predicates: Record<string, PredicateRegistryEntry>\n /** 声明侧引用这些 id 的节律;运行侧据此核对「这一拍的 id 真在注册表里」。 */\n schedules: Array<{ name: string; predicate: string }>\n hash: string\n}\n\n/**\n * 投影的归属 Skill:优先 `dta-basic-behavior`,否则取字典序第一个受管 Skill。\n * 规则必须确定且两侧共用——deploy 按它注入文件,schedule 的唤醒提示词按它写路径,\n * 猜错一边就等于交付了一份没人读的文件。\n */\nexport function predicateRegistryOwner(skills: string[]): string {\n const managed = [...new Set(skills.filter((name) => name.trim()))].sort()\n if (!managed.length) return ''\n return managed.includes(PREDICATE_REGISTRY_DEFAULT_OWNER)\n ? PREDICATE_REGISTRY_DEFAULT_OWNER\n : managed[0]\n}\n\n/**\n * 从 manifest 生成投影。返回 null = manifest 根本没声明注册表(legacy 项目,维持现状);\n * 显式空注册表 `{}` 仍然生成投影——项目已进入引用模式,运行侧应当读到「注册表是空的」\n * 而不是「没有注册表所以随便判」。\n */\nexport function buildPredicateRegistry(project: {\n name: string\n predicates?: Record<string, DeclaredPredicate>\n /**\n * 各节律引用的 predicate id,由调用方用 `completionPredicateId`(唯一 parser)算好传进来。\n * 本模块不反向依赖 schedule-plan:投影是被交付物,解析 completion 文法不是它的职责。\n */\n scheduleRefs: Array<{ name: string; predicate: string }>\n}): PredicateRegistryProjection | null {\n if (project.predicates === undefined) return null\n const predicates: Record<string, PredicateRegistryEntry> = {}\n for (const id of Object.keys(project.predicates).sort()) {\n if (!PREDICATE_ID.test(id)) throw new Error(`predicate registry id 非法: ${id}`)\n const entry = project.predicates[id]\n const description = (entry.description || '').trim()\n if (!description) throw new Error(`predicate registry ${id} 缺 description`)\n const source = (entry.source || '').trim()\n predicates[id] = source ? { description, source } : { description }\n }\n const schedules = project.scheduleRefs\n .filter((item) => item.predicate)\n .map((item) => ({ name: item.name, predicate: item.predicate }))\n .sort((a, b) => a.name.localeCompare(b.name))\n for (const item of schedules) {\n if (!predicates[item.predicate]) {\n throw new Error(\n `节律 ${item.name} 引用未注册的 predicate:${item.predicate}——投影不携带解析不出的引用`)\n }\n }\n const unsigned = {\n $schema: PREDICATE_REGISTRY_SCHEMA as typeof PREDICATE_REGISTRY_SCHEMA,\n project: project.name,\n predicates,\n schedules,\n }\n return { ...unsigned, hash: digest(stableStringify(unsigned)) }\n}\n\n/** 投影的唯一序列化形式:稳定键序 + 末尾换行。字节与 hash 一一对应。 */\nexport function serializeRegistry(projection: PredicateRegistryProjection): string {\n const { hash, ...unsigned } = projection\n return JSON.stringify({ ...unsigned, hash }, null, 2) + '\\n'\n}\n\nexport interface ResolvedPredicate {\n id: string\n description: string\n source?: string\n registryHash: string\n scheduleNames: string[]\n}\n\n/**\n * 运行侧的唯一解析入口(issue #46 期望 3):读随 skill 树交付的投影,按 id 取判据正文。\n *\n * 每一种「读不出来」都是响亮失败,不是空判据:文件缺失/非 JSON/schema 不符/hash 与内容\n * 对不上/id 未注册/description 为空。调用方(运行拍)解析失败时**不得使用完成措辞**——\n * 判据读不到就没有判据,说「已完成」等于凭空宣称。\n */\nexport function resolveFromRegistry(text: string, id: string): ResolvedPredicate {\n const wanted = (id || '').trim()\n if (!PREDICATE_ID.test(wanted)) {\n throw new Error(`predicate id 文法非法: ${wanted || '<empty>'}`)\n }\n const projection = parseRegistry(text)\n const entry = projection.predicates[wanted]\n if (!entry) {\n throw new Error(\n `predicate:${wanted} 不在交付的注册表投影里(registry hash ${projection.hash.slice(0, 12)})——` +\n '判据解析不到,这一拍不得使用完成措辞')\n }\n return {\n id: wanted,\n description: entry.description,\n ...(entry.source ? { source: entry.source } : {}),\n registryHash: projection.hash,\n scheduleNames: projection.schedules.filter((item) => item.predicate === wanted)\n .map((item) => item.name),\n }\n}\n\n/** 结构 + 完整性校验。hash 是无密钥完整性校验:证「文件没被改半截」,不证签发方。 */\nexport function parseRegistry(text: string): PredicateRegistryProjection {\n let raw: unknown\n try { raw = JSON.parse(text) }\n catch (error) { throw new Error(`predicate registry 投影不是合法 JSON: ${error.message}`) }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error('predicate registry 投影必须是 JSON object')\n }\n const value = raw as Record<string, any>\n const keys = Object.keys(value).sort()\n const expected = ['$schema', 'hash', 'predicates', 'project', 'schedules']\n if (keys.length !== expected.length || expected.some((key, i) => keys[i] !== key)) {\n throw new Error(`predicate registry 投影字段集必须恰为 ${expected.join('/')}`)\n }\n if (value.$schema !== PREDICATE_REGISTRY_SCHEMA) {\n throw new Error('predicate registry 投影 schema 非法')\n }\n if (typeof value.project !== 'string' || !value.project.trim()) {\n throw new Error('predicate registry 投影缺 project')\n }\n if (!value.predicates || typeof value.predicates !== 'object' || Array.isArray(value.predicates)) {\n throw new Error('predicate registry 投影 predicates 必须是 object')\n }\n const predicates: Record<string, PredicateRegistryEntry> = {}\n for (const [id, entry] of Object.entries(value.predicates as Record<string, any>)) {\n if (!PREDICATE_ID.test(id)) throw new Error(`predicate registry 投影含非法 id: ${id}`)\n if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n throw new Error(`predicate registry 投影 ${id} 必须是 object`)\n }\n const extra = Object.keys(entry).filter((key) => key !== 'description' && key !== 'source')\n if (extra.length) {\n throw new Error(`predicate registry 投影 ${id} 含未知字段: ${extra.join(', ')}`)\n }\n if (typeof entry.description !== 'string' || !entry.description.trim()) {\n throw new Error(`predicate registry 投影 ${id} 的 description 为空`)\n }\n if (entry.source !== undefined && (typeof entry.source !== 'string' || !entry.source.trim())) {\n throw new Error(`predicate registry 投影 ${id} 的 source 非法`)\n }\n predicates[id] = entry.source\n ? { description: entry.description, source: entry.source }\n : { description: entry.description }\n }\n if (!Array.isArray(value.schedules)) {\n throw new Error('predicate registry 投影 schedules 必须是数组')\n }\n const schedules = value.schedules.map((item: any) => {\n if (!item || typeof item !== 'object' || Array.isArray(item) ||\n typeof item.name !== 'string' || !item.name.trim() ||\n typeof item.predicate !== 'string' || !PREDICATE_ID.test(item.predicate)) {\n throw new Error('predicate registry 投影 schedules 条目非法')\n }\n if (!predicates[item.predicate]) {\n throw new Error(`predicate registry 投影里的节律引用了未注册 id: ${item.predicate}`)\n }\n return { name: item.name, predicate: item.predicate }\n })\n const projection: PredicateRegistryProjection = {\n $schema: PREDICATE_REGISTRY_SCHEMA,\n project: value.project,\n predicates,\n schedules,\n hash: String(value.hash || ''),\n }\n const { hash, ...unsigned } = projection\n if (!hash || hash !== digest(stableStringify(unsigned))) {\n throw new Error(\n 'predicate registry 投影 hash 与内容不一致——交付的判据被改过或截断,fail-closed')\n }\n return projection\n}\n"]}
|
package/dist/src/promotion.js
CHANGED
|
@@ -610,7 +610,7 @@ function emptyDeploymentPlan(target) {
|
|
|
610
610
|
$schema: 'dingtalk-agent/multica-deployment-plan@1',
|
|
611
611
|
planId: 'multica_deploy_plan_unavailable', action: 'apply', workspace: target.name,
|
|
612
612
|
provider: 'multica', profile: '', inspectionId: '', desiredHash: target.desiredHash,
|
|
613
|
-
deploymentHash: emptyHash(), expected: {
|
|
613
|
+
deploymentHash: emptyHash(), predicateRegistry: null, expected: {
|
|
614
614
|
workspaceId: '', runtimeId: '', agentId: '', agentName: '', skills: [],
|
|
615
615
|
definitionHash: emptyHash(), instructionsHash: emptyHash(),
|
|
616
616
|
}, rebindRuntime: false,
|