@xdxer/dingtalk-agent 0.1.4 → 0.1.5-beta.10

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 (186) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/README.en.md +115 -89
  3. package/README.md +111 -86
  4. package/dist/bin/dingtalk-agent.js +742 -152
  5. package/dist/bin/dingtalk-agent.js.map +1 -1
  6. package/dist/src/actions.js +3 -2
  7. package/dist/src/actions.js.map +1 -1
  8. package/dist/src/agent-audit.js +202 -85
  9. package/dist/src/agent-audit.js.map +1 -1
  10. package/dist/src/agent-definition.js +7 -3
  11. package/dist/src/agent-definition.js.map +1 -1
  12. package/dist/src/agent-enhance.js +51 -32
  13. package/dist/src/agent-enhance.js.map +1 -1
  14. package/dist/src/agent-platform.js +4 -4
  15. package/dist/src/agent-platform.js.map +1 -1
  16. package/dist/src/bootstrap.js +6 -2
  17. package/dist/src/bootstrap.js.map +1 -1
  18. package/dist/src/development-workspace.js +210 -34
  19. package/dist/src/development-workspace.js.map +1 -1
  20. package/dist/src/doctor.js +65 -9
  21. package/dist/src/doctor.js.map +1 -1
  22. package/dist/src/dws.js +67 -3
  23. package/dist/src/dws.js.map +1 -1
  24. package/dist/src/init.js +2 -1
  25. package/dist/src/init.js.map +1 -1
  26. package/dist/src/memory/noop-receipt.js +306 -0
  27. package/dist/src/memory/noop-receipt.js.map +1 -0
  28. package/dist/src/memory/operational.js +27 -3
  29. package/dist/src/memory/operational.js.map +1 -1
  30. package/dist/src/memory/remote-state.js +2 -1
  31. package/dist/src/memory/remote-state.js.map +1 -1
  32. package/dist/src/multica-deploy.js +692 -125
  33. package/dist/src/multica-deploy.js.map +1 -1
  34. package/dist/src/multica-provider.js +303 -25
  35. package/dist/src/multica-provider.js.map +1 -1
  36. package/dist/src/multica-runtime-vocabulary.js +110 -0
  37. package/dist/src/multica-runtime-vocabulary.js.map +1 -0
  38. package/dist/src/opencode-evals.js +6 -6
  39. package/dist/src/opencode-evals.js.map +1 -1
  40. package/dist/src/opencode-provider.js +21 -7
  41. package/dist/src/opencode-provider.js.map +1 -1
  42. package/dist/src/opencode-workspace.js +3 -3
  43. package/dist/src/opencode-workspace.js.map +1 -1
  44. package/dist/src/personal-event-evals.js +4 -2
  45. package/dist/src/personal-event-evals.js.map +1 -1
  46. package/dist/src/promotion.js +2 -1
  47. package/dist/src/promotion.js.map +1 -1
  48. package/dist/src/remote-semantic-state-live-evals.js +14 -8
  49. package/dist/src/remote-semantic-state-live-evals.js.map +1 -1
  50. package/dist/src/remote-state-evals.js +2 -2
  51. package/dist/src/remote-state-evals.js.map +1 -1
  52. package/dist/src/robot-evals.js +3 -3
  53. package/dist/src/robot-evals.js.map +1 -1
  54. package/dist/src/schedule-plan.js +380 -0
  55. package/dist/src/schedule-plan.js.map +1 -0
  56. package/dist/src/sessions.js +1 -1
  57. package/dist/src/sessions.js.map +1 -1
  58. package/dist/src/skill-manager.js +145 -13
  59. package/dist/src/skill-manager.js.map +1 -1
  60. package/dist/src/skills.js +2 -0
  61. package/dist/src/skills.js.map +1 -1
  62. package/dist/src/tui.js +369 -0
  63. package/dist/src/tui.js.map +1 -0
  64. package/dist/src/upgrade.js +113 -33
  65. package/dist/src/upgrade.js.map +1 -1
  66. package/dist/src/waits.js +2 -1
  67. package/dist/src/waits.js.map +1 -1
  68. package/dist/src/workspace.js +12 -7
  69. package/dist/src/workspace.js.map +1 -1
  70. package/docs/AGENT-IN-PRODUCTION.md +255 -0
  71. package/docs/ARCHITECTURE.md +366 -0
  72. package/docs/INSTALLATION.md +8 -8
  73. package/docs/PLATFORM-GUARDRAILS.md +188 -0
  74. package/docs/PRIOR-ART.md +126 -0
  75. package/docs/SELF-TEST.md +182 -0
  76. package/docs/architecture/agent-platform-connection-layer.svg +120 -0
  77. package/docs/architecture/digital-employee-composition.svg +92 -0
  78. package/docs/architecture/dingtalk-agent-architecture.svg +125 -0
  79. package/docs/assets/digital-employee-at-work.svg +77 -0
  80. package/docs/schemas/multica-deployment-plan.schema.json +3 -1
  81. package/docs/schemas/multica-deployment-receipt.schema.json +17 -3
  82. package/docs/schemas/multica-deployment-status.schema.json +6 -2
  83. package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
  84. package/docs/schemas/multica-workspace-run-plan.schema.json +31 -0
  85. package/docs/schemas/multica-workspace-run.schema.json +161 -0
  86. package/docs/schemas/multica-workspace-status.schema.json +2 -0
  87. package/docs/schemas/project.schema.json +54 -3
  88. package/docs/schemas/workspace-scaffold.schema.json +38 -0
  89. package/examples/agents/README.md +45 -0
  90. package/examples/agents/fde-coach/AGENTS.md +2 -25
  91. package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
  92. package/examples/agents/fde-coach/agent.bindings.json +10 -0
  93. package/examples/agents/release-manager/AGENTS.md +2 -25
  94. package/examples/agents/release-manager/agent/AGENTS.md +35 -0
  95. package/examples/agents/release-manager/agent.bindings.json +10 -0
  96. package/lab/agent-eval/catalog.json +5 -5
  97. package/lab/agent-eval/classic-failures.json +4 -4
  98. package/lab/agent-eval/completion-gate-regression.json +9 -9
  99. package/lab/agent-eval/personal-event-live.example.json +3 -3
  100. package/lab/agent-eval/remote-semantic-state-live.example.json +1 -1
  101. package/lab/agent-eval/workspace/opencode.json +2 -2
  102. package/lab/project-workspace/fake-multica-provider.mjs +171 -17
  103. package/lab/project-workspace/multica-deploy.fixture.json +2 -2
  104. package/lab/project-workspace/multica-readonly.fixture.json +4 -16
  105. package/lab/project-workspace/opencode-provider-suite.json +3 -3
  106. package/lab/project-workspace/project.fixture.json +2 -6
  107. package/lab/robot-eval/suite.json +1 -1
  108. package/lab/robot-eval/workspace/AGENTS.md +1 -1
  109. package/lab/robot-eval/workspace/opencode.json +2 -2
  110. package/lab/schemas/personal-event-eval.schema.json +1 -1
  111. package/package.json +18 -11
  112. package/skills/README.md +10 -8
  113. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/SKILL.md +49 -24
  114. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/AGENTS.template.md +1 -1
  115. package/skills/core/dta-agent-compose/assets/REPOSITORY.template.md +10 -0
  116. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +2 -2
  117. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.local.template.json +2 -2
  118. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/hosts/opencode/opencode.template.json +3 -2
  119. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/evals/evals.json +4 -4
  120. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/agent-definition-contract.md +7 -7
  121. package/skills/core/dta-agent-compose/references/drive-and-schedules.md +166 -0
  122. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/host-loading-contract.md +11 -13
  123. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/claude-code.md +13 -12
  124. package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/opencode.md +14 -13
  125. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/SKILL.md +28 -3
  126. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/assets/eval-catalog.template.json +1 -1
  127. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/evals/evals.json +1 -1
  128. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/eval-topology.md +3 -3
  129. package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/interactive-debug-channels.md +10 -4
  130. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/SKILL.md +21 -5
  131. package/skills/core/dta-basic-behavior/references/event-to-behavior.md +38 -0
  132. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/memory-and-evolution.md +3 -1
  133. package/skills/core/dta-basic-behavior/references/perception-and-gates.md +87 -0
  134. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/risk-authority-and-privacy.md +12 -0
  135. package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/truth-and-recovery.md +4 -2
  136. package/skills/core/dta-people-group-memory/COMPLETENESS.md +36 -0
  137. package/skills/core/dta-people-group-memory/SKILL.md +69 -0
  138. package/skills/core/dta-people-group-memory/references/adapters.md +273 -0
  139. package/skills/core/dta-people-group-memory/references/assembly-guidance.md +40 -0
  140. package/skills/core/dta-people-group-memory/references/binding.md +110 -0
  141. package/skills/core/dta-people-group-memory/references/cold-start.md +70 -0
  142. package/skills/core/dta-people-group-memory/references/config-binding.md +89 -0
  143. package/skills/core/dta-people-group-memory/references/consent-and-visibility.md +83 -0
  144. package/skills/core/dta-people-group-memory/references/consolidation.md +162 -0
  145. package/skills/core/dta-people-group-memory/references/event-ingest.md +103 -0
  146. package/skills/core/dta-people-group-memory/references/guided-setup.md +70 -0
  147. package/skills/core/dta-people-group-memory/references/model.md +148 -0
  148. package/skills/core/dta-people-group-memory/references/storage-port.md +107 -0
  149. package/skills/platforms/deap/PLATFORM.md +30 -1
  150. package/skills/platforms/multica-dingtalk/PLATFORM.md +35 -9
  151. package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/SKILL.md +10 -8
  152. package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +67 -0
  153. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/SKILL.md +81 -11
  154. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/bootstrap.sh +2 -2
  155. package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/multica_ext.py +264 -16
  156. package/dist/src/map.js +0 -157
  157. package/dist/src/map.js.map +0 -1
  158. package/docs/SECOND-AGENT-ACCEPTANCE.md +0 -62
  159. package/docs/architecture/agent-memory-topology.png +0 -0
  160. package/docs/architecture/agent-memory-topology.svg +0 -132
  161. package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
  162. package/docs/architecture/durable-async-agent-runtime.png +0 -0
  163. package/docs/architecture/general-agent-kernel-topology.png +0 -0
  164. package/docs/architecture/provider-bound-development-workspace.png +0 -0
  165. package/docs/architecture/task-completion-gate.png +0 -0
  166. package/docs/assets/agent-delivery-lifecycle.svg +0 -103
  167. package/skills/core/dingtalk-basic-behavior/references/event-to-behavior.md +0 -24
  168. package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +0 -28
  169. package/skills/platforms/deap/README.md +0 -3
  170. package/skills/platforms/multica-dingtalk/dingtalk-agent-boot-multica/SKILL.md +0 -40
  171. package/skills/platforms/multica-dingtalk/dingtalk-agent-deploy-multica/references/multica-deployment-contract.md +0 -49
  172. /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
  173. /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
  174. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/role-skill.template.md +0 -0
  175. /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/storage-routing.md +0 -0
  176. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/evidence-contract.md +0 -0
  177. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/failure-to-case.md +0 -0
  178. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/local-connector-smoke.md +0 -0
  179. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/scenario-taxonomy.md +0 -0
  180. /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/storage-modes.md +0 -0
  181. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
  182. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/task-checkpoint.json +0 -0
  183. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/action-contract.md +0 -0
  184. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/runtime-modes.md +0 -0
  185. /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/task-lifecycle.md +0 -0
  186. /package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/references/promotion-observation-contract.md +0 -0
@@ -313,15 +313,33 @@ def cmd_runtime_create_fc(ctx: Ctx, args: argparse.Namespace) -> None:
313
313
  # The server resolves template_id against id, template, or name;
314
314
  # id is best-effort and may be absent.
315
315
  ref = lambda t: t.get("id") or t.get("template") or t.get("name") # noqa: E731
316
- if len(templates) > 1:
316
+ status_of = lambda t: str(t.get("status") or "").lower() # noqa: E731
317
+ # waiting 等未就绪状态选了也建不起来;status 字段缺失时保守放行(Observed 字段)。
318
+ usable = [t for t in templates if not status_of(t) or status_of(t) == "ready"]
319
+ if not usable:
317
320
  fail(
318
- "multiple templates available, pass --template-id: "
319
- + ", ".join(f"{ref(t)} ({t.get('name') or t.get('template')})" for t in templates)
321
+ "no ready FC-E2B templates (all "
322
+ + ", ".join(f"{ref(t)}={status_of(t) or 'unknown'}" for t in templates)
323
+ + ")"
320
324
  )
321
- template_id = ref(templates[0])
325
+ if len(usable) > 1:
326
+ newest = max(usable, key=lambda t: str(t.get("created_at") or ""))
327
+ fail(
328
+ "multiple ready templates, pass --template-id: "
329
+ + ", ".join(
330
+ f"{ref(t)} ({t.get('name') or t.get('template')}, "
331
+ f"{status_of(t) or 'unknown'}, created {t.get('created_at') or '?'})"
332
+ for t in sorted(usable, key=lambda t: str(t.get("created_at") or ""), reverse=True)
333
+ )
334
+ + f"; component_versions 通常完全相同,选最新 ready 即可(推荐 {ref(newest)})"
335
+ )
336
+ template_id = ref(usable[0])
322
337
  if not template_id:
323
338
  fail("template list entry has no usable identifier")
324
- note(f"using template {template_id} ({templates[0].get('name') or templates[0].get('template')})")
339
+ note(
340
+ f"using template {template_id} ({usable[0].get('name') or usable[0].get('template')}, "
341
+ f"{status_of(usable[0]) or 'unknown'})"
342
+ )
325
343
  body: Dict[str, Any] = {"template_id": template_id}
326
344
  for flag in ("name", "provider", "visibility"):
327
345
  value = getattr(args, flag, None)
@@ -397,7 +415,15 @@ def cmd_dingtalk_bind(ctx: Ctx, args: argparse.Namespace) -> None:
397
415
  "robot_code": args.robot_code or args.client_id,
398
416
  "allow_unbound": args.allow_unbound,
399
417
  }
400
- out(api(ctx, "POST", f"/api/workspaces/{ws_id}/dingtalk/install/manual", body=body))
418
+ res = api(ctx, "POST", f"/api/workspaces/{ws_id}/dingtalk/install/manual", body=body)
419
+ if not args.allow_unbound:
420
+ note(
421
+ "allow_unbound=false: senders whose DingTalk account is NOT bound to Multica "
422
+ "(including the robot owner) only get a binding prompt when they @ the robot. "
423
+ "For acceptance/customer-facing use, either run dingtalk-account-begin first or "
424
+ "re-bind with --allow-unbound (upsert; installation id unchanged)."
425
+ )
426
+ out(res)
401
427
 
402
428
 
403
429
  def cmd_dingtalk_begin(ctx: Ctx, args: argparse.Namespace) -> None:
@@ -450,7 +476,9 @@ def cmd_dingtalk_account_begin(ctx: Ctx, args: argparse.Namespace) -> None:
450
476
  ctx,
451
477
  "POST",
452
478
  f"/api/workspaces/{ws_id}/dingtalk/account-bindings/begin",
453
- body={"agent_id": args.agent},
479
+ # Server enforces binding_mode (400 invalid_binding_mode without it): message-only
480
+ # identity mapping vs delegated identity. Wire contract: begin reads it from the BODY.
481
+ body={"agent_id": args.agent, "binding_mode": args.binding_mode},
454
482
  )
455
483
  note(f"scan to bind account: {res.get('qr_code_url')}")
456
484
  out(res)
@@ -463,7 +491,14 @@ def cmd_dingtalk_account_list(ctx: Ctx, args: argparse.Namespace) -> None:
463
491
 
464
492
  def cmd_dingtalk_account_unbind(ctx: Ctx, args: argparse.Namespace) -> None:
465
493
  ws_id = resolve_workspace_id(ctx)
466
- api(ctx, "DELETE", f"/api/workspaces/{ws_id}/dingtalk/account-bindings/{args.installation}")
494
+ # Wire contract asymmetry (verified live): unbind reads binding_mode from the QUERY
495
+ # string; putting it in the DELETE body still returns 400 invalid_binding_mode.
496
+ api(
497
+ ctx,
498
+ "DELETE",
499
+ f"/api/workspaces/{ws_id}/dingtalk/account-bindings/{args.installation}"
500
+ f"?binding_mode={urllib.parse.quote(args.binding_mode)}",
501
+ )
467
502
  out({"unbound": True})
468
503
 
469
504
 
@@ -544,16 +579,115 @@ def cmd_task_snapshot(ctx: Ctx, args: argparse.Namespace) -> None:
544
579
  out(api(ctx, "GET", "/api/agent-task-snapshot", ws_header=True))
545
580
 
546
581
 
582
+ # dta completion-predicate 标记的唯一 parser(issue #40)。
583
+ # 与 TS 侧 schedule-plan.ts 的 COMPLETION_PREDICATE_MARKER 是同一正则的镜像——
584
+ # 改任一侧必须同步另一侧;两套各自手写的 parser 静默分叉正是注册表要消灭的事故。
585
+ PREDICATE_MARKER_RE = re.compile(
586
+ r"^\[dta-completion-predicate\] ([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)$"
587
+ )
588
+
589
+
590
+ def parse_predicate_marker(description: str) -> Dict[str, Any]:
591
+ """0 枚 → none;1 枚(或多枚一致)→ id;多枚不一致 → conflict;行首出现 marker
592
+ 前缀但 id 空/非法 → malformed(issue #45:意图已表达,折成 none 会静默跳过
593
+ 整个回读校验——fail-open)。响亮,不挑选、不降级。"""
594
+ ids: List[str] = []
595
+ raws: List[str] = []
596
+ for line in (description or "").splitlines():
597
+ if not line.startswith("[dta-completion-predicate]"):
598
+ continue
599
+ m = PREDICATE_MARKER_RE.match(line)
600
+ if m:
601
+ ids.append(m.group(1))
602
+ else:
603
+ raws.append(line)
604
+ if raws:
605
+ return {"kind": "malformed", "raws": raws}
606
+ if not ids:
607
+ return {"kind": "none"}
608
+ unique = sorted(set(ids))
609
+ if len(unique) == 1:
610
+ return {"kind": "id", "id": unique[0]}
611
+ return {"kind": "conflict", "ids": unique}
612
+
613
+
614
+ def load_predicate_registry(manifest_path: str) -> Dict[str, Any]:
615
+ """读 dingtalk-agent.json 顶层 predicates——与声明侧同一份文件、同一批 id,
616
+ 不另建第二份判据源。"""
617
+ try:
618
+ with open(manifest_path, "r", encoding="utf-8") as f:
619
+ manifest = json.load(f)
620
+ except OSError as e:
621
+ fail(f"manifest 读不到: {manifest_path} ({e})")
622
+ except json.JSONDecodeError as e:
623
+ fail(f"manifest 不是合法 JSON: {manifest_path} ({e})")
624
+ predicates = manifest.get("predicates")
625
+ if predicates is None:
626
+ fail(f"manifest 未声明 predicates 注册表: {manifest_path}")
627
+ if not isinstance(predicates, dict):
628
+ fail("manifest 的 predicates 必须是 object")
629
+ return predicates
630
+
631
+
632
+ LOCAL_COMMANDS = {"predicate-resolve"}
633
+
634
+
635
+ def cmd_predicate_resolve(ctx: Optional[Ctx], args: argparse.Namespace) -> None:
636
+ """运行时路径:拿到 autopilot description(或直接给 id)→ 在同一份 manifest
637
+ 注册表里解析出判据正文。未注册 / marker 缺失 / 冲突都非零退出——判据解析
638
+ 失败时这一拍不能假装知道怎么算完成。"""
639
+ predicates = load_predicate_registry(args.manifest)
640
+ if args.id:
641
+ pid = args.id
642
+ source = "flag"
643
+ else:
644
+ parsed = parse_predicate_marker(args.description or "")
645
+ if parsed["kind"] == "none":
646
+ fail("description 里没有 [dta-completion-predicate] 标记")
647
+ if parsed["kind"] == "conflict":
648
+ fail(f"description 里有多枚不一致的 predicate 标记: {parsed['ids']}")
649
+ if parsed["kind"] == "malformed":
650
+ fail(f"description 的 predicate 标记格式非法: {parsed['raws']}")
651
+ pid = parsed["id"]
652
+ source = "description-marker"
653
+ entry = predicates.get(pid)
654
+ if entry is None:
655
+ fail(f"predicate:{pid} 未在 manifest 注册表登记(registered={sorted(predicates)})")
656
+ out({"id": pid, "resolvedFrom": source, "predicate": entry})
657
+
658
+
659
+ def _annotate_predicate_ref(autopilot: Any) -> Any:
660
+ """对账消费面:把 description 里解析出的判据标记贴在对象旁,漂移在 list/get 就可见。"""
661
+ if isinstance(autopilot, dict) and "description" in autopilot:
662
+ parsed = parse_predicate_marker(str(autopilot.get("description") or ""))
663
+ if parsed["kind"] != "none":
664
+ autopilot["predicateRef"] = parsed
665
+ return autopilot
666
+
667
+
547
668
  def cmd_autopilot_list(ctx: Ctx, args: argparse.Namespace) -> None:
548
669
  query = f"?status={args.status}" if args.status else ""
549
- out(api(ctx, "GET", f"/api/autopilots{query}", ws_header=True))
670
+ result = api(ctx, "GET", f"/api/autopilots{query}", ws_header=True)
671
+ if isinstance(result, list):
672
+ result = [_annotate_predicate_ref(item) for item in result]
673
+ out(result)
550
674
 
551
675
 
552
676
  def cmd_autopilot_get(ctx: Ctx, args: argparse.Namespace) -> None:
553
- out(api(ctx, "GET", f"/api/autopilots/{args.autopilot}", ws_header=True))
677
+ out(_annotate_predicate_ref(api(ctx, "GET", f"/api/autopilots/{args.autopilot}", ws_header=True)))
554
678
 
555
679
 
556
680
  def cmd_autopilot_create(ctx: Ctx, args: argparse.Namespace) -> None:
681
+ # 本地能发现的问题必须在任何 API 调用之前拦下(issue #45):create 非幂等,
682
+ # POST 之后才报错会留下孤儿 autopilot,还要 operator 事后收拾。
683
+ declared_marker = parse_predicate_marker(args.description or "")
684
+ if declared_marker["kind"] == "conflict":
685
+ fail(f"--description 里有多枚不一致的 predicate 标记: {declared_marker['ids']}(零写入拒绝)")
686
+ if declared_marker["kind"] == "malformed":
687
+ fail(
688
+ f"--description 的 predicate 标记格式非法: {declared_marker['raws']}"
689
+ "(需要独立一行 [dta-completion-predicate] <kebab-id>;零写入拒绝)"
690
+ )
557
691
  body: Dict[str, Any] = {
558
692
  "title": args.title,
559
693
  "assignee_id": args.agent,
@@ -564,7 +698,37 @@ def cmd_autopilot_create(ctx: Ctx, args: argparse.Namespace) -> None:
564
698
  if args.issue_title_template:
565
699
  body["issue_title_template"] = args.issue_title_template
566
700
  autopilot = api(ctx, "POST", "/api/autopilots", body=body, ws_header=True)
567
- result: Dict[str, Any] = {"autopilot": autopilot}
701
+ # create 非幂等且响应可能只是请求回显——真相只能独立回读(issue #40)。
702
+ autopilot_id = autopilot.get("id")
703
+ if not autopilot_id:
704
+ fail("autopilot-create 响应缺 id,无法独立回读核对——不视为创建成功")
705
+ readback = api(ctx, "GET", f"/api/autopilots/{autopilot_id}", ws_header=True)
706
+ result: Dict[str, Any] = {"autopilot": autopilot, "readback": readback}
707
+ if declared_marker["kind"] == "id":
708
+ remote_marker = parse_predicate_marker(str(readback.get("description") or ""))
709
+ verified = remote_marker == declared_marker
710
+ reason = (
711
+ "" if verified
712
+ else "remote-marker-missing" if remote_marker["kind"] == "none"
713
+ else "remote-marker-conflict" if remote_marker["kind"] == "conflict"
714
+ else "remote-marker-malformed" if remote_marker["kind"] == "malformed"
715
+ else "remote-marker-mismatch"
716
+ )
717
+ result["predicateRef"] = {
718
+ "declared": declared_marker["id"],
719
+ "remote": remote_marker.get("id") or remote_marker.get("ids") or None,
720
+ "verified": verified,
721
+ **({"reason": reason} if reason else {}),
722
+ }
723
+ if not verified:
724
+ # fail-closed:平台丢弃/改写了判据标记,运行时将读不到声明的判据 ID。
725
+ # autopilot 已经建出来了(result 带回读证据),但不能当部署成功交差。
726
+ out(result)
727
+ fail(
728
+ f"autopilot {autopilot_id} 回读的 description 判据标记与声明不符"
729
+ f"(declared=predicate:{declared_marker['id']}, {reason})——"
730
+ "请人工核对该 autopilot(必要时 autopilot-set-status --status paused)"
731
+ )
568
732
  if args.cron:
569
733
  trigger_body = {"kind": "schedule", "cron_expression": args.cron, "timezone": args.timezone}
570
734
  trigger = api(
@@ -675,6 +839,38 @@ def cmd_agent_check(ctx: Ctx, args: argparse.Namespace) -> None:
675
839
  json.dumps(account.get("dws_identity"), ensure_ascii=False) if account else "no account binding",
676
840
  )
677
841
 
842
+ # 基础设施健康 ≠ 用户可用:机器人未绑/账号 unbound 时用户发消息只会收到绑定引导、
843
+ # 不产生任何 Run,但没有一项是 "fail"。旧的单布尔 `healthy` 正是把这两层混成一团、
844
+ # 让 operator 看着全绿而用户侧完全不工作的祸根——按验收证据(P-01/M-06)拆开并移除。
845
+ infra_checks = {"agent", "runtime", "last_task", "recent_tasks"}
846
+ infrastructure_healthy = all(
847
+ c["status"] != "fail" for c in checks if c["check"] in infra_checks
848
+ )
849
+ robot_ok = bool(robot)
850
+ account_bound = bool(account and (account.get("dws_identity") or {}).get("status") == "bound")
851
+ user_ready = infrastructure_healthy and robot_ok and account_bound
852
+ reasons = [
853
+ f"{c['check']}: {c['detail']}"
854
+ for c in checks
855
+ if (c["check"] in infra_checks and c["status"] == "fail")
856
+ or (c["check"] in ("dingtalk_robot", "dingtalk_account") and c["status"] != "ok")
857
+ ]
858
+ if agent.get("archived_at"):
859
+ next_action = "agent restore(或重新部署)后再检查"
860
+ elif runtime_health in ("missing", "offline"):
861
+ next_action = "runtime-list 核对;必要时 runtime-create-fc --provider opencode 重建并迁移"
862
+ elif not robot_ok:
863
+ next_action = (
864
+ f"python3 multica_ext.py dingtalk-bind --agent {args.agent} "
865
+ "--client-id <AppKey> --client-secret-stdin(或 dingtalk-begin 扫码)"
866
+ )
867
+ elif not account_bound:
868
+ next_action = (
869
+ f"python3 multica_ext.py dingtalk-account-begin --agent {args.agent} "
870
+ "--binding-mode message(或重绑机器人加 --allow-unbound)"
871
+ )
872
+ else:
873
+ next_action = ""
678
874
  out({
679
875
  "agent": {k: agent.get(k) for k in ("id", "name", "status", "model", "runtime_id", "archived_at")},
680
876
  "runtime": {
@@ -683,7 +879,14 @@ def cmd_agent_check(ctx: Ctx, args: argparse.Namespace) -> None:
683
879
  },
684
880
  "tasks": {"active": len(active), "last_outcome_status": (last_outcome or {}).get("status")},
685
881
  "checks": checks,
686
- "healthy": all(c["status"] != "fail" for c in checks),
882
+ "infrastructureHealthy": infrastructure_healthy,
883
+ "userReady": user_ready,
884
+ "reasons": reasons,
885
+ "nextAction": next_action,
886
+ "userReadyCaveat": (
887
+ "安装回读不含 effective allow_unbound(上游缺口 MUL-005):若绑定时开启过 "
888
+ "allow_unbound,未绑定账号的发送者实际也能对话"
889
+ ),
687
890
  "warnings": sum(1 for c in checks if c["status"] == "warn"),
688
891
  })
689
892
 
@@ -936,11 +1139,19 @@ def build_parser() -> argparse.ArgumentParser:
936
1139
  p = cmd(
937
1140
  "runtime-create-fc",
938
1141
  cmd_runtime_create_fc,
939
- "Create an FC-E2B cloud runtime; picks the sole available template when --template-id is omitted",
1142
+ "Create an FC-E2B cloud runtime (provider defaults to opencode); "
1143
+ "picks the sole READY template when --template-id is omitted",
940
1144
  )
941
1145
  p.add_argument("--template-id", dest="template_id")
942
1146
  p.add_argument("--name")
943
- p.add_argument("--provider")
1147
+ p.add_argument(
1148
+ "--provider",
1149
+ default="opencode",
1150
+ help="runtime provider; defaults to opencode — the only provider dta's skill-loading "
1151
+ "contract supports. The SERVER default is hermes, where workspace skills never load "
1152
+ "(agents report 'Skill not found' with zero warning); pass --provider hermes only if "
1153
+ "you explicitly want that",
1154
+ )
944
1155
  p.add_argument("--visibility", choices=["private", "public"])
945
1156
 
946
1157
  p = cmd("agent-list", cmd_agent_list, "List agents in the workspace")
@@ -984,7 +1195,14 @@ def build_parser() -> argparse.ArgumentParser:
984
1195
  p.add_argument("--client-secret", dest="client_secret", help="DingTalk AppSecret (prefer stdin/env)")
985
1196
  p.add_argument("--client-secret-stdin", dest="client_secret_stdin", action="store_true")
986
1197
  p.add_argument("--client-secret-env", dest="client_secret_env", metavar="VAR")
987
- p.add_argument("--allow-unbound", dest="allow_unbound", action="store_true")
1198
+ p.add_argument(
1199
+ "--allow-unbound",
1200
+ dest="allow_unbound",
1201
+ action="store_true",
1202
+ help="serve senders whose DingTalk account is not bound to Multica "
1203
+ "(connect-to-customers). WITHOUT this flag such senders — the robot owner "
1204
+ "included — only receive a binding prompt",
1205
+ )
988
1206
 
989
1207
  p = cmd(
990
1208
  "dingtalk-begin",
@@ -1000,6 +1218,15 @@ def build_parser() -> argparse.ArgumentParser:
1000
1218
 
1001
1219
  p = cmd("dingtalk-account-begin", cmd_dingtalk_account_begin, "Start binding a personal DingTalk account to an agent")
1002
1220
  p.add_argument("--agent", required=True, help="agent uuid")
1221
+ p.add_argument(
1222
+ "--binding-mode",
1223
+ dest="binding_mode",
1224
+ choices=("message", "identity"),
1225
+ default="message",
1226
+ help="binding capability: message-only identity mapping (default), or delegated "
1227
+ "DingTalk identity — identity grants a persistent execution identity, never pick "
1228
+ "it silently for the user",
1229
+ )
1003
1230
 
1004
1231
  cmd("dingtalk-account-list", cmd_dingtalk_account_list, "List personal DingTalk account bindings")
1005
1232
 
@@ -1009,6 +1236,14 @@ def build_parser() -> argparse.ArgumentParser:
1009
1236
  "Remove a personal DingTalk account binding (row kept as revoked for audit)",
1010
1237
  )
1011
1238
  p.add_argument("--installation", required=True, help="installation uuid")
1239
+ p.add_argument(
1240
+ "--binding-mode",
1241
+ dest="binding_mode",
1242
+ choices=("message", "identity"),
1243
+ default="message",
1244
+ help="binding capability to remove: message-only identity mapping (default) or "
1245
+ "delegated DingTalk identity",
1246
+ )
1012
1247
 
1013
1248
  p = cmd("agent-tasks", cmd_agent_tasks, "List an agent's task run history")
1014
1249
  p.add_argument("--agent", required=True, help="agent uuid")
@@ -1054,6 +1289,17 @@ def build_parser() -> argparse.ArgumentParser:
1054
1289
  p.add_argument("--timezone", default="Asia/Shanghai", help="IANA timezone for --cron")
1055
1290
  p.add_argument("--issue-title-template", dest="issue_title_template", help="only {{date}} token supported")
1056
1291
 
1292
+ p = cmd(
1293
+ "predicate-resolve",
1294
+ cmd_predicate_resolve,
1295
+ "Resolve a completion predicate against the project manifest registry "
1296
+ "(runtime reads the SAME dingtalk-agent.json the declaration lives in)",
1297
+ )
1298
+ p.add_argument("--manifest", required=True, help="path to dingtalk-agent.json")
1299
+ src_group = p.add_mutually_exclusive_group(required=True)
1300
+ src_group.add_argument("--id", help="predicate id (skip marker parsing)")
1301
+ src_group.add_argument("--description", help="autopilot description; the [dta-completion-predicate] marker is parsed out")
1302
+
1057
1303
  p = cmd("autopilot-trigger", cmd_autopilot_trigger, "Run an autopilot once, now")
1058
1304
  p.add_argument("--autopilot", required=True, help="autopilot uuid")
1059
1305
 
@@ -1167,7 +1413,9 @@ def main() -> None:
1167
1413
  if not getattr(args, "command", None):
1168
1414
  parser.print_help(sys.stderr)
1169
1415
  sys.exit(0 if len(sys.argv) == 1 else 1)
1170
- ctx = build_ctx(args)
1416
+ # 纯本地命令不做凭据解析:predicate-resolve 只读 manifest,跑在 runtime 沙箱里
1417
+ # 时未必有(也不需要)平台凭据;强行 build_ctx 会让判据解析被登录问题挡死。
1418
+ ctx = None if args.command in LOCAL_COMMANDS else build_ctx(args)
1171
1419
  try:
1172
1420
  args.func(ctx, args)
1173
1421
  except ApiError as e:
package/dist/src/map.js DELETED
@@ -1,157 +0,0 @@
1
- // Managed Agents Platform(MAP)注册表与工作区归属解析。
2
- //
3
- // 一个 Agent 工作区归属于至多一个托管平台;归属解析顺序固定:
4
- // DTA_MAP 环境变量 > dingtalk-agent.json#map > .dingtalk-agent/map.json > 按 Provider 声明推断。
5
- // 平台相关的方法学不写进 CLI——它们随对应平台的 Skill 包分发,只在用户选择该平台时安装。
6
- // 平台命令(如 deploy/promote/observe)必须先通过归属门禁;coming-soon 平台一律 fail-closed。
7
- import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
8
- import { dirname, join, parse, resolve } from 'node:path';
9
- import { findAgentProjectRoot } from './development-workspace.js';
10
- import { installGlobalSkill } from './skill-manager.js';
11
- export const MANAGED_AGENT_PLATFORMS = [
12
- {
13
- name: 'multica-dingtalk',
14
- label: 'Multica (DingTalk)',
15
- status: 'supported',
16
- description: '钉钉 Multica 托管平台:受控部署、晋级与观测回流。',
17
- skills: ['dingtalk-agent-deploy', 'dingtalk-agent-boot-multica'],
18
- commands: ['deploy', 'promote', 'observe'],
19
- },
20
- {
21
- name: 'deap',
22
- label: 'DEAP',
23
- status: 'coming-soon',
24
- description: '敬请期待。',
25
- skills: [],
26
- commands: [],
27
- },
28
- ];
29
- export const MAP_FILE = '.dingtalk-agent/map.json';
30
- const PROJECT_FILE = 'dingtalk-agent.json';
31
- export function mapDefinition(name) {
32
- return MANAGED_AGENT_PLATFORMS.find((item) => item.name === name) || null;
33
- }
34
- export function resolveMap(start = process.cwd(), env = process.env) {
35
- const fromEnv = String(env.DTA_MAP || '').trim();
36
- if (fromEnv)
37
- return resolution(fromEnv, 'env', '');
38
- const projectRoot = findAgentProjectRoot(start);
39
- if (projectRoot) {
40
- const manifestPath = join(projectRoot, PROJECT_FILE);
41
- if (existsSync(manifestPath) && !lstatSync(manifestPath).isSymbolicLink()) {
42
- const manifest = readJsonOrNull(manifestPath);
43
- const declared = typeof manifest?.map === 'string' ? manifest.map.trim() : '';
44
- if (declared)
45
- return resolution(declared, 'project-manifest', manifestPath);
46
- }
47
- }
48
- const mapPath = findUp(start, MAP_FILE);
49
- if (mapPath) {
50
- const stored = readJsonOrNull(mapPath);
51
- const declared = typeof stored?.map === 'string' ? stored.map.trim() : '';
52
- if (declared)
53
- return resolution(declared, 'map-file', mapPath);
54
- }
55
- if (projectRoot) {
56
- const manifest = readJsonOrNull(join(projectRoot, PROJECT_FILE));
57
- const providers = Object.values(manifest?.workspaces || {})
58
- .map((workspace) => workspace?.provider?.kind);
59
- if (providers.includes('multica')) {
60
- return resolution('multica-dingtalk', 'inferred', join(projectRoot, PROJECT_FILE));
61
- }
62
- }
63
- return resolution(null, 'none', '');
64
- }
65
- export function useMap(start, name, options) {
66
- const definition = mapDefinition(name);
67
- if (!definition) {
68
- throw new Error(`未知 managed agents platform: ${name}\n` +
69
- `可用: ${MANAGED_AGENT_PLATFORMS.map((item) => `${item.name}(${statusLabel(item.status)})`).join('、')}`);
70
- }
71
- if (definition.status !== 'supported') {
72
- throw new Error(`${definition.label} 敬请期待——当前版本尚未开放该平台。`);
73
- }
74
- const projectRoot = findAgentProjectRoot(start);
75
- let written;
76
- if (projectRoot && existsSync(join(projectRoot, PROJECT_FILE))) {
77
- const manifestPath = join(projectRoot, PROJECT_FILE);
78
- if (lstatSync(manifestPath).isSymbolicLink()) {
79
- throw new Error(`${PROJECT_FILE} 不能是符号链接`);
80
- }
81
- const manifest = readJsonOrNull(manifestPath);
82
- if (!manifest || typeof manifest !== 'object') {
83
- throw new Error(`${PROJECT_FILE} 不是合法 JSON,无法写入 map 归属`);
84
- }
85
- manifest.map = definition.name;
86
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
87
- written = manifestPath;
88
- }
89
- else {
90
- const target = join(resolve(start), MAP_FILE);
91
- mkdirSync(dirname(target), { recursive: true });
92
- writeFileSync(target, JSON.stringify({ $schema: 'dingtalk-agent/map@1', map: definition.name }, null, 2) + '\n');
93
- written = target;
94
- }
95
- const skillInstalls = [];
96
- if (options.installSkills !== false) {
97
- for (const skill of definition.skills) {
98
- skillInstalls.push(installGlobalSkill(options.packageRoot, {
99
- name: skill, home: options.home, env: options.env,
100
- }));
101
- }
102
- }
103
- return { ...resolveMap(start, options.env || process.env), written, skillInstalls };
104
- }
105
- /** deploy/promote/observe 等平台命令的归属门禁:未归属、未知或 coming-soon 一律 fail-closed。 */
106
- export function requireSupportedMap(command, start = process.cwd(), env = process.env) {
107
- const resolved = resolveMap(start, env);
108
- if (!resolved.map) {
109
- throw new Error(`当前工作区未声明 managed agents platform,无法执行 \`${command}\`。\n` +
110
- '先运行 `dta map use multica-dingtalk`,或设置 DTA_MAP 环境变量。');
111
- }
112
- if (!resolved.definition) {
113
- throw new Error(`工作区声明了未知 platform: ${resolved.map}(来源 ${resolved.source})。\n` +
114
- `可用: ${MANAGED_AGENT_PLATFORMS.map((item) => item.name).join('、')}`);
115
- }
116
- if (resolved.definition.status !== 'supported') {
117
- throw new Error(`${resolved.definition.label} 敬请期待——\`${command}\` 尚未在该平台开放。`);
118
- }
119
- if (!resolved.definition.commands.includes(command)) {
120
- throw new Error(`${resolved.definition.label} 不承接命令 \`${command}\`。`);
121
- }
122
- return resolved;
123
- }
124
- export function statusLabel(status) {
125
- return status === 'supported' ? '已支持' : '敬请期待';
126
- }
127
- function resolution(name, source, file) {
128
- return {
129
- $schema: 'dingtalk-agent/map-resolution@1',
130
- map: name,
131
- source,
132
- file,
133
- definition: name ? mapDefinition(name) : null,
134
- platforms: MANAGED_AGENT_PLATFORMS,
135
- };
136
- }
137
- function readJsonOrNull(path) {
138
- try {
139
- return JSON.parse(readFileSync(path, 'utf8'));
140
- }
141
- catch {
142
- return null;
143
- }
144
- }
145
- function findUp(start, relativePath) {
146
- let dir = resolve(start);
147
- while (true) {
148
- const candidate = join(dir, relativePath);
149
- if (existsSync(candidate) && !lstatSync(candidate).isSymbolicLink())
150
- return candidate;
151
- const parent = dirname(dir);
152
- if (parent === dir || dir === parse(dir).root)
153
- return null;
154
- dir = parent;
155
- }
156
- }
157
- //# sourceMappingURL=map.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/map.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,oCAAoC;AACpC,yFAAyF;AACzF,oDAAoD;AACpD,wEAAwE;AAExE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAA0B,MAAM,oBAAoB,CAAA;AAa/E,MAAM,CAAC,MAAM,uBAAuB,GAAoB;IACtD;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,oBAAoB;QAC3B,MAAM,EAAE,WAAW;QACnB,WAAW,EAAE,+BAA+B;QAC5C,MAAM,EAAE,CAAC,uBAAuB,EAAE,6BAA6B,CAAC;QAChE,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC;KAC3C;IACD;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,aAAa;QACrB,WAAW,EAAE,OAAO;QACpB,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,EAAE;KACb;CACF,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,0BAA0B,CAAA;AAClD,MAAM,YAAY,GAAG,qBAAqB,CAAA;AAkB1C,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAA;AAC3E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,MAAyB,OAAO,CAAC,GAAG;IACpF,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,IAAI,OAAO;QAAE,OAAO,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IAElD,MAAM,WAAW,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;QACpD,IAAI,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1E,MAAM,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;YAC7C,MAAM,QAAQ,GAAG,OAAO,QAAQ,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC7E,IAAI,QAAQ;gBAAE,OAAO,UAAU,CAAC,QAAQ,EAAE,kBAAkB,EAAE,YAAY,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IACvC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QACtC,MAAM,QAAQ,GAAG,OAAO,MAAM,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QACzE,IAAI,QAAQ;YAAE,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAA;QAChE,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,CAAC;aACxD,GAAG,CAAC,CAAC,SAAc,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;QACrD,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,OAAO,UAAU,CAAC,kBAAkB,EAAE,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAA;QACpF,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AACrC,CAAC;AASD,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,IAAY,EAAE,OAAsB;IACxE,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,+BAA+B,IAAI,IAAI;YACvC,OAAO,uBAAuB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACtG,CAAA;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,qBAAqB,CAAC,CAAA;IAC3D,CAAC;IAED,MAAM,WAAW,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,OAAe,CAAA;IACnB,IAAI,WAAW,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;QACpD,IAAI,SAAS,CAAC,YAAY,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,GAAG,YAAY,UAAU,CAAC,CAAA;QAC5C,CAAC;QACD,MAAM,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;QAC7C,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,YAAY,wBAAwB,CAAC,CAAA;QAC1D,CAAC;QACD,QAAQ,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,CAAA;QAC9B,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;QACrE,OAAO,GAAG,YAAY,CAAA;IACxB,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAA;QAC7C,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAClC,EAAE,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CACnE,GAAG,IAAI,CAAC,CAAA;QACT,OAAO,GAAG,MAAM,CAAA;IAClB,CAAC;IAED,MAAM,aAAa,GAAwB,EAAE,CAAA;IAC7C,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACtC,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,WAAW,EAAE;gBACzD,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG;aAClD,CAAC,CAAC,CAAA;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;AACrF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB,CACjC,OAAe,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,MAAyB,OAAO,CAAC,GAAG;IAE5E,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,2CAA2C,OAAO,OAAO;YACzD,sDAAsD,CACvD,CAAA;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,sBAAsB,QAAQ,CAAC,GAAG,OAAO,QAAQ,CAAC,MAAM,MAAM;YAC9D,OAAO,uBAAuB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACpE,CAAA;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,KAAK,YAAY,OAAO,cAAc,CAAC,CAAA;IAChF,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,KAAK,YAAY,OAAO,KAAK,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;AAChD,CAAC;AAED,SAAS,UAAU,CAAC,IAAmB,EAAE,MAAiB,EAAE,IAAY;IACtE,OAAO;QACL,OAAO,EAAE,iCAAiC;QAC1C,GAAG,EAAE,IAAI;QACT,MAAM;QACN,IAAI;QACJ,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QAC7C,SAAS,EAAE,uBAAuB;KACnC,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;AAC7E,CAAC;AAED,SAAS,MAAM,CAAC,KAAa,EAAE,YAAoB;IACjD,IAAI,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IACxB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACzC,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE;YAAE,OAAO,SAAS,CAAA;QACrF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAC1D,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC","sourcesContent":["// Managed Agents Platform(MAP)注册表与工作区归属解析。\n//\n// 一个 Agent 工作区归属于至多一个托管平台;归属解析顺序固定:\n// DTA_MAP 环境变量 > dingtalk-agent.json#map > .dingtalk-agent/map.json > 按 Provider 声明推断。\n// 平台相关的方法学不写进 CLI——它们随对应平台的 Skill 包分发,只在用户选择该平台时安装。\n// 平台命令(如 deploy/promote/observe)必须先通过归属门禁;coming-soon 平台一律 fail-closed。\n\nimport { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join, parse, resolve } from 'node:path'\n\nimport { findAgentProjectRoot } from './development-workspace.js'\nimport { installGlobalSkill, type GlobalSkillStatus } from './skill-manager.js'\n\nexport type MapStatus = 'supported' | 'coming-soon'\n\nexport interface MapDefinition {\n name: string\n label: string\n status: MapStatus\n description: string\n skills: string[]\n commands: string[]\n}\n\nexport const MANAGED_AGENT_PLATFORMS: MapDefinition[] = [\n {\n name: 'multica-dingtalk',\n label: 'Multica (DingTalk)',\n status: 'supported',\n description: '钉钉 Multica 托管平台:受控部署、晋级与观测回流。',\n skills: ['dingtalk-agent-deploy', 'dingtalk-agent-boot-multica'],\n commands: ['deploy', 'promote', 'observe'],\n },\n {\n name: 'deap',\n label: 'DEAP',\n status: 'coming-soon',\n description: '敬请期待。',\n skills: [],\n commands: [],\n },\n]\n\nexport const MAP_FILE = '.dingtalk-agent/map.json'\nconst PROJECT_FILE = 'dingtalk-agent.json'\n\nexport type MapSource = 'env' | 'project-manifest' | 'map-file' | 'inferred' | 'none'\n\nexport interface MapResolution {\n $schema: 'dingtalk-agent/map-resolution@1'\n map: string | null\n source: MapSource\n file: string\n definition: MapDefinition | null\n platforms: MapDefinition[]\n}\n\nexport interface MapUseResult extends MapResolution {\n written: string\n skillInstalls: GlobalSkillStatus[]\n}\n\nexport function mapDefinition(name: string): MapDefinition | null {\n return MANAGED_AGENT_PLATFORMS.find((item) => item.name === name) || null\n}\n\nexport function resolveMap(start = process.cwd(), env: NodeJS.ProcessEnv = process.env): MapResolution {\n const fromEnv = String(env.DTA_MAP || '').trim()\n if (fromEnv) return resolution(fromEnv, 'env', '')\n\n const projectRoot = findAgentProjectRoot(start)\n if (projectRoot) {\n const manifestPath = join(projectRoot, PROJECT_FILE)\n if (existsSync(manifestPath) && !lstatSync(manifestPath).isSymbolicLink()) {\n const manifest = readJsonOrNull(manifestPath)\n const declared = typeof manifest?.map === 'string' ? manifest.map.trim() : ''\n if (declared) return resolution(declared, 'project-manifest', manifestPath)\n }\n }\n\n const mapPath = findUp(start, MAP_FILE)\n if (mapPath) {\n const stored = readJsonOrNull(mapPath)\n const declared = typeof stored?.map === 'string' ? stored.map.trim() : ''\n if (declared) return resolution(declared, 'map-file', mapPath)\n }\n\n if (projectRoot) {\n const manifest = readJsonOrNull(join(projectRoot, PROJECT_FILE))\n const providers = Object.values(manifest?.workspaces || {})\n .map((workspace: any) => workspace?.provider?.kind)\n if (providers.includes('multica')) {\n return resolution('multica-dingtalk', 'inferred', join(projectRoot, PROJECT_FILE))\n }\n }\n\n return resolution(null, 'none', '')\n}\n\nexport interface MapUseOptions {\n packageRoot: string\n home?: string\n env?: NodeJS.ProcessEnv\n installSkills?: boolean\n}\n\nexport function useMap(start: string, name: string, options: MapUseOptions): MapUseResult {\n const definition = mapDefinition(name)\n if (!definition) {\n throw new Error(\n `未知 managed agents platform: ${name}\\n` +\n `可用: ${MANAGED_AGENT_PLATFORMS.map((item) => `${item.name}(${statusLabel(item.status)})`).join('、')}`,\n )\n }\n if (definition.status !== 'supported') {\n throw new Error(`${definition.label} 敬请期待——当前版本尚未开放该平台。`)\n }\n\n const projectRoot = findAgentProjectRoot(start)\n let written: string\n if (projectRoot && existsSync(join(projectRoot, PROJECT_FILE))) {\n const manifestPath = join(projectRoot, PROJECT_FILE)\n if (lstatSync(manifestPath).isSymbolicLink()) {\n throw new Error(`${PROJECT_FILE} 不能是符号链接`)\n }\n const manifest = readJsonOrNull(manifestPath)\n if (!manifest || typeof manifest !== 'object') {\n throw new Error(`${PROJECT_FILE} 不是合法 JSON,无法写入 map 归属`)\n }\n manifest.map = definition.name\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\\n')\n written = manifestPath\n } else {\n const target = join(resolve(start), MAP_FILE)\n mkdirSync(dirname(target), { recursive: true })\n writeFileSync(target, JSON.stringify(\n { $schema: 'dingtalk-agent/map@1', map: definition.name }, null, 2,\n ) + '\\n')\n written = target\n }\n\n const skillInstalls: GlobalSkillStatus[] = []\n if (options.installSkills !== false) {\n for (const skill of definition.skills) {\n skillInstalls.push(installGlobalSkill(options.packageRoot, {\n name: skill, home: options.home, env: options.env,\n }))\n }\n }\n\n return { ...resolveMap(start, options.env || process.env), written, skillInstalls }\n}\n\n/** deploy/promote/observe 等平台命令的归属门禁:未归属、未知或 coming-soon 一律 fail-closed。 */\nexport function requireSupportedMap(\n command: string, start = process.cwd(), env: NodeJS.ProcessEnv = process.env,\n): MapResolution {\n const resolved = resolveMap(start, env)\n if (!resolved.map) {\n throw new Error(\n `当前工作区未声明 managed agents platform,无法执行 \\`${command}\\`。\\n` +\n '先运行 `dta map use multica-dingtalk`,或设置 DTA_MAP 环境变量。',\n )\n }\n if (!resolved.definition) {\n throw new Error(\n `工作区声明了未知 platform: ${resolved.map}(来源 ${resolved.source})。\\n` +\n `可用: ${MANAGED_AGENT_PLATFORMS.map((item) => item.name).join('、')}`,\n )\n }\n if (resolved.definition.status !== 'supported') {\n throw new Error(`${resolved.definition.label} 敬请期待——\\`${command}\\` 尚未在该平台开放。`)\n }\n if (!resolved.definition.commands.includes(command)) {\n throw new Error(`${resolved.definition.label} 不承接命令 \\`${command}\\`。`)\n }\n return resolved\n}\n\nexport function statusLabel(status: MapStatus): string {\n return status === 'supported' ? '已支持' : '敬请期待'\n}\n\nfunction resolution(name: string | null, source: MapSource, file: string): MapResolution {\n return {\n $schema: 'dingtalk-agent/map-resolution@1',\n map: name,\n source,\n file,\n definition: name ? mapDefinition(name) : null,\n platforms: MANAGED_AGENT_PLATFORMS,\n }\n}\n\nfunction readJsonOrNull(path: string): any {\n try { return JSON.parse(readFileSync(path, 'utf8')) } catch { return null }\n}\n\nfunction findUp(start: string, relativePath: string): string | null {\n let dir = resolve(start)\n while (true) {\n const candidate = join(dir, relativePath)\n if (existsSync(candidate) && !lstatSync(candidate).isSymbolicLink()) return candidate\n const parent = dirname(dir)\n if (parent === dir || dir === parse(dir).root) return null\n dir = parent\n }\n}\n"]}
@@ -1,62 +0,0 @@
1
- # 一小时复制第二个岗位 Agent
2
-
3
- ## 结论
4
-
5
- 第二个 Agent 不复制运行时,只复制一份 Agent kit 并替换四类绑定:`AGENTS.md`、Role Skills、Field/存储、DWS authority。事件监听不在本流程内;没有事件时 Agent 仍以 Direct/Mounted Session 正常工作,有可信事件时再进入同一个 Prepared Runtime。
6
-
7
- 仓库提供两份已通过同一合同的实例:
8
-
9
- - `examples/agents/fde-coach`:FDE 教练;
10
- - `examples/agents/release-manager`:发布经理。
11
-
12
- 两者共享 Basic Behavior、Invocation、Session/Run/Wait、双半闸门与 Receipt;Definition、Role Skill、Field、Session state、记忆和权限完全隔离。
13
-
14
- ## 60 分钟执行表
15
-
16
- | 时间 | 开发者动作 | 验收标准 |
17
- |---|---|---|
18
- | 0–10 分钟 | 复制任一 example,改目录名 | 原 example 不被覆盖,新目录不含 `.dingtalk-agent` |
19
- | 10–20 分钟 | 改 `AGENTS.md` 的身份、服务对象、职责、交付与拒绝边界 | 另一个 Agent 能回答“我是谁、负责什么、不做什么” |
20
- | 20–30 分钟 | 新建/改名 `skills/<role>/SKILL.md` | 目录名等于 frontmatter `name`;只含岗位方法,不重复基础行为 |
21
- | 30–40 分钟 | 配置 Field、存储路由和 DWS authority | 使用可信 ID;不同 Agent 不共用 state root、业务 scope 或生产权限 |
22
- | 40–50 分钟 | 先 `dta bootstrap --json`,需要 Prepared Run 时再显式 `dta init` | Direct 默认 ready/partial 可解释;init 采用 `AGENTS.md` 并发现 Role Skill |
23
- | 50–60 分钟 | 跑合同/mock Lab,检查冻结快照和隔离 | 无可信 target 不外发;两个 Agent 的 Definition/Session/记忆/权限不串 |
24
-
25
- ## 最小操作
26
-
27
- ```bash
28
- cp -R examples/agents/release-manager /path/to/my-agent
29
- cd /path/to/my-agent
30
-
31
- # 先验证本体;这一步不要求 Workspace,也不产生钉钉副作用
32
- dta bootstrap --json
33
-
34
- # 只有需要可信事件和 Prepared Run 时才初始化一次
35
- dta init
36
- dta bootstrap --json
37
- ```
38
-
39
- 首次 `init` 会优先采用已有 `AGENTS.md`,发现 `skills/*/SKILL.md`,并保留已有 `fields/default/field.json`。初始化后新增 Skill 不会自动热启用:离线评审后把 Skill name 加入 `.dingtalk-agent/workspace.json#skills`,由新 Session 重新冻结。
40
-
41
- ## 自动化验收
42
-
43
- ```bash
44
- npm run typecheck
45
- npm run eval:contract
46
- ```
47
-
48
- 合同 42 会从两份 example 分别创建临时 Workspace,并检查:
49
-
50
- 1. 初始化前 Direct bootstrap 可工作且没有可信 target;
51
- 2. 初始化后 `AGENTS.md`、Role Skill 与显式 Field 被正确采用;
52
- 3. 两个 Definition、DWS profile、Session ID 不同,共享 Basic Behavior hash;
53
- 4. FDE 的 Task Checkpoint 与 Memory Candidate 在发布经理 state root 中不可见;
54
- 5. 两者都通过同一 fake-DWS Lab 的 Action/Receipt/平台回读合同。
55
-
56
- 2026-07-16 本地基线为 42/42,通过;双 Agent 合同自动执行约 2–3 秒。这个数字证明模板化装配和隔离可自动验证,不代表真实账号、专用群、AI 表格和钉钉文档已接通。真实 L4 仍需为每个 Agent 提供专用测试对象、allowlist 和明确 `--live --yes` 授权。
57
-
58
- ## 验收边界
59
-
60
- - 通过:开发者只改本体、岗位 Skill、存储/Field/authority,即可得到第二个结构完整、状态隔离的 Agent;
61
- - 不包含:Webhook、定时器、personal-event listener 等触发端;
62
- - 不冒充:mock 平台可见不等于真实钉钉送达,真实写入必须另走 Lab L3/L4 晋级门禁。