@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +2219 -147
  2. package/dist/index.js +2514 -420
  3. 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 z11 } from "zod";
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 { error: this.bizCode ?? this.code, message: this.message, resource: this.resource, id: this.id };
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
- scope === "project" ? { name, scope: "project", projectId } : { name, scope: "global" }
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 listSkillsByScopeFilter(scope, projectId) {
240
- return base.list(scope === "project" ? { scope: "project", projectId } : { scope: "global" });
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 base = createRepo(db.collection("agents"), { nameField: "name" });
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
- scope === "project" ? { name, scope: "project", projectId } : { name, scope: "global" }
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(scope === "project" ? { scope: "project", projectId } : { scope: "global" });
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/store/repos/dag-template.repo.ts
384
- function createDagTemplateRepo(db) {
385
- const base = createRepo(db.collection("dag_templates"));
386
- return {
387
- ...base,
388
- createDagTemplate(data) {
389
- return base.create(data);
390
- },
391
- async listDagTemplates(filter) {
392
- const query = {};
393
- if (filter?.projectId) query.projectId = filter.projectId;
394
- if (filter?.name) query.name = filter.name;
395
- return base.list(query);
396
- },
397
- async copyDagTemplate(sourceId, targetProjectId, newName) {
398
- const source = await base.getById(sourceId);
399
- if (!source) {
400
- throw new ConflictError("dagTemplate", `copy source not found: ${sourceId}`);
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
- const dup = await base._collection.findOne(
403
- { projectId: targetProjectId, name: newName },
404
- { projection: { _id: 1 } }
405
- );
406
- if (dup) {
407
- throw new ConflictError("dagTemplate", `name conflict in target project: ${newName}`);
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
- import { z } from "zod";
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 = z.object({
439
- id: z.string().optional(),
440
- name: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
441
- description: z.string(),
442
- content: z.string(),
443
- category: z.string(),
444
- version: z.string().default("1.0.0"),
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: z.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
449
- createdAt: z.date().optional(),
450
- updatedAt: z.date().optional()
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 SkillSchema = SkillShapeSchema.superRefine(SkillScopeRefine);
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
- }).extend({ scope: SkillScopeSchema }).superRefine(SkillScopeRefine);
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: z.string().optional(),
687
+ version: z2.string().optional(),
464
688
  scope: SkillScopeSchema.optional()
465
- }).partial().superRefine(SkillScopeRefine);
466
- var SkillCopySchema = z.object({
467
- newName: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
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: z.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
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
- import { z as z2 } from "zod";
482
- var DagNodePhaseSchema = z2.string();
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 DagNodeSchema = z2.object({
488
- id: z2.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"),
489
- label: z2.string(),
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: z2.string(),
493
- skills: z2.array(z2.string()).default([])
731
+ prompt: z3.string(),
732
+ skills: capabilityNamesSchema.default([]),
733
+ /** 存量模板允许缺键;新组装和任务实例化路径写入完整数组 */
734
+ agents: capabilityNamesSchema.optional(),
735
+ sourcePreset: SourcePresetSchema.optional()
494
736
  });
495
- var PausePointTypeSchema = z2.enum(["human_approval", "checkpoint"]);
737
+ var PausePointTypeSchema = z3.enum(["human_approval", "checkpoint"]);
496
738
  var PAUSE_POINT_TYPES = ["human_approval", "checkpoint"];
497
- var EdgePausePointBaseSchema = z2.object({
739
+ var EdgePausePointBaseSchema = z3.object({
498
740
  type: PausePointTypeSchema,
499
- description: z2.string(),
500
- autoResume: z2.boolean().default(false)
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 DagEdgeSchema = z2.object({
517
- from: z2.string(),
518
- to: z2.string(),
519
- condition: z2.string().optional(),
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 = z2.object({
524
- id: z2.string().optional(),
525
- name: z2.string(),
773
+ var DagTemplateSchema = z3.object({
774
+ id: z3.string().optional(),
775
+ name: z3.string(),
526
776
  /** 归属项目 id(N012:实体层去哨兵 default,由路由层解析写入——校验项目存在且 active 后落库) */
527
- projectId: z2.string(),
528
- description: z2.string(),
529
- nodes: z2.array(DagNodeSchema),
530
- edges: z2.array(DagEdgeSchema),
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: z2.record(z2.string(), z2.object({ x: z2.number(), y: z2.number() })).optional(),
533
- isDefault: z2.boolean().default(false),
534
- version: z2.string().default("1.0.0"),
535
- createdAt: z2.date().optional(),
536
- updatedAt: z2.date().optional()
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: z2.boolean().optional(),
545
- version: z2.string().optional()
808
+ isDefault: z3.boolean().optional(),
809
+ version: z3.string().optional()
546
810
  }).partial();
547
- var DagTemplateCopySchema = z2.object({
548
- newName: z2.string().min(1),
549
- targetProjectId: z2.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex")
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 = z3.object({
556
- id: z3.string().regex(ENTRY_ID_REGEX),
557
- item: z3.string().min(1),
558
- passed: z3.boolean()
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 = z3.object({
562
- id: z3.string().regex(ENTRY_ID_REGEX),
563
- type: z3.enum(ARTIFACT_TYPES),
564
- path: z3.string().min(1),
565
- note: z3.string().optional(),
566
- content: z3.string().max(2e5).optional()
567
- });
568
- var ConfirmationSchema = z3.object({
569
- id: z3.string().regex(ENTRY_ID_REGEX),
570
- quote: z3.string().min(1).max(2e3),
571
- at: z3.date()
572
- });
573
- var DecisionSchema = z3.object({
574
- id: z3.string().regex(ENTRY_ID_REGEX),
575
- topic: z3.string().min(1),
576
- decision: z3.string().min(1)
577
- });
578
- var ReviewSummarySchema = z3.object({
579
- verdict: z3.enum(["pass", "fail"]),
580
- rounds: z3.number().int().min(1),
581
- critical: z3.number().int().min(0)
582
- });
583
- var NodeRecordSchema = z3.object({
584
- summary: z3.string().optional(),
585
- checks: z3.array(CheckItemSchema).default([]),
586
- artifacts: z3.array(ArtifactSchema).default([]),
587
- confirmations: z3.array(ConfirmationSchema).default([]),
588
- decisions: z3.array(DecisionSchema).default([]),
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 = z3.object({
592
- what: z3.string().min(1),
593
- why: z3.string().min(1),
594
- acceptance: z3.array(z3.string()).default([]),
595
- nonGoals: z3.array(z3.string()).default([]),
596
- trackNote: z3.string().optional()
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 = z3.object({
599
- id: z3.string().regex(ENTRY_ID_REGEX),
600
- text: z3.string().min(1).max(2e3),
601
- at: z3.date()
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 = z3.string();
1085
+ var NodeStatusSchema = z4.string();
604
1086
  var NODE_STATUSES = ["pending", "active", "completed", "skipped"];
605
- var NodeStateSchema = z3.object({
1087
+ var NodeStateSchema = z4.object({
606
1088
  status: NodeStatusSchema,
607
- enteredAt: z3.date().nullable().default(null),
608
- completedAt: z3.date().nullable().default(null)
609
- });
610
- var DagInstanceSchema = z3.object({
611
- templateId: z3.string(),
612
- templateVersion: z3.string(),
613
- nodes: z3.array(z3.object({
614
- id: z3.string(),
615
- label: z3.string(),
616
- // N016 F9:枚举放宽(注册表权威)
617
- phase: z3.string(),
618
- track: z3.string(),
619
- prompt: z3.string(),
620
- skills: z3.array(z3.string()).default([])
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: z3.array(z3.object({
624
- from: z3.string(),
625
- to: z3.string(),
626
- condition: z3.string().optional(),
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: z3.array(z3.string()),
630
- nodeStates: z3.record(z3.string(), NodeStateSchema)
1113
+ prunedNodes: z4.array(z4.string()),
1114
+ nodeStates: z4.record(z4.string(), NodeStateSchema)
631
1115
  });
632
- var HistoryActionSchema = z3.enum(["entered", "advanced", "paused", "resumed", "approved", "rejected", "completed", "cancelled"]);
633
- var HistoryEntrySchema = z3.object({
634
- nodeId: z3.string(),
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: z3.date().default(() => /* @__PURE__ */ new Date()),
637
- details: z3.record(z3.string(), z3.unknown()).default({})
1120
+ timestamp: z4.date().default(() => /* @__PURE__ */ new Date()),
1121
+ details: z4.record(z4.string(), z4.unknown()).default({})
638
1122
  });
639
- var TaskStatusSchema = z3.string();
640
- var TaskPhaseSchema = z3.string();
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 = z3.object({
644
- id: z3.string().optional(),
645
- taskId: z3.string(),
646
- title: z3.string(),
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: z3.enum(TASK_TYPES).optional(),
1135
+ type: z4.enum(TASK_TYPES).optional(),
652
1136
  /** 归属项目 id(N012:实体层去哨兵 default,由 repo 显式写入——路由层解析:显式携带校验一致性 / 缺省继承模板 projectId) */
653
- projectId: z3.string(),
654
- dagTemplateId: z3.string(),
1137
+ projectId: z4.string(),
1138
+ dagTemplateId: z4.string(),
655
1139
  dagInstance: DagInstanceSchema,
656
- currentNode: z3.string(),
1140
+ currentNode: z4.string(),
657
1141
  currentPhase: TaskPhaseSchema,
658
1142
  status: TaskStatusSchema,
659
1143
  /** 暂停位置:from 节点 id(暂停点暂停,approve 经 findNextEdge 重推导边)/ null(手动暂停) */
660
- pausedAt: z3.string().nullable().default(null),
661
- track: z3.string(),
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: z3.record(z3.string(), NodeRecordSchema).default({}),
1156
+ nodeRecords: z4.record(z4.string(), NodeRecordSchema).default({}),
673
1157
  /** N020 D1:跨节点累积素材(架构信息收集条目,服务端生成 id/at) */
674
- archNotes: z3.array(ArchNoteSchema).default([]),
675
- history: z3.array(HistoryEntrySchema).default([]),
676
- createdAt: z3.date().optional(),
677
- updatedAt: z3.date().optional()
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 = z3.object({
680
- title: z3.string(),
681
- track: z3.string(),
682
- dagTemplateId: z3.string(),
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: z3.array(z3.string().regex(NODE_ID_PATTERN, "skipNodes \u6761\u76EE\u987B\u4E3A\u5408\u6CD5\u8282\u70B9 id")).optional(),
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: z3.enum(TASK_TYPES).optional(),
1180
+ type: z4.enum(TASK_TYPES).optional(),
691
1181
  /** 归属项目:可选(缺省 → 继承模板 projectId,D2;显式携带且 ≠ 模板 → 409 TEMPLATE_PROJECT_MISMATCH) */
692
- projectId: z3.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
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: { collection: "dag_templates", query: (v) => ({ "nodes.phase": v }) },
1533
- dag_track: { collection: "dag_templates", query: (v) => ({ "nodes.track": v }) },
1534
- pause_type: { collection: "dag_templates", query: (v) => ({ "edges.pausePoint.type": v }) },
1535
- task_status: { collection: "tasks", query: (v) => ({ status: v }) },
1536
- node_status: { collection: "tasks", query: (v) => ({ "dagInstance.nodeStates.status": v }) },
1537
- skill_category: { collection: "skills", query: (v) => ({ category: v }) },
1538
- scope: void 0
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 ref = REFERENCE_CHECKS[category];
1589
- if (ref) {
1590
- const count = await db.collection(ref.collection).countDocuments(ref.query(incoming.value));
1591
- if (count > 0) {
1592
- throw new ConflictError("enum-registry", `${category}/${incoming.value}`, {
1593
- bizCode: "ENUM_ENTRY_IN_USE",
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 ref = REFERENCE_CHECKS[category];
1624
- if (ref) {
1625
- const count = await db.collection(ref.collection).countDocuments(ref.query(value));
1626
- if (count > 0) {
1627
- throw new ConflictError("enum-registry", `${category}/${value}`, {
1628
- bizCode: "ENUM_ENTRY_IN_USE",
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/schemas/agent.ts
1640
- import { z as z5 } from "zod";
1641
-
1642
- // src/schemas/model-alias.ts
1643
- import { z as z4 } from "zod";
1644
- var SIMING_CODE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1645
- var ModelAliasShapeSchema = z4.object({
1646
- id: z4.string().optional(),
1647
- /** siming code:创建后不可变(PUT 显式拒绝 MODEL_CODE_IMMUTABLE,非静默剥离) */
1648
- code: z4.string().regex(SIMING_CODE_REGEX, "code \u5FC5\u987B\u4E3A kebab-case\uFF08\u5982 primary\u3001fast-model\uFF09"),
1649
- /** 展示名(如「主力模型」) */
1650
- name: z4.string().min(1, "\u5C55\u793A\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
1651
- /** 真实模型标识(如 zhipuai/glm-5.3;改此处 = 引用方全局替换生效) */
1652
- realModel: z4.string().min(1, "\u771F\u5B9E\u6A21\u578B\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A"),
1653
- createdAt: z4.date().optional(),
1654
- updatedAt: z4.date().optional()
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 = z4.object({
1663
- code: z4.string().regex(SIMING_CODE_REGEX).optional(),
1664
- name: z4.string().min(1).optional(),
1665
- realModel: z4.string().min(1).optional()
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: z4.number().int().min(0)
2487
+ refCount: z6.number().int().min(0)
1669
2488
  });
1670
2489
 
1671
2490
  // src/schemas/agent.ts
1672
- var AgentScopeSchema = z5.enum(["global", "project"]);
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 = z5.object({
1683
- id: z5.string().optional(),
1684
- name: z5.string(),
1685
- description: z5.string(),
1686
- systemPrompt: z5.string(),
1687
- boundSkills: z5.array(z5.string()).default([]),
1688
- model: z5.string(),
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: z5.string().default("1.0.0"),
1691
- tools: z5.array(z5.string()).default([]),
1692
- permissions: z5.array(z5.string()).default([]),
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: z5.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
1697
- createdAt: z5.date().optional(),
1698
- updatedAt: z5.date().optional()
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 AgentSchema = AgentShapeSchema.superRefine(AgentScopeRefine);
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
- }).extend({ scope: AgentScopeSchema }).extend({ model: z5.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09") }).superRefine(AgentScopeRefine);
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: z5.array(z5.string()).optional(),
2553
+ boundSkills: z7.array(z7.string()).optional(),
1712
2554
  // N015:version 放行(export apply 版本递增随 PUT 携带);optional 剥离实体层 default 避免 partial 空 body 注入
1713
- version: z5.string().optional(),
1714
- tools: z5.array(z5.string()).optional(),
1715
- permissions: z5.array(z5.string()).optional(),
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: z5.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09").optional()
1719
- }).partial().superRefine(AgentScopeRefine);
1720
- var AgentCopySchema = z5.object({
1721
- newName: z5.string(),
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: z5.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional()
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 z6 } from "zod";
1736
- var AdvanceRequestSchema = z6.object({
1737
- note: z6.string().optional(),
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: z6.string().min(1).max(2e3).optional()
2735
+ summary: z9.string().min(1).max(2e3).optional()
1740
2736
  });
1741
- var ApproveRequestSchema = z6.object({
1742
- decision: z6.enum(["approved", "rejected"]),
1743
- comment: z6.string().optional()
2737
+ var ApproveRequestSchema = z9.object({
2738
+ decision: z9.enum(["approved", "rejected"]),
2739
+ comment: z9.string().optional()
1744
2740
  });
1745
- var TaskPublicSchema = z6.object({
1746
- taskId: z6.string(),
1747
- title: z6.string(),
2741
+ var TaskPublicSchema = z9.object({
2742
+ taskId: z9.string(),
2743
+ title: z9.string(),
1748
2744
  // T202608240003:任务类型标签(缺省 = 裸任务/旧任务;模板 PRD 调研变体判定消费)
1749
- type: z6.enum(TASK_TYPES).optional(),
1750
- currentNode: z6.string(),
1751
- currentPhase: z6.string(),
1752
- status: z6.string(),
1753
- pausedAt: z6.string().nullable(),
1754
- track: z6.string()
1755
- });
1756
- var NodeInfoSchema = z6.object({
1757
- nodeId: z6.string(),
1758
- label: z6.string(),
1759
- track: z6.string(),
1760
- prompt: z6.string(),
1761
- skills: z6.array(z6.string()),
1762
- upcomingPause: z6.string().nullable()
1763
- });
1764
- var AdvanceResponseSchema = z6.discriminatedUnion("status", [
1765
- z6.object({
1766
- status: z6.literal("advanced"),
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
- z6.object({
1771
- status: z6.literal("paused"),
2767
+ z9.object({
2768
+ status: z9.literal("paused"),
1772
2769
  task: TaskPublicSchema,
1773
2770
  pausePoint: EdgePausePointSchema,
1774
- guidance: z6.string()
2771
+ guidance: z9.string()
1775
2772
  }),
1776
- z6.object({
1777
- status: z6.literal("completed"),
2773
+ z9.object({
2774
+ status: z9.literal("completed"),
1778
2775
  task: TaskPublicSchema
1779
2776
  })
1780
2777
  ]);
1781
- var ApproveResponseSchema = z6.discriminatedUnion("status", [
1782
- z6.object({
1783
- status: z6.literal("advanced"),
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
- z6.object({
1788
- status: z6.literal("rejected"),
2784
+ z9.object({
2785
+ status: z9.literal("rejected"),
1789
2786
  task: TaskPublicSchema,
1790
- guidance: z6.string()
2787
+ guidance: z9.string()
1791
2788
  }),
1792
2789
  // 「completed」分支当前引擎不可达(approve 从 paused 流转必有下一节点,技术方案 §2.2
1793
2790
  // 已删 paused→completed 直达路径)——按方案 §2.1 契约保留作前向兼容预留
1794
- z6.object({
1795
- status: z6.literal("completed"),
2791
+ z9.object({
2792
+ status: z9.literal("completed"),
1796
2793
  task: TaskPublicSchema
1797
2794
  })
1798
2795
  ]);
1799
- var PauseRequestSchema = z6.object({
1800
- reason: z6.string().optional()
2796
+ var PauseRequestSchema = z9.object({
2797
+ reason: z9.string().optional()
1801
2798
  });
1802
- var ResumeRequestSchema = z6.object({
1803
- decision: z6.string().optional()
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 z7 } from "zod";
1808
- var TaskContextSchema = z7.object({
1809
- task: z7.object({
1810
- taskId: z7.string(),
1811
- title: z7.string(),
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: z7.enum(TASK_TYPES).optional(),
1814
- currentNode: z7.string(),
1815
- currentPhase: z7.string(),
1816
- status: z7.string(),
1817
- pausedAt: z7.string().nullable(),
1818
- track: z7.string()
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: z7.object({
1821
- nodeId: z7.string(),
1822
- label: z7.string(),
1823
- phase: z7.string(),
1824
- track: z7.string(),
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: z7.string(),
1827
- skills: z7.array(z7.string()),
2826
+ prompt: z10.string(),
2827
+ skills: z10.array(z10.string()),
2828
+ agents: z10.array(z10.string()),
1828
2829
  /** 完成当前节点后出边上的暂停点描述(经 findNextEdge 按轨选边) */
1829
- upcomingPause: z7.string().nullable()
2830
+ upcomingPause: z10.string().nullable()
1830
2831
  }).nullable(),
1831
- pausedAtEdge: z7.object({
1832
- from: z7.string(),
1833
- to: z7.string(),
2832
+ pausedAtEdge: z10.object({
2833
+ from: z10.string(),
2834
+ to: z10.string(),
1834
2835
  pausePoint: EdgePausePointBaseSchema
1835
2836
  }).nullable(),
1836
2837
  /** 全节点实时状态清单 */
1837
- nodes: z7.array(z7.object({
1838
- nodeId: z7.string(),
1839
- label: z7.string(),
1840
- status: z7.string(),
1841
- enteredAt: z7.date().nullable(),
1842
- completedAt: z7.date().nullable()
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: z7.record(z7.string(), NodeRecordSchema),
2848
+ nodeRecords: z10.record(z10.string(), NodeRecordSchema),
1848
2849
  /** N020 D1:跨节点累积素材(架构信息收集条目) */
1849
- archNotes: z7.array(ArchNoteSchema)
2850
+ archNotes: z10.array(ArchNoteSchema)
1850
2851
  });
1851
- var TaskSummarySchema = z7.object({
1852
- taskId: z7.string(),
1853
- title: z7.string(),
2852
+ var TaskSummarySchema = z10.object({
2853
+ taskId: z10.string(),
2854
+ title: z10.string(),
1854
2855
  // T202608240003:任务类型标签(缺省 = 裸任务/旧任务)
1855
- type: z7.enum(TASK_TYPES).optional(),
1856
- projectId: z7.string(),
1857
- dagTemplateId: z7.string(),
1858
- currentNode: z7.string(),
1859
- currentPhase: z7.string(),
1860
- status: z7.string(),
1861
- pausedAt: z7.string().nullable(),
1862
- track: z7.string(),
1863
- progress: z7.object({
1864
- completed: z7.number(),
1865
- total: z7.number()
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: z7.date().optional(),
1868
- updatedAt: z7.date().optional()
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 z8 } from "zod";
1873
- var ProjectStatusSchema = z8.enum(["active", "archived"]);
1874
- var ProjectSchema = z8.object({
1875
- id: z8.string().optional(),
1876
- key: z8.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09"),
1877
- name: z8.string().min(1).max(50),
1878
- description: z8.string().max(500).optional(),
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: z8.date().optional(),
1881
- updatedAt: z8.date().optional()
2950
+ createdAt: z12.date().optional(),
2951
+ updatedAt: z12.date().optional()
1882
2952
  });
1883
- var ProjectCreateSchema = z8.object({
1884
- key: z8.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09").optional(),
1885
- name: z8.string().min(1).max(50),
1886
- description: z8.string().max(500).optional()
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 = z8.object({
1889
- name: z8.string().min(1).max(50).optional(),
1890
- description: z8.string().max(500).optional(),
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 z9 } from "zod";
1896
- var EnumRegistryCategorySchema = z9.enum([
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 = z9.object({
2976
+ var EnumEntrySchema = z13.object({
1906
2977
  /** 领域层存库的原始枚举值(不可臆造;builtin 值 engine/DB 强依赖;kebab 约束与 skill name 对齐,保证 DELETE /entries/:value 可寻址) */
1907
- value: z9.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"),
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: z9.string().min(1, "label \u4E0D\u80FD\u4E3A\u7A7A"),
2980
+ label: z13.string().min(1, "label \u4E0D\u80FD\u4E3A\u7A7A"),
1910
2981
  /** 可选语义色 token(如 --success / --warning),消费端映射到 UI */
1911
- color: z9.string().optional(),
2982
+ color: z13.string().optional(),
1912
2983
  /** 下拉排序(升序) */
1913
- order: z9.number().int().default(0),
2984
+ order: z13.number().int().default(0),
1914
2985
  /** 内置值:禁删 + label/color 可改但 value 不可改(D8);seed 时标记 */
1915
- builtin: z9.boolean().default(false),
2986
+ builtin: z13.boolean().default(false),
1916
2987
  /** 启停:false = 下拉禁用展示(历史数据仍可读,新选择不可用) */
1917
- active: z9.boolean().default(true)
2988
+ active: z13.boolean().default(true)
1918
2989
  });
1919
- var EnumRegistrySchema = z9.object({
1920
- id: z9.string().optional(),
2990
+ var EnumRegistrySchema = z13.object({
2991
+ id: z13.string().optional(),
1921
2992
  category: EnumRegistryCategorySchema,
1922
- entries: z9.array(EnumEntrySchema).default([]),
1923
- updatedAt: z9.date().optional()
2993
+ entries: z13.array(EnumEntrySchema).default([]),
2994
+ updatedAt: z13.date().optional()
1924
2995
  });
1925
- var EnumRegistryUpdateSchema = z9.object({
1926
- entries: z9.array(EnumEntrySchema)
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 z10 } from "zod";
1931
- var SimingLogLevelSchema = z10.enum(["debug", "info", "warn", "error"]);
1932
- var SimingConfigSchema = z10.object({
1933
- mongoUri: z10.string().min(1).default("mongodb://localhost:27017"),
1934
- port: z10.coerce.number().int().min(1).max(65535).default(7777),
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: z10.string().min(1).default("127.0.0.1"),
3223
+ host: z19.string().min(1).default("127.0.0.1"),
1940
3224
  logLevel: SimingLogLevelSchema.default("info"),
1941
3225
  /**
1942
- * F6 预留字段(V2 远程部署):宽松形态——出现即接受、形状不校验、运行时不读取。
1943
- * 严格满足 PRD「配置中出现预留字段时不报错」;V2 落地时收紧为类型化 schema。
3226
+ * T202608310001:鉴权开关(F1)——从 F6 预留宽松形态收紧为类型化 schema。
3227
+ * 默认关闭 = 本地开发/存量行为零破坏;云主机部署显式开启(SIMING_AUTH_ENABLED=true)。
1944
3228
  */
1945
- auth: z10.unknown().optional(),
1946
- cors: z10.unknown().optional()
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 = z11.object({ version: z11.string().min(1) });
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
  };