create-workframe 0.1.12 → 0.1.14
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-BT9iZUSd.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-m1aPUQ_r.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-CaEDd3np.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-DHJhMrIS.js} +1 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-Cdyz9hIV.js} +1 -1
- package/workframe-ui/public/assets/channel-3M69ekE5.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-BbZx76LX.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DcSxjTIP.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-B7F-kTY1.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-BwwEiRTK.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-DAi_NksX.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-BIaF4XCN.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-DjYSk1dG.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-_twnyfgV.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CkNmdy5s.js} +2 -2
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CjLqd-gl.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-_6oNqFjj.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-CeO9Am_D.js} +1 -1
- package/workframe-ui/public/assets/chunk-LZXEDZCA-sRM4YeIe.js +2 -0
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-CH8tMb21.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-DAI1bsII.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-B2e-5a2G.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-BMl9ed8P.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-C08HH6BU.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-DM2-0q37.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BFcOE4Aq.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BFcOE4Aq.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-BnbbewLv.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-C4zmB1Vt.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-n-BJmsiC.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-BzQGQnZi.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-Cx_De-wS.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-CIOI3Lok.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-DkRuPLWY.js} +1 -1
- package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-XhCT1Q-V.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-C3GwhDU1.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-DIVBL6CF.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-CLWH8z2e.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D4h66Vy3.js} +1 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-IW5F-Q6c.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-BjNIbmRM.js} +1 -1
- package/workframe-ui/public/assets/index-DL2vJP4L.css +1 -0
- package/workframe-ui/public/assets/index-ElS_D59P.js +130 -0
- package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CcJspM79.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-DP4xR9jt.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-D4cZb21V.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-DsLuHkIJ.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Wu65nOsR.js} +1 -1
- package/workframe-ui/public/assets/{line-CgQsmU35.js → line-ByBDzRpy.js} +1 -1
- package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-DzXi2vnq.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-CaB-1Ckn.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-D7HTHC17.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BfKsC9aj.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-ClpU98TO.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-KeOyY_-u.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-CUpjnzbs.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-CJdPTszF.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-C9BpandZ.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-CHDZPwry.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-CMJJh91s.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-BsV4GppP.js} +1 -1
- package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-WHks82Up.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-eSX9P8z0.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-FzMK4PkM.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Dg1UkhGo.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-B7yGsBuj.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-j_G2oyQ_.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-BRrT0jqC.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-DEdUqez2.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-C-bBK9Bb.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-DhH5xuR2.js} +1 -1
- package/workframe-ui/public/index.html +11 -7
- 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,166 @@
|
|
|
1
|
+
"""WF-032 extract: Docker socket and gateway container exec helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import http.client
|
|
6
|
+
import json
|
|
7
|
+
import shlex
|
|
8
|
+
import socket
|
|
9
|
+
import urllib.parse
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import updates as stack_updates
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _srv():
|
|
16
|
+
import server as srv
|
|
17
|
+
|
|
18
|
+
return srv
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _UnixHTTPConnection(http.client.HTTPConnection):
|
|
22
|
+
def __init__(self, unix_path: str):
|
|
23
|
+
super().__init__("localhost")
|
|
24
|
+
self.unix_path = unix_path
|
|
25
|
+
|
|
26
|
+
def connect(self) -> None:
|
|
27
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
28
|
+
sock.connect(self.unix_path)
|
|
29
|
+
self.sock = sock
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _docker_request(method: str, path: str, body: dict[str, Any] | None = None) -> tuple[int, Any]:
|
|
33
|
+
if _srv().SECURE_MODE:
|
|
34
|
+
raise RuntimeError("Docker socket access is disabled in SECURE_MODE")
|
|
35
|
+
conn = _UnixHTTPConnection(_srv().DOCKER_SOCK)
|
|
36
|
+
payload = json.dumps(body).encode("utf-8") if body is not None else None
|
|
37
|
+
headers = {"Content-Type": "application/json"} if payload is not None else {}
|
|
38
|
+
conn.request(method, f"/v1.41{path}", body=payload, headers=headers)
|
|
39
|
+
resp = conn.getresponse()
|
|
40
|
+
raw = resp.read()
|
|
41
|
+
conn.close()
|
|
42
|
+
if not raw:
|
|
43
|
+
return resp.status, None
|
|
44
|
+
try:
|
|
45
|
+
return resp.status, json.loads(raw.decode("utf-8", errors="replace"))
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
return resp.status, raw.decode("utf-8", errors="replace")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _docker_exec_detached(container: str, cmd: list[str], acting_profile: str = "") -> tuple[int, str]:
|
|
51
|
+
if _srv()._exec_targets_runtime_profile_secrets(cmd, acting_profile):
|
|
52
|
+
return 1, "blocked: runtime profile credential paths are not readable via gateway exec"
|
|
53
|
+
create_status, create_data = _docker_request(
|
|
54
|
+
"POST",
|
|
55
|
+
f"/containers/{urllib.parse.quote(container, safe='')}/exec",
|
|
56
|
+
{
|
|
57
|
+
"AttachStdout": True,
|
|
58
|
+
"AttachStderr": True,
|
|
59
|
+
"Tty": False,
|
|
60
|
+
"Cmd": cmd,
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
if create_status >= 300 or not isinstance(create_data, dict) or not create_data.get("Id"):
|
|
64
|
+
return create_status, f"exec create failed: {create_data}"
|
|
65
|
+
exec_id = str(create_data["Id"])
|
|
66
|
+
start_status, _start_data = _docker_request(
|
|
67
|
+
"POST",
|
|
68
|
+
f"/exec/{urllib.parse.quote(exec_id, safe='')}/start",
|
|
69
|
+
{"Detach": True, "Tty": False},
|
|
70
|
+
)
|
|
71
|
+
if start_status >= 300:
|
|
72
|
+
return start_status, "exec start failed"
|
|
73
|
+
return 0, ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _docker_exec(container: str, cmd: list[str], acting_profile: str = "") -> tuple[int, str]:
|
|
77
|
+
if _srv()._exec_targets_runtime_profile_secrets(cmd, acting_profile):
|
|
78
|
+
return 1, "blocked: runtime profile credential paths are not readable via gateway exec"
|
|
79
|
+
create_status, create_data = _docker_request(
|
|
80
|
+
"POST",
|
|
81
|
+
f"/containers/{urllib.parse.quote(container, safe='')}/exec",
|
|
82
|
+
{
|
|
83
|
+
"AttachStdout": True,
|
|
84
|
+
"AttachStderr": True,
|
|
85
|
+
"Tty": False,
|
|
86
|
+
"Cmd": cmd,
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
if create_status >= 300 or not isinstance(create_data, dict) or not create_data.get("Id"):
|
|
90
|
+
return create_status, f"exec create failed: {create_data}"
|
|
91
|
+
exec_id = str(create_data["Id"])
|
|
92
|
+
start_status, start_data = _docker_request(
|
|
93
|
+
"POST",
|
|
94
|
+
f"/exec/{urllib.parse.quote(exec_id, safe='')}/start",
|
|
95
|
+
{"Detach": False, "Tty": False},
|
|
96
|
+
)
|
|
97
|
+
out = start_data if isinstance(start_data, str) else json.dumps(start_data or {})
|
|
98
|
+
if isinstance(out, str) and out:
|
|
99
|
+
try:
|
|
100
|
+
raw = out.encode("latin-1", errors="ignore")
|
|
101
|
+
buf = bytearray()
|
|
102
|
+
i = 0
|
|
103
|
+
# Docker non-TTY exec streams are 8-byte header framed.
|
|
104
|
+
while i + 8 <= len(raw):
|
|
105
|
+
size = int.from_bytes(raw[i + 4 : i + 8], "big")
|
|
106
|
+
i += 8
|
|
107
|
+
if size < 0 or i + size > len(raw):
|
|
108
|
+
break
|
|
109
|
+
buf.extend(raw[i : i + size])
|
|
110
|
+
i += size
|
|
111
|
+
if buf:
|
|
112
|
+
out = buf.decode("utf-8", errors="replace")
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
inspect_status, inspect_data = _docker_request("GET", f"/exec/{urllib.parse.quote(exec_id, safe='')}/json")
|
|
116
|
+
if inspect_status >= 300 or not isinstance(inspect_data, dict):
|
|
117
|
+
return start_status, out
|
|
118
|
+
exit_raw = inspect_data.get("ExitCode")
|
|
119
|
+
exit_code = 0 if exit_raw in (0, "0") else int(exit_raw or 1)
|
|
120
|
+
return exit_code, out
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _gateway_container_exec(cmd: list[str]) -> tuple[int, str]:
|
|
124
|
+
"""Run a command in the gateway container — via supervisor in SECURE_MODE."""
|
|
125
|
+
if _srv().SECURE_MODE:
|
|
126
|
+
return _srv()._supervisor_container_exec(cmd)
|
|
127
|
+
return _docker_exec(_srv().GATEWAY_CONTAINER_NAME, cmd)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _gateway_container_exec_detached(cmd: list[str]) -> tuple[int, str]:
|
|
131
|
+
"""Detached gateway exec — long-running jobs survive after exec returns."""
|
|
132
|
+
if _srv().SECURE_MODE:
|
|
133
|
+
return _srv()._supervisor_container_exec(cmd, detach=True)
|
|
134
|
+
return _docker_exec_detached(_srv().GATEWAY_CONTAINER_NAME, cmd)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _profile_home_container(profile: str) -> str:
|
|
138
|
+
return f"/opt/data/profiles/{_srv().resolve_hermes_profile(profile)}"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _gateway_exec(profile: str, args: list[str]) -> tuple[int, str]:
|
|
142
|
+
"""Run hermes CLI in the gateway container with specialist profile home."""
|
|
143
|
+
prof = _srv().resolve_hermes_profile(profile)
|
|
144
|
+
if _srv().SECURE_MODE:
|
|
145
|
+
return _srv()._supervisor_gateway_exec(prof, args)
|
|
146
|
+
cli = ["/opt/hermes/bin/hermes", "-p", prof, *args]
|
|
147
|
+
if prof == _srv()._primary_profile():
|
|
148
|
+
return _docker_exec(_srv().GATEWAY_CONTAINER_NAME, cli, acting_profile=prof)
|
|
149
|
+
home = _profile_home_container(prof)
|
|
150
|
+
inner = " ".join(shlex.quote(part) for part in cli)
|
|
151
|
+
shell = (
|
|
152
|
+
f"export HERMES_HOME={shlex.quote(home)} HOME={shlex.quote(home)}; "
|
|
153
|
+
f"cd {shlex.quote(home)}; {inner}"
|
|
154
|
+
)
|
|
155
|
+
return _docker_exec(_srv().GATEWAY_CONTAINER_NAME, ["sh", "-lc", shell], acting_profile=prof)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _hermes_agent_version() -> str:
|
|
159
|
+
"""Native Hermes semver from `hermes --version` inside the gateway container."""
|
|
160
|
+
try:
|
|
161
|
+
code, out = _gateway_exec(_srv()._primary_profile(), ["--version"])
|
|
162
|
+
if code != 0:
|
|
163
|
+
return ""
|
|
164
|
+
return stack_updates.parse_hermes_version_output(out)
|
|
165
|
+
except Exception: # noqa: BLE001
|
|
166
|
+
return ""
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""WF-032 extract: doctor audit/repair for agent DM runtime profiles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sqlite3
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _srv():
|
|
10
|
+
import server as srv
|
|
11
|
+
|
|
12
|
+
return srv
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def iter_agent_dm_runtime_slots(conn: sqlite3.Connection) -> list[dict[str, str]]:
|
|
16
|
+
"""All (workspace, agent, user) slots that need a u-* runtime for agent DM bind."""
|
|
17
|
+
rows = conn.execute(
|
|
18
|
+
"""
|
|
19
|
+
SELECT r.workspace_id, r.agent_profile_id, rm.user_id
|
|
20
|
+
FROM rooms r
|
|
21
|
+
JOIN room_memberships rm ON rm.room_id = r.id AND rm.deleted_at IS NULL
|
|
22
|
+
WHERE r.deleted_at IS NULL
|
|
23
|
+
AND r.room_type = 'direct'
|
|
24
|
+
AND r.agent_profile_id IS NOT NULL
|
|
25
|
+
AND TRIM(r.agent_profile_id) != ''
|
|
26
|
+
""",
|
|
27
|
+
).fetchall()
|
|
28
|
+
seen: set[tuple[str, str, str]] = set()
|
|
29
|
+
slots: list[dict[str, str]] = []
|
|
30
|
+
for row in rows:
|
|
31
|
+
workspace_id = str(row["workspace_id"] or "").strip()
|
|
32
|
+
agent_ref = str(row["agent_profile_id"] or "").strip()
|
|
33
|
+
user_id = str(row["user_id"] or "").strip()
|
|
34
|
+
if not workspace_id or not agent_ref or not user_id:
|
|
35
|
+
continue
|
|
36
|
+
key = (workspace_id, agent_ref, user_id)
|
|
37
|
+
if key in seen:
|
|
38
|
+
continue
|
|
39
|
+
seen.add(key)
|
|
40
|
+
agent_row = _srv()._lookup_agent_profile(conn, workspace_id, agent_ref)
|
|
41
|
+
if not agent_row:
|
|
42
|
+
continue
|
|
43
|
+
template = str(agent_row["slug"] or "").strip()
|
|
44
|
+
if not template:
|
|
45
|
+
continue
|
|
46
|
+
runtime = _srv()._runtime_profile_slug(user_id, template)
|
|
47
|
+
slots.append({
|
|
48
|
+
"workspace_id": workspace_id,
|
|
49
|
+
"agent_profile_id": str(agent_row["id"]),
|
|
50
|
+
"user_id": user_id,
|
|
51
|
+
"template": template,
|
|
52
|
+
"runtime": runtime,
|
|
53
|
+
"on_disk": _srv()._runtime_profile_on_disk(runtime),
|
|
54
|
+
})
|
|
55
|
+
return slots
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def doctor_audit_agent_dm_runtimes() -> dict[str, Any]:
|
|
59
|
+
"""Report missing u-* runtimes for agent DM rooms — no mutations."""
|
|
60
|
+
conn = _srv()._workframe_db()
|
|
61
|
+
try:
|
|
62
|
+
slots = iter_agent_dm_runtime_slots(conn)
|
|
63
|
+
finally:
|
|
64
|
+
conn.close()
|
|
65
|
+
missing = [s for s in slots if not s["on_disk"]]
|
|
66
|
+
return {
|
|
67
|
+
"ok": True,
|
|
68
|
+
"total": len(slots),
|
|
69
|
+
"present": len(slots) - len(missing),
|
|
70
|
+
"missing": missing,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def doctor_repair_agent_dm_runtimes(*, repair: bool = True) -> dict[str, Any]:
|
|
75
|
+
"""Explicit repair — provision missing u-* runtimes for agent DM rooms."""
|
|
76
|
+
conn = _srv()._workframe_db()
|
|
77
|
+
try:
|
|
78
|
+
slots = iter_agent_dm_runtime_slots(conn)
|
|
79
|
+
finally:
|
|
80
|
+
conn.close()
|
|
81
|
+
missing = [s for s in slots if not s["on_disk"]]
|
|
82
|
+
if not repair:
|
|
83
|
+
return doctor_audit_agent_dm_runtimes()
|
|
84
|
+
repaired: list[dict[str, str]] = []
|
|
85
|
+
failed: list[dict[str, str]] = []
|
|
86
|
+
for slot in missing:
|
|
87
|
+
runtime = slot["runtime"]
|
|
88
|
+
try:
|
|
89
|
+
_srv().ensure_runtime_profile(
|
|
90
|
+
runtime,
|
|
91
|
+
slot["template"],
|
|
92
|
+
slot["user_id"],
|
|
93
|
+
slot["workspace_id"],
|
|
94
|
+
)
|
|
95
|
+
if _srv()._runtime_profile_on_disk(runtime):
|
|
96
|
+
repaired.append(slot)
|
|
97
|
+
else:
|
|
98
|
+
failed.append({**slot, "error": "runtime_still_missing"})
|
|
99
|
+
except Exception as exc: # noqa: BLE001
|
|
100
|
+
failed.append({**slot, "error": str(exc)})
|
|
101
|
+
still_missing = [s for s in missing if not _srv()._runtime_profile_on_disk(s["runtime"])]
|
|
102
|
+
return {
|
|
103
|
+
"ok": not failed and not still_missing,
|
|
104
|
+
"total": len(slots),
|
|
105
|
+
"missing_before": len(missing),
|
|
106
|
+
"repaired": repaired,
|
|
107
|
+
"failed": failed,
|
|
108
|
+
"still_missing": still_missing,
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Workframe domain model (WF-039)."""
|
|
2
|
+
|
|
3
|
+
from domain.entities import (
|
|
4
|
+
ActorType,
|
|
5
|
+
AgentIdentity,
|
|
6
|
+
Cell,
|
|
7
|
+
CredentialPolicy,
|
|
8
|
+
CredentialRef,
|
|
9
|
+
DeploymentMode,
|
|
10
|
+
FundingSource,
|
|
11
|
+
Grant,
|
|
12
|
+
GrantCapability,
|
|
13
|
+
Lease,
|
|
14
|
+
LeaseStatus,
|
|
15
|
+
Run,
|
|
16
|
+
RunEvent,
|
|
17
|
+
RunStatus,
|
|
18
|
+
RunSurface,
|
|
19
|
+
RuntimeBinding,
|
|
20
|
+
RuntimeBindingStatus,
|
|
21
|
+
RuntimeKind,
|
|
22
|
+
User,
|
|
23
|
+
Workspace,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ActorType",
|
|
28
|
+
"AgentIdentity",
|
|
29
|
+
"Cell",
|
|
30
|
+
"CredentialPolicy",
|
|
31
|
+
"CredentialRef",
|
|
32
|
+
"DeploymentMode",
|
|
33
|
+
"FundingSource",
|
|
34
|
+
"Grant",
|
|
35
|
+
"GrantCapability",
|
|
36
|
+
"Lease",
|
|
37
|
+
"LeaseStatus",
|
|
38
|
+
"Run",
|
|
39
|
+
"RunEvent",
|
|
40
|
+
"RunStatus",
|
|
41
|
+
"RunSurface",
|
|
42
|
+
"RuntimeBinding",
|
|
43
|
+
"RuntimeBindingStatus",
|
|
44
|
+
"RuntimeKind",
|
|
45
|
+
"User",
|
|
46
|
+
"Workspace",
|
|
47
|
+
]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Workframe domain entities — stable vocabulary for authority, runs, and credentials.
|
|
2
|
+
|
|
3
|
+
Referenced by WF-009 RunAuthorityGate, WF-NS-P2 runs tables, and public glossary (WF-NS-P0).
|
|
4
|
+
Implementation in server.py still uses legacy shapes; migrate incrementally (WF-032).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict, dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Literal, Mapping, TypeVar
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", bound="DomainEntity")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DomainEntity:
|
|
18
|
+
"""Minimal JSON round-trip for dataclass entities."""
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict[str, Any]:
|
|
21
|
+
return _encode_value(asdict(self))
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_dict(cls: type[T], data: Mapping[str, Any]) -> T:
|
|
25
|
+
return cls(**dict(data))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _encode_value(value: Any) -> Any:
|
|
29
|
+
if isinstance(value, datetime):
|
|
30
|
+
return value.isoformat()
|
|
31
|
+
if isinstance(value, Enum):
|
|
32
|
+
return value.value
|
|
33
|
+
if isinstance(value, dict):
|
|
34
|
+
return {k: _encode_value(v) for k, v in value.items()}
|
|
35
|
+
if isinstance(value, list):
|
|
36
|
+
return [_encode_value(v) for v in value]
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DeploymentMode(str, Enum):
|
|
41
|
+
SINGLE_USER_LOCAL = "single_user_local"
|
|
42
|
+
TRUSTED_TEAM = "trusted_team"
|
|
43
|
+
PUBLIC_MULTI_USER = "public_multi_user"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ActorType(str, Enum):
|
|
47
|
+
USER = "user"
|
|
48
|
+
AGENT = "agent"
|
|
49
|
+
SYSTEM = "system"
|
|
50
|
+
WEBHOOK = "webhook"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RunStatus(str, Enum):
|
|
54
|
+
PENDING = "pending"
|
|
55
|
+
AUTHORIZED = "authorized"
|
|
56
|
+
RUNNING = "running"
|
|
57
|
+
COMPLETED = "completed"
|
|
58
|
+
FAILED = "failed"
|
|
59
|
+
CANCELLED = "cancelled"
|
|
60
|
+
DENIED = "denied"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RunSurface(str, Enum):
|
|
64
|
+
AGENT_RAIL = "agent_rail"
|
|
65
|
+
CHAT = "chat"
|
|
66
|
+
FILES = "files"
|
|
67
|
+
BROWSER = "browser"
|
|
68
|
+
ACTIVITY = "activity"
|
|
69
|
+
KANBAN = "kanban"
|
|
70
|
+
CRON = "cron"
|
|
71
|
+
SLASH = "slash"
|
|
72
|
+
WEBHOOK = "webhook"
|
|
73
|
+
MENTION = "mention"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RuntimeKind(str, Enum):
|
|
77
|
+
HERMES_MANAGED = "hermes_managed"
|
|
78
|
+
CANDIDATE = "candidate"
|
|
79
|
+
ADAPTER = "adapter"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RuntimeBindingStatus(str, Enum):
|
|
83
|
+
ACTIVE = "active"
|
|
84
|
+
DETACHED = "detached"
|
|
85
|
+
CANDIDATE = "candidate"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class FundingSource(str, Enum):
|
|
89
|
+
BYOK = "byok"
|
|
90
|
+
COMPANY = "company"
|
|
91
|
+
HYBRID = "hybrid"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CredentialPolicy(str, Enum):
|
|
95
|
+
BYOK = "byok"
|
|
96
|
+
COMPANY = "company"
|
|
97
|
+
USER_ONLY = "user_only"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class LeaseStatus(str, Enum):
|
|
101
|
+
ACTIVE = "active"
|
|
102
|
+
EXPIRED = "expired"
|
|
103
|
+
REVOKED = "revoked"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class GrantCapability(str, Enum):
|
|
107
|
+
LLM_TURN = "llm_turn"
|
|
108
|
+
TOOL_CALL = "tool_call"
|
|
109
|
+
FILE_READ = "file_read"
|
|
110
|
+
FILE_WRITE = "file_write"
|
|
111
|
+
FILE_DELETE = "file_delete"
|
|
112
|
+
RUNTIME_EXEC = "runtime_exec"
|
|
113
|
+
BROKER_EGRESS = "broker_egress"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class Cell(DomainEntity):
|
|
118
|
+
"""One Workframe install/deployment unit (generated project root)."""
|
|
119
|
+
|
|
120
|
+
cell_id: str
|
|
121
|
+
install_root: str
|
|
122
|
+
manifest_path: str
|
|
123
|
+
package_name: str
|
|
124
|
+
package_version: str
|
|
125
|
+
deployment_mode: DeploymentMode
|
|
126
|
+
created_at: datetime
|
|
127
|
+
git_ref: str | None = None
|
|
128
|
+
packed_artifact_digest: str | None = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class User(DomainEntity):
|
|
133
|
+
user_id: str
|
|
134
|
+
workspace_id: str
|
|
135
|
+
email: str
|
|
136
|
+
display_name: str
|
|
137
|
+
role: Literal["owner", "admin", "member", "guest"] = "member"
|
|
138
|
+
created_at: datetime | None = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class Workspace(DomainEntity):
|
|
143
|
+
workspace_id: str
|
|
144
|
+
cell_id: str
|
|
145
|
+
slug: str
|
|
146
|
+
name: str
|
|
147
|
+
created_at: datetime | None = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class AgentIdentity(DomainEntity):
|
|
152
|
+
"""Persistent Workframe agent — distinct from Hermes profile (WF-013 seam)."""
|
|
153
|
+
|
|
154
|
+
agent_id: str
|
|
155
|
+
workspace_id: str
|
|
156
|
+
template_slug: str
|
|
157
|
+
display_name: str
|
|
158
|
+
is_native: bool = False
|
|
159
|
+
created_at: datetime | None = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class RuntimeBinding(DomainEntity):
|
|
164
|
+
"""Links AgentIdentity to a concrete runtime (Hermes profile today)."""
|
|
165
|
+
|
|
166
|
+
binding_id: str
|
|
167
|
+
agent_id: str
|
|
168
|
+
runtime_kind: RuntimeKind
|
|
169
|
+
profile_slug: str
|
|
170
|
+
status: RuntimeBindingStatus = RuntimeBindingStatus.ACTIVE
|
|
171
|
+
detected_at: datetime | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True)
|
|
175
|
+
class Run(DomainEntity):
|
|
176
|
+
"""Unit of authority, execution, and economics (WF-009, WF-NS-P2)."""
|
|
177
|
+
|
|
178
|
+
run_id: str
|
|
179
|
+
workspace_id: str
|
|
180
|
+
surface: RunSurface
|
|
181
|
+
actor_type: ActorType
|
|
182
|
+
actor_id: str
|
|
183
|
+
triggering_user_id: str
|
|
184
|
+
agent_id: str
|
|
185
|
+
runtime_binding_id: str
|
|
186
|
+
status: RunStatus
|
|
187
|
+
payer_user_id: str
|
|
188
|
+
funding_source: FundingSource
|
|
189
|
+
room_id: str | None = None
|
|
190
|
+
card_id: str | None = None
|
|
191
|
+
engine: str = "hermes"
|
|
192
|
+
runtime: str = "hermes_managed"
|
|
193
|
+
risk_tier: Literal["low", "medium", "high"] = "low"
|
|
194
|
+
budget_usd: float | None = None
|
|
195
|
+
deny_reason: str | None = None
|
|
196
|
+
created_at: datetime | None = None
|
|
197
|
+
started_at: datetime | None = None
|
|
198
|
+
ended_at: datetime | None = None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass(frozen=True)
|
|
202
|
+
class RunEvent(DomainEntity):
|
|
203
|
+
event_id: str
|
|
204
|
+
run_id: str
|
|
205
|
+
event_type: str
|
|
206
|
+
payload: dict[str, Any] = field(default_factory=dict)
|
|
207
|
+
created_at: datetime | None = None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True)
|
|
211
|
+
class CredentialRef(DomainEntity):
|
|
212
|
+
"""Vault pointer — never holds raw secret material."""
|
|
213
|
+
|
|
214
|
+
ref_id: str
|
|
215
|
+
workspace_id: str
|
|
216
|
+
provider: str
|
|
217
|
+
policy: CredentialPolicy
|
|
218
|
+
user_id: str | None = None
|
|
219
|
+
binding_id: str | None = None
|
|
220
|
+
label_redacted: str | None = None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(frozen=True)
|
|
224
|
+
class Lease(DomainEntity):
|
|
225
|
+
"""Scoped credential lease for one run (maps to turn_credential_leases / wf_rt_*)."""
|
|
226
|
+
|
|
227
|
+
lease_id: str
|
|
228
|
+
run_id: str
|
|
229
|
+
workspace_id: str
|
|
230
|
+
payer_user_id: str
|
|
231
|
+
provider: str
|
|
232
|
+
profile_slug: str
|
|
233
|
+
credential_ref_id: str | None
|
|
234
|
+
status: LeaseStatus
|
|
235
|
+
expires_at: datetime
|
|
236
|
+
revoked_at: datetime | None = None
|
|
237
|
+
created_at: datetime | None = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True)
|
|
241
|
+
class Grant(DomainEntity):
|
|
242
|
+
"""Capability granted to a run before execution (RunAuthorityGate output)."""
|
|
243
|
+
|
|
244
|
+
grant_id: str
|
|
245
|
+
run_id: str
|
|
246
|
+
capability: GrantCapability
|
|
247
|
+
scope: dict[str, Any] = field(default_factory=dict)
|
|
248
|
+
granted_by: Literal["run_authority_gate", "cell_authority_gate", "policy"] = (
|
|
249
|
+
"run_authority_gate"
|
|
250
|
+
)
|
|
251
|
+
expires_at: datetime | None = None
|
|
252
|
+
created_at: datetime | None = None
|