create-workframe 0.1.1 → 0.1.3

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 (100) hide show
  1. package/README.md +14 -1
  2. package/SECURITY.md +4 -6
  3. package/bin/create-workframe.js +3 -3
  4. package/package.json +1 -1
  5. package/scripts/apply-update-hermes.sh +17 -17
  6. package/scripts/apply-update-workframe.sh +77 -77
  7. package/scripts/bootstrap-workspace-link.sh +8 -8
  8. package/scripts/compose-docker-host.sh +37 -37
  9. package/scripts/fix-zk-encryption-key.sh +35 -35
  10. package/scripts/restart-gateway-hermes.sh +12 -12
  11. package/scripts/setup-stack-secrets.sh +50 -50
  12. package/scripts/sync-canonical-to-package.mjs +19 -2
  13. package/scripts/verify-public-deploy.sh +105 -105
  14. package/workframe-api/README.md +10 -14
  15. package/workframe-api/concierge.py +173 -0
  16. package/workframe-api/llm_error_glossary.py +236 -0
  17. package/workframe-api/llm_proxy.py +3 -0
  18. package/workframe-api/openrouter_catalog.py +114 -0
  19. package/workframe-api/package.json +1 -1
  20. package/workframe-api/server.py +144 -48
  21. package/workframe-ui/public/assets/{arc-COAT3laO.js → arc-CRhulRPr.js} +1 -1
  22. package/workframe-ui/public/assets/architecture-7EHR7CIX-CwxC-UJz.js +1 -0
  23. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-BFjWV24l.js → architectureDiagram-3BPJPVTR-B1Gq5onc.js} +1 -1
  24. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-DSQLPfrj.js → blockDiagram-GPEHLZMM-DtXEetq-.js} +1 -1
  25. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-DKEHv1t2.js → c4Diagram-AAUBKEIU-Bw7NEN3_.js} +1 -1
  26. package/workframe-ui/public/assets/channel-BaZSQvmW.js +1 -0
  27. package/workframe-ui/public/assets/{chunk-2J33WTMH-DHZg-DUi.js → chunk-2J33WTMH-91lObBre.js} +1 -1
  28. package/workframe-ui/public/assets/{chunk-3OPIFGDE-BB-OYTfp.js → chunk-3OPIFGDE-D5IopWdW.js} +1 -1
  29. package/workframe-ui/public/assets/{chunk-4BX2VUAB-C93q0YIm.js → chunk-4BX2VUAB-DRHbh4Yd.js} +1 -1
  30. package/workframe-ui/public/assets/{chunk-55IACEB6-MAYniqik.js → chunk-55IACEB6-DcfKoh86.js} +1 -1
  31. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-ChgN6YJs.js → chunk-5ZQYHXKU-BMtcWXOU.js} +1 -1
  32. package/workframe-ui/public/assets/{chunk-727SXJPM-B_FYwdAv.js → chunk-727SXJPM-BW7tTfej.js} +1 -1
  33. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-1_Hw_h1A.js → chunk-AQP2D5EJ-CGoSQAgm.js} +1 -1
  34. package/workframe-ui/public/assets/{chunk-BSJP7CBP-CFiDQ1Rv.js → chunk-BSJP7CBP-RW1Jng9K.js} +1 -1
  35. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-DZ9UMTlB.js → chunk-CSCIHK7Q-D8PwFfuF.js} +1 -1
  36. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DlMlyFgw.js → chunk-FMBD7UC4-rUp_DSZ2.js} +1 -1
  37. package/workframe-ui/public/assets/{chunk-KSCS5N6A-DHXtQ_Hf.js → chunk-KSCS5N6A-C3eMeGne.js} +1 -1
  38. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-CuQzg-QG.js → chunk-L5ZTLDWV-DaE5LwXn.js} +1 -1
  39. package/workframe-ui/public/assets/{chunk-LZXEDZCA-BHzjzCGg.js → chunk-LZXEDZCA-Cmn67nL3.js} +2 -2
  40. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DHXx05n2.js → chunk-ND2GUHAM-Bg-FPAN5.js} +1 -1
  41. package/workframe-ui/public/assets/{chunk-NZK2D7GU-CV5pmDM_.js → chunk-NZK2D7GU-orlL5R9g.js} +1 -1
  42. package/workframe-ui/public/assets/{chunk-O5CBEL6O-6tkCHxsV.js → chunk-O5CBEL6O-BaPyh9SK.js} +1 -1
  43. package/workframe-ui/public/assets/chunk-QZHKN3VN-DXcsnu-x.js +1 -0
  44. package/workframe-ui/public/assets/chunk-WU5MYG2G-B2O0DZxu.js +1 -0
  45. package/workframe-ui/public/assets/{chunk-XPW4576I-BClwIiCp.js → chunk-XPW4576I-CXYFE8tT.js} +1 -1
  46. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-C_TyUBYX.js +1 -0
  47. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-C_TyUBYX.js +1 -0
  48. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-DOrGV6DQ.js → cose-bilkent-S5V4N54A-DDzKMTbp.js} +1 -1
  49. package/workframe-ui/public/assets/{dagre-BM42HDAG-DXTPvJkX.js → dagre-BM42HDAG-DCa6EvS3.js} +1 -1
  50. package/workframe-ui/public/assets/{diagram-2AECGRRQ-xX_v-pbf.js → diagram-2AECGRRQ-C-WIQoE_.js} +1 -1
  51. package/workframe-ui/public/assets/{diagram-5GNKFQAL-Cd2pXbBe.js → diagram-5GNKFQAL-UDk1nRH6.js} +1 -1
  52. package/workframe-ui/public/assets/{diagram-KO2AKTUF-Df3XvUtk.js → diagram-KO2AKTUF-DjLxUTPx.js} +1 -1
  53. package/workframe-ui/public/assets/{diagram-LMA3HP47-CsijIPaD.js → diagram-LMA3HP47-DNA9GSwJ.js} +1 -1
  54. package/workframe-ui/public/assets/{diagram-OG6HWLK6-aq5fmfHd.js → diagram-OG6HWLK6-DO2nQu2f.js} +1 -1
  55. package/workframe-ui/public/assets/{dist-D1c0mkbB.js → dist-DyW8oFIW.js} +1 -1
  56. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-DnFysVRY.js → erDiagram-TEJ5UH35-DFtJLZx1.js} +1 -1
  57. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BTmiKS74.js +1 -0
  58. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-C6Ebi3su.js → flowDiagram-I6XJVG4X-B6K6EnPA.js} +1 -1
  59. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-BQXQtUpa.js → ganttDiagram-6RSMTGT7-CbuWdupl.js} +1 -1
  60. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-Dt0zIs_M.js → gitGraph-WXDBUCRP-BW_jwUPU.js} +1 -1
  61. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BF8gHzRn.js → gitGraphDiagram-PVQCEYII-B4MYuP9j.js} +1 -1
  62. package/workframe-ui/public/assets/index-BYacg3Nj.js +133 -0
  63. package/workframe-ui/public/assets/{info-J43DQDTF-CSmszQJT.js → info-J43DQDTF-Xa61PMDn.js} +1 -1
  64. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-CVTKGW6p.js → infoDiagram-5YYISTIA-BNbQgdUG.js} +1 -1
  65. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js → ishikawaDiagram-YF4QCWOH-BDfiCzHo.js} +1 -1
  66. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-r3wD68_T.js → journeyDiagram-JHISSGLW-Dn52B8tT.js} +1 -1
  67. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-Il8VglqN.js → kanban-definition-UN3LZRKU-rhwkS_g6.js} +1 -1
  68. package/workframe-ui/public/assets/{line-oyjpfz2A.js → line-BQJgrj-x.js} +1 -1
  69. package/workframe-ui/public/assets/{linear-Cf7p5tVp.js → linear-Bq2ZvWSJ.js} +1 -1
  70. package/workframe-ui/public/assets/{mermaid-parser.core-YmbZ-AfY.js → mermaid-parser.core-CwFK_i8p.js} +2 -2
  71. package/workframe-ui/public/assets/{mermaid.core-BFdCAqCo.js → mermaid.core-C9CAm83c.js} +3 -3
  72. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-Cy2iCtEl.js → mindmap-definition-RKZ34NQL-BXwEccNZ.js} +1 -1
  73. package/workframe-ui/public/assets/{packet-YPE3B663-DwOBZL6K.js → packet-YPE3B663-D754iSKT.js} +1 -1
  74. package/workframe-ui/public/assets/{pie-LRSECV5Y-04PPhnKK.js → pie-LRSECV5Y-BQ2V4UWk.js} +1 -1
  75. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-LxIpgHqi.js → pieDiagram-4H26LBE5-CNHecl4C.js} +1 -1
  76. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-0nBYfYm4.js → quadrantDiagram-W4KKPZXB-Bu6ciL2O.js} +1 -1
  77. package/workframe-ui/public/assets/{radar-GUYGQ44K-D2-vBqps.js → radar-GUYGQ44K-Dr8b9Qak.js} +1 -1
  78. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DbuU0nlu.js → requirementDiagram-4Y6WPE33-Beua6Xju.js} +1 -1
  79. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-B2hQ6B2x.js → sankeyDiagram-5OEKKPKP-CSDFJ0P_.js} +1 -1
  80. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BBrU30e1.js → sequenceDiagram-3UESZ5HK-KKziodNI.js} +1 -1
  81. package/workframe-ui/public/assets/{src-BJEDmV70.js → src-PKTIkQR0.js} +1 -1
  82. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-7FGO4kkH.js → stateDiagram-AJRCARHV-Bu8FNMcj.js} +1 -1
  83. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-E9A5RCa9.js +1 -0
  84. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-ptDm4rCN.js → timeline-definition-PNZ67QCA-DMa-RPqy.js} +1 -1
  85. package/workframe-ui/public/assets/{treeView-BLDUP644-CS6Z-0q8.js → treeView-BLDUP644-CzQeUY7D.js} +1 -1
  86. package/workframe-ui/public/assets/{treemap-LRROVOQU-DqV4Y2VA.js → treemap-LRROVOQU-BT4RYAhY.js} +1 -1
  87. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-C0UrZJYt.js → vennDiagram-CIIHVFJN-nKaUH6GG.js} +1 -1
  88. package/workframe-ui/public/assets/{wardley-L42UT6IY-bNDN3_Sa.js → wardley-L42UT6IY-eSpvH7au.js} +1 -1
  89. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-jWiJsefM.js → wardleyDiagram-YWT4CUSO-B5u4s4a5.js} +1 -1
  90. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-Dsh_fLCy.js → xychartDiagram-2RQKCTM6-DcXo5hDi.js} +1 -1
  91. package/workframe-ui/public/index.html +1 -1
  92. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +0 -1
  93. package/workframe-ui/public/assets/channel-g7r_RGaY.js +0 -1
  94. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +0 -1
  95. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +0 -1
  96. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +0 -1
  97. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +0 -1
  98. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +0 -1
  99. package/workframe-ui/public/assets/index-lRpzpNPT.js +0 -133
  100. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +0 -1
@@ -0,0 +1,114 @@
1
+ """Live OpenRouter model list with short TTL cache."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ import urllib.error
8
+ import urllib.request
9
+ from typing import Any
10
+
11
+ _CACHE: dict[str, Any] = {"at": 0.0, "rows": []}
12
+ _TTL_SEC = int(__import__("os").environ.get("WORKFRAME_OPENROUTER_CATALOG_TTL", "3600"))
13
+
14
+
15
+ def _fetch_models(api_key: str) -> list[dict[str, str]]:
16
+ req = urllib.request.Request(
17
+ "https://openrouter.ai/api/v1/models",
18
+ headers={
19
+ "Authorization": f"Bearer {api_key}",
20
+ "HTTP-Referer": "https://workfra.me",
21
+ "X-Title": "Workframe",
22
+ },
23
+ method="GET",
24
+ )
25
+ with urllib.request.urlopen(req, timeout=45) as resp:
26
+ data = json.loads(resp.read())
27
+ rows: list[dict[str, str]] = []
28
+ for item in data.get("data") or []:
29
+ if not isinstance(item, dict):
30
+ continue
31
+ mid = str(item.get("id") or "").strip()
32
+ if not mid:
33
+ continue
34
+ name = str(item.get("name") or mid.rsplit("/", 1)[-1]).strip()
35
+ pricing = item.get("pricing") if isinstance(item.get("pricing"), dict) else {}
36
+ prompt = str(pricing.get("prompt") or "0")
37
+ completion = str(pricing.get("completion") or "0")
38
+ free = prompt in ("0", "0.0") and completion in ("0", "0.0")
39
+ rows.append(
40
+ {
41
+ "provider": "OpenRouter",
42
+ "model": mid,
43
+ "label": name,
44
+ "description": f"{'Free tier · ' if free else ''}{mid}",
45
+ },
46
+ )
47
+ rows.sort(key=lambda r: (0 if ":free" in r["model"] else 1, r["label"].lower()))
48
+ return rows[:120]
49
+
50
+
51
+ def live_suggestions(api_key: str = "", *, limit: int = 40) -> list[dict[str, str]]:
52
+ """Return cached OpenRouter suggestions; empty without a key."""
53
+ key = str(api_key or "").strip()
54
+ if not key:
55
+ return []
56
+ now = time.time()
57
+ if _CACHE["rows"] and now - float(_CACHE["at"]) < _TTL_SEC:
58
+ return list(_CACHE["rows"])[:limit]
59
+ try:
60
+ rows = _fetch_models(key)
61
+ except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError, OSError):
62
+ return list(_CACHE["rows"])[:limit]
63
+ _CACHE["at"] = now
64
+ _CACHE["rows"] = rows
65
+ return rows[:limit]
66
+
67
+
68
+ def probe_account(api_key: str) -> dict[str, Any]:
69
+ """OpenRouter /key + /credits health for connect-time feedback."""
70
+ key = str(api_key or "").strip()
71
+ if not key:
72
+ return {"ok": False, "error": "missing_key"}
73
+ headers = {
74
+ "Authorization": f"Bearer {key}",
75
+ "HTTP-Referer": "https://workfra.me",
76
+ "X-Title": "Workframe",
77
+ }
78
+ out: dict[str, Any] = {"ok": True, "provider": "openrouter"}
79
+ try:
80
+ with urllib.request.urlopen(
81
+ urllib.request.Request("https://openrouter.ai/api/v1/key", headers=headers, method="GET"),
82
+ timeout=30,
83
+ ) as resp:
84
+ key_data = json.loads(resp.read()).get("data") or {}
85
+ out["key_valid"] = True
86
+ out["label"] = str(key_data.get("label") or "")
87
+ out["usage"] = key_data.get("usage")
88
+ except urllib.error.HTTPError as exc:
89
+ out["ok"] = False
90
+ out["key_valid"] = False
91
+ out["error"] = f"HTTP {exc.code}"
92
+ return out
93
+ except OSError as exc:
94
+ return {"ok": False, "key_valid": False, "error": str(exc)}
95
+ try:
96
+ with urllib.request.urlopen(
97
+ urllib.request.Request("https://openrouter.ai/api/v1/credits", headers=headers, method="GET"),
98
+ timeout=30,
99
+ ) as resp:
100
+ cred = json.loads(resp.read()).get("data") or {}
101
+ total = cred.get("total_credits")
102
+ usage = cred.get("total_usage")
103
+ out["credits_total"] = total
104
+ out["credits_usage"] = usage
105
+ if total is not None and usage is not None:
106
+ out["credits_remaining"] = max(0.0, float(total) - float(usage))
107
+ except (urllib.error.HTTPError, OSError, json.JSONDecodeError, TypeError, ValueError):
108
+ pass
109
+ return out
110
+
111
+
112
+ if __name__ == "__main__":
113
+ assert live_suggestions("") == []
114
+ print("openrouter_catalog ok (no network without key)")
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workframe/workframe-api",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -46,6 +46,9 @@ import turn_credentials
46
46
  import llm_proxy
47
47
  import action_proxy
48
48
  import internal_proxy_auth
49
+ import concierge
50
+ import llm_error_glossary
51
+ import openrouter_catalog
49
52
  import updates as stack_updates
50
53
 
51
54
  HERMES_DATA = Path(os.environ.get("HERMES_DATA", "/opt/data"))
@@ -12302,8 +12305,9 @@ def _provider_pool_entry(provider: str, env_var: str, *, base_url: str = "") ->
12302
12305
 
12303
12306
 
12304
12307
  def _runtime_provider_pool_entry(provider: str, env_var: str) -> dict[str, Any]:
12305
- """Credential pool row for u-* profiles — keys live in .env leases, upstream via API proxy."""
12306
- return _provider_pool_entry(provider, env_var, base_url=_llm_proxy_base_url(provider))
12308
+ """Credential pool row for u-* profiles — lease token lives in OPENAI_API_KEY."""
12309
+ lease_env = "OPENAI_API_KEY"
12310
+ return _provider_pool_entry(provider, lease_env, base_url=_llm_proxy_base_url(provider))
12307
12311
 
12308
12312
 
12309
12313
  def _provider_pool_has_entries(auth: dict[str, Any], provider: str) -> bool:
@@ -13313,23 +13317,75 @@ def _enrich_room_chat_payload(payload: dict[str, Any], user_id: str) -> dict[str
13313
13317
  return body
13314
13318
 
13315
13319
 
13316
- def _emit_stream_chat_error(handler: BaseHTTPRequestHandler, message: str) -> None:
13317
- """SSE error frame — keeps UI stream parser on the happy path."""
13318
- text = str(message or "").strip() or "Chat failed."
13319
- payload = json.dumps({"error": text, "text": text, "message": text})
13320
+ def _emit_stream_chat_error(
13321
+ handler: BaseHTTPRequestHandler,
13322
+ message: str = "",
13323
+ *,
13324
+ entry: dict[str, Any] | None = None,
13325
+ ) -> None:
13326
+ """SSE error frame — structured playbook when entry supplied."""
13327
+ payload = llm_error_glossary.notice_payload(entry) if entry else {}
13328
+ if not payload:
13329
+ text = str(message or "").strip() or "Chat failed."
13330
+ payload = {"error": text, "text": text, "message": text}
13320
13331
  try:
13321
13332
  handler.send_response(200)
13322
13333
  handler.send_header("Content-Type", "text/event-stream; charset=utf-8")
13323
13334
  handler.send_header("Cache-Control", "no-cache")
13324
13335
  handler.send_header("Connection", "keep-alive")
13325
13336
  handler.end_headers()
13326
- handler.wfile.write(f"event: error\ndata: {payload}\n\n".encode("utf-8"))
13337
+ body = json.dumps(payload)
13338
+ handler.wfile.write(f"event: error\ndata: {body}\n\n".encode("utf-8"))
13327
13339
  handler.wfile.write(b"event: done\ndata: {}\n\n")
13328
13340
  handler.wfile.flush()
13329
13341
  except (BrokenPipeError, ConnectionResetError, OSError):
13330
13342
  return
13331
13343
 
13332
13344
 
13345
+ def _emit_stream_concierge(handler: BaseHTTPRequestHandler, entry: dict[str, Any]) -> None:
13346
+ """Deterministic assistant reply when LLM path is unavailable."""
13347
+ payload = llm_error_glossary.notice_payload(entry)
13348
+ text = str(payload.get("message") or "")
13349
+ hint = str(payload.get("hint") or "").strip()
13350
+ if hint:
13351
+ text = f"{text}\n\n{hint}"
13352
+ try:
13353
+ handler.send_response(200)
13354
+ handler.send_header("Content-Type", "text/event-stream; charset=utf-8")
13355
+ handler.send_header("Cache-Control", "no-cache")
13356
+ handler.send_header("Connection", "keep-alive")
13357
+ handler.end_headers()
13358
+ handler.wfile.write(
13359
+ f"event: concierge\ndata: {json.dumps(payload)}\n\n".encode("utf-8"),
13360
+ )
13361
+ handler.wfile.write(
13362
+ f"event: message.complete\ndata: {json.dumps({'content': text, 'text': text})}\n\n".encode(
13363
+ "utf-8",
13364
+ ),
13365
+ )
13366
+ handler.wfile.write(b"event: done\ndata: {}\n\n")
13367
+ handler.wfile.flush()
13368
+ except (BrokenPipeError, ConnectionResetError, OSError):
13369
+ return
13370
+
13371
+
13372
+ def _log_llm_failure(
13373
+ handler: BaseHTTPRequestHandler,
13374
+ code: str,
13375
+ *,
13376
+ provider: str = "",
13377
+ model: str = "",
13378
+ profile: str = "",
13379
+ ) -> None:
13380
+ if hasattr(handler, "_log_audit"):
13381
+ handler._log_audit( # type: ignore[attr-defined]
13382
+ "llm_failure",
13383
+ "llm",
13384
+ profile or provider or "unknown",
13385
+ f"code={code} provider={provider} model={model}",
13386
+ )
13387
+
13388
+
13333
13389
  def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload: dict[str, Any]) -> None:
13334
13390
  _triggering_user = str(payload.get("user_id", "") or "")
13335
13391
  _workspace_id = str(payload.get("workspace_id", "") or "")
@@ -13345,6 +13401,15 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13345
13401
  if not text:
13346
13402
  raise ValueError("text required")
13347
13403
  llm_provider = _llm_billing_provider(prof)
13404
+ llm_ready = bool(
13405
+ _triggering_user
13406
+ and _user_can_use_llm(_triggering_user, _workspace_id, llm_provider)
13407
+ )
13408
+ if _triggering_user and not llm_ready:
13409
+ entry = concierge.respond(text, situation="no_provider")
13410
+ _log_llm_failure(handler, str(entry.get("code") or "no_llm_provider"), provider=llm_provider, profile=prof)
13411
+ _emit_stream_concierge(handler, entry)
13412
+ return
13348
13413
  try:
13349
13414
  lifecycle = ensure_profile_api(
13350
13415
  prof,
@@ -13358,7 +13423,15 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13358
13423
  )
13359
13424
  _overlay_turn_user_env(prof, _triggering_user, _workspace_id, _turn_run_id)
13360
13425
  except ValueError as exc:
13361
- _emit_stream_chat_error(handler, str(exc))
13426
+ err_text = str(exc)
13427
+ if "no_llm_provider_for_user" in err_text:
13428
+ entry = concierge.respond(text, situation="no_provider")
13429
+ _log_llm_failure(handler, "no_llm_provider", provider=llm_provider, profile=prof)
13430
+ _emit_stream_concierge(handler, entry)
13431
+ return
13432
+ entry = llm_error_glossary.classify_exception_text(err_text)
13433
+ _log_llm_failure(handler, str(entry.get("code") or ""), provider=llm_provider, profile=prof)
13434
+ _emit_stream_chat_error(handler, entry=entry)
13362
13435
  return
13363
13436
  upstream_body = json.dumps(_profile_turn_payload(prof, text, _room_id)).encode("utf-8")
13364
13437
 
@@ -13487,34 +13560,21 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13487
13560
  handler.wfile.flush()
13488
13561
  if not saw_complete or not complete_text:
13489
13562
  config_key = _read_config_model_api_key(prof)
13563
+ _, model_name = _read_model_from_config(prof)
13490
13564
  if config_key.startswith(turn_credentials.LEASE_PREFIX) and not turn_credentials.validate_lease(
13491
13565
  config_key,
13492
13566
  ):
13493
- err_text = (
13494
- "Turn credential expired before the model replied. "
13495
- "Send the message again."
13496
- )
13497
- err_code = "invalid_lease"
13498
- elif not saw_complete:
13499
- err_text = (
13500
- "Model provider returned no reply. "
13501
- "The request may have failed at the gateway or provider."
13502
- )
13503
- err_code = "provider_empty_reply"
13567
+ entry = llm_error_glossary.playbook_entry("invalid_lease")
13504
13568
  else:
13505
- err_text = (
13506
- "Model provider returned an empty reply. "
13507
- "Check the model name in Settings or try again."
13508
- )
13509
- err_code = "provider_empty_reply"
13510
- err_payload = json.dumps(
13511
- {
13512
- "error": err_text,
13513
- "text": err_text,
13514
- "message": err_text,
13515
- "code": err_code,
13516
- },
13569
+ entry = llm_error_glossary.playbook_entry("provider_empty_reply")
13570
+ _log_llm_failure(
13571
+ handler,
13572
+ str(entry.get("code") or "provider_empty_reply"),
13573
+ provider=llm_provider,
13574
+ model=str(model_name or ""),
13575
+ profile=prof,
13517
13576
  )
13577
+ err_payload = json.dumps(llm_error_glossary.notice_payload(entry))
13518
13578
  handler.wfile.write(f"event: error\ndata: {err_payload}\n\n".encode("utf-8"))
13519
13579
  handler.wfile.flush()
13520
13580
  handler.wfile.write(b"event: done\ndata: {}\n\n")
@@ -14252,16 +14312,16 @@ def _resolve_command(token: str) -> dict[str, Any] | None:
14252
14312
  # overrides live in the profile's config.yaml under `model.default` and
14253
14313
  # `fallback_providers`; this constant is the install-wide default the
14254
14314
  # migration writes when a profile is missing either field.
14255
- HERMES_DEFAULT_PRIMARY = "openrouter/owl-alpha"
14315
+ HERMES_DEFAULT_PRIMARY = "google/gemini-2.5-flash"
14256
14316
  HERMES_DEFAULT_FALLBACK_CHAIN: list[dict[str, str]] = [
14257
- {"provider": "openrouter", "model": "openrouter/nex-agi"},
14258
- {"provider": "openrouter", "model": "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free"},
14317
+ {"provider": "openrouter", "model": "anthropic/claude-sonnet-4.5"},
14318
+ {"provider": "openrouter", "model": "meta-llama/llama-3.3-70b-instruct:free"},
14259
14319
  ]
14260
14320
 
14261
14321
  # ponytail: minimum-viable agentic models per LLM provider; user can escalate in settings.
14262
14322
  PROVIDER_MVP_MODELS: dict[str, dict[str, Any]] = {
14263
14323
  "openrouter": {
14264
- "primary": "openrouter/owl-alpha",
14324
+ "primary": "google/gemini-2.5-flash",
14265
14325
  "fallbacks": list(HERMES_DEFAULT_FALLBACK_CHAIN),
14266
14326
  },
14267
14327
  "openai": {
@@ -14273,8 +14333,8 @@ PROVIDER_MVP_MODELS: dict[str, dict[str, Any]] = {
14273
14333
  "fallbacks": [{"provider": "anthropic", "model": "claude-3-5-haiku-20241022"}],
14274
14334
  },
14275
14335
  "google": {
14276
- "primary": "gemini-2.0-flash",
14277
- "fallbacks": [{"provider": "google", "model": "gemini-2.0-flash"}],
14336
+ "primary": "gemini-2.5-flash",
14337
+ "fallbacks": [{"provider": "google", "model": "gemini-2.5-flash-lite"}],
14278
14338
  },
14279
14339
  "deepseek": {
14280
14340
  "primary": "deepseek-chat",
@@ -14285,8 +14345,8 @@ PROVIDER_MVP_MODELS: dict[str, dict[str, Any]] = {
14285
14345
  "fallbacks": [{"provider": "openai-codex", "model": "gpt-5.4-medium"}],
14286
14346
  },
14287
14347
  "nous": {
14288
- "primary": "openrouter/owl-alpha",
14289
- "fallbacks": [{"provider": "nous", "model": "openrouter/owl-alpha"}],
14348
+ "primary": "nousresearch/hermes-4-70b",
14349
+ "fallbacks": [{"provider": "nous", "model": "nousresearch/hermes-4-70b"}],
14290
14350
  },
14291
14351
  }
14292
14352
 
@@ -14394,15 +14454,21 @@ HERMES_MODEL_CATALOG: list[dict[str, str]] = [
14394
14454
  {"provider": "OpenRouter", "model": "openrouter/auto",
14395
14455
  "label": "OpenRouter Auto",
14396
14456
  "description": "OpenRouter picks the best model per prompt."},
14397
- {"provider": "OpenRouter", "model": "openrouter/owl-alpha",
14398
- "label": "Owl Alpha",
14399
- "description": "Workframe default primary strong general agent."},
14400
- {"provider": "OpenRouter", "model": "openrouter/nex-agi",
14401
- "label": "Nex AGI",
14402
- "description": "Fallback-tier reasoning model on OpenRouter."},
14403
- {"provider": "OpenRouter", "model": "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free",
14404
- "label": "Nemotron 3 Ultra (free)",
14405
- "description": "OpenRouter free tier long-context agentic fallback."},
14457
+ {"provider": "OpenRouter", "model": "google/gemini-2.5-flash",
14458
+ "label": "Gemini 2.5 Flash",
14459
+ "description": "Fast, capable default for OpenRouter BYOK."},
14460
+ {"provider": "OpenRouter", "model": "anthropic/claude-sonnet-4.5",
14461
+ "label": "Claude Sonnet 4.5 (via OR)",
14462
+ "description": "Strong agentic fallback on OpenRouter."},
14463
+ {"provider": "OpenRouter", "model": "meta-llama/llama-3.3-70b-instruct:free",
14464
+ "label": "Llama 3.3 70B (free)",
14465
+ "description": "Free-tier OpenRouter fallback when available."},
14466
+ {"provider": "OpenRouter", "model": "z-ai/glm-5.1",
14467
+ "label": "GLM 5.1",
14468
+ "description": "High-availability model on OpenRouter."},
14469
+ {"provider": "OpenRouter", "model": "nousresearch/hermes-4-70b",
14470
+ "label": "Hermes 4 70B",
14471
+ "description": "Agent-tuned model on OpenRouter."},
14406
14472
  {"provider": "OpenRouter", "model": "openrouter/nvidia/llama-3.1-nemotron-70b-instruct",
14407
14473
  "label": "Nemotron 70B Instruct",
14408
14474
  "description": "NVIDIA Nemotron via OpenRouter."},
@@ -14780,6 +14846,17 @@ def hermes_models(profile: str = "", user_id: str = "", workspace_id: str = "")
14780
14846
  ] if (picker_llm or connected) else [],
14781
14847
  block,
14782
14848
  )
14849
+ if user_id and "openrouter" in {n.lower() for n in (picker_llm or connected)}:
14850
+ resolved = _resolve_credential(user_id, workspace_id, "openrouter", user_only=True)
14851
+ secret = _credential_secret(resolved or {}, user_id) if resolved else ""
14852
+ live = openrouter_catalog.live_suggestions(secret, limit=30)
14853
+ if live:
14854
+ seen = {str(r.get("model") or "") for r in suggestions}
14855
+ suggestions = [
14856
+ {**row, "label": f"Live · {row.get('label', row.get('model', ''))}"}
14857
+ for row in live
14858
+ if str(row.get("model") or "") not in seen
14859
+ ] + suggestions
14783
14860
  return {
14784
14861
  "ok": True,
14785
14862
  "profile": primary_profile,
@@ -17391,11 +17468,29 @@ class Handler(BaseHTTPRequestHandler):
17391
17468
  VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
17392
17469
  (cred_id, None, user_id, None, provider, credential_type, cred_ref, label, 1, user_id, now, now),
17393
17470
  )
17471
+ conn.execute(
17472
+ """UPDATE credential_bindings
17473
+ SET is_active = 0, deleted_at = ?, updated_at = ?
17474
+ WHERE user_id = ? AND provider = ? AND deleted_at IS NULL AND id != ?""",
17475
+ (now, now, user_id, provider, cred_id),
17476
+ )
17394
17477
  conn.commit()
17395
17478
  conn.close()
17396
17479
  except sqlite3.Error as exc:
17397
17480
  return self._json(500, {"ok": False, "error": f"db_error: {exc}"})
17398
17481
  self._log_audit("credential_stored", "credential_binding", cred_id, f"provider={provider}")
17482
+ health: dict[str, Any] = {}
17483
+ if provider == "openrouter":
17484
+ secret_probe = _credential_secret(
17485
+ {
17486
+ "credential_ref": cred_ref,
17487
+ "scope": "user",
17488
+ "user_id": user_id,
17489
+ },
17490
+ user_id,
17491
+ )
17492
+ if secret_probe:
17493
+ health = openrouter_catalog.probe_account(secret_probe)
17399
17494
  _bootstrap_model_after_llm_connect(user_id, str(body.get("workspace_id") or ""), provider)
17400
17495
  return self._json(200, {
17401
17496
  "ok": True,
@@ -17407,6 +17502,7 @@ class Handler(BaseHTTPRequestHandler):
17407
17502
  "user_id": user_id,
17408
17503
  "created_at": now,
17409
17504
  "updated_at": now,
17505
+ "health": health,
17410
17506
  **payload,
17411
17507
  })
17412
17508
 
@@ -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-D1c0mkbB.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-DyW8oFIW.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-CwFK_i8p.js";export{e as createArchitectureServices};