create-workframe 0.1.12 → 0.1.13
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/bin/create-workframe.js +2 -2
- package/bin/workframe.js +130 -2
- package/docs/security.md +2 -2
- package/package.json +1 -1
- package/scripts/bundle-workframe-ui.mjs +9 -0
- package/scripts/sync-canonical-to-package.mjs +1 -0
- package/scripts/verify-public-deploy.sh +65 -3
- package/workframe-api/action_proxy.py +23 -17
- package/workframe-api/activity_feed.py +798 -0
- package/workframe-api/api_meta.py +157 -0
- package/workframe-api/auth_gate.py +566 -0
- package/workframe-api/avatar_registry.py +343 -0
- package/workframe-api/broker_audit.py +164 -0
- package/workframe-api/cell_authority.py +231 -0
- package/workframe-api/chat_bind.py +319 -0
- package/workframe-api/chat_sessions.py +509 -0
- package/workframe-api/chat_stream.py +694 -0
- package/workframe-api/cleanup_dogfood_smoke.py +252 -0
- package/workframe-api/credential_broker.py +162 -0
- package/workframe-api/credential_resolve.py +202 -0
- package/workframe-api/credential_store.py +245 -0
- package/workframe-api/crew_registry.py +250 -0
- package/workframe-api/db_schema.py +755 -0
- package/workframe-api/docker_gateway.py +166 -0
- package/workframe-api/doctor_runtime.py +109 -0
- package/workframe-api/domain/__init__.py +47 -0
- package/workframe-api/domain/entities.py +252 -0
- package/workframe-api/domain/schema/workframe-domain.schema.json +278 -0
- package/workframe-api/egress_policy.py +42 -0
- package/workframe-api/handler_modules/__init__.py +17 -0
- package/workframe-api/handler_modules/handler_admin.py +542 -0
- package/workframe-api/handler_modules/handler_auth.py +512 -0
- package/workframe-api/handler_modules/handler_chat.py +641 -0
- package/workframe-api/handler_modules/handler_install.py +178 -0
- package/workframe-api/handler_modules/handler_provider.py +325 -0
- package/workframe-api/handler_modules/handler_workspace.py +1420 -0
- package/workframe-api/health_monitor.py +80 -0
- package/workframe-api/hermes_admin.py +756 -0
- package/workframe-api/hermes_profiles.py +1328 -0
- package/workframe-api/install_api.py +123 -0
- package/workframe-api/kanban_cron.py +473 -0
- package/workframe-api/lane_bindings.py +423 -0
- package/workframe-api/llm_proxy.py +17 -43
- package/workframe-api/mention_helpers.py +180 -0
- package/workframe-api/mention_invoke.py +431 -0
- package/workframe-api/model_surface.py +1139 -0
- package/workframe-api/oauth_pending.py +85 -0
- package/workframe-api/oauth_redirect.py +381 -0
- package/workframe-api/package.json +2 -2
- package/workframe-api/profile_api_lifecycle.py +66 -0
- package/workframe-api/profile_gateway.py +436 -0
- package/workframe-api/provider_bindings.py +673 -0
- package/workframe-api/provider_bootstrap.py +347 -0
- package/workframe-api/provider_catalog.py +180 -0
- package/workframe-api/rooms.py +1613 -0
- package/workframe-api/route_registry.py +331 -191
- package/workframe-api/run-typecheck.mjs +2 -1
- package/workframe-api/run_authority.py +287 -0
- package/workframe-api/run_ledger.py +470 -0
- package/workframe-api/run_surface_wiring.py +344 -0
- package/workframe-api/runtime_cohort.py +752 -0
- package/workframe-api/runtime_tokens.py +127 -0
- package/workframe-api/server.py +1453 -19513
- package/workframe-api/snapshot_feed.py +49 -0
- package/workframe-api/stack_config.py +33 -1
- package/workframe-api/supervisor_client.py +154 -0
- package/workframe-api/test_api_meta_build_stamp.py +34 -0
- package/workframe-api/test_cell_authority.py +79 -0
- package/workframe-api/test_chat_bind.py +48 -0
- package/workframe-api/test_credential_lease_matrix.py +171 -0
- package/workframe-api/test_credential_resolve.py +51 -0
- package/workframe-api/test_credential_store.py +27 -0
- package/workframe-api/test_dogfood_flows.py +346 -0
- package/workframe-api/test_domain_entities.py +152 -0
- package/workframe-api/test_exception_hygiene.py +12 -0
- package/workframe-api/test_hermes_admin.py +72 -0
- package/workframe-api/test_install_wizard_resume.py +78 -0
- package/workframe-api/test_local_bootstrap.py +67 -0
- package/workframe-api/test_mention_helpers.py +35 -0
- package/workframe-api/test_mention_invoke.py +26 -0
- package/workframe-api/test_model_surface_consistency.py +1 -0
- package/workframe-api/test_oauth_pending.py +41 -0
- package/workframe-api/test_provider_bindings.py +52 -0
- package/workframe-api/test_provider_catalog.py +28 -0
- package/workframe-api/test_route_registry.py +171 -35
- package/workframe-api/test_run_authority.py +112 -0
- package/workframe-api/test_run_ledger.py +157 -0
- package/workframe-api/test_run_surface_wiring.py +130 -0
- package/workframe-api/test_runtime_cohort.py +85 -0
- package/workframe-api/test_secure_mode_docker_boundary.py +39 -0
- package/workframe-api/test_server_reexports.py +99 -0
- package/workframe-api/test_stack_config_install_smtp.py +7 -0
- package/workframe-api/turn_credentials.py +28 -13
- package/workframe-api/turn_overlay.py +715 -0
- package/workframe-api/updates.py +16 -1
- package/workframe-api/user_prefs.py +387 -0
- package/workframe-api/wf032_extract_remaining_handlers.py +417 -0
- package/workframe-api/workspace_bootstrap.py +536 -0
- package/workframe-api/workspace_files.py +344 -0
- package/workframe-api/workspace_messaging.py +206 -0
- package/workframe-api/zk_auth.py +3 -2
- package/workframe-supervisor/server.py +10 -1
- package/workframe-supervisor/test_supervisor_negative.py +75 -0
- package/workframe-ui/public/assets/{arc-B0OFRGmJ.js → arc-aUUffclm.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-BSUP4S8J.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-DU6oaqIb.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-D2p8BU6-.js} +1 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-ouwyPmTJ.js} +1 -1
- package/workframe-ui/public/assets/channel-DOunZZ0X.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-DsE7U5dj.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DM0kkZ9h.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-CQ8mAyXp.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-DN1BDtbH.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-BzK2KqOt.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-C3AxtOi9.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-4mVwEFuD.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-23kN2q0A.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CPBKVW-L.js} +2 -2
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CKqcYq7K.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-JRQnhxQE.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-BLZrdIwa.js} +1 -1
- package/workframe-ui/public/assets/chunk-LZXEDZCA-DisG0XJT.js +2 -0
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-ClAOxArS.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-XUE1aNkm.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-DDN-Ifon.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-EVQ0VMd5.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-k_-ZsLOS.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-B4_iYZ6r.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-W9WVQsby.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-W9WVQsby.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-Cy1HhS7j.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-Bjal81Ie.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-v5Gdvzp_.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-DuyRdfmY.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-WwWCGbIW.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-DTYwZALT.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-B46kBIqE.js} +1 -1
- package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-mQWmB3z6.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-__40xO-y.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-Cm1uRTxU.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-Cwj6dVUz.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D_IOSwAS.js} +1 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-C1vGecee.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-DMTxFJxn.js} +1 -1
- package/workframe-ui/public/assets/index-BdiM4-lI.js +130 -0
- package/workframe-ui/public/assets/index-Fnad47vV.css +1 -0
- package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CwjmyPmD.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-BGzh7ZL2.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-BLXdnR_5.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-BK3behMe.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Bx172cCl.js} +1 -1
- package/workframe-ui/public/assets/{line-CgQsmU35.js → line-BTCEHb7l.js} +1 -1
- package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-CkbydpnK.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-DvrtArpk.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-yj_1x_qj.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BXbMltQ0.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-9pVCBQ7S.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-CdqoWesP.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-kGs-VfXd.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-D9D8gQw5.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-d4zMZpQS.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-BI7EQ9zc.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-Bcz19jQB.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-iQiCEfYp.js} +1 -1
- package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-D6mvlP96.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-CxaTXs02.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CXikj9xi.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Bej-EAnf.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-C7PnnzwX.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-CqISLmkO.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-eJMTTEW-.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-D4sMroBF.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-DC3DCUs7.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-BeMaM6Aq.js} +1 -1
- package/workframe-ui/public/index.html +7 -5
- package/workframe-ui/public/workframe-build.json +5 -0
- package/workframe-ui/public/assets/architecture-7EHR7CIX-Dwwi9yA0.js +0 -1
- package/workframe-ui/public/assets/channel-fNHbDpV4.js +0 -1
- package/workframe-ui/public/assets/chunk-LZXEDZCA-DMfdMUwz.js +0 -2
- package/workframe-ui/public/assets/chunk-QZHKN3VN-ewTgEQK8.js +0 -1
- package/workframe-ui/public/assets/chunk-WU5MYG2G-D4Tg-A0l.js +0 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CFAgBpEl.js +0 -1
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CFAgBpEl.js +0 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-BIJTP5k-.js +0 -1
- package/workframe-ui/public/assets/index-8CuZDEIG.css +0 -1
- package/workframe-ui/public/assets/index-xS9lFekI.js +0 -129
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CMLuNyeC.js +0 -1
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
"""WF-032 extract: workspace bootstrap and onboarding helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sqlite3
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _srv():
|
|
14
|
+
import server as srv
|
|
15
|
+
|
|
16
|
+
return srv
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _provision_invited_member_agent_runtimes(workspace_id: str, user_id: str) -> None:
|
|
20
|
+
"""Provision u-* runtimes for workspace agents when a member joins."""
|
|
21
|
+
workspace_id = str(workspace_id or "").strip()
|
|
22
|
+
user_id = str(user_id or "").strip()
|
|
23
|
+
if not workspace_id or not user_id:
|
|
24
|
+
return
|
|
25
|
+
try:
|
|
26
|
+
conn = _srv()._workframe_db()
|
|
27
|
+
rows = conn.execute(
|
|
28
|
+
"""
|
|
29
|
+
SELECT id FROM agent_profiles
|
|
30
|
+
WHERE workspace_id = ? AND deleted_at IS NULL AND status = 'available'
|
|
31
|
+
""",
|
|
32
|
+
(workspace_id,),
|
|
33
|
+
).fetchall()
|
|
34
|
+
conn.close()
|
|
35
|
+
except Exception: # noqa: BLE001
|
|
36
|
+
return
|
|
37
|
+
for row in rows:
|
|
38
|
+
_provision_agent_dm_runtimes(workspace_id, str(row["id"]), [user_id])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _provision_agent_dm_runtimes(
|
|
42
|
+
workspace_id: str,
|
|
43
|
+
agent_profile_id: str,
|
|
44
|
+
user_ids: list[str],
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Create u-* runtime profiles when an agent DM room is created — install/onboarding only, not bind."""
|
|
47
|
+
workspace_id = str(workspace_id or "").strip()
|
|
48
|
+
agent_profile_id = str(agent_profile_id or "").strip()
|
|
49
|
+
if not workspace_id or not agent_profile_id:
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
conn = _srv()._workframe_db()
|
|
53
|
+
agent_row = _srv()._lookup_agent_profile(conn, workspace_id, agent_profile_id)
|
|
54
|
+
conn.close()
|
|
55
|
+
except Exception: # noqa: BLE001
|
|
56
|
+
return
|
|
57
|
+
if not agent_row:
|
|
58
|
+
return
|
|
59
|
+
template = str(agent_row["slug"] or "").strip()
|
|
60
|
+
if not template:
|
|
61
|
+
return
|
|
62
|
+
try:
|
|
63
|
+
template = _srv().resolve_validated_profile(template)
|
|
64
|
+
except ValueError as exc:
|
|
65
|
+
print(
|
|
66
|
+
f"[provision_agent_dm_runtimes] skip {agent_profile_id}: invalid template {template!r}: {exc}",
|
|
67
|
+
flush=True,
|
|
68
|
+
)
|
|
69
|
+
return
|
|
70
|
+
for uid in user_ids:
|
|
71
|
+
user = str(uid or "").strip()
|
|
72
|
+
if not user:
|
|
73
|
+
continue
|
|
74
|
+
runtime = _srv()._runtime_profile_slug(user, template)
|
|
75
|
+
if _srv()._runtime_profile_on_disk(runtime):
|
|
76
|
+
continue
|
|
77
|
+
try:
|
|
78
|
+
_srv().ensure_runtime_profile(runtime, template, user, workspace_id)
|
|
79
|
+
except Exception as exc: # noqa: BLE001
|
|
80
|
+
print(
|
|
81
|
+
f"[provision_agent_dm_runtimes] failed {runtime} for {user}: {exc}",
|
|
82
|
+
flush=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def bootstrap_agent_dm_lane(
|
|
87
|
+
user_id: str,
|
|
88
|
+
workspace_id: str,
|
|
89
|
+
template_slug: str,
|
|
90
|
+
*,
|
|
91
|
+
model: str = "",
|
|
92
|
+
soul: str = "",
|
|
93
|
+
bind_session: bool = True,
|
|
94
|
+
room_name: str = "",
|
|
95
|
+
role: str = "",
|
|
96
|
+
tagline: str = "",
|
|
97
|
+
created_by: str = "",
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
"""Provision u-* runtime, model/proxy, DM room, and optional session bind — install/create-agent parity."""
|
|
100
|
+
user_id = str(user_id or "").strip()
|
|
101
|
+
workspace_id = str(workspace_id or "").strip()
|
|
102
|
+
if not _srv()._native_profile_present():
|
|
103
|
+
_srv()._ensure_native_hermes_profile()
|
|
104
|
+
template = _srv().resolve_validated_profile(str(template_slug or "").strip())
|
|
105
|
+
if not user_id or not workspace_id:
|
|
106
|
+
return {"ok": False, "error": "user_id and workspace_id required"}
|
|
107
|
+
runtime = _srv()._runtime_profile_slug(user_id, template)
|
|
108
|
+
steps: list[dict[str, Any]] = []
|
|
109
|
+
try:
|
|
110
|
+
_srv().ensure_runtime_profile(runtime, template, user_id, workspace_id)
|
|
111
|
+
steps.append({"step": "runtime_profile", "ok": True, "runtime": runtime})
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
steps.append({"step": "runtime_profile", "ok": False, "error": str(exc)})
|
|
114
|
+
return {"ok": False, "template": template, "runtime": runtime, "steps": steps, "error": str(exc)}
|
|
115
|
+
|
|
116
|
+
model_id = str(model or "").strip()
|
|
117
|
+
if model_id:
|
|
118
|
+
try:
|
|
119
|
+
applied = _srv().hermes_model_set(runtime, model_id, user_id, workspace_id)
|
|
120
|
+
steps.append(
|
|
121
|
+
{
|
|
122
|
+
"step": "set_runtime_model",
|
|
123
|
+
"ok": bool(applied.get("ok")),
|
|
124
|
+
"model": model_id,
|
|
125
|
+
**({"error": applied.get("error")} if not applied.get("ok") else {}),
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
if not applied.get("ok"):
|
|
129
|
+
return {
|
|
130
|
+
"ok": False,
|
|
131
|
+
"template": template,
|
|
132
|
+
"runtime": runtime,
|
|
133
|
+
"steps": steps,
|
|
134
|
+
"error": str(applied.get("error") or "set_runtime_model_failed"),
|
|
135
|
+
}
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
steps.append({"step": "set_runtime_model", "ok": False, "error": str(exc)})
|
|
138
|
+
return {
|
|
139
|
+
"ok": False,
|
|
140
|
+
"template": template,
|
|
141
|
+
"runtime": runtime,
|
|
142
|
+
"steps": steps,
|
|
143
|
+
"error": str(exc),
|
|
144
|
+
}
|
|
145
|
+
else:
|
|
146
|
+
try:
|
|
147
|
+
_srv()._bootstrap_profile_providers(runtime, user_id, workspace_id)
|
|
148
|
+
steps.append({"step": "bootstrap_providers", "ok": True})
|
|
149
|
+
user_primary, user_chain = _srv()._read_user_llm_prefs(user_id)
|
|
150
|
+
if user_primary:
|
|
151
|
+
applied = _srv().hermes_model_set(runtime, user_primary, user_id, workspace_id)
|
|
152
|
+
steps.append(
|
|
153
|
+
{
|
|
154
|
+
"step": "user_default_model",
|
|
155
|
+
"ok": bool(applied.get("ok")),
|
|
156
|
+
"model": user_primary,
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
if user_chain:
|
|
160
|
+
chained = _srv().hermes_fallback_chain_set(runtime, user_chain, user_id=user_id)
|
|
161
|
+
steps.append(
|
|
162
|
+
{
|
|
163
|
+
"step": "user_default_fallbacks",
|
|
164
|
+
"ok": bool(chained.get("ok")),
|
|
165
|
+
"count": len(user_chain),
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
except Exception as exc:
|
|
169
|
+
steps.append({"step": "bootstrap_providers", "ok": False, "error": str(exc)})
|
|
170
|
+
|
|
171
|
+
soul_text = str(soul or "").strip()
|
|
172
|
+
identity_role = str(role or "").strip()
|
|
173
|
+
identity_tagline = str(tagline or "").strip()
|
|
174
|
+
identity_name = room_name.strip()
|
|
175
|
+
if soul_text or identity_name or identity_role or identity_tagline:
|
|
176
|
+
_srv()._seed_native_user_overlay(
|
|
177
|
+
runtime,
|
|
178
|
+
template,
|
|
179
|
+
display_name=identity_name,
|
|
180
|
+
role=identity_role,
|
|
181
|
+
tagline=identity_tagline,
|
|
182
|
+
user_soul=soul_text,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
gateway_started = False
|
|
186
|
+
last_gw_exc: Exception | None = None
|
|
187
|
+
for attempt in range(3):
|
|
188
|
+
try:
|
|
189
|
+
_srv().ensure_profile_api(runtime, user_id, workspace_id, bootstrap_providers=False)
|
|
190
|
+
step_row: dict[str, Any] = {"step": "start_gateway", "ok": True, "runtime": runtime}
|
|
191
|
+
if attempt:
|
|
192
|
+
step_row["retried"] = True
|
|
193
|
+
steps.append(step_row)
|
|
194
|
+
gateway_started = True
|
|
195
|
+
break
|
|
196
|
+
except Exception as exc:
|
|
197
|
+
last_gw_exc = exc
|
|
198
|
+
if attempt < 2:
|
|
199
|
+
time.sleep(1.5)
|
|
200
|
+
if not gateway_started:
|
|
201
|
+
steps.append({"step": "start_gateway", "ok": False, "error": str(last_gw_exc)})
|
|
202
|
+
return {
|
|
203
|
+
"ok": False,
|
|
204
|
+
"template": template,
|
|
205
|
+
"runtime": runtime,
|
|
206
|
+
"steps": steps,
|
|
207
|
+
"error": str(last_gw_exc),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
display = _srv()._runtime_display_label(user_id, template, workspace_id)
|
|
211
|
+
_srv()._ensure_workspace_agent_profile_row(
|
|
212
|
+
template,
|
|
213
|
+
workspace_id=workspace_id,
|
|
214
|
+
display_name=room_name.strip() or display,
|
|
215
|
+
role=identity_role,
|
|
216
|
+
tagline=identity_tagline,
|
|
217
|
+
is_native=_srv()._is_native_profile(template),
|
|
218
|
+
)
|
|
219
|
+
status, room_payload = _srv()._create_room(
|
|
220
|
+
workspace_id,
|
|
221
|
+
{
|
|
222
|
+
"name": room_name.strip() or display,
|
|
223
|
+
"room_type": "direct",
|
|
224
|
+
"agent_profile_id": template,
|
|
225
|
+
"member_user_ids": [user_id],
|
|
226
|
+
},
|
|
227
|
+
created_by or user_id,
|
|
228
|
+
)
|
|
229
|
+
room = room_payload.get("room") if isinstance(room_payload.get("room"), dict) else {}
|
|
230
|
+
room_id = str(room.get("id") or "").strip()
|
|
231
|
+
dm_error = ""
|
|
232
|
+
if not room_id:
|
|
233
|
+
dm_error = str(
|
|
234
|
+
room_payload.get("error")
|
|
235
|
+
or room_payload.get("detail")
|
|
236
|
+
or ("dm_room_failed" if status not in (200, 201) else "dm_room_missing_id"),
|
|
237
|
+
).strip()
|
|
238
|
+
steps.append(
|
|
239
|
+
{
|
|
240
|
+
"step": "dm_room",
|
|
241
|
+
"ok": status in (200, 201) and bool(room_id),
|
|
242
|
+
"room_id": room_id,
|
|
243
|
+
"status": status,
|
|
244
|
+
**({"error": dm_error} if dm_error else {}),
|
|
245
|
+
},
|
|
246
|
+
)
|
|
247
|
+
if not room_id:
|
|
248
|
+
return {
|
|
249
|
+
"ok": False,
|
|
250
|
+
"template": template,
|
|
251
|
+
"runtime": runtime,
|
|
252
|
+
"steps": steps,
|
|
253
|
+
"error": dm_error or "dm_room_failed",
|
|
254
|
+
"room_error": room_payload,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
session_id = ""
|
|
258
|
+
if bind_session:
|
|
259
|
+
bind_payload: dict[str, Any] = {
|
|
260
|
+
"workspace_id": workspace_id,
|
|
261
|
+
"source_id": "ui",
|
|
262
|
+
"client_id": "default",
|
|
263
|
+
"new_session": False,
|
|
264
|
+
}
|
|
265
|
+
if template == _srv()._primary_profile():
|
|
266
|
+
bind_payload["binding_version"] = 2
|
|
267
|
+
try:
|
|
268
|
+
bound = _srv().room_chat_bind(room_id, bind_payload, user_id)
|
|
269
|
+
session_id = str(bound.get("session_id") or "").strip()
|
|
270
|
+
steps.append({"step": "room_chat_bind", "ok": bool(session_id), "session_id": session_id})
|
|
271
|
+
except Exception as exc:
|
|
272
|
+
steps.append({"step": "room_chat_bind", "ok": False, "error": str(exc)})
|
|
273
|
+
|
|
274
|
+
bind_ok = not bind_session or bool(session_id)
|
|
275
|
+
steps_ok = all(s.get("ok") is not False for s in steps)
|
|
276
|
+
return {
|
|
277
|
+
"ok": bind_ok and steps_ok,
|
|
278
|
+
"template": template,
|
|
279
|
+
"runtime": runtime,
|
|
280
|
+
"room_id": room_id,
|
|
281
|
+
"room": room,
|
|
282
|
+
"session_id": session_id,
|
|
283
|
+
"steps": steps,
|
|
284
|
+
**({"error": "room_chat_bind_failed"} if bind_session and not session_id else {}),
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _onboard_workspace_member_rooms(
|
|
289
|
+
conn: sqlite3.Connection,
|
|
290
|
+
workspace_id: str,
|
|
291
|
+
user_id: str,
|
|
292
|
+
*,
|
|
293
|
+
inviter_user_id: str | None = None,
|
|
294
|
+
) -> dict[str, Any]:
|
|
295
|
+
room_join = _join_workspace_default_room(
|
|
296
|
+
conn,
|
|
297
|
+
workspace_id,
|
|
298
|
+
user_id,
|
|
299
|
+
inviter_user_id=inviter_user_id,
|
|
300
|
+
)
|
|
301
|
+
spaces_joined = _srv()._add_workspace_member_to_space_rooms(conn, workspace_id, user_id)
|
|
302
|
+
return {**room_join, "spaces_joined": spaces_joined}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _join_workspace_default_room(
|
|
306
|
+
conn: sqlite3.Connection,
|
|
307
|
+
workspace_id: str,
|
|
308
|
+
user_id: str,
|
|
309
|
+
*,
|
|
310
|
+
inviter_user_id: str | None = None,
|
|
311
|
+
) -> dict[str, Any]:
|
|
312
|
+
room = _srv()._default_workspace_room(conn, workspace_id)
|
|
313
|
+
if not room:
|
|
314
|
+
return {"room_id": None, "joined": False}
|
|
315
|
+
room_id = str(room["id"])
|
|
316
|
+
joined = _srv()._ensure_user_in_room(conn, room_id, user_id)
|
|
317
|
+
inviter_joined = False
|
|
318
|
+
if inviter_user_id and inviter_user_id != user_id:
|
|
319
|
+
inviter_joined = _srv()._ensure_user_in_room(conn, room_id, inviter_user_id)
|
|
320
|
+
return {
|
|
321
|
+
"room_id": room_id,
|
|
322
|
+
"room_slug": str(room["slug"]),
|
|
323
|
+
"joined": joined,
|
|
324
|
+
"inviter_joined": inviter_joined,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _ensure_user_dm_room(
|
|
329
|
+
conn: sqlite3.Connection,
|
|
330
|
+
workspace_id: str,
|
|
331
|
+
user_a: str,
|
|
332
|
+
user_b: str,
|
|
333
|
+
) -> dict[str, Any]:
|
|
334
|
+
"""Idempotent DM between two workspace users (no agent)."""
|
|
335
|
+
left_id, right_id = sorted([str(user_a).strip(), str(user_b).strip()])
|
|
336
|
+
if not left_id or not right_id or left_id == right_id:
|
|
337
|
+
return {"room_id": None, "created": False}
|
|
338
|
+
existing = conn.execute(
|
|
339
|
+
"""
|
|
340
|
+
SELECT id FROM rooms
|
|
341
|
+
WHERE workspace_id = ?
|
|
342
|
+
AND room_type = 'direct'
|
|
343
|
+
AND deleted_at IS NULL
|
|
344
|
+
AND agent_profile_id IS NULL
|
|
345
|
+
AND EXISTS (
|
|
346
|
+
SELECT 1 FROM room_memberships rm
|
|
347
|
+
WHERE rm.room_id = rooms.id AND rm.deleted_at IS NULL AND rm.user_id = ?
|
|
348
|
+
)
|
|
349
|
+
AND EXISTS (
|
|
350
|
+
SELECT 1 FROM room_memberships rm
|
|
351
|
+
WHERE rm.room_id = rooms.id AND rm.deleted_at IS NULL AND rm.user_id = ?
|
|
352
|
+
)
|
|
353
|
+
AND NOT EXISTS (
|
|
354
|
+
SELECT 1 FROM room_memberships rm
|
|
355
|
+
WHERE rm.room_id = rooms.id AND rm.deleted_at IS NULL AND rm.user_id NOT IN (?, ?)
|
|
356
|
+
)
|
|
357
|
+
LIMIT 1
|
|
358
|
+
""",
|
|
359
|
+
(workspace_id, left_id, right_id, left_id, right_id),
|
|
360
|
+
).fetchone()
|
|
361
|
+
if existing:
|
|
362
|
+
return {"room_id": str(existing["id"]), "created": False}
|
|
363
|
+
now = str(int(time.time()))
|
|
364
|
+
room_id = str(uuid.uuid4())
|
|
365
|
+
slug = _srv()._normalize_room_slug(f"dm-{left_id}-{right_id}", "dm")
|
|
366
|
+
conn.execute(
|
|
367
|
+
"""
|
|
368
|
+
INSERT INTO rooms
|
|
369
|
+
(id, workspace_id, agent_profile_id, name, slug, topic, room_type, status, created_by, created_at, updated_at)
|
|
370
|
+
VALUES (?, ?, NULL, ?, ?, '', 'direct', 'active', ?, ?, ?)
|
|
371
|
+
""",
|
|
372
|
+
(room_id, workspace_id, "DM", slug, left_id, now, now),
|
|
373
|
+
)
|
|
374
|
+
for uid in (left_id, right_id):
|
|
375
|
+
_srv()._ensure_user_in_room(conn, room_id, uid)
|
|
376
|
+
return {"room_id": room_id, "created": True}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _promote_workspace_owner_if_unclaimed(conn: sqlite3.Connection, workspace_id: str, user_id: str) -> bool:
|
|
380
|
+
if not _srv()._install_window_open():
|
|
381
|
+
return False
|
|
382
|
+
row = conn.execute(
|
|
383
|
+
"SELECT owner_id FROM workspaces WHERE id = ? AND deleted_at IS NULL",
|
|
384
|
+
(workspace_id,),
|
|
385
|
+
).fetchone()
|
|
386
|
+
if not row:
|
|
387
|
+
return False
|
|
388
|
+
if str(row["owner_id"] or "").strip():
|
|
389
|
+
return False
|
|
390
|
+
now = str(int(time.time()))
|
|
391
|
+
conn.execute(
|
|
392
|
+
"UPDATE workspaces SET owner_id = ?, updated_at = ? WHERE id = ?",
|
|
393
|
+
(user_id, now, workspace_id),
|
|
394
|
+
)
|
|
395
|
+
mem = conn.execute(
|
|
396
|
+
"SELECT id FROM workspace_memberships WHERE workspace_id = ? AND user_id = ? AND deleted_at IS NULL",
|
|
397
|
+
(workspace_id, user_id),
|
|
398
|
+
).fetchone()
|
|
399
|
+
if mem:
|
|
400
|
+
conn.execute(
|
|
401
|
+
"UPDATE workspace_memberships SET role = ?, updated_at = ? WHERE id = ?",
|
|
402
|
+
("owner", now, mem["id"]),
|
|
403
|
+
)
|
|
404
|
+
else:
|
|
405
|
+
conn.execute(
|
|
406
|
+
"""
|
|
407
|
+
INSERT INTO workspace_memberships
|
|
408
|
+
(id, workspace_id, user_id, role, status, created_at, updated_at)
|
|
409
|
+
VALUES (?,?,?,?,?,?,?)
|
|
410
|
+
""",
|
|
411
|
+
(str(uuid.uuid4()), workspace_id, user_id, "owner", "active", now, now),
|
|
412
|
+
)
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _sync_workspace_home_room(conn: sqlite3.Connection, workspace_id: str) -> None:
|
|
417
|
+
"""Keep slug=general space aligned with workspace display_name, tagline, avatar."""
|
|
418
|
+
workspace_id = str(workspace_id or "").strip()
|
|
419
|
+
if not workspace_id:
|
|
420
|
+
return
|
|
421
|
+
ws_row = conn.execute(
|
|
422
|
+
"SELECT display_name, avatar_url, settings_json FROM workspaces WHERE id = ? AND deleted_at IS NULL",
|
|
423
|
+
(workspace_id,),
|
|
424
|
+
).fetchone()
|
|
425
|
+
if not ws_row:
|
|
426
|
+
return
|
|
427
|
+
settings = _srv()._parse_workspace_settings(ws_row)
|
|
428
|
+
name = str(ws_row["display_name"] or "").strip() or "Workspace"
|
|
429
|
+
tagline = str(settings.get("tagline") or "").strip()
|
|
430
|
+
avatar = str(ws_row["avatar_url"] or "").strip() or None
|
|
431
|
+
now = str(int(time.time()))
|
|
432
|
+
room = conn.execute(
|
|
433
|
+
"""
|
|
434
|
+
SELECT id FROM rooms
|
|
435
|
+
WHERE workspace_id = ? AND slug = 'general' AND deleted_at IS NULL
|
|
436
|
+
""",
|
|
437
|
+
(workspace_id,),
|
|
438
|
+
).fetchone()
|
|
439
|
+
if room:
|
|
440
|
+
conn.execute(
|
|
441
|
+
"""
|
|
442
|
+
UPDATE rooms
|
|
443
|
+
SET name = ?, topic = ?, avatar_url = COALESCE(?, avatar_url), updated_at = ?
|
|
444
|
+
WHERE id = ?
|
|
445
|
+
""",
|
|
446
|
+
(name, tagline, avatar, now, str(room["id"])),
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _ensure_default_workspace() -> bool:
|
|
451
|
+
"""Idempotent first-run DB seed: Workframe workspace + native agent + General room."""
|
|
452
|
+
project_name = str(os.environ.get("WORKFRAME_PROJECT", "Workframe") or "Workframe").strip() or "Workframe"
|
|
453
|
+
native_slug = str(_srv().NATIVE_PROFILE or "workframe-agent").strip() or "workframe-agent"
|
|
454
|
+
native_display = f"{project_name} Agent"
|
|
455
|
+
conn = _srv()._workframe_db()
|
|
456
|
+
try:
|
|
457
|
+
ws = conn.execute(
|
|
458
|
+
"SELECT id FROM workspaces WHERE slug = 'default' AND deleted_at IS NULL",
|
|
459
|
+
).fetchone()
|
|
460
|
+
now = str(int(time.time()))
|
|
461
|
+
if not ws:
|
|
462
|
+
ws_id = str(uuid.uuid4())
|
|
463
|
+
settings = json.dumps({
|
|
464
|
+
"credential_mode": "byok",
|
|
465
|
+
"admin_onboarding_done": False,
|
|
466
|
+
"admin_integrations_done": False,
|
|
467
|
+
})
|
|
468
|
+
ws_logo = _srv()._pick_logo_url() or None
|
|
469
|
+
conn.execute(
|
|
470
|
+
"""
|
|
471
|
+
INSERT INTO workspaces (
|
|
472
|
+
id, slug, display_name, description, owner_id, status,
|
|
473
|
+
settings_json, avatar_url, created_at, updated_at
|
|
474
|
+
) VALUES (?, 'default', ?, '', '', 'active', ?, ?, ?, ?)
|
|
475
|
+
""",
|
|
476
|
+
(ws_id, project_name, settings, ws_logo, now, now),
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
ws_id = str(ws["id"])
|
|
480
|
+
ws_row = conn.execute(
|
|
481
|
+
"SELECT display_name, avatar_url, settings_json FROM workspaces WHERE id = ?",
|
|
482
|
+
(ws_id,),
|
|
483
|
+
).fetchone()
|
|
484
|
+
settings = _srv()._parse_workspace_settings(ws_row) if ws_row else {}
|
|
485
|
+
home_name = str(ws_row["display_name"] or "").strip() if ws_row else project_name
|
|
486
|
+
home_name = home_name or project_name
|
|
487
|
+
home_tagline = str(settings.get("tagline") or "").strip()
|
|
488
|
+
home_avatar = str(ws_row["avatar_url"] or "").strip() if ws_row else ""
|
|
489
|
+
if not home_avatar:
|
|
490
|
+
home_avatar = _srv()._pick_logo_url() or ""
|
|
491
|
+
room = conn.execute(
|
|
492
|
+
"""
|
|
493
|
+
SELECT id FROM rooms
|
|
494
|
+
WHERE workspace_id = ? AND slug = 'general' AND deleted_at IS NULL
|
|
495
|
+
""",
|
|
496
|
+
(ws_id,),
|
|
497
|
+
).fetchone()
|
|
498
|
+
if not room:
|
|
499
|
+
conn.execute(
|
|
500
|
+
"""
|
|
501
|
+
INSERT INTO rooms (
|
|
502
|
+
id, workspace_id, name, slug, topic, avatar_url, room_type, status, created_at, updated_at
|
|
503
|
+
) VALUES (?, ?, ?, 'general', ?, ?, 'channel', 'active', ?, ?)
|
|
504
|
+
""",
|
|
505
|
+
(
|
|
506
|
+
str(uuid.uuid4()),
|
|
507
|
+
ws_id,
|
|
508
|
+
home_name,
|
|
509
|
+
home_tagline,
|
|
510
|
+
home_avatar or None,
|
|
511
|
+
now,
|
|
512
|
+
now,
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
_sync_workspace_home_room(conn, ws_id)
|
|
516
|
+
conn.commit()
|
|
517
|
+
finally:
|
|
518
|
+
conn.close()
|
|
519
|
+
_srv()._ensure_workspace_agent_profile_row(
|
|
520
|
+
native_slug,
|
|
521
|
+
display_name=native_display,
|
|
522
|
+
role="native",
|
|
523
|
+
is_native=True,
|
|
524
|
+
)
|
|
525
|
+
return True
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _bootstrap_after_setup(agent_personality: str = "") -> dict[str, Any]:
|
|
529
|
+
"""ponytail: verify native gateway after setup; optional SOUL overlay from onboarding text."""
|
|
530
|
+
profile = _srv()._primary_profile()
|
|
531
|
+
result: dict[str, Any] = {"profile": profile}
|
|
532
|
+
personality = str(agent_personality or "").strip()
|
|
533
|
+
if personality:
|
|
534
|
+
_srv()._seed_native_user_overlay(profile, profile, user_soul=personality)
|
|
535
|
+
result["soul_seeded"] = True
|
|
536
|
+
return result
|