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