create-workframe 0.1.14 → 0.1.16

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 (136) hide show
  1. package/README.md +1 -1
  2. package/bin/create-workframe.js +4 -0
  3. package/package.json +1 -1
  4. package/scripts/sync-canonical-to-package.mjs +3 -0
  5. package/workframe-api/activity_feed.py +53 -5
  6. package/workframe-api/api_errors.py +126 -0
  7. package/workframe-api/chat_bind.py +8 -1
  8. package/workframe-api/chat_stream.py +70 -26
  9. package/workframe-api/crew_registry.py +3 -6
  10. package/workframe-api/google_auth.py +20 -12
  11. package/workframe-api/handler_modules/handler_auth.py +66 -21
  12. package/workframe-api/handler_modules/handler_chat.py +25 -0
  13. package/workframe-api/handler_modules/handler_install.py +45 -0
  14. package/workframe-api/handler_modules/handler_provider.py +1 -0
  15. package/workframe-api/handler_modules/handler_workspace.py +30 -16
  16. package/workframe-api/hermes_admin.py +9 -2
  17. package/workframe-api/hermes_profiles.py +25 -5
  18. package/workframe-api/install_api.py +38 -4
  19. package/workframe-api/lane_bindings.py +24 -0
  20. package/workframe-api/llm_error_glossary.py +58 -4
  21. package/workframe-api/mention_invoke.py +89 -27
  22. package/workframe-api/model_surface.py +1 -1
  23. package/workframe-api/provider_bindings.py +44 -2
  24. package/workframe-api/provider_bootstrap.py +9 -0
  25. package/workframe-api/provider_catalog.py +3 -3
  26. package/workframe-api/rooms.py +3 -0
  27. package/workframe-api/route_registry.py +1 -0
  28. package/workframe-api/run_ledger.py +52 -8
  29. package/workframe-api/runtime_cohort.py +11 -1
  30. package/workframe-api/server.py +25 -1
  31. package/workframe-api/stack_config.py +4 -1
  32. package/workframe-api/test_api_errors.py +33 -0
  33. package/workframe-api/test_enrich_room_messages.py +72 -0
  34. package/workframe-api/test_exception_hygiene.py +4 -2
  35. package/workframe-api/test_hermes_admin.py +7 -0
  36. package/workframe-api/test_install_wizard_resume.py +39 -13
  37. package/workframe-api/test_model_surface_consistency.py +118 -100
  38. package/workframe-api/test_profile_model_yaml.py +4 -1
  39. package/workframe-api/test_provider_bindings.py +10 -0
  40. package/workframe-api/test_run_ledger.py +2 -1
  41. package/workframe-api/test_server_reexports.py +4 -0
  42. package/workframe-api/test_user_llm_prefs.py +99 -0
  43. package/workframe-api/time-overlay-split.py +58 -0
  44. package/workframe-api/time-ttft-quick.py +135 -0
  45. package/workframe-api/time-ttft.py +250 -0
  46. package/workframe-api/time-wf-stream.py +145 -0
  47. package/workframe-api/turn_overlay.py +96 -23
  48. package/workframe-api/updates.py +1 -2
  49. package/workframe-api/user_prefs.py +26 -5
  50. package/workframe-api/workspace_bootstrap.py +16 -10
  51. package/workframe-ui/public/assets/{arc-BT9iZUSd.js → arc-esSBm9C_.js} +1 -1
  52. package/workframe-ui/public/assets/architecture-7EHR7CIX-j-DiBdGu.js +1 -0
  53. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-CaEDd3np.js → architectureDiagram-3BPJPVTR-DVVz9RGn.js} +1 -1
  54. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-DHJhMrIS.js → blockDiagram-GPEHLZMM-B6i10GMX.js} +1 -1
  55. package/workframe-ui/public/assets/brave-B885JYnH.svg +11 -0
  56. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-Cdyz9hIV.js → c4Diagram-AAUBKEIU-BqwJ7JoH.js} +1 -1
  57. package/workframe-ui/public/assets/channel-CJ9pFXbx.js +1 -0
  58. package/workframe-ui/public/assets/{chunk-2J33WTMH-BbZx76LX.js → chunk-2J33WTMH-CyhcedSa.js} +1 -1
  59. package/workframe-ui/public/assets/{chunk-3OPIFGDE-DcSxjTIP.js → chunk-3OPIFGDE-1Rr6WuvI.js} +1 -1
  60. package/workframe-ui/public/assets/{chunk-4BX2VUAB-B7F-kTY1.js → chunk-4BX2VUAB-D2bRrARV.js} +1 -1
  61. package/workframe-ui/public/assets/{chunk-55IACEB6-BwwEiRTK.js → chunk-55IACEB6-CKXeZxSr.js} +1 -1
  62. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DAi_NksX.js → chunk-5ZQYHXKU-DYQxxPZ4.js} +1 -1
  63. package/workframe-ui/public/assets/{chunk-727SXJPM-BIaF4XCN.js → chunk-727SXJPM-CGpIgqvD.js} +1 -1
  64. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-DjYSk1dG.js → chunk-AQP2D5EJ-B88NGQ7d.js} +1 -1
  65. package/workframe-ui/public/assets/{chunk-BSJP7CBP-_twnyfgV.js → chunk-BSJP7CBP-CCymiI0O.js} +1 -1
  66. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-CkNmdy5s.js → chunk-CSCIHK7Q-DRgey03j.js} +1 -1
  67. package/workframe-ui/public/assets/{chunk-FMBD7UC4-CjLqd-gl.js → chunk-FMBD7UC4-l1adn8gM.js} +1 -1
  68. package/workframe-ui/public/assets/{chunk-KSCS5N6A-_6oNqFjj.js → chunk-KSCS5N6A-CaEvDRPK.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-CeO9Am_D.js → chunk-L5ZTLDWV-BgCnvPTv.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-LZXEDZCA-sRM4YeIe.js → chunk-LZXEDZCA-t2zokHpn.js} +2 -2
  71. package/workframe-ui/public/assets/{chunk-ND2GUHAM-CH8tMb21.js → chunk-ND2GUHAM-C3c9p0sv.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-NZK2D7GU-DAI1bsII.js → chunk-NZK2D7GU-DSujF-R8.js} +1 -1
  73. package/workframe-ui/public/assets/{chunk-O5CBEL6O-B2e-5a2G.js → chunk-O5CBEL6O-DJJTbM6p.js} +1 -1
  74. package/workframe-ui/public/assets/chunk-QZHKN3VN-Bxk3enxo.js +1 -0
  75. package/workframe-ui/public/assets/chunk-WU5MYG2G-DdbkX1rg.js +1 -0
  76. package/workframe-ui/public/assets/{chunk-XPW4576I-DM2-0q37.js → chunk-XPW4576I-D2Cxn3AG.js} +1 -1
  77. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-bwCRiiUl.js +1 -0
  78. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-bwCRiiUl.js +1 -0
  79. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-BnbbewLv.js → cose-bilkent-S5V4N54A---dQ-pFK.js} +1 -1
  80. package/workframe-ui/public/assets/{dagre-BM42HDAG-C4zmB1Vt.js → dagre-BM42HDAG-B_qe1ODY.js} +1 -1
  81. package/workframe-ui/public/assets/{diagram-2AECGRRQ-n-BJmsiC.js → diagram-2AECGRRQ-Bou8vEYl.js} +1 -1
  82. package/workframe-ui/public/assets/{diagram-5GNKFQAL-BzQGQnZi.js → diagram-5GNKFQAL-DEUEzONU.js} +1 -1
  83. package/workframe-ui/public/assets/{diagram-KO2AKTUF-Cx_De-wS.js → diagram-KO2AKTUF-Ctq571VA.js} +1 -1
  84. package/workframe-ui/public/assets/{diagram-LMA3HP47-CIOI3Lok.js → diagram-LMA3HP47-CnKn89lm.js} +1 -1
  85. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DkRuPLWY.js → diagram-OG6HWLK6-DnKlrMx2.js} +1 -1
  86. package/workframe-ui/public/assets/{dist-XhCT1Q-V.js → dist-ubrEgQyZ.js} +1 -1
  87. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-C3GwhDU1.js → erDiagram-TEJ5UH35-PjqO7CJd.js} +1 -1
  88. package/workframe-ui/public/assets/eventmodeling-FCH6USID-g-Xo_eS4.js +1 -0
  89. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CLWH8z2e.js → flowDiagram-I6XJVG4X-B2PN_nNG.js} +1 -1
  90. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-D4h66Vy3.js → ganttDiagram-6RSMTGT7-ThyAtNQP.js} +1 -1
  91. package/workframe-ui/public/assets/gemini-Cq-0-rmr.svg +105 -0
  92. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-IW5F-Q6c.js → gitGraph-WXDBUCRP-CB2UgoWy.js} +1 -1
  93. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BjNIbmRM.js → gitGraphDiagram-PVQCEYII-DRJJ-zT-.js} +1 -1
  94. package/workframe-ui/public/assets/index-1euVfd3b.js +132 -0
  95. package/workframe-ui/public/assets/index-BLVAsAeO.css +1 -0
  96. package/workframe-ui/public/assets/{info-J43DQDTF-CcJspM79.js → info-J43DQDTF-CLtR33Q_.js} +1 -1
  97. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-DP4xR9jt.js → infoDiagram-5YYISTIA-Cmunqfvi.js} +1 -1
  98. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-D4cZb21V.js → ishikawaDiagram-YF4QCWOH-DWE_84eL.js} +1 -1
  99. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DsLuHkIJ.js → journeyDiagram-JHISSGLW-BtBhpyRL.js} +1 -1
  100. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-Wu65nOsR.js → kanban-definition-UN3LZRKU-B8QspDDL.js} +1 -1
  101. package/workframe-ui/public/assets/{line-ByBDzRpy.js → line-iOgDn4O8.js} +1 -1
  102. package/workframe-ui/public/assets/{linear-DzXi2vnq.js → linear-BBehZNVA.js} +1 -1
  103. package/workframe-ui/public/assets/{mermaid-parser.core-CaB-1Ckn.js → mermaid-parser.core-DQK5HcdU.js} +2 -2
  104. package/workframe-ui/public/assets/{mermaid.core-D7HTHC17.js → mermaid.core-Dsrdd1Wr.js} +3 -3
  105. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-BfKsC9aj.js → mindmap-definition-RKZ34NQL-Ds_BfsJA.js} +1 -1
  106. package/workframe-ui/public/assets/{packet-YPE3B663-ClpU98TO.js → packet-YPE3B663-Bz3pi4i8.js} +1 -1
  107. package/workframe-ui/public/assets/{pie-LRSECV5Y-KeOyY_-u.js → pie-LRSECV5Y-BFFah-6g.js} +1 -1
  108. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CUpjnzbs.js → pieDiagram-4H26LBE5-CVCvLPcC.js} +1 -1
  109. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CJdPTszF.js → quadrantDiagram-W4KKPZXB-Cx6rrc_7.js} +1 -1
  110. package/workframe-ui/public/assets/{radar-GUYGQ44K-C9BpandZ.js → radar-GUYGQ44K-DJ5vN_cg.js} +1 -1
  111. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-CHDZPwry.js → requirementDiagram-4Y6WPE33-Cbry6b5c.js} +1 -1
  112. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-CMJJh91s.js → sankeyDiagram-5OEKKPKP-CICUr5En.js} +1 -1
  113. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BsV4GppP.js → sequenceDiagram-3UESZ5HK-BsOuB3F-.js} +1 -1
  114. package/workframe-ui/public/assets/{src-WHks82Up.js → src-TS8jZspq.js} +1 -1
  115. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-eSX9P8z0.js → stateDiagram-AJRCARHV-avBzMQy_.js} +1 -1
  116. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-BFRA5TYD.js +1 -0
  117. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-Dg1UkhGo.js → timeline-definition-PNZ67QCA-DHLm6UFN.js} +1 -1
  118. package/workframe-ui/public/assets/{treeView-BLDUP644-B7yGsBuj.js → treeView-BLDUP644-B3Qd5t3j.js} +1 -1
  119. package/workframe-ui/public/assets/{treemap-LRROVOQU-j_G2oyQ_.js → treemap-LRROVOQU-D1dMQS7x.js} +1 -1
  120. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BRrT0jqC.js → vennDiagram-CIIHVFJN-DZ6SoniY.js} +1 -1
  121. package/workframe-ui/public/assets/{wardley-L42UT6IY-DEdUqez2.js → wardley-L42UT6IY-9gK17Tp3.js} +1 -1
  122. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-C-bBK9Bb.js → wardleyDiagram-YWT4CUSO-FCU_wmRE.js} +1 -1
  123. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DhH5xuR2.js → xychartDiagram-2RQKCTM6-DWowvseN.js} +1 -1
  124. package/workframe-ui/public/index.html +2 -2
  125. package/workframe-ui/public/workframe-build.json +3 -3
  126. package/docs/security.md +0 -85
  127. package/workframe-ui/public/assets/architecture-7EHR7CIX-m1aPUQ_r.js +0 -1
  128. package/workframe-ui/public/assets/channel-3M69ekE5.js +0 -1
  129. package/workframe-ui/public/assets/chunk-QZHKN3VN-BMl9ed8P.js +0 -1
  130. package/workframe-ui/public/assets/chunk-WU5MYG2G-C08HH6BU.js +0 -1
  131. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BFcOE4Aq.js +0 -1
  132. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BFcOE4Aq.js +0 -1
  133. package/workframe-ui/public/assets/eventmodeling-FCH6USID-DIVBL6CF.js +0 -1
  134. package/workframe-ui/public/assets/index-DL2vJP4L.css +0 -1
  135. package/workframe-ui/public/assets/index-ElS_D59P.js +0 -130
  136. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-FzMK4PkM.js +0 -1
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import threading
6
+ import time
5
7
  import uuid
6
8
  from typing import Any
7
9
 
@@ -345,18 +347,56 @@ def _read_config_model_api_key(profile: str) -> str:
345
347
 
346
348
 
347
349
 
348
- def _sync_profile_model_api_key(profile: str, api_key: str) -> None:
350
+ def _sync_profile_model_api_key(profile: str, api_key: str, *, wait_healthy: bool = False) -> None:
349
351
  value = str(api_key or "").strip()
350
352
  if not value:
351
353
  _clear_profile_model_api_key(profile)
352
- _srv()._restart_runtime_profile_gateway(profile)
354
+ if wait_healthy:
355
+ _srv()._restart_runtime_profile_gateway(profile)
356
+ else:
357
+ _srv()._schedule_gateway_reload(profile)
353
358
  return
354
359
  if _read_config_model_api_key(profile) == value:
355
360
  return
356
361
  ok, err = _set_profile_model_api_key(profile, value)
357
362
  if not ok:
358
363
  raise ValueError(err or "profile model api_key write failed")
359
- _srv()._restart_runtime_profile_gateway(profile)
364
+ if wait_healthy:
365
+ _srv()._restart_runtime_profile_gateway(profile)
366
+ else:
367
+ _srv()._schedule_gateway_reload(profile)
368
+
369
+
370
+ def _schedule_profile_lease_yaml_reconcile(
371
+ profile: str,
372
+ user_id: str,
373
+ workspace_id: str,
374
+ provider: str,
375
+ ) -> None:
376
+ """Bind path — align config.yaml lease token without blocking the client."""
377
+ user = str(user_id or "").strip()
378
+ prov = str(provider or "openrouter").strip().lower()
379
+ if not user:
380
+ return
381
+
382
+ def _run() -> None:
383
+ try:
384
+ prof = _srv().resolve_hermes_profile(profile)
385
+ if not _srv()._is_runtime_profile_slug(prof):
386
+ return
387
+ env_var = _profile_lease_env_var(prof, prov)
388
+ token = _read_profile_lease_token(prof, env_var)
389
+ if not token or _read_config_model_api_key(prof) == token:
390
+ return
391
+ _sync_profile_model_api_key(prof, token)
392
+ except Exception: # noqa: BLE001
393
+ return
394
+
395
+ threading.Thread(
396
+ target=_run,
397
+ name=f"lease-yaml-{profile}",
398
+ daemon=True,
399
+ ).start()
360
400
 
361
401
 
362
402
  def _apply_turn_credential_lease(
@@ -369,6 +409,30 @@ def _apply_turn_credential_lease(
369
409
  """Issue per-run lease token into profile env; real secret stays in API vault."""
370
410
  provider = str(provider or "openrouter").strip().lower()
371
411
  prof = _srv().resolve_hermes_profile(profile)
412
+ env_var = _profile_lease_env_var(prof, provider)
413
+ config_lease = _read_config_model_api_key(prof)
414
+ if config_lease and not turn_credentials.validate_lease(config_lease):
415
+ _clear_profile_model_api_key(prof)
416
+ existing_token = _read_profile_lease_token(prof, env_var)
417
+ if existing_token and not turn_credentials.validate_lease(existing_token):
418
+ _strip_profile_llm_env(prof, include_leases=True)
419
+ _clear_profile_model_api_key(prof)
420
+ existing_token = ""
421
+ if existing_token:
422
+ lease_meta = turn_credentials.validate_lease(existing_token)
423
+ if lease_meta and _lease_reusable_for_turn(
424
+ existing_token,
425
+ user_id=user_id,
426
+ workspace_id=workspace_id,
427
+ provider=provider,
428
+ binding_id=str(lease_meta.get("credential_binding_id") or ""),
429
+ ):
430
+ # ponytail: hot path — skip credential resolve + env rewrite when lease still valid
431
+ if _srv()._is_runtime_profile_slug(prof) and existing_token:
432
+ if _read_config_model_api_key(prof) != existing_token:
433
+ _sync_profile_model_api_key(prof, existing_token)
434
+ return existing_token
435
+
372
436
  if _srv()._is_runtime_profile_slug(prof):
373
437
  resolved = _require_runtime_owner_provider(user_id, workspace_id, provider)
374
438
  else:
@@ -401,15 +465,6 @@ def _apply_turn_credential_lease(
401
465
  user_id=user_id,
402
466
  workspace_id=workspace_id,
403
467
  )
404
- env_var = _profile_lease_env_var(prof, provider)
405
- config_lease = _read_config_model_api_key(prof)
406
- if config_lease and not turn_credentials.validate_lease(config_lease):
407
- _clear_profile_model_api_key(prof)
408
- existing_token = _read_profile_lease_token(prof, env_var)
409
- if existing_token and not turn_credentials.validate_lease(existing_token):
410
- _strip_profile_llm_env(prof, include_leases=True)
411
- _clear_profile_model_api_key(prof)
412
- existing_token = ""
413
468
  if _lease_reusable_for_turn(
414
469
  existing_token,
415
470
  user_id=user_id,
@@ -417,7 +472,6 @@ def _apply_turn_credential_lease(
417
472
  provider=provider,
418
473
  binding_id=binding_id,
419
474
  ):
420
- # ponytail: hot path — skip yaml/env rewrite when lease still valid
421
475
  if _srv()._is_runtime_profile_slug(prof) and existing_token:
422
476
  if _read_config_model_api_key(prof) != existing_token:
423
477
  _sync_profile_model_api_key(prof, existing_token)
@@ -462,11 +516,38 @@ def _user_action_env_specs() -> list[tuple[str, str]]:
462
516
  return specs
463
517
 
464
518
 
519
+ _USER_ACTION_CRED_CACHE: dict[tuple[str, str], tuple[bool, float]] = {}
520
+ _USER_ACTION_CRED_TTL_SEC = 30.0
521
+
522
+
523
+ def _user_has_action_credentials(user_id: str, workspace_id: str) -> bool:
524
+ """Cached negative scan — skip per-turn tool overlay when user has no PAT/OAuth tools."""
525
+ user = str(user_id or "").strip()
526
+ if not user:
527
+ return False
528
+ ws = str(workspace_id or "").strip()
529
+ key = (user, ws)
530
+ now = time.monotonic()
531
+ cached = _USER_ACTION_CRED_CACHE.get(key)
532
+ if cached and now - cached[1] < _USER_ACTION_CRED_TTL_SEC:
533
+ return cached[0]
534
+ has_any = False
535
+ for provider_id, _env_var in _user_action_env_specs():
536
+ resolved = _srv()._resolve_credential(user, ws, provider_id, user_only=True)
537
+ if resolved and _srv()._credential_secret(resolved, user):
538
+ has_any = True
539
+ break
540
+ _USER_ACTION_CRED_CACHE[key] = (has_any, now)
541
+ return has_any
542
+
543
+
465
544
  def _overlay_turn_user_env(profile: str, user_id: str, workspace_id: str, run_id: str = "") -> None:
466
545
  """Issue per-run lease tokens for user-only tool credentials (never raw PATs on Agents mount)."""
467
546
  user = str(user_id or "").strip()
468
547
  if not user:
469
548
  return
549
+ if not _user_has_action_credentials(user, workspace_id):
550
+ return
470
551
  base_run = str(run_id or "").strip() or str(uuid.uuid4())
471
552
  for provider_id, _env_var in _user_action_env_specs():
472
553
  sub_run = f"{base_run}::{provider_id}"
@@ -658,16 +739,8 @@ def _overlay_turn_provider_env(
658
739
  ) -> str:
659
740
  """Per-run lease into profile env; upstream key resolved only inside workframe-api."""
660
741
  run_id = str(run_id or "").strip() or str(uuid.uuid4())
661
- prof = _srv().resolve_hermes_profile(profile)
662
- is_runtime = _srv()._is_runtime_profile_slug(prof)
663
- config_before = _read_config_model_api_key(prof) if is_runtime else ""
664
- token = _apply_turn_credential_lease(profile, user_id, workspace_id, provider, run_id)
665
- if is_runtime and _read_config_model_api_key(prof) != config_before:
666
- _srv()._invalidate_profile_health_cache(prof)
667
- # ponytail: gateway restart after lease yaml sync can take ~40s on cold runtime profiles.
668
- if not _srv()._wait_profile_api_healthy(prof, attempts=160, delay=0.25):
669
- _srv().ensure_profile_api(prof, user_id, workspace_id, bootstrap_providers=False)
670
- return token
742
+ # ponytail: yaml sync + gateway reload are async on the send path — never block TTFT (~40s).
743
+ return _apply_turn_credential_lease(profile, user_id, workspace_id, provider, run_id)
671
744
 
672
745
 
673
746
  def _try_overlay_turn_provider_env(
@@ -557,9 +557,8 @@ def apply_update(target: str, *, user_ack: bool = False) -> dict[str, Any]:
557
557
  )
558
558
  if update_decision.decision == "deny":
559
559
  raise ValueError(update_decision.reason or "cell_update_denied")
560
- if user_ack and update_decision.decision != "allow":
560
+ if update_decision.decision != "allow":
561
561
  raise ValueError(update_decision.reason or "cell_update_denied")
562
- # ponytail: without user_ack, needs_user_action does not block apply until UI sends ack
563
562
  target = str(target or "all").strip().lower()
564
563
  if target not in {"hermes", "workframe", "all"}:
565
564
  raise ValueError("invalid_update_target")
@@ -125,6 +125,15 @@ def read_user_llm_prefs(user_id: str) -> tuple[str, list[dict[str, str]]]:
125
125
  chain.append({"provider": prov, "model": model})
126
126
  except json.JSONDecodeError:
127
127
  pass
128
+ if not primary and chain:
129
+ first = chain[0]
130
+ prov = str(first.get("provider") or "").strip()
131
+ model = str(first.get("model") or "").strip()
132
+ if model:
133
+ if prov and not model.lower().startswith(f"{prov}/"):
134
+ primary = f"{prov}/{model}"
135
+ else:
136
+ primary = model
128
137
  return primary, chain
129
138
 
130
139
 
@@ -137,13 +146,25 @@ def write_user_llm_prefs(
137
146
  user_id = str(user_id or "").strip()
138
147
  if not user_id:
139
148
  return
140
- patch: dict[str, str] = {}
149
+ if not primary and fallback_chain is None:
150
+ return
151
+ user = get_workframe_user(user_id)
152
+ pids: dict[str, Any] = {}
153
+ if user and isinstance(user.get("platform_ids"), dict):
154
+ pids = dict(user["platform_ids"])
141
155
  if primary:
142
- patch["llm_primary"] = primary.strip()
156
+ pids["llm_primary"] = primary.strip()
143
157
  if fallback_chain is not None:
144
- patch["llm_fallback_chain"] = json.dumps(fallback_chain, separators=(",", ":"))
145
- if patch:
146
- merge_user_platform_ids(user_id, patch)
158
+ pids["llm_fallback_chain"] = json.dumps(fallback_chain, separators=(",", ":"))
159
+ conn = _srv()._workframe_db()
160
+ try:
161
+ conn.execute(
162
+ "UPDATE users SET platform_ids = ?, updated_at = ? WHERE id = ?",
163
+ (json.dumps(pids, sort_keys=True), str(int(time.time())), user_id),
164
+ )
165
+ conn.commit()
166
+ finally:
167
+ conn.close()
147
168
 
148
169
 
149
170
  def validate_me_profile_updates(updates: dict[str, Any]) -> None:
@@ -16,12 +16,12 @@ def _srv():
16
16
  return srv
17
17
 
18
18
 
19
- def _provision_invited_member_agent_runtimes(workspace_id: str, user_id: str) -> None:
19
+ def _provision_invited_member_agent_runtimes(workspace_id: str, user_id: str) -> bool:
20
20
  """Provision u-* runtimes for workspace agents when a member joins."""
21
21
  workspace_id = str(workspace_id or "").strip()
22
22
  user_id = str(user_id or "").strip()
23
23
  if not workspace_id or not user_id:
24
- return
24
+ return False
25
25
  try:
26
26
  conn = _srv()._workframe_db()
27
27
  rows = conn.execute(
@@ -33,32 +33,35 @@ def _provision_invited_member_agent_runtimes(workspace_id: str, user_id: str) ->
33
33
  ).fetchall()
34
34
  conn.close()
35
35
  except Exception: # noqa: BLE001
36
- return
36
+ return False
37
+ ok = True
37
38
  for row in rows:
38
- _provision_agent_dm_runtimes(workspace_id, str(row["id"]), [user_id])
39
+ if not _provision_agent_dm_runtimes(workspace_id, str(row["id"]), [user_id]):
40
+ ok = False
41
+ return ok
39
42
 
40
43
 
41
44
  def _provision_agent_dm_runtimes(
42
45
  workspace_id: str,
43
46
  agent_profile_id: str,
44
47
  user_ids: list[str],
45
- ) -> None:
48
+ ) -> bool:
46
49
  """Create u-* runtime profiles when an agent DM room is created — install/onboarding only, not bind."""
47
50
  workspace_id = str(workspace_id or "").strip()
48
51
  agent_profile_id = str(agent_profile_id or "").strip()
49
52
  if not workspace_id or not agent_profile_id:
50
- return
53
+ return False
51
54
  try:
52
55
  conn = _srv()._workframe_db()
53
56
  agent_row = _srv()._lookup_agent_profile(conn, workspace_id, agent_profile_id)
54
57
  conn.close()
55
58
  except Exception: # noqa: BLE001
56
- return
59
+ return False
57
60
  if not agent_row:
58
- return
61
+ return False
59
62
  template = str(agent_row["slug"] or "").strip()
60
63
  if not template:
61
- return
64
+ return False
62
65
  try:
63
66
  template = _srv().resolve_validated_profile(template)
64
67
  except ValueError as exc:
@@ -66,7 +69,8 @@ def _provision_agent_dm_runtimes(
66
69
  f"[provision_agent_dm_runtimes] skip {agent_profile_id}: invalid template {template!r}: {exc}",
67
70
  flush=True,
68
71
  )
69
- return
72
+ return False
73
+ ok = True
70
74
  for uid in user_ids:
71
75
  user = str(uid or "").strip()
72
76
  if not user:
@@ -77,10 +81,12 @@ def _provision_agent_dm_runtimes(
77
81
  try:
78
82
  _srv().ensure_runtime_profile(runtime, template, user, workspace_id)
79
83
  except Exception as exc: # noqa: BLE001
84
+ ok = False
80
85
  print(
81
86
  f"[provision_agent_dm_runtimes] failed {runtime} for {user}: {exc}",
82
87
  flush=True,
83
88
  )
89
+ return ok
84
90
 
85
91
 
86
92
  def bootstrap_agent_dm_lane(
@@ -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-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};
1
+ import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-ubrEgQyZ.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -0,0 +1 @@
1
+ import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-DQK5HcdU.js";export{e as createArchitectureServices};