create-workframe 0.1.4 → 0.1.6

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 (182) hide show
  1. package/.dockerignore +22 -22
  2. package/.gitignore +73 -73
  3. package/LICENSE +201 -201
  4. package/NOTICE +12 -12
  5. package/README.md +40 -40
  6. package/SECURITY.md +38 -38
  7. package/bin/create-workframe.js +2816 -2814
  8. package/bin/workframe.js +329 -329
  9. package/docs/workspace-instructions/WORKFRAME_DISCORD.md +20 -20
  10. package/docs/workspace-instructions/WORKFRAME_DOCUMENTS_AND_ARTIFACTS.md +20 -20
  11. package/docs/workspace-instructions/WORKFRAME_KANBAN.md +20 -20
  12. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +21 -21
  13. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +29 -29
  14. package/docs/workspace-instructions/WORKFRAME_TELEGRAM.md +19 -19
  15. package/package.json +64 -64
  16. package/profiles/README.md +15 -15
  17. package/profiles/architect/AGENTS.md +29 -29
  18. package/profiles/architect/SOUL.md +44 -44
  19. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/designer/AGENTS.md +26 -26
  21. package/profiles/designer/SOUL.md +31 -31
  22. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  23. package/profiles/dev/AGENTS.md +28 -28
  24. package/profiles/dev/SOUL.md +31 -31
  25. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  26. package/profiles/docs/AGENTS.md +27 -27
  27. package/profiles/docs/SOUL.md +30 -30
  28. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  29. package/profiles/research/AGENTS.md +26 -26
  30. package/profiles/research/SOUL.md +31 -31
  31. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  32. package/profiles/visionary/AGENTS.md +25 -25
  33. package/profiles/visionary/SOUL.md +31 -31
  34. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  35. package/profiles/workframe-agent/AGENTS.md +37 -37
  36. package/profiles/workframe-agent/SETUP.md +185 -185
  37. package/profiles/workframe-agent/SOUL.md +61 -61
  38. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  39. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  40. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  41. package/prompts/WORKFRAME_PROMPT_TEMPLATES.md +16 -16
  42. package/rules/.hermes.md +11 -11
  43. package/rules/AGENTS.md +22 -22
  44. package/rules/workspace-README.md +5 -5
  45. package/scripts/apply-update-workframe.sh +91 -21
  46. package/scripts/bundle-workframe-ui.mjs +83 -83
  47. package/scripts/create_workframe_scaffold.py +648 -648
  48. package/scripts/ensure-compose-host-paths.mjs +51 -51
  49. package/scripts/lib/install-identity.mjs +212 -212
  50. package/scripts/lib/workframe-registry.mjs +290 -290
  51. package/scripts/new-project.mjs +68 -68
  52. package/scripts/select_agent_pack.py +31 -31
  53. package/scripts/set-compose-public-url.mjs +92 -92
  54. package/scripts/sync-canonical-to-package.mjs +164 -164
  55. package/shared/WORKFRAME_AGENT_LIBRARY.md +31 -31
  56. package/shared/WORKFRAME_AGENT_OPERATIONS.md +29 -29
  57. package/shared/WORKFRAME_AGENT_PACKS.json +64 -64
  58. package/shared/WORKFRAME_AGENT_PACKS.yaml +20 -20
  59. package/shared/WORKFRAME_CHAT_PERMISSION_MODEL.md +20 -20
  60. package/shared/WORKFRAME_HANDOFF_SCHEMA.md +25 -25
  61. package/shared/WORKFRAME_SKILL_CURATION.md +27 -27
  62. package/workframe-api/Dockerfile +14 -14
  63. package/workframe-api/README.md +24 -24
  64. package/workframe-api/action_proxy.py +131 -131
  65. package/workframe-api/auth_rate_limit.py +49 -49
  66. package/workframe-api/credential_vault.py +445 -445
  67. package/workframe-api/data/avatar-catalog.json +41 -41
  68. package/workframe-api/data/logo-catalog.json +14 -14
  69. package/workframe-api/data/user-avatar-catalog.json +18 -18
  70. package/workframe-api/email_sender.py +220 -220
  71. package/workframe-api/google_auth.py +90 -90
  72. package/workframe-api/install_api.py +359 -359
  73. package/workframe-api/internal_proxy_auth.py +150 -150
  74. package/workframe-api/llm_proxy.py +280 -280
  75. package/workframe-api/oidc_jwt.py +108 -108
  76. package/workframe-api/package.json +12 -12
  77. package/workframe-api/platform_auth.py +194 -194
  78. package/workframe-api/profile_secret_policy.py +86 -86
  79. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  80. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  81. package/workframe-api/public/assets/index-DglUqFB_.js +8 -8
  82. package/workframe-api/public/index.html +12 -12
  83. package/workframe-api/requirements.txt +2 -2
  84. package/workframe-api/server.py +19866 -19774
  85. package/workframe-api/site_meta.py +271 -271
  86. package/workframe-api/stack_config.py +427 -427
  87. package/workframe-api/test_billing_provider.py +24 -0
  88. package/workframe-api/time-bind-chat.py +99 -99
  89. package/workframe-api/turn_credentials.py +226 -226
  90. package/workframe-api/updates.py +506 -506
  91. package/workframe-api/vault_kek.py +159 -159
  92. package/workframe-api/zk_auth.py +633 -633
  93. package/workframe-supervisor/Dockerfile +11 -11
  94. package/workframe-supervisor/profile_secret_policy.py +76 -76
  95. package/workframe-supervisor/server.py +860 -860
  96. package/workframe-ui/docker/nginx.conf +85 -85
  97. package/workframe-ui/public/assets/{arc-BskqxLmE.js → arc-DoYr2T5c.js} +1 -1
  98. package/workframe-ui/public/assets/architecture-7EHR7CIX-CmDi28hh.js +1 -0
  99. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B3eGdqvq.js → architectureDiagram-3BPJPVTR-Clher08G.js} +1 -1
  100. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-CzFZG7uX.js → blockDiagram-GPEHLZMM-Bdw9txrZ.js} +1 -1
  101. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BS-gGyCH.js → c4Diagram-AAUBKEIU-DlI_h_uo.js} +1 -1
  102. package/workframe-ui/public/assets/channel-DZYVoEdD.js +1 -0
  103. package/workframe-ui/public/assets/{chunk-2J33WTMH-Cjvqf7c9.js → chunk-2J33WTMH-ol-uwoeq.js} +1 -1
  104. package/workframe-ui/public/assets/{chunk-3OPIFGDE--eUuL8AK.js → chunk-3OPIFGDE-7eDhBen0.js} +1 -1
  105. package/workframe-ui/public/assets/{chunk-4BX2VUAB-s9lHUFKL.js → chunk-4BX2VUAB-xPORD7Js.js} +1 -1
  106. package/workframe-ui/public/assets/{chunk-55IACEB6-D1McA5nz.js → chunk-55IACEB6-Cwd7dr1z.js} +1 -1
  107. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DIstLfU2.js → chunk-5ZQYHXKU-Ckt4Sjy1.js} +1 -1
  108. package/workframe-ui/public/assets/{chunk-727SXJPM-Cd0GD2CO.js → chunk-727SXJPM-sMk1op_G.js} +1 -1
  109. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-gc5Ra6G4.js → chunk-AQP2D5EJ-CMgSvr6H.js} +1 -1
  110. package/workframe-ui/public/assets/{chunk-BSJP7CBP-Cg5QfihJ.js → chunk-BSJP7CBP-Dx7GN0T4.js} +1 -1
  111. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D_gobbhY.js → chunk-CSCIHK7Q--OCu86u0.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-FMBD7UC4-B6eLY9_d.js → chunk-FMBD7UC4-CejmwJpz.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-KSCS5N6A-BGroAeW2.js → chunk-KSCS5N6A-CvOj2Yvp.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-34xv7WgT.js → chunk-L5ZTLDWV-B0nGBjLU.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-LZXEDZCA-XJBctFZw.js → chunk-LZXEDZCA-Bdzvu227.js} +2 -2
  116. package/workframe-ui/public/assets/{chunk-ND2GUHAM-nTOZpln8.js → chunk-ND2GUHAM-CGR07jTY.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BBrzcaPH.js → chunk-NZK2D7GU-CBojDPNC.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-O5CBEL6O-Cntncx6L.js → chunk-O5CBEL6O-DBin5MPF.js} +1 -1
  119. package/workframe-ui/public/assets/chunk-QZHKN3VN-DwiajAaK.js +1 -0
  120. package/workframe-ui/public/assets/chunk-WU5MYG2G-Bz8vA5s7.js +1 -0
  121. package/workframe-ui/public/assets/{chunk-XPW4576I-DSbtd0cg.js → chunk-XPW4576I-TD7I7pcb.js} +1 -1
  122. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-DQiq8od0.js +1 -0
  123. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-DQiq8od0.js +1 -0
  124. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-lmYstEMQ.js → cose-bilkent-S5V4N54A-Bmm5x5xm.js} +1 -1
  125. package/workframe-ui/public/assets/{dagre-BM42HDAG-Bdkbdul2.js → dagre-BM42HDAG-zU2r0aq2.js} +1 -1
  126. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Clb9kSXe.js → diagram-2AECGRRQ-AL01qukm.js} +1 -1
  127. package/workframe-ui/public/assets/{diagram-5GNKFQAL-BApg0yec.js → diagram-5GNKFQAL-DKi1xSMg.js} +1 -1
  128. package/workframe-ui/public/assets/{diagram-KO2AKTUF-peZjDRV6.js → diagram-KO2AKTUF-DQJDQ-f-.js} +1 -1
  129. package/workframe-ui/public/assets/{diagram-LMA3HP47-DZm_1pjc.js → diagram-LMA3HP47-B1oisGUj.js} +1 -1
  130. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DP_N6fYL.js → diagram-OG6HWLK6-DjpJKffk.js} +1 -1
  131. package/workframe-ui/public/assets/{dist-CqP43hXK.js → dist-Jk20AnQG.js} +1 -1
  132. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-Blfs6kLv.js → erDiagram-TEJ5UH35-Vu8RtLUf.js} +1 -1
  133. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BXUnTe0c.js +1 -0
  134. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CcmMOkwH.js → flowDiagram-I6XJVG4X-CHAXEhbR.js} +1 -1
  135. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ByoO3dF7.js → ganttDiagram-6RSMTGT7-DhiEkfH8.js} +1 -1
  136. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-DCyTpmPs.js → gitGraph-WXDBUCRP-Cuq44XvU.js} +1 -1
  137. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-CvLYB55b.js → gitGraphDiagram-PVQCEYII-B7kgReRt.js} +1 -1
  138. package/workframe-ui/public/assets/index-72-j0sA5.js +129 -0
  139. package/workframe-ui/public/assets/index-auJ0bLMU.css +1 -0
  140. package/workframe-ui/public/assets/{info-J43DQDTF-CfSRIfOI.js → info-J43DQDTF-oV4b3M4f.js} +1 -1
  141. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-DwS44DS6.js → infoDiagram-5YYISTIA-CthN4b6o.js} +1 -1
  142. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-MRlyodyU.js → ishikawaDiagram-YF4QCWOH-oEGS3TKg.js} +1 -1
  143. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-sq7awk87.js → journeyDiagram-JHISSGLW-CXJsWmKF.js} +1 -1
  144. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CH6HbcWu.js → kanban-definition-UN3LZRKU-DTPo8yHW.js} +1 -1
  145. package/workframe-ui/public/assets/{line-DRBjFmUW.js → line-Bm9Pmcnz.js} +1 -1
  146. package/workframe-ui/public/assets/{linear-BR0LunjE.js → linear-Cn-uD6eX.js} +1 -1
  147. package/workframe-ui/public/assets/{mermaid-parser.core-Cl1GjHkT.js → mermaid-parser.core-BBVSTQZ4.js} +2 -2
  148. package/workframe-ui/public/assets/{mermaid.core-DsiKK1kE.js → mermaid.core-DE668nUy.js} +3 -3
  149. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DBvi3OpW.js → mindmap-definition-RKZ34NQL-4qVsiwCf.js} +1 -1
  150. package/workframe-ui/public/assets/{packet-YPE3B663-CPDgtUHg.js → packet-YPE3B663-CqJeoiFG.js} +1 -1
  151. package/workframe-ui/public/assets/{pie-LRSECV5Y-BXJIPPDm.js → pie-LRSECV5Y-DcOqVssg.js} +1 -1
  152. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CZwLEK-o.js → pieDiagram-4H26LBE5-CosMnpAM.js} +1 -1
  153. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-B65bEUIQ.js → quadrantDiagram-W4KKPZXB-DtsnIK98.js} +1 -1
  154. package/workframe-ui/public/assets/{radar-GUYGQ44K-vXHkZzRK.js → radar-GUYGQ44K-BqLpJuWc.js} +1 -1
  155. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-BGWdvDfG.js → requirementDiagram-4Y6WPE33-Pb7cuX8_.js} +1 -1
  156. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-BT2oC8dV.js → sankeyDiagram-5OEKKPKP-BJBlMM1m.js} +1 -1
  157. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-45yj0Wtx.js → sequenceDiagram-3UESZ5HK-i4HnjP39.js} +1 -1
  158. package/workframe-ui/public/assets/{src-D-P8U2PB.js → src-DIhmc_XG.js} +1 -1
  159. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-SBrBJBZG.js → stateDiagram-AJRCARHV-Ba2iClPT.js} +1 -1
  160. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-aUpqRAhy.js +1 -0
  161. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DegmxXXj.js → timeline-definition-PNZ67QCA-CL4X9vSF.js} +1 -1
  162. package/workframe-ui/public/assets/{treeView-BLDUP644-BVy7CsCj.js → treeView-BLDUP644-BJrjnbMj.js} +1 -1
  163. package/workframe-ui/public/assets/{treemap-LRROVOQU-BD5Kb74J.js → treemap-LRROVOQU-CN6H54RF.js} +1 -1
  164. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-hwAkNbl3.js → vennDiagram-CIIHVFJN-Cee6sbUy.js} +1 -1
  165. package/workframe-ui/public/assets/{wardley-L42UT6IY-CBMPT8Y1.js → wardley-L42UT6IY-D0cXq60c.js} +1 -1
  166. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DXqihVlE.js → wardleyDiagram-YWT4CUSO-CaH9XkCx.js} +1 -1
  167. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-Cbb4LBzj.js → xychartDiagram-2RQKCTM6-BEisgFoI.js} +1 -1
  168. package/workframe-ui/public/favicon.svg +7 -7
  169. package/workframe-ui/public/icons.svg +24 -24
  170. package/workframe-ui/public/index.html +47 -50
  171. package/workframe-ui/public/manifest.webmanifest +18 -18
  172. package/workframe-ui/public/workframe-config.json +3 -3
  173. package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +0 -1
  174. package/workframe-ui/public/assets/channel-Baa20pIs.js +0 -1
  175. package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +0 -1
  176. package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +0 -1
  177. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +0 -1
  178. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +0 -1
  179. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +0 -1
  180. package/workframe-ui/public/assets/index-BIfRGM8p.js +0 -133
  181. package/workframe-ui/public/assets/index-DpoUZAxh.css +0 -1
  182. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +0 -1
@@ -0,0 +1,24 @@
1
+ """ponytail self-check: billing provider source of truth = proxy URL, never model-id prefix.
2
+
3
+ Run: python services/workframe-api/test_billing_provider.py
4
+ No framework, no fixtures. Fails fast if the inference regression returns.
5
+ """
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
10
+
11
+ import server # noqa: E402 (module import is side-effect-free; main() is guarded)
12
+
13
+ # The bug case: profile model is google/gemini-2.5-flash but proxy routes via openrouter.
14
+ # The model-id prefix MUST NOT leak as the billing provider.
15
+ assert server._billing_provider_from_block("/internal/llm/openrouter/v1", "custom", "") == "openrouter", \
16
+ "proxy URL segment must win over model-id prefix"
17
+ # Explicit hint honored.
18
+ assert server._billing_provider_from_block("", "", "anthropic") == "anthropic", "hint honored"
19
+ # Empty block defaults to openrouter (no inference).
20
+ assert server._billing_provider_from_block("", "", "") == "openrouter", "empty-block default"
21
+ # cfg_provider honored only when no proxy URL is set.
22
+ assert server._billing_provider_from_block("", "openai", "") == "openai", "cfg_provider honored without proxy"
23
+
24
+ print("billing provider self-check ok")
@@ -1,99 +1,99 @@
1
- #!/usr/bin/env python3
2
- """Time bind + credential lease on running workframe-api (docker exec)."""
3
- from __future__ import annotations
4
-
5
- import sqlite3
6
- import sys
7
- import time
8
-
9
- import server
10
-
11
-
12
- def main() -> int:
13
- conn = sqlite3.connect(str(server.DATA_DIR / "workframe.db"))
14
- conn.row_factory = sqlite3.Row
15
- room = conn.execute(
16
- """
17
- SELECT r.id, r.workspace_id, r.agent_profile_id, ap.slug AS agent_slug
18
- FROM rooms r
19
- JOIN agent_profiles ap ON ap.id = r.agent_profile_id
20
- JOIN room_memberships rm ON rm.room_id = r.id AND rm.status = 'active'
21
- WHERE r.room_type = 'direct' AND r.deleted_at IS NULL
22
- AND ap.slug = 'workframe-agent'
23
- ORDER BY r.updated_at DESC
24
- LIMIT 1
25
- """,
26
- ).fetchone()
27
- if not room:
28
- print("no workframe-agent room", file=sys.stderr)
29
- return 1
30
- user = conn.execute(
31
- """
32
- SELECT u.id FROM users u
33
- JOIN room_memberships rm ON rm.user_id = u.id AND rm.room_id = ?
34
- WHERE u.status = 'active'
35
- LIMIT 1
36
- """,
37
- (str(room["id"]),),
38
- ).fetchone()
39
- conn.close()
40
- if not room or not user:
41
- print("no room/user", file=sys.stderr)
42
- return 1
43
-
44
- user_id = str(user["id"])
45
- room_id = str(room["id"])
46
- workspace_id = str(room["workspace_id"])
47
- payload = {
48
- "room_id": room_id,
49
- "workspace_id": workspace_id,
50
- "source_id": "timing",
51
- "client_id": "timing",
52
- "binding_version": 2,
53
- }
54
-
55
- t_rt = time.perf_counter()
56
- prof = server._resolve_chat_hermes_profile("workframe-agent", user_id, room_id, workspace_id)
57
- rt_ms = (time.perf_counter() - t_rt) * 1000
58
- print(f"resolve_runtime_ms={rt_ms:.0f} profile={prof}")
59
-
60
- provider = server._llm_billing_provider(prof)
61
- t_api = time.perf_counter()
62
- server.ensure_profile_api(prof, user_id, workspace_id)
63
- api_ms = (time.perf_counter() - t_api) * 1000
64
- t_llm = time.perf_counter()
65
- server._overlay_chat_llm_env(prof, user_id, workspace_id, provider)
66
- llm_ms = (time.perf_counter() - t_llm) * 1000
67
- print(f"ensure_profile_api_ms={api_ms:.0f} overlay_llm_ms={llm_ms:.0f}")
68
-
69
- t0 = time.perf_counter()
70
- session = server.profile_chat_session("workframe-agent", payload, user_id=user_id)
71
- session_ms = (time.perf_counter() - t0) * 1000
72
- sid = str(session.get("session_id") or "")
73
- print(f"session_ms={session_ms:.0f} session={sid[:40]}")
74
-
75
- t_hist = time.perf_counter()
76
- history = server.chat_messages(prof, sid)
77
- hist_ms = (time.perf_counter() - t_hist) * 1000
78
- print(f"history_ms={hist_ms:.0f} messages={len(history.get('messages') or [])}")
79
-
80
- healthy = server._profile_api_healthy(prof)
81
- print(f"profile_api_healthy={healthy} port={server._profile_api_port(prof)}")
82
-
83
- run_a = "timing-run-a"
84
- run_b = "timing-run-b"
85
- t1 = time.perf_counter()
86
- server._apply_turn_credential_lease(prof, user_id, workspace_id, provider, run_a)
87
- lease1_ms = (time.perf_counter() - t1) * 1000
88
- t2 = time.perf_counter()
89
- server._apply_turn_credential_lease(prof, user_id, workspace_id, provider, run_b)
90
- lease2_ms = (time.perf_counter() - t2) * 1000
91
- print(f"lease_cold_ms={lease1_ms:.0f} lease_reuse_ms={lease2_ms:.0f} provider={provider}")
92
-
93
- _, model = server._read_model_from_config(prof)
94
- print(f"model={model}")
95
- return 0
96
-
97
-
98
- if __name__ == "__main__":
99
- raise SystemExit(main())
1
+ #!/usr/bin/env python3
2
+ """Time bind + credential lease on running workframe-api (docker exec)."""
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ import sys
7
+ import time
8
+
9
+ import server
10
+
11
+
12
+ def main() -> int:
13
+ conn = sqlite3.connect(str(server.DATA_DIR / "workframe.db"))
14
+ conn.row_factory = sqlite3.Row
15
+ room = conn.execute(
16
+ """
17
+ SELECT r.id, r.workspace_id, r.agent_profile_id, ap.slug AS agent_slug
18
+ FROM rooms r
19
+ JOIN agent_profiles ap ON ap.id = r.agent_profile_id
20
+ JOIN room_memberships rm ON rm.room_id = r.id AND rm.status = 'active'
21
+ WHERE r.room_type = 'direct' AND r.deleted_at IS NULL
22
+ AND ap.slug = 'workframe-agent'
23
+ ORDER BY r.updated_at DESC
24
+ LIMIT 1
25
+ """,
26
+ ).fetchone()
27
+ if not room:
28
+ print("no workframe-agent room", file=sys.stderr)
29
+ return 1
30
+ user = conn.execute(
31
+ """
32
+ SELECT u.id FROM users u
33
+ JOIN room_memberships rm ON rm.user_id = u.id AND rm.room_id = ?
34
+ WHERE u.status = 'active'
35
+ LIMIT 1
36
+ """,
37
+ (str(room["id"]),),
38
+ ).fetchone()
39
+ conn.close()
40
+ if not room or not user:
41
+ print("no room/user", file=sys.stderr)
42
+ return 1
43
+
44
+ user_id = str(user["id"])
45
+ room_id = str(room["id"])
46
+ workspace_id = str(room["workspace_id"])
47
+ payload = {
48
+ "room_id": room_id,
49
+ "workspace_id": workspace_id,
50
+ "source_id": "timing",
51
+ "client_id": "timing",
52
+ "binding_version": 2,
53
+ }
54
+
55
+ t_rt = time.perf_counter()
56
+ prof = server._resolve_chat_hermes_profile("workframe-agent", user_id, room_id, workspace_id)
57
+ rt_ms = (time.perf_counter() - t_rt) * 1000
58
+ print(f"resolve_runtime_ms={rt_ms:.0f} profile={prof}")
59
+
60
+ provider = server._llm_billing_provider(prof)
61
+ t_api = time.perf_counter()
62
+ server.ensure_profile_api(prof, user_id, workspace_id)
63
+ api_ms = (time.perf_counter() - t_api) * 1000
64
+ t_llm = time.perf_counter()
65
+ server._overlay_chat_llm_env(prof, user_id, workspace_id, provider)
66
+ llm_ms = (time.perf_counter() - t_llm) * 1000
67
+ print(f"ensure_profile_api_ms={api_ms:.0f} overlay_llm_ms={llm_ms:.0f}")
68
+
69
+ t0 = time.perf_counter()
70
+ session = server.profile_chat_session("workframe-agent", payload, user_id=user_id)
71
+ session_ms = (time.perf_counter() - t0) * 1000
72
+ sid = str(session.get("session_id") or "")
73
+ print(f"session_ms={session_ms:.0f} session={sid[:40]}")
74
+
75
+ t_hist = time.perf_counter()
76
+ history = server.chat_messages(prof, sid)
77
+ hist_ms = (time.perf_counter() - t_hist) * 1000
78
+ print(f"history_ms={hist_ms:.0f} messages={len(history.get('messages') or [])}")
79
+
80
+ healthy = server._profile_api_healthy(prof)
81
+ print(f"profile_api_healthy={healthy} port={server._profile_api_port(prof)}")
82
+
83
+ run_a = "timing-run-a"
84
+ run_b = "timing-run-b"
85
+ t1 = time.perf_counter()
86
+ server._apply_turn_credential_lease(prof, user_id, workspace_id, provider, run_a)
87
+ lease1_ms = (time.perf_counter() - t1) * 1000
88
+ t2 = time.perf_counter()
89
+ server._apply_turn_credential_lease(prof, user_id, workspace_id, provider, run_b)
90
+ lease2_ms = (time.perf_counter() - t2) * 1000
91
+ print(f"lease_cold_ms={lease1_ms:.0f} lease_reuse_ms={lease2_ms:.0f} provider={provider}")
92
+
93
+ _, model = server._read_model_from_config(prof)
94
+ print(f"model={model}")
95
+ return 0
96
+
97
+
98
+ if __name__ == "__main__":
99
+ raise SystemExit(main())