@siming-org/core 0.4.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 +1086 -9
  2. package/dist/index.js +1556 -354
  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 z15 } from "zod";
3
+ import { z as z20 } from "zod";
4
4
 
5
5
  // src/store/client.ts
6
6
  import { MongoClient } from "mongodb";
@@ -104,6 +104,9 @@ var BIZ_CODE_MESSAGES = {
104
104
  TASK_PROJECT_IMMUTABLE: "\u4EFB\u52A1\u5F52\u5C5E\u9879\u76EE\u521B\u5EFA\u540E\u4E0D\u53EF\u53D8\u66F4",
105
105
  DEFAULT_PROJECT_IMMUTABLE: "\u9ED8\u8BA4\u9879\u76EE\u4E0D\u53EF\u505C\u7528",
106
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",
107
110
  // 仅覆盖 404 分支(不存在);archived 走独立的 409 PROJECT_ARCHIVED,勿在此合并语义
108
111
  PROJECT_NOT_FOUND: "\u9879\u76EE\u4E0D\u5B58\u5728",
109
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",
@@ -131,7 +134,26 @@ var BIZ_CODE_MESSAGES = {
131
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",
132
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",
133
136
  // T202608290001:停用模板创建任务守卫(资产启停——停用对创建通道同样不可见)
134
- 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"
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"
135
157
  };
136
158
  var AppError = class extends Error {
137
159
  };
@@ -150,8 +172,25 @@ var NotFoundError = class extends AppError {
150
172
  statusCode = 404;
151
173
  code = "not_found";
152
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
+ }
153
186
  toResponse() {
154
- 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
+ };
155
194
  }
156
195
  };
157
196
  var BadRequestError = class extends AppError {
@@ -207,6 +246,31 @@ var ValidationError = class extends AppError {
207
246
  return { error: this.bizCode ?? this.code, message: this.message, issues: this.issues };
208
247
  }
209
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
+ };
210
274
 
211
275
  // src/store/repos/skill.repo.ts
212
276
  function createSkillRepo(db) {
@@ -268,6 +332,12 @@ function createSkillRepo(db) {
268
332
  const skills = await base.list(filter);
269
333
  return new Map(skills.map((s) => [s.name, s]));
270
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
+ },
271
341
  async copySkill(sourceName, source, target, newName) {
272
342
  const src = await base._collection.findOne(
273
343
  source.scope === "project" ? { name: sourceName, scope: "project", projectId: source.projectId } : { name: sourceName, scope: "global" }
@@ -379,6 +449,12 @@ function createAgentRepo(db) {
379
449
  async listAgents(opts) {
380
450
  return base.list(listQuery({}, opts));
381
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;
457
+ },
382
458
  async copyAgent(sourceName, source, target, newName, skillRepo) {
383
459
  const src = await base._collection.findOne(
384
460
  source.scope === "project" ? { name: sourceName, scope: "project", projectId: source.projectId } : { name: sourceName, scope: "global" }
@@ -435,48 +511,8 @@ async function assertBoundSkillsCompatible(skillRepo, agentScope, agentProjectId
435
511
  }
436
512
  }
437
513
 
438
- // src/store/repos/dag-template.repo.ts
439
- function createDagTemplateRepo(db) {
440
- const base = createRepo(db.collection("dag_templates"));
441
- return {
442
- ...base,
443
- createDagTemplate(data) {
444
- return base.create(data);
445
- },
446
- async listDagTemplates(filter) {
447
- const query = {};
448
- if (filter?.projectId) query.projectId = filter.projectId;
449
- if (filter?.name) query.name = filter.name;
450
- if (!filter?.includeDisabled) Object.assign(query, enabledNeFalse());
451
- return base.list(query);
452
- },
453
- async copyDagTemplate(sourceId, targetProjectId, newName) {
454
- const source = await base.getById(sourceId);
455
- if (!source) {
456
- throw new ConflictError("dagTemplate", `copy source not found: ${sourceId}`);
457
- }
458
- const dup = await base._collection.findOne(
459
- { projectId: targetProjectId, name: newName },
460
- { projection: { _id: 1 } }
461
- );
462
- if (dup) {
463
- throw new ConflictError("dagTemplate", `name conflict in target project: ${newName}`);
464
- }
465
- const { id: _sourceId, createdAt: _ca, updatedAt: _ua, enabled: _sourceEnabled, ...rest } = source;
466
- const copy = {
467
- ...rest,
468
- name: newName,
469
- projectId: targetProjectId,
470
- isDefault: false,
471
- version: "1.0.0"
472
- };
473
- return base.create(copy);
474
- }
475
- };
476
- }
477
-
478
- // src/schemas/task.ts
479
- import { z as z4 } from "zod";
514
+ // src/schemas/dag-template.ts
515
+ import { z as z3 } from "zod";
480
516
 
481
517
  // src/schemas/skill.ts
482
518
  import { z as z2 } from "zod";
@@ -666,19 +702,37 @@ var SkillCopySchema = z2.object({
666
702
  });
667
703
 
668
704
  // src/schemas/dag-template.ts
669
- import { z as z3 } from "zod";
670
705
  var DagNodePhaseSchema = z3.string();
671
706
  var DagNodeTrackSchema = z3.string();
672
707
  var DAG_PHASES = ["entry", "track", "test", "exit"];
673
708
  var DAG_TRACKS = ["backend", "ui", "all"];
674
709
  var NODE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
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
+ });
675
726
  var DagNodeSchema = z3.object({
676
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"),
677
728
  label: z3.string(),
678
729
  phase: DagNodePhaseSchema,
679
730
  track: DagNodeTrackSchema,
680
731
  prompt: z3.string(),
681
- skills: z3.array(z3.string()).default([])
732
+ skills: capabilityNamesSchema.default([]),
733
+ /** 存量模板允许缺键;新组装和任务实例化路径写入完整数组 */
734
+ agents: capabilityNamesSchema.optional(),
735
+ sourcePreset: SourcePresetSchema.optional()
682
736
  });
683
737
  var PausePointTypeSchema = z3.enum(["human_approval", "checkpoint"]);
684
738
  var PAUSE_POINT_TYPES = ["human_approval", "checkpoint"];
@@ -701,6 +755,14 @@ var EdgePausePointSchema = EdgePausePointBaseSchema.superRefine((pp, ctx) => {
701
755
  });
702
756
  }
703
757
  });
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
+ });
704
766
  var DagEdgeSchema = z3.object({
705
767
  from: z3.string(),
706
768
  to: z3.string(),
@@ -714,6 +776,12 @@ var DagTemplateSchema = z3.object({
714
776
  /** 归属项目 id(N012:实体层去哨兵 default,由路由层解析写入——校验项目存在且 active 后落库) */
715
777
  projectId: z3.string(),
716
778
  description: z3.string(),
779
+ /**
780
+ * T202609020002 业务 code(可选——存量迁移前的模板无此字段,由启动迁移补齐)。
781
+ * 项目内唯一({projectId, code} 复合唯一索引,迁移后建)、创建后不可变;
782
+ * name 仍承担展示职责(可改、可中文)——职责分离,改名不影响 code 引用。
783
+ */
784
+ code: TemplateCodeFieldSchema.optional(),
717
785
  nodes: z3.array(DagNodeSchema),
718
786
  edges: z3.array(DagEdgeSchema),
719
787
  /** UI 布局坐标(nodeId → {x,y})。UI 关注点,advance 引擎零感知;旧模板无此字段 → 前端 dagre 兜底布局(N008 D2) */
@@ -742,10 +810,228 @@ var DagTemplateUpdateSchema = DagTemplateCreateSchema.extend({
742
810
  }).partial();
743
811
  var DagTemplateCopySchema = z3.object({
744
812
  newName: z3.string().min(1),
745
- targetProjectId: z3.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex")
813
+ targetProjectId: z3.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex"),
814
+ newCode: TemplateCodeFieldSchema.optional()
746
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
+ }
747
1032
 
748
1033
  // src/schemas/task.ts
1034
+ import { z as z4 } from "zod";
749
1035
  var TASK_TYPES = ["feature", "bugfix", "ui-tweak", "research"];
750
1036
  var ENTRY_ID_REGEX = /^[a-z0-9]{6}$/;
751
1037
  var CheckItemSchema = z4.object({
@@ -803,18 +1089,20 @@ var NodeStateSchema = z4.object({
803
1089
  enteredAt: z4.date().nullable().default(null),
804
1090
  completedAt: z4.date().nullable().default(null)
805
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
+ });
806
1102
  var DagInstanceSchema = z4.object({
807
1103
  templateId: z4.string(),
808
1104
  templateVersion: z4.string(),
809
- nodes: z4.array(z4.object({
810
- id: z4.string(),
811
- label: z4.string(),
812
- // N016 F9:枚举放宽(注册表权威)
813
- phase: z4.string(),
814
- track: z4.string(),
815
- prompt: z4.string(),
816
- skills: z4.array(z4.string()).default([])
817
- })),
1105
+ nodes: z4.array(DagInstanceNodeSchema),
818
1106
  // N017 D1:暂停点内联到边(human_approval=审批门 / checkpoint=检查点),顶层数组删除
819
1107
  edges: z4.array(z4.object({
820
1108
  from: z4.string(),
@@ -875,6 +1163,12 @@ var TaskSchema = z4.object({
875
1163
  var TaskCreateInputSchema = z4.object({
876
1164
  title: z4.string(),
877
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
+ */
878
1172
  dagTemplateId: z4.string(),
879
1173
  /**
880
1174
  * T202608240003 创建时剪枝:显式剔除的节点 id(在轨道自动剔除之外)。
@@ -1236,6 +1530,7 @@ function toNodeInfo(node, task) {
1236
1530
  track: node.track,
1237
1531
  prompt: renderPrompt(node.prompt, task),
1238
1532
  skills: node.skills,
1533
+ agents: node.agents ?? [],
1239
1534
  upcomingPause: nextEdge?.pausePoint?.description ?? null
1240
1535
  };
1241
1536
  }
@@ -1619,7 +1914,8 @@ function deepCopyNode(node) {
1619
1914
  phase: node.phase,
1620
1915
  track: node.track,
1621
1916
  prompt: node.prompt,
1622
- skills: [...node.skills]
1917
+ skills: [...node.skills],
1918
+ agents: [...node.agents ?? []]
1623
1919
  };
1624
1920
  }
1625
1921
  function deepCopyEdge(edge) {
@@ -1746,17 +2042,32 @@ async function assertModelAliasExists(repo, code) {
1746
2042
 
1747
2043
  // src/store/repos/enum-registry.repo.ts
1748
2044
  var REFERENCE_CHECKS = {
1749
- dag_phase: { collection: "dag_templates", query: (v) => ({ "nodes.phase": v }) },
1750
- dag_track: { collection: "dag_templates", query: (v) => ({ "nodes.track": v }) },
1751
- pause_type: { collection: "dag_templates", query: (v) => ({ "edges.pausePoint.type": v }) },
1752
- task_status: { collection: "tasks", query: (v) => ({ status: v }) },
1753
- node_status: { collection: "tasks", query: (v) => ({ "dagInstance.nodeStates.status": v }) },
1754
- skill_category: { collection: "skills", query: (v) => ({ category: v }) },
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 }) }],
1755
2057
  scope: void 0,
1756
2058
  // T202608260002:agent 功能类型被 agents.function 引用(reviewer/executor 为 builtin 禁删;
1757
2059
  // 检测映射为未来非 builtin 扩值留防线)
1758
- agent_function: { collection: "agents", query: (v) => ({ "function": v }) }
2060
+ agent_function: [{ collection: "agents", query: (v) => ({ "function": v }) }]
1759
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
+ }
1760
2071
  function createEnumRegistryRepo(db) {
1761
2072
  const base = createRepo(db.collection("enum_registry"), { nameField: "category" });
1762
2073
  return {
@@ -1805,15 +2116,12 @@ function createEnumRegistryRepo(db) {
1805
2116
  });
1806
2117
  }
1807
2118
  if (prev?.active === true && incoming.active === false) {
1808
- const ref = REFERENCE_CHECKS[category];
1809
- if (ref) {
1810
- const count = await db.collection(ref.collection).countDocuments(ref.query(incoming.value));
1811
- if (count > 0) {
1812
- throw new ConflictError("enum-registry", `${category}/${incoming.value}`, {
1813
- bizCode: "ENUM_ENTRY_IN_USE",
1814
- message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
1815
- });
1816
- }
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
+ });
1817
2125
  }
1818
2126
  }
1819
2127
  }
@@ -1840,15 +2148,12 @@ function createEnumRegistryRepo(db) {
1840
2148
  message: BIZ_CODE_MESSAGES.ENUM_ENTRY_BUILTIN
1841
2149
  });
1842
2150
  }
1843
- const ref = REFERENCE_CHECKS[category];
1844
- if (ref) {
1845
- const count = await db.collection(ref.collection).countDocuments(ref.query(value));
1846
- if (count > 0) {
1847
- throw new ConflictError("enum-registry", `${category}/${value}`, {
1848
- bizCode: "ENUM_ENTRY_IN_USE",
1849
- message: `${BIZ_CODE_MESSAGES.ENUM_ENTRY_IN_USE}\uFF08${count} \u4E2A\u8D44\u6E90\u5F15\u7528\uFF09`
1850
- });
1851
- }
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
+ });
1852
2157
  }
1853
2158
  const remaining = existing.entries.filter((e) => e.value !== value);
1854
2159
  return await base.update(existing.id, { entries: remaining }) !== null;
@@ -1856,22 +2161,316 @@ function createEnumRegistryRepo(db) {
1856
2161
  };
1857
2162
  }
1858
2163
 
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
+
1859
2458
  // src/schemas/agent.ts
1860
- import { z as z6 } from "zod";
2459
+ import { z as z7 } from "zod";
1861
2460
 
1862
2461
  // src/schemas/model-alias.ts
1863
- import { z as z5 } from "zod";
2462
+ import { z as z6 } from "zod";
1864
2463
  var SIMING_CODE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1865
- var ModelAliasShapeSchema = z5.object({
1866
- id: z5.string().optional(),
2464
+ var ModelAliasShapeSchema = z6.object({
2465
+ id: z6.string().optional(),
1867
2466
  /** siming code:创建后不可变(PUT 显式拒绝 MODEL_CODE_IMMUTABLE,非静默剥离) */
1868
- code: z5.string().regex(SIMING_CODE_REGEX, "code \u5FC5\u987B\u4E3A kebab-case\uFF08\u5982 primary\u3001fast-model\uFF09"),
2467
+ code: z6.string().regex(SIMING_CODE_REGEX, "code \u5FC5\u987B\u4E3A kebab-case\uFF08\u5982 primary\u3001fast-model\uFF09"),
1869
2468
  /** 展示名(如「主力模型」) */
1870
- name: z5.string().min(1, "\u5C55\u793A\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
2469
+ name: z6.string().min(1, "\u5C55\u793A\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
1871
2470
  /** 真实模型标识(如 zhipuai/glm-5.3;改此处 = 引用方全局替换生效) */
1872
- realModel: z5.string().min(1, "\u771F\u5B9E\u6A21\u578B\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A"),
1873
- createdAt: z5.date().optional(),
1874
- updatedAt: z5.date().optional()
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()
1875
2474
  });
1876
2475
  var ModelAliasSchema = ModelAliasShapeSchema;
1877
2476
  var ModelAliasCreateSchema = ModelAliasShapeSchema.omit({
@@ -1879,18 +2478,18 @@ var ModelAliasCreateSchema = ModelAliasShapeSchema.omit({
1879
2478
  createdAt: true,
1880
2479
  updatedAt: true
1881
2480
  });
1882
- var ModelAliasUpdateSchema = z5.object({
1883
- code: z5.string().regex(SIMING_CODE_REGEX).optional(),
1884
- name: z5.string().min(1).optional(),
1885
- realModel: z5.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()
1886
2485
  });
1887
2486
  var ModelAliasWithRefCountSchema = ModelAliasShapeSchema.extend({
1888
- refCount: z5.number().int().min(0)
2487
+ refCount: z6.number().int().min(0)
1889
2488
  });
1890
2489
 
1891
2490
  // src/schemas/agent.ts
1892
- var AgentScopeSchema = z6.enum(["global", "project"]);
1893
- var AgentFunctionSchema = z6.enum(["reviewer", "executor"]);
2491
+ var AgentScopeSchema = z7.enum(["global", "project"]);
2492
+ var AgentFunctionSchema = z7.enum(["reviewer", "executor"]);
1894
2493
  var AgentScopeRefine = (val, ctx) => {
1895
2494
  if (val.scope === "project" && !val.projectId) {
1896
2495
  ctx.addIssue({
@@ -1900,17 +2499,17 @@ var AgentScopeRefine = (val, ctx) => {
1900
2499
  });
1901
2500
  }
1902
2501
  };
1903
- var AgentShapeSchema = z6.object({
1904
- id: z6.string().optional(),
1905
- name: z6.string(),
1906
- description: z6.string(),
1907
- systemPrompt: z6.string(),
1908
- boundSkills: z6.array(z6.string()).default([]),
1909
- model: z6.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(),
1910
2509
  /** 资产版本(N015 F13):install/export 版本对比依据;default 仅读侧兜底(createRepo 不做读侧解析,存量回填走 M6 迁移) */
1911
- version: z6.string().default("1.0.0"),
1912
- tools: z6.array(z6.string()).default([]),
1913
- permissions: z6.array(z6.string()).default([]),
2510
+ version: z7.string().default("1.0.0"),
2511
+ tools: z7.array(z7.string()).default([]),
2512
+ permissions: z7.array(z7.string()).default([]),
1914
2513
  /** 引用文档集合(可选——存量单文件资产无此字段,行为与升级前一致) */
1915
2514
  references: createReferencesSchema(AGENT_MAIN_DOC).optional(),
1916
2515
  /** 作用域:global=跨项目复用(默认,仅作存量迁移读侧兜底)/ project=项目专用(N012 D3) */
@@ -1918,15 +2517,15 @@ var AgentShapeSchema = z6.object({
1918
2517
  /** 功能类型(T202608260002):default 仅创建侧归一——toEntity 直转不经 Zod parse,读侧归一在 repo 层 + 回填走 M12 */
1919
2518
  "function": AgentFunctionSchema.default("executor"),
1920
2519
  /** 仅 scope=project 时必填且为合法 ObjectId hex(superRefine 保证) */
1921
- projectId: z6.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
2520
+ projectId: z7.string().regex(OBJECT_ID_HEX, "projectId must be a valid ObjectId hex").optional(),
1922
2521
  /**
1923
2522
  * T202608290001 资产启停(可选——存量无此字段 = 启用,零迁移)。
1924
2523
  * 全局判断规则:enabled === false 即停用,true/缺失均启用。
1925
2524
  * 唯一写入口是专用启停端点(POST /:name/enabled)——Create/Update/Copy 均不含此字段。
1926
2525
  */
1927
- enabled: z6.boolean().optional(),
1928
- createdAt: z6.date().optional(),
1929
- updatedAt: z6.date().optional()
2526
+ enabled: z7.boolean().optional(),
2527
+ createdAt: z7.date().optional(),
2528
+ updatedAt: z7.date().optional()
1930
2529
  });
1931
2530
  var agentAggregateLimitRefine = (val, ctx) => {
1932
2531
  const totalChars = (val.systemPrompt?.length ?? 0) + sumReferenceContentChars(val.references);
@@ -1944,28 +2543,28 @@ var AgentCreateSchema = AgentShapeSchema.omit({
1944
2543
  createdAt: true,
1945
2544
  updatedAt: true,
1946
2545
  enabled: true
1947
- }).extend({ scope: AgentScopeSchema }).extend({ model: z6.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09") }).extend({ "function": AgentFunctionSchema.default("executor") }).superRefine(AgentScopeRefine).superRefine(agentAggregateLimitRefine);
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);
1948
2547
  var AgentUpdateSchema = AgentShapeSchema.omit({
1949
2548
  id: true,
1950
2549
  createdAt: true,
1951
2550
  updatedAt: true,
1952
2551
  enabled: true
1953
2552
  }).extend({
1954
- boundSkills: z6.array(z6.string()).optional(),
2553
+ boundSkills: z7.array(z7.string()).optional(),
1955
2554
  // N015:version 放行(export apply 版本递增随 PUT 携带);optional 剥离实体层 default 避免 partial 空 body 注入
1956
- version: z6.string().optional(),
1957
- tools: z6.array(z6.string()).optional(),
1958
- permissions: z6.array(z6.string()).optional(),
2555
+ version: z7.string().optional(),
2556
+ tools: z7.array(z7.string()).optional(),
2557
+ permissions: z7.array(z7.string()).optional(),
1959
2558
  scope: AgentScopeSchema.optional(),
1960
2559
  // N013:model 收紧为 siming code 格式(存在性校验在 handler 层;undefined = 不更新该字段)
1961
- model: z6.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09").optional(),
2560
+ model: z7.string().regex(SIMING_CODE_REGEX, "model \u5FC5\u987B\u662F siming code\uFF08kebab-case\uFF09").optional(),
1962
2561
  // T202608260002:optional 剥离实体层 default——否则 PUT 空 body 会注入 executor 覆盖 reviewer
1963
2562
  "function": AgentFunctionSchema.optional()
1964
2563
  }).partial().superRefine(AgentScopeRefine).superRefine(agentAggregateLimitRefine);
1965
- var AgentCopySchema = z6.object({
1966
- newName: z6.string(),
2564
+ var AgentCopySchema = z7.object({
2565
+ newName: z7.string(),
1967
2566
  newScope: AgentScopeSchema,
1968
- targetProjectId: z6.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()
1969
2568
  }).superRefine((val, ctx) => {
1970
2569
  if (val.newScope === "project" && !val.targetProjectId) {
1971
2570
  ctx.addIssue({
@@ -1977,17 +2576,18 @@ var AgentCopySchema = z6.object({
1977
2576
  });
1978
2577
 
1979
2578
  // src/schemas/export-bundle.ts
1980
- import { z as z7 } from "zod";
2579
+ import { z as z8 } from "zod";
1981
2580
  var EXPORT_FORMAT_VERSION = "1.0";
1982
2581
  var TemplatePayloadSchema = DagTemplateSchema.omit({
1983
2582
  id: true,
1984
2583
  projectId: true,
1985
2584
  enabled: true,
1986
2585
  createdAt: true,
1987
- updatedAt: true
2586
+ updatedAt: true,
2587
+ code: true
1988
2588
  }).extend({
1989
- version: z7.string(),
1990
- isDefault: z7.boolean()
2589
+ version: z8.string(),
2590
+ isDefault: z8.boolean()
1991
2591
  });
1992
2592
  var SkillPayloadSchema = SkillShapeSchema.omit({
1993
2593
  id: true,
@@ -1996,7 +2596,7 @@ var SkillPayloadSchema = SkillShapeSchema.omit({
1996
2596
  createdAt: true,
1997
2597
  updatedAt: true
1998
2598
  }).extend({
1999
- version: z7.string(),
2599
+ version: z8.string(),
2000
2600
  scope: SkillScopeSchema,
2001
2601
  references: createReferencesSchema(SKILL_MAIN_DOC)
2002
2602
  });
@@ -2007,12 +2607,12 @@ var AgentPayloadSchema = AgentShapeSchema.omit({
2007
2607
  createdAt: true,
2008
2608
  updatedAt: true
2009
2609
  }).extend({
2010
- version: z7.string(),
2610
+ version: z8.string(),
2011
2611
  scope: AgentScopeSchema,
2012
2612
  references: createReferencesSchema(AGENT_MAIN_DOC),
2013
- boundSkills: z7.array(z7.string()),
2014
- tools: z7.array(z7.string()),
2015
- permissions: z7.array(z7.string()),
2613
+ boundSkills: z8.array(z8.string()),
2614
+ tools: z8.array(z8.string()),
2615
+ permissions: z8.array(z8.string()),
2016
2616
  "function": AgentFunctionSchema
2017
2617
  });
2018
2618
  var ModelAliasPayloadSchema = ModelAliasSchema.omit({
@@ -2020,271 +2620,307 @@ var ModelAliasPayloadSchema = ModelAliasSchema.omit({
2020
2620
  createdAt: true,
2021
2621
  updatedAt: true
2022
2622
  });
2023
- var ExportBundleSchema = z7.object({
2024
- exportFormatVersion: z7.literal(EXPORT_FORMAT_VERSION, {
2623
+ var ExportBundleSchema = z8.object({
2624
+ exportFormatVersion: z8.literal(EXPORT_FORMAT_VERSION, {
2025
2625
  error: `\u5BFC\u51FA\u683C\u5F0F\u7248\u672C\u4E0D\u652F\u6301\uFF0C\u5F53\u524D\u652F\u6301 ${EXPORT_FORMAT_VERSION}`
2026
2626
  }),
2027
- exportedAt: z7.string(),
2627
+ exportedAt: z8.string(),
2028
2628
  /** 源环境悬空提示(§2.2:悬空 skill/agent.model 不阻断导出)。导出响应恒携带;导入入参可缺省——导入侧提示由 plan 生成,不消费本字段 */
2029
- warnings: z7.array(z7.string()).optional(),
2629
+ warnings: z8.array(z8.string()).optional(),
2030
2630
  template: TemplatePayloadSchema,
2031
- dependencies: z7.object({
2032
- skills: z7.array(SkillPayloadSchema),
2033
- agents: z7.array(AgentPayloadSchema),
2034
- modelAliases: z7.array(ModelAliasPayloadSchema)
2631
+ dependencies: z8.object({
2632
+ skills: z8.array(SkillPayloadSchema),
2633
+ agents: z8.array(AgentPayloadSchema),
2634
+ modelAliases: z8.array(ModelAliasPayloadSchema)
2035
2635
  })
2036
2636
  });
2037
- var ImportPlanRequestSchema = z7.object({
2038
- targetProjectId: z7.string().regex(OBJECT_ID_HEX, "targetProjectId must be a valid ObjectId hex"),
2637
+ var ImportPlanRequestSchema = z8.object({
2638
+ targetProjectId: z8.string().regex(OBJECT_ID_HEX, "targetProjectId must be a valid ObjectId hex"),
2039
2639
  bundle: ExportBundleSchema
2040
2640
  });
2041
- var ImportDecisionSchema = z7.discriminatedUnion("kind", [
2042
- z7.object({
2043
- kind: z7.literal("skill"),
2044
- name: z7.string(),
2641
+ var ImportDecisionSchema = z8.discriminatedUnion("kind", [
2642
+ z8.object({
2643
+ kind: z8.literal("skill"),
2644
+ name: z8.string(),
2045
2645
  scope: SkillScopeSchema,
2046
- action: z7.enum(["overwrite", "skip"])
2646
+ action: z8.enum(["overwrite", "skip"])
2047
2647
  }),
2048
- z7.object({
2049
- kind: z7.literal("agent"),
2050
- name: z7.string(),
2648
+ z8.object({
2649
+ kind: z8.literal("agent"),
2650
+ name: z8.string(),
2051
2651
  scope: AgentScopeSchema,
2052
- action: z7.enum(["overwrite", "skip"])
2652
+ action: z8.enum(["overwrite", "skip"])
2053
2653
  }),
2054
- z7.object({
2055
- kind: z7.literal("modelAlias"),
2056
- name: z7.string(),
2057
- action: z7.enum(["overwrite", "skip"])
2654
+ z8.object({
2655
+ kind: z8.literal("modelAlias"),
2656
+ name: z8.string(),
2657
+ action: z8.enum(["overwrite", "skip"])
2058
2658
  })
2059
2659
  ]);
2060
2660
  var ImportApplyRequestSchema = ImportPlanRequestSchema.extend({
2061
2661
  /** 未列出的冲突项缺省 skip(保守默认:未决策不覆盖) */
2062
- decisions: z7.array(ImportDecisionSchema).default([])
2662
+ decisions: z8.array(ImportDecisionSchema).default([])
2063
2663
  });
2064
- var DependencyStatusSchema = z7.enum(["create", "identical", "conflict"]);
2065
- var DependencyCheckSchema = z7.object({
2066
- kind: z7.enum(["skill", "agent", "modelAlias"]),
2067
- name: z7.string(),
2664
+ var DependencyStatusSchema = z8.enum(["create", "identical", "conflict"]);
2665
+ var DependencyCheckSchema = z8.object({
2666
+ kind: z8.enum(["skill", "agent", "modelAlias"]),
2667
+ name: z8.string(),
2068
2668
  /** skill/agent 携带;modelAlias 无 */
2069
- scope: z7.enum(["global", "project"]).optional(),
2669
+ scope: z8.enum(["global", "project"]).optional(),
2070
2670
  status: DependencyStatusSchema,
2071
2671
  /** status ≠ create 时目标环境现值版本(modelAlias 无 version 概念不填) */
2072
- currentVersion: z7.string().optional(),
2073
- importVersion: z7.string().optional(),
2672
+ currentVersion: z8.string().optional(),
2673
+ importVersion: z8.string().optional(),
2074
2674
  /** status = conflict 时差异字段名清单(如 "content, references") */
2075
- changeSummary: z7.string().optional(),
2675
+ changeSummary: z8.string().optional(),
2076
2676
  /** 影响范围(PRD F13):global 资产影响所有项目 / project 资产仅影响所属项目 */
2077
- impact: z7.enum(["global", "project"])
2078
- });
2079
- var ImportPlanResponseSchema = z7.object({
2080
- template: z7.object({
2081
- name: z7.string(),
2082
- willOverwrite: z7.boolean(),
2083
- currentVersion: z7.string().optional(),
2084
- importVersion: z7.string(),
2085
- isDefaultRequested: z7.boolean(),
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(),
2086
2686
  /** isDefault 降级后的最终生效值(F17:项目已有默认 → false) */
2087
- isDefaultEffect: z7.boolean(),
2088
- downgradeReason: z7.string().optional()
2687
+ isDefaultEffect: z8.boolean(),
2688
+ downgradeReason: z8.string().optional()
2089
2689
  }),
2090
- dependencies: z7.array(DependencyCheckSchema),
2091
- /** 非阻断提示(源环境悬空绑定、agent.model 引用缺失等) */
2092
- warnings: z7.array(z7.string())
2093
- });
2094
- var DependencyActionSchema = z7.enum(["created", "updated", "skipped", "identical", "failed"]);
2095
- var ImportApplyResponseSchema = z7.object({
2096
- template: z7.object({
2097
- id: z7.string(),
2098
- name: z7.string(),
2099
- action: z7.enum(["created", "updated"]),
2100
- isDefaultEffect: z7.boolean()
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()
2101
2712
  }),
2102
- dependencies: z7.array(
2103
- z7.object({
2104
- kind: z7.enum(["skill", "agent", "modelAlias"]),
2105
- name: z7.string(),
2106
- scope: z7.enum(["global", "project"]).optional(),
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(),
2107
2718
  action: DependencyActionSchema,
2108
2719
  /** action = failed 时的错误说明 */
2109
- message: z7.string().optional()
2720
+ message: z8.string().optional()
2110
2721
  })
2111
- )
2722
+ ),
2723
+ /** K10②:apply 时刻目标环境缺失来源而被剥除 sourcePreset 的节点 id 清单(降级自建导入) */
2724
+ sourceStripped: z8.array(z8.string())
2112
2725
  });
2113
2726
  function decisionKey(kind, scope, name) {
2114
2727
  return `${kind}:${scope ?? ""}:${name}`;
2115
2728
  }
2116
2729
 
2117
2730
  // src/schemas/advance.ts
2118
- import { z as z8 } from "zod";
2119
- var AdvanceRequestSchema = z8.object({
2120
- note: z8.string().optional(),
2731
+ import { z as z9 } from "zod";
2732
+ var AdvanceRequestSchema = z9.object({
2733
+ note: z9.string().optional(),
2121
2734
  /** min(1).max(2000) 与 record summary 端点对齐(同一 $set 目标,两入口校验强度一致) */
2122
- summary: z8.string().min(1).max(2e3).optional()
2735
+ summary: z9.string().min(1).max(2e3).optional()
2123
2736
  });
2124
- var ApproveRequestSchema = z8.object({
2125
- decision: z8.enum(["approved", "rejected"]),
2126
- comment: z8.string().optional()
2737
+ var ApproveRequestSchema = z9.object({
2738
+ decision: z9.enum(["approved", "rejected"]),
2739
+ comment: z9.string().optional()
2127
2740
  });
2128
- var TaskPublicSchema = z8.object({
2129
- taskId: z8.string(),
2130
- title: z8.string(),
2741
+ var TaskPublicSchema = z9.object({
2742
+ taskId: z9.string(),
2743
+ title: z9.string(),
2131
2744
  // T202608240003:任务类型标签(缺省 = 裸任务/旧任务;模板 PRD 调研变体判定消费)
2132
- type: z8.enum(TASK_TYPES).optional(),
2133
- currentNode: z8.string(),
2134
- currentPhase: z8.string(),
2135
- status: z8.string(),
2136
- pausedAt: z8.string().nullable(),
2137
- track: z8.string()
2138
- });
2139
- var NodeInfoSchema = z8.object({
2140
- nodeId: z8.string(),
2141
- label: z8.string(),
2142
- track: z8.string(),
2143
- prompt: z8.string(),
2144
- skills: z8.array(z8.string()),
2145
- upcomingPause: z8.string().nullable()
2146
- });
2147
- var AdvanceResponseSchema = z8.discriminatedUnion("status", [
2148
- z8.object({
2149
- status: z8.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"),
2150
2764
  task: TaskPublicSchema,
2151
2765
  nextNode: NodeInfoSchema
2152
2766
  }),
2153
- z8.object({
2154
- status: z8.literal("paused"),
2767
+ z9.object({
2768
+ status: z9.literal("paused"),
2155
2769
  task: TaskPublicSchema,
2156
2770
  pausePoint: EdgePausePointSchema,
2157
- guidance: z8.string()
2771
+ guidance: z9.string()
2158
2772
  }),
2159
- z8.object({
2160
- status: z8.literal("completed"),
2773
+ z9.object({
2774
+ status: z9.literal("completed"),
2161
2775
  task: TaskPublicSchema
2162
2776
  })
2163
2777
  ]);
2164
- var ApproveResponseSchema = z8.discriminatedUnion("status", [
2165
- z8.object({
2166
- status: z8.literal("advanced"),
2778
+ var ApproveResponseSchema = z9.discriminatedUnion("status", [
2779
+ z9.object({
2780
+ status: z9.literal("advanced"),
2167
2781
  task: TaskPublicSchema,
2168
2782
  nextNode: NodeInfoSchema
2169
2783
  }),
2170
- z8.object({
2171
- status: z8.literal("rejected"),
2784
+ z9.object({
2785
+ status: z9.literal("rejected"),
2172
2786
  task: TaskPublicSchema,
2173
- guidance: z8.string()
2787
+ guidance: z9.string()
2174
2788
  }),
2175
2789
  // 「completed」分支当前引擎不可达(approve 从 paused 流转必有下一节点,技术方案 §2.2
2176
2790
  // 已删 paused→completed 直达路径)——按方案 §2.1 契约保留作前向兼容预留
2177
- z8.object({
2178
- status: z8.literal("completed"),
2791
+ z9.object({
2792
+ status: z9.literal("completed"),
2179
2793
  task: TaskPublicSchema
2180
2794
  })
2181
2795
  ]);
2182
- var PauseRequestSchema = z8.object({
2183
- reason: z8.string().optional()
2796
+ var PauseRequestSchema = z9.object({
2797
+ reason: z9.string().optional()
2184
2798
  });
2185
- var ResumeRequestSchema = z8.object({
2186
- decision: z8.string().optional()
2799
+ var ResumeRequestSchema = z9.object({
2800
+ decision: z9.string().optional()
2187
2801
  });
2188
- var CancelRequestSchema = z8.object({
2189
- reason: z8.string().min(1).max(500).optional()
2802
+ var CancelRequestSchema = z9.object({
2803
+ reason: z9.string().min(1).max(500).optional()
2190
2804
  });
2191
2805
 
2192
2806
  // src/schemas/task-doc.ts
2193
- import { z as z9 } from "zod";
2194
- var TaskContextSchema = z9.object({
2195
- task: z9.object({
2196
- taskId: z9.string(),
2197
- title: z9.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(),
2198
2812
  // T202608240003:与 TaskPublicSchema 同步(toTaskPublic 运行时已产出,schema 声明漂移会导致 typed 消费方 strip 掉 type)
2199
- type: z9.enum(TASK_TYPES).optional(),
2200
- currentNode: z9.string(),
2201
- currentPhase: z9.string(),
2202
- status: z9.string(),
2203
- pausedAt: z9.string().nullable(),
2204
- track: z9.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()
2205
2819
  }),
2206
- currentNode: z9.object({
2207
- nodeId: z9.string(),
2208
- label: z9.string(),
2209
- phase: z9.string(),
2210
- track: z9.string(),
2820
+ currentNode: z10.object({
2821
+ nodeId: z10.string(),
2822
+ label: z10.string(),
2823
+ phase: z10.string(),
2824
+ track: z10.string(),
2211
2825
  /** renderPrompt 渲染后(AI 推进任务的直接输入) */
2212
- prompt: z9.string(),
2213
- skills: z9.array(z9.string()),
2826
+ prompt: z10.string(),
2827
+ skills: z10.array(z10.string()),
2828
+ agents: z10.array(z10.string()),
2214
2829
  /** 完成当前节点后出边上的暂停点描述(经 findNextEdge 按轨选边) */
2215
- upcomingPause: z9.string().nullable()
2830
+ upcomingPause: z10.string().nullable()
2216
2831
  }).nullable(),
2217
- pausedAtEdge: z9.object({
2218
- from: z9.string(),
2219
- to: z9.string(),
2832
+ pausedAtEdge: z10.object({
2833
+ from: z10.string(),
2834
+ to: z10.string(),
2220
2835
  pausePoint: EdgePausePointBaseSchema
2221
2836
  }).nullable(),
2222
2837
  /** 全节点实时状态清单 */
2223
- nodes: z9.array(z9.object({
2224
- nodeId: z9.string(),
2225
- label: z9.string(),
2226
- status: z9.string(),
2227
- enteredAt: z9.date().nullable(),
2228
- completedAt: z9.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()
2229
2844
  })),
2230
2845
  /** N020 D1:任务级结构化文档(未写时 null) */
2231
2846
  taskDoc: TaskDocContentSchema.nullable(),
2232
2847
  /** N020 D1:节点执行记录(仅含已写记录的 nodeId) */
2233
- nodeRecords: z9.record(z9.string(), NodeRecordSchema),
2848
+ nodeRecords: z10.record(z10.string(), NodeRecordSchema),
2234
2849
  /** N020 D1:跨节点累积素材(架构信息收集条目) */
2235
- archNotes: z9.array(ArchNoteSchema)
2850
+ archNotes: z10.array(ArchNoteSchema)
2236
2851
  });
2237
- var TaskSummarySchema = z9.object({
2238
- taskId: z9.string(),
2239
- title: z9.string(),
2852
+ var TaskSummarySchema = z10.object({
2853
+ taskId: z10.string(),
2854
+ title: z10.string(),
2240
2855
  // T202608240003:任务类型标签(缺省 = 裸任务/旧任务)
2241
- type: z9.enum(TASK_TYPES).optional(),
2242
- projectId: z9.string(),
2243
- dagTemplateId: z9.string(),
2244
- currentNode: z9.string(),
2245
- currentPhase: z9.string(),
2246
- status: z9.string(),
2247
- pausedAt: z9.string().nullable(),
2248
- track: z9.string(),
2249
- progress: z9.object({
2250
- completed: z9.number(),
2251
- total: z9.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()
2252
2867
  }),
2253
- createdAt: z9.date().optional(),
2254
- updatedAt: z9.date().optional()
2868
+ createdAt: z10.date().optional(),
2869
+ updatedAt: z10.date().optional()
2255
2870
  });
2256
2871
 
2257
2872
  // src/schemas/write-ack.ts
2258
- import { z as z10 } from "zod";
2873
+ import { z as z11 } from "zod";
2259
2874
  var WRITE_ACK_ENTRY_KINDS = ["check", "artifact", "decision", "confirmation", "archnote"];
2260
- var WriteAckEntryKindSchema = z10.enum(WRITE_ACK_ENTRY_KINDS);
2261
- var EchoItemSchema = z10.object({
2262
- path: z10.string(),
2263
- excerpt: z10.string()
2875
+ var WriteAckEntryKindSchema = z11.enum(WRITE_ACK_ENTRY_KINDS);
2876
+ var EchoItemSchema = z11.object({
2877
+ path: z11.string(),
2878
+ excerpt: z11.string()
2264
2879
  });
2265
- var TaskProgressSchema = z10.object({
2266
- completed: z10.number().int(),
2267
- total: z10.number().int()
2880
+ var TaskProgressSchema = z11.object({
2881
+ completed: z11.number().int(),
2882
+ total: z11.number().int()
2268
2883
  });
2269
2884
  var TaskProgressPublicSchema = TaskPublicSchema.extend({
2270
- projectId: z10.string(),
2885
+ projectId: z11.string(),
2271
2886
  progress: TaskProgressSchema
2272
2887
  });
2273
- var WriteAckSchema = z10.object({
2274
- taskId: z10.string(),
2888
+ var WriteAckSchema = z11.object({
2889
+ taskId: z11.string(),
2275
2890
  /** 本次写入触及的字段点路径(如 doc.what / nodeRecords.PRD.checks) */
2276
- updated: z10.array(z10.string()),
2891
+ updated: z11.array(z11.string()),
2277
2892
  /** 新建条目标识($push 五类对象条目;无新条目的端点缺省) */
2278
- entry: z10.object({
2279
- id: z10.string(),
2893
+ entry: z11.object({
2894
+ id: z11.string(),
2280
2895
  kind: WriteAckEntryKindSchema
2281
2896
  }).optional(),
2282
2897
  /** 写入内容回显(触及字段逐条;无值可回显的端点可缺省) */
2283
- echo: z10.array(EchoItemSchema).optional(),
2284
- updatedAt: z10.date(),
2898
+ echo: z11.array(EchoItemSchema).optional(),
2899
+ updatedAt: z11.date(),
2285
2900
  task: TaskProgressPublicSchema
2286
2901
  });
2287
- var CreateTaskResponseSchema = z10.object({
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({
2288
2924
  task: TaskProgressPublicSchema,
2289
2925
  firstNode: NodeInfoSchema
2290
2926
  });
@@ -2300,34 +2936,34 @@ function excerpt(value, max = 120) {
2300
2936
  return `${chars.slice(0, max - 1).join("")}\u2026`;
2301
2937
  }
2302
2938
  var CONTEXT_VIEWS = ["basic", "full"];
2303
- var ContextViewSchema = z10.enum(CONTEXT_VIEWS);
2939
+ var ContextViewSchema = z11.enum(CONTEXT_VIEWS);
2304
2940
 
2305
2941
  // src/schemas/project.ts
2306
- import { z as z11 } from "zod";
2307
- var ProjectStatusSchema = z11.enum(["active", "archived"]);
2308
- var ProjectSchema = z11.object({
2309
- id: z11.string().optional(),
2310
- key: z11.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09"),
2311
- name: z11.string().min(1).max(50),
2312
- description: z11.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(),
2313
2949
  status: ProjectStatusSchema.default("active"),
2314
- createdAt: z11.date().optional(),
2315
- updatedAt: z11.date().optional()
2950
+ createdAt: z12.date().optional(),
2951
+ updatedAt: z12.date().optional()
2316
2952
  });
2317
- var ProjectCreateSchema = z11.object({
2318
- key: z11.string().regex(/^[a-z0-9][a-z0-9-]{1,30}$/, "key \u5FC5\u987B\u4E3A kebab-case\uFF082-31 \u5B57\u7B26\uFF09").optional(),
2319
- name: z11.string().min(1).max(50),
2320
- description: z11.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()
2321
2957
  });
2322
- var ProjectUpdateSchema = z11.object({
2323
- name: z11.string().min(1).max(50).optional(),
2324
- description: z11.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(),
2325
2961
  status: ProjectStatusSchema.optional()
2326
2962
  });
2327
2963
 
2328
2964
  // src/schemas/enum-registry.ts
2329
- import { z as z12 } from "zod";
2330
- var EnumRegistryCategorySchema = z12.enum([
2965
+ import { z as z13 } from "zod";
2966
+ var EnumRegistryCategorySchema = z13.enum([
2331
2967
  "dag_phase",
2332
2968
  "dag_track",
2333
2969
  "pause_type",
@@ -2337,62 +2973,277 @@ var EnumRegistryCategorySchema = z12.enum([
2337
2973
  "scope",
2338
2974
  "agent_function"
2339
2975
  ]);
2340
- var EnumEntrySchema = z12.object({
2976
+ var EnumEntrySchema = z13.object({
2341
2977
  /** 领域层存库的原始枚举值(不可臆造;builtin 值 engine/DB 强依赖;kebab 约束与 skill name 对齐,保证 DELETE /entries/:value 可寻址) */
2342
- value: z12.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"),
2343
2979
  /** 展示文案(消费端下拉直接取用) */
2344
- label: z12.string().min(1, "label \u4E0D\u80FD\u4E3A\u7A7A"),
2980
+ label: z13.string().min(1, "label \u4E0D\u80FD\u4E3A\u7A7A"),
2345
2981
  /** 可选语义色 token(如 --success / --warning),消费端映射到 UI */
2346
- color: z12.string().optional(),
2982
+ color: z13.string().optional(),
2347
2983
  /** 下拉排序(升序) */
2348
- order: z12.number().int().default(0),
2984
+ order: z13.number().int().default(0),
2349
2985
  /** 内置值:禁删 + label/color 可改但 value 不可改(D8);seed 时标记 */
2350
- builtin: z12.boolean().default(false),
2986
+ builtin: z13.boolean().default(false),
2351
2987
  /** 启停:false = 下拉禁用展示(历史数据仍可读,新选择不可用) */
2352
- active: z12.boolean().default(true)
2988
+ active: z13.boolean().default(true)
2353
2989
  });
2354
- var EnumRegistrySchema = z12.object({
2355
- id: z12.string().optional(),
2990
+ var EnumRegistrySchema = z13.object({
2991
+ id: z13.string().optional(),
2356
2992
  category: EnumRegistryCategorySchema,
2357
- entries: z12.array(EnumEntrySchema).default([]),
2358
- updatedAt: z12.date().optional()
2993
+ entries: z13.array(EnumEntrySchema).default([]),
2994
+ updatedAt: z13.date().optional()
2359
2995
  });
2360
- var EnumRegistryUpdateSchema = z12.object({
2361
- entries: z12.array(EnumEntrySchema)
2996
+ var EnumRegistryUpdateSchema = z13.object({
2997
+ entries: z13.array(EnumEntrySchema)
2362
2998
  });
2363
2999
 
2364
3000
  // src/schemas/asset-enabled.ts
2365
- import { z as z13 } from "zod";
2366
- var AssetSetEnabledSchema = z13.object({
2367
- enabled: z13.boolean()
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)
2368
3211
  });
2369
3212
 
2370
3213
  // src/config/server-config.ts
2371
- import { z as z14 } from "zod";
2372
- var SimingLogLevelSchema = z14.enum(["debug", "info", "warn", "error"]);
2373
- var SimingConfigSchema = z14.object({
2374
- mongoUri: z14.string().min(1).default("mongodb://localhost:27017"),
2375
- port: z14.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),
2376
3219
  /**
2377
3220
  * N018 D8:默认仅绑定本机回环(安全默认);远程/局域接入(F7)显式配置 host。
2378
3221
  * 超出 PRD V1 配置清单的新增项,依据见技术方案 §3.4「超范围扩展标注」。
2379
3222
  */
2380
- host: z14.string().min(1).default("127.0.0.1"),
3223
+ host: z19.string().min(1).default("127.0.0.1"),
2381
3224
  logLevel: SimingLogLevelSchema.default("info"),
2382
3225
  /**
2383
- * F6 预留字段(V2 远程部署):宽松形态——出现即接受、形状不校验、运行时不读取。
2384
- * 严格满足 PRD「配置中出现预留字段时不报错」;V2 落地时收紧为类型化 schema。
3226
+ * T202608310001:鉴权开关(F1)——从 F6 预留宽松形态收紧为类型化 schema。
3227
+ * 默认关闭 = 本地开发/存量行为零破坏;云主机部署显式开启(SIMING_AUTH_ENABLED=true)。
2385
3228
  */
2386
- auth: z14.unknown().optional(),
2387
- cors: z14.unknown().optional()
3229
+ auth: z19.object({
3230
+ enabled: z19.boolean().default(false)
3231
+ }).prefault({}),
3232
+ cors: z19.unknown().optional()
2388
3233
  });
2389
- var SIMING_CONFIG_KEYS = ["mongoUri", "port", "host", "logLevel"];
3234
+ var SIMING_CONFIG_KEYS = ["mongoUri", "port", "host", "logLevel", "auth"];
2390
3235
  var SIMING_CONFIG_ENV_KEYS = {
2391
3236
  mongoUri: "SIMING_MONGO_URI",
2392
3237
  port: "PORT",
2393
3238
  host: "SIMING_HOST",
2394
- logLevel: "SIMING_LOG_LEVEL"
3239
+ logLevel: "SIMING_LOG_LEVEL",
3240
+ auth: "SIMING_AUTH_ENABLED"
2395
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
+ }
2396
3247
 
2397
3248
  // src/version-bump.ts
2398
3249
  function tryBumpPatch(version) {
@@ -2407,6 +3258,266 @@ function bumpPatchOrPassthrough(version) {
2407
3258
  return tryBumpPatch(version) ?? version;
2408
3259
  }
2409
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
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
+ }
3520
+
2410
3521
  // src/task/entry-id.ts
2411
3522
  var ENTRY_ID_LENGTH = 6;
2412
3523
  var BASE36_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -2424,7 +3535,7 @@ function generateEntryId(existing) {
2424
3535
 
2425
3536
  // src/template-transfer/payload.ts
2426
3537
  function toTemplatePayload(tpl) {
2427
- const { id: _id, projectId: _projectId, enabled: _enabled, createdAt: _ca, updatedAt: _ua, ...rest } = tpl;
3538
+ const { id: _id, projectId: _projectId, enabled: _enabled, createdAt: _ca, updatedAt: _ua, code: _code, ...rest } = tpl;
2428
3539
  return { ...rest };
2429
3540
  }
2430
3541
  function toSkillPayload(skill) {
@@ -2615,6 +3726,26 @@ async function buildImportPlan(deps, targetProjectId, bundle) {
2615
3726
  }
2616
3727
  }
2617
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
+ }
2618
3749
  return {
2619
3750
  template: {
2620
3751
  name: bundle.template.name,
@@ -2626,6 +3757,7 @@ async function buildImportPlan(deps, targetProjectId, bundle) {
2626
3757
  ...bundle.template.isDefault && !isDefaultEffect ? { downgradeReason: "project_has_default" } : {}
2627
3758
  },
2628
3759
  dependencies: [...skillChecks, ...agentChecks, ...aliasChecks],
3760
+ directSkillChecks,
2629
3761
  warnings
2630
3762
  };
2631
3763
  }
@@ -2637,15 +3769,27 @@ async function applyImport(deps, targetProjectId, bundle, decisions) {
2637
3769
  const projectHasDefault = await hasProjectDefault(deps, targetProjectId);
2638
3770
  const isDefaultEffect = bundle.template.isDefault && !projectHasDefault;
2639
3771
  const tplPayload = bundle.template;
2640
- const createInput = { ...tplPayload, projectId: targetProjectId, isDefault: isDefaultEffect };
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 };
2641
3784
  const parsed = DagTemplateCreateSchema.safeParse(createInput);
2642
3785
  if (!parsed.success) {
2643
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);
2644
3787
  }
3788
+ assertUniqueNodeIds(nodesForWrite);
2645
3789
  const contentFields = {
2646
3790
  name: tplPayload.name,
2647
3791
  description: tplPayload.description,
2648
- nodes: tplPayload.nodes,
3792
+ nodes: nodesForWrite,
2649
3793
  edges: tplPayload.edges,
2650
3794
  version: tplPayload.version,
2651
3795
  isDefault: isDefaultEffect,
@@ -2794,7 +3938,7 @@ async function applyImport(deps, targetProjectId, bundle, decisions) {
2794
3938
  results.push({ kind: "modelAlias", name: payload.code, action: "failed", message: errorMessage(err) });
2795
3939
  }
2796
3940
  }
2797
- return { template: templateResult, dependencies: results };
3941
+ return { template: templateResult, dependencies: results, sourceStripped };
2798
3942
  }
2799
3943
  function decide(status, decision) {
2800
3944
  if (status === "identical") return "identical";
@@ -2812,7 +3956,7 @@ function errorMessage(err) {
2812
3956
  }
2813
3957
 
2814
3958
  // src/index.ts
2815
- var PackageJsonSchema = z15.object({ version: z15.string().min(1) });
3959
+ var PackageJsonSchema = z20.object({ version: z20.string().min(1) });
2816
3960
  var packageJsonParsed = PackageJsonSchema.safeParse(
2817
3961
  JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"))
2818
3962
  );
@@ -2826,6 +3970,7 @@ function ping() {
2826
3970
  export {
2827
3971
  AGENT_MAIN_DOC,
2828
3972
  ARTIFACT_TYPES,
3973
+ AUTH_COLLECTIONS,
2829
3974
  AdvanceRequestSchema,
2830
3975
  AdvanceResponseSchema,
2831
3976
  AgentCopySchema,
@@ -2842,11 +3987,21 @@ export {
2842
3987
  ArchNoteSchema,
2843
3988
  ArtifactSchema,
2844
3989
  AssetSetEnabledSchema,
3990
+ AuthAccountCreateSchema,
3991
+ AuthAccountSchema,
3992
+ AuthStatusSchema,
3993
+ AuthTokenGenResponseSchema,
3994
+ AuthTokenSchema,
3995
+ AuthTokenStatusSchema,
3996
+ AuthTokenTypeSchema,
3997
+ AuthWhoamiSchema,
2845
3998
  BIZ_CODE_MESSAGES,
2846
3999
  BadRequestError,
2847
4000
  CONTEXT_VIEWS,
2848
4001
  CancelRequestSchema,
2849
4002
  CheckItemSchema,
4003
+ CompositionEdgeSchema,
4004
+ CompositionSchema,
2850
4005
  ConfirmationSchema,
2851
4006
  ConflictError,
2852
4007
  ContextViewSchema,
@@ -2855,6 +4010,7 @@ export {
2855
4010
  DAG_TRACKS,
2856
4011
  DEFAULT_PROJECT_KEY,
2857
4012
  DagEdgeSchema,
4013
+ DagInstanceNodeSchema,
2858
4014
  DagInstanceSchema,
2859
4015
  DagNodePhaseSchema,
2860
4016
  DagNodeSchema,
@@ -2877,6 +4033,7 @@ export {
2877
4033
  EnumRegistrySchema,
2878
4034
  EnumRegistryUpdateSchema,
2879
4035
  ExportBundleSchema,
4036
+ ForbiddenError,
2880
4037
  HistoryActionSchema,
2881
4038
  HistoryEntrySchema,
2882
4039
  ImportApplyRequestSchema,
@@ -2884,6 +4041,7 @@ export {
2884
4041
  ImportDecisionSchema,
2885
4042
  ImportPlanRequestSchema,
2886
4043
  ImportPlanResponseSchema,
4044
+ LoginSchema,
2887
4045
  ModelAliasCreateSchema,
2888
4046
  ModelAliasPayloadSchema,
2889
4047
  ModelAliasSchema,
@@ -2891,14 +4049,23 @@ export {
2891
4049
  ModelAliasUpdateSchema,
2892
4050
  ModelAliasWithRefCountSchema,
2893
4051
  NODE_ID_PATTERN,
4052
+ NODE_PRESET_CODE_PATTERN,
2894
4053
  NODE_STATUSES,
2895
4054
  NodeInfoSchema,
4055
+ NodeLibrarySchema,
4056
+ NodeLibraryUpsertSchema,
4057
+ NodePresetCopySchema,
4058
+ NodePresetCreateSchema,
4059
+ NodePresetSchema,
4060
+ NodePresetShapeSchema,
4061
+ NodePresetUpdateSchema,
2896
4062
  NodeRecordSchema,
2897
4063
  NodeStateSchema,
2898
4064
  NodeStatusSchema,
2899
4065
  NotFoundError,
2900
4066
  OBJECT_ID_HEX,
2901
4067
  PAUSE_POINT_TYPES,
4068
+ PasswordChangeSchema,
2902
4069
  PausePointTypeSchema,
2903
4070
  PauseRequestSchema,
2904
4071
  ProjectCreateSchema,
@@ -2921,9 +4088,15 @@ export {
2921
4088
  SkillScopeSchema,
2922
4089
  SkillShapeSchema,
2923
4090
  SkillUpdateSchema,
4091
+ SourcePresetSchema,
2924
4092
  TASK_PHASES,
2925
4093
  TASK_STATUSES,
2926
4094
  TASK_TYPES,
4095
+ TEMPLATE_CODE_MAX_LENGTH,
4096
+ TEMPLATE_CODE_REGEX,
4097
+ TaskBatchAckSchema,
4098
+ TaskBatchEntrySchema,
4099
+ TaskBatchFailureSchema,
2927
4100
  TaskContextSchema,
2928
4101
  TaskCreateInputSchema,
2929
4102
  TaskDocContentSchema,
@@ -2934,7 +4107,14 @@ export {
2934
4107
  TaskSchema,
2935
4108
  TaskStatusSchema,
2936
4109
  TaskSummarySchema,
4110
+ TemplateCandidateSchema,
4111
+ TemplateCodeFieldSchema,
2937
4112
  TemplatePayloadSchema,
4113
+ UnauthorizedError,
4114
+ UpgradeActionSchema,
4115
+ UpgradeApplyRequestSchema,
4116
+ UpgradeDecisionSchema,
4117
+ UpgradeNodeStatusSchema,
2938
4118
  VERSION,
2939
4119
  ValidationError,
2940
4120
  WRITE_ACK_ENTRY_KINDS,
@@ -2942,38 +4122,58 @@ export {
2942
4122
  WriteAckSchema,
2943
4123
  advanceTask,
2944
4124
  applyImport,
4125
+ applyUpgradeDecisions,
2945
4126
  approveTask,
2946
4127
  assertBoundSkillsCompatible,
2947
4128
  assertModelAliasExists,
2948
4129
  assertReferenceAggregateLimit,
2949
4130
  assertReferencesDeletable,
4131
+ assertUniqueNodeIds,
2950
4132
  buildImportPlan,
4133
+ buildTemplateCode,
4134
+ buildUpgradePlan,
2951
4135
  bumpPatch,
2952
4136
  bumpPatchOrPassthrough,
2953
4137
  cancelTask,
2954
4138
  canonicalizeReferencePath,
2955
4139
  checkTermination,
2956
4140
  composeExportBundle,
4141
+ computeNodeContentHash,
4142
+ computeUpgradeStatus,
2957
4143
  createAgentRepo,
4144
+ createAuthAccountRepo,
4145
+ createAuthSessionRepo,
4146
+ createAuthTokenRepo,
2958
4147
  createDagTemplateRepo,
2959
4148
  createEnumRegistryRepo,
2960
4149
  createModelAliasRepo,
2961
4150
  createMongoClient,
4151
+ createNodeLibraryRepo,
4152
+ createNodePresetRepo,
2962
4153
  createProjectRepo,
2963
4154
  createReferencesSchema,
2964
4155
  createRepo,
2965
4156
  createSkillRepo,
2966
4157
  createTaskRepo,
2967
4158
  decisionKey,
4159
+ diffTemplateContent,
2968
4160
  enabledNeFalse,
2969
4161
  escapeRegExpLiteral,
2970
4162
  excerpt,
4163
+ findDuplicateNodeIds,
2971
4164
  findNextEdge,
2972
4165
  findTemplateByName,
2973
4166
  generateEntryId,
2974
4167
  generateProjectKey,
4168
+ generateSessionId,
4169
+ generateToken,
2975
4170
  hasProjectDefault,
4171
+ hashPassword,
4172
+ hashToken,
4173
+ isLegalTemplateCode,
2976
4174
  mkHistory,
4175
+ normalizeNameToTemplateCode,
4176
+ parseAuthEnabledEnvValue,
2977
4177
  pauseTask,
2978
4178
  ping,
2979
4179
  pruneInstance,
@@ -2983,6 +4183,8 @@ export {
2983
4183
  taskProgress,
2984
4184
  toNodeInfo,
2985
4185
  toTaskPublic,
4186
+ tokenTypeFromValue,
2986
4187
  trackMatchSet,
4188
+ verifyPassword,
2987
4189
  withTransaction
2988
4190
  };