@siming-org/server 0.3.0 → 0.4.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.
@@ -235,7 +235,10 @@ import {
235
235
  SkillCreateSchema,
236
236
  SkillUpdateSchema,
237
237
  SkillCopySchema,
238
+ AssetSetEnabledSchema,
238
239
  OBJECT_ID_HEX,
240
+ assertReferencesDeletable,
241
+ assertReferenceAggregateLimit,
239
242
  NotFoundError as NotFoundError2,
240
243
  ConflictError as ConflictError2,
241
244
  BadRequestError,
@@ -243,19 +246,26 @@ import {
243
246
  } from "@siming-org/core";
244
247
  var SkillListQuerySchema = z.object({
245
248
  projectId: z.string().optional(),
246
- scope: z.enum(["global", "project"]).optional()
249
+ scope: z.enum(["global", "project"]).optional(),
250
+ q: z.string().trim().min(1).max(100).optional(),
251
+ includeDisabled: z.stringbool().optional()
247
252
  }).refine((q) => !(q.scope === "project" && !q.projectId), {
248
253
  message: "scope=project requires projectId",
249
254
  path: ["projectId"]
250
255
  });
251
256
  var SkillByNameQuerySchema = z.object({
252
257
  scope: z.enum(["global", "project"]).optional(),
253
- projectId: z.string().regex(OBJECT_ID_HEX).optional()
258
+ projectId: z.string().regex(OBJECT_ID_HEX).optional(),
259
+ includeDisabled: z.stringbool().optional()
254
260
  }).superRefine((q, ctx) => {
255
261
  if (q.scope === "project" && !q.projectId) {
256
262
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: "scope=project requires projectId", path: ["projectId"] });
257
263
  }
258
264
  });
265
+ function skillToListItem(skill) {
266
+ const { references, ...rest } = skill;
267
+ return { ...rest, referenceCount: references?.length ?? 0 };
268
+ }
259
269
  function createSkillRoutes(client) {
260
270
  const app = new Hono4();
261
271
  const repo = createSkillRepo(client.db());
@@ -272,15 +282,18 @@ function createSkillRoutes(client) {
272
282
  };
273
283
  app.get("/api/skills", zValidator2("query", SkillListQuerySchema), async (c) => {
274
284
  const q = c.req.valid("query");
285
+ const opts = { includeDisabled: q.includeDisabled, q: q.q };
275
286
  if (q.scope) {
276
- return c.json(await repo.listSkillsByScopeFilter(q.scope, q.projectId));
287
+ return c.json((await repo.listSkillsByScopeFilter(q.scope, q.projectId, opts)).map(skillToListItem));
277
288
  }
278
- return c.json(q.projectId ? await repo.listSkillsByScope(q.projectId) : await repo.list());
289
+ return c.json(
290
+ (q.projectId ? await repo.listSkillsByScope(q.projectId, opts) : await repo.listSkills(opts)).map(skillToListItem)
291
+ );
279
292
  });
280
293
  app.get("/api/skills/:name", zValidator2("query", SkillByNameQuerySchema), async (c) => {
281
294
  const name = c.req.param("name");
282
295
  const q = c.req.valid("query");
283
- const skill = await resolveAssetByScope(repo, name, q);
296
+ const skill = await resolveAssetByScope(repo, name, q, { includeDisabled: q.includeDisabled });
284
297
  if (!skill) throw new NotFoundError2("skill", name);
285
298
  return c.json(skill);
286
299
  });
@@ -294,7 +307,7 @@ function createSkillRoutes(client) {
294
307
  const name = c.req.param("name");
295
308
  const data = c.req.valid("json");
296
309
  const q = c.req.valid("query");
297
- const existing = await resolveAssetByScope(repo, name, q);
310
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
298
311
  if (!existing) throw new NotFoundError2("skill", name);
299
312
  if (data.category !== void 0) await assertCategoryValid(data.category);
300
313
  if (isScopeMutation(existing, data)) {
@@ -307,6 +320,18 @@ function createSkillRoutes(client) {
307
320
  if ((data.scope ?? existing.scope) === "project" && effectiveProjectId) {
308
321
  await loadProjectForWrite(projectRepo, effectiveProjectId);
309
322
  }
323
+ assertReferencesDeletable({
324
+ resource: "skill",
325
+ name,
326
+ existingReferences: existing.references,
327
+ nextReferences: data.references,
328
+ nextMainBody: data.content,
329
+ currentMainBody: existing.content
330
+ });
331
+ assertReferenceAggregateLimit({
332
+ effectiveMainBody: data.content ?? existing.content,
333
+ nextReferences: data.references
334
+ });
310
335
  const skill = await repo.updateByNameScoped(name, q.scope ?? "global", q.projectId, data);
311
336
  if (!skill) throw new NotFoundError2("skill", name);
312
337
  return c.json(skill);
@@ -315,7 +340,7 @@ function createSkillRoutes(client) {
315
340
  const name = c.req.param("name");
316
341
  const input = c.req.valid("json");
317
342
  const q = c.req.valid("query");
318
- const existing = await resolveAssetByScope(repo, name, q);
343
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
319
344
  if (!existing) throw new NotFoundError2("skill", name);
320
345
  if (input.newScope === "project") {
321
346
  await loadProjectForWrite(projectRepo, input.targetProjectId);
@@ -331,12 +356,26 @@ function createSkillRoutes(client) {
331
356
  app.delete("/api/skills/:name", zValidator2("query", SkillByNameQuerySchema), async (c) => {
332
357
  const name = c.req.param("name");
333
358
  const q = c.req.valid("query");
334
- const existing = await resolveAssetByScope(repo, name, q);
359
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
335
360
  if (!existing) throw new NotFoundError2("skill", name);
336
361
  const deleted = await repo.deleteByNameScoped(name, q.scope ?? "global", q.projectId);
337
362
  if (!deleted) throw new NotFoundError2("skill", name);
338
363
  return c.body(null, 204);
339
364
  });
365
+ app.post("/api/skills/:name/enabled", zValidator2("query", SkillByNameQuerySchema), zValidator2("json", AssetSetEnabledSchema), async (c) => {
366
+ const name = c.req.param("name");
367
+ const body = c.req.valid("json");
368
+ const q = c.req.valid("query");
369
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
370
+ if (!existing) throw new NotFoundError2("skill", name);
371
+ const scope = q.scope ?? "global";
372
+ if (scope === "project") {
373
+ await loadProjectForWrite(projectRepo, existing.projectId ?? q.projectId);
374
+ }
375
+ const skill = await repo.updateByNameScoped(name, scope, q.projectId, { enabled: body.enabled });
376
+ if (!skill) throw new NotFoundError2("skill", name);
377
+ return c.json({ name: skill.name, enabled: skill.enabled ?? true });
378
+ });
340
379
  return app;
341
380
  }
342
381
  function isScopeMutation(existing, patch) {
@@ -344,8 +383,8 @@ function isScopeMutation(existing, patch) {
344
383
  if (patch.projectId !== void 0 && patch.projectId !== existing.projectId) return true;
345
384
  return false;
346
385
  }
347
- async function resolveAssetByScope(repo, name, q) {
348
- return repo.getByNameScoped(name, q.scope ?? "global", q.projectId);
386
+ async function resolveAssetByScope(repo, name, q, opts) {
387
+ return repo.getByNameScoped(name, q.scope ?? "global", q.projectId, opts);
349
388
  }
350
389
 
351
390
  // src/routes/agent.routes.ts
@@ -353,6 +392,7 @@ import { Hono as Hono5 } from "hono";
353
392
  import { zValidator as zValidator3 } from "@hono/zod-validator";
354
393
  import { z as z2 } from "zod";
355
394
  import {
395
+ bumpPatch,
356
396
  createAgentRepo,
357
397
  createSkillRepo as createSkillRepo2,
358
398
  createProjectRepo as createProjectRepo3,
@@ -360,15 +400,24 @@ import {
360
400
  AgentCreateSchema,
361
401
  AgentUpdateSchema,
362
402
  AgentCopySchema,
403
+ AssetSetEnabledSchema as AssetSetEnabledSchema2,
404
+ assertReferencesDeletable as assertReferencesDeletable2,
405
+ assertReferenceAggregateLimit as assertReferenceAggregateLimit2,
363
406
  NotFoundError as NotFoundError3,
364
407
  ConflictError as ConflictError3,
365
408
  BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES3,
366
409
  assertBoundSkillsCompatible,
367
410
  assertModelAliasExists
368
411
  } from "@siming-org/core";
412
+ function agentToListItem(agent) {
413
+ const { references, ...rest } = agent;
414
+ return { ...rest, referenceCount: references?.length ?? 0 };
415
+ }
369
416
  var AgentListQuerySchema = z2.object({
370
417
  projectId: z2.string().optional(),
371
- scope: z2.enum(["global", "project"]).optional()
418
+ scope: z2.enum(["global", "project"]).optional(),
419
+ q: z2.string().trim().min(1).max(100).optional(),
420
+ includeDisabled: z2.stringbool().optional()
372
421
  }).refine((q) => !(q.scope === "project" && !q.projectId), {
373
422
  message: "scope=project requires projectId",
374
423
  path: ["projectId"]
@@ -381,15 +430,18 @@ function createAgentRoutes(client) {
381
430
  const aliasRepo = createModelAliasRepo(client.db());
382
431
  app.get("/api/agents", zValidator3("query", AgentListQuerySchema), async (c) => {
383
432
  const q = c.req.valid("query");
433
+ const opts = { includeDisabled: q.includeDisabled, q: q.q };
384
434
  if (q.scope) {
385
- return c.json(await repo.listAgentsByScopeFilter(q.scope, q.projectId));
435
+ return c.json((await repo.listAgentsByScopeFilter(q.scope, q.projectId, opts)).map(agentToListItem));
386
436
  }
387
- return c.json(q.projectId ? await repo.listAgentsByScope(q.projectId) : await repo.list());
437
+ return c.json(
438
+ (q.projectId ? await repo.listAgentsByScope(q.projectId, opts) : await repo.listAgents(opts)).map(agentToListItem)
439
+ );
388
440
  });
389
441
  app.get("/api/agents/:name", zValidator3("query", SkillByNameQuerySchema), async (c) => {
390
442
  const name = c.req.param("name");
391
443
  const q = c.req.valid("query");
392
- const agent = await resolveAssetByScope(repo, name, q);
444
+ const agent = await resolveAssetByScope(repo, name, q, { includeDisabled: q.includeDisabled });
393
445
  if (!agent) throw new NotFoundError3("agent", name);
394
446
  return c.json(agent);
395
447
  });
@@ -404,8 +456,12 @@ function createAgentRoutes(client) {
404
456
  const name = c.req.param("name");
405
457
  const data = c.req.valid("json");
406
458
  const q = c.req.valid("query");
407
- const existing = await resolveAssetByScope(repo, name, q);
459
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
408
460
  if (!existing) throw new NotFoundError3("agent", name);
461
+ let payload = data;
462
+ if (data["function"] !== void 0 && data["function"] !== existing["function"]) {
463
+ payload = { ...data, version: bumpPatch(data.version ?? existing.version) };
464
+ }
409
465
  if (isScopeMutation(existing, data)) {
410
466
  throw new ConflictError3("agent", name, {
411
467
  bizCode: "SCOPE_IMMUTABLE",
@@ -427,7 +483,19 @@ function createAgentRoutes(client) {
427
483
  if (data.model !== void 0) {
428
484
  await assertModelAliasExists(aliasRepo, data.model);
429
485
  }
430
- const agent = await repo.updateByNameScoped(name, q.scope ?? "global", q.projectId, data);
486
+ assertReferencesDeletable2({
487
+ resource: "agent",
488
+ name,
489
+ existingReferences: existing.references,
490
+ nextReferences: data.references,
491
+ nextMainBody: data.systemPrompt,
492
+ currentMainBody: existing.systemPrompt
493
+ });
494
+ assertReferenceAggregateLimit2({
495
+ effectiveMainBody: data.systemPrompt ?? existing.systemPrompt,
496
+ nextReferences: data.references
497
+ });
498
+ const agent = await repo.updateByNameScoped(name, q.scope ?? "global", q.projectId, payload);
431
499
  if (!agent) throw new NotFoundError3("agent", name);
432
500
  return c.json(agent);
433
501
  });
@@ -435,7 +503,7 @@ function createAgentRoutes(client) {
435
503
  const name = c.req.param("name");
436
504
  const input = c.req.valid("json");
437
505
  const q = c.req.valid("query");
438
- const existing = await resolveAssetByScope(repo, name, q);
506
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
439
507
  if (!existing) throw new NotFoundError3("agent", name);
440
508
  if (input.newScope === "project") {
441
509
  await loadProjectForWrite(projectRepo, input.targetProjectId);
@@ -452,12 +520,26 @@ function createAgentRoutes(client) {
452
520
  app.delete("/api/agents/:name", zValidator3("query", SkillByNameQuerySchema), async (c) => {
453
521
  const name = c.req.param("name");
454
522
  const q = c.req.valid("query");
455
- const existing = await resolveAssetByScope(repo, name, q);
523
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
456
524
  if (!existing) throw new NotFoundError3("agent", name);
457
525
  const deleted = await repo.deleteByNameScoped(name, q.scope ?? "global", q.projectId);
458
526
  if (!deleted) throw new NotFoundError3("agent", name);
459
527
  return c.body(null, 204);
460
528
  });
529
+ app.post("/api/agents/:name/enabled", zValidator3("query", SkillByNameQuerySchema), zValidator3("json", AssetSetEnabledSchema2), async (c) => {
530
+ const name = c.req.param("name");
531
+ const body = c.req.valid("json");
532
+ const q = c.req.valid("query");
533
+ const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
534
+ if (!existing) throw new NotFoundError3("agent", name);
535
+ const scope = q.scope ?? "global";
536
+ if (scope === "project") {
537
+ await loadProjectForWrite(projectRepo, existing.projectId ?? q.projectId);
538
+ }
539
+ const agent = await repo.updateByNameScoped(name, scope, q.projectId, { enabled: body.enabled });
540
+ if (!agent) throw new NotFoundError3("agent", name);
541
+ return c.json({ name: agent.name, enabled: agent.enabled ?? true });
542
+ });
461
543
  return app;
462
544
  }
463
545
 
@@ -523,27 +605,44 @@ import { z as z3 } from "zod";
523
605
  import {
524
606
  createDagTemplateRepo,
525
607
  createProjectRepo as createProjectRepo4,
608
+ createSkillRepo as createSkillRepo3,
609
+ createAgentRepo as createAgentRepo2,
610
+ createModelAliasRepo as createModelAliasRepo3,
526
611
  DagTemplateCreateSchema,
527
612
  DagTemplateCopySchema,
528
613
  DagTemplateUpdateSchema,
614
+ AssetSetEnabledSchema as AssetSetEnabledSchema3,
615
+ ImportPlanRequestSchema,
616
+ ImportApplyRequestSchema,
617
+ composeExportBundle,
618
+ buildImportPlan,
619
+ applyImport,
529
620
  NotFoundError as NotFoundError5,
530
621
  ConflictError as ConflictError5,
531
622
  BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES5
532
623
  } from "@siming-org/core";
533
624
  var DagTemplateListQuerySchema = z3.object({
534
625
  projectId: z3.string().optional(),
535
- name: z3.string().optional()
626
+ name: z3.string().optional(),
627
+ includeDisabled: z3.stringbool().optional()
536
628
  });
537
629
  function createDagTemplateRoutes(client) {
538
630
  const app = new Hono7();
539
631
  const repo = createDagTemplateRepo(client.db());
540
632
  const projectRepo = createProjectRepo4(client.db());
633
+ const transferDeps = {
634
+ templateRepo: repo,
635
+ skillRepo: createSkillRepo3(client.db()),
636
+ agentRepo: createAgentRepo2(client.db()),
637
+ modelAliasRepo: createModelAliasRepo3(client.db())
638
+ };
541
639
  app.get("/api/dag/templates", zValidator5("query", DagTemplateListQuerySchema), async (c) => {
542
640
  const q = c.req.valid("query");
543
641
  return c.json(
544
642
  await repo.listDagTemplates({
545
643
  ...q.projectId ? { projectId: q.projectId } : {},
546
- ...q.name ? { name: q.name } : {}
644
+ ...q.name ? { name: q.name } : {},
645
+ ...q.includeDisabled !== void 0 ? { includeDisabled: q.includeDisabled } : {}
547
646
  })
548
647
  );
549
648
  });
@@ -553,6 +652,16 @@ function createDagTemplateRoutes(client) {
553
652
  if (!template) throw new NotFoundError5("dag-template", id);
554
653
  return c.json(template);
555
654
  });
655
+ app.post("/api/dag/templates/:id/enabled", zValidator5("json", AssetSetEnabledSchema3), async (c) => {
656
+ const id = c.req.param("id");
657
+ const body = c.req.valid("json");
658
+ const existing = await repo.getById(id);
659
+ if (!existing) throw new NotFoundError5("dag-template", id);
660
+ await loadProjectForWrite(projectRepo, existing.projectId);
661
+ const template = await repo.update(id, { enabled: body.enabled });
662
+ if (!template) throw new NotFoundError5("dag-template", id);
663
+ return c.json({ id: template.id, name: template.name, enabled: template.enabled ?? true });
664
+ });
556
665
  app.post("/api/dag/templates", zValidator5("json", DagTemplateCreateSchema), async (c) => {
557
666
  const data = c.req.valid("json");
558
667
  await loadProjectForWrite(projectRepo, data.projectId);
@@ -605,6 +714,20 @@ function createDagTemplateRoutes(client) {
605
714
  const copy = await repo.copyDagTemplate(id, input.targetProjectId, input.newName);
606
715
  return c.json(copy, 201);
607
716
  });
717
+ app.get("/api/dag/templates/:id/export", async (c) => {
718
+ const id = c.req.param("id");
719
+ return c.json(await composeExportBundle(transferDeps, id));
720
+ });
721
+ app.post("/api/dag/templates/import/plan", zValidator5("json", ImportPlanRequestSchema), async (c) => {
722
+ const body = c.req.valid("json");
723
+ await loadProjectForWrite(projectRepo, body.targetProjectId);
724
+ return c.json(await buildImportPlan(transferDeps, body.targetProjectId, body.bundle));
725
+ });
726
+ app.post("/api/dag/templates/import/apply", zValidator5("json", ImportApplyRequestSchema), async (c) => {
727
+ const body = c.req.valid("json");
728
+ await loadProjectForWrite(projectRepo, body.targetProjectId);
729
+ return c.json(await applyImport(transferDeps, body.targetProjectId, body.bundle, body.decisions));
730
+ });
608
731
  app.delete("/api/dag/templates/:id", async (c) => {
609
732
  const id = c.req.param("id");
610
733
  const deleted = await repo.delete(id);
@@ -627,6 +750,11 @@ import {
627
750
  ApproveRequestSchema,
628
751
  PauseRequestSchema,
629
752
  ResumeRequestSchema,
753
+ CancelRequestSchema,
754
+ taskProgress,
755
+ excerpt,
756
+ ContextViewSchema,
757
+ CONTEXT_VIEWS,
630
758
  ARTIFACT_TYPES,
631
759
  NODE_ID_PATTERN,
632
760
  createEnumRegistryRepo as createEnumRegistryRepo2,
@@ -639,6 +767,7 @@ import {
639
767
  approveTask,
640
768
  pauseTask,
641
769
  resumeTask,
770
+ cancelTask,
642
771
  renderPrompt,
643
772
  toNodeInfo,
644
773
  toTaskPublic,
@@ -649,6 +778,7 @@ var TaskListQuerySchema = z4.object({
649
778
  status: z4.string().optional(),
650
779
  track: z4.string().optional(),
651
780
  projectId: z4.string().optional(),
781
+ q: z4.string().trim().min(1).max(100).optional(),
652
782
  page: z4.coerce.number().int().min(1).default(1),
653
783
  limit: z4.coerce.number().int().min(1).max(500).default(20),
654
784
  sort: z4.enum(["progress", "createdAt", "updatedAt"]).optional()
@@ -701,6 +831,30 @@ function normalizeNodeRecords(records) {
701
831
  }
702
832
  return out;
703
833
  }
834
+ function stripArtifactContent(records) {
835
+ const out = {};
836
+ for (const [nodeId, record] of Object.entries(records)) {
837
+ out[nodeId] = {
838
+ ...record,
839
+ artifacts: record.artifacts.map((a) => {
840
+ if (a.content === void 0) return a;
841
+ const { content: _stripped, ...rest } = a;
842
+ return rest;
843
+ })
844
+ };
845
+ }
846
+ return out;
847
+ }
848
+ function writeAck(entity, updated, entry, echo) {
849
+ return {
850
+ taskId: entity.taskId,
851
+ updated,
852
+ ...entry !== void 0 ? { entry } : {},
853
+ ...echo !== void 0 ? { echo } : {},
854
+ updatedAt: entity.updatedAt ?? /* @__PURE__ */ new Date(),
855
+ task: { ...toTaskPublic(entity), projectId: entity.projectId, progress: taskProgress(entity) }
856
+ };
857
+ }
704
858
  function createTaskRoutes(client) {
705
859
  const app = new Hono8();
706
860
  const repo = createTaskRepo(client.db());
@@ -713,6 +867,7 @@ function createTaskRoutes(client) {
713
867
  ...q.status ? { status: q.status } : {},
714
868
  ...q.track ? { track: q.track } : {},
715
869
  ...q.projectId ? { projectId: q.projectId } : {},
870
+ ...q.q !== void 0 ? { q: q.q } : {},
716
871
  page: q.page,
717
872
  limit: q.limit,
718
873
  ...q.sort ? { sort: q.sort } : {}
@@ -739,6 +894,12 @@ function createTaskRoutes(client) {
739
894
  assertTypeTrackCompatible(input.type, input.track);
740
895
  const template = await templateRepo.getById(input.dagTemplateId);
741
896
  if (!template) throw new NotFoundError6("dag-template", input.dagTemplateId);
897
+ if (template.enabled === false) {
898
+ throw new ConflictError6("task", input.dagTemplateId, {
899
+ bizCode: "TEMPLATE_DISABLED",
900
+ message: BIZ_CODE_MESSAGES6.TEMPLATE_DISABLED
901
+ });
902
+ }
742
903
  if (input.projectId && input.projectId !== template.projectId) {
743
904
  throw new ConflictError6("task", input.dagTemplateId, {
744
905
  bizCode: "TEMPLATE_PROJECT_MISMATCH",
@@ -747,16 +908,33 @@ function createTaskRoutes(client) {
747
908
  }
748
909
  const resolvedProjectId = input.projectId ?? template.projectId;
749
910
  await loadProjectForWrite(projectRepo, resolvedProjectId);
750
- return c.json(await repo.createTask(input, template, resolvedProjectId), 201);
911
+ const created = await repo.createTask(input, template, resolvedProjectId);
912
+ const firstActive = created.dagInstance.nodes.find(
913
+ (n) => created.dagInstance.nodeStates[n.id]?.status === "active"
914
+ );
915
+ if (!firstActive) {
916
+ throw new BadRequestError2(
917
+ `task has no active node to start (all nodes pruned by skipNodes); \u6A21\u677F ${template.name} \u526A\u679D\u540E\u65E0\u53EF\u5F00\u5DE5\u8282\u70B9\uFF0C\u521B\u5EFA\u88AB\u62D2\u7EDD`
918
+ );
919
+ }
920
+ const response = {
921
+ task: { ...toTaskPublic(created), projectId: created.projectId, progress: taskProgress(created) },
922
+ firstNode: toNodeInfo(firstActive, created)
923
+ };
924
+ return c.json(response, 201);
751
925
  });
752
926
  app.patch("/api/tasks/:taskId", zValidator6("json", TaskPatchSchema), async (c) => {
753
927
  const taskId = c.req.param("taskId");
754
928
  const body = c.req.valid("json");
929
+ if (body.title === void 0) {
930
+ throw new BadRequestError2("PATCH /api/tasks/:taskId \u9700\u8981\u81F3\u5C11\u4E00\u4E2A\u53EF\u66F4\u65B0\u5B57\u6BB5\uFF08title\uFF09");
931
+ }
755
932
  await assertTaskProjectActive(repo, projectRepo, taskId);
756
- const patch = {
757
- ...body.title !== void 0 ? { title: body.title } : {}
758
- };
759
- return c.json(await repo.updateTask(taskId, patch));
933
+ const patch = { title: body.title };
934
+ const updatedTask = await repo.updateTask(taskId, patch);
935
+ return c.json(
936
+ writeAck(updatedTask, ["title"], void 0, [{ path: "title", excerpt: excerpt(updatedTask.title) }])
937
+ );
760
938
  });
761
939
  app.post("/api/tasks/:taskId/advance", zValidator6("json", AdvanceRequestSchema), async (c) => {
762
940
  const taskId = c.req.param("taskId");
@@ -782,14 +960,31 @@ function createTaskRoutes(client) {
782
960
  const body = c.req.valid("json");
783
961
  await assertTaskProjectActive(repo, projectRepo, taskId);
784
962
  const result = await pauseTask(mkDeps(repo), taskId, body.reason);
785
- return c.json(result, 200);
963
+ return c.json(writeAck(result, ["status", "pausedAt"], void 0, [{ path: "status", excerpt: result.status }]));
786
964
  });
787
965
  app.post("/api/tasks/:taskId/resume", zValidator6("json", ResumeRequestSchema), async (c) => {
788
966
  const taskId = c.req.param("taskId");
789
967
  const body = c.req.valid("json");
790
968
  await assertTaskProjectActive(repo, projectRepo, taskId);
791
969
  const result = await resumeTask(mkDeps(repo), taskId, body.decision);
792
- return c.json(result, 200);
970
+ return c.json(writeAck(result, ["status"], void 0, [{ path: "status", excerpt: `${result.status}\uFF08\u5DF2\u6062\u590D\u624B\u52A8\u6682\u505C\uFF09` }]));
971
+ });
972
+ app.post("/api/tasks/:taskId/cancel", zValidator6("json", CancelRequestSchema), async (c) => {
973
+ const taskId = c.req.param("taskId");
974
+ const body = c.req.valid("json");
975
+ await assertTaskProjectActive(repo, projectRepo, taskId);
976
+ const result = await cancelTask(mkDeps(repo), taskId, body.reason);
977
+ return c.json(
978
+ writeAck(
979
+ result,
980
+ ["status", "pausedAt"],
981
+ void 0,
982
+ [
983
+ { path: "status", excerpt: result.status },
984
+ ...body.reason !== void 0 ? [{ path: "history.reason", excerpt: excerpt(body.reason) }] : []
985
+ ]
986
+ )
987
+ );
793
988
  });
794
989
  app.get("/api/tasks/:taskId/history", async (c) => {
795
990
  const taskId = c.req.param("taskId");
@@ -808,6 +1003,13 @@ function createTaskRoutes(client) {
808
1003
  });
809
1004
  app.get("/api/tasks/:taskId/context", async (c) => {
810
1005
  const taskId = c.req.param("taskId");
1006
+ const viewR = ContextViewSchema.safeParse(c.req.query("view") ?? "basic");
1007
+ if (!viewR.success) {
1008
+ throw new BadRequestError2(
1009
+ `context view '${c.req.query("view")}' is invalid. Valid values: ${CONTEXT_VIEWS.join(", ")}`
1010
+ );
1011
+ }
1012
+ const view = viewR.data;
811
1013
  const task = await repo.getByTaskId(taskId);
812
1014
  if (!task) throw new NotFoundError6("task", taskId);
813
1015
  const currentNodeActive = task.status === "active" ? task.dagInstance.nodes.find((n) => n.id === task.currentNode) : void 0;
@@ -839,7 +1041,7 @@ function createTaskRoutes(client) {
839
1041
  // (toEntity 不跑 parse 补 default——点路径 $set/$push 只写触及字段,record/doc 内数组
840
1042
  // 可能缺键,读侧按字段归一到 schema 目标形状,Web/CLI 消费方拿到的恒为完整形)
841
1043
  taskDoc: normalizeTaskDoc(task.doc),
842
- nodeRecords: normalizeNodeRecords(task.nodeRecords),
1044
+ nodeRecords: view === "basic" ? stripArtifactContent(normalizeNodeRecords(task.nodeRecords)) : normalizeNodeRecords(task.nodeRecords),
843
1045
  archNotes: task.archNotes ?? []
844
1046
  };
845
1047
  return c.json(context);
@@ -852,28 +1054,49 @@ function createTaskRoutes(client) {
852
1054
  if (body.what !== void 0) sets["doc.what"] = body.what;
853
1055
  if (body.why !== void 0) sets["doc.why"] = body.why;
854
1056
  if (body.trackNote !== void 0) sets["doc.trackNote"] = body.trackNote;
855
- return c.json(await repo.updateTaskPaths(taskId, { sets }));
1057
+ const updatedDoc = await repo.updateTaskPaths(taskId, { sets });
1058
+ return c.json(
1059
+ writeAck(
1060
+ updatedDoc,
1061
+ Object.keys(sets),
1062
+ void 0,
1063
+ Object.entries(sets).map(([path, value]) => ({ path, excerpt: excerpt(String(value)) }))
1064
+ )
1065
+ );
856
1066
  });
857
1067
  app.post("/api/tasks/:taskId/doc/acceptance", zValidator6("json", TextItemSchema), async (c) => {
858
1068
  const taskId = c.req.param("taskId");
859
1069
  const body = c.req.valid("json");
860
1070
  await loadTaskForRecordWrite(repo, projectRepo, taskId);
861
- return c.json(await repo.updateTaskPaths(taskId, { pushes: { "doc.acceptance": body.text } }));
1071
+ const updatedAcc = await repo.updateTaskPaths(taskId, { pushes: { "doc.acceptance": body.text } });
1072
+ return c.json(
1073
+ writeAck(updatedAcc, ["doc.acceptance"], void 0, [
1074
+ { path: "doc.acceptance", excerpt: excerpt(body.text) }
1075
+ ])
1076
+ );
862
1077
  });
863
1078
  app.post("/api/tasks/:taskId/doc/non-goal", zValidator6("json", TextItemSchema), async (c) => {
864
1079
  const taskId = c.req.param("taskId");
865
1080
  const body = c.req.valid("json");
866
1081
  await loadTaskForRecordWrite(repo, projectRepo, taskId);
867
- return c.json(await repo.updateTaskPaths(taskId, { pushes: { "doc.nonGoals": body.text } }));
1082
+ const updatedNg = await repo.updateTaskPaths(taskId, { pushes: { "doc.nonGoals": body.text } });
1083
+ return c.json(
1084
+ writeAck(updatedNg, ["doc.nonGoals"], void 0, [{ path: "doc.nonGoals", excerpt: excerpt(body.text) }])
1085
+ );
868
1086
  });
869
1087
  app.patch("/api/tasks/:taskId/records/:nodeId/summary", zValidator6("json", RecordSummarySchema), async (c) => {
870
1088
  const taskId = c.req.param("taskId");
871
1089
  const nodeId = c.req.param("nodeId");
872
1090
  const body = c.req.valid("json");
873
1091
  await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
874
- return c.json(await repo.updateTaskPaths(taskId, {
1092
+ const updatedSummary = await repo.updateTaskPaths(taskId, {
875
1093
  sets: { [`nodeRecords.${nodeId}.summary`]: body.summary }
876
- }));
1094
+ });
1095
+ return c.json(
1096
+ writeAck(updatedSummary, [`nodeRecords.${nodeId}.summary`], void 0, [
1097
+ { path: `nodeRecords.${nodeId}.summary`, excerpt: excerpt(body.summary) }
1098
+ ])
1099
+ );
877
1100
  });
878
1101
  app.post("/api/tasks/:taskId/records/:nodeId/checks", zValidator6("json", CheckAddSchema), async (c) => {
879
1102
  const taskId = c.req.param("taskId");
@@ -886,9 +1109,17 @@ function createTaskRoutes(client) {
886
1109
  item: body.item,
887
1110
  passed: body.passed ?? true
888
1111
  };
889
- return c.json(await repo.updateTaskPaths(taskId, {
1112
+ const updatedCheck = await repo.updateTaskPaths(taskId, {
890
1113
  pushes: { [`nodeRecords.${nodeId}.checks`]: check }
891
- }));
1114
+ });
1115
+ return c.json(
1116
+ writeAck(
1117
+ updatedCheck,
1118
+ [`nodeRecords.${nodeId}.checks`],
1119
+ { id: check.id, kind: "check" },
1120
+ [{ path: `nodeRecords.${nodeId}.checks`, excerpt: excerpt(`${check.item}\uFF08passed=${String(check.passed)}\uFF09`) }]
1121
+ )
1122
+ );
892
1123
  });
893
1124
  app.patch("/api/tasks/:taskId/records/:nodeId/checks/:checkId", zValidator6("json", CheckPatchSchema), async (c) => {
894
1125
  const taskId = c.req.param("taskId");
@@ -903,10 +1134,15 @@ function createTaskRoutes(client) {
903
1134
  message: `${BIZ_CODE_MESSAGES6.CHECK_NOT_FOUND}\uFF08node ${nodeId}, check ${checkId}\uFF09`
904
1135
  });
905
1136
  }
906
- return c.json(await repo.updateTaskPaths(taskId, {
1137
+ const updatedFlip = await repo.updateTaskPaths(taskId, {
907
1138
  sets: { [`nodeRecords.${nodeId}.checks.$[e].passed`]: body.passed },
908
1139
  arrayFilters: [{ "e.id": checkId }]
909
- }));
1140
+ });
1141
+ return c.json(
1142
+ writeAck(updatedFlip, [`nodeRecords.${nodeId}.checks.${checkId}.passed`], void 0, [
1143
+ { path: `nodeRecords.${nodeId}.checks.${checkId}.passed`, excerpt: String(body.passed) }
1144
+ ])
1145
+ );
910
1146
  });
911
1147
  app.post("/api/tasks/:taskId/records/:nodeId/artifacts", zValidator6("json", ArtifactAddSchema), async (c) => {
912
1148
  const taskId = c.req.param("taskId");
@@ -920,9 +1156,24 @@ function createTaskRoutes(client) {
920
1156
  ...body.note !== void 0 ? { note: body.note } : {},
921
1157
  ...body.content !== void 0 ? { content: body.content } : {}
922
1158
  };
923
- return c.json(await repo.updateTaskPaths(taskId, {
1159
+ const updatedArtifact = await repo.updateTaskPaths(taskId, {
924
1160
  pushes: { [`nodeRecords.${nodeId}.artifacts`]: artifact }
925
- }));
1161
+ });
1162
+ return c.json(
1163
+ writeAck(
1164
+ updatedArtifact,
1165
+ [`nodeRecords.${nodeId}.artifacts`],
1166
+ { id: artifact.id, kind: "artifact" },
1167
+ [
1168
+ {
1169
+ path: `nodeRecords.${nodeId}.artifacts`,
1170
+ excerpt: excerpt(
1171
+ `${artifact.type} ${artifact.path}${body.content !== void 0 ? `\uFF08\u542B\u5168\u6587\u5FEB\u7167 ${body.content.length} \u5B57\u7B26\uFF09` : ""}`
1172
+ )
1173
+ }
1174
+ ]
1175
+ )
1176
+ );
926
1177
  });
927
1178
  app.post("/api/tasks/:taskId/records/:nodeId/confirmations", zValidator6("json", ConfirmAddSchema), async (c) => {
928
1179
  const taskId = c.req.param("taskId");
@@ -934,9 +1185,14 @@ function createTaskRoutes(client) {
934
1185
  quote: body.quote,
935
1186
  at: /* @__PURE__ */ new Date()
936
1187
  };
937
- return c.json(await repo.updateTaskPaths(taskId, {
1188
+ const updatedConfirm = await repo.updateTaskPaths(taskId, {
938
1189
  pushes: { [`nodeRecords.${nodeId}.confirmations`]: confirmation }
939
- }));
1190
+ });
1191
+ return c.json(
1192
+ writeAck(updatedConfirm, [`nodeRecords.${nodeId}.confirmations`], { id: confirmation.id, kind: "confirmation" }, [
1193
+ { path: `nodeRecords.${nodeId}.confirmations`, excerpt: excerpt(confirmation.quote) }
1194
+ ])
1195
+ );
940
1196
  });
941
1197
  app.post("/api/tasks/:taskId/records/:nodeId/decisions", zValidator6("json", DecisionAddSchema), async (c) => {
942
1198
  const taskId = c.req.param("taskId");
@@ -948,25 +1204,40 @@ function createTaskRoutes(client) {
948
1204
  topic: body.topic,
949
1205
  decision: body.decision
950
1206
  };
951
- return c.json(await repo.updateTaskPaths(taskId, {
1207
+ const updatedDecision = await repo.updateTaskPaths(taskId, {
952
1208
  pushes: { [`nodeRecords.${nodeId}.decisions`]: decision }
953
- }));
1209
+ });
1210
+ return c.json(
1211
+ writeAck(updatedDecision, [`nodeRecords.${nodeId}.decisions`], { id: decision.id, kind: "decision" }, [
1212
+ { path: `nodeRecords.${nodeId}.decisions`, excerpt: excerpt(`${decision.topic}: ${decision.decision}`) }
1213
+ ])
1214
+ );
954
1215
  });
955
1216
  app.put("/api/tasks/:taskId/records/:nodeId/review", zValidator6("json", ReviewSetSchema), async (c) => {
956
1217
  const taskId = c.req.param("taskId");
957
1218
  const nodeId = c.req.param("nodeId");
958
1219
  const body = c.req.valid("json");
959
1220
  await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
960
- return c.json(await repo.updateTaskPaths(taskId, {
1221
+ const updatedReview = await repo.updateTaskPaths(taskId, {
961
1222
  sets: { [`nodeRecords.${nodeId}.review`]: body }
962
- }));
1223
+ });
1224
+ return c.json(
1225
+ writeAck(updatedReview, [`nodeRecords.${nodeId}.review`], void 0, [
1226
+ { path: `nodeRecords.${nodeId}.review`, excerpt: `${body.verdict}\uFF08rounds=${String(body.rounds)}, critical=${String(body.critical)}\uFF09` }
1227
+ ])
1228
+ );
963
1229
  });
964
1230
  app.post("/api/tasks/:taskId/archnotes", zValidator6("json", TextItemSchema), async (c) => {
965
1231
  const taskId = c.req.param("taskId");
966
1232
  const body = c.req.valid("json");
967
1233
  const task = await loadTaskForRecordWrite(repo, projectRepo, taskId);
968
1234
  const note = { id: generateEntryId((task.archNotes ?? []).map((n) => n.id)), text: body.text, at: /* @__PURE__ */ new Date() };
969
- return c.json(await repo.updateTaskPaths(taskId, { pushes: { archNotes: note } }));
1235
+ const updatedNote = await repo.updateTaskPaths(taskId, { pushes: { archNotes: note } });
1236
+ return c.json(
1237
+ writeAck(updatedNote, ["archNotes"], { id: note.id, kind: "archnote" }, [
1238
+ { path: "archNotes", excerpt: excerpt(note.text) }
1239
+ ])
1240
+ );
970
1241
  });
971
1242
  return app;
972
1243
  }
@@ -1116,7 +1387,7 @@ import { serve } from "@hono/node-server";
1116
1387
  import { createMongoClient } from "@siming-org/core";
1117
1388
 
1118
1389
  // src/ensure-collections.ts
1119
- import { EnumRegistrySchema } from "@siming-org/core";
1390
+ import { EnumRegistrySchema, bumpPatch as bumpPatch2 } from "@siming-org/core";
1120
1391
  async function ensureCollections(client) {
1121
1392
  const db = client.db();
1122
1393
  await Promise.all([
@@ -1132,6 +1403,7 @@ async function ensureCollections(client) {
1132
1403
  await migrateGateRemovalData(db);
1133
1404
  await migrateTaskRecordData(db);
1134
1405
  await migrateDagTrackTaskValues(db);
1406
+ await migrateAgentFunctionBackfill(db);
1135
1407
  await ensurePostMigrationIndexes(db);
1136
1408
  }
1137
1409
  var ENUM_REGISTRY_SEEDS = {
@@ -1174,6 +1446,12 @@ var ENUM_REGISTRY_SEEDS = {
1174
1446
  scope: [
1175
1447
  { value: "global", label: "\u5168\u5C40", builtin: true },
1176
1448
  { value: "project", label: "\u9879\u76EE", builtin: true }
1449
+ ],
1450
+ // T202608260002 C3:schema 固定枚举为消费权威(AgentFunctionSchema),registry 仅展示(仿 pause_type 双轨)——
1451
+ // web 后续经 useEnumEntries('agent_function') 消费;两值 builtin(writer 落盘注入按 reviewer 判定,承重字段)
1452
+ agent_function: [
1453
+ { value: "reviewer", label: "\u5BA1\u67E5\uFF08\u843D\u76D8\u6CE8\u5165\u4FE1\u606F\u8FB9\u754C\uFF09", builtin: true },
1454
+ { value: "executor", label: "\u6267\u884C/\u901A\u7528\uFF08\u9ED8\u8BA4\uFF09", builtin: true }
1177
1455
  ]
1178
1456
  };
1179
1457
  function buildSeedEntries(seeds) {
@@ -1258,10 +1536,24 @@ async function ensureSkillCollection(db) {
1258
1536
  content: { bsonType: "string" },
1259
1537
  // N016 F9:category 放宽为任意字符串(枚举注册表为运行时权威,DB 只做类型兜底)
1260
1538
  category: { bsonType: "string" },
1539
+ // T202608270002:引用文档(可选,存量单文件资产无此字段合法)——路径规则/上限归应用层 Zod,DB 只做类型级声明
1540
+ references: {
1541
+ bsonType: "array",
1542
+ items: {
1543
+ bsonType: "object",
1544
+ required: ["path", "content"],
1545
+ properties: {
1546
+ path: { bsonType: "string", minLength: 1 },
1547
+ content: { bsonType: "string" }
1548
+ }
1549
+ }
1550
+ },
1261
1551
  // N012 F7:作用域字段(全局 / 项目专用);projectId 仅 scope=project 携带
1262
1552
  scope: { enum: ["global", "project"] },
1263
1553
  projectId: { bsonType: "string" },
1264
1554
  version: { bsonType: "string" },
1555
+ // T202608290001:资产启停(可选——enabled === false 即停用,缺失 = 启用,存量零迁移)
1556
+ enabled: { bsonType: "bool" },
1265
1557
  createdAt: { bsonType: "date" },
1266
1558
  updatedAt: { bsonType: "date" }
1267
1559
  }
@@ -1288,8 +1580,24 @@ async function ensureAgentCollection(db) {
1288
1580
  version: { bsonType: "string" },
1289
1581
  tools: { bsonType: "array", items: { bsonType: "string" } },
1290
1582
  permissions: { bsonType: "array", items: { bsonType: "string" } },
1583
+ // T202608270002:引用文档(可选,存量单文件资产无此字段合法)——路径规则/上限归应用层 Zod,DB 只做类型级声明
1584
+ references: {
1585
+ bsonType: "array",
1586
+ items: {
1587
+ bsonType: "object",
1588
+ required: ["path", "content"],
1589
+ properties: {
1590
+ path: { bsonType: "string", minLength: 1 },
1591
+ content: { bsonType: "string" }
1592
+ }
1593
+ }
1594
+ },
1291
1595
  scope: { enum: ["global", "project"] },
1292
1596
  projectId: { bsonType: "string" },
1597
+ // T202608260002:功能类型(可选——存量缺省合法,M12 回填)——取值域归应用层 Zod,DB 只做类型兜底
1598
+ "function": { bsonType: "string" },
1599
+ // T202608290001:资产启停(可选——enabled === false 即停用,缺失 = 启用,存量零迁移)
1600
+ enabled: { bsonType: "bool" },
1293
1601
  createdAt: { bsonType: "date" },
1294
1602
  updatedAt: { bsonType: "date" }
1295
1603
  }
@@ -1315,6 +1623,8 @@ async function ensureDagTemplateCollection(db) {
1315
1623
  edges: { bsonType: "array" },
1316
1624
  isDefault: { bsonType: "bool" },
1317
1625
  version: { bsonType: "string" },
1626
+ // T202608290001:模板启停(可选——enabled === false 即停用,缺失 = 启用,存量零迁移)
1627
+ enabled: { bsonType: "bool" },
1318
1628
  createdAt: { bsonType: "date" },
1319
1629
  updatedAt: { bsonType: "date" }
1320
1630
  }
@@ -1452,6 +1762,22 @@ async function migrateDagTrackTaskValues(db) {
1452
1762
  throw new Error("T202608240003 M11 \u8FC1\u79FB\u5931\u8D25\uFF1Adag_track doc \u66F4\u65B0\u672A\u547D\u4E2D\uFF08\u542F\u52A8\u671F\u4E0D\u5E94\u53D1\u751F\uFF09");
1453
1763
  }
1454
1764
  }
1765
+ async function migrateAgentFunctionBackfill(db) {
1766
+ const coll = db.collection("agents");
1767
+ const docs = await coll.find({ "function": { $exists: false } }, { projection: { version: 1 } }).toArray();
1768
+ if (docs.length === 0) return;
1769
+ console.log(`[siming] T202608260002 M12\uFF1Aagents.function \u56DE\u586B ${docs.length} \u4E2A\uFF08executor + version bump\uFF0C\u9A71\u52A8\u5DF2\u88C5\u73AF\u5883\u91CD\u5199\uFF09`);
1770
+ for (const doc of docs) {
1771
+ const baseVersion = typeof doc.version === "string" ? doc.version : "1.0.0";
1772
+ const result = await coll.updateOne(
1773
+ { _id: doc._id, "function": { $exists: false } },
1774
+ { $set: { "function": "executor", version: bumpPatch2(baseVersion) } }
1775
+ );
1776
+ if (result.matchedCount === 0) {
1777
+ throw new Error("T202608260002 M12 \u8FC1\u79FB\u5931\u8D25\uFF1Aagents.function \u56DE\u586B\u672A\u547D\u4E2D\uFF08\u542F\u52A8\u671F\u5355\u8FDB\u7A0B\u4E32\u884C\uFF0C\u4E0D\u5E94\u53D1\u751F\uFF09");
1778
+ }
1779
+ }
1780
+ }
1455
1781
  async function ensurePostMigrationIndexes(db) {
1456
1782
  const tasks = db.collection("tasks");
1457
1783
  const taskIndexes = await tasks.listIndexes().toArray();