create-workframe 0.1.7 → 0.1.8

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 (87) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/workframe-api/package.json +1 -1
  4. package/workframe-api/server.py +304 -35
  5. package/workframe-api/stack_config.py +7 -7
  6. package/workframe-api/test_stack_config_install_smtp.py +5 -0
  7. package/workframe-ui/public/assets/{arc-Cn-lt9fT.js → arc-DjpS9a2b.js} +1 -1
  8. package/workframe-ui/public/assets/architecture-7EHR7CIX-CvhbXEvt.js +1 -0
  9. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-qXoGCqfH.js → architectureDiagram-3BPJPVTR-puCKmKHV.js} +1 -1
  10. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-DoopFlWj.js → blockDiagram-GPEHLZMM-C2P0g-eH.js} +1 -1
  11. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BRa7hfzE.js → c4Diagram-AAUBKEIU-DdvYEITH.js} +1 -1
  12. package/workframe-ui/public/assets/channel-D63s_yN9.js +1 -0
  13. package/workframe-ui/public/assets/{chunk-2J33WTMH-Cl33E2hh.js → chunk-2J33WTMH-Cxe8QsEF.js} +1 -1
  14. package/workframe-ui/public/assets/{chunk-3OPIFGDE-BRywYZQO.js → chunk-3OPIFGDE-DPNyTwR3.js} +1 -1
  15. package/workframe-ui/public/assets/{chunk-4BX2VUAB-CI3xzp-0.js → chunk-4BX2VUAB-XKG9gfRN.js} +1 -1
  16. package/workframe-ui/public/assets/{chunk-55IACEB6-Bkf3Ye3F.js → chunk-55IACEB6-oPMTulgb.js} +1 -1
  17. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-C5RN3hUT.js → chunk-5ZQYHXKU-BWwXvpa_.js} +1 -1
  18. package/workframe-ui/public/assets/{chunk-727SXJPM-BaY0yDwt.js → chunk-727SXJPM-Cml2twO_.js} +1 -1
  19. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-VMMBaDqd.js → chunk-AQP2D5EJ-DzrRBhQu.js} +1 -1
  20. package/workframe-ui/public/assets/{chunk-BSJP7CBP-DlNZ1UV3.js → chunk-BSJP7CBP-EZ7STU6h.js} +1 -1
  21. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-tT2HyVy0.js → chunk-CSCIHK7Q-BGTQzWYw.js} +1 -1
  22. package/workframe-ui/public/assets/{chunk-FMBD7UC4-CyACRaUT.js → chunk-FMBD7UC4-Cm4stfRa.js} +1 -1
  23. package/workframe-ui/public/assets/{chunk-KSCS5N6A-seK5WAZs.js → chunk-KSCS5N6A-exbMijjy.js} +1 -1
  24. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-r5cyG5AH.js → chunk-L5ZTLDWV-C75kbQwN.js} +1 -1
  25. package/workframe-ui/public/assets/{chunk-LZXEDZCA-SnbeVxsJ.js → chunk-LZXEDZCA-CsRdBqUB.js} +2 -2
  26. package/workframe-ui/public/assets/{chunk-ND2GUHAM-zbFgEXSJ.js → chunk-ND2GUHAM-D84zyIu2.js} +1 -1
  27. package/workframe-ui/public/assets/{chunk-NZK2D7GU-UhZc8kOk.js → chunk-NZK2D7GU-CUNNo1bj.js} +1 -1
  28. package/workframe-ui/public/assets/{chunk-O5CBEL6O-DHvu2iZ-.js → chunk-O5CBEL6O-DN6WEIh-.js} +1 -1
  29. package/workframe-ui/public/assets/chunk-QZHKN3VN-Dj_Yxhgo.js +1 -0
  30. package/workframe-ui/public/assets/chunk-WU5MYG2G-AFchshb6.js +1 -0
  31. package/workframe-ui/public/assets/{chunk-XPW4576I-CCPNKbzG.js → chunk-XPW4576I-Ct5VQv2D.js} +1 -1
  32. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-Opj8t01I.js +1 -0
  33. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-Opj8t01I.js +1 -0
  34. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-D1CLe6Y0.js → cose-bilkent-S5V4N54A-Bj4W5oMk.js} +1 -1
  35. package/workframe-ui/public/assets/{dagre-BM42HDAG-DYHcGVs_.js → dagre-BM42HDAG-DAjHBkAy.js} +1 -1
  36. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DVUkJ-II.js → diagram-2AECGRRQ-Ch2gGFPG.js} +1 -1
  37. package/workframe-ui/public/assets/{diagram-5GNKFQAL-C9Y1Acc2.js → diagram-5GNKFQAL-BzIIvgYH.js} +1 -1
  38. package/workframe-ui/public/assets/{diagram-KO2AKTUF-Db3zez-n.js → diagram-KO2AKTUF-Df0Q8nv6.js} +1 -1
  39. package/workframe-ui/public/assets/{diagram-LMA3HP47-BaOR5sIr.js → diagram-LMA3HP47-D-G3ekmF.js} +1 -1
  40. package/workframe-ui/public/assets/{diagram-OG6HWLK6-fLBzMY5W.js → diagram-OG6HWLK6-TBHhT9f5.js} +1 -1
  41. package/workframe-ui/public/assets/{dist-uZYeD3o1.js → dist-CPbiDkEZ.js} +1 -1
  42. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BoCxDSyE.js → erDiagram-TEJ5UH35-YxkUmLLV.js} +1 -1
  43. package/workframe-ui/public/assets/eventmodeling-FCH6USID-LQOYL50n.js +1 -0
  44. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-BnYa3CAj.js → flowDiagram-I6XJVG4X-kfZ7-RSd.js} +1 -1
  45. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-Dm_e_Kaa.js → ganttDiagram-6RSMTGT7-DmTx3hzR.js} +1 -1
  46. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-CBAj8ntv.js → gitGraph-WXDBUCRP-BXa0ewp3.js} +1 -1
  47. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-D5auJjJh.js → gitGraphDiagram-PVQCEYII-CQr2qMxU.js} +1 -1
  48. package/workframe-ui/public/assets/{index-auJ0bLMU.css → index-B08aShJy.css} +1 -1
  49. package/workframe-ui/public/assets/index-DCF3W3G_.js +129 -0
  50. package/workframe-ui/public/assets/{info-J43DQDTF-BNMADxE2.js → info-J43DQDTF-t-Qlx8zv.js} +1 -1
  51. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C8c45T4J.js → infoDiagram-5YYISTIA-CzkYofPx.js} +1 -1
  52. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CyWuHB4e.js → ishikawaDiagram-YF4QCWOH-djK3Oxgb.js} +1 -1
  53. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-B43pCuDT.js → journeyDiagram-JHISSGLW-CWxYXxdF.js} +1 -1
  54. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-Dyn480BI.js → kanban-definition-UN3LZRKU-D7leCAD9.js} +1 -1
  55. package/workframe-ui/public/assets/{line-CNtIbnmt.js → line-BSFj2OC5.js} +1 -1
  56. package/workframe-ui/public/assets/{linear-pmSg_4uV.js → linear-pfPwnu-o.js} +1 -1
  57. package/workframe-ui/public/assets/{mermaid-parser.core-BX4tnojE.js → mermaid-parser.core-Dm2L9Ib_.js} +2 -2
  58. package/workframe-ui/public/assets/{mermaid.core-BtpBva5o.js → mermaid.core-Bevx9bif.js} +3 -3
  59. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DcMcB_69.js → mindmap-definition-RKZ34NQL-UkjDssmP.js} +1 -1
  60. package/workframe-ui/public/assets/{packet-YPE3B663-KSzK9WlD.js → packet-YPE3B663-JO2ezH3_.js} +1 -1
  61. package/workframe-ui/public/assets/{pie-LRSECV5Y-DLOH6Mg8.js → pie-LRSECV5Y-DdMFsfR8.js} +1 -1
  62. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-BhrX2aGk.js → pieDiagram-4H26LBE5-CEPHFBU1.js} +1 -1
  63. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-B5dDjbTX.js → quadrantDiagram-W4KKPZXB-_RF9_86k.js} +1 -1
  64. package/workframe-ui/public/assets/{radar-GUYGQ44K-BPdjqZiu.js → radar-GUYGQ44K-Cb-YfdaA.js} +1 -1
  65. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-21Dq5e0N.js → requirementDiagram-4Y6WPE33-DBVOxmBn.js} +1 -1
  66. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CiA99dbO.js → sankeyDiagram-5OEKKPKP-BTpxsGUA.js} +1 -1
  67. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-Bwpui7_x.js → sequenceDiagram-3UESZ5HK-DHP_Bq3l.js} +1 -1
  68. package/workframe-ui/public/assets/{src-B3Axsi1U.js → src-CY_By-r7.js} +1 -1
  69. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-C76qwtE8.js → stateDiagram-AJRCARHV-BVtp_LPR.js} +1 -1
  70. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CvJhS_BJ.js +1 -0
  71. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-CufInOyk.js → timeline-definition-PNZ67QCA-D38HJhop.js} +1 -1
  72. package/workframe-ui/public/assets/{treeView-BLDUP644-C4sxh0R-.js → treeView-BLDUP644-CFCG3Sc_.js} +1 -1
  73. package/workframe-ui/public/assets/{treemap-LRROVOQU-BjV72ANV.js → treemap-LRROVOQU-BeiMs_dF.js} +1 -1
  74. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-DgsdR0Rl.js → vennDiagram-CIIHVFJN-CKMeT6PW.js} +1 -1
  75. package/workframe-ui/public/assets/{wardley-L42UT6IY-DZ279G_t.js → wardley-L42UT6IY-BljDy8ph.js} +1 -1
  76. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-Bde25kvz.js → wardleyDiagram-YWT4CUSO-DEE8D94J.js} +1 -1
  77. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-B8VnS2Pb.js → xychartDiagram-2RQKCTM6-BRaAWV_g.js} +1 -1
  78. package/workframe-ui/public/index.html +2 -2
  79. package/workframe-ui/public/assets/architecture-7EHR7CIX-CCMNXHod.js +0 -1
  80. package/workframe-ui/public/assets/channel-CtFV3sE3.js +0 -1
  81. package/workframe-ui/public/assets/chunk-QZHKN3VN-B1g7l1_B.js +0 -1
  82. package/workframe-ui/public/assets/chunk-WU5MYG2G-B_qfZ6QP.js +0 -1
  83. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CJoeCNq6.js +0 -1
  84. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CJoeCNq6.js +0 -1
  85. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Bw910jho.js +0 -1
  86. package/workframe-ui/public/assets/index-DHLQNAXd.js +0 -129
  87. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-Dn0CDWJD.js +0 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Published on npm as **create-workframe**.
4
4
 
5
5
  ```bash
6
- npx create-workframe@0.1.6 MyProject
6
+ npx create-workframe@0.1.8 MyProject
7
7
  ```
8
8
 
9
9
  Scaffolds an isolated Workframe + Hermes project on Windows, macOS, and Linux.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-workframe",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Scaffold a Workframe + Hermes workspace with guided onboarding",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workframe/workframe-api",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -908,8 +908,13 @@ def _auth_check(handler: BaseHTTPRequestHandler) -> bool:
908
908
  if method == "OPTIONS":
909
909
  return True
910
910
 
911
- # GET public routes
911
+ # GET public routes — still attach session user when cookie/header present
912
+ # (e.g. /api/hermes/models needs auth_user for per-user provider detection).
912
913
  if method == "GET" and _is_public_get(path):
914
+ if sid:
915
+ session_info = _zk.validate_session_token(sid)
916
+ if session_info:
917
+ _apply_session_user(handler, session_info["user_id"])
913
918
  return True
914
919
 
915
920
  # POST auth endpoints (start/verify/logout/refresh) are public
@@ -2076,6 +2081,53 @@ def _merge_user_platform_ids(user_id: str, patch: dict[str, str]) -> None:
2076
2081
  _apply_me_profile_updates(user_id, {"platform_ids": merged})
2077
2082
 
2078
2083
 
2084
+ def _read_user_llm_prefs(user_id: str) -> tuple[str, list[dict[str, str]]]:
2085
+ """User-level default model prefs (not agent profile config)."""
2086
+ user = _get_workframe_user(str(user_id or "").strip())
2087
+ pids = user.get("platform_ids") if user and isinstance(user.get("platform_ids"), dict) else {}
2088
+ primary = str(pids.get("llm_primary") or "").strip()
2089
+ chain_raw = pids.get("llm_fallback_chain")
2090
+ chain: list[dict[str, str]] = []
2091
+ if isinstance(chain_raw, str) and chain_raw.strip():
2092
+ try:
2093
+ parsed = json.loads(chain_raw)
2094
+ if isinstance(parsed, list):
2095
+ for entry in parsed:
2096
+ if isinstance(entry, dict):
2097
+ prov = str(entry.get("provider") or "").strip()
2098
+ model = str(entry.get("model") or "").strip()
2099
+ if prov and model:
2100
+ chain.append({"provider": prov, "model": model})
2101
+ except json.JSONDecodeError:
2102
+ pass
2103
+ return primary, chain
2104
+
2105
+
2106
+ def _write_user_llm_prefs(
2107
+ user_id: str,
2108
+ *,
2109
+ primary: str = "",
2110
+ fallback_chain: list[dict[str, str]] | None = None,
2111
+ ) -> None:
2112
+ user_id = str(user_id or "").strip()
2113
+ if not user_id:
2114
+ return
2115
+ patch: dict[str, str] = {}
2116
+ if primary:
2117
+ patch["llm_primary"] = primary.strip()
2118
+ if fallback_chain is not None:
2119
+ patch["llm_fallback_chain"] = json.dumps(fallback_chain, separators=(",", ":"))
2120
+ if patch:
2121
+ _merge_user_platform_ids(user_id, patch)
2122
+
2123
+
2124
+ def _user_llm_has_provider(user_id: str, workspace_id: str = "") -> bool:
2125
+ if _user_has_llm_provider(user_id):
2126
+ return True
2127
+ ws = str(workspace_id or "").strip()
2128
+ return bool(ws and _workspace_has_llm_provider(ws))
2129
+
2130
+
2079
2131
  def _start_discord_oauth(user_id: str, workspace_id: str = "") -> dict[str, Any]:
2080
2132
  cfg = platform_auth.resolved_discord_oauth()
2081
2133
  if not cfg.get("client_id") or not cfg.get("client_secret"):
@@ -5098,6 +5150,25 @@ def bootstrap_agent_dm_lane(
5098
5150
  try:
5099
5151
  _bootstrap_profile_providers(runtime, user_id, workspace_id)
5100
5152
  steps.append({"step": "bootstrap_providers", "ok": True})
5153
+ user_primary, user_chain = _read_user_llm_prefs(user_id)
5154
+ if user_primary:
5155
+ applied = hermes_model_set(runtime, user_primary, user_id, workspace_id)
5156
+ steps.append(
5157
+ {
5158
+ "step": "user_default_model",
5159
+ "ok": bool(applied.get("ok")),
5160
+ "model": user_primary,
5161
+ },
5162
+ )
5163
+ if user_chain:
5164
+ chained = hermes_fallback_chain_set(runtime, user_chain, user_id=user_id)
5165
+ steps.append(
5166
+ {
5167
+ "step": "user_default_fallbacks",
5168
+ "ok": bool(chained.get("ok")),
5169
+ "count": len(user_chain),
5170
+ },
5171
+ )
5101
5172
  except Exception as exc:
5102
5173
  steps.append({"step": "bootstrap_providers", "ok": False, "error": str(exc)})
5103
5174
 
@@ -6169,6 +6240,7 @@ def ensure_runtime_profile(
6169
6240
  if not _profile_toolsets_ready(runtime, _chat_toolsets_for_profile(runtime)):
6170
6241
  _ensure_profile_toolsets(runtime)
6171
6242
  _ensure_profile_terminal_cwd(runtime)
6243
+ _sync_runtime_model_from_template(runtime, template)
6172
6244
  return
6173
6245
  _purge_runtime_profile(runtime)
6174
6246
  _register_runtime_profile(runtime, template)
@@ -10678,6 +10750,28 @@ def _profile_model(profile: str) -> str:
10678
10750
  conn.close()
10679
10751
 
10680
10752
 
10753
+ def _llm_attribution_for_profile(profile: str, session_model: str = "") -> tuple[str, str]:
10754
+ """Resolve model id + billing provider for chat turn footers."""
10755
+ _ = session_model # ponytail: Hermes sessions.model is profile slug, not LLM id
10756
+ try:
10757
+ prof = resolve_hermes_profile(str(profile or "").strip())
10758
+ except ValueError:
10759
+ return "", ""
10760
+ block = _read_model_block(prof)
10761
+ model = str(block.get("default") or "").strip()
10762
+ billing = _billing_provider_id_from_hermes_config(str(block.get("provider") or ""))
10763
+ if not billing:
10764
+ billing = _llm_billing_provider(prof)
10765
+ if not billing and model:
10766
+ infer_llm = {
10767
+ str(spec["id"]).lower()
10768
+ for spec in PROVIDER_CONNECT_CATALOG
10769
+ if str(spec.get("category") or "") == "llm"
10770
+ }
10771
+ billing = _resolve_billing_provider_for_model(model, infer_llm)
10772
+ return model, str(billing or "").strip()
10773
+
10774
+
10681
10775
  def _latest_session_id(profile: str) -> str:
10682
10776
  """Most recent session — prefer active (ended_at IS NULL), then by started_at."""
10683
10777
  db = _profile_dir(profile) / "state.db"
@@ -10904,13 +10998,16 @@ def chat_messages(profile: str, session_id: str = "", source_id: str = "ui") ->
10904
10998
  _runtime_template_slug(profile) if _is_runtime_profile_slug(profile) else profile
10905
10999
  )
10906
11000
  display = _profile_display_name(profile)
11001
+ attr_model, attr_provider = _llm_attribution_for_profile(profile)
10907
11002
  try:
10908
11003
  raw = conn.execute(
10909
11004
  """
10910
- SELECT id, role, content, reasoning_content, tool_name, timestamp, token_count
10911
- FROM messages
10912
- WHERE role IN ('user', 'assistant', 'tool') AND session_id = ?
10913
- ORDER BY timestamp ASC, id ASC
11005
+ SELECT m.id, m.role, m.content, m.reasoning_content, m.tool_name,
11006
+ m.timestamp, m.token_count, s.model AS session_model
11007
+ FROM messages m
11008
+ LEFT JOIN sessions s ON s.id = m.session_id
11009
+ WHERE m.role IN ('user', 'assistant', 'tool') AND m.session_id = ?
11010
+ ORDER BY m.timestamp ASC, m.id ASC
10914
11011
  LIMIT 200
10915
11012
  """,
10916
11013
  (sid,),
@@ -10943,6 +11040,9 @@ def chat_messages(profile: str, session_id: str = "", source_id: str = "ui") ->
10943
11040
  if current and current["role"] == "agent":
10944
11041
  current["segments"].extend(segments)
10945
11042
  current["tokens"] += tokens
11043
+ if attr_model:
11044
+ current["model"] = attr_model
11045
+ current["llm_provider"] = attr_provider
10946
11046
  else:
10947
11047
  if current:
10948
11048
  turns.append(current)
@@ -10954,6 +11054,8 @@ def chat_messages(profile: str, session_id: str = "", source_id: str = "ui") ->
10954
11054
  "segments": list(segments),
10955
11055
  "timestamp": ts,
10956
11056
  "tokens": tokens,
11057
+ "model": attr_model,
11058
+ "llm_provider": attr_provider,
10957
11059
  }
10958
11060
  if current:
10959
11061
  turns.append(current)
@@ -12678,13 +12780,13 @@ def _overlay_chat_llm_env(
12678
12780
 
12679
12781
 
12680
12782
  def _resolve_models_profile(profile: str) -> str:
12681
- """Profile slug for model picker reads — accepts runtime u-* Hermes dirs."""
12783
+ """Template profile slug for model picker reads — never per-user runtime u-*."""
12682
12784
  raw = str(profile or "").strip()
12683
12785
  if not raw:
12684
12786
  return _primary_profile()
12685
12787
  slug = safe_profile_slug(raw)
12686
12788
  if _is_runtime_profile_slug(slug):
12687
- return resolve_hermes_profile(slug)
12789
+ return resolve_validated_profile(_runtime_template_slug(slug))
12688
12790
  return resolve_validated_profile(slug)
12689
12791
 
12690
12792
 
@@ -12713,10 +12815,13 @@ def _augment_model_suggestions(
12713
12815
  extra: list[dict[str, str]] = []
12714
12816
  primary = str(block.get("default") or "").strip()
12715
12817
  if primary and primary not in seen:
12818
+ bill = _billing_provider_id_from_hermes_config(str(block.get("provider") or "")) or str(
12819
+ block.get("provider") or "openrouter"
12820
+ )
12716
12821
  extra.append(
12717
12822
  _model_suggestion_row(
12718
12823
  primary,
12719
- provider_label=str(block.get("provider") or "OpenRouter").title(),
12824
+ provider_label=_provider_display_label(bill),
12720
12825
  label="Current primary",
12721
12826
  description="Active model for this agent profile",
12722
12827
  ),
@@ -12732,10 +12837,11 @@ def _augment_model_suggestions(
12732
12837
  full = model if "/" in model else f"{prov}/{model}"
12733
12838
  if full in seen:
12734
12839
  continue
12840
+ bill = _billing_provider_id_from_hermes_config(prov) or prov
12735
12841
  extra.append(
12736
12842
  _model_suggestion_row(
12737
12843
  full,
12738
- provider_label=prov.title(),
12844
+ provider_label=_provider_display_label(bill),
12739
12845
  label=f"Fallback · {model}",
12740
12846
  description="Configured in this profile's fallback chain",
12741
12847
  ),
@@ -13732,6 +13838,8 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13732
13838
  if _triggering_user:
13733
13839
  _reconcile_profile_llm_for_user(prof, _triggering_user, _workspace_id)
13734
13840
  llm_provider = _llm_billing_provider(prof, user_id=_triggering_user, workspace_id=_workspace_id)
13841
+ model_block = _read_model_block(prof)
13842
+ model_used = str(model_block.get("default") or "").strip()
13735
13843
  llm_ready = bool(
13736
13844
  _triggering_user
13737
13845
  and _user_can_use_llm(_triggering_user, _workspace_id, llm_provider)
@@ -13810,8 +13918,10 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13810
13918
  handler.send_header("X-Workframe-Api-Port", str(lifecycle["api_port"]))
13811
13919
  handler.end_headers()
13812
13920
  handler.wfile.write(
13813
- b"event: run.started\n"
13814
- b"data: {\"text\":\"Contacting model...\"}\n\n"
13921
+ (
13922
+ "event: run.started\n"
13923
+ f"data: {json.dumps({'text': 'Contacting model...', 'model': model_used, 'llm_provider': llm_provider})}\n\n"
13924
+ ).encode("utf-8")
13815
13925
  )
13816
13926
  handler.wfile.flush()
13817
13927
  buffer = b""
@@ -15122,7 +15232,7 @@ def _set_profile_model_provider(profile: str, provider: str) -> tuple[bool, str]
15122
15232
  return True, ""
15123
15233
 
15124
15234
 
15125
- def _read_model_block(profile: str) -> dict[str, Any]:
15235
+ def _parse_model_block_from_disk(profile: str) -> dict[str, Any]:
15126
15236
  """Parse the model surface from the profile's config.yaml:
15127
15237
  `model.default`, `model.provider`, etc. (siblings of `model:`) AND
15128
15238
  the top-level `fallback_providers:` block. Pure text scan — no
@@ -15231,6 +15341,48 @@ def _read_model_block(profile: str) -> dict[str, Any]:
15231
15341
  return out
15232
15342
 
15233
15343
 
15344
+ def _read_model_block(profile: str) -> dict[str, Any]:
15345
+ """Model surface for a profile. Runtime u-* slugs inherit default/chain from template."""
15346
+ raw = str(profile or "").strip()
15347
+ if not raw:
15348
+ return _parse_model_block_from_disk("")
15349
+ try:
15350
+ prof = resolve_hermes_profile(raw)
15351
+ except ValueError:
15352
+ return _parse_model_block_from_disk(raw)
15353
+ block = _parse_model_block_from_disk(prof)
15354
+ if not _is_runtime_profile_slug(prof):
15355
+ return block
15356
+ template = resolve_validated_profile(_runtime_template_slug(prof))
15357
+ tblock = _parse_model_block_from_disk(template)
15358
+ if str(tblock.get("default") or "").strip():
15359
+ block["default"] = tblock["default"]
15360
+ if tblock.get("fallback_chain"):
15361
+ block["fallback_chain"] = list(tblock["fallback_chain"])
15362
+ tpl_provider = str(tblock.get("provider") or "").strip()
15363
+ if tpl_provider:
15364
+ block["provider"] = tpl_provider
15365
+ return block
15366
+
15367
+
15368
+ def _sync_runtime_model_from_template(runtime: str, template: str) -> None:
15369
+ """Keep per-user runtime Hermes config aligned with agent template model picks."""
15370
+ runtime_slug = safe_profile_slug(runtime)
15371
+ template_slug = resolve_validated_profile(template)
15372
+ if not _is_runtime_profile_slug(runtime_slug) or not profile_exists(runtime_slug):
15373
+ return
15374
+ tblock = _parse_model_block_from_disk(template_slug)
15375
+ default = str(tblock.get("default") or "").strip()
15376
+ if default:
15377
+ _set_profile_model(runtime_slug, default)
15378
+ chain = [e for e in (tblock.get("fallback_chain") or []) if isinstance(e, dict)]
15379
+ if chain:
15380
+ _write_fallback_chain(runtime_slug, chain)
15381
+ billing = _billing_provider_id_from_hermes_config(str(tblock.get("provider") or ""))
15382
+ if billing and default:
15383
+ _apply_model_for_billing_provider(runtime_slug, billing, default)
15384
+
15385
+
15234
15386
  def _write_fallback_chain(profile: str, chain: list[dict[str, str]]) -> tuple[bool, str]:
15235
15387
  """Rewrite the `fallback_providers` block in config.yaml to match the
15236
15388
  supplied chain. Preserves all other keys, comments, and ordering.
@@ -15332,24 +15484,22 @@ def _write_fallback_chain(profile: str, chain: list[dict[str, str]]) -> tuple[bo
15332
15484
  return True, ""
15333
15485
 
15334
15486
 
15335
- def hermes_models(profile: str = "", user_id: str = "", workspace_id: str = "") -> dict[str, Any]:
15487
+ def hermes_models(
15488
+ profile: str = "",
15489
+ user_id: str = "",
15490
+ workspace_id: str = "",
15491
+ *,
15492
+ selection_only: bool = False,
15493
+ ) -> dict[str, Any]:
15336
15494
  """Return a profile's model surface: primary, fallback chain,
15337
15495
  and curated suggestions scoped to the active provider. The catalog
15338
15496
  is a hint list, not a hardcoded menu — the BFF accepts any
15339
15497
  `provider/model` id and the picker always lets the user type a
15340
15498
  custom one.
15341
15499
  """
15342
- primary_profile = _resolve_models_profile(profile) if profile else _primary_profile()
15343
- if primary_profile:
15344
- _bootstrap_profile_providers(primary_profile, user_id, workspace_id)
15345
- block = _read_model_block(primary_profile) if primary_profile else {
15346
- "default": "", "provider": "", "base_url": "",
15347
- "providers": {}, "fallback_chain": [],
15348
- }
15349
- active_provider = block.get("provider", "").strip()
15350
15500
  picker_llm = _user_llm_providers_for_picker(user_id) if user_id else set()
15351
15501
  connected = _connected_provider_names(user_id, workspace_id)
15352
- has_llm = bool(picker_llm) if user_id else any(
15502
+ has_llm = _user_llm_has_provider(user_id, workspace_id) if user_id else any(
15353
15503
  str(spec.get("category") or "") == "llm"
15354
15504
  and str(spec.get("id")).lower() in {n.lower() for n in connected}
15355
15505
  for spec in PROVIDER_CONNECT_CATALOG
@@ -15360,6 +15510,48 @@ def hermes_models(profile: str = "", user_id: str = "", workspace_id: str = "")
15360
15510
  if str(spec.get("category") or "") == "llm"
15361
15511
  and str(spec.get("id")).lower() in {n.lower() for n in connected}
15362
15512
  }
15513
+
15514
+ if selection_only:
15515
+ user_primary, user_chain = _read_user_llm_prefs(user_id) if user_id else ("", [])
15516
+ primary = user_primary or (HERMES_DEFAULT_PRIMARY if has_llm else "")
15517
+ billing = _resolve_billing_provider_for_model(primary, connected_llm) if primary else ""
15518
+ suggestions = _augment_model_suggestions(
15519
+ _suggestions_for_connected_llm_providers(connected_llm) if connected_llm else [],
15520
+ {
15521
+ "default": primary,
15522
+ "provider": billing or "",
15523
+ "base_url": "",
15524
+ "providers": {},
15525
+ "fallback_chain": user_chain,
15526
+ },
15527
+ )
15528
+ return {
15529
+ "ok": True,
15530
+ "profile": "",
15531
+ "primary": primary if has_llm else "",
15532
+ "provider": billing if has_llm else "",
15533
+ "base_url": "",
15534
+ "fallback_chain": user_chain if has_llm else [],
15535
+ "suggestions": suggestions,
15536
+ "connected_providers": sorted(picker_llm if user_id else connected),
15537
+ "has_llm_provider": has_llm,
15538
+ "billing_provider": billing if has_llm else "",
15539
+ "selection_only": True,
15540
+ "default_primary": HERMES_DEFAULT_PRIMARY,
15541
+ "default_fallback_chain": HERMES_DEFAULT_FALLBACK_CHAIN,
15542
+ }
15543
+
15544
+ try:
15545
+ primary_profile = _resolve_models_profile(profile) if profile else _primary_profile()
15546
+ except ValueError as exc:
15547
+ return {"ok": False, "error": str(exc)}
15548
+ if primary_profile:
15549
+ _bootstrap_profile_providers(primary_profile, user_id, workspace_id)
15550
+ block = _read_model_block(primary_profile) if primary_profile else {
15551
+ "default": "", "provider": "", "base_url": "",
15552
+ "providers": {}, "fallback_chain": [],
15553
+ }
15554
+ active_provider = block.get("provider", "").strip()
15363
15555
  suggestions = _augment_model_suggestions(
15364
15556
  _suggestions_for_connected_llm_providers(connected_llm) if connected_llm else [],
15365
15557
  block,
@@ -15384,16 +15576,25 @@ def hermes_models(profile: str = "", user_id: str = "", workspace_id: str = "")
15384
15576
  seen_models.add(mid)
15385
15577
  deduped.append(row)
15386
15578
  suggestions = deduped
15579
+ primary_model = str(block.get("default") or "").strip()
15580
+ billing = _billing_provider_id_from_hermes_config(active_provider)
15581
+ if not billing and primary_model:
15582
+ billing = _resolve_billing_provider_for_model(primary_model, connected_llm) or ""
15583
+ if str(active_provider or "").lower() == "custom" and billing:
15584
+ display_provider = billing
15585
+ else:
15586
+ display_provider = active_provider
15387
15587
  return {
15388
15588
  "ok": True,
15389
15589
  "profile": primary_profile,
15390
- "primary": block.get("default", "") if has_llm else "",
15391
- "provider": active_provider if has_llm else "",
15590
+ "primary": primary_model if has_llm else "",
15591
+ "provider": display_provider if has_llm else "",
15392
15592
  "base_url": block.get("base_url", ""),
15393
15593
  "fallback_chain": block.get("fallback_chain", []) if has_llm else [],
15394
15594
  "suggestions": suggestions,
15395
15595
  "connected_providers": sorted(picker_llm if user_id else connected),
15396
15596
  "has_llm_provider": has_llm,
15597
+ "billing_provider": billing if has_llm else "",
15397
15598
  "default_primary": HERMES_DEFAULT_PRIMARY,
15398
15599
  "default_fallback_chain": HERMES_DEFAULT_FALLBACK_CHAIN,
15399
15600
  }
@@ -15428,8 +15629,27 @@ def hermes_model_set(
15428
15629
  model_id: str,
15429
15630
  user_id: str = "",
15430
15631
  workspace_id: str = "",
15632
+ *,
15633
+ selection_only: bool = False,
15431
15634
  ) -> dict[str, Any]:
15432
15635
  """Set primary model and align Hermes provider routing to a connected billing provider."""
15636
+ user = str(user_id or "").strip()
15637
+ if selection_only:
15638
+ if not user:
15639
+ return {"ok": False, "error": "unauthorized"}
15640
+ connected = _user_llm_providers_for_picker(user)
15641
+ billing = _resolve_billing_provider_for_model(model_id, connected)
15642
+ if not billing and not _user_llm_has_provider(user, workspace_id):
15643
+ return {"ok": False, "error": "connect an LLM provider first"}
15644
+ _write_user_llm_prefs(user, primary=model_id)
15645
+ return {
15646
+ "ok": True,
15647
+ "profile": "",
15648
+ "model": model_id,
15649
+ "provider": billing or "",
15650
+ "billing_provider": billing or "",
15651
+ "selection_only": True,
15652
+ }
15433
15653
  target = profile or _primary_profile()
15434
15654
  if not target:
15435
15655
  return {"ok": False, "error": "no profile resolved"}
@@ -15446,6 +15666,11 @@ def hermes_model_set(
15446
15666
  ok, err = _set_profile_model(target, model_id)
15447
15667
  if not ok:
15448
15668
  return {"ok": False, "error": err}
15669
+ tpl = safe_profile_slug(target)
15670
+ if user and not _is_runtime_profile_slug(tpl):
15671
+ runtime = _runtime_profile_slug(user, tpl)
15672
+ if _runtime_profile_on_disk(runtime):
15673
+ _sync_runtime_model_from_template(runtime, tpl)
15449
15674
  block = _read_model_block(target)
15450
15675
  return {
15451
15676
  "ok": True,
@@ -15458,14 +15683,17 @@ def hermes_model_set(
15458
15683
  }
15459
15684
 
15460
15685
 
15461
- def hermes_fallback_chain_set(profile: str, chain: list[Any]) -> dict[str, Any]:
15686
+ def hermes_fallback_chain_set(
15687
+ profile: str,
15688
+ chain: list[Any],
15689
+ *,
15690
+ selection_only: bool = False,
15691
+ user_id: str = "",
15692
+ ) -> dict[str, Any]:
15462
15693
  """Set the fallback chain for a profile. Each entry must have
15463
15694
  `provider` and `model` keys. Order matters: Hermes tries them in
15464
15695
  sequence when the primary fails.
15465
15696
  """
15466
- target = profile or _primary_profile()
15467
- if not target:
15468
- return {"ok": False, "error": "no profile resolved"}
15469
15697
  normalized: list[dict[str, str]] = []
15470
15698
  for entry in chain:
15471
15699
  if not isinstance(entry, dict):
@@ -15478,9 +15706,24 @@ def hermes_fallback_chain_set(profile: str, chain: list[Any]) -> dict[str, Any]:
15478
15706
  "error": f"invalid fallback entry: provider={provider!r} model={model!r}",
15479
15707
  }
15480
15708
  normalized.append({"provider": provider, "model": model})
15709
+ if selection_only:
15710
+ user = str(user_id or "").strip()
15711
+ if not user:
15712
+ return {"ok": False, "error": "unauthorized"}
15713
+ _write_user_llm_prefs(user, fallback_chain=normalized)
15714
+ return {"ok": True, "profile": "", "fallback_chain": normalized, "selection_only": True}
15715
+ target = profile or _primary_profile()
15716
+ if not target:
15717
+ return {"ok": False, "error": "no profile resolved"}
15481
15718
  ok, err = _write_fallback_chain(target, normalized)
15482
15719
  if not ok:
15483
15720
  return {"ok": False, "error": err}
15721
+ user = str(user_id or "").strip()
15722
+ tpl = safe_profile_slug(target)
15723
+ if user and not _is_runtime_profile_slug(tpl):
15724
+ runtime = _runtime_profile_slug(user, tpl)
15725
+ if _runtime_profile_on_disk(runtime):
15726
+ _sync_runtime_model_from_template(runtime, tpl)
15484
15727
  return {"ok": True, "profile": target, "fallback_chain": normalized}
15485
15728
 
15486
15729
 
@@ -16958,7 +17201,10 @@ class Handler(BaseHTTPRequestHandler):
16958
17201
  profile = qs.get("profile", [""])[0]
16959
17202
  user_id = str(getattr(self, "auth_user", "") or "")
16960
17203
  workspace_id = qs.get("workspace_id", [""])[0]
16961
- return self._json(200, hermes_models(profile, user_id, workspace_id))
17204
+ selection_only = qs.get("selection_only", ["0"])[0] in {"1", "true", "yes"}
17205
+ payload = hermes_models(profile, user_id, workspace_id, selection_only=selection_only)
17206
+ status = 200 if payload.get("ok", True) else 400
17207
+ return self._json(status, payload)
16962
17208
  if path == "/api/hermes/profiles/status":
16963
17209
  profile = resolve_validated_profile(qs.get("profile", [""])[0] or _primary_profile())
16964
17210
  if SUPERVISOR_URL:
@@ -18961,11 +19207,24 @@ class Handler(BaseHTTPRequestHandler):
18961
19207
  profile = str(body.get("profile", "")).strip()
18962
19208
  user_id = str(getattr(self, "auth_user", "") or "")
18963
19209
  workspace_id = str(body.get("workspace_id", "")).strip()
19210
+ selection_only = bool(body.get("selection_only"))
18964
19211
  if not model_id:
18965
19212
  return self._json(400, {"error": "model required"})
18966
- if profile:
18967
- _bootstrap_profile_providers(resolve_validated_profile(profile), user_id, workspace_id)
18968
- return self._json(200, hermes_model_set(profile, model_id, user_id, workspace_id))
19213
+ if profile and not selection_only:
19214
+ try:
19215
+ _bootstrap_profile_providers(_resolve_models_profile(profile), user_id, workspace_id)
19216
+ except ValueError as exc:
19217
+ return self._json(400, {"ok": False, "error": str(exc)})
19218
+ return self._json(
19219
+ 200,
19220
+ hermes_model_set(
19221
+ profile,
19222
+ model_id,
19223
+ user_id,
19224
+ workspace_id,
19225
+ selection_only=selection_only,
19226
+ ),
19227
+ )
18969
19228
  if path == "/api/hermes/model/apply-default":
18970
19229
  if not _check_auth(self):
18971
19230
  return self._json(401, {"error": "unauthorized"})
@@ -18975,13 +19234,23 @@ class Handler(BaseHTTPRequestHandler):
18975
19234
  if path == "/api/hermes/fallback-chain":
18976
19235
  if not _check_auth(self):
18977
19236
  return self._json(401, {"error": "unauthorized"})
18978
- if not _role_allows(self, OWNER_ADMIN_ROLES):
18979
- return self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
18980
19237
  profile = str(body.get("profile", "")).strip()
18981
19238
  chain = body.get("chain", [])
19239
+ selection_only = bool(body.get("selection_only"))
19240
+ user_id = str(getattr(self, "auth_user", "") or "")
18982
19241
  if not isinstance(chain, list):
18983
19242
  return self._json(400, {"error": "chain must be a list"})
18984
- return self._json(200, hermes_fallback_chain_set(profile, chain))
19243
+ if not selection_only and not _role_allows(self, OWNER_ADMIN_ROLES):
19244
+ return self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
19245
+ return self._json(
19246
+ 200,
19247
+ hermes_fallback_chain_set(
19248
+ profile,
19249
+ chain,
19250
+ selection_only=selection_only,
19251
+ user_id=user_id,
19252
+ ),
19253
+ )
18985
19254
  if path == "/api/board":
18986
19255
  if not _check_auth(self):
18987
19256
  return self._json(401, {"error": "unauthorized"})
@@ -251,18 +251,18 @@ def patch_stack_config(body: dict[str, Any]) -> dict[str, Any]:
251
251
 
252
252
 
253
253
  def resolve_deployment_mode(env_default: str = "trusted_team") -> str:
254
- """Explicit WORKFRAME_DEPLOYMENT_MODE env wins; stack_config applies only when env is unset."""
255
- env_raw = (os.environ.get("WORKFRAME_DEPLOYMENT_MODE") or "").strip().lower()
256
- if env_raw in DEPLOYMENT_MODES:
257
- return env_raw
258
- if env_raw:
259
- return env_raw
254
+ """Persisted stack_config deployment_mode wins when set; else WORKFRAME_DEPLOYMENT_MODE env."""
260
255
  try:
261
- sc_mode = str(get_stack_config().get("deployment_mode") or "").strip().lower()
256
+ sc_mode = str(_read_raw().get("deployment_mode") or "").strip().lower()
262
257
  if sc_mode in DEPLOYMENT_MODES:
263
258
  return sc_mode
264
259
  except Exception:
265
260
  pass
261
+ env_raw = (os.environ.get("WORKFRAME_DEPLOYMENT_MODE") or "").strip().lower()
262
+ if env_raw in DEPLOYMENT_MODES:
263
+ return env_raw
264
+ if env_raw:
265
+ return env_raw
266
266
  raw = (env_default or "trusted_team").strip().lower()
267
267
  return raw if raw in DEPLOYMENT_MODES else "trusted_team"
268
268
 
@@ -48,4 +48,9 @@ resolved = stack_config.resolved_smtp()
48
48
  assert resolved.get("host") == "smtp.ghost.example", resolved
49
49
  assert resolved.get("source") == "env", resolved
50
50
 
51
+ # Wizard deployment_mode persists over compose .env default
52
+ os.environ["WORKFRAME_DEPLOYMENT_MODE"] = "trusted_team"
53
+ stack_config.patch_stack_config({"deployment_mode": "single_user_local"})
54
+ assert stack_config.resolve_deployment_mode() == "single_user_local"
55
+
51
56
  print("stack_config install-window smtp self-check ok")
@@ -1 +1 @@
1
- import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-uZYeD3o1.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-CPbiDkEZ.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -0,0 +1 @@
1
+ import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-Dm2L9Ib_.js";export{e as createArchitectureServices};