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.
- package/README.md +1 -1
- package/bin/create-workframe.js +73 -9
- package/package.json +1 -1
- package/scripts/apply-update-workframe.sh +21 -18
- package/scripts/compose-docker-host.sh +26 -0
- package/workframe-api/action_proxy.py +8 -0
- package/workframe-api/chat_sessions.py +37 -6
- package/workframe-api/chat_stream.py +16 -0
- package/workframe-api/db_schema.py +29 -0
- package/workframe-api/handler_modules/handler_admin.py +32 -0
- package/workframe-api/handler_modules/handler_auth.py +6 -4
- package/workframe-api/handler_modules/handler_chat.py +17 -26
- package/workframe-api/handler_modules/handler_provider.py +13 -14
- package/workframe-api/handler_modules/handler_workspace.py +60 -2
- package/workframe-api/hermes_profiles.py +31 -9
- package/workframe-api/llm_proxy.py +18 -0
- package/workframe-api/mention_invoke.py +29 -24
- package/workframe-api/message_reactions.py +139 -0
- package/workframe-api/model_surface.py +329 -113
- package/workframe-api/package.json +4 -4
- package/workframe-api/profile_gateway.py +14 -6
- package/workframe-api/provider_bindings.py +118 -17
- package/workframe-api/provider_bootstrap.py +74 -38
- package/workframe-api/provider_model_catalog.py +261 -0
- package/workframe-api/rooms.py +4 -3
- package/workframe-api/route_registry.py +4 -0
- package/workframe-api/runtime_cohort.py +118 -23
- package/workframe-api/scripts/run-python.mjs +28 -0
- package/workframe-api/server.py +44 -12
- package/workframe-api/test_agent_identity_authority.py +96 -0
- package/workframe-api/test_chat_attribution_history.py +59 -0
- package/workframe-api/test_enrich_room_messages.py +43 -2
- package/workframe-api/test_llm_proxy_encoding.py +33 -0
- package/workframe-api/test_message_reactions.py +151 -0
- package/workframe-api/test_model_routing_reconcile.py +122 -0
- package/workframe-api/test_model_surface_consistency.py +45 -5
- package/workframe-api/test_oauth_llm.py +1 -1
- package/workframe-api/test_profile_gateway_health.py +37 -0
- package/workframe-api/test_provider_bindings.py +115 -1
- package/workframe-api/test_provider_model_catalog.py +27 -0
- package/workframe-api/test_provider_model_surface.py +2 -2
- package/workframe-api/test_runtime_lease_lifecycle.py +93 -0
- package/workframe-api/test_server_reexports.py +2 -0
- package/workframe-api/test_user_llm_prefs.py +27 -0
- package/workframe-api/test_workspace_file_actions.py +119 -0
- package/workframe-api/turn_credentials.py +41 -0
- package/workframe-api/turn_overlay.py +95 -34
- package/workframe-api/updates.py +14 -5
- package/workframe-api/user_prefs.py +15 -0
- package/workframe-api/workspace_bootstrap.py +1 -0
- package/workframe-api/workspace_files.py +124 -0
- package/workframe-supervisor/server.py +18 -2
- package/workframe-ui/public/assets/{arc-esSBm9C_.js → arc-BkATcoqt.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-yusDFKmK.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DVVz9RGn.js → architectureDiagram-3BPJPVTR-31_yAiUS.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-B6i10GMX.js → blockDiagram-GPEHLZMM-ClM6Si9T.js} +1 -1
- package/workframe-ui/public/assets/{brave-B885JYnH.svg → brave-BoYMbZi1.svg} +0 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BqwJ7JoH.js → c4Diagram-AAUBKEIU-DJzEktF3.js} +1 -1
- package/workframe-ui/public/assets/channel-Nb_UjZ3O.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-CyhcedSa.js → chunk-2J33WTMH-DPFiyb5O.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-1Rr6WuvI.js → chunk-3OPIFGDE-DXNJzuCn.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-D2bRrARV.js → chunk-4BX2VUAB-DDNibGdr.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-CKXeZxSr.js → chunk-55IACEB6-Co3_ulwA.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DYQxxPZ4.js → chunk-5ZQYHXKU-Cmxz8o4d.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-CGpIgqvD.js → chunk-727SXJPM-CDAQGesU.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-B88NGQ7d.js → chunk-AQP2D5EJ-x-swy3kc.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-CCymiI0O.js → chunk-BSJP7CBP-28nU0JOE.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-DRgey03j.js → chunk-CSCIHK7Q-FUURFika.js} +1 -1
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-l1adn8gM.js → chunk-FMBD7UC4-B4RgRO2R.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-CaEvDRPK.js → chunk-KSCS5N6A-CkuppW3T.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-BgCnvPTv.js → chunk-L5ZTLDWV-DyrPSaz3.js} +1 -1
- package/workframe-ui/public/assets/{chunk-LZXEDZCA-t2zokHpn.js → chunk-LZXEDZCA-DFaZSDNV.js} +2 -2
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-C3c9p0sv.js → chunk-ND2GUHAM-DXI0OJxg.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-DSujF-R8.js → chunk-NZK2D7GU-CyOp1ZG2.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O-DJJTbM6p.js → chunk-O5CBEL6O-tZZMCfbx.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-C_TtOXi5.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-BjSJQEIY.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-D2Cxn3AG.js → chunk-XPW4576I-B7_2gS3x.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-JD6iZUGh.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-JD6iZUGh.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A---dQ-pFK.js → cose-bilkent-S5V4N54A-BWJaHw_L.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-B_qe1ODY.js → dagre-BM42HDAG-Bu5MadGV.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-Bou8vEYl.js → diagram-2AECGRRQ-ckLZkDFD.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-DEUEzONU.js → diagram-5GNKFQAL-Cf6Abx1Y.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-Ctq571VA.js → diagram-KO2AKTUF-TLk7_5mJ.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-CnKn89lm.js → diagram-LMA3HP47-ouNxsAb0.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-DnKlrMx2.js → diagram-OG6HWLK6-BMCBQ35D.js} +1 -1
- package/workframe-ui/public/assets/{dist-ubrEgQyZ.js → dist-C9bxMexz.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-PjqO7CJd.js → erDiagram-TEJ5UH35-BN3e6jb1.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-Bqd_sTz0.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-B2PN_nNG.js → flowDiagram-I6XJVG4X-BiinzZrd.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ThyAtNQP.js → ganttDiagram-6RSMTGT7-AYE8oBBG.js} +1 -1
- package/workframe-ui/public/assets/{gemini-Cq-0-rmr.svg → gemini-DqnZlvMo.svg} +0 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-CB2UgoWy.js → gitGraph-WXDBUCRP-mIq_E2vs.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-DRJJ-zT-.js → gitGraphDiagram-PVQCEYII-Cf8IFEcO.js} +1 -1
- package/workframe-ui/public/assets/index-BnfFRGR3.js +133 -0
- package/workframe-ui/public/assets/index-COshPjF3.css +1 -0
- package/workframe-ui/public/assets/{info-J43DQDTF-CLtR33Q_.js → info-J43DQDTF--_3kCaIQ.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-Cmunqfvi.js → infoDiagram-5YYISTIA-BuIOyYMu.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-DWE_84eL.js → ishikawaDiagram-YF4QCWOH-D3bbsXQ8.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-BtBhpyRL.js → journeyDiagram-JHISSGLW-BrZ26C8L.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-B8QspDDL.js → kanban-definition-UN3LZRKU-CJiUyUOr.js} +1 -1
- package/workframe-ui/public/assets/{line-iOgDn4O8.js → line-Bee1snmU.js} +1 -1
- package/workframe-ui/public/assets/{linear-BBehZNVA.js → linear-dfD-zlI7.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-DQK5HcdU.js → mermaid-parser.core-D6IqqDSH.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-Dsrdd1Wr.js → mermaid.core-CuuxYt2r.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-Ds_BfsJA.js → mindmap-definition-RKZ34NQL-T7W2fkwA.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-Bz3pi4i8.js → packet-YPE3B663-CiXMDasj.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-BFFah-6g.js → pie-LRSECV5Y-CUx1_96f.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CVCvLPcC.js → pieDiagram-4H26LBE5-DfR96FUO.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-Cx6rrc_7.js → quadrantDiagram-W4KKPZXB-Dj6mqgQE.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-DJ5vN_cg.js → radar-GUYGQ44K-DKT2q98y.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-Cbry6b5c.js → requirementDiagram-4Y6WPE33-DYDnBVw9.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CICUr5En.js → sankeyDiagram-5OEKKPKP-BSkxpjjO.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BsOuB3F-.js → sequenceDiagram-3UESZ5HK-Dz6SNH9S.js} +1 -1
- package/workframe-ui/public/assets/{src-TS8jZspq.js → src-DC521nE_.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-avBzMQy_.js → stateDiagram-AJRCARHV-D4ZoF5NP.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DknveEiR.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DHLm6UFN.js → timeline-definition-PNZ67QCA-d0ezg1Hu.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-B3Qd5t3j.js → treeView-BLDUP644-kXyaqwIy.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-D1dMQS7x.js → treemap-LRROVOQU-g168p7-O.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-DZ6SoniY.js → vennDiagram-CIIHVFJN-C0_QsmRl.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-9gK17Tp3.js → wardley-L42UT6IY-BNFCdzxQ.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-FCU_wmRE.js → wardleyDiagram-YWT4CUSO-BOwUS9sY.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DWowvseN.js → xychartDiagram-2RQKCTM6-Cxr1ZiKh.js} +1 -1
- package/workframe-ui/public/index.html +2 -2
- package/workframe-ui/public/workframe-build.json +3 -3
- package/workframe-ui/public/assets/architecture-7EHR7CIX-j-DiBdGu.js +0 -1
- package/workframe-ui/public/assets/channel-CJ9pFXbx.js +0 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-Bxk3enxo.js +0 -1
- package/workframe-ui/public/assets/chunk-WU5MYG2G-DdbkX1rg.js +0 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-bwCRiiUl.js +0 -1
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-bwCRiiUl.js +0 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-g-Xo_eS4.js +0 -1
- package/workframe-ui/public/assets/index-1euVfd3b.js +0 -132
- package/workframe-ui/public/assets/index-BLVAsAeO.css +0 -1
- 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
|
-
"""
|
|
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
|
|
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().
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
"""
|
|
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(
|
|
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(
|
|
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):
|
package/workframe-api/updates.py
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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 = ?",
|
|
@@ -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(
|
|
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
|
-
|
|
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-
|
|
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};
|