@siming-org/core 0.3.0 → 0.5.0
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/dist/index.d.ts +2219 -147
- package/dist/index.js +2514 -420
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
|
-
import { z as
|
|
3
|
+
import { z as z20 } from "zod";
|
|
4
4
|
|
|
5
5
|
// src/store/client.ts
|
|
6
6
|
import { MongoClient } from "mongodb";
|
|
@@ -88,6 +88,12 @@ function createRepo(collection, options = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
function escapeRegExpLiteral(input) {
|
|
92
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
93
|
+
}
|
|
94
|
+
function enabledNeFalse() {
|
|
95
|
+
return { enabled: { $ne: false } };
|
|
96
|
+
}
|
|
91
97
|
|
|
92
98
|
// src/errors.ts
|
|
93
99
|
var BIZ_CODE_MESSAGES = {
|
|
@@ -98,6 +104,9 @@ var BIZ_CODE_MESSAGES = {
|
|
|
98
104
|
TASK_PROJECT_IMMUTABLE: "\u4EFB\u52A1\u5F52\u5C5E\u9879\u76EE\u521B\u5EFA\u540E\u4E0D\u53EF\u53D8\u66F4",
|
|
99
105
|
DEFAULT_PROJECT_IMMUTABLE: "\u9ED8\u8BA4\u9879\u76EE\u4E0D\u53EF\u505C\u7528",
|
|
100
106
|
TEMPLATE_NAME_CONFLICT: "\u76EE\u6807\u9879\u76EE\u5185\u5DF2\u5B58\u5728\u540C\u540D\u6A21\u677F",
|
|
107
|
+
// T202609020002 模板业务 code 域(创建冲突/不可变;E11000 兜底由 repo 层按 keyValue 二次映射为 TEMPLATE_CODE_CONFLICT)
|
|
108
|
+
TEMPLATE_CODE_CONFLICT: "\u76EE\u6807\u9879\u76EE\u5185\u5DF2\u5B58\u5728\u76F8\u540C code \u7684\u6A21\u677F\uFF08code \u9879\u76EE\u5185\u552F\u4E00\uFF09",
|
|
109
|
+
TEMPLATE_CODE_IMMUTABLE: "\u6A21\u677F code \u521B\u5EFA\u540E\u4E0D\u53EF\u53D8\uFF08\u5982\u9700\u65B0\u6807\u8BC6\u8BF7\u590D\u5236\u6A21\u677F\u6307\u5B9A\u65B0 code\uFF09",
|
|
101
110
|
// 仅覆盖 404 分支(不存在);archived 走独立的 409 PROJECT_ARCHIVED,勿在此合并语义
|
|
102
111
|
PROJECT_NOT_FOUND: "\u9879\u76EE\u4E0D\u5B58\u5728",
|
|
103
112
|
AGENT_SKILL_SCOPE_CONFLICT: "Agent \u7ED1\u5B9A\u7684 Skill \u4F5C\u7528\u57DF\u4E0D\u517C\u5BB9\uFF08\u5168\u5C40 Agent \u4EC5\u53EF\u7ED1\u5B9A\u5168\u5C40 Skill\uFF09",
|
|
@@ -108,6 +117,8 @@ var BIZ_CODE_MESSAGES = {
|
|
|
108
117
|
ENUM_ENTRY_IN_USE: "\u679A\u4E3E\u503C\u88AB\u8D44\u6E90\u5F15\u7528\uFF0C\u7981\u6B62\u5220\u9664",
|
|
109
118
|
ENUM_VALUE_CONFLICT: "\u679A\u4E3E\u503C\u5DF2\u5B58\u5728",
|
|
110
119
|
NAME_SCOPE_CONFLICT: "\u8BE5\u4F5C\u7528\u57DF\u5185\u5DF2\u5B58\u5728\u540C\u540D\u8D44\u6E90\uFF08\u5168\u5C40\u8D44\u6E90\u4E0E\u9879\u76EE\u8D44\u6E90\u4E0D\u53EF\u540C\u540D\uFF09",
|
|
120
|
+
// T202608270002:主文档引用的引用文档删除保护(message 由路由层附加具体 path 清单)
|
|
121
|
+
REFERENCE_IN_USE: "\u4E3B\u6587\u6863\u4ECD\u5F15\u7528\u5F85\u5220\u9664\u7684\u5F15\u7528\u6587\u6863\uFF0C\u8BF7\u5148\u79FB\u9664\u4E3B\u6587\u6863\u4E2D\u7684\u5F15\u7528\u518D\u5220\u9664",
|
|
111
122
|
TASK_DOC_NOT_SET: "\u4EFB\u52A1\u672A\u5173\u8054\u4EFB\u52A1\u6587\u4EF6",
|
|
112
123
|
TASK_DOC_NOT_FOUND: "\u4EFB\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728",
|
|
113
124
|
TASK_DOC_SECTION_NOT_FOUND: "\u4EFB\u52A1\u6587\u4EF6\u4E2D\u672A\u627E\u5230\u5339\u914D\u6BB5\u843D",
|
|
@@ -121,7 +132,28 @@ var BIZ_CODE_MESSAGES = {
|
|
|
121
132
|
// T202608240003:类型化任务节点路径(创建时剪枝 + 类型×轨道入口校验)
|
|
122
133
|
TASK_PRUNE_GRAPH_INVALID: "\u526A\u679D\u540E DAG \u56FE\u4E0D\u5B8C\u6574\uFF08\u8282\u70B9\u6E05\u5355\u4E0E\u6A21\u677F\u7ED3\u6784\u4E0D\u517C\u5BB9\uFF0C\u89C1\u9519\u8BEF\u8BE6\u60C5\uFF09",
|
|
123
134
|
TASK_SKIP_NODE_NOT_FOUND: "skip \u6E05\u5355\u542B\u6A21\u677F\u4E0D\u5B58\u5728\u7684\u8282\u70B9 id\uFF08\u89C1\u9519\u8BEF\u8BE6\u60C5\u7684\u5408\u6CD5\u8282\u70B9\u6E05\u5355\uFF09",
|
|
124
|
-
TASK_TYPE_TRACK_MISMATCH: "\u4EFB\u52A1\u7C7B\u578B\u4E0E\u8F68\u9053\u7EC4\u5408\u975E\u6CD5\uFF08research \u8F68\u9053\u4E3A\u8C03\u7814\u7C7B\u578B\u4E13\u7528\u503C\uFF1BUI \u5FAE\u8C03\u4EC5\u9002\u7528 ui \u8F68\u9053\uFF09"
|
|
135
|
+
TASK_TYPE_TRACK_MISMATCH: "\u4EFB\u52A1\u7C7B\u578B\u4E0E\u8F68\u9053\u7EC4\u5408\u975E\u6CD5\uFF08research \u8F68\u9053\u4E3A\u8C03\u7814\u7C7B\u578B\u4E13\u7528\u503C\uFF1BUI \u5FAE\u8C03\u4EC5\u9002\u7528 ui \u8F68\u9053\uFF09",
|
|
136
|
+
// T202608290001:停用模板创建任务守卫(资产启停——停用对创建通道同样不可见)
|
|
137
|
+
TEMPLATE_DISABLED: "\u6A21\u677F\u5DF2\u505C\u7528\uFF0C\u7981\u6B62\u7528\u4E8E\u521B\u5EFA\u4EFB\u52A1\uFF08Web \u7BA1\u7406\u53F0\u53EF\u91CD\u65B0\u542F\u7528\uFF09",
|
|
138
|
+
// T202608310001:鉴权域(错误响应以 error: bizCode 为机器可判键)
|
|
139
|
+
AUTH_REQUIRED: "\u9700\u8981\u767B\u5F55\u6216\u914D\u7F6E Token\uFF08Web \u767B\u5F55\uFF1BCLI \u8FD0\u884C siming auth token set \u914D\u7F6E\uFF09",
|
|
140
|
+
AUTH_TOKEN_INVALID: "Token \u65E0\u6548\u6216\u5DF2\u88AB\u91CD\u7F6E\uFF08Web \u7BA1\u7406\u53F0\u91CD\u65B0\u751F\u6210\u540E\u8FD0\u884C siming auth token set \u66F4\u65B0\uFF09",
|
|
141
|
+
AUTH_INVALID_CREDENTIALS: "\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF",
|
|
142
|
+
AUTH_FORBIDDEN: "\u5F53\u524D\u51ED\u8BC1\u65E0\u6743\u6267\u884C\u6B64\u64CD\u4F5C\uFF08\u9879\u76EE Token \u4EC5\u9650\u672C\u9879\u76EE\u8BFB\u5199\u4E0E\u5168\u5C40\u8D44\u4EA7\u53EA\u8BFB\uFF09",
|
|
143
|
+
AUTH_ACCOUNT_EXISTS: "\u7BA1\u7406\u5458\u8D26\u53F7\u5DF2\u521D\u59CB\u5316\uFF08\u65E0\u9700\u91CD\u590D\u521B\u5EFA\uFF09",
|
|
144
|
+
// T202609010002:批量写入域(信封级——空批/超限整批拒绝并提示拆分)
|
|
145
|
+
BATCH_EMPTY: "\u6279\u91CF\u6761\u76EE\u4E3A\u7A7A\uFF08items \u81F3\u5C11 1 \u6761\uFF09",
|
|
146
|
+
BATCH_LIMIT_EXCEEDED: "\u5355\u6B21\u6279\u91CF\u4E0A\u9650 50 \u6761\uFF0C\u8BF7\u62C6\u5206\u4E3A\u591A\u6B21\u8C03\u7528",
|
|
147
|
+
BATCH_PAYLOAD_TOO_LARGE: "\u6279\u91CF\u5168\u6587\u5FEB\u7167\u603B\u91CF\u8D85\u9650\uFF082M \u5B57\u7B26/\u6279\uFF09\uFF0C\u8BF7\u62C6\u5206\u4E3A\u591A\u6B21\u8C03\u7528\u6216\u6539\u7528 path \u5F15\u7528",
|
|
148
|
+
// T202609020001:通用 DAG 节点资产域
|
|
149
|
+
NODE_PRESET_CODE_EXISTS: "\u8282\u70B9 code \u5DF2\u5B58\u5728\uFF08code \u5168\u5C40\u552F\u4E00\uFF0C\u8DE8\u4F5C\u7528\u57DF\u4E0D\u53EF\u91CD\u590D\uFF09",
|
|
150
|
+
NODE_PRESET_CODE_IMMUTABLE: "\u8282\u70B9 code \u521B\u5EFA\u540E\u4E0D\u53EF\u53D8",
|
|
151
|
+
NODE_PRESET_VERSION_REQUIRED: "\u8282\u70B9\u5185\u5BB9\u53D8\u66F4\u5FC5\u987B\u540C\u6B65\u63D0\u5347\u7248\u672C\u53F7\uFF08version \u5B57\u6BB5\uFF09",
|
|
152
|
+
TEMPLATE_NODE_ID_DUPLICATE: "\u6A21\u677F\u5185\u8282\u70B9 id \u91CD\u590D\uFF08\u5148\u5728\u8282\u70B9\u5E93\u4E2D\u590D\u5236\u6539\u540D\u6216\u8C03\u6574\u6A21\u677F\u8282\u70B9\u6807\u8BC6\uFF09",
|
|
153
|
+
NODE_LIBRARY_COMPOSITION_NOT_FOUND: "\u7EC4\u5408\u5B9A\u4E49\u4E0D\u5B58\u5728\uFF08\u591A\u5E93\u540C\u540D\u7EC4\u5408\u9700\u663E\u5F0F\u6307\u5B9A\u6765\u6E90\u5E93\uFF09",
|
|
154
|
+
UPGRADE_DECISION_REQUIRED: "\u672C\u5730\u5DF2\u4FEE\u6539\u7684\u8282\u70B9\u5FC5\u987B\u663E\u5F0F\u51B3\u7B56\uFF08upgrade/keep/skip\uFF09",
|
|
155
|
+
UPGRADE_STALE: "\u6A21\u677F\u72B6\u6001\u4E0E\u5347\u7EA7\u8BA1\u5212\u4E0D\u7B26\uFF08\u8BA1\u5212\u540E\u6A21\u677F\u5DF2\u88AB\u7F16\u8F91\uFF09\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6\u5347\u7EA7\u8BA1\u5212",
|
|
156
|
+
FLOW_MANIFEST_INVALID: "\u8282\u70B9\u5E93 manifest \u683C\u5F0F\u6216\u7248\u672C\u4E0D\u652F\u6301"
|
|
125
157
|
};
|
|
126
158
|
var AppError = class extends Error {
|
|
127
159
|
};
|
|
@@ -140,8 +172,25 @@ var NotFoundError = class extends AppError {
|
|
|
140
172
|
statusCode = 404;
|
|
141
173
|
code = "not_found";
|
|
142
174
|
bizCode;
|
|
175
|
+
/**
|
|
176
|
+
* T202609020002:not-found 相近候选清单(模板寻址防转录错误——格式合法但不存在的
|
|
177
|
+
* code/数据库 id,错误信息携带项目内相近模板,转录错误可当场发现)。附加到响应体
|
|
178
|
+
* 的 candidates 字段;仅模板寻址路由经 withTemplateCandidates 注入(构造后写入故非
|
|
179
|
+
* readonly——外部读、内部一次写),其余资源 not-found 形态不变。
|
|
180
|
+
*/
|
|
181
|
+
templateCandidates;
|
|
182
|
+
withTemplateCandidates(candidates) {
|
|
183
|
+
this.templateCandidates = candidates;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
143
186
|
toResponse() {
|
|
144
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
error: this.bizCode ?? this.code,
|
|
189
|
+
message: this.message,
|
|
190
|
+
resource: this.resource,
|
|
191
|
+
id: this.id,
|
|
192
|
+
...this.templateCandidates !== void 0 ? { candidates: this.templateCandidates } : {}
|
|
193
|
+
};
|
|
145
194
|
}
|
|
146
195
|
};
|
|
147
196
|
var BadRequestError = class extends AppError {
|
|
@@ -197,10 +246,40 @@ var ValidationError = class extends AppError {
|
|
|
197
246
|
return { error: this.bizCode ?? this.code, message: this.message, issues: this.issues };
|
|
198
247
|
}
|
|
199
248
|
};
|
|
249
|
+
var UnauthorizedError = class extends AppError {
|
|
250
|
+
constructor(bizCode) {
|
|
251
|
+
super(BIZ_CODE_MESSAGES[bizCode]);
|
|
252
|
+
this.bizCode = bizCode;
|
|
253
|
+
this.name = "UnauthorizedError";
|
|
254
|
+
}
|
|
255
|
+
bizCode;
|
|
256
|
+
statusCode = 401;
|
|
257
|
+
code = "unauthorized";
|
|
258
|
+
toResponse() {
|
|
259
|
+
return { error: this.bizCode, message: this.message };
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var ForbiddenError = class extends AppError {
|
|
263
|
+
statusCode = 403;
|
|
264
|
+
code = "forbidden";
|
|
265
|
+
bizCode = "AUTH_FORBIDDEN";
|
|
266
|
+
constructor() {
|
|
267
|
+
super(BIZ_CODE_MESSAGES.AUTH_FORBIDDEN);
|
|
268
|
+
this.name = "ForbiddenError";
|
|
269
|
+
}
|
|
270
|
+
toResponse() {
|
|
271
|
+
return { error: this.bizCode, message: this.message };
|
|
272
|
+
}
|
|
273
|
+
};
|
|
200
274
|
|
|
201
275
|
// src/store/repos/skill.repo.ts
|
|
202
276
|
function createSkillRepo(db) {
|
|
203
277
|
const base = createRepo(db.collection("skills"), { nameField: "name" });
|
|
278
|
+
const listQuery = (cond, opts) => ({
|
|
279
|
+
...cond,
|
|
280
|
+
...opts?.includeDisabled ? {} : enabledNeFalse(),
|
|
281
|
+
...opts?.q ? { name: { $regex: escapeRegExpLiteral(opts.q), $options: "i" } } : {}
|
|
282
|
+
});
|
|
204
283
|
return {
|
|
205
284
|
...base,
|
|
206
285
|
async createSkill(data) {
|
|
@@ -218,26 +297,34 @@ function createSkillRepo(db) {
|
|
|
218
297
|
return base.delete(skill.id);
|
|
219
298
|
},
|
|
220
299
|
async updateByNameScoped(name, scope, projectId, patch) {
|
|
221
|
-
const skill = await this.getByNameScoped(name, scope, projectId);
|
|
300
|
+
const skill = await this.getByNameScoped(name, scope, projectId, { includeDisabled: true });
|
|
222
301
|
if (!skill || !skill.id) return null;
|
|
223
302
|
return base.update(skill.id, patch);
|
|
224
303
|
},
|
|
225
304
|
async deleteByNameScoped(name, scope, projectId) {
|
|
226
|
-
const skill = await this.getByNameScoped(name, scope, projectId);
|
|
305
|
+
const skill = await this.getByNameScoped(name, scope, projectId, { includeDisabled: true });
|
|
227
306
|
if (!skill || !skill.id) return false;
|
|
228
307
|
return base.delete(skill.id);
|
|
229
308
|
},
|
|
230
|
-
async getByNameScoped(name, scope, projectId) {
|
|
309
|
+
async getByNameScoped(name, scope, projectId, opts) {
|
|
231
310
|
const doc = await base._collection.findOne(
|
|
232
|
-
|
|
311
|
+
listQuery(
|
|
312
|
+
scope === "project" ? { name, scope: "project", projectId } : { name, scope: "global" },
|
|
313
|
+
opts
|
|
314
|
+
)
|
|
233
315
|
);
|
|
234
316
|
return doc ? toEntity(doc) : null;
|
|
235
317
|
},
|
|
236
|
-
async listSkillsByScope(projectId) {
|
|
237
|
-
return base.list({ $or: [{ scope: "global" }, { scope: "project", projectId }] });
|
|
318
|
+
async listSkillsByScope(projectId, opts) {
|
|
319
|
+
return base.list(listQuery({ $or: [{ scope: "global" }, { scope: "project", projectId }] }, opts));
|
|
320
|
+
},
|
|
321
|
+
async listSkillsByScopeFilter(scope, projectId, opts) {
|
|
322
|
+
return base.list(
|
|
323
|
+
listQuery(scope === "project" ? { scope: "project", projectId } : { scope: "global" }, opts)
|
|
324
|
+
);
|
|
238
325
|
},
|
|
239
|
-
async
|
|
240
|
-
return base.list(
|
|
326
|
+
async listSkills(opts) {
|
|
327
|
+
return base.list(listQuery({}, opts));
|
|
241
328
|
},
|
|
242
329
|
async listSkillsByNames(names, scope, projectId) {
|
|
243
330
|
if (names.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -245,6 +332,12 @@ function createSkillRepo(db) {
|
|
|
245
332
|
const skills = await base.list(filter);
|
|
246
333
|
return new Map(skills.map((s) => [s.name, s]));
|
|
247
334
|
},
|
|
335
|
+
async hasNameScopeConflict(name, scope) {
|
|
336
|
+
return await base._collection.countDocuments(
|
|
337
|
+
scope === "global" ? { name, scope: "project" } : { name, scope: "global" },
|
|
338
|
+
{ limit: 1 }
|
|
339
|
+
) > 0;
|
|
340
|
+
},
|
|
248
341
|
async copySkill(sourceName, source, target, newName) {
|
|
249
342
|
const src = await base._collection.findOne(
|
|
250
343
|
source.scope === "project" ? { name: sourceName, scope: "project", projectId: source.projectId } : { name: sourceName, scope: "global" }
|
|
@@ -261,7 +354,7 @@ function createSkillRepo(db) {
|
|
|
261
354
|
throw new ConflictError("skill", `name already exists: ${newName}`);
|
|
262
355
|
}
|
|
263
356
|
await assertNameScopeAvailable(base._collection, newName, target.scope, target.projectId);
|
|
264
|
-
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, projectId: _sourceProjectId, scope: _sourceScope, ...rest } = sourceEntity;
|
|
357
|
+
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, projectId: _sourceProjectId, scope: _sourceScope, enabled: _sourceEnabled, ...rest } = sourceEntity;
|
|
265
358
|
const copy = {
|
|
266
359
|
...JSON.parse(JSON.stringify(rest)),
|
|
267
360
|
name: newName,
|
|
@@ -285,7 +378,31 @@ async function assertNameScopeAvailable(collection, name, scope, projectId) {
|
|
|
285
378
|
|
|
286
379
|
// src/store/repos/agent.repo.ts
|
|
287
380
|
function createAgentRepo(db) {
|
|
288
|
-
const
|
|
381
|
+
const raw = createRepo(db.collection("agents"), { nameField: "name" });
|
|
382
|
+
const normalizeFunction = (agent) => ({ ...agent, "function": agent["function"] ?? "executor" });
|
|
383
|
+
const base = {
|
|
384
|
+
...raw,
|
|
385
|
+
async list(filter = void 0) {
|
|
386
|
+
return (await raw.list(filter)).map(normalizeFunction);
|
|
387
|
+
},
|
|
388
|
+
async getById(id) {
|
|
389
|
+
const agent = await raw.getById(id);
|
|
390
|
+
return agent ? normalizeFunction(agent) : null;
|
|
391
|
+
},
|
|
392
|
+
async getByName(name) {
|
|
393
|
+
const agent = await raw.getByName(name);
|
|
394
|
+
return agent ? normalizeFunction(agent) : null;
|
|
395
|
+
},
|
|
396
|
+
async update(id, patch) {
|
|
397
|
+
const agent = await raw.update(id, patch);
|
|
398
|
+
return agent ? normalizeFunction(agent) : null;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const listQuery = (cond, opts) => ({
|
|
402
|
+
...cond,
|
|
403
|
+
...opts?.includeDisabled ? {} : enabledNeFalse(),
|
|
404
|
+
...opts?.q ? { name: { $regex: escapeRegExpLiteral(opts.q), $options: "i" } } : {}
|
|
405
|
+
});
|
|
289
406
|
return {
|
|
290
407
|
...base,
|
|
291
408
|
async createAgent(data) {
|
|
@@ -303,26 +420,40 @@ function createAgentRepo(db) {
|
|
|
303
420
|
return base.delete(agent.id);
|
|
304
421
|
},
|
|
305
422
|
async updateByNameScoped(name, scope, projectId, patch) {
|
|
306
|
-
const agent = await this.getByNameScoped(name, scope, projectId);
|
|
423
|
+
const agent = await this.getByNameScoped(name, scope, projectId, { includeDisabled: true });
|
|
307
424
|
if (!agent || !agent.id) return null;
|
|
308
425
|
return base.update(agent.id, patch);
|
|
309
426
|
},
|
|
310
427
|
async deleteByNameScoped(name, scope, projectId) {
|
|
311
|
-
const agent = await this.getByNameScoped(name, scope, projectId);
|
|
428
|
+
const agent = await this.getByNameScoped(name, scope, projectId, { includeDisabled: true });
|
|
312
429
|
if (!agent || !agent.id) return false;
|
|
313
430
|
return base.delete(agent.id);
|
|
314
431
|
},
|
|
315
|
-
async getByNameScoped(name, scope, projectId) {
|
|
432
|
+
async getByNameScoped(name, scope, projectId, opts) {
|
|
316
433
|
const doc = await base._collection.findOne(
|
|
317
|
-
|
|
434
|
+
listQuery(
|
|
435
|
+
scope === "project" ? { name, scope: "project", projectId } : { name, scope: "global" },
|
|
436
|
+
opts
|
|
437
|
+
)
|
|
318
438
|
);
|
|
319
|
-
return doc ? toEntity(doc) : null;
|
|
439
|
+
return doc ? normalizeFunction(toEntity(doc)) : null;
|
|
320
440
|
},
|
|
321
|
-
async listAgentsByScope(projectId) {
|
|
322
|
-
return base.list({ $or: [{ scope: "global" }, { scope: "project", projectId }] });
|
|
441
|
+
async listAgentsByScope(projectId, opts) {
|
|
442
|
+
return base.list(listQuery({ $or: [{ scope: "global" }, { scope: "project", projectId }] }, opts));
|
|
323
443
|
},
|
|
324
|
-
async listAgentsByScopeFilter(scope, projectId) {
|
|
325
|
-
return base.list(
|
|
444
|
+
async listAgentsByScopeFilter(scope, projectId, opts) {
|
|
445
|
+
return base.list(
|
|
446
|
+
listQuery(scope === "project" ? { scope: "project", projectId } : { scope: "global" }, opts)
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
async listAgents(opts) {
|
|
450
|
+
return base.list(listQuery({}, opts));
|
|
451
|
+
},
|
|
452
|
+
async hasNameScopeConflict(name, scope) {
|
|
453
|
+
return await base._collection.countDocuments(
|
|
454
|
+
scope === "global" ? { name, scope: "project" } : { name, scope: "global" },
|
|
455
|
+
{ limit: 1 }
|
|
456
|
+
) > 0;
|
|
326
457
|
},
|
|
327
458
|
async copyAgent(sourceName, source, target, newName, skillRepo) {
|
|
328
459
|
const src = await base._collection.findOne(
|
|
@@ -331,7 +462,7 @@ function createAgentRepo(db) {
|
|
|
331
462
|
if (!src) {
|
|
332
463
|
throw new ConflictError("agent", `copy source not found: ${sourceName}`);
|
|
333
464
|
}
|
|
334
|
-
const sourceEntity = toEntity(src);
|
|
465
|
+
const sourceEntity = normalizeFunction(toEntity(src));
|
|
335
466
|
const sameScopeDup = await base._collection.findOne(
|
|
336
467
|
target.scope === "project" ? { name: newName, scope: "project", projectId: target.projectId } : { name: newName, scope: "global" },
|
|
337
468
|
{ projection: { _id: 1 } }
|
|
@@ -341,7 +472,7 @@ function createAgentRepo(db) {
|
|
|
341
472
|
}
|
|
342
473
|
await assertNameScopeAvailable2(base._collection, newName, target.scope, target.projectId);
|
|
343
474
|
await assertBoundSkillsCompatible(skillRepo, target.scope, target.projectId, src.boundSkills ?? []);
|
|
344
|
-
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, projectId: _sourceProjectId, scope: _sourceScope, ...rest } = sourceEntity;
|
|
475
|
+
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, projectId: _sourceProjectId, scope: _sourceScope, enabled: _sourceEnabled, ...rest } = sourceEntity;
|
|
345
476
|
const copy = {
|
|
346
477
|
...JSON.parse(JSON.stringify(rest)),
|
|
347
478
|
name: newName,
|
|
@@ -380,51 +511,123 @@ async function assertBoundSkillsCompatible(skillRepo, agentScope, agentProjectId
|
|
|
380
511
|
}
|
|
381
512
|
}
|
|
382
513
|
|
|
383
|
-
// src/
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
514
|
+
// src/schemas/dag-template.ts
|
|
515
|
+
import { z as z3 } from "zod";
|
|
516
|
+
|
|
517
|
+
// src/schemas/skill.ts
|
|
518
|
+
import { z as z2 } from "zod";
|
|
519
|
+
|
|
520
|
+
// src/schemas/reference.ts
|
|
521
|
+
import { z } from "zod";
|
|
522
|
+
var REFERENCE_LIMITS = {
|
|
523
|
+
/** 单资产引用文档数量上限 */
|
|
524
|
+
maxCount: 20,
|
|
525
|
+
/** 单引用文档内容字符数上限 */
|
|
526
|
+
maxContentChars: 1e6,
|
|
527
|
+
/** 聚合护栏:主文档正文 + Σ引用文档内容的字符总量上限 */
|
|
528
|
+
maxTotalChars: 3e6
|
|
529
|
+
};
|
|
530
|
+
var SKILL_MAIN_DOC = "SKILL.md";
|
|
531
|
+
var AGENT_MAIN_DOC = "agent.md";
|
|
532
|
+
var ReferenceDocBaseSchema = z.object({
|
|
533
|
+
/** 相对资产根的相对路径(正斜杠分隔;禁止绝对路径 / .. 回溯 / 反斜杠 / 空段 / 主文档名冲突) */
|
|
534
|
+
path: z.string().min(1),
|
|
535
|
+
/** 文档全文(空文档合法) */
|
|
536
|
+
content: z.string().max(REFERENCE_LIMITS.maxContentChars)
|
|
537
|
+
});
|
|
538
|
+
function canonicalizeReferencePath(path) {
|
|
539
|
+
return path.split("/").filter((segment) => segment !== ".").join("/").toLowerCase();
|
|
540
|
+
}
|
|
541
|
+
function sumReferenceContentChars(references) {
|
|
542
|
+
return (references ?? []).reduce((total, doc) => total + doc.content.length, 0);
|
|
543
|
+
}
|
|
544
|
+
function createReferencesSchema(mainDocName) {
|
|
545
|
+
const elementSchema = ReferenceDocBaseSchema.superRefine((val, ctx) => {
|
|
546
|
+
const path = val.path;
|
|
547
|
+
let reason = null;
|
|
548
|
+
if (path.startsWith("/")) {
|
|
549
|
+
reason = "\u7981\u6B62\u7EDD\u5BF9\u8DEF\u5F84";
|
|
550
|
+
} else if (path.includes("\\")) {
|
|
551
|
+
reason = "\u7981\u6B62\u53CD\u659C\u6760\u8DEF\u5F84\u5206\u9694\u7B26\uFF08\u987B\u4F7F\u7528\u6B63\u659C\u6760 /\uFF09";
|
|
552
|
+
} else {
|
|
553
|
+
const rawSegments = path.split("/");
|
|
554
|
+
if (rawSegments.some((segment) => segment === "")) {
|
|
555
|
+
reason = "\u7981\u6B62\u7A7A\u8DEF\u5F84\u6BB5\uFF08\u8FDE\u7EED\u6216\u5C3E\u968F\u659C\u6760\uFF09";
|
|
556
|
+
} else if (rawSegments.includes("..")) {
|
|
557
|
+
reason = "\u7981\u6B62\u8D8A\u754C\u56DE\u6EAF\uFF08.. \u8DEF\u5F84\u6BB5\uFF09";
|
|
558
|
+
} else if (canonicalizeReferencePath(path).length === 0) {
|
|
559
|
+
reason = '\u8DEF\u5F84\u4E0D\u80FD\u53EA\u7531 "." \u7EC4\u6210';
|
|
560
|
+
} else if (canonicalizeReferencePath(path) === canonicalizeReferencePath(mainDocName)) {
|
|
561
|
+
reason = `\u4E0E\u4E3B\u6587\u6863\u6587\u4EF6\u540D ${mainDocName} \u51B2\u7A81`;
|
|
401
562
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
563
|
+
}
|
|
564
|
+
if (reason !== null) {
|
|
565
|
+
ctx.addIssue({ code: "custom", path: ["path"], message: `${reason}\uFF1A${path}` });
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
return z.array(elementSchema).max(REFERENCE_LIMITS.maxCount).superRefine((docs, ctx) => {
|
|
569
|
+
const seen = /* @__PURE__ */ new Map();
|
|
570
|
+
docs.forEach((doc, index) => {
|
|
571
|
+
const canonical = canonicalizeReferencePath(doc.path);
|
|
572
|
+
const firstIndex = seen.get(canonical);
|
|
573
|
+
if (firstIndex !== void 0) {
|
|
574
|
+
ctx.addIssue({
|
|
575
|
+
code: "custom",
|
|
576
|
+
path: [index],
|
|
577
|
+
message: `\u5F15\u7528\u6587\u6863\u8DEF\u5F84\u91CD\u590D\uFF08\u4E0E\u7B2C ${firstIndex + 1} \u6761\u89C4\u8303\u5316\u540E\u76F8\u540C\uFF09\uFF1A${doc.path}`
|
|
578
|
+
});
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
seen.set(canonical, index);
|
|
582
|
+
});
|
|
583
|
+
const entries = docs.map((doc, index) => ({ canonical: canonicalizeReferencePath(doc.path), index }));
|
|
584
|
+
for (const a of entries) {
|
|
585
|
+
for (const b of entries) {
|
|
586
|
+
if (a.index >= b.index) continue;
|
|
587
|
+
if (isDirectoryPrefix(a.canonical, b.canonical) || isDirectoryPrefix(b.canonical, a.canonical)) {
|
|
588
|
+
ctx.addIssue({
|
|
589
|
+
code: "custom",
|
|
590
|
+
path: [b.index],
|
|
591
|
+
message: `\u5F15\u7528\u6587\u6863\u8DEF\u5F84\u4E92\u4E3A\u76EE\u5F55\u524D\u7F00\uFF08\u843D\u76D8\u65F6\u6587\u4EF6\u4E0E\u76EE\u5F55\u51B2\u7A81\uFF09\uFF1A${docs[a.index]?.path ?? "?"} \u4E0E ${docs[b.index]?.path ?? "?"}`
|
|
592
|
+
});
|
|
593
|
+
}
|
|
408
594
|
}
|
|
409
|
-
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, ...rest } = source;
|
|
410
|
-
const copy = {
|
|
411
|
-
...rest,
|
|
412
|
-
name: newName,
|
|
413
|
-
projectId: targetProjectId,
|
|
414
|
-
isDefault: false,
|
|
415
|
-
version: "1.0.0"
|
|
416
|
-
};
|
|
417
|
-
return base.create(copy);
|
|
418
595
|
}
|
|
419
|
-
};
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function isDirectoryPrefix(prefixCanonical, candidateCanonical) {
|
|
599
|
+
if (prefixCanonical.length >= candidateCanonical.length) return false;
|
|
600
|
+
if (!candidateCanonical.startsWith(prefixCanonical)) return false;
|
|
601
|
+
return candidateCanonical.charAt(prefixCanonical.length) === "/";
|
|
602
|
+
}
|
|
603
|
+
function assertReferencesDeletable(params) {
|
|
604
|
+
const { resource, name, existingReferences, nextReferences, nextMainBody, currentMainBody } = params;
|
|
605
|
+
if (nextReferences === void 0) return;
|
|
606
|
+
const nextPaths = new Set(nextReferences.map((doc) => doc.path));
|
|
607
|
+
const deletedPaths = (existingReferences ?? []).map((doc) => doc.path).filter((path) => !nextPaths.has(path));
|
|
608
|
+
if (deletedPaths.length === 0) return;
|
|
609
|
+
const mainBody = nextMainBody ?? currentMainBody;
|
|
610
|
+
const violating = deletedPaths.filter((path) => mainBody.includes(path));
|
|
611
|
+
if (violating.length === 0) return;
|
|
612
|
+
throw new ConflictError(resource, name, {
|
|
613
|
+
bizCode: "REFERENCE_IN_USE",
|
|
614
|
+
message: `${BIZ_CODE_MESSAGES.REFERENCE_IN_USE}\uFF08${resource} ${name}\uFF09\uFF1A${violating.join("\u3001")}`
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function assertReferenceAggregateLimit(params) {
|
|
618
|
+
const { effectiveMainBody, nextReferences } = params;
|
|
619
|
+
if (nextReferences === void 0) return;
|
|
620
|
+
const totalChars = effectiveMainBody.length + sumReferenceContentChars(nextReferences);
|
|
621
|
+
if (totalChars > REFERENCE_LIMITS.maxTotalChars) {
|
|
622
|
+
throw new ValidationError(
|
|
623
|
+
`\u4E3B\u6587\u6863\u4E0E\u5F15\u7528\u6587\u6863\u5185\u5BB9\u603B\u91CF ${String(totalChars)} \u5B57\u7B26\u8D85\u51FA\u805A\u5408\u4E0A\u9650 ${String(REFERENCE_LIMITS.maxTotalChars)}\uFF08\u542B\u5E93\u5185\u73B0\u6709\u4E3B\u6587\u6863\u6B63\u6587\u5408\u5E76\u8BA1\u7B97\uFF09`,
|
|
624
|
+
[{ path: ["references"], message: `\u603B\u91CF ${String(totalChars)} \u8D85\u51FA\u4E0A\u9650 ${String(REFERENCE_LIMITS.maxTotalChars)}` }]
|
|
625
|
+
);
|
|
626
|
+
}
|
|
420
627
|
}
|
|
421
|
-
|
|
422
|
-
// src/schemas/task.ts
|
|
423
|
-
import { z as z3 } from "zod";
|
|
424
628
|
|
|
425
629
|
// src/schemas/skill.ts
|
|
426
|
-
|
|
427
|
-
var SkillScopeSchema = z.enum(["global", "project"]);
|
|
630
|
+
var SkillScopeSchema = z2.enum(["global", "project"]);
|
|
428
631
|
var OBJECT_ID_HEX = /^[a-f0-9]{24}$/;
|
|
429
632
|
var SkillScopeRefine = (val, ctx) => {
|
|
430
633
|
if (val.scope === "project" && !val.projectId) {
|
|
@@ -435,38 +638,59 @@ var SkillScopeRefine = (val, ctx) => {
|
|
|
435
638
|
});
|
|
436
639
|
}
|
|
437
640
|
};
|
|
438
|
-
var SkillShapeSchema =
|
|
439
|
-
id:
|
|
440
|
-
name:
|
|
441
|
-
description:
|
|
442
|
-
content:
|
|
443
|
-
category:
|
|
444
|
-
version:
|
|
641
|
+
var SkillShapeSchema = z2.object({
|
|
642
|
+
id: z2.string().optional(),
|
|
643
|
+
name: z2.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
|
|
644
|
+
description: z2.string(),
|
|
645
|
+
content: z2.string(),
|
|
646
|
+
category: z2.string(),
|
|
647
|
+
version: z2.string().default("1.0.0"),
|
|
648
|
+
/** 引用文档集合(可选——存量单文件资产无此字段,行为与升级前一致) */
|
|
649
|
+
references: createReferencesSchema(SKILL_MAIN_DOC).optional(),
|
|
445
650
|
/** 作用域:global=跨项目复用(默认,仅作存量迁移读侧兜底)/ project=项目专用(N012 D3) */
|
|
446
651
|
scope: SkillScopeSchema.default("global"),
|
|
447
652
|
/** 仅 scope=project 时必填且为合法 ObjectId hex(superRefine 保证) */
|
|
448
|
-
projectId:
|
|
449
|
-
|
|
450
|
-
|
|
653
|
+
projectId: z2.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
|
|
654
|
+
/**
|
|
655
|
+
* T202608290001 资产启停(可选——存量无此字段 = 启用,零迁移)。
|
|
656
|
+
* 全局判断规则:enabled === false 即停用,true/缺失均启用。
|
|
657
|
+
* 唯一写入口是专用启停端点(POST /:name/enabled)——Create/Update/Copy 均不含此字段
|
|
658
|
+
* (规避 Zod4 .partial() default 注入 + 启停不触发版本提升)。
|
|
659
|
+
*/
|
|
660
|
+
enabled: z2.boolean().optional(),
|
|
661
|
+
createdAt: z2.date().optional(),
|
|
662
|
+
updatedAt: z2.date().optional()
|
|
451
663
|
});
|
|
452
|
-
var
|
|
664
|
+
var skillAggregateLimitRefine = (val, ctx) => {
|
|
665
|
+
const totalChars = (val.content?.length ?? 0) + sumReferenceContentChars(val.references);
|
|
666
|
+
if (totalChars > REFERENCE_LIMITS.maxTotalChars) {
|
|
667
|
+
ctx.addIssue({
|
|
668
|
+
code: "custom",
|
|
669
|
+
path: ["references"],
|
|
670
|
+
message: `\u4E3B\u6587\u6863\u4E0E\u5F15\u7528\u6587\u6863\u5185\u5BB9\u603B\u91CF ${totalChars} \u5B57\u7B26\u8D85\u51FA\u805A\u5408\u4E0A\u9650 ${REFERENCE_LIMITS.maxTotalChars}`
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
var SkillSchema = SkillShapeSchema.superRefine(SkillScopeRefine).superRefine(skillAggregateLimitRefine);
|
|
453
675
|
var SkillCreateSchema = SkillShapeSchema.omit({
|
|
454
676
|
id: true,
|
|
455
677
|
createdAt: true,
|
|
456
|
-
updatedAt: true
|
|
457
|
-
|
|
678
|
+
updatedAt: true,
|
|
679
|
+
enabled: true
|
|
680
|
+
}).extend({ scope: SkillScopeSchema }).superRefine(SkillScopeRefine).superRefine(skillAggregateLimitRefine);
|
|
458
681
|
var SkillUpdateSchema = SkillShapeSchema.omit({
|
|
459
682
|
id: true,
|
|
460
683
|
createdAt: true,
|
|
461
|
-
updatedAt: true
|
|
684
|
+
updatedAt: true,
|
|
685
|
+
enabled: true
|
|
462
686
|
}).extend({
|
|
463
|
-
version:
|
|
687
|
+
version: z2.string().optional(),
|
|
464
688
|
scope: SkillScopeSchema.optional()
|
|
465
|
-
}).partial().superRefine(SkillScopeRefine);
|
|
466
|
-
var SkillCopySchema =
|
|
467
|
-
newName:
|
|
689
|
+
}).partial().superRefine(SkillScopeRefine).superRefine(skillAggregateLimitRefine);
|
|
690
|
+
var SkillCopySchema = z2.object({
|
|
691
|
+
newName: z2.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
|
|
468
692
|
newScope: SkillScopeSchema,
|
|
469
|
-
targetProjectId:
|
|
693
|
+
targetProjectId: z2.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
|
|
470
694
|
}).superRefine((val, ctx) => {
|
|
471
695
|
if (val.newScope === "project" && !val.targetProjectId) {
|
|
472
696
|
ctx.addIssue({
|
|
@@ -478,26 +702,44 @@ var SkillCopySchema = z.object({
|
|
|
478
702
|
});
|
|
479
703
|
|
|
480
704
|
// src/schemas/dag-template.ts
|
|
481
|
-
|
|
482
|
-
var
|
|
483
|
-
var DagNodeTrackSchema = z2.string();
|
|
705
|
+
var DagNodePhaseSchema = z3.string();
|
|
706
|
+
var DagNodeTrackSchema = z3.string();
|
|
484
707
|
var DAG_PHASES = ["entry", "track", "test", "exit"];
|
|
485
708
|
var DAG_TRACKS = ["backend", "ui", "all"];
|
|
486
709
|
var NODE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
487
|
-
var
|
|
488
|
-
|
|
489
|
-
|
|
710
|
+
var capabilityNamesSchema = z3.array(z3.string().min(1)).superRefine((names, ctx) => {
|
|
711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
712
|
+
names.forEach((name, index) => {
|
|
713
|
+
if (seen.has(name)) {
|
|
714
|
+
ctx.addIssue({ code: "custom", path: [index], message: `\u540D\u79F0\u91CD\u590D\uFF1A${name}` });
|
|
715
|
+
}
|
|
716
|
+
seen.add(name);
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
var SourcePresetSchema = z3.object({
|
|
720
|
+
code: z3.string().min(1),
|
|
721
|
+
version: z3.string(),
|
|
722
|
+
contentHash: z3.string(),
|
|
723
|
+
/** Agent 名称独立基线;不改变既有 contentHash 算法,存量缺键按空清单比较 */
|
|
724
|
+
agents: capabilityNamesSchema.optional()
|
|
725
|
+
});
|
|
726
|
+
var DagNodeSchema = z3.object({
|
|
727
|
+
id: z3.string().regex(NODE_ID_PATTERN, "node id \u53EA\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u4E0B\u5212\u7EBF/\u8FDE\u5B57\u7B26\uFF08\u70B9\u8DEF\u5F84\u62FC\u63A5\u5B89\u5168\u524D\u63D0\uFF09"),
|
|
728
|
+
label: z3.string(),
|
|
490
729
|
phase: DagNodePhaseSchema,
|
|
491
730
|
track: DagNodeTrackSchema,
|
|
492
|
-
prompt:
|
|
493
|
-
skills:
|
|
731
|
+
prompt: z3.string(),
|
|
732
|
+
skills: capabilityNamesSchema.default([]),
|
|
733
|
+
/** 存量模板允许缺键;新组装和任务实例化路径写入完整数组 */
|
|
734
|
+
agents: capabilityNamesSchema.optional(),
|
|
735
|
+
sourcePreset: SourcePresetSchema.optional()
|
|
494
736
|
});
|
|
495
|
-
var PausePointTypeSchema =
|
|
737
|
+
var PausePointTypeSchema = z3.enum(["human_approval", "checkpoint"]);
|
|
496
738
|
var PAUSE_POINT_TYPES = ["human_approval", "checkpoint"];
|
|
497
|
-
var EdgePausePointBaseSchema =
|
|
739
|
+
var EdgePausePointBaseSchema = z3.object({
|
|
498
740
|
type: PausePointTypeSchema,
|
|
499
|
-
description:
|
|
500
|
-
autoResume:
|
|
741
|
+
description: z3.string(),
|
|
742
|
+
autoResume: z3.boolean().default(false)
|
|
501
743
|
});
|
|
502
744
|
var EdgePausePointSchema = EdgePausePointBaseSchema.superRefine((pp, ctx) => {
|
|
503
745
|
if (pp.type === "human_approval" && pp.autoResume) {
|
|
@@ -513,152 +755,394 @@ var EdgePausePointSchema = EdgePausePointBaseSchema.superRefine((pp, ctx) => {
|
|
|
513
755
|
});
|
|
514
756
|
}
|
|
515
757
|
});
|
|
516
|
-
var
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
758
|
+
var TEMPLATE_CODE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)+$/;
|
|
759
|
+
var TEMPLATE_CODE_MAX_LENGTH = 31;
|
|
760
|
+
var TemplateCodeFieldSchema = z3.string().max(TEMPLATE_CODE_MAX_LENGTH).regex(TEMPLATE_CODE_REGEX, "code \u5FC5\u987B\u4E3A kebab-case \u4E14\u81F3\u5C11\u542B\u4E00\u4E2A\u8FDE\u5B57\u7B26\u6BB5\uFF08\u5982 full-workflow\uFF09\uFF0C\u4E0D\u53EF\u4E0E\u6570\u636E\u5E93 id \u5F62\u6001\u6DF7\u6DC6");
|
|
761
|
+
var TemplateCandidateSchema = z3.object({
|
|
762
|
+
id: z3.string(),
|
|
763
|
+
code: z3.string(),
|
|
764
|
+
name: z3.string()
|
|
765
|
+
});
|
|
766
|
+
var DagEdgeSchema = z3.object({
|
|
767
|
+
from: z3.string(),
|
|
768
|
+
to: z3.string(),
|
|
769
|
+
condition: z3.string().optional(),
|
|
520
770
|
/** N017 D1:边暂停点(可选)。human_approval=人工审批门(advance 落 paused 等 approve);checkpoint=检查点(仅记录直接流转) */
|
|
521
771
|
pausePoint: EdgePausePointSchema.optional()
|
|
522
772
|
});
|
|
523
|
-
var DagTemplateSchema =
|
|
524
|
-
id:
|
|
525
|
-
name:
|
|
773
|
+
var DagTemplateSchema = z3.object({
|
|
774
|
+
id: z3.string().optional(),
|
|
775
|
+
name: z3.string(),
|
|
526
776
|
/** 归属项目 id(N012:实体层去哨兵 default,由路由层解析写入——校验项目存在且 active 后落库) */
|
|
527
|
-
projectId:
|
|
528
|
-
description:
|
|
529
|
-
|
|
530
|
-
|
|
777
|
+
projectId: z3.string(),
|
|
778
|
+
description: z3.string(),
|
|
779
|
+
/**
|
|
780
|
+
* T202609020002 业务 code(可选——存量迁移前的模板无此字段,由启动迁移补齐)。
|
|
781
|
+
* 项目内唯一({projectId, code} 复合唯一索引,迁移后建)、创建后不可变;
|
|
782
|
+
* name 仍承担展示职责(可改、可中文)——职责分离,改名不影响 code 引用。
|
|
783
|
+
*/
|
|
784
|
+
code: TemplateCodeFieldSchema.optional(),
|
|
785
|
+
nodes: z3.array(DagNodeSchema),
|
|
786
|
+
edges: z3.array(DagEdgeSchema),
|
|
531
787
|
/** UI 布局坐标(nodeId → {x,y})。UI 关注点,advance 引擎零感知;旧模板无此字段 → 前端 dagre 兜底布局(N008 D2) */
|
|
532
|
-
layout:
|
|
533
|
-
isDefault:
|
|
534
|
-
version:
|
|
535
|
-
|
|
536
|
-
|
|
788
|
+
layout: z3.record(z3.string(), z3.object({ x: z3.number(), y: z3.number() })).optional(),
|
|
789
|
+
isDefault: z3.boolean().default(false),
|
|
790
|
+
version: z3.string().default("1.0.0"),
|
|
791
|
+
/**
|
|
792
|
+
* T202608290001 资产启停(可选——存量无此字段 = 启用,零迁移)。
|
|
793
|
+
* 全局判断规则:enabled === false 即停用,true/缺失均启用。
|
|
794
|
+
* 唯一写入口是专用启停端点(POST /:id/enabled)——Create/Update/Copy 均不含此字段;
|
|
795
|
+
* 停用模板对 CLI/MCP 列举与任务创建入口不可见(Web 管理台始终可见可重启)。
|
|
796
|
+
*/
|
|
797
|
+
enabled: z3.boolean().optional(),
|
|
798
|
+
createdAt: z3.date().optional(),
|
|
799
|
+
updatedAt: z3.date().optional()
|
|
537
800
|
});
|
|
538
801
|
var DagTemplateCreateSchema = DagTemplateSchema.omit({
|
|
539
802
|
id: true,
|
|
540
803
|
createdAt: true,
|
|
541
|
-
updatedAt: true
|
|
804
|
+
updatedAt: true,
|
|
805
|
+
enabled: true
|
|
542
806
|
});
|
|
543
807
|
var DagTemplateUpdateSchema = DagTemplateCreateSchema.extend({
|
|
544
|
-
isDefault:
|
|
545
|
-
version:
|
|
808
|
+
isDefault: z3.boolean().optional(),
|
|
809
|
+
version: z3.string().optional()
|
|
546
810
|
}).partial();
|
|
547
|
-
var DagTemplateCopySchema =
|
|
548
|
-
newName:
|
|
549
|
-
targetProjectId:
|
|
811
|
+
var DagTemplateCopySchema = z3.object({
|
|
812
|
+
newName: z3.string().min(1),
|
|
813
|
+
targetProjectId: z3.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex"),
|
|
814
|
+
newCode: TemplateCodeFieldSchema.optional()
|
|
550
815
|
});
|
|
816
|
+
function findDuplicateNodeIds(nodes) {
|
|
817
|
+
const seen = /* @__PURE__ */ new Set();
|
|
818
|
+
const duplicated = /* @__PURE__ */ new Set();
|
|
819
|
+
for (const node of nodes) {
|
|
820
|
+
if (seen.has(node.id)) duplicated.add(node.id);
|
|
821
|
+
seen.add(node.id);
|
|
822
|
+
}
|
|
823
|
+
return [...duplicated];
|
|
824
|
+
}
|
|
825
|
+
function assertUniqueNodeIds(nodes) {
|
|
826
|
+
const duplicated = findDuplicateNodeIds(nodes);
|
|
827
|
+
if (duplicated.length > 0) {
|
|
828
|
+
throw new ValidationError(
|
|
829
|
+
`\u6A21\u677F\u8282\u70B9 id \u91CD\u590D\uFF1A${duplicated.join(", ")}`,
|
|
830
|
+
duplicated.map((id) => ({ path: ["nodes"], message: `\u8282\u70B9 id "${id}" \u5728\u6A21\u677F\u5185\u91CD\u590D` })),
|
|
831
|
+
{
|
|
832
|
+
bizCode: "TEMPLATE_NODE_ID_DUPLICATE",
|
|
833
|
+
message: `${BIZ_CODE_MESSAGES.TEMPLATE_NODE_ID_DUPLICATE}\uFF1A${duplicated.join(", ")}`
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/store/repos/dag-template.repo.ts
|
|
840
|
+
function normalizeNameToTemplateCode(name) {
|
|
841
|
+
const folded = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
842
|
+
return folded.slice(0, TEMPLATE_CODE_MAX_LENGTH);
|
|
843
|
+
}
|
|
844
|
+
function isLegalTemplateCode(code) {
|
|
845
|
+
return code.length <= TEMPLATE_CODE_MAX_LENGTH && TEMPLATE_CODE_REGEX.test(code);
|
|
846
|
+
}
|
|
847
|
+
function buildTemplateCode(seedName, takenCodes) {
|
|
848
|
+
const taken = new Set(takenCodes);
|
|
849
|
+
const base = normalizeNameToTemplateCode(seedName);
|
|
850
|
+
let primary;
|
|
851
|
+
if (base === "") {
|
|
852
|
+
primary = "tpl-1";
|
|
853
|
+
} else if (isLegalTemplateCode(base)) {
|
|
854
|
+
primary = base;
|
|
855
|
+
} else {
|
|
856
|
+
primary = `tpl-${base}`.slice(0, TEMPLATE_CODE_MAX_LENGTH);
|
|
857
|
+
}
|
|
858
|
+
let candidate = primary;
|
|
859
|
+
let n = 1;
|
|
860
|
+
while (taken.has(candidate) || !isLegalTemplateCode(candidate)) {
|
|
861
|
+
n += 1;
|
|
862
|
+
const suffix = `-${n}`;
|
|
863
|
+
candidate = `${primary.slice(0, TEMPLATE_CODE_MAX_LENGTH - suffix.length)}${suffix}`;
|
|
864
|
+
}
|
|
865
|
+
return candidate;
|
|
866
|
+
}
|
|
867
|
+
function levenshtein(a, b) {
|
|
868
|
+
const m = a.length;
|
|
869
|
+
const n = b.length;
|
|
870
|
+
if (m === 0) return n;
|
|
871
|
+
if (n === 0) return m;
|
|
872
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
873
|
+
for (let i = 1; i <= m; i++) {
|
|
874
|
+
const curr = [i];
|
|
875
|
+
for (let j = 1; j <= n; j++) {
|
|
876
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
877
|
+
curr[j] = Math.min(prev[j - 1] + cost, curr[j - 1] + 1, prev[j] + 1);
|
|
878
|
+
}
|
|
879
|
+
prev = curr;
|
|
880
|
+
}
|
|
881
|
+
return prev[n];
|
|
882
|
+
}
|
|
883
|
+
function scoreTemplateCandidate(ref, tpl) {
|
|
884
|
+
const refLower = ref.toLowerCase();
|
|
885
|
+
if (tpl.name === ref) return 100;
|
|
886
|
+
const code = tpl.code?.toLowerCase();
|
|
887
|
+
if (code === refLower) return 100;
|
|
888
|
+
if (code !== void 0) {
|
|
889
|
+
if (code.startsWith(refLower) || refLower.startsWith(code)) return 80;
|
|
890
|
+
if (levenshtein(code, refLower) <= 2) return 60;
|
|
891
|
+
}
|
|
892
|
+
const nameLower = tpl.name.toLowerCase();
|
|
893
|
+
if (nameLower.startsWith(refLower) || refLower.startsWith(nameLower)) return 70;
|
|
894
|
+
if (nameLower.includes(refLower) || refLower.includes(nameLower)) return 65;
|
|
895
|
+
if (tpl.id !== void 0 && ref.length === 24 && levenshtein(tpl.id, ref) <= 2) return 50;
|
|
896
|
+
if (levenshtein(nameLower, refLower) <= 2) return 40;
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
var OBJECT_ID_HEX_RE = /^[a-f0-9]{24}$/;
|
|
900
|
+
function dupKeyViolation(e) {
|
|
901
|
+
if (typeof e !== "object" || e === null) return null;
|
|
902
|
+
if (!("code" in e && e.code === 11e3)) return null;
|
|
903
|
+
const kv = e.keyValue;
|
|
904
|
+
return typeof kv === "object" && kv !== null ? kv : null;
|
|
905
|
+
}
|
|
906
|
+
function createDagTemplateRepo(db) {
|
|
907
|
+
const base = createRepo(db.collection("dag_templates"));
|
|
908
|
+
const listProjectCodes = async (projectId) => {
|
|
909
|
+
const docs = await base._collection.find({ projectId }, { projection: { code: 1 } }).toArray();
|
|
910
|
+
return docs.map((d) => typeof d.code === "string" ? d.code : "").filter((c) => c !== "");
|
|
911
|
+
};
|
|
912
|
+
const getByCode = async (projectId, code) => {
|
|
913
|
+
const doc = await base._collection.findOne({ projectId, code });
|
|
914
|
+
return doc ? toEntity(doc) : null;
|
|
915
|
+
};
|
|
916
|
+
return {
|
|
917
|
+
...base,
|
|
918
|
+
async createDagTemplate(data) {
|
|
919
|
+
const payload = { ...data };
|
|
920
|
+
if (payload.code === void 0) {
|
|
921
|
+
payload.code = buildTemplateCode(data.name, await listProjectCodes(data.projectId));
|
|
922
|
+
}
|
|
923
|
+
try {
|
|
924
|
+
return await base.create(payload);
|
|
925
|
+
} catch (e) {
|
|
926
|
+
const kv = dupKeyViolation(e);
|
|
927
|
+
if (kv !== null && "code" in kv) {
|
|
928
|
+
throw new ConflictError("dag-template", `code conflict: ${String(kv.code)}`, {
|
|
929
|
+
bizCode: "TEMPLATE_CODE_CONFLICT",
|
|
930
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_CODE_CONFLICT
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
if (kv !== null && "name" in kv) {
|
|
934
|
+
throw new ConflictError("dag-template", `name conflict: ${String(kv.name)}`, {
|
|
935
|
+
bizCode: "TEMPLATE_NAME_CONFLICT",
|
|
936
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_NAME_CONFLICT
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
throw e;
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
async listDagTemplates(filter) {
|
|
943
|
+
const query = {};
|
|
944
|
+
if (filter?.projectId) query.projectId = filter.projectId;
|
|
945
|
+
if (filter?.name) query.name = filter.name;
|
|
946
|
+
if (filter?.code) query.code = filter.code;
|
|
947
|
+
if (!filter?.includeDisabled) Object.assign(query, enabledNeFalse());
|
|
948
|
+
return base.list(query);
|
|
949
|
+
},
|
|
950
|
+
async copyDagTemplate(sourceId, targetProjectId, newName, newCode) {
|
|
951
|
+
const source = await base.getById(sourceId);
|
|
952
|
+
if (!source) {
|
|
953
|
+
throw new ConflictError("dagTemplate", `copy source not found: ${sourceId}`);
|
|
954
|
+
}
|
|
955
|
+
const dup = await base._collection.findOne(
|
|
956
|
+
{ projectId: targetProjectId, name: newName },
|
|
957
|
+
{ projection: { _id: 1 } }
|
|
958
|
+
);
|
|
959
|
+
if (dup) {
|
|
960
|
+
throw new ConflictError("dagTemplate", `name conflict in target project: ${newName}`, {
|
|
961
|
+
bizCode: "TEMPLATE_NAME_CONFLICT",
|
|
962
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_NAME_CONFLICT
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
if (newCode !== void 0) {
|
|
966
|
+
const dupCode = await base._collection.findOne(
|
|
967
|
+
{ projectId: targetProjectId, code: newCode },
|
|
968
|
+
{ projection: { _id: 1, name: 1 } }
|
|
969
|
+
);
|
|
970
|
+
if (dupCode) {
|
|
971
|
+
throw new ConflictError("dagTemplate", `code conflict in target project: ${newCode}`, {
|
|
972
|
+
bizCode: "TEMPLATE_CODE_CONFLICT",
|
|
973
|
+
message: `${BIZ_CODE_MESSAGES.TEMPLATE_CODE_CONFLICT}\uFF08\u51B2\u7A81\u6A21\u677F\uFF1A${String(dupCode.name)}\uFF09`
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
const code = newCode ?? buildTemplateCode(newName, await listProjectCodes(targetProjectId));
|
|
978
|
+
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, enabled: _sourceEnabled, code: _sourceCode, ...rest } = source;
|
|
979
|
+
const copy = {
|
|
980
|
+
...rest,
|
|
981
|
+
name: newName,
|
|
982
|
+
code,
|
|
983
|
+
projectId: targetProjectId,
|
|
984
|
+
isDefault: false,
|
|
985
|
+
version: "1.0.0"
|
|
986
|
+
};
|
|
987
|
+
try {
|
|
988
|
+
return await base.create(copy);
|
|
989
|
+
} catch (e) {
|
|
990
|
+
const kv = dupKeyViolation(e);
|
|
991
|
+
if (kv !== null && "code" in kv) {
|
|
992
|
+
throw new ConflictError("dag-template", `code conflict: ${String(kv.code)}`, {
|
|
993
|
+
bizCode: "TEMPLATE_CODE_CONFLICT",
|
|
994
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_CODE_CONFLICT
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
if (kv !== null && "name" in kv) {
|
|
998
|
+
throw new ConflictError("dag-template", `name conflict: ${String(kv.name)}`, {
|
|
999
|
+
bizCode: "TEMPLATE_NAME_CONFLICT",
|
|
1000
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_NAME_CONFLICT
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
throw e;
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
async getByCode(projectId, code) {
|
|
1007
|
+
return getByCode(projectId, code);
|
|
1008
|
+
},
|
|
1009
|
+
async resolveTemplateByRef(ref, projectCtx) {
|
|
1010
|
+
if (OBJECT_ID_HEX_RE.test(ref)) {
|
|
1011
|
+
const byId = await base.getById(ref);
|
|
1012
|
+
if (byId) return byId;
|
|
1013
|
+
}
|
|
1014
|
+
if (projectCtx !== void 0 && !OBJECT_ID_HEX_RE.test(ref)) {
|
|
1015
|
+
return getByCode(projectCtx, ref);
|
|
1016
|
+
}
|
|
1017
|
+
return null;
|
|
1018
|
+
},
|
|
1019
|
+
async findSimilarTemplates(projectId, ref, limit = 5) {
|
|
1020
|
+
const docs = await base._collection.find({ projectId }, { projection: { _id: 1, code: 1, name: 1 } }).toArray();
|
|
1021
|
+
const scored = docs.map((d) => {
|
|
1022
|
+
const id = d._id.toString();
|
|
1023
|
+
const code = typeof d.code === "string" ? d.code : "";
|
|
1024
|
+
const name = typeof d.name === "string" ? d.name : "";
|
|
1025
|
+
const score = scoreTemplateCandidate(ref, { id, ...code !== "" ? { code } : {}, name });
|
|
1026
|
+
return score === null ? null : { score, candidate: { id, code, name } };
|
|
1027
|
+
}).filter((x) => x !== null).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1028
|
+
return scored.map((x) => x.candidate);
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
551
1032
|
|
|
552
1033
|
// src/schemas/task.ts
|
|
1034
|
+
import { z as z4 } from "zod";
|
|
553
1035
|
var TASK_TYPES = ["feature", "bugfix", "ui-tweak", "research"];
|
|
554
1036
|
var ENTRY_ID_REGEX = /^[a-z0-9]{6}$/;
|
|
555
|
-
var CheckItemSchema =
|
|
556
|
-
id:
|
|
557
|
-
item:
|
|
558
|
-
passed:
|
|
1037
|
+
var CheckItemSchema = z4.object({
|
|
1038
|
+
id: z4.string().regex(ENTRY_ID_REGEX),
|
|
1039
|
+
item: z4.string().min(1),
|
|
1040
|
+
passed: z4.boolean()
|
|
559
1041
|
});
|
|
560
1042
|
var ARTIFACT_TYPES = ["prd", "tech", "code", "test", "doc", "other"];
|
|
561
|
-
var ArtifactSchema =
|
|
562
|
-
id:
|
|
563
|
-
type:
|
|
564
|
-
path:
|
|
565
|
-
note:
|
|
566
|
-
content:
|
|
567
|
-
});
|
|
568
|
-
var ConfirmationSchema =
|
|
569
|
-
id:
|
|
570
|
-
quote:
|
|
571
|
-
at:
|
|
572
|
-
});
|
|
573
|
-
var DecisionSchema =
|
|
574
|
-
id:
|
|
575
|
-
topic:
|
|
576
|
-
decision:
|
|
577
|
-
});
|
|
578
|
-
var ReviewSummarySchema =
|
|
579
|
-
verdict:
|
|
580
|
-
rounds:
|
|
581
|
-
critical:
|
|
582
|
-
});
|
|
583
|
-
var NodeRecordSchema =
|
|
584
|
-
summary:
|
|
585
|
-
checks:
|
|
586
|
-
artifacts:
|
|
587
|
-
confirmations:
|
|
588
|
-
decisions:
|
|
1043
|
+
var ArtifactSchema = z4.object({
|
|
1044
|
+
id: z4.string().regex(ENTRY_ID_REGEX),
|
|
1045
|
+
type: z4.enum(ARTIFACT_TYPES),
|
|
1046
|
+
path: z4.string().min(1),
|
|
1047
|
+
note: z4.string().optional(),
|
|
1048
|
+
content: z4.string().max(2e5).optional()
|
|
1049
|
+
});
|
|
1050
|
+
var ConfirmationSchema = z4.object({
|
|
1051
|
+
id: z4.string().regex(ENTRY_ID_REGEX),
|
|
1052
|
+
quote: z4.string().min(1).max(2e3),
|
|
1053
|
+
at: z4.date()
|
|
1054
|
+
});
|
|
1055
|
+
var DecisionSchema = z4.object({
|
|
1056
|
+
id: z4.string().regex(ENTRY_ID_REGEX),
|
|
1057
|
+
topic: z4.string().min(1),
|
|
1058
|
+
decision: z4.string().min(1)
|
|
1059
|
+
});
|
|
1060
|
+
var ReviewSummarySchema = z4.object({
|
|
1061
|
+
verdict: z4.enum(["pass", "fail"]),
|
|
1062
|
+
rounds: z4.number().int().min(1),
|
|
1063
|
+
critical: z4.number().int().min(0)
|
|
1064
|
+
});
|
|
1065
|
+
var NodeRecordSchema = z4.object({
|
|
1066
|
+
summary: z4.string().optional(),
|
|
1067
|
+
checks: z4.array(CheckItemSchema).default([]),
|
|
1068
|
+
artifacts: z4.array(ArtifactSchema).default([]),
|
|
1069
|
+
confirmations: z4.array(ConfirmationSchema).default([]),
|
|
1070
|
+
decisions: z4.array(DecisionSchema).default([]),
|
|
589
1071
|
review: ReviewSummarySchema.optional()
|
|
590
1072
|
});
|
|
591
|
-
var TaskDocContentSchema =
|
|
592
|
-
what:
|
|
593
|
-
why:
|
|
594
|
-
acceptance:
|
|
595
|
-
nonGoals:
|
|
596
|
-
trackNote:
|
|
1073
|
+
var TaskDocContentSchema = z4.object({
|
|
1074
|
+
what: z4.string().min(1),
|
|
1075
|
+
why: z4.string().min(1),
|
|
1076
|
+
acceptance: z4.array(z4.string()).default([]),
|
|
1077
|
+
nonGoals: z4.array(z4.string()).default([]),
|
|
1078
|
+
trackNote: z4.string().optional()
|
|
597
1079
|
});
|
|
598
|
-
var ArchNoteSchema =
|
|
599
|
-
id:
|
|
600
|
-
text:
|
|
601
|
-
at:
|
|
1080
|
+
var ArchNoteSchema = z4.object({
|
|
1081
|
+
id: z4.string().regex(ENTRY_ID_REGEX),
|
|
1082
|
+
text: z4.string().min(1).max(2e3),
|
|
1083
|
+
at: z4.date()
|
|
602
1084
|
});
|
|
603
|
-
var NodeStatusSchema =
|
|
1085
|
+
var NodeStatusSchema = z4.string();
|
|
604
1086
|
var NODE_STATUSES = ["pending", "active", "completed", "skipped"];
|
|
605
|
-
var NodeStateSchema =
|
|
1087
|
+
var NodeStateSchema = z4.object({
|
|
606
1088
|
status: NodeStatusSchema,
|
|
607
|
-
enteredAt:
|
|
608
|
-
completedAt:
|
|
609
|
-
});
|
|
610
|
-
var
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1089
|
+
enteredAt: z4.date().nullable().default(null),
|
|
1090
|
+
completedAt: z4.date().nullable().default(null)
|
|
1091
|
+
});
|
|
1092
|
+
var DagInstanceNodeSchema = z4.object({
|
|
1093
|
+
id: z4.string(),
|
|
1094
|
+
label: z4.string(),
|
|
1095
|
+
// N016 F9:枚举放宽(注册表权威)
|
|
1096
|
+
phase: z4.string(),
|
|
1097
|
+
track: z4.string(),
|
|
1098
|
+
prompt: z4.string(),
|
|
1099
|
+
skills: z4.array(z4.string()).default([]),
|
|
1100
|
+
agents: z4.array(z4.string()).default([])
|
|
1101
|
+
});
|
|
1102
|
+
var DagInstanceSchema = z4.object({
|
|
1103
|
+
templateId: z4.string(),
|
|
1104
|
+
templateVersion: z4.string(),
|
|
1105
|
+
nodes: z4.array(DagInstanceNodeSchema),
|
|
622
1106
|
// N017 D1:暂停点内联到边(human_approval=审批门 / checkpoint=检查点),顶层数组删除
|
|
623
|
-
edges:
|
|
624
|
-
from:
|
|
625
|
-
to:
|
|
626
|
-
condition:
|
|
1107
|
+
edges: z4.array(z4.object({
|
|
1108
|
+
from: z4.string(),
|
|
1109
|
+
to: z4.string(),
|
|
1110
|
+
condition: z4.string().optional(),
|
|
627
1111
|
pausePoint: EdgePausePointBaseSchema.optional()
|
|
628
1112
|
})),
|
|
629
|
-
prunedNodes:
|
|
630
|
-
nodeStates:
|
|
1113
|
+
prunedNodes: z4.array(z4.string()),
|
|
1114
|
+
nodeStates: z4.record(z4.string(), NodeStateSchema)
|
|
631
1115
|
});
|
|
632
|
-
var HistoryActionSchema =
|
|
633
|
-
var HistoryEntrySchema =
|
|
634
|
-
nodeId:
|
|
1116
|
+
var HistoryActionSchema = z4.enum(["entered", "advanced", "paused", "resumed", "approved", "rejected", "completed", "cancelled"]);
|
|
1117
|
+
var HistoryEntrySchema = z4.object({
|
|
1118
|
+
nodeId: z4.string(),
|
|
635
1119
|
action: HistoryActionSchema,
|
|
636
|
-
timestamp:
|
|
637
|
-
details:
|
|
1120
|
+
timestamp: z4.date().default(() => /* @__PURE__ */ new Date()),
|
|
1121
|
+
details: z4.record(z4.string(), z4.unknown()).default({})
|
|
638
1122
|
});
|
|
639
|
-
var TaskStatusSchema =
|
|
640
|
-
var TaskPhaseSchema =
|
|
1123
|
+
var TaskStatusSchema = z4.string();
|
|
1124
|
+
var TaskPhaseSchema = z4.string();
|
|
641
1125
|
var TASK_STATUSES = ["active", "paused", "completed", "cancelled"];
|
|
642
1126
|
var TASK_PHASES = ["entry", "track", "test", "exit"];
|
|
643
|
-
var TaskSchema =
|
|
644
|
-
id:
|
|
645
|
-
taskId:
|
|
646
|
-
title:
|
|
1127
|
+
var TaskSchema = z4.object({
|
|
1128
|
+
id: z4.string().optional(),
|
|
1129
|
+
taskId: z4.string(),
|
|
1130
|
+
title: z4.string(),
|
|
647
1131
|
/**
|
|
648
1132
|
* 任务类型标签(T202608240003):创建时确定、此后不可变(PATCH 白名单不含 type)。
|
|
649
1133
|
* 入参与落库同源 enum——持久层防脏值,模板 PRD 调研变体判定依赖精确值。
|
|
650
1134
|
*/
|
|
651
|
-
type:
|
|
1135
|
+
type: z4.enum(TASK_TYPES).optional(),
|
|
652
1136
|
/** 归属项目 id(N012:实体层去哨兵 default,由 repo 显式写入——路由层解析:显式携带校验一致性 / 缺省继承模板 projectId) */
|
|
653
|
-
projectId:
|
|
654
|
-
dagTemplateId:
|
|
1137
|
+
projectId: z4.string(),
|
|
1138
|
+
dagTemplateId: z4.string(),
|
|
655
1139
|
dagInstance: DagInstanceSchema,
|
|
656
|
-
currentNode:
|
|
1140
|
+
currentNode: z4.string(),
|
|
657
1141
|
currentPhase: TaskPhaseSchema,
|
|
658
1142
|
status: TaskStatusSchema,
|
|
659
1143
|
/** 暂停位置:from 节点 id(暂停点暂停,approve 经 findNextEdge 重推导边)/ null(手动暂停) */
|
|
660
|
-
pausedAt:
|
|
661
|
-
track:
|
|
1144
|
+
pausedAt: z4.string().nullable().default(null),
|
|
1145
|
+
track: z4.string(),
|
|
662
1146
|
/**
|
|
663
1147
|
* N020 D1:任务级结构化文档(未写时字段缺省——语义即「未写」,不做 null 占位)。
|
|
664
1148
|
* 写入口:PATCH /api/tasks/:id/doc(字段级 $set + acceptance/nonGoals 条目 $push)。
|
|
@@ -669,27 +1153,33 @@ var TaskSchema = z3.object({
|
|
|
669
1153
|
* MongoDB 自动创建中间对象,故单条目字段可能先于容器整体存在——读侧对
|
|
670
1154
|
* record 内数组字段按 `?? []` 容错(toEntity 不跑 parse 补 default)。
|
|
671
1155
|
*/
|
|
672
|
-
nodeRecords:
|
|
1156
|
+
nodeRecords: z4.record(z4.string(), NodeRecordSchema).default({}),
|
|
673
1157
|
/** N020 D1:跨节点累积素材(架构信息收集条目,服务端生成 id/at) */
|
|
674
|
-
archNotes:
|
|
675
|
-
history:
|
|
676
|
-
createdAt:
|
|
677
|
-
updatedAt:
|
|
1158
|
+
archNotes: z4.array(ArchNoteSchema).default([]),
|
|
1159
|
+
history: z4.array(HistoryEntrySchema).default([]),
|
|
1160
|
+
createdAt: z4.date().optional(),
|
|
1161
|
+
updatedAt: z4.date().optional()
|
|
678
1162
|
});
|
|
679
|
-
var TaskCreateInputSchema =
|
|
680
|
-
title:
|
|
681
|
-
track:
|
|
682
|
-
|
|
1163
|
+
var TaskCreateInputSchema = z4.object({
|
|
1164
|
+
title: z4.string(),
|
|
1165
|
+
track: z4.string(),
|
|
1166
|
+
/**
|
|
1167
|
+
* T202609020002:模板引用支持 code|id 双形态——24-hex 直查数据库 id 命中即用
|
|
1168
|
+
* (hex 未命中不转 code 检索:code 必含连字符,与 hex 形态天然互斥);非 hex 在
|
|
1169
|
+
* 项目上下文内按 code 检索(项目域经鉴权过滤:project Token 强制本项目,
|
|
1170
|
+
* open/admin 显式 projectId 优先)。任务实体落库的 dagTemplateId 恒为解析后的数据库 id。
|
|
1171
|
+
*/
|
|
1172
|
+
dagTemplateId: z4.string(),
|
|
683
1173
|
/**
|
|
684
1174
|
* T202608240003 创建时剪枝:显式剔除的节点 id(在轨道自动剔除之外)。
|
|
685
1175
|
* 清单来源约束 = task-create skill 的类型×轨道映射矩阵(skip 只允许来自映射,禁止自由发挥);
|
|
686
1176
|
* 剪枝算法与图校验见 core/task/prune.ts。
|
|
687
1177
|
*/
|
|
688
|
-
skipNodes:
|
|
1178
|
+
skipNodes: z4.array(z4.string().regex(NODE_ID_PATTERN, "skipNodes \u6761\u76EE\u987B\u4E3A\u5408\u6CD5\u8282\u70B9 id")).optional(),
|
|
689
1179
|
/** 任务类型标签(见 TASK_TYPES;与 track 的合法组合由 server 入口校验) */
|
|
690
|
-
type:
|
|
1180
|
+
type: z4.enum(TASK_TYPES).optional(),
|
|
691
1181
|
/** 归属项目:可选(缺省 → 继承模板 projectId,D2;显式携带且 ≠ 模板 → 409 TEMPLATE_PROJECT_MISMATCH) */
|
|
692
|
-
projectId:
|
|
1182
|
+
projectId: z4.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
|
|
693
1183
|
});
|
|
694
1184
|
|
|
695
1185
|
// src/task/track-match.ts
|
|
@@ -948,6 +1438,26 @@ async function resumeTask(deps, taskId, decision) {
|
|
|
948
1438
|
expectedCurrentNode: task.currentNode
|
|
949
1439
|
});
|
|
950
1440
|
}
|
|
1441
|
+
async function cancelTask(deps, taskId, reason) {
|
|
1442
|
+
const task = await deps.getTask(taskId);
|
|
1443
|
+
if (!task) throw new NotFoundError("task", taskId);
|
|
1444
|
+
if (task.status !== "active" && task.status !== "paused") {
|
|
1445
|
+
throw new BadRequestError(
|
|
1446
|
+
`Cannot cancel: task status is '${task.status}' (terminal). Only active/paused tasks can be cancelled.`,
|
|
1447
|
+
void 0,
|
|
1448
|
+
{ bizCode: "TASK_TERMINATED", message: `\u4EFB\u52A1\u5DF2\u5B8C\u7ED3\uFF08completed/cancelled\uFF09\uFF0C\u7981\u6B62\u53D6\u6D88\uFF08\u5F53\u524D status: ${task.status}\uFF09` }
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const patch = {
|
|
1452
|
+
status: "cancelled",
|
|
1453
|
+
pausedAt: null,
|
|
1454
|
+
history: [...task.history, mkHistory(task.currentNode, "cancelled", reason ? { reason } : {})]
|
|
1455
|
+
};
|
|
1456
|
+
return deps.updateTask(taskId, patch, {
|
|
1457
|
+
expectedStatus: task.status,
|
|
1458
|
+
expectedCurrentNode: task.currentNode
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
951
1461
|
function findNextEdge(edges, nodes, fromNodeId, track) {
|
|
952
1462
|
const outEdges = edges.filter((e) => e.from === fromNodeId);
|
|
953
1463
|
const matchSet = trackMatchSet(track);
|
|
@@ -1020,6 +1530,7 @@ function toNodeInfo(node, task) {
|
|
|
1020
1530
|
track: node.track,
|
|
1021
1531
|
prompt: renderPrompt(node.prompt, task),
|
|
1022
1532
|
skills: node.skills,
|
|
1533
|
+
agents: node.agents ?? [],
|
|
1023
1534
|
upcomingPause: nextEdge?.pausePoint?.description ?? null
|
|
1024
1535
|
};
|
|
1025
1536
|
}
|
|
@@ -1172,6 +1683,7 @@ function createTaskRepo(db) {
|
|
|
1172
1683
|
if (filter?.status) match.status = filter.status;
|
|
1173
1684
|
if (filter?.track) match.track = filter.track;
|
|
1174
1685
|
if (filter?.projectId) match.projectId = filter.projectId;
|
|
1686
|
+
if (filter?.q) match.title = { $regex: escapeRegExpLiteral(filter.q), $options: "i" };
|
|
1175
1687
|
const page = filter?.page ?? 1;
|
|
1176
1688
|
const limit = filter?.limit ?? 20;
|
|
1177
1689
|
const skip = (page - 1) * limit;
|
|
@@ -1402,7 +1914,8 @@ function deepCopyNode(node) {
|
|
|
1402
1914
|
phase: node.phase,
|
|
1403
1915
|
track: node.track,
|
|
1404
1916
|
prompt: node.prompt,
|
|
1405
|
-
skills: [...node.skills]
|
|
1917
|
+
skills: [...node.skills],
|
|
1918
|
+
agents: [...node.agents ?? []]
|
|
1406
1919
|
};
|
|
1407
1920
|
}
|
|
1408
1921
|
function deepCopyEdge(edge) {
|
|
@@ -1529,14 +2042,32 @@ async function assertModelAliasExists(repo, code) {
|
|
|
1529
2042
|
|
|
1530
2043
|
// src/store/repos/enum-registry.repo.ts
|
|
1531
2044
|
var REFERENCE_CHECKS = {
|
|
1532
|
-
dag_phase:
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
2045
|
+
dag_phase: [
|
|
2046
|
+
{ collection: "dag_templates", query: (v) => ({ "nodes.phase": v }) },
|
|
2047
|
+
{ collection: "node_presets", query: (v) => ({ phase: v }) }
|
|
2048
|
+
],
|
|
2049
|
+
dag_track: [
|
|
2050
|
+
{ collection: "dag_templates", query: (v) => ({ "nodes.track": v }) },
|
|
2051
|
+
{ collection: "node_presets", query: (v) => ({ track: v }) }
|
|
2052
|
+
],
|
|
2053
|
+
pause_type: [{ collection: "dag_templates", query: (v) => ({ "edges.pausePoint.type": v }) }],
|
|
2054
|
+
task_status: [{ collection: "tasks", query: (v) => ({ status: v }) }],
|
|
2055
|
+
node_status: [{ collection: "tasks", query: (v) => ({ "dagInstance.nodeStates.status": v }) }],
|
|
2056
|
+
skill_category: [{ collection: "skills", query: (v) => ({ category: v }) }],
|
|
2057
|
+
scope: void 0,
|
|
2058
|
+
// T202608260002:agent 功能类型被 agents.function 引用(reviewer/executor 为 builtin 禁删;
|
|
2059
|
+
// 检测映射为未来非 builtin 扩值留防线)
|
|
2060
|
+
agent_function: [{ collection: "agents", query: (v) => ({ "function": v }) }]
|
|
1539
2061
|
};
|
|
2062
|
+
async function countReferences(db, category, value) {
|
|
2063
|
+
const referrers = REFERENCE_CHECKS[category];
|
|
2064
|
+
if (!referrers) return 0;
|
|
2065
|
+
let total = 0;
|
|
2066
|
+
for (const referrer of referrers) {
|
|
2067
|
+
total += await db.collection(referrer.collection).countDocuments(referrer.query(value));
|
|
2068
|
+
}
|
|
2069
|
+
return total;
|
|
2070
|
+
}
|
|
1540
2071
|
function createEnumRegistryRepo(db) {
|
|
1541
2072
|
const base = createRepo(db.collection("enum_registry"), { nameField: "category" });
|
|
1542
2073
|
return {
|
|
@@ -1585,15 +2116,12 @@ function createEnumRegistryRepo(db) {
|
|
|
1585
2116
|
});
|
|
1586
2117
|
}
|
|
1587
2118
|
if (prev?.active === true && incoming.active === false) {
|
|
1588
|
-
const
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
2119
|
+
const count = await countReferences(db, category, incoming.value);
|
|
2120
|
+
if (count > 0) {
|
|
2121
|
+
throw new ConflictError("enum-registry", `${category}/${incoming.value}`, {
|
|
2122
|
+
bizCode: "ENUM_ENTRY_IN_USE",
|
|
2123
|
+
message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
|
|
2124
|
+
});
|
|
1597
2125
|
}
|
|
1598
2126
|
}
|
|
1599
2127
|
}
|
|
@@ -1620,15 +2148,12 @@ function createEnumRegistryRepo(db) {
|
|
|
1620
2148
|
message: BIZ_CODE_MESSAGES.ENUM_ENTRY_BUILTIN
|
|
1621
2149
|
});
|
|
1622
2150
|
}
|
|
1623
|
-
const
|
|
1624
|
-
if (
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
2151
|
+
const count = await countReferences(db, category, value);
|
|
2152
|
+
if (count > 0) {
|
|
2153
|
+
throw new ConflictError("enum-registry", `${category}/${value}`, {
|
|
2154
|
+
bizCode: "ENUM_ENTRY_IN_USE",
|
|
2155
|
+
message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
|
|
2156
|
+
});
|
|
1632
2157
|
}
|
|
1633
2158
|
const remaining = existing.entries.filter((e) => e.value !== value);
|
|
1634
2159
|
return await base.update(existing.id, { entries: remaining }) !== null;
|
|
@@ -1636,22 +2161,316 @@ function createEnumRegistryRepo(db) {
|
|
|
1636
2161
|
};
|
|
1637
2162
|
}
|
|
1638
2163
|
|
|
1639
|
-
// src/
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
2164
|
+
// src/store/repos/node-preset.repo.ts
|
|
2165
|
+
function createNodePresetRepo(db) {
|
|
2166
|
+
const base = createRepo(db.collection("node_presets"), { nameField: "code" });
|
|
2167
|
+
const assertCodeAvailable = async (code) => {
|
|
2168
|
+
const clash = await base._collection.findOne({ code }, { projection: { _id: 1 } });
|
|
2169
|
+
if (clash) {
|
|
2170
|
+
throw new ConflictError("node-preset", code, {
|
|
2171
|
+
bizCode: "NODE_PRESET_CODE_EXISTS",
|
|
2172
|
+
message: BIZ_CODE_MESSAGES.NODE_PRESET_CODE_EXISTS
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
};
|
|
2176
|
+
return {
|
|
2177
|
+
...base,
|
|
2178
|
+
async createNodePreset(data) {
|
|
2179
|
+
await assertCodeAvailable(data.code);
|
|
2180
|
+
return base.create(data);
|
|
2181
|
+
},
|
|
2182
|
+
async getByCode(code, opts) {
|
|
2183
|
+
const doc = await base._collection.findOne({
|
|
2184
|
+
code,
|
|
2185
|
+
...opts?.includeDisabled ? {} : enabledNeFalse()
|
|
2186
|
+
});
|
|
2187
|
+
return doc ? toEntity(doc) : null;
|
|
2188
|
+
},
|
|
2189
|
+
async updateByCode(code, patch) {
|
|
2190
|
+
const existing = await this.getByCode(code, { includeDisabled: true });
|
|
2191
|
+
if (!existing || !existing.id) return null;
|
|
2192
|
+
return base.update(existing.id, patch);
|
|
2193
|
+
},
|
|
2194
|
+
async deleteByCode(code) {
|
|
2195
|
+
const existing = await this.getByCode(code, { includeDisabled: true });
|
|
2196
|
+
if (!existing || !existing.id) return false;
|
|
2197
|
+
return base.delete(existing.id);
|
|
2198
|
+
},
|
|
2199
|
+
async listNodePresets(filter) {
|
|
2200
|
+
const query = {
|
|
2201
|
+
...filter?.includeDisabled ? {} : enabledNeFalse(),
|
|
2202
|
+
...filter?.scope ? filter.scope === "project" ? { scope: "project", ...filter.projectId ? { projectId: filter.projectId } : {} } : { scope: "global" } : filter?.projectId ? { $or: [{ scope: "global" }, { scope: "project", projectId: filter.projectId }] } : {},
|
|
2203
|
+
...filter?.q ? { $or: [
|
|
2204
|
+
{ code: { $regex: escapeRegExpLiteral(filter.q), $options: "i" } },
|
|
2205
|
+
{ label: { $regex: escapeRegExpLiteral(filter.q), $options: "i" } }
|
|
2206
|
+
] } : {}
|
|
2207
|
+
};
|
|
2208
|
+
return base.list(query);
|
|
2209
|
+
},
|
|
2210
|
+
async listByCodes(codes) {
|
|
2211
|
+
if (codes.length === 0) return /* @__PURE__ */ new Map();
|
|
2212
|
+
const presets = await base.list({ code: { $in: codes } });
|
|
2213
|
+
return new Map(presets.map((p) => [p.code, p]));
|
|
2214
|
+
},
|
|
2215
|
+
async isCodeOccupied(code) {
|
|
2216
|
+
return await base._collection.countDocuments({ code }, { limit: 1 }) > 0;
|
|
2217
|
+
},
|
|
2218
|
+
async copyNodePreset(sourceCode, target, input) {
|
|
2219
|
+
const source = await this.getByCode(sourceCode, { includeDisabled: true });
|
|
2220
|
+
if (!source) {
|
|
2221
|
+
throw new ConflictError("node-preset", `copy source not found: ${sourceCode}`);
|
|
2222
|
+
}
|
|
2223
|
+
await assertCodeAvailable(input.newCode);
|
|
2224
|
+
const { id: _sourceId, createdAt: _ca, updatedAt: _ua, enabled: _sourceEnabled, source: _sourceOrigin, projectId: _sourceProjectId, ...rest } = source;
|
|
2225
|
+
const copy = {
|
|
2226
|
+
...JSON.parse(JSON.stringify(rest)),
|
|
2227
|
+
agents: [...source.agents ?? []],
|
|
2228
|
+
code: input.newCode,
|
|
2229
|
+
scope: target.scope,
|
|
2230
|
+
version: "1.0.0",
|
|
2231
|
+
...target.scope === "project" && target.projectId ? { projectId: target.projectId } : {}
|
|
2232
|
+
};
|
|
2233
|
+
return base.create(copy);
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/store/repos/node-library.repo.ts
|
|
2239
|
+
function createNodeLibraryRepo(db) {
|
|
2240
|
+
const base = createRepo(db.collection("node_libraries"), { nameField: "name" });
|
|
2241
|
+
return {
|
|
2242
|
+
...base,
|
|
2243
|
+
async upsertNodeLibrary(data) {
|
|
2244
|
+
const now = /* @__PURE__ */ new Date();
|
|
2245
|
+
const existing = await base.getByName(data.name);
|
|
2246
|
+
if (existing && existing.id) {
|
|
2247
|
+
const updated = await base.update(existing.id, {
|
|
2248
|
+
version: data.version,
|
|
2249
|
+
scope: data.scope,
|
|
2250
|
+
...data.scope === "project" && data.projectId ? { projectId: data.projectId } : {},
|
|
2251
|
+
nodes: data.nodes,
|
|
2252
|
+
compositions: data.compositions,
|
|
2253
|
+
installedAt: now
|
|
2254
|
+
});
|
|
2255
|
+
if (!updated) throw new Error(`node_library upsert \u672A\u547D\u4E2D\u5DF2\u5B9A\u4F4D\u767B\u8BB0\uFF1A${data.name}\uFF08\u542F\u52A8\u671F\u4E0D\u5E94\u53D1\u751F\uFF09`);
|
|
2256
|
+
return updated;
|
|
2257
|
+
}
|
|
2258
|
+
const created = await base.create({
|
|
2259
|
+
name: data.name,
|
|
2260
|
+
version: data.version,
|
|
2261
|
+
scope: data.scope,
|
|
2262
|
+
...data.scope === "project" && data.projectId ? { projectId: data.projectId } : {},
|
|
2263
|
+
nodes: data.nodes,
|
|
2264
|
+
compositions: data.compositions,
|
|
2265
|
+
installedAt: now
|
|
2266
|
+
});
|
|
2267
|
+
return created;
|
|
2268
|
+
},
|
|
2269
|
+
async listNodeLibraries(filter) {
|
|
2270
|
+
const query = filter?.scope ? filter.scope === "project" ? { scope: "project", ...filter.projectId ? { projectId: filter.projectId } : {} } : { scope: "global" } : filter?.projectId ? { $or: [{ scope: "global" }, { scope: "project", projectId: filter.projectId }] } : {};
|
|
2271
|
+
const docs = await base._collection.find(query).toArray();
|
|
2272
|
+
return docs.map((doc) => toEntity(doc));
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// src/store/repos/auth.repo.ts
|
|
2278
|
+
import { ObjectId as ObjectId3 } from "mongodb";
|
|
2279
|
+
import { z as z5 } from "zod";
|
|
2280
|
+
var AUTH_COLLECTIONS = {
|
|
2281
|
+
accounts: "auth_accounts",
|
|
2282
|
+
tokens: "auth_tokens",
|
|
2283
|
+
sessions: "auth_sessions"
|
|
2284
|
+
};
|
|
2285
|
+
var SINGLETON_ACCOUNT_ID = new ObjectId3("000000000000000000000001");
|
|
2286
|
+
function createAuthAccountRepo(db) {
|
|
2287
|
+
const base = createRepo(db.collection(AUTH_COLLECTIONS.accounts));
|
|
2288
|
+
return {
|
|
2289
|
+
...base,
|
|
2290
|
+
async getAccount() {
|
|
2291
|
+
const doc = await base._collection.findOne({});
|
|
2292
|
+
return doc ? toEntity(doc) : null;
|
|
2293
|
+
},
|
|
2294
|
+
/** 创建唯一管理员账号:已有账号 → 409(并发初始化仅首个成功,PRD §3.2)。
|
|
2295
|
+
* 单例机制 = 固定 _id 插入(E11000 兜底任意并发形态——异名并发同样仅首个成功) */
|
|
2296
|
+
async createAccount(username, passwordHash) {
|
|
2297
|
+
const existing = await base._collection.findOne({}, { projection: { _id: 1 } });
|
|
2298
|
+
if (existing) {
|
|
2299
|
+
throw new ConflictError("auth-account", "singleton", { message: "admin account already exists" });
|
|
2300
|
+
}
|
|
2301
|
+
const now = /* @__PURE__ */ new Date();
|
|
2302
|
+
const result = await base._collection.insertOne({
|
|
2303
|
+
_id: SINGLETON_ACCOUNT_ID,
|
|
2304
|
+
username,
|
|
2305
|
+
passwordHash,
|
|
2306
|
+
createdAt: now,
|
|
2307
|
+
updatedAt: now
|
|
2308
|
+
});
|
|
2309
|
+
return { username, passwordHash, createdAt: now, updatedAt: now, id: result.insertedId.toString() };
|
|
2310
|
+
},
|
|
2311
|
+
async updatePassword(accountId, newPasswordHash) {
|
|
2312
|
+
return base.update(accountId, { passwordHash: newPasswordHash });
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
function createAuthTokenRepo(db) {
|
|
2317
|
+
const base = createRepo(db.collection(AUTH_COLLECTIONS.tokens));
|
|
2318
|
+
return {
|
|
2319
|
+
...base,
|
|
2320
|
+
/** sha256(token 全串含前缀) 等值查询(唯一索引命中) */
|
|
2321
|
+
async findByHash(tokenHash) {
|
|
2322
|
+
const doc = await base._collection.findOne({ tokenHash });
|
|
2323
|
+
return doc ? toEntity(doc) : null;
|
|
2324
|
+
},
|
|
2325
|
+
async getTokenStatus(type, projectId) {
|
|
2326
|
+
const query = { type };
|
|
2327
|
+
if (type === "project") query.projectId = projectId;
|
|
2328
|
+
const doc = await base._collection.findOne(query, { projection: { createdAt: 1 } });
|
|
2329
|
+
return doc ? { hasToken: true, createdAt: doc.createdAt ?? null } : { hasToken: false, createdAt: null };
|
|
2330
|
+
},
|
|
2331
|
+
/** 重置语义:旧 Token 删除 + 新 Token 落库(重置后旧值立即失效,PRD §3.4)。
|
|
2332
|
+
* 事务包删+插(rs0 支持——失败窗口不留零 Token 状态,失败不变性);admin 单例 /
|
|
2333
|
+
* project 每项目一枚由 partial unique 索引兜底并发交错 */
|
|
2334
|
+
async upsertToken(type, tokenHash, projectId) {
|
|
2335
|
+
const query = { type };
|
|
2336
|
+
if (type === "project") {
|
|
2337
|
+
if (!projectId) throw new ConflictError("auth-token", "missing-projectId", { message: "project token requires projectId" });
|
|
2338
|
+
query.projectId = projectId;
|
|
2339
|
+
}
|
|
2340
|
+
const db2 = base._collection.db;
|
|
2341
|
+
const session = db2.client.startSession();
|
|
2342
|
+
try {
|
|
2343
|
+
const holder = { token: null };
|
|
2344
|
+
await session.withTransaction(async () => {
|
|
2345
|
+
await base._collection.deleteMany(query, { session });
|
|
2346
|
+
const now = /* @__PURE__ */ new Date();
|
|
2347
|
+
const doc = { tokenHash, type, createdAt: now, updatedAt: now };
|
|
2348
|
+
if (type === "project" && projectId !== void 0) doc.projectId = projectId;
|
|
2349
|
+
const result = await base._collection.insertOne(doc, { session });
|
|
2350
|
+
holder.token = { ...doc, id: result.insertedId.toString() };
|
|
2351
|
+
});
|
|
2352
|
+
if (holder.token === null) {
|
|
2353
|
+
throw new ConflictError("auth-token", "transaction-empty", { message: "Token upsert \u672A\u4EA7\u751F\u7ED3\u679C" });
|
|
2354
|
+
}
|
|
2355
|
+
return holder.token;
|
|
2356
|
+
} finally {
|
|
2357
|
+
await session.endSession();
|
|
2358
|
+
}
|
|
2359
|
+
},
|
|
2360
|
+
async listProjectTokenStatuses(projectIds) {
|
|
2361
|
+
const docs = await base._collection.find({ type: "project", projectId: { $in: projectIds } }, { projection: { projectId: 1, createdAt: 1 } }).toArray();
|
|
2362
|
+
const map = /* @__PURE__ */ new Map();
|
|
2363
|
+
for (const id of projectIds) map.set(id, { hasToken: false, createdAt: null });
|
|
2364
|
+
for (const doc of docs) {
|
|
2365
|
+
if (typeof doc.projectId === "string") {
|
|
2366
|
+
map.set(doc.projectId, { hasToken: true, createdAt: doc.createdAt ?? null });
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return map;
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
var AuthSessionSchema = z5.object({
|
|
2374
|
+
id: z5.string().optional(),
|
|
2375
|
+
sessionId: z5.string().min(1),
|
|
2376
|
+
/** 管理员账号 id(会话 = 管理员身份) */
|
|
2377
|
+
accountId: z5.string().min(1),
|
|
2378
|
+
expiresAt: z5.date(),
|
|
2379
|
+
createdAt: z5.date().optional(),
|
|
2380
|
+
updatedAt: z5.date().optional()
|
|
2381
|
+
});
|
|
2382
|
+
function createAuthSessionRepo(db) {
|
|
2383
|
+
const base = createRepo(db.collection(AUTH_COLLECTIONS.sessions));
|
|
2384
|
+
return {
|
|
2385
|
+
...base,
|
|
2386
|
+
async createSession(sessionId, accountId, expiresAt) {
|
|
2387
|
+
return base.create({ sessionId, accountId, expiresAt });
|
|
2388
|
+
},
|
|
2389
|
+
/** 有效期判定内联:过期即删返回 null(不等 TTL 清理) */
|
|
2390
|
+
async findValidSession(sessionId) {
|
|
2391
|
+
const doc = await base._collection.findOne({ sessionId });
|
|
2392
|
+
if (!doc) return null;
|
|
2393
|
+
if (!doc.expiresAt || doc.expiresAt.getTime() <= Date.now()) {
|
|
2394
|
+
await base.delete(doc._id.toString());
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
return toEntity(doc);
|
|
2398
|
+
},
|
|
2399
|
+
async deleteBySessionId(sessionId) {
|
|
2400
|
+
const result = await base._collection.deleteOne({ sessionId });
|
|
2401
|
+
return result.deletedCount > 0;
|
|
2402
|
+
},
|
|
2403
|
+
/** 启动时清扫过期会话(幂等,配合 TTL 索引) */
|
|
2404
|
+
async deleteExpiredSessions() {
|
|
2405
|
+
const result = await base._collection.deleteMany({ expiresAt: { $lte: /* @__PURE__ */ new Date() } });
|
|
2406
|
+
return result.deletedCount;
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// src/store/auth-crypto.ts
|
|
2412
|
+
import { createHash, randomBytes, scrypt as scryptCb, timingSafeEqual } from "crypto";
|
|
2413
|
+
import { promisify } from "util";
|
|
2414
|
+
var scrypt = promisify(scryptCb);
|
|
2415
|
+
var SCRYPT_N = 16384;
|
|
2416
|
+
var SCRYPT_R = 8;
|
|
2417
|
+
var SCRYPT_P = 1;
|
|
2418
|
+
var SCRYPT_KEYLEN = 64;
|
|
2419
|
+
var SALT_LEN = 16;
|
|
2420
|
+
async function hashPassword(password) {
|
|
2421
|
+
const salt = randomBytes(SALT_LEN);
|
|
2422
|
+
const derived = await scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P });
|
|
2423
|
+
return `scrypt$${SCRYPT_N}$${SCRYPT_R}$${SCRYPT_P}$${salt.toString("base64")}$${derived.toString("base64")}`;
|
|
2424
|
+
}
|
|
2425
|
+
async function verifyPassword(password, stored) {
|
|
2426
|
+
const parts = stored.split("$");
|
|
2427
|
+
if (parts.length !== 6 || parts[0] !== "scrypt") return false;
|
|
2428
|
+
const [, nStr, rStr, pStr, saltB64, hashB64] = parts;
|
|
2429
|
+
const N = Number(nStr);
|
|
2430
|
+
const r = Number(rStr);
|
|
2431
|
+
const p = Number(pStr);
|
|
2432
|
+
if (!Number.isInteger(N) || !Number.isInteger(r) || !Number.isInteger(p)) return false;
|
|
2433
|
+
try {
|
|
2434
|
+
const salt = Buffer.from(saltB64, "base64");
|
|
2435
|
+
const expected = Buffer.from(hashB64, "base64");
|
|
2436
|
+
const derived = await scrypt(password, salt, expected.length, { N, r, p });
|
|
2437
|
+
return derived.length === expected.length && timingSafeEqual(derived, expected);
|
|
2438
|
+
} catch {
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
function generateToken(type) {
|
|
2443
|
+
const prefix = type === "admin" ? "admin-" : "project-";
|
|
2444
|
+
return `${prefix}${randomBytes(32).toString("base64url")}`;
|
|
2445
|
+
}
|
|
2446
|
+
function tokenTypeFromValue(token) {
|
|
2447
|
+
if (token.startsWith("admin-")) return "admin";
|
|
2448
|
+
if (token.startsWith("project-")) return "project";
|
|
2449
|
+
return null;
|
|
2450
|
+
}
|
|
2451
|
+
function hashToken(token) {
|
|
2452
|
+
return createHash("sha256").update(token).digest("hex");
|
|
2453
|
+
}
|
|
2454
|
+
function generateSessionId() {
|
|
2455
|
+
return randomBytes(32).toString("hex");
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// src/schemas/agent.ts
|
|
2459
|
+
import { z as z7 } from "zod";
|
|
2460
|
+
|
|
2461
|
+
// src/schemas/model-alias.ts
|
|
2462
|
+
import { z as z6 } from "zod";
|
|
2463
|
+
var SIMING_CODE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2464
|
+
var ModelAliasShapeSchema = z6.object({
|
|
2465
|
+
id: z6.string().optional(),
|
|
2466
|
+
/** siming code:创建后不可变(PUT 显式拒绝 MODEL_CODE_IMMUTABLE,非静默剥离) */
|
|
2467
|
+
code: z6.string().regex(SIMING_CODE_REGEX, "code \u5FC5\u987B\u4E3A kebab-case\uFF08\u5982 primary\u3001fast-model\uFF09"),
|
|
2468
|
+
/** 展示名(如「主力模型」) */
|
|
2469
|
+
name: z6.string().min(1, "\u5C55\u793A\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
|
|
2470
|
+
/** 真实模型标识(如 zhipuai/glm-5.3;改此处 = 引用方全局替换生效) */
|
|
2471
|
+
realModel: z6.string().min(1, "\u771F\u5B9E\u6A21\u578B\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A"),
|
|
2472
|
+
createdAt: z6.date().optional(),
|
|
2473
|
+
updatedAt: z6.date().optional()
|
|
1655
2474
|
});
|
|
1656
2475
|
var ModelAliasSchema = ModelAliasShapeSchema;
|
|
1657
2476
|
var ModelAliasCreateSchema = ModelAliasShapeSchema.omit({
|
|
@@ -1659,17 +2478,18 @@ var ModelAliasCreateSchema = ModelAliasShapeSchema.omit({
|
|
|
1659
2478
|
createdAt: true,
|
|
1660
2479
|
updatedAt: true
|
|
1661
2480
|
});
|
|
1662
|
-
var ModelAliasUpdateSchema =
|
|
1663
|
-
code:
|
|
1664
|
-
name:
|
|
1665
|
-
realModel:
|
|
2481
|
+
var ModelAliasUpdateSchema = z6.object({
|
|
2482
|
+
code: z6.string().regex(SIMING_CODE_REGEX).optional(),
|
|
2483
|
+
name: z6.string().min(1).optional(),
|
|
2484
|
+
realModel: z6.string().min(1).optional()
|
|
1666
2485
|
});
|
|
1667
2486
|
var ModelAliasWithRefCountSchema = ModelAliasShapeSchema.extend({
|
|
1668
|
-
refCount:
|
|
2487
|
+
refCount: z6.number().int().min(0)
|
|
1669
2488
|
});
|
|
1670
2489
|
|
|
1671
2490
|
// src/schemas/agent.ts
|
|
1672
|
-
var AgentScopeSchema =
|
|
2491
|
+
var AgentScopeSchema = z7.enum(["global", "project"]);
|
|
2492
|
+
var AgentFunctionSchema = z7.enum(["reviewer", "executor"]);
|
|
1673
2493
|
var AgentScopeRefine = (val, ctx) => {
|
|
1674
2494
|
if (val.scope === "project" && !val.projectId) {
|
|
1675
2495
|
ctx.addIssue({
|
|
@@ -1679,48 +2499,72 @@ var AgentScopeRefine = (val, ctx) => {
|
|
|
1679
2499
|
});
|
|
1680
2500
|
}
|
|
1681
2501
|
};
|
|
1682
|
-
var AgentShapeSchema =
|
|
1683
|
-
id:
|
|
1684
|
-
name:
|
|
1685
|
-
description:
|
|
1686
|
-
systemPrompt:
|
|
1687
|
-
boundSkills:
|
|
1688
|
-
model:
|
|
2502
|
+
var AgentShapeSchema = z7.object({
|
|
2503
|
+
id: z7.string().optional(),
|
|
2504
|
+
name: z7.string(),
|
|
2505
|
+
description: z7.string(),
|
|
2506
|
+
systemPrompt: z7.string(),
|
|
2507
|
+
boundSkills: z7.array(z7.string()).default([]),
|
|
2508
|
+
model: z7.string(),
|
|
1689
2509
|
/** 资产版本(N015 F13):install/export 版本对比依据;default 仅读侧兜底(createRepo 不做读侧解析,存量回填走 M6 迁移) */
|
|
1690
|
-
version:
|
|
1691
|
-
tools:
|
|
1692
|
-
permissions:
|
|
2510
|
+
version: z7.string().default("1.0.0"),
|
|
2511
|
+
tools: z7.array(z7.string()).default([]),
|
|
2512
|
+
permissions: z7.array(z7.string()).default([]),
|
|
2513
|
+
/** 引用文档集合(可选——存量单文件资产无此字段,行为与升级前一致) */
|
|
2514
|
+
references: createReferencesSchema(AGENT_MAIN_DOC).optional(),
|
|
1693
2515
|
/** 作用域:global=跨项目复用(默认,仅作存量迁移读侧兜底)/ project=项目专用(N012 D3) */
|
|
1694
2516
|
scope: AgentScopeSchema.default("global"),
|
|
2517
|
+
/** 功能类型(T202608260002):default 仅创建侧归一——toEntity 直转不经 Zod parse,读侧归一在 repo 层 + 回填走 M12 */
|
|
2518
|
+
"function": AgentFunctionSchema.default("executor"),
|
|
1695
2519
|
/** 仅 scope=project 时必填且为合法 ObjectId hex(superRefine 保证) */
|
|
1696
|
-
projectId:
|
|
1697
|
-
|
|
1698
|
-
|
|
2520
|
+
projectId: z7.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
|
|
2521
|
+
/**
|
|
2522
|
+
* T202608290001 资产启停(可选——存量无此字段 = 启用,零迁移)。
|
|
2523
|
+
* 全局判断规则:enabled === false 即停用,true/缺失均启用。
|
|
2524
|
+
* 唯一写入口是专用启停端点(POST /:name/enabled)——Create/Update/Copy 均不含此字段。
|
|
2525
|
+
*/
|
|
2526
|
+
enabled: z7.boolean().optional(),
|
|
2527
|
+
createdAt: z7.date().optional(),
|
|
2528
|
+
updatedAt: z7.date().optional()
|
|
1699
2529
|
});
|
|
1700
|
-
var
|
|
2530
|
+
var agentAggregateLimitRefine = (val, ctx) => {
|
|
2531
|
+
const totalChars = (val.systemPrompt?.length ?? 0) + sumReferenceContentChars(val.references);
|
|
2532
|
+
if (totalChars > REFERENCE_LIMITS.maxTotalChars) {
|
|
2533
|
+
ctx.addIssue({
|
|
2534
|
+
code: "custom",
|
|
2535
|
+
path: ["references"],
|
|
2536
|
+
message: `\u4E3B\u6587\u6863\u4E0E\u5F15\u7528\u6587\u6863\u5185\u5BB9\u603B\u91CF ${totalChars} \u5B57\u7B26\u8D85\u51FA\u805A\u5408\u4E0A\u9650 ${REFERENCE_LIMITS.maxTotalChars}`
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
var AgentSchema = AgentShapeSchema.superRefine(AgentScopeRefine).superRefine(agentAggregateLimitRefine);
|
|
1701
2541
|
var AgentCreateSchema = AgentShapeSchema.omit({
|
|
1702
2542
|
id: true,
|
|
1703
2543
|
createdAt: true,
|
|
1704
|
-
updatedAt: true
|
|
1705
|
-
|
|
2544
|
+
updatedAt: true,
|
|
2545
|
+
enabled: true
|
|
2546
|
+
}).extend({ scope: AgentScopeSchema }).extend({ model: z7.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09") }).extend({ "function": AgentFunctionSchema.default("executor") }).superRefine(AgentScopeRefine).superRefine(agentAggregateLimitRefine);
|
|
1706
2547
|
var AgentUpdateSchema = AgentShapeSchema.omit({
|
|
1707
2548
|
id: true,
|
|
1708
2549
|
createdAt: true,
|
|
1709
|
-
updatedAt: true
|
|
2550
|
+
updatedAt: true,
|
|
2551
|
+
enabled: true
|
|
1710
2552
|
}).extend({
|
|
1711
|
-
boundSkills:
|
|
2553
|
+
boundSkills: z7.array(z7.string()).optional(),
|
|
1712
2554
|
// N015:version 放行(export apply 版本递增随 PUT 携带);optional 剥离实体层 default 避免 partial 空 body 注入
|
|
1713
|
-
version:
|
|
1714
|
-
tools:
|
|
1715
|
-
permissions:
|
|
2555
|
+
version: z7.string().optional(),
|
|
2556
|
+
tools: z7.array(z7.string()).optional(),
|
|
2557
|
+
permissions: z7.array(z7.string()).optional(),
|
|
1716
2558
|
scope: AgentScopeSchema.optional(),
|
|
1717
2559
|
// N013:model 收紧为 siming code 格式(存在性校验在 handler 层;undefined = 不更新该字段)
|
|
1718
|
-
model:
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2560
|
+
model: z7.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09").optional(),
|
|
2561
|
+
// T202608260002:optional 剥离实体层 default——否则 PUT 空 body 会注入 executor 覆盖 reviewer
|
|
2562
|
+
"function": AgentFunctionSchema.optional()
|
|
2563
|
+
}).partial().superRefine(AgentScopeRefine).superRefine(agentAggregateLimitRefine);
|
|
2564
|
+
var AgentCopySchema = z7.object({
|
|
2565
|
+
newName: z7.string(),
|
|
1722
2566
|
newScope: AgentScopeSchema,
|
|
1723
|
-
targetProjectId:
|
|
2567
|
+
targetProjectId: z7.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
|
|
1724
2568
|
}).superRefine((val, ctx) => {
|
|
1725
2569
|
if (val.newScope === "project" && !val.targetProjectId) {
|
|
1726
2570
|
ctx.addIssue({
|
|
@@ -1731,227 +2575,948 @@ var AgentCopySchema = z5.object({
|
|
|
1731
2575
|
}
|
|
1732
2576
|
});
|
|
1733
2577
|
|
|
2578
|
+
// src/schemas/export-bundle.ts
|
|
2579
|
+
import { z as z8 } from "zod";
|
|
2580
|
+
var EXPORT_FORMAT_VERSION = "1.0";
|
|
2581
|
+
var TemplatePayloadSchema = DagTemplateSchema.omit({
|
|
2582
|
+
id: true,
|
|
2583
|
+
projectId: true,
|
|
2584
|
+
enabled: true,
|
|
2585
|
+
createdAt: true,
|
|
2586
|
+
updatedAt: true,
|
|
2587
|
+
code: true
|
|
2588
|
+
}).extend({
|
|
2589
|
+
version: z8.string(),
|
|
2590
|
+
isDefault: z8.boolean()
|
|
2591
|
+
});
|
|
2592
|
+
var SkillPayloadSchema = SkillShapeSchema.omit({
|
|
2593
|
+
id: true,
|
|
2594
|
+
projectId: true,
|
|
2595
|
+
enabled: true,
|
|
2596
|
+
createdAt: true,
|
|
2597
|
+
updatedAt: true
|
|
2598
|
+
}).extend({
|
|
2599
|
+
version: z8.string(),
|
|
2600
|
+
scope: SkillScopeSchema,
|
|
2601
|
+
references: createReferencesSchema(SKILL_MAIN_DOC)
|
|
2602
|
+
});
|
|
2603
|
+
var AgentPayloadSchema = AgentShapeSchema.omit({
|
|
2604
|
+
id: true,
|
|
2605
|
+
projectId: true,
|
|
2606
|
+
enabled: true,
|
|
2607
|
+
createdAt: true,
|
|
2608
|
+
updatedAt: true
|
|
2609
|
+
}).extend({
|
|
2610
|
+
version: z8.string(),
|
|
2611
|
+
scope: AgentScopeSchema,
|
|
2612
|
+
references: createReferencesSchema(AGENT_MAIN_DOC),
|
|
2613
|
+
boundSkills: z8.array(z8.string()),
|
|
2614
|
+
tools: z8.array(z8.string()),
|
|
2615
|
+
permissions: z8.array(z8.string()),
|
|
2616
|
+
"function": AgentFunctionSchema
|
|
2617
|
+
});
|
|
2618
|
+
var ModelAliasPayloadSchema = ModelAliasSchema.omit({
|
|
2619
|
+
id: true,
|
|
2620
|
+
createdAt: true,
|
|
2621
|
+
updatedAt: true
|
|
2622
|
+
});
|
|
2623
|
+
var ExportBundleSchema = z8.object({
|
|
2624
|
+
exportFormatVersion: z8.literal(EXPORT_FORMAT_VERSION, {
|
|
2625
|
+
error: `\u5BFC\u51FA\u683C\u5F0F\u7248\u672C\u4E0D\u652F\u6301\uFF0C\u5F53\u524D\u652F\u6301 ${EXPORT_FORMAT_VERSION}`
|
|
2626
|
+
}),
|
|
2627
|
+
exportedAt: z8.string(),
|
|
2628
|
+
/** 源环境悬空提示(§2.2:悬空 skill/agent.model 不阻断导出)。导出响应恒携带;导入入参可缺省——导入侧提示由 plan 生成,不消费本字段 */
|
|
2629
|
+
warnings: z8.array(z8.string()).optional(),
|
|
2630
|
+
template: TemplatePayloadSchema,
|
|
2631
|
+
dependencies: z8.object({
|
|
2632
|
+
skills: z8.array(SkillPayloadSchema),
|
|
2633
|
+
agents: z8.array(AgentPayloadSchema),
|
|
2634
|
+
modelAliases: z8.array(ModelAliasPayloadSchema)
|
|
2635
|
+
})
|
|
2636
|
+
});
|
|
2637
|
+
var ImportPlanRequestSchema = z8.object({
|
|
2638
|
+
targetProjectId: z8.string().regex(OBJECT_ID_HEX, "targetProjectId must be a valid ObjectId hex"),
|
|
2639
|
+
bundle: ExportBundleSchema
|
|
2640
|
+
});
|
|
2641
|
+
var ImportDecisionSchema = z8.discriminatedUnion("kind", [
|
|
2642
|
+
z8.object({
|
|
2643
|
+
kind: z8.literal("skill"),
|
|
2644
|
+
name: z8.string(),
|
|
2645
|
+
scope: SkillScopeSchema,
|
|
2646
|
+
action: z8.enum(["overwrite", "skip"])
|
|
2647
|
+
}),
|
|
2648
|
+
z8.object({
|
|
2649
|
+
kind: z8.literal("agent"),
|
|
2650
|
+
name: z8.string(),
|
|
2651
|
+
scope: AgentScopeSchema,
|
|
2652
|
+
action: z8.enum(["overwrite", "skip"])
|
|
2653
|
+
}),
|
|
2654
|
+
z8.object({
|
|
2655
|
+
kind: z8.literal("modelAlias"),
|
|
2656
|
+
name: z8.string(),
|
|
2657
|
+
action: z8.enum(["overwrite", "skip"])
|
|
2658
|
+
})
|
|
2659
|
+
]);
|
|
2660
|
+
var ImportApplyRequestSchema = ImportPlanRequestSchema.extend({
|
|
2661
|
+
/** 未列出的冲突项缺省 skip(保守默认:未决策不覆盖) */
|
|
2662
|
+
decisions: z8.array(ImportDecisionSchema).default([])
|
|
2663
|
+
});
|
|
2664
|
+
var DependencyStatusSchema = z8.enum(["create", "identical", "conflict"]);
|
|
2665
|
+
var DependencyCheckSchema = z8.object({
|
|
2666
|
+
kind: z8.enum(["skill", "agent", "modelAlias"]),
|
|
2667
|
+
name: z8.string(),
|
|
2668
|
+
/** skill/agent 携带;modelAlias 无 */
|
|
2669
|
+
scope: z8.enum(["global", "project"]).optional(),
|
|
2670
|
+
status: DependencyStatusSchema,
|
|
2671
|
+
/** status ≠ create 时目标环境现值版本(modelAlias 无 version 概念不填) */
|
|
2672
|
+
currentVersion: z8.string().optional(),
|
|
2673
|
+
importVersion: z8.string().optional(),
|
|
2674
|
+
/** status = conflict 时差异字段名清单(如 "content, references") */
|
|
2675
|
+
changeSummary: z8.string().optional(),
|
|
2676
|
+
/** 影响范围(PRD F13):global 资产影响所有项目 / project 资产仅影响所属项目 */
|
|
2677
|
+
impact: z8.enum(["global", "project"])
|
|
2678
|
+
});
|
|
2679
|
+
var ImportPlanResponseSchema = z8.object({
|
|
2680
|
+
template: z8.object({
|
|
2681
|
+
name: z8.string(),
|
|
2682
|
+
willOverwrite: z8.boolean(),
|
|
2683
|
+
currentVersion: z8.string().optional(),
|
|
2684
|
+
importVersion: z8.string(),
|
|
2685
|
+
isDefaultRequested: z8.boolean(),
|
|
2686
|
+
/** isDefault 降级后的最终生效值(F17:项目已有默认 → false) */
|
|
2687
|
+
isDefaultEffect: z8.boolean(),
|
|
2688
|
+
downgradeReason: z8.string().optional()
|
|
2689
|
+
}),
|
|
2690
|
+
dependencies: z8.array(DependencyCheckSchema),
|
|
2691
|
+
/**
|
|
2692
|
+
* K10①:模板节点直接技能引用完整性逐项核对(node × skill 粒度)——
|
|
2693
|
+
* ok = 在 bundle 依赖 ∪ 目标环境可见集;missing = 双侧皆无(绑定悬空,non-blocking warning)
|
|
2694
|
+
*/
|
|
2695
|
+
directSkillChecks: z8.array(
|
|
2696
|
+
z8.object({
|
|
2697
|
+
node: z8.string(),
|
|
2698
|
+
skill: z8.string(),
|
|
2699
|
+
status: z8.enum(["ok", "missing"])
|
|
2700
|
+
})
|
|
2701
|
+
),
|
|
2702
|
+
/** 非阻断提示(源环境悬空绑定、agent.model 引用缺失、sourcePreset 来源缺失等) */
|
|
2703
|
+
warnings: z8.array(z8.string())
|
|
2704
|
+
});
|
|
2705
|
+
var DependencyActionSchema = z8.enum(["created", "updated", "skipped", "identical", "failed"]);
|
|
2706
|
+
var ImportApplyResponseSchema = z8.object({
|
|
2707
|
+
template: z8.object({
|
|
2708
|
+
id: z8.string(),
|
|
2709
|
+
name: z8.string(),
|
|
2710
|
+
action: z8.enum(["created", "updated"]),
|
|
2711
|
+
isDefaultEffect: z8.boolean()
|
|
2712
|
+
}),
|
|
2713
|
+
dependencies: z8.array(
|
|
2714
|
+
z8.object({
|
|
2715
|
+
kind: z8.enum(["skill", "agent", "modelAlias"]),
|
|
2716
|
+
name: z8.string(),
|
|
2717
|
+
scope: z8.enum(["global", "project"]).optional(),
|
|
2718
|
+
action: DependencyActionSchema,
|
|
2719
|
+
/** action = failed 时的错误说明 */
|
|
2720
|
+
message: z8.string().optional()
|
|
2721
|
+
})
|
|
2722
|
+
),
|
|
2723
|
+
/** K10②:apply 时刻目标环境缺失来源而被剥除 sourcePreset 的节点 id 清单(降级自建导入) */
|
|
2724
|
+
sourceStripped: z8.array(z8.string())
|
|
2725
|
+
});
|
|
2726
|
+
function decisionKey(kind, scope, name) {
|
|
2727
|
+
return `${kind}:${scope ?? ""}:${name}`;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
1734
2730
|
// src/schemas/advance.ts
|
|
1735
|
-
import { z as
|
|
1736
|
-
var AdvanceRequestSchema =
|
|
1737
|
-
note:
|
|
2731
|
+
import { z as z9 } from "zod";
|
|
2732
|
+
var AdvanceRequestSchema = z9.object({
|
|
2733
|
+
note: z9.string().optional(),
|
|
1738
2734
|
/** min(1).max(2000) 与 record summary 端点对齐(同一 $set 目标,两入口校验强度一致) */
|
|
1739
|
-
summary:
|
|
2735
|
+
summary: z9.string().min(1).max(2e3).optional()
|
|
1740
2736
|
});
|
|
1741
|
-
var ApproveRequestSchema =
|
|
1742
|
-
decision:
|
|
1743
|
-
comment:
|
|
2737
|
+
var ApproveRequestSchema = z9.object({
|
|
2738
|
+
decision: z9.enum(["approved", "rejected"]),
|
|
2739
|
+
comment: z9.string().optional()
|
|
1744
2740
|
});
|
|
1745
|
-
var TaskPublicSchema =
|
|
1746
|
-
taskId:
|
|
1747
|
-
title:
|
|
2741
|
+
var TaskPublicSchema = z9.object({
|
|
2742
|
+
taskId: z9.string(),
|
|
2743
|
+
title: z9.string(),
|
|
1748
2744
|
// T202608240003:任务类型标签(缺省 = 裸任务/旧任务;模板 PRD 调研变体判定消费)
|
|
1749
|
-
type:
|
|
1750
|
-
currentNode:
|
|
1751
|
-
currentPhase:
|
|
1752
|
-
status:
|
|
1753
|
-
pausedAt:
|
|
1754
|
-
track:
|
|
1755
|
-
});
|
|
1756
|
-
var NodeInfoSchema =
|
|
1757
|
-
nodeId:
|
|
1758
|
-
label:
|
|
1759
|
-
track:
|
|
1760
|
-
prompt:
|
|
1761
|
-
skills:
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2745
|
+
type: z9.enum(TASK_TYPES).optional(),
|
|
2746
|
+
currentNode: z9.string(),
|
|
2747
|
+
currentPhase: z9.string(),
|
|
2748
|
+
status: z9.string(),
|
|
2749
|
+
pausedAt: z9.string().nullable(),
|
|
2750
|
+
track: z9.string()
|
|
2751
|
+
});
|
|
2752
|
+
var NodeInfoSchema = z9.object({
|
|
2753
|
+
nodeId: z9.string(),
|
|
2754
|
+
label: z9.string(),
|
|
2755
|
+
track: z9.string(),
|
|
2756
|
+
prompt: z9.string(),
|
|
2757
|
+
skills: z9.array(z9.string()),
|
|
2758
|
+
agents: z9.array(z9.string()),
|
|
2759
|
+
upcomingPause: z9.string().nullable()
|
|
2760
|
+
});
|
|
2761
|
+
var AdvanceResponseSchema = z9.discriminatedUnion("status", [
|
|
2762
|
+
z9.object({
|
|
2763
|
+
status: z9.literal("advanced"),
|
|
1767
2764
|
task: TaskPublicSchema,
|
|
1768
2765
|
nextNode: NodeInfoSchema
|
|
1769
2766
|
}),
|
|
1770
|
-
|
|
1771
|
-
status:
|
|
2767
|
+
z9.object({
|
|
2768
|
+
status: z9.literal("paused"),
|
|
1772
2769
|
task: TaskPublicSchema,
|
|
1773
2770
|
pausePoint: EdgePausePointSchema,
|
|
1774
|
-
guidance:
|
|
2771
|
+
guidance: z9.string()
|
|
1775
2772
|
}),
|
|
1776
|
-
|
|
1777
|
-
status:
|
|
2773
|
+
z9.object({
|
|
2774
|
+
status: z9.literal("completed"),
|
|
1778
2775
|
task: TaskPublicSchema
|
|
1779
2776
|
})
|
|
1780
2777
|
]);
|
|
1781
|
-
var ApproveResponseSchema =
|
|
1782
|
-
|
|
1783
|
-
status:
|
|
2778
|
+
var ApproveResponseSchema = z9.discriminatedUnion("status", [
|
|
2779
|
+
z9.object({
|
|
2780
|
+
status: z9.literal("advanced"),
|
|
1784
2781
|
task: TaskPublicSchema,
|
|
1785
2782
|
nextNode: NodeInfoSchema
|
|
1786
2783
|
}),
|
|
1787
|
-
|
|
1788
|
-
status:
|
|
2784
|
+
z9.object({
|
|
2785
|
+
status: z9.literal("rejected"),
|
|
1789
2786
|
task: TaskPublicSchema,
|
|
1790
|
-
guidance:
|
|
2787
|
+
guidance: z9.string()
|
|
1791
2788
|
}),
|
|
1792
2789
|
// 「completed」分支当前引擎不可达(approve 从 paused 流转必有下一节点,技术方案 §2.2
|
|
1793
2790
|
// 已删 paused→completed 直达路径)——按方案 §2.1 契约保留作前向兼容预留
|
|
1794
|
-
|
|
1795
|
-
status:
|
|
2791
|
+
z9.object({
|
|
2792
|
+
status: z9.literal("completed"),
|
|
1796
2793
|
task: TaskPublicSchema
|
|
1797
2794
|
})
|
|
1798
2795
|
]);
|
|
1799
|
-
var PauseRequestSchema =
|
|
1800
|
-
reason:
|
|
2796
|
+
var PauseRequestSchema = z9.object({
|
|
2797
|
+
reason: z9.string().optional()
|
|
1801
2798
|
});
|
|
1802
|
-
var ResumeRequestSchema =
|
|
1803
|
-
decision:
|
|
2799
|
+
var ResumeRequestSchema = z9.object({
|
|
2800
|
+
decision: z9.string().optional()
|
|
2801
|
+
});
|
|
2802
|
+
var CancelRequestSchema = z9.object({
|
|
2803
|
+
reason: z9.string().min(1).max(500).optional()
|
|
1804
2804
|
});
|
|
1805
2805
|
|
|
1806
2806
|
// src/schemas/task-doc.ts
|
|
1807
|
-
import { z as
|
|
1808
|
-
var TaskContextSchema =
|
|
1809
|
-
task:
|
|
1810
|
-
taskId:
|
|
1811
|
-
title:
|
|
2807
|
+
import { z as z10 } from "zod";
|
|
2808
|
+
var TaskContextSchema = z10.object({
|
|
2809
|
+
task: z10.object({
|
|
2810
|
+
taskId: z10.string(),
|
|
2811
|
+
title: z10.string(),
|
|
1812
2812
|
// T202608240003:与 TaskPublicSchema 同步(toTaskPublic 运行时已产出,schema 声明漂移会导致 typed 消费方 strip 掉 type)
|
|
1813
|
-
type:
|
|
1814
|
-
currentNode:
|
|
1815
|
-
currentPhase:
|
|
1816
|
-
status:
|
|
1817
|
-
pausedAt:
|
|
1818
|
-
track:
|
|
2813
|
+
type: z10.enum(TASK_TYPES).optional(),
|
|
2814
|
+
currentNode: z10.string(),
|
|
2815
|
+
currentPhase: z10.string(),
|
|
2816
|
+
status: z10.string(),
|
|
2817
|
+
pausedAt: z10.string().nullable(),
|
|
2818
|
+
track: z10.string()
|
|
1819
2819
|
}),
|
|
1820
|
-
currentNode:
|
|
1821
|
-
nodeId:
|
|
1822
|
-
label:
|
|
1823
|
-
phase:
|
|
1824
|
-
track:
|
|
2820
|
+
currentNode: z10.object({
|
|
2821
|
+
nodeId: z10.string(),
|
|
2822
|
+
label: z10.string(),
|
|
2823
|
+
phase: z10.string(),
|
|
2824
|
+
track: z10.string(),
|
|
1825
2825
|
/** renderPrompt 渲染后(AI 推进任务的直接输入) */
|
|
1826
|
-
prompt:
|
|
1827
|
-
skills:
|
|
2826
|
+
prompt: z10.string(),
|
|
2827
|
+
skills: z10.array(z10.string()),
|
|
2828
|
+
agents: z10.array(z10.string()),
|
|
1828
2829
|
/** 完成当前节点后出边上的暂停点描述(经 findNextEdge 按轨选边) */
|
|
1829
|
-
upcomingPause:
|
|
2830
|
+
upcomingPause: z10.string().nullable()
|
|
1830
2831
|
}).nullable(),
|
|
1831
|
-
pausedAtEdge:
|
|
1832
|
-
from:
|
|
1833
|
-
to:
|
|
2832
|
+
pausedAtEdge: z10.object({
|
|
2833
|
+
from: z10.string(),
|
|
2834
|
+
to: z10.string(),
|
|
1834
2835
|
pausePoint: EdgePausePointBaseSchema
|
|
1835
2836
|
}).nullable(),
|
|
1836
2837
|
/** 全节点实时状态清单 */
|
|
1837
|
-
nodes:
|
|
1838
|
-
nodeId:
|
|
1839
|
-
label:
|
|
1840
|
-
status:
|
|
1841
|
-
enteredAt:
|
|
1842
|
-
completedAt:
|
|
2838
|
+
nodes: z10.array(z10.object({
|
|
2839
|
+
nodeId: z10.string(),
|
|
2840
|
+
label: z10.string(),
|
|
2841
|
+
status: z10.string(),
|
|
2842
|
+
enteredAt: z10.date().nullable(),
|
|
2843
|
+
completedAt: z10.date().nullable()
|
|
1843
2844
|
})),
|
|
1844
2845
|
/** N020 D1:任务级结构化文档(未写时 null) */
|
|
1845
2846
|
taskDoc: TaskDocContentSchema.nullable(),
|
|
1846
2847
|
/** N020 D1:节点执行记录(仅含已写记录的 nodeId) */
|
|
1847
|
-
nodeRecords:
|
|
2848
|
+
nodeRecords: z10.record(z10.string(), NodeRecordSchema),
|
|
1848
2849
|
/** N020 D1:跨节点累积素材(架构信息收集条目) */
|
|
1849
|
-
archNotes:
|
|
2850
|
+
archNotes: z10.array(ArchNoteSchema)
|
|
1850
2851
|
});
|
|
1851
|
-
var TaskSummarySchema =
|
|
1852
|
-
taskId:
|
|
1853
|
-
title:
|
|
2852
|
+
var TaskSummarySchema = z10.object({
|
|
2853
|
+
taskId: z10.string(),
|
|
2854
|
+
title: z10.string(),
|
|
1854
2855
|
// T202608240003:任务类型标签(缺省 = 裸任务/旧任务)
|
|
1855
|
-
type:
|
|
1856
|
-
projectId:
|
|
1857
|
-
dagTemplateId:
|
|
1858
|
-
currentNode:
|
|
1859
|
-
currentPhase:
|
|
1860
|
-
status:
|
|
1861
|
-
pausedAt:
|
|
1862
|
-
track:
|
|
1863
|
-
progress:
|
|
1864
|
-
completed:
|
|
1865
|
-
total:
|
|
2856
|
+
type: z10.enum(TASK_TYPES).optional(),
|
|
2857
|
+
projectId: z10.string(),
|
|
2858
|
+
dagTemplateId: z10.string(),
|
|
2859
|
+
currentNode: z10.string(),
|
|
2860
|
+
currentPhase: z10.string(),
|
|
2861
|
+
status: z10.string(),
|
|
2862
|
+
pausedAt: z10.string().nullable(),
|
|
2863
|
+
track: z10.string(),
|
|
2864
|
+
progress: z10.object({
|
|
2865
|
+
completed: z10.number(),
|
|
2866
|
+
total: z10.number()
|
|
1866
2867
|
}),
|
|
1867
|
-
createdAt:
|
|
1868
|
-
updatedAt:
|
|
2868
|
+
createdAt: z10.date().optional(),
|
|
2869
|
+
updatedAt: z10.date().optional()
|
|
2870
|
+
});
|
|
2871
|
+
|
|
2872
|
+
// src/schemas/write-ack.ts
|
|
2873
|
+
import { z as z11 } from "zod";
|
|
2874
|
+
var WRITE_ACK_ENTRY_KINDS = ["check", "artifact", "decision", "confirmation", "archnote"];
|
|
2875
|
+
var WriteAckEntryKindSchema = z11.enum(WRITE_ACK_ENTRY_KINDS);
|
|
2876
|
+
var EchoItemSchema = z11.object({
|
|
2877
|
+
path: z11.string(),
|
|
2878
|
+
excerpt: z11.string()
|
|
2879
|
+
});
|
|
2880
|
+
var TaskProgressSchema = z11.object({
|
|
2881
|
+
completed: z11.number().int(),
|
|
2882
|
+
total: z11.number().int()
|
|
1869
2883
|
});
|
|
2884
|
+
var TaskProgressPublicSchema = TaskPublicSchema.extend({
|
|
2885
|
+
projectId: z11.string(),
|
|
2886
|
+
progress: TaskProgressSchema
|
|
2887
|
+
});
|
|
2888
|
+
var WriteAckSchema = z11.object({
|
|
2889
|
+
taskId: z11.string(),
|
|
2890
|
+
/** 本次写入触及的字段点路径(如 doc.what / nodeRecords.PRD.checks) */
|
|
2891
|
+
updated: z11.array(z11.string()),
|
|
2892
|
+
/** 新建条目标识($push 五类对象条目;无新条目的端点缺省) */
|
|
2893
|
+
entry: z11.object({
|
|
2894
|
+
id: z11.string(),
|
|
2895
|
+
kind: WriteAckEntryKindSchema
|
|
2896
|
+
}).optional(),
|
|
2897
|
+
/** 写入内容回显(触及字段逐条;无值可回显的端点可缺省) */
|
|
2898
|
+
echo: z11.array(EchoItemSchema).optional(),
|
|
2899
|
+
updatedAt: z11.date(),
|
|
2900
|
+
task: TaskProgressPublicSchema
|
|
2901
|
+
});
|
|
2902
|
+
var TaskBatchEntrySchema = z11.object({
|
|
2903
|
+
index: z11.number().int().min(0),
|
|
2904
|
+
id: z11.string().optional(),
|
|
2905
|
+
kind: WriteAckEntryKindSchema.optional()
|
|
2906
|
+
});
|
|
2907
|
+
var TaskBatchFailureSchema = z11.object({
|
|
2908
|
+
index: z11.number().int().min(0),
|
|
2909
|
+
message: z11.string(),
|
|
2910
|
+
hint: z11.string().optional()
|
|
2911
|
+
});
|
|
2912
|
+
var TaskBatchAckSchema = z11.object({
|
|
2913
|
+
taskId: z11.string(),
|
|
2914
|
+
/** 成功落库条目数(部分失败语义:成功条目保留不回滚) */
|
|
2915
|
+
applied: z11.number().int().min(0),
|
|
2916
|
+
/** 本次写入触及的字段点路径 */
|
|
2917
|
+
updated: z11.array(z11.string()),
|
|
2918
|
+
entries: z11.array(TaskBatchEntrySchema),
|
|
2919
|
+
failures: z11.array(TaskBatchFailureSchema),
|
|
2920
|
+
updatedAt: z11.date(),
|
|
2921
|
+
task: TaskProgressPublicSchema
|
|
2922
|
+
});
|
|
2923
|
+
var CreateTaskResponseSchema = z11.object({
|
|
2924
|
+
task: TaskProgressPublicSchema,
|
|
2925
|
+
firstNode: NodeInfoSchema
|
|
2926
|
+
});
|
|
2927
|
+
function taskProgress(task) {
|
|
2928
|
+
const states = Object.values(task.dagInstance.nodeStates);
|
|
2929
|
+
const completed = states.filter((s) => s?.status === "completed").length;
|
|
2930
|
+
const skipped = states.filter((s) => s?.status === "skipped").length;
|
|
2931
|
+
return { completed, total: task.dagInstance.nodes.length - skipped };
|
|
2932
|
+
}
|
|
2933
|
+
function excerpt(value, max = 120) {
|
|
2934
|
+
const chars = Array.from(value);
|
|
2935
|
+
if (chars.length <= max) return value;
|
|
2936
|
+
return `${chars.slice(0, max - 1).join("")}\u2026`;
|
|
2937
|
+
}
|
|
2938
|
+
var CONTEXT_VIEWS = ["basic", "full"];
|
|
2939
|
+
var ContextViewSchema = z11.enum(CONTEXT_VIEWS);
|
|
1870
2940
|
|
|
1871
2941
|
// src/schemas/project.ts
|
|
1872
|
-
import { z as
|
|
1873
|
-
var ProjectStatusSchema =
|
|
1874
|
-
var ProjectSchema =
|
|
1875
|
-
id:
|
|
1876
|
-
key:
|
|
1877
|
-
name:
|
|
1878
|
-
description:
|
|
2942
|
+
import { z as z12 } from "zod";
|
|
2943
|
+
var ProjectStatusSchema = z12.enum(["active", "archived"]);
|
|
2944
|
+
var ProjectSchema = z12.object({
|
|
2945
|
+
id: z12.string().optional(),
|
|
2946
|
+
key: z12.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09"),
|
|
2947
|
+
name: z12.string().min(1).max(50),
|
|
2948
|
+
description: z12.string().max(500).optional(),
|
|
1879
2949
|
status: ProjectStatusSchema.default("active"),
|
|
1880
|
-
createdAt:
|
|
1881
|
-
updatedAt:
|
|
2950
|
+
createdAt: z12.date().optional(),
|
|
2951
|
+
updatedAt: z12.date().optional()
|
|
1882
2952
|
});
|
|
1883
|
-
var ProjectCreateSchema =
|
|
1884
|
-
key:
|
|
1885
|
-
name:
|
|
1886
|
-
description:
|
|
2953
|
+
var ProjectCreateSchema = z12.object({
|
|
2954
|
+
key: z12.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09").optional(),
|
|
2955
|
+
name: z12.string().min(1).max(50),
|
|
2956
|
+
description: z12.string().max(500).optional()
|
|
1887
2957
|
});
|
|
1888
|
-
var ProjectUpdateSchema =
|
|
1889
|
-
name:
|
|
1890
|
-
description:
|
|
2958
|
+
var ProjectUpdateSchema = z12.object({
|
|
2959
|
+
name: z12.string().min(1).max(50).optional(),
|
|
2960
|
+
description: z12.string().max(500).optional(),
|
|
1891
2961
|
status: ProjectStatusSchema.optional()
|
|
1892
2962
|
});
|
|
1893
2963
|
|
|
1894
2964
|
// src/schemas/enum-registry.ts
|
|
1895
|
-
import { z as
|
|
1896
|
-
var EnumRegistryCategorySchema =
|
|
2965
|
+
import { z as z13 } from "zod";
|
|
2966
|
+
var EnumRegistryCategorySchema = z13.enum([
|
|
1897
2967
|
"dag_phase",
|
|
1898
2968
|
"dag_track",
|
|
1899
2969
|
"pause_type",
|
|
1900
2970
|
"task_status",
|
|
1901
2971
|
"node_status",
|
|
1902
2972
|
"skill_category",
|
|
1903
|
-
"scope"
|
|
2973
|
+
"scope",
|
|
2974
|
+
"agent_function"
|
|
1904
2975
|
]);
|
|
1905
|
-
var EnumEntrySchema =
|
|
2976
|
+
var EnumEntrySchema = z13.object({
|
|
1906
2977
|
/** 领域层存库的原始枚举值(不可臆造;builtin 值 engine/DB 强依赖;kebab 约束与 skill name 对齐,保证 DELETE /entries/:value 可寻址) */
|
|
1907
|
-
value:
|
|
2978
|
+
value: z13.string().regex(/^[a-z0-9][a-z0-9_-]*$/, "value \u4EC5\u652F\u6301\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u4E0B\u5212\u7EBF/\u8FDE\u5B57\u7B26\uFF08kebab-case\uFF09"),
|
|
1908
2979
|
/** 展示文案(消费端下拉直接取用) */
|
|
1909
|
-
label:
|
|
2980
|
+
label: z13.string().min(1, "label \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
1910
2981
|
/** 可选语义色 token(如 --success / --warning),消费端映射到 UI */
|
|
1911
|
-
color:
|
|
2982
|
+
color: z13.string().optional(),
|
|
1912
2983
|
/** 下拉排序(升序) */
|
|
1913
|
-
order:
|
|
2984
|
+
order: z13.number().int().default(0),
|
|
1914
2985
|
/** 内置值:禁删 + label/color 可改但 value 不可改(D8);seed 时标记 */
|
|
1915
|
-
builtin:
|
|
2986
|
+
builtin: z13.boolean().default(false),
|
|
1916
2987
|
/** 启停:false = 下拉禁用展示(历史数据仍可读,新选择不可用) */
|
|
1917
|
-
active:
|
|
2988
|
+
active: z13.boolean().default(true)
|
|
1918
2989
|
});
|
|
1919
|
-
var EnumRegistrySchema =
|
|
1920
|
-
id:
|
|
2990
|
+
var EnumRegistrySchema = z13.object({
|
|
2991
|
+
id: z13.string().optional(),
|
|
1921
2992
|
category: EnumRegistryCategorySchema,
|
|
1922
|
-
entries:
|
|
1923
|
-
updatedAt:
|
|
2993
|
+
entries: z13.array(EnumEntrySchema).default([]),
|
|
2994
|
+
updatedAt: z13.date().optional()
|
|
1924
2995
|
});
|
|
1925
|
-
var EnumRegistryUpdateSchema =
|
|
1926
|
-
entries:
|
|
2996
|
+
var EnumRegistryUpdateSchema = z13.object({
|
|
2997
|
+
entries: z13.array(EnumEntrySchema)
|
|
2998
|
+
});
|
|
2999
|
+
|
|
3000
|
+
// src/schemas/asset-enabled.ts
|
|
3001
|
+
import { z as z14 } from "zod";
|
|
3002
|
+
var AssetSetEnabledSchema = z14.object({
|
|
3003
|
+
enabled: z14.boolean()
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
// src/schemas/auth.ts
|
|
3007
|
+
import { z as z15 } from "zod";
|
|
3008
|
+
var AuthTokenTypeSchema = z15.enum(["admin", "project"]);
|
|
3009
|
+
var AuthAccountSchema = z15.object({
|
|
3010
|
+
id: z15.string().optional(),
|
|
3011
|
+
username: z15.string().min(3, "\u8D26\u53F7\u81F3\u5C11 3 \u4E2A\u5B57\u7B26").max(50).regex(/^[a-zA-Z0-9_-]+$/, "\u8D26\u53F7\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u4E0E\u8FDE\u5B57\u7B26"),
|
|
3012
|
+
/** 存储形态:scrypt$N$r$p$salt$hash(base64 段),明文仅存在于请求体 */
|
|
3013
|
+
passwordHash: z15.string().min(1),
|
|
3014
|
+
createdAt: z15.date().optional(),
|
|
3015
|
+
updatedAt: z15.date().optional()
|
|
3016
|
+
});
|
|
3017
|
+
var AuthAccountCreateSchema = z15.object({
|
|
3018
|
+
username: AuthAccountSchema.shape.username,
|
|
3019
|
+
password: z15.string().min(8, "\u5BC6\u7801\u81F3\u5C11 8 \u4E2A\u5B57\u7B26").max(128)
|
|
3020
|
+
});
|
|
3021
|
+
var PasswordChangeSchema = z15.object({
|
|
3022
|
+
currentPassword: z15.string().min(1),
|
|
3023
|
+
newPassword: z15.string().min(8, "\u5BC6\u7801\u81F3\u5C11 8 \u4E2A\u5B57\u7B26").max(128)
|
|
3024
|
+
});
|
|
3025
|
+
var LoginSchema = z15.object({
|
|
3026
|
+
username: z15.string().min(1),
|
|
3027
|
+
password: z15.string().min(1)
|
|
3028
|
+
});
|
|
3029
|
+
var AuthTokenSchema = z15.object({
|
|
3030
|
+
id: z15.string().optional(),
|
|
3031
|
+
/** sha256(token 全串含前缀) hex——明文 Token 不入库 */
|
|
3032
|
+
tokenHash: z15.string().regex(/^[a-f0-9]{64}$/),
|
|
3033
|
+
type: AuthTokenTypeSchema,
|
|
3034
|
+
/** type=project 时必填且不可缺失(superRefine 保证);指向 projects.id(24-hex) */
|
|
3035
|
+
projectId: z15.string().regex(/^[a-f0-9]{24}$/, "projectId \u5FC5\u987B\u4E3A 24-hex ObjectId").optional(),
|
|
3036
|
+
createdAt: z15.date().optional(),
|
|
3037
|
+
updatedAt: z15.date().optional()
|
|
3038
|
+
}).superRefine((data, ctx) => {
|
|
3039
|
+
if (data.type === "project" && data.projectId === void 0) {
|
|
3040
|
+
ctx.addIssue({ code: "custom", path: ["projectId"], message: "\u9879\u76EE Token \u5FC5\u987B\u7ED1\u5B9A projectId" });
|
|
3041
|
+
}
|
|
3042
|
+
});
|
|
3043
|
+
var AuthTokenGenResponseSchema = z15.object({
|
|
3044
|
+
token: z15.string().min(1),
|
|
3045
|
+
type: AuthTokenTypeSchema,
|
|
3046
|
+
projectId: z15.string().optional()
|
|
3047
|
+
});
|
|
3048
|
+
var AuthTokenStatusSchema = z15.object({
|
|
3049
|
+
type: AuthTokenTypeSchema,
|
|
3050
|
+
projectId: z15.string().optional(),
|
|
3051
|
+
hasToken: z15.boolean(),
|
|
3052
|
+
createdAt: z15.date().nullable().optional()
|
|
3053
|
+
});
|
|
3054
|
+
var AuthWhoamiSchema = z15.object({
|
|
3055
|
+
identity: z15.enum(["admin", "project", "none"]),
|
|
3056
|
+
project: z15.object({
|
|
3057
|
+
id: z15.string(),
|
|
3058
|
+
key: z15.string(),
|
|
3059
|
+
name: z15.string()
|
|
3060
|
+
}).nullable().optional(),
|
|
3061
|
+
authEnabled: z15.boolean(),
|
|
3062
|
+
server: z15.object({
|
|
3063
|
+
url: z15.string()
|
|
3064
|
+
})
|
|
3065
|
+
});
|
|
3066
|
+
var AuthStatusSchema = z15.object({
|
|
3067
|
+
authEnabled: z15.boolean(),
|
|
3068
|
+
/** 鉴权开启且无管理员账号 = 初始化引导窗口(PRD §3.2) */
|
|
3069
|
+
needsSetup: z15.boolean()
|
|
3070
|
+
});
|
|
3071
|
+
|
|
3072
|
+
// src/schemas/node-preset.ts
|
|
3073
|
+
import { z as z16 } from "zod";
|
|
3074
|
+
var NODE_PRESET_CODE_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
3075
|
+
var NodePresetScopeRefine = (val, ctx) => {
|
|
3076
|
+
if (val.scope === "project" && !val.projectId) {
|
|
3077
|
+
ctx.addIssue({
|
|
3078
|
+
code: "custom",
|
|
3079
|
+
path: ["projectId"],
|
|
3080
|
+
message: "scope=project \u65F6 projectId \u5FC5\u586B"
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
var uniqueNamesSchema = z16.array(z16.string().min(1)).superRefine((names, ctx) => {
|
|
3085
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3086
|
+
names.forEach((name, index) => {
|
|
3087
|
+
if (seen.has(name)) {
|
|
3088
|
+
ctx.addIssue({ code: "custom", path: [index], message: `\u540D\u79F0\u91CD\u590D\uFF1A${name}` });
|
|
3089
|
+
}
|
|
3090
|
+
seen.add(name);
|
|
3091
|
+
});
|
|
3092
|
+
});
|
|
3093
|
+
var NodePresetShapeSchema = z16.object({
|
|
3094
|
+
id: z16.string().optional(),
|
|
3095
|
+
code: z16.string().regex(NODE_PRESET_CODE_PATTERN, "kebab-case only"),
|
|
3096
|
+
label: z16.string().min(1),
|
|
3097
|
+
/** 组装默认节点标识(模板图结构域;字符集同 NODE_ID_PATTERN——节点记录点路径拼接安全前提) */
|
|
3098
|
+
nodeId: z16.string().regex(/^[A-Za-z0-9_-]+$/, "nodeId \u53EA\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u4E0B\u5212\u7EBF/\u8FDE\u5B57\u7B26"),
|
|
3099
|
+
/** 运行时注册表判定(对齐 dag_phase/dag_track 放宽策略——Zod 只做类型级) */
|
|
3100
|
+
phase: z16.string(),
|
|
3101
|
+
track: z16.string(),
|
|
3102
|
+
prompt: z16.string().min(1),
|
|
3103
|
+
/** 绑定技能名单(by-name 引用;版本要求只存在于 npm 分发层 manifest,不侵入运行链路) */
|
|
3104
|
+
skills: uniqueNamesSchema,
|
|
3105
|
+
/** 节点执行时可调用的 Agent 名单(by-name 引用;正文仍由独立 Agent 资产承载) */
|
|
3106
|
+
agents: uniqueNamesSchema,
|
|
3107
|
+
scope: z16.enum(["global", "project"]),
|
|
3108
|
+
projectId: z16.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
|
|
3109
|
+
description: z16.string().optional(),
|
|
3110
|
+
version: z16.string(),
|
|
3111
|
+
/** 分发来源标注(flow install 落库时填充包名;手工创建无此字段——「来源可见可核查」信任模型) */
|
|
3112
|
+
source: z16.object({ package: z16.string().min(1) }).optional(),
|
|
3113
|
+
/** 资产启停(可选——存量无此字段 = 启用;唯一写入口 POST /:code/enabled,不入 Create/Update/Copy) */
|
|
3114
|
+
enabled: z16.boolean().optional(),
|
|
3115
|
+
createdAt: z16.date().optional(),
|
|
3116
|
+
updatedAt: z16.date().optional()
|
|
3117
|
+
});
|
|
3118
|
+
var NodePresetSchema = NodePresetShapeSchema.superRefine(NodePresetScopeRefine);
|
|
3119
|
+
var NodePresetCreateSchema = NodePresetShapeSchema.omit({
|
|
3120
|
+
id: true,
|
|
3121
|
+
createdAt: true,
|
|
3122
|
+
updatedAt: true,
|
|
3123
|
+
enabled: true
|
|
3124
|
+
}).extend({
|
|
3125
|
+
skills: uniqueNamesSchema.default([]),
|
|
3126
|
+
agents: uniqueNamesSchema.default([]),
|
|
3127
|
+
version: z16.string().default("1.0.0")
|
|
3128
|
+
}).superRefine(NodePresetScopeRefine);
|
|
3129
|
+
var NodePresetUpdateSchema = NodePresetShapeSchema.omit({
|
|
3130
|
+
id: true,
|
|
3131
|
+
createdAt: true,
|
|
3132
|
+
updatedAt: true,
|
|
3133
|
+
enabled: true
|
|
3134
|
+
}).extend({
|
|
3135
|
+
version: z16.string().optional()
|
|
3136
|
+
}).partial().superRefine(NodePresetScopeRefine);
|
|
3137
|
+
var NodePresetCopySchema = z16.object({
|
|
3138
|
+
newCode: z16.string().regex(NODE_PRESET_CODE_PATTERN, "kebab-case only"),
|
|
3139
|
+
newScope: z16.enum(["global", "project"]),
|
|
3140
|
+
targetProjectId: z16.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
|
|
3141
|
+
}).superRefine((val, ctx) => {
|
|
3142
|
+
if (val.newScope === "project" && !val.targetProjectId) {
|
|
3143
|
+
ctx.addIssue({
|
|
3144
|
+
code: "custom",
|
|
3145
|
+
path: ["targetProjectId"],
|
|
3146
|
+
message: "newScope=project \u65F6 targetProjectId \u5FC5\u586B"
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
|
|
3151
|
+
// src/schemas/node-library.ts
|
|
3152
|
+
import { z as z17 } from "zod";
|
|
3153
|
+
var CompositionEdgeSchema = z17.object({
|
|
3154
|
+
from: z17.string().min(1),
|
|
3155
|
+
to: z17.string().min(1),
|
|
3156
|
+
condition: z17.string().optional(),
|
|
3157
|
+
pausePoint: EdgePausePointSchema.optional()
|
|
3158
|
+
});
|
|
3159
|
+
var CompositionSchema = z17.object({
|
|
3160
|
+
name: z17.string().min(1),
|
|
3161
|
+
description: z17.string().optional(),
|
|
3162
|
+
nodes: z17.array(z17.string().min(1)).min(1),
|
|
3163
|
+
edges: z17.array(CompositionEdgeSchema)
|
|
3164
|
+
});
|
|
3165
|
+
var NodeLibrarySchema = z17.object({
|
|
3166
|
+
id: z17.string().optional(),
|
|
3167
|
+
/** 库名 = npm 包名(同库重装按名 upsert 覆盖登记) */
|
|
3168
|
+
name: z17.string().min(1),
|
|
3169
|
+
version: z17.string(),
|
|
3170
|
+
scope: z17.enum(["global", "project"]),
|
|
3171
|
+
projectId: z17.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
|
|
3172
|
+
/** 已装节点 code 清单(flow compose 逐 code 解析 node_presets) */
|
|
3173
|
+
nodes: z17.array(z17.string()),
|
|
3174
|
+
compositions: z17.array(CompositionSchema),
|
|
3175
|
+
installedAt: z17.date().optional()
|
|
3176
|
+
});
|
|
3177
|
+
var NodeLibraryUpsertSchema = z17.object({
|
|
3178
|
+
name: z17.string().min(1),
|
|
3179
|
+
version: z17.string(),
|
|
3180
|
+
scope: z17.enum(["global", "project"]),
|
|
3181
|
+
projectId: z17.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
|
|
3182
|
+
nodes: z17.array(z17.string()),
|
|
3183
|
+
compositions: z17.array(CompositionSchema)
|
|
3184
|
+
}).superRefine((val, ctx) => {
|
|
3185
|
+
if (val.scope === "project" && !val.projectId) {
|
|
3186
|
+
ctx.addIssue({
|
|
3187
|
+
code: "custom",
|
|
3188
|
+
path: ["projectId"],
|
|
3189
|
+
message: "scope=project \u65F6 projectId \u5FC5\u586B"
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
});
|
|
3193
|
+
|
|
3194
|
+
// src/schemas/template-upgrade.ts
|
|
3195
|
+
import { z as z18 } from "zod";
|
|
3196
|
+
var UpgradeNodeStatusSchema = z18.enum([
|
|
3197
|
+
"updatable",
|
|
3198
|
+
"local-modified",
|
|
3199
|
+
"up-to-date",
|
|
3200
|
+
"source-missing",
|
|
3201
|
+
"source-disabled"
|
|
3202
|
+
]);
|
|
3203
|
+
var UpgradeActionSchema = z18.enum(["upgrade", "keep", "skip"]);
|
|
3204
|
+
var UpgradeDecisionSchema = z18.object({
|
|
3205
|
+
nodeId: z18.string().min(1),
|
|
3206
|
+
action: UpgradeActionSchema,
|
|
3207
|
+
expected: z18.object({ status: UpgradeNodeStatusSchema }).optional()
|
|
3208
|
+
});
|
|
3209
|
+
var UpgradeApplyRequestSchema = z18.object({
|
|
3210
|
+
decisions: z18.array(UpgradeDecisionSchema).min(1)
|
|
1927
3211
|
});
|
|
1928
3212
|
|
|
1929
3213
|
// src/config/server-config.ts
|
|
1930
|
-
import { z as
|
|
1931
|
-
var SimingLogLevelSchema =
|
|
1932
|
-
var SimingConfigSchema =
|
|
1933
|
-
mongoUri:
|
|
1934
|
-
port:
|
|
3214
|
+
import { z as z19 } from "zod";
|
|
3215
|
+
var SimingLogLevelSchema = z19.enum(["debug", "info", "warn", "error"]);
|
|
3216
|
+
var SimingConfigSchema = z19.object({
|
|
3217
|
+
mongoUri: z19.string().min(1).default("mongodb://localhost:27017"),
|
|
3218
|
+
port: z19.coerce.number().int().min(1).max(65535).default(7777),
|
|
1935
3219
|
/**
|
|
1936
3220
|
* N018 D8:默认仅绑定本机回环(安全默认);远程/局域接入(F7)显式配置 host。
|
|
1937
3221
|
* 超出 PRD V1 配置清单的新增项,依据见技术方案 §3.4「超范围扩展标注」。
|
|
1938
3222
|
*/
|
|
1939
|
-
host:
|
|
3223
|
+
host: z19.string().min(1).default("127.0.0.1"),
|
|
1940
3224
|
logLevel: SimingLogLevelSchema.default("info"),
|
|
1941
3225
|
/**
|
|
1942
|
-
* F6
|
|
1943
|
-
*
|
|
3226
|
+
* T202608310001:鉴权开关(F1)——从 F6 预留宽松形态收紧为类型化 schema。
|
|
3227
|
+
* 默认关闭 = 本地开发/存量行为零破坏;云主机部署显式开启(SIMING_AUTH_ENABLED=true)。
|
|
1944
3228
|
*/
|
|
1945
|
-
auth:
|
|
1946
|
-
|
|
3229
|
+
auth: z19.object({
|
|
3230
|
+
enabled: z19.boolean().default(false)
|
|
3231
|
+
}).prefault({}),
|
|
3232
|
+
cors: z19.unknown().optional()
|
|
1947
3233
|
});
|
|
1948
|
-
var SIMING_CONFIG_KEYS = ["mongoUri", "port", "host", "logLevel"];
|
|
3234
|
+
var SIMING_CONFIG_KEYS = ["mongoUri", "port", "host", "logLevel", "auth"];
|
|
1949
3235
|
var SIMING_CONFIG_ENV_KEYS = {
|
|
1950
3236
|
mongoUri: "SIMING_MONGO_URI",
|
|
1951
3237
|
port: "PORT",
|
|
1952
3238
|
host: "SIMING_HOST",
|
|
1953
|
-
logLevel: "SIMING_LOG_LEVEL"
|
|
3239
|
+
logLevel: "SIMING_LOG_LEVEL",
|
|
3240
|
+
auth: "SIMING_AUTH_ENABLED"
|
|
3241
|
+
};
|
|
3242
|
+
function parseAuthEnabledEnvValue(raw) {
|
|
3243
|
+
if (raw === "true") return true;
|
|
3244
|
+
if (raw === "false") return false;
|
|
3245
|
+
throw new Error(`SIMING_AUTH_ENABLED \u4EC5\u63A5\u53D7 true/false\uFF0C\u6536\u5230: ${raw}`);
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// src/version-bump.ts
|
|
3249
|
+
function tryBumpPatch(version) {
|
|
3250
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
3251
|
+
if (!m || m[1] === void 0 || m[2] === void 0 || m[3] === void 0) return null;
|
|
3252
|
+
return `${m[1]}.${m[2]}.${String(Number(m[3]) + 1)}`;
|
|
3253
|
+
}
|
|
3254
|
+
function bumpPatch(version) {
|
|
3255
|
+
return tryBumpPatch(version) ?? "1.0.1";
|
|
3256
|
+
}
|
|
3257
|
+
function bumpPatchOrPassthrough(version) {
|
|
3258
|
+
return tryBumpPatch(version) ?? version;
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
// src/content-hash.ts
|
|
3262
|
+
import { createHash as createHash2 } from "crypto";
|
|
3263
|
+
function computeNodeContentHash(node) {
|
|
3264
|
+
const canonical = JSON.stringify({
|
|
3265
|
+
label: node.label,
|
|
3266
|
+
phase: node.phase,
|
|
3267
|
+
track: node.track,
|
|
3268
|
+
prompt: node.prompt,
|
|
3269
|
+
skills: [...node.skills].sort()
|
|
3270
|
+
});
|
|
3271
|
+
return createHash2("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
// src/template-content-diff.ts
|
|
3275
|
+
function stableStringify(value) {
|
|
3276
|
+
if (value === null || typeof value !== "object") {
|
|
3277
|
+
return JSON.stringify(value) ?? "null";
|
|
3278
|
+
}
|
|
3279
|
+
if (Array.isArray(value)) {
|
|
3280
|
+
return `[${value.map((v) => v === void 0 ? "null" : stableStringify(v)).join(",")}]`;
|
|
3281
|
+
}
|
|
3282
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3283
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
3284
|
+
}
|
|
3285
|
+
function sameAgentNames(a, b) {
|
|
3286
|
+
return stableStringify([...a ?? []].sort()) === stableStringify([...b ?? []].sort());
|
|
3287
|
+
}
|
|
3288
|
+
function edgeKey(e) {
|
|
3289
|
+
return JSON.stringify([e.from, e.to, e.condition ?? null, e.pausePoint ?? null]);
|
|
3290
|
+
}
|
|
3291
|
+
function diffTemplateContent(imported, existing) {
|
|
3292
|
+
const importedById = new Map(imported.nodes.map((n) => [n.id, n]));
|
|
3293
|
+
const existingById = new Map(existing.nodes.map((n) => [n.id, n]));
|
|
3294
|
+
const nodesAdded = [...importedById.keys()].filter((id) => !existingById.has(id)).sort();
|
|
3295
|
+
const nodesRemoved = [...existingById.keys()].filter((id) => !importedById.has(id)).sort();
|
|
3296
|
+
const nodesChanged = [...importedById.keys()].filter((id) => {
|
|
3297
|
+
const a = importedById.get(id);
|
|
3298
|
+
const b = existingById.get(id);
|
|
3299
|
+
if (a === void 0 || b === void 0) return false;
|
|
3300
|
+
const normA = { ...a, skills: a.skills ?? [] };
|
|
3301
|
+
const normB = { ...b, skills: b.skills ?? [] };
|
|
3302
|
+
return computeNodeContentHash(normA) !== computeNodeContentHash(normB) || !sameAgentNames(a.agents, b.agents) || !sameAgentNames(a.sourcePreset?.agents, b.sourcePreset?.agents);
|
|
3303
|
+
}).sort();
|
|
3304
|
+
const edgesChanged = stableStringify([...imported.edges].map(edgeKey).sort()) !== stableStringify([...existing.edges].map(edgeKey).sort());
|
|
3305
|
+
const descriptionChanged = imported.description !== existing.description;
|
|
3306
|
+
const layoutChanged = stableStringify(imported.layout ?? {}) !== stableStringify(existing.layout ?? {});
|
|
3307
|
+
const identical = imported.name === existing.name && !descriptionChanged && nodesAdded.length === 0 && nodesRemoved.length === 0 && nodesChanged.length === 0 && !edgesChanged && !layoutChanged;
|
|
3308
|
+
return {
|
|
3309
|
+
identical,
|
|
3310
|
+
changes: { nodesAdded, nodesRemoved, nodesChanged, edgesChanged, descriptionChanged, layoutChanged }
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
// src/template-upgrade.ts
|
|
3315
|
+
function presetContent(preset) {
|
|
3316
|
+
return {
|
|
3317
|
+
label: preset.label,
|
|
3318
|
+
phase: preset.phase,
|
|
3319
|
+
track: preset.track,
|
|
3320
|
+
prompt: preset.prompt,
|
|
3321
|
+
skills: preset.skills
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
var EMPTY_DIFF = {
|
|
3325
|
+
prompt: null,
|
|
3326
|
+
label: null,
|
|
3327
|
+
phase: null,
|
|
3328
|
+
track: null,
|
|
3329
|
+
skills: null,
|
|
3330
|
+
agents: null
|
|
1954
3331
|
};
|
|
3332
|
+
function diffNames(beforeNames, afterNames) {
|
|
3333
|
+
const before = new Set(beforeNames);
|
|
3334
|
+
const after = new Set(afterNames);
|
|
3335
|
+
const added = [...after].filter((name) => !before.has(name)).sort();
|
|
3336
|
+
const removed = [...before].filter((name) => !after.has(name)).sort();
|
|
3337
|
+
return added.length === 0 && removed.length === 0 ? null : { added, removed };
|
|
3338
|
+
}
|
|
3339
|
+
function sameNames(left, right) {
|
|
3340
|
+
return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
|
|
3341
|
+
}
|
|
3342
|
+
function diffNodeAgainstPreset(node, preset) {
|
|
3343
|
+
return {
|
|
3344
|
+
prompt: node.prompt === preset.prompt ? null : "changed",
|
|
3345
|
+
label: node.label === preset.label ? null : "changed",
|
|
3346
|
+
phase: node.phase === preset.phase ? null : "changed",
|
|
3347
|
+
track: node.track === preset.track ? null : "changed",
|
|
3348
|
+
skills: diffNames(node.skills, preset.skills),
|
|
3349
|
+
agents: diffNames(node.agents ?? [], preset.agents ?? [])
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
function computeUpgradeStatus(node, latest) {
|
|
3353
|
+
const source = node.sourcePreset;
|
|
3354
|
+
if (!source) return { status: "up-to-date", localModified: false };
|
|
3355
|
+
const agentsModified = !sameNames(node.agents ?? [], source.agents ?? []);
|
|
3356
|
+
const localModified = computeNodeContentHash(node) !== source.contentHash || agentsModified;
|
|
3357
|
+
if (!latest) return { status: "source-missing", localModified };
|
|
3358
|
+
if (latest.enabled === false) return { status: "source-disabled", localModified };
|
|
3359
|
+
if (localModified) return { status: "local-modified", localModified };
|
|
3360
|
+
return latest.version !== source.version ? { status: "updatable", localModified: false } : { status: "up-to-date", localModified: false };
|
|
3361
|
+
}
|
|
3362
|
+
function buildUpgradePlan(template, presetsByCode) {
|
|
3363
|
+
const items = [];
|
|
3364
|
+
for (const node of template.nodes) {
|
|
3365
|
+
const source = node.sourcePreset;
|
|
3366
|
+
if (!source) continue;
|
|
3367
|
+
const latest = presetsByCode.get(source.code) ?? null;
|
|
3368
|
+
const { status, localModified } = computeUpgradeStatus(node, latest);
|
|
3369
|
+
items.push({
|
|
3370
|
+
nodeId: node.id,
|
|
3371
|
+
source: { code: source.code, version: source.version, contentHash: source.contentHash },
|
|
3372
|
+
latest: latest ? {
|
|
3373
|
+
version: latest.version,
|
|
3374
|
+
contentHash: computeNodeContentHash(presetContent(latest)),
|
|
3375
|
+
enabled: latest.enabled ?? true
|
|
3376
|
+
} : null,
|
|
3377
|
+
status,
|
|
3378
|
+
localModified,
|
|
3379
|
+
diff: latest ? diffNodeAgainstPreset(node, latest) : EMPTY_DIFF
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
return {
|
|
3383
|
+
templateVersion: template.version,
|
|
3384
|
+
items,
|
|
3385
|
+
upgradableCount: items.filter((item) => item.status === "updatable").length
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function applyUpgradeDecisions(template, presetsByCode, decisions) {
|
|
3389
|
+
const sourceNodeIds = new Set(template.nodes.filter((n) => n.sourcePreset).map((n) => n.id));
|
|
3390
|
+
const decisionMap = /* @__PURE__ */ new Map();
|
|
3391
|
+
const unknown = [];
|
|
3392
|
+
const duplicated = [];
|
|
3393
|
+
for (const decision of decisions) {
|
|
3394
|
+
if (decisionMap.has(decision.nodeId)) duplicated.push(decision.nodeId);
|
|
3395
|
+
if (!sourceNodeIds.has(decision.nodeId)) unknown.push(decision.nodeId);
|
|
3396
|
+
decisionMap.set(decision.nodeId, decision);
|
|
3397
|
+
}
|
|
3398
|
+
if (duplicated.length > 0) {
|
|
3399
|
+
throw new ValidationError(
|
|
3400
|
+
`\u51B3\u7B56\u542B\u91CD\u590D\u8282\u70B9 id\uFF1A${duplicated.join(", ")}`,
|
|
3401
|
+
duplicated.map((id) => ({ path: ["decisions"], message: `\u8282\u70B9 ${id} \u5B58\u5728\u591A\u6761\u51B3\u7B56` }))
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
if (unknown.length > 0) {
|
|
3405
|
+
throw new ValidationError(
|
|
3406
|
+
`\u51B3\u7B56\u542B\u6A21\u677F\u5916\u6216\u65E0\u6765\u6E90\u6807\u8BB0\u7684\u8282\u70B9 id\uFF1A${unknown.join(", ")}`,
|
|
3407
|
+
unknown.map((id) => ({ path: ["decisions"], message: `\u8282\u70B9 ${id} \u4E0D\u5728\u53EF\u5347\u7EA7\u8282\u70B9\u6E05\u5355\u4E2D` }))
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
const nodes = template.nodes.map((node) => ({
|
|
3411
|
+
...node,
|
|
3412
|
+
skills: [...node.skills],
|
|
3413
|
+
agents: [...node.agents ?? []],
|
|
3414
|
+
...node.sourcePreset ? {
|
|
3415
|
+
sourcePreset: {
|
|
3416
|
+
...node.sourcePreset,
|
|
3417
|
+
...node.sourcePreset.agents ? { agents: [...node.sourcePreset.agents] } : {}
|
|
3418
|
+
}
|
|
3419
|
+
} : {}
|
|
3420
|
+
}));
|
|
3421
|
+
const results = [];
|
|
3422
|
+
const stale = [];
|
|
3423
|
+
let changed = false;
|
|
3424
|
+
for (let i = 0; i < template.nodes.length; i++) {
|
|
3425
|
+
const original = template.nodes[i];
|
|
3426
|
+
const source = original.sourcePreset;
|
|
3427
|
+
if (!source) continue;
|
|
3428
|
+
const working = nodes[i];
|
|
3429
|
+
const latest = presetsByCode.get(source.code) ?? null;
|
|
3430
|
+
const { status, localModified } = computeUpgradeStatus(original, latest);
|
|
3431
|
+
const decision = decisionMap.get(original.id);
|
|
3432
|
+
if (localModified && !decision) {
|
|
3433
|
+
throw new ValidationError(
|
|
3434
|
+
`\u8282\u70B9 ${original.id} \u672C\u5730\u5DF2\u4FEE\u6539\uFF08\u72B6\u6001 ${status}\uFF09\uFF0C\u5FC5\u987B\u663E\u5F0F\u51B3\u7B56\uFF08upgrade/keep/skip\uFF09`,
|
|
3435
|
+
[{ path: ["decisions"], message: `\u8282\u70B9 ${original.id} \u7F3A\u5C11\u51B3\u7B56` }],
|
|
3436
|
+
{
|
|
3437
|
+
bizCode: "UPGRADE_DECISION_REQUIRED",
|
|
3438
|
+
message: `${BIZ_CODE_MESSAGES.UPGRADE_DECISION_REQUIRED}\uFF1A${original.id}`
|
|
3439
|
+
}
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
if (localModified && decision?.action === "upgrade" && decision.expected === void 0) {
|
|
3443
|
+
throw new ValidationError(
|
|
3444
|
+
`\u8282\u70B9 ${original.id} \u672C\u5730\u5DF2\u4FEE\u6539\uFF0C\u8986\u76D6\u6027 upgrade \u51B3\u7B56\u5FC5\u987B\u643A\u5E26 expected={status:'local-modified'}\uFF08\u91CD\u65B0 plan \u540E\u5E26 expected \u91CD\u8BD5\uFF09`,
|
|
3445
|
+
[{ path: ["decisions"], message: `\u8282\u70B9 ${original.id} \u51B3\u7B56\u7F3A expected \u57FA\u7EBF` }],
|
|
3446
|
+
{
|
|
3447
|
+
bizCode: "UPGRADE_STALE",
|
|
3448
|
+
message: `${BIZ_CODE_MESSAGES.UPGRADE_STALE}\uFF08${original.id} \u51B3\u7B56\u7F3A expected\uFF09`
|
|
3449
|
+
}
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
if (!decision) {
|
|
3453
|
+
results.push({ nodeId: original.id, action: "skip", applied: false, reason: "\u672A\u51B3\u7B56\uFF08\u4FDD\u5B88\u8DF3\u8FC7\uFF09" });
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
if (decision.expected && decision.expected.status !== status) {
|
|
3457
|
+
stale.push(`${original.id}: ${decision.expected.status} \u2192 ${status}`);
|
|
3458
|
+
}
|
|
3459
|
+
if (status === "source-missing" || status === "source-disabled") {
|
|
3460
|
+
results.push({
|
|
3461
|
+
nodeId: original.id,
|
|
3462
|
+
action: "skip",
|
|
3463
|
+
applied: false,
|
|
3464
|
+
reason: status === "source-missing" ? "\u6765\u6E90\u8282\u70B9\u7F3A\u5931\uFF0C\u6309\u7F3A\u7701 skip \u5904\u7406" : "\u6765\u6E90\u8282\u70B9\u5DF2\u505C\u7528\uFF0C\u6309\u7F3A\u7701 skip \u5904\u7406"
|
|
3465
|
+
});
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
if (!latest) continue;
|
|
3469
|
+
if (status === "up-to-date") {
|
|
3470
|
+
results.push({
|
|
3471
|
+
nodeId: original.id,
|
|
3472
|
+
action: decision.action,
|
|
3473
|
+
applied: false,
|
|
3474
|
+
reason: "\u5DF2\u662F\u6700\u65B0\uFF08\u65E0\u53D8\u66F4\uFF09"
|
|
3475
|
+
});
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
if (decision.action === "skip") {
|
|
3479
|
+
results.push({ nodeId: original.id, action: "skip", applied: false });
|
|
3480
|
+
continue;
|
|
3481
|
+
}
|
|
3482
|
+
if (decision.action === "upgrade") {
|
|
3483
|
+
working.label = latest.label;
|
|
3484
|
+
working.phase = latest.phase;
|
|
3485
|
+
working.track = latest.track;
|
|
3486
|
+
working.prompt = latest.prompt;
|
|
3487
|
+
working.skills = [...latest.skills];
|
|
3488
|
+
working.agents = [...latest.agents ?? []];
|
|
3489
|
+
working.sourcePreset = {
|
|
3490
|
+
code: source.code,
|
|
3491
|
+
version: latest.version,
|
|
3492
|
+
contentHash: computeNodeContentHash(presetContent(latest)),
|
|
3493
|
+
agents: [...latest.agents ?? []]
|
|
3494
|
+
};
|
|
3495
|
+
changed = true;
|
|
3496
|
+
results.push({ nodeId: original.id, action: "upgrade", applied: true });
|
|
3497
|
+
} else {
|
|
3498
|
+
working.sourcePreset = {
|
|
3499
|
+
code: source.code,
|
|
3500
|
+
version: latest.version,
|
|
3501
|
+
contentHash: computeNodeContentHash(original),
|
|
3502
|
+
agents: [...original.agents ?? []]
|
|
3503
|
+
};
|
|
3504
|
+
changed = true;
|
|
3505
|
+
results.push({ nodeId: original.id, action: "keep", applied: true });
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
if (stale.length > 0) {
|
|
3509
|
+
throw new ValidationError(
|
|
3510
|
+
`\u6A21\u677F\u72B6\u6001\u4E0E\u5347\u7EA7\u8BA1\u5212\u4E0D\u7B26\uFF08plan \u540E\u5DF2\u88AB\u4FEE\u6539\uFF09\uFF0C\u8BF7\u91CD\u65B0 plan\u3002\u53D8\u5316\uFF1A${stale.join("; ")}`,
|
|
3511
|
+
stale.map((s) => ({ path: ["decisions"], message: s })),
|
|
3512
|
+
{
|
|
3513
|
+
bizCode: "UPGRADE_STALE",
|
|
3514
|
+
message: `${BIZ_CODE_MESSAGES.UPGRADE_STALE}\uFF08${stale.join("; ")}\uFF09`
|
|
3515
|
+
}
|
|
3516
|
+
);
|
|
3517
|
+
}
|
|
3518
|
+
return { nodes, nextVersion: changed ? bumpPatch(template.version) : null, results };
|
|
3519
|
+
}
|
|
1955
3520
|
|
|
1956
3521
|
// src/task/entry-id.ts
|
|
1957
3522
|
var ENTRY_ID_LENGTH = 6;
|
|
@@ -1968,8 +3533,430 @@ function generateEntryId(existing) {
|
|
|
1968
3533
|
throw new Error(`entry id generation failed after 32 attempts (existing: ${existing.length})`);
|
|
1969
3534
|
}
|
|
1970
3535
|
|
|
3536
|
+
// src/template-transfer/payload.ts
|
|
3537
|
+
function toTemplatePayload(tpl) {
|
|
3538
|
+
const { id: _id, projectId: _projectId, enabled: _enabled, createdAt: _ca, updatedAt: _ua, code: _code, ...rest } = tpl;
|
|
3539
|
+
return { ...rest };
|
|
3540
|
+
}
|
|
3541
|
+
function toSkillPayload(skill) {
|
|
3542
|
+
const { id: _id, projectId: _projectId, enabled: _enabled, createdAt: _ca, updatedAt: _ua, references, ...rest } = skill;
|
|
3543
|
+
return { ...rest, references: references ?? [] };
|
|
3544
|
+
}
|
|
3545
|
+
function toAgentPayload(agent) {
|
|
3546
|
+
const { id: _id, projectId: _projectId, enabled: _enabled, createdAt: _ca, updatedAt: _ua, references, ...rest } = agent;
|
|
3547
|
+
return { ...rest, references: references ?? [] };
|
|
3548
|
+
}
|
|
3549
|
+
function toModelAliasPayload(alias) {
|
|
3550
|
+
const { id: _id, createdAt: _ca, updatedAt: _ua, ...rest } = alias;
|
|
3551
|
+
return { ...rest };
|
|
3552
|
+
}
|
|
3553
|
+
function diffReferences(payload, entity) {
|
|
3554
|
+
const a = payload ?? [];
|
|
3555
|
+
const b = entity ?? [];
|
|
3556
|
+
return a.length === b.length && a.every((doc, i) => doc.path === b[i]?.path && doc.content === b[i]?.content);
|
|
3557
|
+
}
|
|
3558
|
+
function diffSkillPayload(payload, entity) {
|
|
3559
|
+
const diffs = [];
|
|
3560
|
+
if (entity.description !== payload.description) diffs.push("description");
|
|
3561
|
+
if (entity.content !== payload.content) diffs.push("content");
|
|
3562
|
+
if (entity.category !== payload.category) diffs.push("category");
|
|
3563
|
+
if (entity.version !== payload.version) diffs.push("version");
|
|
3564
|
+
if (!diffReferences(payload.references, entity.references)) diffs.push("references");
|
|
3565
|
+
return diffs;
|
|
3566
|
+
}
|
|
3567
|
+
function diffAgentPayload(payload, entity) {
|
|
3568
|
+
const diffs = [];
|
|
3569
|
+
if (entity.description !== payload.description) diffs.push("description");
|
|
3570
|
+
if (entity.systemPrompt !== payload.systemPrompt) diffs.push("systemPrompt");
|
|
3571
|
+
if (entity.model !== payload.model) diffs.push("model");
|
|
3572
|
+
if (entity.version !== payload.version) diffs.push("version");
|
|
3573
|
+
if (entity.function !== payload.function) diffs.push("function");
|
|
3574
|
+
const boundA = entity.boundSkills ?? [];
|
|
3575
|
+
if (boundA.length !== payload.boundSkills.length || boundA.some((s, i) => s !== payload.boundSkills[i])) diffs.push("boundSkills");
|
|
3576
|
+
const toolsA = entity.tools ?? [];
|
|
3577
|
+
if (toolsA.length !== payload.tools.length || toolsA.some((t, i) => t !== payload.tools[i])) diffs.push("tools");
|
|
3578
|
+
const permsA = entity.permissions ?? [];
|
|
3579
|
+
if (permsA.length !== payload.permissions.length || permsA.some((p, i) => p !== payload.permissions[i])) diffs.push("permissions");
|
|
3580
|
+
if (!diffReferences(payload.references, entity.references)) diffs.push("references");
|
|
3581
|
+
return diffs;
|
|
3582
|
+
}
|
|
3583
|
+
function diffModelAliasPayload(payload, entity) {
|
|
3584
|
+
const diffs = [];
|
|
3585
|
+
if (entity.name !== payload.name) diffs.push("name");
|
|
3586
|
+
if (entity.realModel !== payload.realModel) diffs.push("realModel");
|
|
3587
|
+
return diffs;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
// src/template-transfer/export-compose.ts
|
|
3591
|
+
async function composeExportBundle(deps, templateId) {
|
|
3592
|
+
const tpl = await deps.templateRepo.getById(templateId);
|
|
3593
|
+
if (!tpl) throw new NotFoundError("dag-template", templateId);
|
|
3594
|
+
const warnings = [];
|
|
3595
|
+
const directSkillNames = [...new Set(tpl.nodes.flatMap((node) => node.skills))];
|
|
3596
|
+
const [globalAgents, projectAgents] = await Promise.all([
|
|
3597
|
+
deps.agentRepo.listAgentsByScopeFilter("global", void 0, { includeDisabled: true }),
|
|
3598
|
+
deps.agentRepo.listAgentsByScopeFilter("project", tpl.projectId, { includeDisabled: true })
|
|
3599
|
+
]);
|
|
3600
|
+
const directSet = new Set(directSkillNames);
|
|
3601
|
+
const agents = [...globalAgents, ...projectAgents].filter(
|
|
3602
|
+
(agent) => (agent.boundSkills ?? []).some((name) => directSet.has(name))
|
|
3603
|
+
);
|
|
3604
|
+
const allSkillNames = [.../* @__PURE__ */ new Set([...directSkillNames, ...agents.flatMap((a) => a.boundSkills ?? [])])];
|
|
3605
|
+
const skillMap = await deps.skillRepo.listSkillsByNames(allSkillNames, "project", tpl.projectId);
|
|
3606
|
+
for (const name of allSkillNames) {
|
|
3607
|
+
if (!skillMap.has(name)) {
|
|
3608
|
+
warnings.push(`SKILL "${name}" \u5728\u6E90\u73AF\u5883\u4E2D\u4E0D\u5B58\u5728\uFF08\u7ED1\u5B9A\u60AC\u7A7A\uFF09\uFF0C\u672A\u5305\u542B\u5728\u5BFC\u51FA\u4F9D\u8D56\u4E2D`);
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
const modelCodes = [...new Set(agents.map((a) => a.model))];
|
|
3612
|
+
const modelAliases = [];
|
|
3613
|
+
for (const code of modelCodes) {
|
|
3614
|
+
const alias = await deps.modelAliasRepo.getByCode(code);
|
|
3615
|
+
if (alias) {
|
|
3616
|
+
modelAliases.push(alias);
|
|
3617
|
+
} else {
|
|
3618
|
+
warnings.push(`\u6A21\u578B\u6620\u5C04 "${code}" \u5728\u6E90\u73AF\u5883\u4E2D\u4E0D\u5B58\u5728\uFF08agent.model \u60AC\u7A7A\uFF09\uFF0C\u672A\u5305\u542B\u5728\u5BFC\u51FA\u4F9D\u8D56\u4E2D`);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
return {
|
|
3622
|
+
exportFormatVersion: EXPORT_FORMAT_VERSION,
|
|
3623
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3624
|
+
warnings,
|
|
3625
|
+
template: toTemplatePayload(tpl),
|
|
3626
|
+
dependencies: {
|
|
3627
|
+
skills: [...skillMap.values()].map(toSkillPayload),
|
|
3628
|
+
agents: agents.map(toAgentPayload),
|
|
3629
|
+
modelAliases: modelAliases.map(toModelAliasPayload)
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
// src/template-transfer/import-plan.ts
|
|
3635
|
+
function statusOf(diffs) {
|
|
3636
|
+
if (diffs === null) return { status: "create" };
|
|
3637
|
+
if (diffs.length === 0) return { status: "identical" };
|
|
3638
|
+
return { status: "conflict", changeSummary: diffs.join(", ") };
|
|
3639
|
+
}
|
|
3640
|
+
async function hasProjectDefault(deps, targetProjectId) {
|
|
3641
|
+
const hit = await deps.templateRepo._collection.findOne(
|
|
3642
|
+
{ projectId: targetProjectId, isDefault: true },
|
|
3643
|
+
{ projection: { _id: 1 } }
|
|
3644
|
+
);
|
|
3645
|
+
return hit !== null;
|
|
3646
|
+
}
|
|
3647
|
+
async function findTemplateByName(deps, targetProjectId, name) {
|
|
3648
|
+
const rows = await deps.templateRepo.listDagTemplates({ projectId: targetProjectId, name, includeDisabled: true });
|
|
3649
|
+
const row = rows[0];
|
|
3650
|
+
return row && row.id ? { id: row.id, version: row.version } : null;
|
|
3651
|
+
}
|
|
3652
|
+
async function buildImportPlan(deps, targetProjectId, bundle) {
|
|
3653
|
+
const warnings = [];
|
|
3654
|
+
const existing = await findTemplateByName(deps, targetProjectId, bundle.template.name);
|
|
3655
|
+
const projectHasDefault = await hasProjectDefault(deps, targetProjectId);
|
|
3656
|
+
const isDefaultEffect = bundle.template.isDefault && !projectHasDefault;
|
|
3657
|
+
const skillChecks = [];
|
|
3658
|
+
for (const payload of bundle.dependencies.skills) {
|
|
3659
|
+
const entity = await deps.skillRepo.getByNameScoped(
|
|
3660
|
+
payload.name,
|
|
3661
|
+
payload.scope,
|
|
3662
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3663
|
+
{ includeDisabled: true }
|
|
3664
|
+
);
|
|
3665
|
+
const { status, changeSummary } = statusOf(entity ? diffSkillPayload(payload, entity) : null);
|
|
3666
|
+
skillChecks.push({
|
|
3667
|
+
kind: "skill",
|
|
3668
|
+
name: payload.name,
|
|
3669
|
+
scope: payload.scope,
|
|
3670
|
+
status,
|
|
3671
|
+
...entity ? { currentVersion: entity.version } : {},
|
|
3672
|
+
importVersion: payload.version,
|
|
3673
|
+
...changeSummary ? { changeSummary } : {},
|
|
3674
|
+
impact: payload.scope
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
const agentChecks = [];
|
|
3678
|
+
for (const payload of bundle.dependencies.agents) {
|
|
3679
|
+
const entity = await deps.agentRepo.getByNameScoped(
|
|
3680
|
+
payload.name,
|
|
3681
|
+
payload.scope,
|
|
3682
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3683
|
+
{ includeDisabled: true }
|
|
3684
|
+
);
|
|
3685
|
+
const { status, changeSummary } = statusOf(entity ? diffAgentPayload(payload, entity) : null);
|
|
3686
|
+
agentChecks.push({
|
|
3687
|
+
kind: "agent",
|
|
3688
|
+
name: payload.name,
|
|
3689
|
+
scope: payload.scope,
|
|
3690
|
+
status,
|
|
3691
|
+
...entity ? { currentVersion: entity.version } : {},
|
|
3692
|
+
importVersion: payload.version,
|
|
3693
|
+
...changeSummary ? { changeSummary } : {},
|
|
3694
|
+
impact: payload.scope
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
const aliasChecks = [];
|
|
3698
|
+
for (const payload of bundle.dependencies.modelAliases) {
|
|
3699
|
+
const entity = await deps.modelAliasRepo.getByCode(payload.code);
|
|
3700
|
+
const { status, changeSummary } = statusOf(entity ? diffModelAliasPayload(payload, entity) : null);
|
|
3701
|
+
aliasChecks.push({
|
|
3702
|
+
kind: "modelAlias",
|
|
3703
|
+
name: payload.code,
|
|
3704
|
+
status,
|
|
3705
|
+
...changeSummary ? { changeSummary } : {},
|
|
3706
|
+
// 模型映射无 version 概念(内容即 name/realModel 二元组)
|
|
3707
|
+
impact: "global"
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
const bundledCodes = new Set(bundle.dependencies.modelAliases.map((a) => a.code));
|
|
3711
|
+
for (const agent of bundle.dependencies.agents) {
|
|
3712
|
+
if (bundledCodes.has(agent.model)) continue;
|
|
3713
|
+
const alias = await deps.modelAliasRepo.getByCode(agent.model);
|
|
3714
|
+
if (!alias) {
|
|
3715
|
+
warnings.push(`AGENT "${agent.name}" \u5F15\u7528\u7684\u6A21\u578B\u6620\u5C04 "${agent.model}" \u4E0D\u5728\u5BFC\u51FA\u5305\u4E2D\u4E14\u76EE\u6807\u73AF\u5883\u4E0D\u5B58\u5728\uFF0C\u5BFC\u5165\u540E\u8BE5\u5F15\u7528\u5C06\u60AC\u7A7A`);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
const bundledSkillNames = new Set(bundle.dependencies.skills.map((s) => s.name));
|
|
3719
|
+
for (const agent of bundle.dependencies.agents) {
|
|
3720
|
+
for (const skillName of agent.boundSkills) {
|
|
3721
|
+
if (bundledSkillNames.has(skillName)) continue;
|
|
3722
|
+
const globalHit = await deps.skillRepo.getByNameScoped(skillName, "global", void 0, { includeDisabled: true });
|
|
3723
|
+
const projectHit = agent.scope === "project" ? await deps.skillRepo.getByNameScoped(skillName, "project", targetProjectId, { includeDisabled: true }) : null;
|
|
3724
|
+
if (!globalHit && !projectHit) {
|
|
3725
|
+
warnings.push(`AGENT "${agent.name}" \u7ED1\u5B9A\u7684 SKILL "${skillName}" \u4E0D\u5728\u5BFC\u51FA\u5305\u4E2D\u4E14\u76EE\u6807\u73AF\u5883\u4E0D\u5B58\u5728\uFF0C\u5BFC\u5165\u540E\u8BE5\u7ED1\u5B9A\u5C06\u60AC\u7A7A`);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
const directSkillChecks = [];
|
|
3730
|
+
const unbundledNames = [...new Set(bundle.template.nodes.flatMap((n) => n.skills).filter((s) => !bundledSkillNames.has(s)))];
|
|
3731
|
+
const envVisibleSkills = unbundledNames.length > 0 ? await deps.skillRepo.listSkillsByNames(unbundledNames, "project", targetProjectId) : /* @__PURE__ */ new Map();
|
|
3732
|
+
for (const node of bundle.template.nodes) {
|
|
3733
|
+
for (const skillName of node.skills) {
|
|
3734
|
+
if (bundledSkillNames.has(skillName) || envVisibleSkills.has(skillName)) {
|
|
3735
|
+
directSkillChecks.push({ node: node.id, skill: skillName, status: "ok" });
|
|
3736
|
+
} else {
|
|
3737
|
+
directSkillChecks.push({ node: node.id, skill: skillName, status: "missing" });
|
|
3738
|
+
warnings.push(`\u6A21\u677F\u8282\u70B9 ${node.id} \u76F4\u63A5\u5F15\u7528\u6280\u80FD "${skillName}" \u4E0D\u5728\u5BFC\u51FA\u5305\u4E2D\u4E14\u76EE\u6807\u73AF\u5883\u4E0D\u5B58\u5728\uFF08\u7ED1\u5B9A\u60AC\u7A7A\uFF09`);
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
const presetCodes = [...new Set(bundle.template.nodes.flatMap((n) => n.sourcePreset ? [n.sourcePreset.code] : []))];
|
|
3743
|
+
const presetsByCode = presetCodes.length > 0 ? await deps.nodePresetRepo.listByCodes(presetCodes) : /* @__PURE__ */ new Map();
|
|
3744
|
+
for (const node of bundle.template.nodes) {
|
|
3745
|
+
if (node.sourcePreset && !presetsByCode.has(node.sourcePreset.code)) {
|
|
3746
|
+
warnings.push(`\u6A21\u677F\u8282\u70B9 ${node.id} \u7684\u6765\u6E90\u8282\u70B9 "${node.sourcePreset.code}" \u5728\u76EE\u6807\u73AF\u5883\u4E0D\u5B58\u5728\uFF0C\u5BFC\u5165\u5C06\u5265\u9664\u6765\u6E90\u6807\u8BB0\uFF08\u964D\u7EA7\u81EA\u5EFA\u8282\u70B9\uFF09`);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
return {
|
|
3750
|
+
template: {
|
|
3751
|
+
name: bundle.template.name,
|
|
3752
|
+
willOverwrite: existing !== null,
|
|
3753
|
+
...existing ? { currentVersion: existing.version } : {},
|
|
3754
|
+
importVersion: bundle.template.version,
|
|
3755
|
+
isDefaultRequested: bundle.template.isDefault,
|
|
3756
|
+
isDefaultEffect,
|
|
3757
|
+
...bundle.template.isDefault && !isDefaultEffect ? { downgradeReason: "project_has_default" } : {}
|
|
3758
|
+
},
|
|
3759
|
+
dependencies: [...skillChecks, ...agentChecks, ...aliasChecks],
|
|
3760
|
+
directSkillChecks,
|
|
3761
|
+
warnings
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// src/template-transfer/import-apply.ts
|
|
3766
|
+
async function applyImport(deps, targetProjectId, bundle, decisions) {
|
|
3767
|
+
const decisionMap = new Map(decisions.map((d) => [decisionKey(d.kind, "scope" in d ? d.scope : void 0, d.name), d.action]));
|
|
3768
|
+
const existing = await findTemplateByName(deps, targetProjectId, bundle.template.name);
|
|
3769
|
+
const projectHasDefault = await hasProjectDefault(deps, targetProjectId);
|
|
3770
|
+
const isDefaultEffect = bundle.template.isDefault && !projectHasDefault;
|
|
3771
|
+
const tplPayload = bundle.template;
|
|
3772
|
+
const sourceCodes = [...new Set(tplPayload.nodes.flatMap((n) => n.sourcePreset ? [n.sourcePreset.code] : []))];
|
|
3773
|
+
const presetsByCode = sourceCodes.length > 0 ? await deps.nodePresetRepo.listByCodes(sourceCodes) : /* @__PURE__ */ new Map();
|
|
3774
|
+
const sourceStripped = [];
|
|
3775
|
+
const nodesForWrite = tplPayload.nodes.map((node) => {
|
|
3776
|
+
if (node.sourcePreset && !presetsByCode.has(node.sourcePreset.code)) {
|
|
3777
|
+
sourceStripped.push(node.id);
|
|
3778
|
+
const { sourcePreset: _stripped, ...rest } = node;
|
|
3779
|
+
return rest;
|
|
3780
|
+
}
|
|
3781
|
+
return node;
|
|
3782
|
+
});
|
|
3783
|
+
const createInput = { ...tplPayload, projectId: targetProjectId, isDefault: isDefaultEffect, nodes: nodesForWrite };
|
|
3784
|
+
const parsed = DagTemplateCreateSchema.safeParse(createInput);
|
|
3785
|
+
if (!parsed.success) {
|
|
3786
|
+
throw new ValidationError("\u6A21\u677F\u5185\u5BB9\u6821\u9A8C\u5931\u8D25\uFF08\u5BFC\u5165\u6587\u4EF6\u5B57\u6BB5\u975E\u6CD5\uFF09", parsed.error.issues);
|
|
3787
|
+
}
|
|
3788
|
+
assertUniqueNodeIds(nodesForWrite);
|
|
3789
|
+
const contentFields = {
|
|
3790
|
+
name: tplPayload.name,
|
|
3791
|
+
description: tplPayload.description,
|
|
3792
|
+
nodes: nodesForWrite,
|
|
3793
|
+
edges: tplPayload.edges,
|
|
3794
|
+
version: tplPayload.version,
|
|
3795
|
+
isDefault: isDefaultEffect,
|
|
3796
|
+
layout: tplPayload.layout ?? {}
|
|
3797
|
+
};
|
|
3798
|
+
let templateResult;
|
|
3799
|
+
if (existing) {
|
|
3800
|
+
const updated = await deps.templateRepo.update(existing.id, contentFields);
|
|
3801
|
+
if (!updated || !updated.id) throw new NotFoundError("dag-template", existing.id);
|
|
3802
|
+
templateResult = { id: updated.id, name: updated.name, action: "updated", isDefaultEffect };
|
|
3803
|
+
} else {
|
|
3804
|
+
const raced = await findTemplateByName(deps, targetProjectId, tplPayload.name);
|
|
3805
|
+
if (raced) {
|
|
3806
|
+
throw new ConflictError("dag-template", `name conflict: ${tplPayload.name}`, {
|
|
3807
|
+
bizCode: "TEMPLATE_NAME_CONFLICT",
|
|
3808
|
+
message: BIZ_CODE_MESSAGES.TEMPLATE_NAME_CONFLICT
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
const created = await deps.templateRepo.createDagTemplate({ ...createInput, layout: tplPayload.layout ?? {} });
|
|
3812
|
+
if (!created.id) throw new Error("\u6A21\u677F\u521B\u5EFA\u8FD4\u56DE\u5B9E\u4F53\u7F3A\u5931 id\uFF08\u4E0D\u53EF\u8FBE\u9632\u5FA1\u5206\u652F\uFF09");
|
|
3813
|
+
templateResult = { id: created.id, name: created.name, action: "created", isDefaultEffect };
|
|
3814
|
+
}
|
|
3815
|
+
const results = [];
|
|
3816
|
+
for (const payload of bundle.dependencies.skills) {
|
|
3817
|
+
try {
|
|
3818
|
+
const key = decisionKey("skill", payload.scope, payload.name);
|
|
3819
|
+
const existingSkill = await deps.skillRepo.getByNameScoped(
|
|
3820
|
+
payload.name,
|
|
3821
|
+
payload.scope,
|
|
3822
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3823
|
+
{ includeDisabled: true }
|
|
3824
|
+
);
|
|
3825
|
+
const diffs = existingSkill ? diffSkillPayload(payload, existingSkill) : null;
|
|
3826
|
+
const status = diffs === null ? "create" : diffs.length === 0 ? "identical" : "conflict";
|
|
3827
|
+
const action = decide(status, decisionMap.get(key));
|
|
3828
|
+
if (action === "identical") {
|
|
3829
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "identical" });
|
|
3830
|
+
continue;
|
|
3831
|
+
}
|
|
3832
|
+
if (action === "skipped") {
|
|
3833
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "skipped" });
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3836
|
+
const createData = { ...payload, ...payload.scope === "project" ? { projectId: targetProjectId } : {} };
|
|
3837
|
+
const check = SkillCreateSchema.safeParse(createData);
|
|
3838
|
+
if (!check.success) {
|
|
3839
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "failed", message: summarizeIssues(check.error.issues) });
|
|
3840
|
+
continue;
|
|
3841
|
+
}
|
|
3842
|
+
if (existingSkill && existingSkill.id) {
|
|
3843
|
+
await deps.skillRepo.updateByNameScoped(
|
|
3844
|
+
payload.name,
|
|
3845
|
+
payload.scope,
|
|
3846
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3847
|
+
{
|
|
3848
|
+
description: payload.description,
|
|
3849
|
+
content: payload.content,
|
|
3850
|
+
category: payload.category,
|
|
3851
|
+
version: payload.version,
|
|
3852
|
+
references: payload.references
|
|
3853
|
+
}
|
|
3854
|
+
);
|
|
3855
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "updated" });
|
|
3856
|
+
} else {
|
|
3857
|
+
await deps.skillRepo.createSkill(check.data);
|
|
3858
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "created" });
|
|
3859
|
+
}
|
|
3860
|
+
} catch (err) {
|
|
3861
|
+
results.push({ kind: "skill", name: payload.name, scope: payload.scope, action: "failed", message: errorMessage(err) });
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
for (const payload of bundle.dependencies.agents) {
|
|
3865
|
+
try {
|
|
3866
|
+
const key = decisionKey("agent", payload.scope, payload.name);
|
|
3867
|
+
const existingAgent = await deps.agentRepo.getByNameScoped(
|
|
3868
|
+
payload.name,
|
|
3869
|
+
payload.scope,
|
|
3870
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3871
|
+
{ includeDisabled: true }
|
|
3872
|
+
);
|
|
3873
|
+
const diffs = existingAgent ? diffAgentPayload(payload, existingAgent) : null;
|
|
3874
|
+
const status = diffs === null ? "create" : diffs.length === 0 ? "identical" : "conflict";
|
|
3875
|
+
const action = decide(status, decisionMap.get(key));
|
|
3876
|
+
if (action === "identical" || action === "skipped") {
|
|
3877
|
+
results.push({ kind: "agent", name: payload.name, scope: payload.scope, action });
|
|
3878
|
+
continue;
|
|
3879
|
+
}
|
|
3880
|
+
const createData = { ...payload, ...payload.scope === "project" ? { projectId: targetProjectId } : {} };
|
|
3881
|
+
const check = AgentCreateSchema.safeParse(createData);
|
|
3882
|
+
if (!check.success) {
|
|
3883
|
+
results.push({ kind: "agent", name: payload.name, scope: payload.scope, action: "failed", message: summarizeIssues(check.error.issues) });
|
|
3884
|
+
continue;
|
|
3885
|
+
}
|
|
3886
|
+
await assertBoundSkillsCompatible(
|
|
3887
|
+
deps.skillRepo,
|
|
3888
|
+
payload.scope,
|
|
3889
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3890
|
+
payload.boundSkills
|
|
3891
|
+
);
|
|
3892
|
+
if (existingAgent && existingAgent.id) {
|
|
3893
|
+
await deps.agentRepo.updateByNameScoped(
|
|
3894
|
+
payload.name,
|
|
3895
|
+
payload.scope,
|
|
3896
|
+
payload.scope === "project" ? targetProjectId : void 0,
|
|
3897
|
+
{
|
|
3898
|
+
description: payload.description,
|
|
3899
|
+
systemPrompt: payload.systemPrompt,
|
|
3900
|
+
boundSkills: payload.boundSkills,
|
|
3901
|
+
model: payload.model,
|
|
3902
|
+
tools: payload.tools,
|
|
3903
|
+
permissions: payload.permissions,
|
|
3904
|
+
version: payload.version,
|
|
3905
|
+
references: payload.references,
|
|
3906
|
+
"function": payload.function
|
|
3907
|
+
}
|
|
3908
|
+
);
|
|
3909
|
+
results.push({ kind: "agent", name: payload.name, scope: payload.scope, action: "updated" });
|
|
3910
|
+
} else {
|
|
3911
|
+
await deps.agentRepo.createAgent(check.data);
|
|
3912
|
+
results.push({ kind: "agent", name: payload.name, scope: payload.scope, action: "created" });
|
|
3913
|
+
}
|
|
3914
|
+
} catch (err) {
|
|
3915
|
+
results.push({ kind: "agent", name: payload.name, scope: payload.scope, action: "failed", message: errorMessage(err) });
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
for (const payload of bundle.dependencies.modelAliases) {
|
|
3919
|
+
try {
|
|
3920
|
+
const key = decisionKey("modelAlias", void 0, payload.code);
|
|
3921
|
+
const existingAlias = await deps.modelAliasRepo.getByCode(payload.code);
|
|
3922
|
+
const diffs = existingAlias ? diffModelAliasPayload(payload, existingAlias) : null;
|
|
3923
|
+
const status = diffs === null ? "create" : diffs.length === 0 ? "identical" : "conflict";
|
|
3924
|
+
const action = decide(status, decisionMap.get(key));
|
|
3925
|
+
if (action === "identical" || action === "skipped") {
|
|
3926
|
+
results.push({ kind: "modelAlias", name: payload.code, action });
|
|
3927
|
+
continue;
|
|
3928
|
+
}
|
|
3929
|
+
if (existingAlias) {
|
|
3930
|
+
await deps.modelAliasRepo.updateByCode(payload.code, { name: payload.name, realModel: payload.realModel });
|
|
3931
|
+
results.push({ kind: "modelAlias", name: payload.code, action: "updated" });
|
|
3932
|
+
} else {
|
|
3933
|
+
const createData = { code: payload.code, name: payload.name, realModel: payload.realModel };
|
|
3934
|
+
await deps.modelAliasRepo.createModelAlias(createData);
|
|
3935
|
+
results.push({ kind: "modelAlias", name: payload.code, action: "created" });
|
|
3936
|
+
}
|
|
3937
|
+
} catch (err) {
|
|
3938
|
+
results.push({ kind: "modelAlias", name: payload.code, action: "failed", message: errorMessage(err) });
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
return { template: templateResult, dependencies: results, sourceStripped };
|
|
3942
|
+
}
|
|
3943
|
+
function decide(status, decision) {
|
|
3944
|
+
if (status === "identical") return "identical";
|
|
3945
|
+
if (status === "create") return "create";
|
|
3946
|
+
return decision === "overwrite" ? "overwrite" : "skipped";
|
|
3947
|
+
}
|
|
3948
|
+
function summarizeIssues(issues) {
|
|
3949
|
+
const parts = issues.map(
|
|
3950
|
+
(issue) => `${issue.path.join(".")}: ${issue.message}`
|
|
3951
|
+
);
|
|
3952
|
+
return `\u6821\u9A8C\u5931\u8D25\uFF1A${parts.join("; ")}`;
|
|
3953
|
+
}
|
|
3954
|
+
function errorMessage(err) {
|
|
3955
|
+
return err instanceof Error ? err.message : String(err);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
1971
3958
|
// src/index.ts
|
|
1972
|
-
var PackageJsonSchema =
|
|
3959
|
+
var PackageJsonSchema = z20.object({ version: z20.string().min(1) });
|
|
1973
3960
|
var packageJsonParsed = PackageJsonSchema.safeParse(
|
|
1974
3961
|
JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"))
|
|
1975
3962
|
);
|
|
@@ -1981,28 +3968,49 @@ function ping() {
|
|
|
1981
3968
|
return "pong";
|
|
1982
3969
|
}
|
|
1983
3970
|
export {
|
|
3971
|
+
AGENT_MAIN_DOC,
|
|
1984
3972
|
ARTIFACT_TYPES,
|
|
3973
|
+
AUTH_COLLECTIONS,
|
|
1985
3974
|
AdvanceRequestSchema,
|
|
1986
3975
|
AdvanceResponseSchema,
|
|
1987
3976
|
AgentCopySchema,
|
|
1988
3977
|
AgentCreateSchema,
|
|
3978
|
+
AgentFunctionSchema,
|
|
3979
|
+
AgentPayloadSchema,
|
|
1989
3980
|
AgentSchema,
|
|
1990
3981
|
AgentScopeSchema,
|
|
3982
|
+
AgentShapeSchema,
|
|
1991
3983
|
AgentUpdateSchema,
|
|
1992
3984
|
AppError,
|
|
1993
3985
|
ApproveRequestSchema,
|
|
1994
3986
|
ApproveResponseSchema,
|
|
1995
3987
|
ArchNoteSchema,
|
|
1996
3988
|
ArtifactSchema,
|
|
3989
|
+
AssetSetEnabledSchema,
|
|
3990
|
+
AuthAccountCreateSchema,
|
|
3991
|
+
AuthAccountSchema,
|
|
3992
|
+
AuthStatusSchema,
|
|
3993
|
+
AuthTokenGenResponseSchema,
|
|
3994
|
+
AuthTokenSchema,
|
|
3995
|
+
AuthTokenStatusSchema,
|
|
3996
|
+
AuthTokenTypeSchema,
|
|
3997
|
+
AuthWhoamiSchema,
|
|
1997
3998
|
BIZ_CODE_MESSAGES,
|
|
1998
3999
|
BadRequestError,
|
|
4000
|
+
CONTEXT_VIEWS,
|
|
4001
|
+
CancelRequestSchema,
|
|
1999
4002
|
CheckItemSchema,
|
|
4003
|
+
CompositionEdgeSchema,
|
|
4004
|
+
CompositionSchema,
|
|
2000
4005
|
ConfirmationSchema,
|
|
2001
4006
|
ConflictError,
|
|
4007
|
+
ContextViewSchema,
|
|
4008
|
+
CreateTaskResponseSchema,
|
|
2002
4009
|
DAG_PHASES,
|
|
2003
4010
|
DAG_TRACKS,
|
|
2004
4011
|
DEFAULT_PROJECT_KEY,
|
|
2005
4012
|
DagEdgeSchema,
|
|
4013
|
+
DagInstanceNodeSchema,
|
|
2006
4014
|
DagInstanceSchema,
|
|
2007
4015
|
DagNodePhaseSchema,
|
|
2008
4016
|
DagNodeSchema,
|
|
@@ -2012,85 +4020,171 @@ export {
|
|
|
2012
4020
|
DagTemplateSchema,
|
|
2013
4021
|
DagTemplateUpdateSchema,
|
|
2014
4022
|
DecisionSchema,
|
|
4023
|
+
DependencyActionSchema,
|
|
4024
|
+
DependencyCheckSchema,
|
|
4025
|
+
DependencyStatusSchema,
|
|
2015
4026
|
ENTRY_ID_REGEX,
|
|
4027
|
+
EXPORT_FORMAT_VERSION,
|
|
4028
|
+
EchoItemSchema,
|
|
2016
4029
|
EdgePausePointBaseSchema,
|
|
2017
4030
|
EdgePausePointSchema,
|
|
2018
4031
|
EnumEntrySchema,
|
|
2019
4032
|
EnumRegistryCategorySchema,
|
|
2020
4033
|
EnumRegistrySchema,
|
|
2021
4034
|
EnumRegistryUpdateSchema,
|
|
4035
|
+
ExportBundleSchema,
|
|
4036
|
+
ForbiddenError,
|
|
2022
4037
|
HistoryActionSchema,
|
|
2023
4038
|
HistoryEntrySchema,
|
|
4039
|
+
ImportApplyRequestSchema,
|
|
4040
|
+
ImportApplyResponseSchema,
|
|
4041
|
+
ImportDecisionSchema,
|
|
4042
|
+
ImportPlanRequestSchema,
|
|
4043
|
+
ImportPlanResponseSchema,
|
|
4044
|
+
LoginSchema,
|
|
2024
4045
|
ModelAliasCreateSchema,
|
|
4046
|
+
ModelAliasPayloadSchema,
|
|
2025
4047
|
ModelAliasSchema,
|
|
2026
4048
|
ModelAliasShapeSchema,
|
|
2027
4049
|
ModelAliasUpdateSchema,
|
|
2028
4050
|
ModelAliasWithRefCountSchema,
|
|
2029
4051
|
NODE_ID_PATTERN,
|
|
4052
|
+
NODE_PRESET_CODE_PATTERN,
|
|
2030
4053
|
NODE_STATUSES,
|
|
2031
4054
|
NodeInfoSchema,
|
|
4055
|
+
NodeLibrarySchema,
|
|
4056
|
+
NodeLibraryUpsertSchema,
|
|
4057
|
+
NodePresetCopySchema,
|
|
4058
|
+
NodePresetCreateSchema,
|
|
4059
|
+
NodePresetSchema,
|
|
4060
|
+
NodePresetShapeSchema,
|
|
4061
|
+
NodePresetUpdateSchema,
|
|
2032
4062
|
NodeRecordSchema,
|
|
2033
4063
|
NodeStateSchema,
|
|
2034
4064
|
NodeStatusSchema,
|
|
2035
4065
|
NotFoundError,
|
|
2036
4066
|
OBJECT_ID_HEX,
|
|
2037
4067
|
PAUSE_POINT_TYPES,
|
|
4068
|
+
PasswordChangeSchema,
|
|
2038
4069
|
PausePointTypeSchema,
|
|
2039
4070
|
PauseRequestSchema,
|
|
2040
4071
|
ProjectCreateSchema,
|
|
2041
4072
|
ProjectSchema,
|
|
2042
4073
|
ProjectStatusSchema,
|
|
2043
4074
|
ProjectUpdateSchema,
|
|
4075
|
+
REFERENCE_LIMITS,
|
|
2044
4076
|
ResumeRequestSchema,
|
|
2045
4077
|
ReviewSummarySchema,
|
|
2046
4078
|
SIMING_CODE_REGEX,
|
|
2047
4079
|
SIMING_CONFIG_ENV_KEYS,
|
|
2048
4080
|
SIMING_CONFIG_KEYS,
|
|
4081
|
+
SKILL_MAIN_DOC,
|
|
2049
4082
|
SimingConfigSchema,
|
|
2050
4083
|
SimingLogLevelSchema,
|
|
2051
4084
|
SkillCopySchema,
|
|
2052
4085
|
SkillCreateSchema,
|
|
4086
|
+
SkillPayloadSchema,
|
|
2053
4087
|
SkillSchema,
|
|
2054
4088
|
SkillScopeSchema,
|
|
4089
|
+
SkillShapeSchema,
|
|
2055
4090
|
SkillUpdateSchema,
|
|
4091
|
+
SourcePresetSchema,
|
|
2056
4092
|
TASK_PHASES,
|
|
2057
4093
|
TASK_STATUSES,
|
|
2058
4094
|
TASK_TYPES,
|
|
4095
|
+
TEMPLATE_CODE_MAX_LENGTH,
|
|
4096
|
+
TEMPLATE_CODE_REGEX,
|
|
4097
|
+
TaskBatchAckSchema,
|
|
4098
|
+
TaskBatchEntrySchema,
|
|
4099
|
+
TaskBatchFailureSchema,
|
|
2059
4100
|
TaskContextSchema,
|
|
2060
4101
|
TaskCreateInputSchema,
|
|
2061
4102
|
TaskDocContentSchema,
|
|
2062
4103
|
TaskPhaseSchema,
|
|
4104
|
+
TaskProgressPublicSchema,
|
|
4105
|
+
TaskProgressSchema,
|
|
2063
4106
|
TaskPublicSchema,
|
|
2064
4107
|
TaskSchema,
|
|
2065
4108
|
TaskStatusSchema,
|
|
2066
4109
|
TaskSummarySchema,
|
|
4110
|
+
TemplateCandidateSchema,
|
|
4111
|
+
TemplateCodeFieldSchema,
|
|
4112
|
+
TemplatePayloadSchema,
|
|
4113
|
+
UnauthorizedError,
|
|
4114
|
+
UpgradeActionSchema,
|
|
4115
|
+
UpgradeApplyRequestSchema,
|
|
4116
|
+
UpgradeDecisionSchema,
|
|
4117
|
+
UpgradeNodeStatusSchema,
|
|
2067
4118
|
VERSION,
|
|
2068
4119
|
ValidationError,
|
|
4120
|
+
WRITE_ACK_ENTRY_KINDS,
|
|
4121
|
+
WriteAckEntryKindSchema,
|
|
4122
|
+
WriteAckSchema,
|
|
2069
4123
|
advanceTask,
|
|
4124
|
+
applyImport,
|
|
4125
|
+
applyUpgradeDecisions,
|
|
2070
4126
|
approveTask,
|
|
2071
4127
|
assertBoundSkillsCompatible,
|
|
2072
4128
|
assertModelAliasExists,
|
|
4129
|
+
assertReferenceAggregateLimit,
|
|
4130
|
+
assertReferencesDeletable,
|
|
4131
|
+
assertUniqueNodeIds,
|
|
4132
|
+
buildImportPlan,
|
|
4133
|
+
buildTemplateCode,
|
|
4134
|
+
buildUpgradePlan,
|
|
4135
|
+
bumpPatch,
|
|
4136
|
+
bumpPatchOrPassthrough,
|
|
4137
|
+
cancelTask,
|
|
4138
|
+
canonicalizeReferencePath,
|
|
2073
4139
|
checkTermination,
|
|
4140
|
+
composeExportBundle,
|
|
4141
|
+
computeNodeContentHash,
|
|
4142
|
+
computeUpgradeStatus,
|
|
2074
4143
|
createAgentRepo,
|
|
4144
|
+
createAuthAccountRepo,
|
|
4145
|
+
createAuthSessionRepo,
|
|
4146
|
+
createAuthTokenRepo,
|
|
2075
4147
|
createDagTemplateRepo,
|
|
2076
4148
|
createEnumRegistryRepo,
|
|
2077
4149
|
createModelAliasRepo,
|
|
2078
4150
|
createMongoClient,
|
|
4151
|
+
createNodeLibraryRepo,
|
|
4152
|
+
createNodePresetRepo,
|
|
2079
4153
|
createProjectRepo,
|
|
4154
|
+
createReferencesSchema,
|
|
2080
4155
|
createRepo,
|
|
2081
4156
|
createSkillRepo,
|
|
2082
4157
|
createTaskRepo,
|
|
4158
|
+
decisionKey,
|
|
4159
|
+
diffTemplateContent,
|
|
4160
|
+
enabledNeFalse,
|
|
4161
|
+
escapeRegExpLiteral,
|
|
4162
|
+
excerpt,
|
|
4163
|
+
findDuplicateNodeIds,
|
|
2083
4164
|
findNextEdge,
|
|
4165
|
+
findTemplateByName,
|
|
2084
4166
|
generateEntryId,
|
|
2085
4167
|
generateProjectKey,
|
|
4168
|
+
generateSessionId,
|
|
4169
|
+
generateToken,
|
|
4170
|
+
hasProjectDefault,
|
|
4171
|
+
hashPassword,
|
|
4172
|
+
hashToken,
|
|
4173
|
+
isLegalTemplateCode,
|
|
2086
4174
|
mkHistory,
|
|
4175
|
+
normalizeNameToTemplateCode,
|
|
4176
|
+
parseAuthEnabledEnvValue,
|
|
2087
4177
|
pauseTask,
|
|
2088
4178
|
ping,
|
|
2089
4179
|
pruneInstance,
|
|
2090
4180
|
renderPrompt,
|
|
2091
4181
|
resumeTask,
|
|
4182
|
+
sumReferenceContentChars,
|
|
4183
|
+
taskProgress,
|
|
2092
4184
|
toNodeInfo,
|
|
2093
4185
|
toTaskPublic,
|
|
4186
|
+
tokenTypeFromValue,
|
|
2094
4187
|
trackMatchSet,
|
|
4188
|
+
verifyPassword,
|
|
2095
4189
|
withTransaction
|
|
2096
4190
|
};
|