create-workframe 0.1.17 → 0.1.20

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 (137) hide show
  1. package/README.md +1 -1
  2. package/bin/create-workframe.js +73 -9
  3. package/package.json +1 -1
  4. package/scripts/apply-update-workframe.sh +21 -18
  5. package/scripts/compose-docker-host.sh +26 -0
  6. package/workframe-api/action_proxy.py +8 -0
  7. package/workframe-api/chat_sessions.py +37 -6
  8. package/workframe-api/chat_stream.py +16 -0
  9. package/workframe-api/db_schema.py +29 -0
  10. package/workframe-api/handler_modules/handler_admin.py +32 -0
  11. package/workframe-api/handler_modules/handler_auth.py +6 -4
  12. package/workframe-api/handler_modules/handler_chat.py +17 -26
  13. package/workframe-api/handler_modules/handler_provider.py +13 -14
  14. package/workframe-api/handler_modules/handler_workspace.py +60 -2
  15. package/workframe-api/hermes_profiles.py +31 -9
  16. package/workframe-api/llm_proxy.py +18 -0
  17. package/workframe-api/mention_invoke.py +29 -24
  18. package/workframe-api/message_reactions.py +139 -0
  19. package/workframe-api/model_surface.py +329 -113
  20. package/workframe-api/package.json +4 -4
  21. package/workframe-api/profile_gateway.py +14 -6
  22. package/workframe-api/provider_bindings.py +118 -17
  23. package/workframe-api/provider_bootstrap.py +74 -38
  24. package/workframe-api/provider_model_catalog.py +261 -0
  25. package/workframe-api/rooms.py +4 -3
  26. package/workframe-api/route_registry.py +4 -0
  27. package/workframe-api/runtime_cohort.py +118 -23
  28. package/workframe-api/scripts/run-python.mjs +28 -0
  29. package/workframe-api/server.py +44 -12
  30. package/workframe-api/test_agent_identity_authority.py +96 -0
  31. package/workframe-api/test_chat_attribution_history.py +59 -0
  32. package/workframe-api/test_enrich_room_messages.py +43 -2
  33. package/workframe-api/test_llm_proxy_encoding.py +33 -0
  34. package/workframe-api/test_message_reactions.py +151 -0
  35. package/workframe-api/test_model_routing_reconcile.py +122 -0
  36. package/workframe-api/test_model_surface_consistency.py +45 -5
  37. package/workframe-api/test_oauth_llm.py +1 -1
  38. package/workframe-api/test_profile_gateway_health.py +37 -0
  39. package/workframe-api/test_provider_bindings.py +115 -1
  40. package/workframe-api/test_provider_model_catalog.py +27 -0
  41. package/workframe-api/test_provider_model_surface.py +2 -2
  42. package/workframe-api/test_runtime_lease_lifecycle.py +93 -0
  43. package/workframe-api/test_server_reexports.py +2 -0
  44. package/workframe-api/test_user_llm_prefs.py +27 -0
  45. package/workframe-api/test_workspace_file_actions.py +119 -0
  46. package/workframe-api/turn_credentials.py +41 -0
  47. package/workframe-api/turn_overlay.py +95 -34
  48. package/workframe-api/updates.py +14 -5
  49. package/workframe-api/user_prefs.py +15 -0
  50. package/workframe-api/workspace_bootstrap.py +1 -0
  51. package/workframe-api/workspace_files.py +124 -0
  52. package/workframe-supervisor/server.py +18 -2
  53. package/workframe-ui/public/assets/{arc-esSBm9C_.js → arc-BkATcoqt.js} +1 -1
  54. package/workframe-ui/public/assets/architecture-7EHR7CIX-yusDFKmK.js +1 -0
  55. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DVVz9RGn.js → architectureDiagram-3BPJPVTR-31_yAiUS.js} +1 -1
  56. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-B6i10GMX.js → blockDiagram-GPEHLZMM-ClM6Si9T.js} +1 -1
  57. package/workframe-ui/public/assets/{brave-B885JYnH.svg → brave-BoYMbZi1.svg} +0 -1
  58. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BqwJ7JoH.js → c4Diagram-AAUBKEIU-DJzEktF3.js} +1 -1
  59. package/workframe-ui/public/assets/channel-Nb_UjZ3O.js +1 -0
  60. package/workframe-ui/public/assets/{chunk-2J33WTMH-CyhcedSa.js → chunk-2J33WTMH-DPFiyb5O.js} +1 -1
  61. package/workframe-ui/public/assets/{chunk-3OPIFGDE-1Rr6WuvI.js → chunk-3OPIFGDE-DXNJzuCn.js} +1 -1
  62. package/workframe-ui/public/assets/{chunk-4BX2VUAB-D2bRrARV.js → chunk-4BX2VUAB-DDNibGdr.js} +1 -1
  63. package/workframe-ui/public/assets/{chunk-55IACEB6-CKXeZxSr.js → chunk-55IACEB6-Co3_ulwA.js} +1 -1
  64. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DYQxxPZ4.js → chunk-5ZQYHXKU-Cmxz8o4d.js} +1 -1
  65. package/workframe-ui/public/assets/{chunk-727SXJPM-CGpIgqvD.js → chunk-727SXJPM-CDAQGesU.js} +1 -1
  66. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-B88NGQ7d.js → chunk-AQP2D5EJ-x-swy3kc.js} +1 -1
  67. package/workframe-ui/public/assets/{chunk-BSJP7CBP-CCymiI0O.js → chunk-BSJP7CBP-28nU0JOE.js} +1 -1
  68. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-DRgey03j.js → chunk-CSCIHK7Q-FUURFika.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-FMBD7UC4-l1adn8gM.js → chunk-FMBD7UC4-B4RgRO2R.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CaEvDRPK.js → chunk-KSCS5N6A-CkuppW3T.js} +1 -1
  71. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-BgCnvPTv.js → chunk-L5ZTLDWV-DyrPSaz3.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-LZXEDZCA-t2zokHpn.js → chunk-LZXEDZCA-DFaZSDNV.js} +2 -2
  73. package/workframe-ui/public/assets/{chunk-ND2GUHAM-C3c9p0sv.js → chunk-ND2GUHAM-DXI0OJxg.js} +1 -1
  74. package/workframe-ui/public/assets/{chunk-NZK2D7GU-DSujF-R8.js → chunk-NZK2D7GU-CyOp1ZG2.js} +1 -1
  75. package/workframe-ui/public/assets/{chunk-O5CBEL6O-DJJTbM6p.js → chunk-O5CBEL6O-tZZMCfbx.js} +1 -1
  76. package/workframe-ui/public/assets/chunk-QZHKN3VN-C_TtOXi5.js +1 -0
  77. package/workframe-ui/public/assets/chunk-WU5MYG2G-BjSJQEIY.js +1 -0
  78. package/workframe-ui/public/assets/{chunk-XPW4576I-D2Cxn3AG.js → chunk-XPW4576I-B7_2gS3x.js} +1 -1
  79. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-JD6iZUGh.js +1 -0
  80. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-JD6iZUGh.js +1 -0
  81. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A---dQ-pFK.js → cose-bilkent-S5V4N54A-BWJaHw_L.js} +1 -1
  82. package/workframe-ui/public/assets/{dagre-BM42HDAG-B_qe1ODY.js → dagre-BM42HDAG-Bu5MadGV.js} +1 -1
  83. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Bou8vEYl.js → diagram-2AECGRRQ-ckLZkDFD.js} +1 -1
  84. package/workframe-ui/public/assets/{diagram-5GNKFQAL-DEUEzONU.js → diagram-5GNKFQAL-Cf6Abx1Y.js} +1 -1
  85. package/workframe-ui/public/assets/{diagram-KO2AKTUF-Ctq571VA.js → diagram-KO2AKTUF-TLk7_5mJ.js} +1 -1
  86. package/workframe-ui/public/assets/{diagram-LMA3HP47-CnKn89lm.js → diagram-LMA3HP47-ouNxsAb0.js} +1 -1
  87. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DnKlrMx2.js → diagram-OG6HWLK6-BMCBQ35D.js} +1 -1
  88. package/workframe-ui/public/assets/{dist-ubrEgQyZ.js → dist-C9bxMexz.js} +1 -1
  89. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-PjqO7CJd.js → erDiagram-TEJ5UH35-BN3e6jb1.js} +1 -1
  90. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Bqd_sTz0.js +1 -0
  91. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-B2PN_nNG.js → flowDiagram-I6XJVG4X-BiinzZrd.js} +1 -1
  92. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ThyAtNQP.js → ganttDiagram-6RSMTGT7-AYE8oBBG.js} +1 -1
  93. package/workframe-ui/public/assets/{gemini-Cq-0-rmr.svg → gemini-DqnZlvMo.svg} +0 -1
  94. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-CB2UgoWy.js → gitGraph-WXDBUCRP-mIq_E2vs.js} +1 -1
  95. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-DRJJ-zT-.js → gitGraphDiagram-PVQCEYII-Cf8IFEcO.js} +1 -1
  96. package/workframe-ui/public/assets/index-BnfFRGR3.js +133 -0
  97. package/workframe-ui/public/assets/index-COshPjF3.css +1 -0
  98. package/workframe-ui/public/assets/{info-J43DQDTF-CLtR33Q_.js → info-J43DQDTF--_3kCaIQ.js} +1 -1
  99. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-Cmunqfvi.js → infoDiagram-5YYISTIA-BuIOyYMu.js} +1 -1
  100. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-DWE_84eL.js → ishikawaDiagram-YF4QCWOH-D3bbsXQ8.js} +1 -1
  101. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-BtBhpyRL.js → journeyDiagram-JHISSGLW-BrZ26C8L.js} +1 -1
  102. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-B8QspDDL.js → kanban-definition-UN3LZRKU-CJiUyUOr.js} +1 -1
  103. package/workframe-ui/public/assets/{line-iOgDn4O8.js → line-Bee1snmU.js} +1 -1
  104. package/workframe-ui/public/assets/{linear-BBehZNVA.js → linear-dfD-zlI7.js} +1 -1
  105. package/workframe-ui/public/assets/{mermaid-parser.core-DQK5HcdU.js → mermaid-parser.core-D6IqqDSH.js} +2 -2
  106. package/workframe-ui/public/assets/{mermaid.core-Dsrdd1Wr.js → mermaid.core-CuuxYt2r.js} +3 -3
  107. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-Ds_BfsJA.js → mindmap-definition-RKZ34NQL-T7W2fkwA.js} +1 -1
  108. package/workframe-ui/public/assets/{packet-YPE3B663-Bz3pi4i8.js → packet-YPE3B663-CiXMDasj.js} +1 -1
  109. package/workframe-ui/public/assets/{pie-LRSECV5Y-BFFah-6g.js → pie-LRSECV5Y-CUx1_96f.js} +1 -1
  110. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CVCvLPcC.js → pieDiagram-4H26LBE5-DfR96FUO.js} +1 -1
  111. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-Cx6rrc_7.js → quadrantDiagram-W4KKPZXB-Dj6mqgQE.js} +1 -1
  112. package/workframe-ui/public/assets/{radar-GUYGQ44K-DJ5vN_cg.js → radar-GUYGQ44K-DKT2q98y.js} +1 -1
  113. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-Cbry6b5c.js → requirementDiagram-4Y6WPE33-DYDnBVw9.js} +1 -1
  114. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CICUr5En.js → sankeyDiagram-5OEKKPKP-BSkxpjjO.js} +1 -1
  115. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BsOuB3F-.js → sequenceDiagram-3UESZ5HK-Dz6SNH9S.js} +1 -1
  116. package/workframe-ui/public/assets/{src-TS8jZspq.js → src-DC521nE_.js} +1 -1
  117. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-avBzMQy_.js → stateDiagram-AJRCARHV-D4ZoF5NP.js} +1 -1
  118. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DknveEiR.js +1 -0
  119. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DHLm6UFN.js → timeline-definition-PNZ67QCA-d0ezg1Hu.js} +1 -1
  120. package/workframe-ui/public/assets/{treeView-BLDUP644-B3Qd5t3j.js → treeView-BLDUP644-kXyaqwIy.js} +1 -1
  121. package/workframe-ui/public/assets/{treemap-LRROVOQU-D1dMQS7x.js → treemap-LRROVOQU-g168p7-O.js} +1 -1
  122. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-DZ6SoniY.js → vennDiagram-CIIHVFJN-C0_QsmRl.js} +1 -1
  123. package/workframe-ui/public/assets/{wardley-L42UT6IY-9gK17Tp3.js → wardley-L42UT6IY-BNFCdzxQ.js} +1 -1
  124. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-FCU_wmRE.js → wardleyDiagram-YWT4CUSO-BOwUS9sY.js} +1 -1
  125. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DWowvseN.js → xychartDiagram-2RQKCTM6-Cxr1ZiKh.js} +1 -1
  126. package/workframe-ui/public/index.html +2 -2
  127. package/workframe-ui/public/workframe-build.json +3 -3
  128. package/workframe-ui/public/assets/architecture-7EHR7CIX-j-DiBdGu.js +0 -1
  129. package/workframe-ui/public/assets/channel-CJ9pFXbx.js +0 -1
  130. package/workframe-ui/public/assets/chunk-QZHKN3VN-Bxk3enxo.js +0 -1
  131. package/workframe-ui/public/assets/chunk-WU5MYG2G-DdbkX1rg.js +0 -1
  132. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-bwCRiiUl.js +0 -1
  133. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-bwCRiiUl.js +0 -1
  134. package/workframe-ui/public/assets/eventmodeling-FCH6USID-g-Xo_eS4.js +0 -1
  135. package/workframe-ui/public/assets/index-1euVfd3b.js +0 -132
  136. package/workframe-ui/public/assets/index-BLVAsAeO.css +0 -1
  137. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-BFRA5TYD.js +0 -1
@@ -131,9 +131,7 @@ def _set_profile_model_base_url(profile: str, base_url: str) -> tuple[bool, str]
131
131
 
132
132
 
133
133
  def _proxy_fallback_chain(profile: str, billing_provider: str) -> None:
134
- """Runtime fallbacks use provider=custom so Hermes keeps the proxy + lease."""
135
- if not _srv()._is_runtime_profile_slug(profile):
136
- return
134
+ """Vault-backed fallbacks use provider=custom so Hermes keeps proxy auth."""
137
135
  block = _srv()._read_model_block(profile)
138
136
  chain = block.get("fallback_chain") or []
139
137
  billing = str(billing_provider or "openrouter").strip().lower()
@@ -214,16 +212,17 @@ def _profile_llm_proxy_ready(profile: str, provider: str) -> bool:
214
212
  llm_provider = str(provider or "openrouter").strip().lower()
215
213
  if str(block.get("base_url") or "").strip() != _llm_proxy_base_url(llm_provider):
216
214
  return False
217
- if not _srv()._is_runtime_profile_slug(prof):
218
- return True
219
215
  if str(block.get("provider") or "").strip().lower() != "custom":
220
216
  return False
217
+ valid_fallbacks = 0
221
218
  for entry in block.get("fallback_chain") or []:
222
219
  prov = str(entry.get("provider", "")).strip().lower()
223
220
  model = str(entry.get("model", "")).strip()
221
+ if model and prov:
222
+ valid_fallbacks += 1
224
223
  if model and prov == llm_provider and prov != "custom":
225
224
  return False
226
- return True
225
+ return valid_fallbacks > 0
227
226
 
228
227
 
229
228
  def _ensure_profile_llm_proxy(profile: str, provider: str) -> None:
@@ -232,20 +231,22 @@ def _ensure_profile_llm_proxy(profile: str, provider: str) -> None:
232
231
  prof = _srv().safe_profile_slug(profile)
233
232
  if _srv()._oauth_llm_provider_spec(llm_provider):
234
233
  _srv()._normalize_profile_config_yaml(prof)
235
- _srv()._apply_mvp_model_for_provider(prof, llm_provider)
234
+ current_model = str(_srv()._read_model_block(prof).get("default") or "").strip()
235
+ if current_model:
236
+ _srv()._apply_model_for_billing_provider(prof, llm_provider, current_model)
237
+ else:
238
+ _srv()._apply_mvp_model_for_provider(prof, llm_provider)
236
239
  return
237
240
  _srv()._normalize_profile_config_yaml(prof)
238
241
  if _profile_llm_proxy_ready(prof, llm_provider):
239
242
  _ensure_profile_proxy_headers(prof)
240
- if _srv()._is_runtime_profile_slug(prof):
241
- _proxy_fallback_chain(prof, llm_provider)
243
+ _proxy_fallback_chain(prof, llm_provider)
242
244
  return
243
245
  _coalesce_profile_model_yaml(prof)
244
246
  _set_profile_model_base_url(prof, _llm_proxy_base_url(llm_provider))
245
247
  _ensure_profile_proxy_headers(prof)
246
- if _srv()._is_runtime_profile_slug(prof):
247
- _srv()._set_profile_model_provider(prof, "custom")
248
- _proxy_fallback_chain(prof, llm_provider)
248
+ _srv()._set_profile_model_provider(prof, "custom")
249
+ _proxy_fallback_chain(prof, llm_provider)
249
250
 
250
251
 
251
252
  def _set_profile_model_api_key(profile: str, api_key: str) -> tuple[bool, str]:
@@ -345,6 +346,24 @@ def _read_config_model_api_key(profile: str) -> str:
345
346
  return str(_srv()._read_model_block(profile).get("api_key") or "").strip()
346
347
 
347
348
 
349
+ def _runtime_llm_lease_id(
350
+ profile: str,
351
+ user_id: str,
352
+ workspace_id: str,
353
+ provider: str,
354
+ ) -> str:
355
+ """Stable DB key for the opaque credential bound to one user runtime."""
356
+ seed = "\x1f".join(
357
+ (
358
+ str(profile or "").strip(),
359
+ str(user_id or "").strip(),
360
+ str(workspace_id or "").strip(),
361
+ str(provider or "openrouter").strip().lower(),
362
+ ),
363
+ )
364
+ return f"runtime-llm:{uuid.uuid5(uuid.NAMESPACE_URL, seed).hex}"
365
+
366
+
348
367
 
349
368
 
350
369
  def _sync_profile_model_api_key(profile: str, api_key: str, *, wait_healthy: bool = False) -> None:
@@ -405,8 +424,16 @@ def _apply_turn_credential_lease(
405
424
  workspace_id: str,
406
425
  provider: str,
407
426
  run_id: str,
427
+ *,
428
+ wait_healthy: bool = True,
408
429
  ) -> str:
409
- """Issue per-run lease token into profile env; real secret stays in API vault."""
430
+ """Bind an opaque LLM broker token to the user runtime.
431
+
432
+ ``run_id`` remains part of the public call shape because action credentials
433
+ really are turn-scoped. LLM credentials are runtime-scoped: Hermes caches
434
+ ``model.api_key``, so per-turn rotation/revocation would restart the gateway
435
+ during normal chat and sever active streams.
436
+ """
410
437
  provider = str(provider or "openrouter").strip().lower()
411
438
  prof = _srv().resolve_hermes_profile(profile)
412
439
  env_var = _profile_lease_env_var(prof, provider)
@@ -428,9 +455,9 @@ def _apply_turn_credential_lease(
428
455
  binding_id=str(lease_meta.get("credential_binding_id") or ""),
429
456
  ):
430
457
  # ponytail: hot path — skip credential resolve + env rewrite when lease still valid
431
- if _srv()._is_runtime_profile_slug(prof) and existing_token:
458
+ if existing_token:
432
459
  if _read_config_model_api_key(prof) != existing_token:
433
- _sync_profile_model_api_key(prof, existing_token)
460
+ _sync_profile_model_api_key(prof, existing_token, wait_healthy=wait_healthy)
434
461
  return existing_token
435
462
 
436
463
  if _srv()._is_runtime_profile_slug(prof):
@@ -442,7 +469,7 @@ def _apply_turn_credential_lease(
442
469
  _srv()._sync_oauth_llm_to_profile(prof, user_id, provider)
443
470
  if not _srv()._profile_routing_matches_billing(prof, provider):
444
471
  current_model = str(_srv()._read_model_from_config(prof)[1] or "").strip()
445
- if not _srv()._resolve_billing_provider_for_model(current_model, {provider}):
472
+ if not current_model:
446
473
  current_model = str(
447
474
  (_srv().PROVIDER_MVP_MODELS.get(provider) or {}).get("primary") or current_model
448
475
  ).strip()
@@ -472,24 +499,27 @@ def _apply_turn_credential_lease(
472
499
  provider=provider,
473
500
  binding_id=binding_id,
474
501
  ):
475
- if _srv()._is_runtime_profile_slug(prof) and existing_token:
502
+ if existing_token:
476
503
  if _read_config_model_api_key(prof) != existing_token:
477
- _sync_profile_model_api_key(prof, existing_token)
504
+ _sync_profile_model_api_key(prof, existing_token, wait_healthy=wait_healthy)
478
505
  return existing_token
479
506
 
507
+ lease_id = _runtime_llm_lease_id(prof, user_id, workspace_id, provider)
480
508
  token = turn_credentials.issue_lease(
481
- run_id,
509
+ lease_id,
482
510
  user_id,
483
511
  workspace_id,
484
512
  provider,
485
513
  prof,
486
514
  binding_id or None,
515
+ ttl_seconds=turn_credentials.RUNTIME_TTL,
487
516
  )
488
517
  if token != existing_token:
489
518
  _srv()._upsert_env_secret(_srv()._profile_dir(prof) / ".env", env_var, token)
490
- if _srv()._is_runtime_profile_slug(prof):
491
- _ensure_profile_llm_proxy(prof, provider)
492
- _sync_profile_model_api_key(prof, token)
519
+ _ensure_profile_llm_proxy(prof, provider)
520
+ # config.yaml is cached by Hermes. Wait for the reload here; scheduling it
521
+ # in the background races the request and causes intermittent disconnects.
522
+ _sync_profile_model_api_key(prof, token, wait_healthy=wait_healthy)
493
523
  return token
494
524
 
495
525
 
@@ -608,20 +638,33 @@ def _apply_action_credential_lease(
608
638
 
609
639
 
610
640
  def _revoke_turn_credential_lease(run_id: str, profile: str) -> None:
611
- """Revoke lease in DB; clear stale bearer from runtime profile config/gateway."""
641
+ """Revoke genuinely turn-scoped action leases after a run.
642
+
643
+ The profile's LLM broker token is deliberately retained. It contains no
644
+ provider secret, is profile-bound by the internal proxy, and is resolved
645
+ against the owner's current vault binding on every request.
646
+ """
612
647
  run_id = str(run_id or "").strip()
613
648
  if not run_id:
614
649
  return
615
- turn_credentials.revoke_lease(run_id)
616
650
  for provider_id, _env_var in _user_action_env_specs():
617
651
  turn_credentials.revoke_lease(f"{run_id}::{provider_id}")
618
- prof = _srv().resolve_hermes_profile(profile)
619
- if not _srv()._is_runtime_profile_slug(prof):
620
- return
621
- config_key = _read_config_model_api_key(prof)
622
- if config_key.startswith(turn_credentials.LEASE_PREFIX):
623
- _clear_profile_model_api_key(prof)
624
- _srv()._restart_runtime_profile_gateway(prof)
652
+
653
+
654
+ def _revoke_runtime_llm_leases(
655
+ *,
656
+ payer_user_id: str = "",
657
+ workspace_id: str = "",
658
+ provider: str = "",
659
+ credential_binding_id: str = "",
660
+ ) -> int:
661
+ """Invalidate opaque runtime credentials when vault authority changes."""
662
+ return turn_credentials.revoke_matching_leases(
663
+ payer_user_id=payer_user_id,
664
+ workspace_id=workspace_id,
665
+ provider=provider,
666
+ credential_binding_id=credential_binding_id,
667
+ )
625
668
 
626
669
 
627
670
  def _require_user_provider(user_id: str, workspace_id: str, provider: str) -> dict[str, Any]:
@@ -736,11 +779,20 @@ def _overlay_turn_provider_env(
736
779
  workspace_id: str,
737
780
  provider: str,
738
781
  run_id: str = "",
782
+ *,
783
+ wait_healthy: bool = True,
739
784
  ) -> str:
740
- """Per-run lease into profile env; upstream key resolved only inside workframe-api."""
785
+ """Runtime broker credential into profile env; raw key stays API-only."""
741
786
  run_id = str(run_id or "").strip() or str(uuid.uuid4())
742
787
  # ponytail: yaml sync + gateway reload are async on the send path — never block TTFT (~40s).
743
- return _apply_turn_credential_lease(profile, user_id, workspace_id, provider, run_id)
788
+ return _apply_turn_credential_lease(
789
+ profile,
790
+ user_id,
791
+ workspace_id,
792
+ provider,
793
+ run_id,
794
+ wait_healthy=wait_healthy,
795
+ )
744
796
 
745
797
 
746
798
  def _try_overlay_turn_provider_env(
@@ -749,10 +801,19 @@ def _try_overlay_turn_provider_env(
749
801
  workspace_id: str,
750
802
  provider: str,
751
803
  run_id: str = "",
804
+ *,
805
+ wait_healthy: bool = True,
752
806
  ) -> bool:
753
807
  """Best-effort LLM lease; returns False when the user has no key yet."""
754
808
  try:
755
- _overlay_turn_provider_env(profile, user_id, workspace_id, provider, run_id)
809
+ _overlay_turn_provider_env(
810
+ profile,
811
+ user_id,
812
+ workspace_id,
813
+ provider,
814
+ run_id,
815
+ wait_healthy=wait_healthy,
816
+ )
756
817
  return True
757
818
  except ValueError as exc:
758
819
  if "no_llm_provider_for_user" in str(exc):
@@ -96,11 +96,13 @@ def prefetch_workframe_npm_tarball(version: str) -> str:
96
96
  body = resp.read()
97
97
  except (urllib.error.URLError, TimeoutError, OSError) as exc:
98
98
  raise ValueError(f"npm_download_failed:{exc}") from exc
99
- if integrity.startswith("sha512-"):
100
- actual = "sha512-" + base64.b64encode(hashlib.sha512(body).digest()).decode()
101
- if actual != integrity:
102
- raise ValueError("npm_integrity_mismatch")
99
+ if not integrity.startswith("sha512-"):
100
+ raise ValueError("npm_integrity_missing")
101
+ actual = "sha512-" + base64.b64encode(hashlib.sha512(body).digest()).decode()
102
+ if actual != integrity:
103
+ raise ValueError("npm_integrity_mismatch")
103
104
  dest.write_bytes(body)
105
+ dest.with_suffix(dest.suffix + ".sha512").write_text(actual + "\n", encoding="utf-8")
104
106
  return _supervisor_tarball_path(pkg, ver)
105
107
 
106
108
 
@@ -540,7 +542,9 @@ def _run_apply_scripts(target: str, env: dict[str, str]) -> dict[str, Any]:
540
542
  )
541
543
  logs.append(f"=== {script} (exit {proc.returncode}) ===\n{proc.stdout}\n{proc.stderr}")
542
544
  if proc.returncode != 0:
543
- raise ValueError(f"update_failed:{Path(script).name}")
545
+ detail = (proc.stderr or proc.stdout).strip().splitlines()
546
+ tail = detail[-1].strip() if detail else f"exit_{proc.returncode}"
547
+ raise ValueError(f"update_failed:{Path(script).name}:{tail[-600:]}")
544
548
  return {"ok": True, "target": target, "log": "\n".join(logs)[-12000:]}
545
549
 
546
550
 
@@ -573,6 +577,11 @@ def apply_update(target: str, *, user_ack: bool = False) -> dict[str, Any]:
573
577
 
574
578
  if channel == "supervisor":
575
579
  body: dict[str, Any] = {"target": target}
580
+ host_compose_dir = str(os.environ.get("WORKFRAME_HOST_COMPOSE_DIR") or "").strip()
581
+ host_project_root = str(os.environ.get("WORKFRAME_HOST_PROJECT_ROOT") or "").strip()
582
+ if host_compose_dir and host_project_root:
583
+ body["host_compose_dir"] = host_compose_dir
584
+ body["host_project_root"] = host_project_root
576
585
  if workframe_version:
577
586
  body["workframe_version"] = workframe_version
578
587
  if workframe_tarball:
@@ -192,6 +192,21 @@ def apply_me_profile_updates(user_id: str, body: dict[str, Any]) -> dict[str, An
192
192
  platform_ids = parse_user_platform_ids(body.get("platform_ids"))
193
193
  conn = _srv()._workframe_db()
194
194
  try:
195
+ current_row = conn.execute(
196
+ "SELECT platform_ids FROM users WHERE id = ?", (user_id,),
197
+ ).fetchone()
198
+ if current_row:
199
+ try:
200
+ current_ids = json.loads(current_row["platform_ids"] or "{}")
201
+ except (TypeError, json.JSONDecodeError):
202
+ current_ids = {}
203
+ if isinstance(current_ids, dict):
204
+ # LLM preferences share this legacy JSON column but are
205
+ # not public messaging identities. Preserve them when
206
+ # Linked Accounts saves discord/telegram/slack values.
207
+ for key in ("llm_primary", "llm_fallback_chain"):
208
+ if key in current_ids:
209
+ platform_ids[key] = current_ids[key]
195
210
  now_ts = str(int(time.time()))
196
211
  conn.execute(
197
212
  "UPDATE users SET platform_ids = ?, updated_at = ? WHERE id = ?",
@@ -527,6 +527,7 @@ def _ensure_default_workspace() -> bool:
527
527
  display_name=native_display,
528
528
  role="native",
529
529
  is_native=True,
530
+ preserve_existing_identity=True,
530
531
  )
531
532
  return True
532
533
 
@@ -3,9 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import base64
6
+ import io
6
7
  import mimetypes
8
+ import os
7
9
  import threading
8
10
  import time
11
+ import zipfile
9
12
  from pathlib import Path
10
13
  from typing import Any
11
14
 
@@ -26,6 +29,8 @@ TREE_SKIP_NAMES = {
26
29
 
27
30
  PROTECTED_WORKSPACE_FILE_NAMES = {"AGENTS.md", ".hermes.md"}
28
31
  PROTECTED_PROFILE_CONFIG_FILE_NAMES = {"config.yaml", "profile.yaml"}
32
+ MAX_FILE_ACTION_COUNT = 500
33
+ MAX_ARCHIVE_BYTES = 100 * 1024 * 1024
29
34
 
30
35
  _workspace_state_lock = threading.Lock()
31
36
  _workspace_state_cache: dict[str, Any] = {
@@ -342,3 +347,122 @@ def file_raw(rel: str) -> tuple[bytes, str]:
342
347
  raise ValueError("not found")
343
348
  mime, _ = mimetypes.guess_type(str(fp))
344
349
  return fp.read_bytes(), mime or "application/octet-stream"
350
+
351
+
352
+ def _selected_workspace_paths(paths: list[Any]) -> list[tuple[str, Path]]:
353
+ if not isinstance(paths, list) or not paths:
354
+ raise ValueError("select at least one file")
355
+ if len(paths) > MAX_FILE_ACTION_COUNT:
356
+ raise ValueError(f"select at most {MAX_FILE_ACTION_COUNT} files")
357
+
358
+ selected: list[tuple[str, Path]] = []
359
+ seen: set[str] = set()
360
+ for raw in paths:
361
+ rel = _normalized_workspace_rel(str(raw or "").strip())
362
+ if not rel:
363
+ continue
364
+ reason = workspace_protected_reason(rel)
365
+ if reason:
366
+ raise PermissionError(f"protected file: {reason}")
367
+ path = safe_workspace_path(rel)
368
+ if path is None or not path.exists():
369
+ raise ValueError(f"file not found: {rel}")
370
+ canonical_rel = _workspace_rel(path)
371
+ if canonical_rel in seen:
372
+ continue
373
+ selected.append((canonical_rel, path))
374
+ seen.add(canonical_rel)
375
+
376
+ if not selected:
377
+ raise ValueError("select at least one file")
378
+ return selected
379
+
380
+
381
+ def _selected_workspace_files(paths: list[Any]) -> list[tuple[str, Path]]:
382
+ selected = _selected_workspace_paths(paths)
383
+ for rel, path in selected:
384
+ if not path.is_file():
385
+ raise ValueError(f"not a file: {rel}")
386
+ return selected
387
+
388
+
389
+ def _archive_workspace_files(paths: list[Any]) -> list[tuple[str, Path]]:
390
+ selected = _selected_workspace_paths(paths)
391
+ files: dict[str, Path] = {}
392
+
393
+ def add_file(path: Path) -> None:
394
+ try:
395
+ rel = _workspace_rel(path)
396
+ except ValueError as exc:
397
+ raise ValueError("selected file is outside the workspace") from exc
398
+ if workspace_protected_reason(rel):
399
+ return
400
+ if any(part in TREE_SKIP_NAMES for part in Path(rel).parts):
401
+ return
402
+ files.setdefault(rel, path)
403
+ if len(files) > MAX_FILE_ACTION_COUNT:
404
+ raise ValueError(f"select at most {MAX_FILE_ACTION_COUNT} files")
405
+
406
+ for rel, path in selected:
407
+ if path.is_file():
408
+ add_file(path)
409
+ continue
410
+ if not path.is_dir():
411
+ raise ValueError(f"not a file or folder: {rel}")
412
+ def on_walk_error(exc: OSError) -> None:
413
+ raise ValueError(f"could not read folder: {rel}") from exc
414
+
415
+ for folder, dirnames, filenames in os.walk(path, followlinks=False, onerror=on_walk_error):
416
+ folder_path = Path(folder)
417
+ dirnames[:] = sorted(
418
+ (
419
+ name
420
+ for name in dirnames
421
+ if name not in TREE_SKIP_NAMES
422
+ and not workspace_protected_reason(_workspace_rel(folder_path / name))
423
+ ),
424
+ key=str.lower,
425
+ )
426
+ for filename in sorted(filenames, key=str.lower):
427
+ add_file(folder_path / filename)
428
+
429
+ if not files:
430
+ raise ValueError("selected folders do not contain downloadable files")
431
+ return sorted(files.items(), key=lambda item: item[0].lower())
432
+
433
+
434
+ def files_archive(paths: list[Any]) -> bytes:
435
+ selected = _archive_workspace_files(paths)
436
+ total_bytes = 0
437
+ for _, path in selected:
438
+ try:
439
+ total_bytes += path.stat().st_size
440
+ except OSError as exc:
441
+ raise ValueError("could not read selected file") from exc
442
+ if total_bytes > MAX_ARCHIVE_BYTES:
443
+ raise ValueError("selected files exceed the 100 MB download limit")
444
+
445
+ output = io.BytesIO()
446
+ try:
447
+ with zipfile.ZipFile(output, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
448
+ for rel, path in selected:
449
+ archive.write(path, arcname=rel)
450
+ except OSError as exc:
451
+ raise ValueError("could not archive selected files") from exc
452
+ return output.getvalue()
453
+
454
+
455
+ def files_delete(paths: list[Any]) -> dict[str, Any]:
456
+ # Validate the complete batch before mutating anything so a stale or protected
457
+ # selection cannot produce a partial deletion.
458
+ selected = _selected_workspace_files(paths)
459
+ deleted: list[str] = []
460
+ for rel, path in selected:
461
+ try:
462
+ path.unlink()
463
+ except OSError as exc:
464
+ raise ValueError(f"could not delete file: {rel}") from exc
465
+ deleted.append(rel)
466
+
467
+ bump_workspace_state(selected[0][1])
468
+ return {"ok": True, "deleted": deleted, "count": len(deleted)}
@@ -60,7 +60,14 @@ def _exec_targets_runtime_profile_secrets(cmd: list[str], acting_profile: str =
60
60
  return exec_blocked_for_profile(cmd, acting_profile)
61
61
 
62
62
 
63
- def _stack_apply(target: str, *, workframe_version: str = "", workframe_tarball: str = "") -> dict[str, Any]:
63
+ def _stack_apply(
64
+ target: str,
65
+ *,
66
+ workframe_version: str = "",
67
+ workframe_tarball: str = "",
68
+ host_compose_dir: str = "",
69
+ host_project_root: str = "",
70
+ ) -> dict[str, Any]:
64
71
  target = str(target or "all").strip().lower()
65
72
  if target == "gateway-restart":
66
73
  script = SCRIPTS_DIR / "restart-gateway-hermes.sh"
@@ -94,6 +101,9 @@ def _stack_apply(target: str, *, workframe_version: str = "", workframe_tarball:
94
101
  version = str(workframe_version or "").strip()
95
102
  tarball = str(workframe_tarball or "").strip()
96
103
  env["WORKFRAME_UPDATE_FROM_SUPERVISOR"] = "1"
104
+ if host_compose_dir and host_project_root:
105
+ env["WORKFRAME_HOST_COMPOSE_DIR"] = str(host_compose_dir).strip()
106
+ env["WORKFRAME_HOST_PROJECT_ROOT"] = str(host_project_root).strip()
97
107
  if tarball and target in {"workframe", "all"}:
98
108
  env["WORKFRAME_UPDATE_TARBALL"] = tarball
99
109
  if version:
@@ -113,7 +123,9 @@ def _stack_apply(target: str, *, workframe_version: str = "", workframe_tarball:
113
123
  )
114
124
  logs.append(f"=== {script} (exit {proc.returncode}) ===\n{proc.stdout}\n{proc.stderr}")
115
125
  if proc.returncode != 0:
116
- raise ValueError(f"update_failed:{script.name}")
126
+ detail = (proc.stderr or proc.stdout).strip().splitlines()
127
+ tail = detail[-1].strip() if detail else f"exit_{proc.returncode}"
128
+ raise ValueError(f"update_failed:{script.name}:{tail[-600:]}")
117
129
  return {"ok": True, "target": target, "log": "\n".join(logs)[-12000:]}
118
130
 
119
131
 
@@ -796,6 +808,8 @@ class Handler(BaseHTTPRequestHandler):
796
808
  target = str(body.get("target") or "all").strip().lower()
797
809
  workframe_version = str(body.get("workframe_version") or "").strip()
798
810
  workframe_tarball = str(body.get("workframe_tarball") or "").strip()
811
+ host_compose_dir = str(body.get("host_compose_dir") or "").strip()
812
+ host_project_root = str(body.get("host_project_root") or "").strip()
799
813
  try:
800
814
  return self._json(
801
815
  200,
@@ -803,6 +817,8 @@ class Handler(BaseHTTPRequestHandler):
803
817
  target,
804
818
  workframe_version=workframe_version,
805
819
  workframe_tarball=workframe_tarball,
820
+ host_compose_dir=host_compose_dir,
821
+ host_project_root=host_project_root,
806
822
  ),
807
823
  )
808
824
  except ValueError as exc:
@@ -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-ubrEgQyZ.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-C9bxMexz.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-D6IqqDSH.js";export{e as createArchitectureServices};