create-workframe 0.1.12 → 0.1.14

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 (188) hide show
  1. package/README.md +1 -1
  2. package/bin/create-workframe.js +2 -2
  3. package/bin/workframe.js +130 -2
  4. package/docs/security.md +2 -2
  5. package/package.json +1 -1
  6. package/scripts/bundle-workframe-ui.mjs +9 -0
  7. package/scripts/sync-canonical-to-package.mjs +1 -0
  8. package/scripts/verify-public-deploy.sh +65 -3
  9. package/workframe-api/action_proxy.py +23 -17
  10. package/workframe-api/activity_feed.py +798 -0
  11. package/workframe-api/api_meta.py +157 -0
  12. package/workframe-api/auth_gate.py +566 -0
  13. package/workframe-api/avatar_registry.py +343 -0
  14. package/workframe-api/broker_audit.py +164 -0
  15. package/workframe-api/cell_authority.py +231 -0
  16. package/workframe-api/chat_bind.py +319 -0
  17. package/workframe-api/chat_sessions.py +509 -0
  18. package/workframe-api/chat_stream.py +694 -0
  19. package/workframe-api/cleanup_dogfood_smoke.py +252 -0
  20. package/workframe-api/credential_broker.py +162 -0
  21. package/workframe-api/credential_resolve.py +202 -0
  22. package/workframe-api/credential_store.py +245 -0
  23. package/workframe-api/crew_registry.py +250 -0
  24. package/workframe-api/db_schema.py +755 -0
  25. package/workframe-api/docker_gateway.py +166 -0
  26. package/workframe-api/doctor_runtime.py +109 -0
  27. package/workframe-api/domain/__init__.py +47 -0
  28. package/workframe-api/domain/entities.py +252 -0
  29. package/workframe-api/domain/schema/workframe-domain.schema.json +278 -0
  30. package/workframe-api/egress_policy.py +42 -0
  31. package/workframe-api/handler_modules/__init__.py +17 -0
  32. package/workframe-api/handler_modules/handler_admin.py +542 -0
  33. package/workframe-api/handler_modules/handler_auth.py +512 -0
  34. package/workframe-api/handler_modules/handler_chat.py +641 -0
  35. package/workframe-api/handler_modules/handler_install.py +178 -0
  36. package/workframe-api/handler_modules/handler_provider.py +325 -0
  37. package/workframe-api/handler_modules/handler_workspace.py +1420 -0
  38. package/workframe-api/health_monitor.py +80 -0
  39. package/workframe-api/hermes_admin.py +756 -0
  40. package/workframe-api/hermes_profiles.py +1328 -0
  41. package/workframe-api/install_api.py +123 -0
  42. package/workframe-api/kanban_cron.py +473 -0
  43. package/workframe-api/lane_bindings.py +423 -0
  44. package/workframe-api/llm_proxy.py +17 -43
  45. package/workframe-api/mention_helpers.py +180 -0
  46. package/workframe-api/mention_invoke.py +431 -0
  47. package/workframe-api/model_surface.py +1139 -0
  48. package/workframe-api/oauth_pending.py +85 -0
  49. package/workframe-api/oauth_redirect.py +381 -0
  50. package/workframe-api/package.json +2 -2
  51. package/workframe-api/profile_api_lifecycle.py +66 -0
  52. package/workframe-api/profile_gateway.py +436 -0
  53. package/workframe-api/provider_bindings.py +673 -0
  54. package/workframe-api/provider_bootstrap.py +347 -0
  55. package/workframe-api/provider_catalog.py +180 -0
  56. package/workframe-api/rooms.py +1613 -0
  57. package/workframe-api/route_registry.py +331 -191
  58. package/workframe-api/run-typecheck.mjs +2 -1
  59. package/workframe-api/run_authority.py +287 -0
  60. package/workframe-api/run_ledger.py +470 -0
  61. package/workframe-api/run_surface_wiring.py +344 -0
  62. package/workframe-api/runtime_cohort.py +752 -0
  63. package/workframe-api/runtime_tokens.py +127 -0
  64. package/workframe-api/server.py +1453 -19513
  65. package/workframe-api/snapshot_feed.py +49 -0
  66. package/workframe-api/stack_config.py +33 -1
  67. package/workframe-api/supervisor_client.py +154 -0
  68. package/workframe-api/test_api_meta_build_stamp.py +34 -0
  69. package/workframe-api/test_cell_authority.py +79 -0
  70. package/workframe-api/test_chat_bind.py +48 -0
  71. package/workframe-api/test_credential_lease_matrix.py +171 -0
  72. package/workframe-api/test_credential_resolve.py +51 -0
  73. package/workframe-api/test_credential_store.py +27 -0
  74. package/workframe-api/test_dogfood_flows.py +346 -0
  75. package/workframe-api/test_domain_entities.py +152 -0
  76. package/workframe-api/test_exception_hygiene.py +12 -0
  77. package/workframe-api/test_hermes_admin.py +72 -0
  78. package/workframe-api/test_install_wizard_resume.py +78 -0
  79. package/workframe-api/test_local_bootstrap.py +67 -0
  80. package/workframe-api/test_mention_helpers.py +35 -0
  81. package/workframe-api/test_mention_invoke.py +26 -0
  82. package/workframe-api/test_model_surface_consistency.py +1 -0
  83. package/workframe-api/test_oauth_pending.py +41 -0
  84. package/workframe-api/test_provider_bindings.py +52 -0
  85. package/workframe-api/test_provider_catalog.py +28 -0
  86. package/workframe-api/test_route_registry.py +171 -35
  87. package/workframe-api/test_run_authority.py +112 -0
  88. package/workframe-api/test_run_ledger.py +157 -0
  89. package/workframe-api/test_run_surface_wiring.py +130 -0
  90. package/workframe-api/test_runtime_cohort.py +85 -0
  91. package/workframe-api/test_secure_mode_docker_boundary.py +39 -0
  92. package/workframe-api/test_server_reexports.py +99 -0
  93. package/workframe-api/test_stack_config_install_smtp.py +7 -0
  94. package/workframe-api/turn_credentials.py +28 -13
  95. package/workframe-api/turn_overlay.py +715 -0
  96. package/workframe-api/updates.py +16 -1
  97. package/workframe-api/user_prefs.py +387 -0
  98. package/workframe-api/wf032_extract_remaining_handlers.py +417 -0
  99. package/workframe-api/workspace_bootstrap.py +536 -0
  100. package/workframe-api/workspace_files.py +344 -0
  101. package/workframe-api/workspace_messaging.py +206 -0
  102. package/workframe-api/zk_auth.py +3 -2
  103. package/workframe-supervisor/server.py +10 -1
  104. package/workframe-supervisor/test_supervisor_negative.py +75 -0
  105. package/workframe-ui/public/assets/{arc-B0OFRGmJ.js → arc-BT9iZUSd.js} +1 -1
  106. package/workframe-ui/public/assets/architecture-7EHR7CIX-m1aPUQ_r.js +1 -0
  107. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-CaEDd3np.js} +1 -1
  108. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-DHJhMrIS.js} +1 -1
  109. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-Cdyz9hIV.js} +1 -1
  110. package/workframe-ui/public/assets/channel-3M69ekE5.js +1 -0
  111. package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-BbZx76LX.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DcSxjTIP.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-B7F-kTY1.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-BwwEiRTK.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-DAi_NksX.js} +1 -1
  116. package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-BIaF4XCN.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-DjYSk1dG.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-_twnyfgV.js} +1 -1
  119. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CkNmdy5s.js} +2 -2
  120. package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CjLqd-gl.js} +1 -1
  121. package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-_6oNqFjj.js} +1 -1
  122. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-CeO9Am_D.js} +1 -1
  123. package/workframe-ui/public/assets/chunk-LZXEDZCA-sRM4YeIe.js +2 -0
  124. package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-CH8tMb21.js} +1 -1
  125. package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-DAI1bsII.js} +1 -1
  126. package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-B2e-5a2G.js} +1 -1
  127. package/workframe-ui/public/assets/chunk-QZHKN3VN-BMl9ed8P.js +1 -0
  128. package/workframe-ui/public/assets/chunk-WU5MYG2G-C08HH6BU.js +1 -0
  129. package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-DM2-0q37.js} +1 -1
  130. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BFcOE4Aq.js +1 -0
  131. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BFcOE4Aq.js +1 -0
  132. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-BnbbewLv.js} +1 -1
  133. package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-C4zmB1Vt.js} +1 -1
  134. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-n-BJmsiC.js} +1 -1
  135. package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-BzQGQnZi.js} +1 -1
  136. package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-Cx_De-wS.js} +1 -1
  137. package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-CIOI3Lok.js} +1 -1
  138. package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-DkRuPLWY.js} +1 -1
  139. package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-XhCT1Q-V.js} +1 -1
  140. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-C3GwhDU1.js} +1 -1
  141. package/workframe-ui/public/assets/eventmodeling-FCH6USID-DIVBL6CF.js +1 -0
  142. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-CLWH8z2e.js} +1 -1
  143. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D4h66Vy3.js} +1 -1
  144. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-IW5F-Q6c.js} +1 -1
  145. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-BjNIbmRM.js} +1 -1
  146. package/workframe-ui/public/assets/index-DL2vJP4L.css +1 -0
  147. package/workframe-ui/public/assets/index-ElS_D59P.js +130 -0
  148. package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CcJspM79.js} +1 -1
  149. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-DP4xR9jt.js} +1 -1
  150. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-D4cZb21V.js} +1 -1
  151. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-DsLuHkIJ.js} +1 -1
  152. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Wu65nOsR.js} +1 -1
  153. package/workframe-ui/public/assets/{line-CgQsmU35.js → line-ByBDzRpy.js} +1 -1
  154. package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-DzXi2vnq.js} +1 -1
  155. package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-CaB-1Ckn.js} +2 -2
  156. package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-D7HTHC17.js} +3 -3
  157. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BfKsC9aj.js} +1 -1
  158. package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-ClpU98TO.js} +1 -1
  159. package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-KeOyY_-u.js} +1 -1
  160. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-CUpjnzbs.js} +1 -1
  161. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-CJdPTszF.js} +1 -1
  162. package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-C9BpandZ.js} +1 -1
  163. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-CHDZPwry.js} +1 -1
  164. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-CMJJh91s.js} +1 -1
  165. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-BsV4GppP.js} +1 -1
  166. package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-WHks82Up.js} +1 -1
  167. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-eSX9P8z0.js} +1 -1
  168. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-FzMK4PkM.js +1 -0
  169. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Dg1UkhGo.js} +1 -1
  170. package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-B7yGsBuj.js} +1 -1
  171. package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-j_G2oyQ_.js} +1 -1
  172. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-BRrT0jqC.js} +1 -1
  173. package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-DEdUqez2.js} +1 -1
  174. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-C-bBK9Bb.js} +1 -1
  175. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-DhH5xuR2.js} +1 -1
  176. package/workframe-ui/public/index.html +11 -7
  177. package/workframe-ui/public/workframe-build.json +5 -0
  178. package/workframe-ui/public/assets/architecture-7EHR7CIX-Dwwi9yA0.js +0 -1
  179. package/workframe-ui/public/assets/channel-fNHbDpV4.js +0 -1
  180. package/workframe-ui/public/assets/chunk-LZXEDZCA-DMfdMUwz.js +0 -2
  181. package/workframe-ui/public/assets/chunk-QZHKN3VN-ewTgEQK8.js +0 -1
  182. package/workframe-ui/public/assets/chunk-WU5MYG2G-D4Tg-A0l.js +0 -1
  183. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CFAgBpEl.js +0 -1
  184. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CFAgBpEl.js +0 -1
  185. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BIJTP5k-.js +0 -1
  186. package/workframe-ui/public/assets/index-8CuZDEIG.css +0 -1
  187. package/workframe-ui/public/assets/index-xS9lFekI.js +0 -129
  188. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CMLuNyeC.js +0 -1
@@ -0,0 +1,344 @@
1
+ """WF-032 extract: workspace file tree, read/write, revision state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import mimetypes
7
+ import threading
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ TREE_SKIP_NAMES = {
13
+ ".git",
14
+ ".next",
15
+ ".turbo",
16
+ ".venv",
17
+ ".workframe-cache",
18
+ "__pycache__",
19
+ "node_modules",
20
+ "dist",
21
+ "build",
22
+ "coverage",
23
+ "users", # ponytail: legacy scaffold dir — hide if present
24
+ "content", # ponytail: legacy nested tree — hide if present
25
+ }
26
+
27
+ PROTECTED_WORKSPACE_FILE_NAMES = {"AGENTS.md", ".hermes.md"}
28
+ PROTECTED_PROFILE_CONFIG_FILE_NAMES = {"config.yaml", "profile.yaml"}
29
+
30
+ _workspace_state_lock = threading.Lock()
31
+ _workspace_state_cache: dict[str, Any] = {
32
+ "ok": True,
33
+ "revision": "0:0:",
34
+ "files": 0,
35
+ "updated_at": "",
36
+ "latest_path": "",
37
+ "generation": 0,
38
+ }
39
+ _workspace_tree_lock = threading.Lock()
40
+ _workspace_tree_cache: dict[str, Any] = {
41
+ "revision": "",
42
+ "root": None,
43
+ }
44
+
45
+
46
+ def _srv():
47
+ import server as srv
48
+
49
+ return srv
50
+
51
+
52
+ def _normalized_workspace_rel(rel: str) -> str:
53
+ return (rel or "").replace("\\", "/").lstrip("/")
54
+
55
+
56
+ def _workspace_root() -> Path:
57
+ return _srv().WORKSPACE.resolve()
58
+
59
+
60
+ def safe_workspace_path(rel: str) -> Path | None:
61
+ rel = rel.replace("\\", "/").lstrip("/")
62
+ if not rel or ".." in rel.split("/"):
63
+ return None
64
+ root = _workspace_root()
65
+ path = (root / rel).resolve()
66
+ try:
67
+ path.relative_to(root)
68
+ except ValueError:
69
+ return None
70
+ return path
71
+
72
+
73
+ def _workspace_rel(path: Path) -> str:
74
+ return path.resolve().relative_to(_workspace_root()).as_posix()
75
+
76
+
77
+ def _is_env_like_name(name: str) -> bool:
78
+ lower = name.lower()
79
+ return (
80
+ lower == ".env"
81
+ or lower.startswith(".env.")
82
+ or lower.endswith(".env")
83
+ or lower == "environment"
84
+ )
85
+
86
+
87
+ def workspace_protected_reason(rel: str) -> str | None:
88
+ """Return a stable reason when a workspace-relative path is protected."""
89
+ safe = safe_workspace_path(_normalized_workspace_rel(rel))
90
+ workspace = _srv().WORKSPACE.resolve()
91
+ if safe is None:
92
+ return "invalid_path"
93
+ try:
94
+ rel_path = safe.relative_to(workspace)
95
+ except ValueError:
96
+ return "invalid_path"
97
+
98
+ parts = rel_path.parts
99
+ if not parts:
100
+ return None
101
+
102
+ for part in parts:
103
+ if part in PROTECTED_WORKSPACE_FILE_NAMES:
104
+ return "protected_workspace_file"
105
+ if _is_env_like_name(part):
106
+ return "protected_env_file"
107
+
108
+ if len(parts) >= 3 and parts[0] == "profiles" and parts[2] in PROTECTED_PROFILE_CONFIG_FILE_NAMES:
109
+ return "protected_profile_config"
110
+ if len(parts) >= 2 and parts[0] == "profiles" and parts[-1] in PROTECTED_PROFILE_CONFIG_FILE_NAMES:
111
+ return "protected_profile_config"
112
+ return None
113
+
114
+
115
+ def safe_content_path(rel: str) -> Path | None:
116
+ return safe_workspace_path(rel)
117
+
118
+
119
+ def _list_folder_nodes(folder: Path) -> list[dict[str, Any]]:
120
+ out: list[dict[str, Any]] = []
121
+ try:
122
+ entries = sorted(folder.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
123
+ except OSError:
124
+ return out
125
+ for p in entries:
126
+ if p.name in TREE_SKIP_NAMES:
127
+ continue
128
+ rel = _workspace_rel(p)
129
+ if workspace_protected_reason(rel):
130
+ continue
131
+ node: dict[str, Any] = {
132
+ "id": rel,
133
+ "name": p.name,
134
+ "type": "folder" if p.is_dir() else "file",
135
+ }
136
+ if p.is_dir():
137
+ node["children"] = []
138
+ node["children_loaded"] = False
139
+ out.append(node)
140
+ return out
141
+
142
+
143
+ def _files_tree_root_name() -> str:
144
+ """Navigator root label follows workspace branding, not WORKFRAME_PROJECT env."""
145
+ try:
146
+ conn = _srv()._workframe_db()
147
+ branding = _srv()._primary_workspace_branding(conn)
148
+ conn.close()
149
+ if branding:
150
+ name = str(branding.get("display_name") or "").strip()
151
+ if name:
152
+ return name
153
+ except Exception: # noqa: BLE001
154
+ pass
155
+ return _srv().PROJECT_NAME
156
+
157
+
158
+ def files_tree() -> dict[str, Any]:
159
+ workspace = _srv().WORKSPACE
160
+ current_revision = str(workspace_state().get("revision") or "")
161
+ root_name = _files_tree_root_name()
162
+ with _workspace_tree_lock:
163
+ cached_revision = str(_workspace_tree_cache.get("revision") or "")
164
+ cached_root = _workspace_tree_cache.get("root")
165
+ if cached_root and cached_revision == current_revision:
166
+ if str(cached_root.get("name") or "") == root_name:
167
+ return cached_root
168
+ refreshed = dict(cached_root)
169
+ refreshed["name"] = root_name
170
+ _workspace_tree_cache["root"] = refreshed
171
+ return refreshed
172
+
173
+ root = {
174
+ "id": "root",
175
+ "name": root_name,
176
+ "type": "folder",
177
+ "children": [],
178
+ }
179
+ if not workspace.is_dir():
180
+ return root
181
+ root["children"] = _list_folder_nodes(workspace)
182
+ with _workspace_tree_lock:
183
+ _workspace_tree_cache["revision"] = current_revision
184
+ _workspace_tree_cache["root"] = root
185
+ return root
186
+
187
+
188
+ def files_list(rel: str = "") -> dict[str, Any]:
189
+ workspace = _srv().WORKSPACE
190
+ folder = workspace if not rel.strip() else safe_workspace_path(rel)
191
+ if folder is None or not folder.exists() or not folder.is_dir():
192
+ raise ValueError("folder not found")
193
+ reason = workspace_protected_reason(rel)
194
+ if reason:
195
+ raise PermissionError(f"protected file: {reason}")
196
+ folder_rel = "" if folder == workspace else _workspace_rel(folder)
197
+ return {
198
+ "ok": True,
199
+ "path": folder_rel,
200
+ "children": _list_folder_nodes(folder),
201
+ }
202
+
203
+
204
+ def _scan_workspace_state() -> dict[str, Any]:
205
+ workspace = _srv().WORKSPACE
206
+ files = 0
207
+ latest_mtime = 0.0
208
+ latest_rel = ""
209
+ if workspace.is_dir():
210
+ try:
211
+ for path in workspace.rglob("*"):
212
+ if not path.is_file():
213
+ continue
214
+ if path.name in TREE_SKIP_NAMES:
215
+ continue
216
+ try:
217
+ rel = _workspace_rel(path)
218
+ except ValueError:
219
+ continue
220
+ if workspace_protected_reason(rel):
221
+ continue
222
+ files += 1
223
+ try:
224
+ mtime = float(path.stat().st_mtime)
225
+ except OSError:
226
+ continue
227
+ if mtime >= latest_mtime:
228
+ latest_mtime = mtime
229
+ latest_rel = rel
230
+ except OSError:
231
+ pass
232
+ revision = f"{int(latest_mtime)}:{files}:{latest_rel}"
233
+ return {
234
+ "ok": True,
235
+ "revision": revision,
236
+ "files": files,
237
+ "updated_at": _srv()._iso_from_unix(latest_mtime),
238
+ "latest_path": latest_rel,
239
+ }
240
+
241
+
242
+ def _refresh_workspace_state() -> dict[str, Any]:
243
+ state = _scan_workspace_state()
244
+ with _workspace_state_lock:
245
+ generation = int(_workspace_state_cache.get("generation") or 0)
246
+ _workspace_state_cache.update(state)
247
+ _workspace_state_cache["generation"] = generation
248
+ if generation:
249
+ _workspace_state_cache["revision"] = f"{state['revision']}:{generation}"
250
+ return dict(_workspace_state_cache)
251
+
252
+
253
+ def workspace_state() -> dict[str, Any]:
254
+ with _workspace_state_lock:
255
+ return dict(_workspace_state_cache)
256
+
257
+
258
+ def bump_workspace_state(path: Path | None = None) -> None:
259
+ now = time.time()
260
+ rel = ""
261
+ if path is not None:
262
+ try:
263
+ rel = _workspace_rel(path)
264
+ except Exception: # noqa: BLE001
265
+ rel = ""
266
+ with _workspace_state_lock:
267
+ current_files = int(_workspace_state_cache.get("files") or 0)
268
+ generation = int(_workspace_state_cache.get("generation") or 0) + 1
269
+ _workspace_state_cache.update(
270
+ {
271
+ "ok": True,
272
+ "revision": f"{int(now)}:{current_files}:{rel}:{generation}",
273
+ "updated_at": _srv()._iso_from_unix(now),
274
+ "latest_path": rel,
275
+ "generation": generation,
276
+ }
277
+ )
278
+ with _workspace_tree_lock:
279
+ _workspace_tree_cache["revision"] = ""
280
+ _workspace_tree_cache["root"] = None
281
+
282
+
283
+ def workspace_state_daemon() -> None:
284
+ while True:
285
+ try:
286
+ _refresh_workspace_state()
287
+ except Exception: # noqa: BLE001
288
+ pass
289
+ time.sleep(2)
290
+
291
+
292
+ def file_read(rel: str) -> dict[str, Any]:
293
+ reason = workspace_protected_reason(rel)
294
+ if reason:
295
+ raise PermissionError(f"protected file: {reason}")
296
+ fp = safe_workspace_path(rel)
297
+ if not fp or not fp.exists() or not fp.is_file():
298
+ raise ValueError("not found")
299
+ try:
300
+ text = fp.read_text(encoding="utf-8")
301
+ return {"path": _workspace_rel(fp), "content": text}
302
+ except UnicodeDecodeError:
303
+ return {"path": _workspace_rel(fp), "content": ""}
304
+
305
+
306
+ def file_write(rel: str, content: str) -> dict[str, Any]:
307
+ reason = workspace_protected_reason(rel)
308
+ if reason:
309
+ raise PermissionError(f"protected file: {reason}")
310
+ fp = safe_workspace_path(rel)
311
+ if not fp:
312
+ raise ValueError("invalid path")
313
+ fp.parent.mkdir(parents=True, exist_ok=True)
314
+ fp.write_text(content, encoding="utf-8")
315
+ bump_workspace_state(fp)
316
+ return {"ok": True, "path": _workspace_rel(fp)}
317
+
318
+
319
+ def file_upload_binary(rel: str, content_b64: str) -> dict[str, Any]:
320
+ reason = workspace_protected_reason(rel)
321
+ if reason:
322
+ raise PermissionError(f"protected file: {reason}")
323
+ fp = safe_workspace_path(rel)
324
+ if not fp:
325
+ raise ValueError("invalid path")
326
+ try:
327
+ raw = base64.b64decode(content_b64, validate=True)
328
+ except ValueError as exc:
329
+ raise ValueError("invalid base64") from exc
330
+ fp.parent.mkdir(parents=True, exist_ok=True)
331
+ fp.write_bytes(raw)
332
+ bump_workspace_state(fp)
333
+ return {"ok": True, "path": _workspace_rel(fp), "size": len(raw)}
334
+
335
+
336
+ def file_raw(rel: str) -> tuple[bytes, str]:
337
+ reason = workspace_protected_reason(rel)
338
+ if reason:
339
+ raise PermissionError(f"protected file: {reason}")
340
+ fp = safe_workspace_path(rel)
341
+ if not fp or not fp.is_file():
342
+ raise ValueError("not found")
343
+ mime, _ = mimetypes.guess_type(str(fp))
344
+ return fp.read_bytes(), mime or "application/octet-stream"
@@ -0,0 +1,206 @@
1
+ """WF-032 extract: workspace messaging settings and gateway env sync."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ import sqlite3
8
+ from typing import Any
9
+
10
+ # Hermes gateway reads these from the primary profile .env; vault is source of truth.
11
+ _MESSAGING_GATEWAY_ENV: dict[str, dict[str, str]] = {
12
+ "discord": {
13
+ "token": "DISCORD_BOT_TOKEN",
14
+ "home_channel": "DISCORD_HOME_CHANNEL",
15
+ "allowed_users": "DISCORD_ALLOWED_USERS",
16
+ },
17
+ "telegram": {
18
+ "token": "TELEGRAM_BOT_TOKEN",
19
+ "home_channel": "TELEGRAM_HOME_CHANNEL",
20
+ "allowed_users": "TELEGRAM_ALLOWED_USERS",
21
+ },
22
+ }
23
+
24
+
25
+ def _srv():
26
+ import server as srv
27
+
28
+ return srv
29
+
30
+
31
+ def _parse_messaging_settings_patch(body: dict[str, Any], settings: dict[str, Any]) -> dict[str, Any]:
32
+ """Merge admin messaging channel/allowlist config into workspace settings."""
33
+ raw = body.get("messaging")
34
+ if not isinstance(raw, dict):
35
+ return settings
36
+ messaging = settings.get("messaging") if isinstance(settings.get("messaging"), dict) else {}
37
+ merged = dict(messaging)
38
+ for provider in ("discord", "telegram"):
39
+ block = raw.get(provider)
40
+ if not isinstance(block, dict):
41
+ continue
42
+ current = merged.get(provider) if isinstance(merged.get(provider), dict) else {}
43
+ row = dict(current)
44
+ if "home_channel" in block:
45
+ row["home_channel"] = str(block.get("home_channel") or "").strip()
46
+ if "allowed_users" in block:
47
+ row["allowed_users"] = str(block.get("allowed_users") or "").strip()
48
+ merged[provider] = row
49
+ settings["messaging"] = merged
50
+ return settings
51
+
52
+
53
+ def _workspace_member_platform_ids(workspace_id: str) -> dict[str, set[str]]:
54
+ """Collect linked Discord/Telegram user IDs for workspace members."""
55
+ workspace_id = str(workspace_id or "").strip()
56
+ out: dict[str, set[str]] = {"discord": set(), "telegram": set(), "slack": set()}
57
+ if not workspace_id:
58
+ return out
59
+ conn = _srv()._workframe_db()
60
+ try:
61
+ rows = conn.execute(
62
+ """
63
+ SELECT u.platform_ids
64
+ FROM users u
65
+ JOIN workspace_memberships wm ON wm.user_id = u.id
66
+ WHERE wm.workspace_id = ?
67
+ AND wm.deleted_at IS NULL
68
+ AND u.deleted_at IS NULL
69
+ AND wm.status = 'active'
70
+ """,
71
+ (workspace_id,),
72
+ ).fetchall()
73
+ finally:
74
+ conn.close()
75
+ for row in rows:
76
+ raw = row[0] if row else "{}"
77
+ try:
78
+ parsed = json.loads(str(raw or "{}"))
79
+ except (TypeError, json.JSONDecodeError):
80
+ parsed = {}
81
+ if not isinstance(parsed, dict):
82
+ continue
83
+ for platform in out:
84
+ val = str(parsed.get(platform) or "").strip()
85
+ if val:
86
+ out[platform].add(val)
87
+ return out
88
+
89
+
90
+ def _merged_messaging_allowed_users(workspace_id: str, provider: str, seed: str) -> str:
91
+ """Admin seed allowlist + member-linked platform IDs for gateway env."""
92
+ provider = str(provider or "").strip().lower()
93
+ ids: list[str] = []
94
+ seen: set[str] = set()
95
+ for part in re.split(r"[\s,;]+", str(seed or "").strip()):
96
+ token = part.strip()
97
+ if token and token not in seen:
98
+ seen.add(token)
99
+ ids.append(token)
100
+ for member_id in sorted(_workspace_member_platform_ids(workspace_id).get(provider, set())):
101
+ if member_id not in seen:
102
+ seen.add(member_id)
103
+ ids.append(member_id)
104
+ return ",".join(ids)
105
+
106
+
107
+ def _workspace_messaging_integrations_payload(workspace_id: str, settings: dict[str, Any]) -> dict[str, Any]:
108
+ messaging = settings.get("messaging") if isinstance(settings.get("messaging"), dict) else {}
109
+ payload: dict[str, Any] = {}
110
+ for provider, env_map in _MESSAGING_GATEWAY_ENV.items():
111
+ block = messaging.get(provider) if isinstance(messaging.get(provider), dict) else {}
112
+ resolved = _srv()._resolve_credential("", workspace_id, provider)
113
+ has_token = bool(resolved and _srv()._credential_secret(resolved, ""))
114
+ payload[provider] = {
115
+ "bot_token_configured": has_token,
116
+ "home_channel": str(block.get("home_channel") or ""),
117
+ "allowed_users": str(block.get("allowed_users") or ""),
118
+ }
119
+ return payload
120
+
121
+
122
+ def _set_primary_messaging_platforms(enabled: dict[str, bool]) -> tuple[bool, str]:
123
+ primary = _srv()._primary_profile()
124
+ if not primary:
125
+ return False, "no_primary_profile"
126
+ flags = {name: bool(enabled.get(name)) for name in ("discord", "telegram")}
127
+ cfg_path = _srv()._profile_gateway_config_path(primary)
128
+ if cfg_path is None:
129
+ return False, "no_primary_profile"
130
+ try:
131
+ import yaml
132
+
133
+ cfg: dict[str, Any] = {}
134
+ if cfg_path.is_file():
135
+ loaded = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
136
+ cfg = loaded if isinstance(loaded, dict) else {}
137
+ plats = cfg.setdefault("platforms", {})
138
+ if not isinstance(plats, dict):
139
+ plats = {}
140
+ cfg["platforms"] = plats
141
+ for name, on in flags.items():
142
+ row = plats.get(name) if isinstance(plats.get(name), dict) else {}
143
+ row["enabled"] = bool(on)
144
+ plats[name] = row
145
+ cfg_path.write_text(yaml.safe_dump(cfg, sort_keys=False), encoding="utf-8")
146
+ return True, "ok"
147
+ except (OSError, ImportError) as exc:
148
+ return False, str(exc)
149
+
150
+
151
+ def _sync_workspace_messaging_gateway(workspace_id: str) -> dict[str, Any]:
152
+ """Vault → primary profile .env overlay + platform toggles + gateway restart."""
153
+ workspace_id = str(workspace_id or "").strip()
154
+ if not workspace_id:
155
+ return {"ok": False, "error": "workspace_id_required"}
156
+ primary = _srv()._primary_profile()
157
+ if not primary:
158
+ return {"ok": False, "error": "no_primary_profile"}
159
+ try:
160
+ conn = _srv()._workframe_db()
161
+ row = conn.execute(
162
+ "SELECT settings_json FROM workspaces WHERE id = ? AND deleted_at IS NULL",
163
+ (workspace_id,),
164
+ ).fetchone()
165
+ conn.close()
166
+ except sqlite3.Error as exc:
167
+ return {"ok": False, "error": f"db_error: {exc}"}
168
+ settings = _srv()._parse_workspace_settings(row) if row else {}
169
+ messaging = settings.get("messaging") if isinstance(settings.get("messaging"), dict) else {}
170
+ env_path = _srv()._profile_dir(primary) / ".env"
171
+ enabled: dict[str, bool] = {}
172
+ for provider, env_map in _MESSAGING_GATEWAY_ENV.items():
173
+ token_var = env_map["token"]
174
+ resolved = _srv()._resolve_credential("", workspace_id, provider)
175
+ secret = _srv()._credential_secret(resolved, "") if resolved else ""
176
+ block = messaging.get(provider) if isinstance(messaging.get(provider), dict) else {}
177
+ if secret:
178
+ _srv()._upsert_env_secret(env_path, token_var, secret)
179
+ enabled[provider] = True
180
+ home = str(block.get("home_channel") or "").strip()
181
+ if home:
182
+ _srv()._upsert_env_secret(env_path, env_map["home_channel"], home)
183
+ else:
184
+ _srv()._remove_env_secret(env_path, env_map["home_channel"])
185
+ allowed = _merged_messaging_allowed_users(
186
+ workspace_id,
187
+ provider,
188
+ str(block.get("allowed_users") or ""),
189
+ )
190
+ if allowed:
191
+ _srv()._upsert_env_secret(env_path, env_map["allowed_users"], allowed)
192
+ else:
193
+ _srv()._remove_env_secret(env_path, env_map["allowed_users"])
194
+ else:
195
+ enabled[provider] = False
196
+ for key in env_map.values():
197
+ _srv()._remove_env_secret(env_path, key)
198
+ ok, out = _set_primary_messaging_platforms(enabled)
199
+ if not ok:
200
+ return {"ok": False, "error": f"messaging_platform_config_failed: {out}"}
201
+ try:
202
+ restart = _srv()._restart_stack_gateway()
203
+ except (ValueError, OSError, RuntimeError) as exc:
204
+ _srv()._log_handler_error("_sync_workspace_messaging_gateway restart", exc)
205
+ return {"ok": False, "error": str(exc)}
206
+ return {"ok": True, "gateway": restart}
@@ -165,8 +165,9 @@ def _zk_db() -> sqlite3.Connection:
165
165
  os.makedirs(os.path.dirname(db_path), exist_ok=True)
166
166
  conn = sqlite3.connect(db_path, timeout=10.0)
167
167
  conn.row_factory = sqlite3.Row
168
- conn.execute("PRAGMA journal_mode=WAL")
169
- conn.execute("PRAGMA busy_timeout=5000")
168
+ # ponytail: DELETE only — WAL sidecars fail on Docker Desktop Windows bind mounts.
169
+ conn.execute("PRAGMA journal_mode=DELETE")
170
+ conn.execute("PRAGMA busy_timeout=30000")
170
171
  _zk_init_db(conn)
171
172
  return conn
172
173
 
@@ -272,10 +272,19 @@ def load_routes() -> dict[str, Any]:
272
272
  if p.is_dir():
273
273
  raw_routes.append({"profile": p.name})
274
274
  routes = []
275
+ seen: set[str] = set()
275
276
  for row in raw_routes:
276
277
  slug = str(row.get("profile") or "").strip()
277
- if slug and profile_exists(slug):
278
+ if slug and profile_exists(slug) and slug not in seen:
278
279
  routes.append({"profile": slug})
280
+ seen.add(slug)
281
+ if default_profile and default_profile not in seen and profile_exists(default_profile):
282
+ routes.insert(0, {"profile": default_profile})
283
+ seen.add(default_profile)
284
+ native = safe_profile_slug(NATIVE_PROFILE) if NATIVE_PROFILE else ""
285
+ if native and native not in seen and profile_exists(native):
286
+ routes.insert(0, {"profile": native})
287
+ seen.add(native)
279
288
  return {"default_profile": default_profile, "routes": routes}
280
289
 
281
290
 
@@ -0,0 +1,75 @@
1
+ """WF-011 supervisor negative tests — docker boundary, profile/path misuse, auth.
2
+
3
+ Run: python services/workframe-supervisor/test_supervisor_negative.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
13
+
14
+ import server as supervisor
15
+ from profile_secret_policy import exec_blocked_for_profile, is_secret_read_attempt
16
+
17
+
18
+ def test_secret_read_blocked() -> None:
19
+ cmd = ["sh", "-lc", "cat /opt/data/profiles/u-alice-dev/.env"]
20
+ assert is_secret_read_attempt(cmd)
21
+ assert exec_blocked_for_profile(cmd, "u-alice-dev")
22
+
23
+
24
+ def test_foreign_profile_secrets_blocked() -> None:
25
+ cmd = ["cat", "/opt/data/profiles/u-bob-dev/.env"]
26
+ assert exec_blocked_for_profile(cmd, "u-alice-dev")
27
+
28
+
29
+ def test_invalid_profile_slug_rejected() -> None:
30
+ for bad in ("", "../etc", "UPPER", "a" * 70):
31
+ try:
32
+ supervisor.safe_profile_slug(bad)
33
+ raise AssertionError(f"expected ValueError for {bad!r}")
34
+ except ValueError:
35
+ pass
36
+
37
+
38
+ def test_supervisor_auth_required() -> None:
39
+ supervisor.SUPERVISOR_TOKEN = "neg-test-token"
40
+
41
+ class _Handler:
42
+ headers: dict[str, str] = {}
43
+
44
+ assert not supervisor._auth_ok(_Handler()) # type: ignore[arg-type]
45
+ _Handler.headers = {"Authorization": "Bearer neg-test-token"}
46
+ assert supervisor._auth_ok(_Handler()) # type: ignore[arg-type]
47
+
48
+
49
+ def test_raw_container_exec_disabled_by_default() -> None:
50
+ os.environ.pop("WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC", None)
51
+ assert os.environ.get("WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC", "0") != "1"
52
+
53
+
54
+ def test_api_compose_public_has_no_docker_sock() -> None:
55
+ repo = Path(__file__).resolve().parents[2]
56
+ public = (repo / "infra" / "compose" / "workframe" / "docker-compose.public.yml").read_text(
57
+ encoding="utf-8"
58
+ )
59
+ api_block = public.split("workframe-api:")[1].split("workframe-supervisor:")[0]
60
+ assert "/var/run/docker.sock" not in api_block
61
+ assert "WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC=0" in public
62
+
63
+
64
+ def main() -> None:
65
+ test_secret_read_blocked()
66
+ test_foreign_profile_secrets_blocked()
67
+ test_invalid_profile_slug_rejected()
68
+ test_supervisor_auth_required()
69
+ test_raw_container_exec_disabled_by_default()
70
+ test_api_compose_public_has_no_docker_sock()
71
+ print("supervisor negative tests ok")
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()
@@ -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-Cz2turIT.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-XhCT1Q-V.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-CaB-1Ckn.js";export{e as createArchitectureServices};