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,755 @@
|
|
|
1
|
+
"""Sprint G–K workframe.db schema migrations (WF-032)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sqlite3
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
OpenDb = Callable[[], sqlite3.Connection]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ensure_agent_runs_schema(open_db: OpenDb) -> None:
|
|
12
|
+
"""Create or migrate the Workframe agent_runs tracking table."""
|
|
13
|
+
conn = open_db()
|
|
14
|
+
try:
|
|
15
|
+
tables = {
|
|
16
|
+
row["name"]
|
|
17
|
+
for row in conn.execute(
|
|
18
|
+
"SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
19
|
+
).fetchall()
|
|
20
|
+
}
|
|
21
|
+
if "agent_runs" not in tables:
|
|
22
|
+
conn.execute(
|
|
23
|
+
"""
|
|
24
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
agent_profile_id TEXT NOT NULL,
|
|
27
|
+
room_id TEXT DEFAULT NULL,
|
|
28
|
+
triggered_by_user_id TEXT DEFAULT NULL,
|
|
29
|
+
session_id TEXT DEFAULT NULL,
|
|
30
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
31
|
+
trigger_source TEXT NOT NULL DEFAULT 'manual',
|
|
32
|
+
prompt_text TEXT DEFAULT '',
|
|
33
|
+
result_summary TEXT DEFAULT '',
|
|
34
|
+
model_provider TEXT DEFAULT '',
|
|
35
|
+
model_name TEXT DEFAULT '',
|
|
36
|
+
input_tokens INTEGER DEFAULT 0,
|
|
37
|
+
output_tokens INTEGER DEFAULT 0,
|
|
38
|
+
cost_usd REAL DEFAULT 0.0,
|
|
39
|
+
error_message TEXT DEFAULT '',
|
|
40
|
+
started_at TEXT NOT NULL,
|
|
41
|
+
completed_at TEXT DEFAULT NULL,
|
|
42
|
+
created_at TEXT NOT NULL,
|
|
43
|
+
updated_at TEXT NOT NULL,
|
|
44
|
+
FOREIGN KEY (agent_profile_id) REFERENCES agent_profiles (id),
|
|
45
|
+
FOREIGN KEY (room_id) REFERENCES rooms (id),
|
|
46
|
+
FOREIGN KEY (triggered_by_user_id) REFERENCES users (id)
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
49
|
+
)
|
|
50
|
+
conn.execute(
|
|
51
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_agent "
|
|
52
|
+
"ON agent_runs(agent_profile_id, created_at DESC)"
|
|
53
|
+
)
|
|
54
|
+
conn.execute(
|
|
55
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_room "
|
|
56
|
+
"ON agent_runs(room_id, created_at DESC) WHERE room_id IS NOT NULL"
|
|
57
|
+
)
|
|
58
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs(status)")
|
|
59
|
+
conn.execute(
|
|
60
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_session "
|
|
61
|
+
"ON agent_runs(session_id) WHERE session_id IS NOT NULL"
|
|
62
|
+
)
|
|
63
|
+
conn.execute(
|
|
64
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_triggered_by "
|
|
65
|
+
"ON agent_runs(triggered_by_user_id) WHERE triggered_by_user_id IS NOT NULL"
|
|
66
|
+
)
|
|
67
|
+
conn.execute(
|
|
68
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_started_at "
|
|
69
|
+
"ON agent_runs(started_at DESC)"
|
|
70
|
+
)
|
|
71
|
+
conn.execute(
|
|
72
|
+
"INSERT OR IGNORE INTO schema_migrations "
|
|
73
|
+
"(version, description, applied_at) VALUES "
|
|
74
|
+
"('4', 'agent_runs tracking table with trigger, prompt, result, and cost fields', datetime('now'))"
|
|
75
|
+
)
|
|
76
|
+
conn.commit()
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
cols = {c["name"] for c in conn.execute("PRAGMA table_info(agent_runs)").fetchall()}
|
|
80
|
+
column_specs = {
|
|
81
|
+
"trigger_source": "TEXT NOT NULL DEFAULT 'manual'",
|
|
82
|
+
"prompt_text": "TEXT DEFAULT ''",
|
|
83
|
+
"result_summary": "TEXT DEFAULT ''",
|
|
84
|
+
"model_provider": "TEXT DEFAULT ''",
|
|
85
|
+
"model_name": "TEXT DEFAULT ''",
|
|
86
|
+
"input_tokens": "INTEGER DEFAULT 0",
|
|
87
|
+
"output_tokens": "INTEGER DEFAULT 0",
|
|
88
|
+
"cost_usd": "REAL DEFAULT 0.0",
|
|
89
|
+
"error_message": "TEXT DEFAULT ''",
|
|
90
|
+
"started_at": "TEXT DEFAULT ''",
|
|
91
|
+
"completed_at": "TEXT DEFAULT NULL",
|
|
92
|
+
"created_at": "TEXT DEFAULT ''",
|
|
93
|
+
"updated_at": "TEXT DEFAULT ''",
|
|
94
|
+
}
|
|
95
|
+
for column, spec in column_specs.items():
|
|
96
|
+
if column not in cols:
|
|
97
|
+
conn.execute(f"ALTER TABLE agent_runs ADD COLUMN {column} {spec}")
|
|
98
|
+
conn.execute(
|
|
99
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_agent "
|
|
100
|
+
"ON agent_runs(agent_profile_id, created_at DESC)"
|
|
101
|
+
)
|
|
102
|
+
conn.execute(
|
|
103
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_room "
|
|
104
|
+
"ON agent_runs(room_id, created_at DESC) WHERE room_id IS NOT NULL"
|
|
105
|
+
)
|
|
106
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs(status)")
|
|
107
|
+
conn.execute(
|
|
108
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_session "
|
|
109
|
+
"ON agent_runs(session_id) WHERE session_id IS NOT NULL"
|
|
110
|
+
)
|
|
111
|
+
conn.execute(
|
|
112
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_triggered_by "
|
|
113
|
+
"ON agent_runs(triggered_by_user_id) WHERE triggered_by_user_id IS NOT NULL"
|
|
114
|
+
)
|
|
115
|
+
conn.execute(
|
|
116
|
+
"CREATE INDEX IF NOT EXISTS idx_agent_runs_started_at "
|
|
117
|
+
"ON agent_runs(started_at DESC)"
|
|
118
|
+
)
|
|
119
|
+
conn.execute(
|
|
120
|
+
"INSERT OR IGNORE INTO schema_migrations "
|
|
121
|
+
"(version, description, applied_at) VALUES "
|
|
122
|
+
"('4', 'agent_runs tracking table with trigger, prompt, result, and cost fields', datetime('now'))"
|
|
123
|
+
)
|
|
124
|
+
conn.commit()
|
|
125
|
+
finally:
|
|
126
|
+
conn.close()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def ensure_invites_schema(open_db: OpenDb) -> None:
|
|
130
|
+
"""Create workspace_invites table if not exists."""
|
|
131
|
+
conn = open_db()
|
|
132
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS workspace_invites (
|
|
133
|
+
id TEXT PRIMARY KEY,
|
|
134
|
+
workspace_id TEXT NOT NULL,
|
|
135
|
+
email TEXT NOT NULL,
|
|
136
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
137
|
+
token_hash TEXT NOT NULL,
|
|
138
|
+
invited_by_user_id TEXT,
|
|
139
|
+
accepted_by_user_id TEXT,
|
|
140
|
+
expires_at TEXT NOT NULL,
|
|
141
|
+
created_at TEXT NOT NULL,
|
|
142
|
+
accepted_at TEXT,
|
|
143
|
+
deleted_at TEXT
|
|
144
|
+
)""")
|
|
145
|
+
conn.commit()
|
|
146
|
+
conn.close()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def ensure_memory_schema(open_db: OpenDb) -> None:
|
|
150
|
+
"""Create memory_items table if not exists."""
|
|
151
|
+
conn = open_db()
|
|
152
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS memory_items (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
workspace_id TEXT NOT NULL,
|
|
155
|
+
agent_profile_id TEXT,
|
|
156
|
+
scope TEXT NOT NULL CHECK(scope IN ('workspace', 'agent_profile', 'room', 'user_agent')),
|
|
157
|
+
room_id TEXT,
|
|
158
|
+
user_id TEXT,
|
|
159
|
+
content TEXT NOT NULL,
|
|
160
|
+
source_message_id TEXT,
|
|
161
|
+
visibility TEXT NOT NULL DEFAULT 'shared' CHECK(visibility IN ('shared', 'private')),
|
|
162
|
+
created_by_user_id TEXT,
|
|
163
|
+
created_at TEXT NOT NULL,
|
|
164
|
+
updated_at TEXT NOT NULL,
|
|
165
|
+
deleted_at TEXT
|
|
166
|
+
)""")
|
|
167
|
+
conn.commit()
|
|
168
|
+
conn.close()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def ensure_policies_schema(open_db: OpenDb) -> None:
|
|
172
|
+
"""Create workspace_provider_policies table if not exists."""
|
|
173
|
+
conn = open_db()
|
|
174
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS workspace_provider_policies (
|
|
175
|
+
id TEXT PRIMARY KEY,
|
|
176
|
+
workspace_id TEXT NOT NULL,
|
|
177
|
+
provider TEXT NOT NULL,
|
|
178
|
+
allow_user_credentials INTEGER NOT NULL DEFAULT 1,
|
|
179
|
+
allow_workspace_fallback INTEGER NOT NULL DEFAULT 1,
|
|
180
|
+
default_model TEXT,
|
|
181
|
+
created_at TEXT NOT NULL,
|
|
182
|
+
updated_at TEXT NOT NULL,
|
|
183
|
+
UNIQUE(workspace_id, provider)
|
|
184
|
+
)""")
|
|
185
|
+
conn.commit()
|
|
186
|
+
conn.close()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def ensure_user_prefs_schema(open_db: OpenDb) -> None:
|
|
190
|
+
"""Add users.current_workspace_id for active workspace selection."""
|
|
191
|
+
conn = open_db()
|
|
192
|
+
try:
|
|
193
|
+
cols = {c[1] for c in conn.execute("PRAGMA table_info(users)").fetchall()}
|
|
194
|
+
if "current_workspace_id" not in cols:
|
|
195
|
+
conn.execute("ALTER TABLE users ADD COLUMN current_workspace_id TEXT DEFAULT NULL")
|
|
196
|
+
conn.commit()
|
|
197
|
+
conn.execute(
|
|
198
|
+
"""
|
|
199
|
+
UPDATE users
|
|
200
|
+
SET current_workspace_id = (
|
|
201
|
+
SELECT wi.workspace_id
|
|
202
|
+
FROM workspace_invites wi
|
|
203
|
+
WHERE wi.accepted_by_user_id = users.id
|
|
204
|
+
AND wi.accepted_at IS NOT NULL
|
|
205
|
+
ORDER BY wi.accepted_at DESC
|
|
206
|
+
LIMIT 1
|
|
207
|
+
)
|
|
208
|
+
WHERE COALESCE(current_workspace_id, '') = ''
|
|
209
|
+
AND EXISTS (
|
|
210
|
+
SELECT 1 FROM workspace_invites wi
|
|
211
|
+
WHERE wi.accepted_by_user_id = users.id AND wi.accepted_at IS NOT NULL
|
|
212
|
+
)
|
|
213
|
+
"""
|
|
214
|
+
)
|
|
215
|
+
conn.commit()
|
|
216
|
+
finally:
|
|
217
|
+
conn.close()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def ensure_budgets_grants_schema(open_db: OpenDb) -> None:
|
|
221
|
+
"""Create budget_policies and credential_grants tables if not exists."""
|
|
222
|
+
conn = open_db()
|
|
223
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS budget_policies (
|
|
224
|
+
id TEXT PRIMARY KEY,
|
|
225
|
+
workspace_id TEXT NOT NULL,
|
|
226
|
+
subject_type TEXT NOT NULL CHECK(subject_type IN ('workspace', 'user', 'agent_profile')),
|
|
227
|
+
subject_id TEXT,
|
|
228
|
+
provider TEXT,
|
|
229
|
+
daily_limit_cents INTEGER,
|
|
230
|
+
monthly_limit_cents INTEGER,
|
|
231
|
+
allowed_models TEXT,
|
|
232
|
+
created_at TEXT NOT NULL,
|
|
233
|
+
updated_at TEXT NOT NULL
|
|
234
|
+
)""")
|
|
235
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS credential_grants (
|
|
236
|
+
id TEXT PRIMARY KEY,
|
|
237
|
+
credential_binding_id TEXT NOT NULL,
|
|
238
|
+
granted_by_user_id TEXT NOT NULL,
|
|
239
|
+
grantee_type TEXT NOT NULL CHECK(grantee_type IN ('user', 'workspace', 'agent_profile', 'room')),
|
|
240
|
+
grantee_id TEXT NOT NULL,
|
|
241
|
+
provider TEXT NOT NULL,
|
|
242
|
+
max_daily_cents INTEGER,
|
|
243
|
+
max_total_cents INTEGER,
|
|
244
|
+
allowed_models TEXT,
|
|
245
|
+
allowed_agent_profile_ids TEXT,
|
|
246
|
+
allowed_room_ids TEXT,
|
|
247
|
+
expires_at TEXT,
|
|
248
|
+
revoked_at TEXT,
|
|
249
|
+
created_at TEXT NOT NULL
|
|
250
|
+
)""")
|
|
251
|
+
conn.commit()
|
|
252
|
+
conn.close()
|
|
253
|
+
|
|
254
|
+
WORKFRAME_DB_SCHEMA = """
|
|
255
|
+
PRAGMA journal_mode = WAL;
|
|
256
|
+
PRAGMA foreign_keys = ON;
|
|
257
|
+
|
|
258
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
259
|
+
id TEXT PRIMARY KEY,
|
|
260
|
+
email TEXT UNIQUE,
|
|
261
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
262
|
+
avatar_url TEXT DEFAULT NULL,
|
|
263
|
+
platform_ids TEXT DEFAULT '{}',
|
|
264
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
265
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
266
|
+
created_at TEXT NOT NULL,
|
|
267
|
+
updated_at TEXT NOT NULL,
|
|
268
|
+
deleted_at TEXT DEFAULT NULL
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
272
|
+
id TEXT PRIMARY KEY,
|
|
273
|
+
slug TEXT UNIQUE NOT NULL,
|
|
274
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
275
|
+
description TEXT DEFAULT '',
|
|
276
|
+
owner_id TEXT NOT NULL,
|
|
277
|
+
runtime_mode TEXT NOT NULL DEFAULT 'docker',
|
|
278
|
+
ports TEXT DEFAULT '{}',
|
|
279
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
280
|
+
created_at TEXT NOT NULL,
|
|
281
|
+
updated_at TEXT NOT NULL,
|
|
282
|
+
deleted_at TEXT DEFAULT NULL
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE TABLE IF NOT EXISTS workspace_memberships (
|
|
286
|
+
id TEXT PRIMARY KEY,
|
|
287
|
+
workspace_id TEXT NOT NULL,
|
|
288
|
+
user_id TEXT NOT NULL,
|
|
289
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
290
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
291
|
+
invited_by TEXT DEFAULT NULL,
|
|
292
|
+
created_at TEXT NOT NULL,
|
|
293
|
+
updated_at TEXT NOT NULL,
|
|
294
|
+
deleted_at TEXT DEFAULT NULL
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
CREATE TABLE IF NOT EXISTS credential_bindings (
|
|
298
|
+
id TEXT PRIMARY KEY,
|
|
299
|
+
workspace_id TEXT DEFAULT NULL,
|
|
300
|
+
user_id TEXT DEFAULT NULL,
|
|
301
|
+
agent_profile_id TEXT DEFAULT NULL,
|
|
302
|
+
provider TEXT NOT NULL,
|
|
303
|
+
credential_type TEXT NOT NULL DEFAULT 'api_key',
|
|
304
|
+
credential_ref TEXT NOT NULL,
|
|
305
|
+
label TEXT DEFAULT '',
|
|
306
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
307
|
+
expires_at TEXT DEFAULT NULL,
|
|
308
|
+
created_by TEXT DEFAULT NULL,
|
|
309
|
+
created_at TEXT NOT NULL,
|
|
310
|
+
updated_at TEXT NOT NULL,
|
|
311
|
+
deleted_at TEXT DEFAULT NULL,
|
|
312
|
+
CHECK (
|
|
313
|
+
(workspace_id IS NOT NULL AND user_id IS NULL AND agent_profile_id IS NULL)
|
|
314
|
+
OR (workspace_id IS NULL AND user_id IS NOT NULL AND agent_profile_id IS NULL)
|
|
315
|
+
OR (workspace_id IS NULL AND user_id IS NULL AND agent_profile_id IS NOT NULL)
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
CREATE INDEX IF NOT EXISTS idx_credential_bindings_workspace_active
|
|
320
|
+
ON credential_bindings(workspace_id, provider, is_active, deleted_at);
|
|
321
|
+
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_credential_bindings_user_active
|
|
323
|
+
ON credential_bindings(user_id, provider, is_active, deleted_at);
|
|
324
|
+
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_credential_bindings_agent_active
|
|
326
|
+
ON credential_bindings(agent_profile_id, provider, is_active, deleted_at);
|
|
327
|
+
|
|
328
|
+
CREATE TABLE IF NOT EXISTS audit_events (
|
|
329
|
+
id TEXT PRIMARY KEY,
|
|
330
|
+
user_id TEXT DEFAULT NULL,
|
|
331
|
+
event_type TEXT NOT NULL,
|
|
332
|
+
target_type TEXT DEFAULT NULL,
|
|
333
|
+
target_id TEXT DEFAULT NULL,
|
|
334
|
+
summary TEXT DEFAULT '',
|
|
335
|
+
metadata TEXT DEFAULT '{}',
|
|
336
|
+
ip_address TEXT DEFAULT NULL,
|
|
337
|
+
created_at TEXT NOT NULL
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
341
|
+
version TEXT PRIMARY KEY,
|
|
342
|
+
description TEXT DEFAULT '',
|
|
343
|
+
applied_at TEXT NOT NULL
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
CREATE TABLE IF NOT EXISTS agent_profiles (
|
|
347
|
+
id TEXT PRIMARY KEY,
|
|
348
|
+
workspace_id TEXT NOT NULL,
|
|
349
|
+
slug TEXT NOT NULL,
|
|
350
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
351
|
+
tagline TEXT DEFAULT '',
|
|
352
|
+
role TEXT DEFAULT '',
|
|
353
|
+
model_provider TEXT DEFAULT '',
|
|
354
|
+
model_name TEXT DEFAULT '',
|
|
355
|
+
is_native INTEGER NOT NULL DEFAULT 0,
|
|
356
|
+
avatar_url TEXT DEFAULT NULL,
|
|
357
|
+
status TEXT NOT NULL DEFAULT 'available',
|
|
358
|
+
created_at TEXT NOT NULL,
|
|
359
|
+
updated_at TEXT NOT NULL,
|
|
360
|
+
deleted_at TEXT DEFAULT NULL,
|
|
361
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces (id),
|
|
362
|
+
UNIQUE (workspace_id, slug, deleted_at)
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
CREATE INDEX IF NOT EXISTS idx_agent_profiles_workspace
|
|
366
|
+
ON agent_profiles(workspace_id)
|
|
367
|
+
WHERE deleted_at IS NULL;
|
|
368
|
+
|
|
369
|
+
CREATE INDEX IF NOT EXISTS idx_agent_profiles_slug
|
|
370
|
+
ON agent_profiles(slug)
|
|
371
|
+
WHERE deleted_at IS NULL;
|
|
372
|
+
|
|
373
|
+
CREATE TABLE IF NOT EXISTS rooms (
|
|
374
|
+
id TEXT PRIMARY KEY,
|
|
375
|
+
workspace_id TEXT NOT NULL,
|
|
376
|
+
agent_profile_id TEXT DEFAULT NULL,
|
|
377
|
+
name TEXT NOT NULL,
|
|
378
|
+
slug TEXT NOT NULL,
|
|
379
|
+
topic TEXT DEFAULT '',
|
|
380
|
+
room_type TEXT NOT NULL DEFAULT 'lane',
|
|
381
|
+
platform_ids TEXT DEFAULT '{}',
|
|
382
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
383
|
+
created_by TEXT DEFAULT NULL,
|
|
384
|
+
created_at TEXT NOT NULL,
|
|
385
|
+
updated_at TEXT NOT NULL,
|
|
386
|
+
deleted_at TEXT DEFAULT NULL,
|
|
387
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces (id),
|
|
388
|
+
FOREIGN KEY (agent_profile_id) REFERENCES agent_profiles (id),
|
|
389
|
+
FOREIGN KEY (created_by) REFERENCES users (id),
|
|
390
|
+
UNIQUE (workspace_id, slug, deleted_at)
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
CREATE INDEX IF NOT EXISTS idx_rooms_workspace
|
|
394
|
+
ON rooms(workspace_id)
|
|
395
|
+
WHERE deleted_at IS NULL;
|
|
396
|
+
|
|
397
|
+
CREATE INDEX IF NOT EXISTS idx_rooms_agent_profile
|
|
398
|
+
ON rooms(agent_profile_id)
|
|
399
|
+
WHERE deleted_at IS NULL;
|
|
400
|
+
|
|
401
|
+
CREATE INDEX IF NOT EXISTS idx_rooms_type
|
|
402
|
+
ON rooms(room_type, workspace_id)
|
|
403
|
+
WHERE deleted_at IS NULL;
|
|
404
|
+
|
|
405
|
+
CREATE TABLE IF NOT EXISTS room_memberships (
|
|
406
|
+
id TEXT PRIMARY KEY,
|
|
407
|
+
room_id TEXT NOT NULL,
|
|
408
|
+
user_id TEXT DEFAULT NULL,
|
|
409
|
+
agent_profile_id TEXT DEFAULT NULL,
|
|
410
|
+
role TEXT NOT NULL DEFAULT 'participant',
|
|
411
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
412
|
+
joined_at TEXT NOT NULL,
|
|
413
|
+
updated_at TEXT NOT NULL,
|
|
414
|
+
deleted_at TEXT DEFAULT NULL,
|
|
415
|
+
FOREIGN KEY (room_id) REFERENCES rooms (id),
|
|
416
|
+
FOREIGN KEY (user_id) REFERENCES users (id),
|
|
417
|
+
FOREIGN KEY (agent_profile_id) REFERENCES agent_profiles (id),
|
|
418
|
+
CHECK (
|
|
419
|
+
(user_id IS NOT NULL AND agent_profile_id IS NULL)
|
|
420
|
+
OR (user_id IS NULL AND agent_profile_id IS NOT NULL)
|
|
421
|
+
),
|
|
422
|
+
UNIQUE (room_id, user_id, agent_profile_id, deleted_at)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
CREATE INDEX IF NOT EXISTS idx_room_memberships_room
|
|
426
|
+
ON room_memberships(room_id)
|
|
427
|
+
WHERE deleted_at IS NULL;
|
|
428
|
+
|
|
429
|
+
CREATE INDEX IF NOT EXISTS idx_room_memberships_user
|
|
430
|
+
ON room_memberships(user_id)
|
|
431
|
+
WHERE deleted_at IS NULL;
|
|
432
|
+
|
|
433
|
+
CREATE INDEX IF NOT EXISTS idx_room_memberships_agent
|
|
434
|
+
ON room_memberships(agent_profile_id)
|
|
435
|
+
WHERE deleted_at IS NULL;
|
|
436
|
+
|
|
437
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
438
|
+
id TEXT PRIMARY KEY,
|
|
439
|
+
room_id TEXT NOT NULL,
|
|
440
|
+
sender_user_id TEXT DEFAULT NULL,
|
|
441
|
+
sender_agent_id TEXT DEFAULT NULL,
|
|
442
|
+
parent_message_id TEXT DEFAULT NULL,
|
|
443
|
+
content TEXT NOT NULL DEFAULT '',
|
|
444
|
+
content_type TEXT NOT NULL DEFAULT 'text',
|
|
445
|
+
metadata TEXT DEFAULT '{}',
|
|
446
|
+
is_edited INTEGER NOT NULL DEFAULT 0,
|
|
447
|
+
created_at TEXT NOT NULL,
|
|
448
|
+
updated_at TEXT NOT NULL,
|
|
449
|
+
deleted_at TEXT DEFAULT NULL,
|
|
450
|
+
FOREIGN KEY (room_id) REFERENCES rooms (id),
|
|
451
|
+
FOREIGN KEY (sender_user_id) REFERENCES users (id),
|
|
452
|
+
FOREIGN KEY (sender_agent_id) REFERENCES agent_profiles (id),
|
|
453
|
+
FOREIGN KEY (parent_message_id) REFERENCES messages (id),
|
|
454
|
+
CHECK (
|
|
455
|
+
(sender_user_id IS NOT NULL AND sender_agent_id IS NULL)
|
|
456
|
+
OR (sender_user_id IS NULL AND sender_agent_id IS NOT NULL)
|
|
457
|
+
OR (sender_user_id IS NULL AND sender_agent_id IS NULL)
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
CREATE INDEX IF NOT EXISTS idx_messages_room_created
|
|
462
|
+
ON messages(room_id, created_at DESC)
|
|
463
|
+
WHERE deleted_at IS NULL;
|
|
464
|
+
|
|
465
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_memberships_workspace_user_active
|
|
466
|
+
ON workspace_memberships(workspace_id, user_id)
|
|
467
|
+
WHERE deleted_at IS NULL;
|
|
468
|
+
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_memberships_user_active
|
|
470
|
+
ON workspace_memberships(user_id, deleted_at, status);
|
|
471
|
+
|
|
472
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_memberships_workspace_active
|
|
473
|
+
ON workspace_memberships(workspace_id, deleted_at, status);
|
|
474
|
+
|
|
475
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_memberships_workspace_role
|
|
476
|
+
ON workspace_memberships(workspace_id, role)
|
|
477
|
+
WHERE deleted_at IS NULL;
|
|
478
|
+
|
|
479
|
+
CREATE INDEX IF NOT EXISTS idx_workspaces_active
|
|
480
|
+
ON workspaces(deleted_at, status);
|
|
481
|
+
|
|
482
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
483
|
+
VALUES ('1', 'workframe-api runtime schema for /api/me', datetime('now'));
|
|
484
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
485
|
+
VALUES ('2', 'runtime schema with credential_bindings for credential resolution', datetime('now'));
|
|
486
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
487
|
+
VALUES ('3', 'runtime schema with agent_profiles, rooms, room_memberships, and messages', datetime('now'));
|
|
488
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
489
|
+
VALUES ('4', 'agent_runs tracking table with trigger, prompt, result, and cost fields', datetime('now'));
|
|
490
|
+
|
|
491
|
+
CREATE TABLE IF NOT EXISTS room_sessions (
|
|
492
|
+
id TEXT PRIMARY KEY,
|
|
493
|
+
room_id TEXT NOT NULL,
|
|
494
|
+
agent_profile_id TEXT NOT NULL,
|
|
495
|
+
session_id TEXT NOT NULL,
|
|
496
|
+
gateway_session_id TEXT NOT NULL,
|
|
497
|
+
title TEXT DEFAULT '',
|
|
498
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
499
|
+
forked_from TEXT DEFAULT NULL,
|
|
500
|
+
created_by TEXT DEFAULT NULL,
|
|
501
|
+
created_at TEXT NOT NULL,
|
|
502
|
+
updated_at TEXT NOT NULL,
|
|
503
|
+
deleted_at TEXT DEFAULT NULL,
|
|
504
|
+
FOREIGN KEY (room_id) REFERENCES rooms (id),
|
|
505
|
+
FOREIGN KEY (agent_profile_id) REFERENCES agent_profiles (id)
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
CREATE INDEX IF NOT EXISTS idx_room_sessions_room
|
|
509
|
+
ON room_sessions(room_id, agent_profile_id)
|
|
510
|
+
WHERE deleted_at IS NULL;
|
|
511
|
+
|
|
512
|
+
CREATE INDEX IF NOT EXISTS idx_room_sessions_status
|
|
513
|
+
ON room_sessions(room_id, status)
|
|
514
|
+
WHERE deleted_at IS NULL;
|
|
515
|
+
|
|
516
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
517
|
+
VALUES ('5', 'room_sessions table for forkable Hermes sessions per room+agent', datetime('now'));
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _srv():
|
|
522
|
+
import server as srv
|
|
523
|
+
return srv
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _migrate_v6_space_rooms(conn: sqlite3.Connection) -> None:
|
|
527
|
+
applied = conn.execute(
|
|
528
|
+
"SELECT 1 FROM schema_migrations WHERE version = '6' LIMIT 1",
|
|
529
|
+
).fetchone()
|
|
530
|
+
if applied:
|
|
531
|
+
return
|
|
532
|
+
now = str(int(time.time()))
|
|
533
|
+
conn.execute(
|
|
534
|
+
"""
|
|
535
|
+
UPDATE rooms
|
|
536
|
+
SET agent_profile_id = NULL, updated_at = ?
|
|
537
|
+
WHERE deleted_at IS NULL
|
|
538
|
+
AND room_type IN ('channel', 'group')
|
|
539
|
+
AND agent_profile_id IS NOT NULL
|
|
540
|
+
""",
|
|
541
|
+
(now,),
|
|
542
|
+
)
|
|
543
|
+
conn.execute(
|
|
544
|
+
"""
|
|
545
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
546
|
+
VALUES ('6', 'channel/group rooms are spaces not agent lanes', datetime('now'))
|
|
547
|
+
""",
|
|
548
|
+
)
|
|
549
|
+
def _migrate_v7_space_agent_members(conn: sqlite3.Connection) -> None:
|
|
550
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '7' LIMIT 1").fetchone():
|
|
551
|
+
return
|
|
552
|
+
rooms = conn.execute(
|
|
553
|
+
"""
|
|
554
|
+
SELECT id, workspace_id FROM rooms
|
|
555
|
+
WHERE deleted_at IS NULL AND room_type IN ('channel', 'group')
|
|
556
|
+
""",
|
|
557
|
+
).fetchall()
|
|
558
|
+
for room in rooms:
|
|
559
|
+
agents = conn.execute(
|
|
560
|
+
"""
|
|
561
|
+
SELECT id FROM agent_profiles
|
|
562
|
+
WHERE workspace_id = ? AND deleted_at IS NULL
|
|
563
|
+
""",
|
|
564
|
+
(str(room["workspace_id"]),),
|
|
565
|
+
).fetchall()
|
|
566
|
+
for agent in agents:
|
|
567
|
+
_srv()._ensure_agent_in_space_room(conn, str(room["id"]), str(agent["id"]))
|
|
568
|
+
conn.execute(
|
|
569
|
+
"""
|
|
570
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
571
|
+
VALUES ('7', 'workspace agents joined to space rooms as members', datetime('now'))
|
|
572
|
+
""",
|
|
573
|
+
)
|
|
574
|
+
def _migrate_v13_workspace_members_in_spaces(conn: sqlite3.Connection) -> None:
|
|
575
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '13' LIMIT 1").fetchone():
|
|
576
|
+
return
|
|
577
|
+
rooms = conn.execute(
|
|
578
|
+
"""
|
|
579
|
+
SELECT id, workspace_id FROM rooms
|
|
580
|
+
WHERE deleted_at IS NULL AND room_type IN ('channel', 'group')
|
|
581
|
+
""",
|
|
582
|
+
).fetchall()
|
|
583
|
+
for room in rooms:
|
|
584
|
+
_srv()._add_workspace_members_to_space_room(conn, str(room["workspace_id"]), str(room["id"]))
|
|
585
|
+
conn.execute(
|
|
586
|
+
"""
|
|
587
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
588
|
+
VALUES ('13', 'workspace members joined to all space rooms', datetime('now'))
|
|
589
|
+
""",
|
|
590
|
+
)
|
|
591
|
+
def _migrate_v8_room_avatars(conn: sqlite3.Connection) -> None:
|
|
592
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '8' LIMIT 1").fetchone():
|
|
593
|
+
return
|
|
594
|
+
cols = {str(row[1]) for row in conn.execute("PRAGMA table_info(rooms)")}
|
|
595
|
+
if "avatar_url" not in cols:
|
|
596
|
+
conn.execute("ALTER TABLE rooms ADD COLUMN avatar_url TEXT DEFAULT NULL")
|
|
597
|
+
conn.execute(
|
|
598
|
+
"""
|
|
599
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
600
|
+
VALUES ('8', 'room avatar_url for space settings', datetime('now'))
|
|
601
|
+
""",
|
|
602
|
+
)
|
|
603
|
+
def _migrate_v9_workspace_avatars(conn: sqlite3.Connection) -> None:
|
|
604
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '9' LIMIT 1").fetchone():
|
|
605
|
+
return
|
|
606
|
+
cols = {str(row[1]) for row in conn.execute("PRAGMA table_info(workspaces)")}
|
|
607
|
+
if "avatar_url" not in cols:
|
|
608
|
+
conn.execute("ALTER TABLE workspaces ADD COLUMN avatar_url TEXT DEFAULT NULL")
|
|
609
|
+
conn.execute(
|
|
610
|
+
"""
|
|
611
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
612
|
+
VALUES ('9', 'workspace avatar_url for workframe settings', datetime('now'))
|
|
613
|
+
""",
|
|
614
|
+
)
|
|
615
|
+
def _migrate_v11_delegation_kanban_boards(conn: sqlite3.Connection) -> None:
|
|
616
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '11' LIMIT 1").fetchone():
|
|
617
|
+
return
|
|
618
|
+
conn.execute(
|
|
619
|
+
"""
|
|
620
|
+
CREATE TABLE IF NOT EXISTS agent_delegation_grants (
|
|
621
|
+
id TEXT PRIMARY KEY,
|
|
622
|
+
workspace_id TEXT NOT NULL,
|
|
623
|
+
grantor_user_id TEXT NOT NULL,
|
|
624
|
+
grantee_user_id TEXT NOT NULL,
|
|
625
|
+
scope TEXT NOT NULL DEFAULT 'agents:delegate',
|
|
626
|
+
expires_at TEXT DEFAULT NULL,
|
|
627
|
+
created_at TEXT NOT NULL,
|
|
628
|
+
updated_at TEXT NOT NULL,
|
|
629
|
+
deleted_at TEXT DEFAULT NULL,
|
|
630
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces (id)
|
|
631
|
+
)
|
|
632
|
+
"""
|
|
633
|
+
)
|
|
634
|
+
conn.execute(
|
|
635
|
+
"""
|
|
636
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_delegation_grants_active
|
|
637
|
+
ON agent_delegation_grants(workspace_id, grantor_user_id, grantee_user_id, scope)
|
|
638
|
+
WHERE deleted_at IS NULL
|
|
639
|
+
"""
|
|
640
|
+
)
|
|
641
|
+
conn.execute(
|
|
642
|
+
"""
|
|
643
|
+
CREATE TABLE IF NOT EXISTS workspace_kanban_boards (
|
|
644
|
+
id TEXT PRIMARY KEY,
|
|
645
|
+
workspace_id TEXT NOT NULL,
|
|
646
|
+
hermes_board_slug TEXT NOT NULL,
|
|
647
|
+
display_name TEXT NOT NULL DEFAULT '',
|
|
648
|
+
created_at TEXT NOT NULL,
|
|
649
|
+
updated_at TEXT NOT NULL,
|
|
650
|
+
deleted_at TEXT DEFAULT NULL,
|
|
651
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces (id)
|
|
652
|
+
)
|
|
653
|
+
"""
|
|
654
|
+
)
|
|
655
|
+
conn.execute(
|
|
656
|
+
"""
|
|
657
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_kanban_board_active
|
|
658
|
+
ON workspace_kanban_boards(workspace_id)
|
|
659
|
+
WHERE deleted_at IS NULL
|
|
660
|
+
"""
|
|
661
|
+
)
|
|
662
|
+
conn.execute(
|
|
663
|
+
"""
|
|
664
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
665
|
+
VALUES ('11', 'agent_delegation_grants and workspace_kanban_boards', datetime('now'))
|
|
666
|
+
"""
|
|
667
|
+
)
|
|
668
|
+
def _migrate_v12_adopt_install_keys_to_owners(conn: sqlite3.Connection) -> None:
|
|
669
|
+
"""Hermes install keys on the primary profile become the workspace owner's personal keys."""
|
|
670
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '12' LIMIT 1").fetchone():
|
|
671
|
+
return
|
|
672
|
+
primary = _srv()._install_profile_slug()
|
|
673
|
+
adopted = False
|
|
674
|
+
if primary:
|
|
675
|
+
stack_env = _srv()._read_env_map(_srv()._profile_dir(primary) / ".env")
|
|
676
|
+
owner_ids = _srv()._install_key_owner_user_ids(conn)
|
|
677
|
+
for owner_id in owner_ids:
|
|
678
|
+
for spec in _srv().PROVIDER_CONNECT_CATALOG:
|
|
679
|
+
if str(spec.get("category") or "") != "llm":
|
|
680
|
+
continue
|
|
681
|
+
env_var = str(spec.get("env_var") or "").strip()
|
|
682
|
+
secret = str(stack_env.get(env_var) or "").strip()
|
|
683
|
+
if not env_var or not secret:
|
|
684
|
+
continue
|
|
685
|
+
if _srv()._adopt_install_llm_key_for_user(
|
|
686
|
+
conn,
|
|
687
|
+
owner_id,
|
|
688
|
+
str(spec["id"]),
|
|
689
|
+
env_var,
|
|
690
|
+
secret,
|
|
691
|
+
):
|
|
692
|
+
adopted = True
|
|
693
|
+
# ponytail: never strip install-profile .env — install keys stay on workframe-agent;
|
|
694
|
+
# user keys live only in profiles/{user-uuid}/. Never copy from local host Hermes.
|
|
695
|
+
conn.execute(
|
|
696
|
+
"""
|
|
697
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
698
|
+
VALUES ('12', 'adopt primary-profile LLM install keys as owner personal keys', datetime('now'))
|
|
699
|
+
"""
|
|
700
|
+
)
|
|
701
|
+
def _migrate_v10_workspace_settings_oauth(conn: sqlite3.Connection) -> None:
|
|
702
|
+
if conn.execute("SELECT 1 FROM schema_migrations WHERE version = '10' LIMIT 1").fetchone():
|
|
703
|
+
return
|
|
704
|
+
cols = {str(row[1]) for row in conn.execute("PRAGMA table_info(workspaces)")}
|
|
705
|
+
if "settings_json" not in cols:
|
|
706
|
+
conn.execute("ALTER TABLE workspaces ADD COLUMN settings_json TEXT NOT NULL DEFAULT '{}'")
|
|
707
|
+
conn.execute(
|
|
708
|
+
"""
|
|
709
|
+
CREATE TABLE IF NOT EXISTS oauth_pending_states (
|
|
710
|
+
state TEXT PRIMARY KEY,
|
|
711
|
+
user_id TEXT NOT NULL,
|
|
712
|
+
provider TEXT NOT NULL,
|
|
713
|
+
code_verifier TEXT NOT NULL,
|
|
714
|
+
workspace_id TEXT DEFAULT NULL,
|
|
715
|
+
expires_at TEXT NOT NULL,
|
|
716
|
+
created_at TEXT NOT NULL
|
|
717
|
+
)
|
|
718
|
+
"""
|
|
719
|
+
)
|
|
720
|
+
conn.execute(
|
|
721
|
+
"""
|
|
722
|
+
INSERT INTO schema_migrations (version, description, applied_at)
|
|
723
|
+
VALUES ('10', 'workspace settings_json and oauth_pending_states for user GitHub OAuth', datetime('now'))
|
|
724
|
+
""",
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def ensure_workframe_db_schema(open_db: OpenDb) -> None:
|
|
730
|
+
conn = open_db()
|
|
731
|
+
try:
|
|
732
|
+
conn.executescript(WORKFRAME_DB_SCHEMA)
|
|
733
|
+
_migrate_v6_space_rooms(conn)
|
|
734
|
+
_migrate_v7_space_agent_members(conn)
|
|
735
|
+
_migrate_v8_room_avatars(conn)
|
|
736
|
+
_migrate_v9_workspace_avatars(conn)
|
|
737
|
+
_migrate_v10_workspace_settings_oauth(conn)
|
|
738
|
+
_migrate_v11_delegation_kanban_boards(conn)
|
|
739
|
+
_migrate_v12_adopt_install_keys_to_owners(conn)
|
|
740
|
+
_migrate_v13_workspace_members_in_spaces(conn)
|
|
741
|
+
conn.commit()
|
|
742
|
+
finally:
|
|
743
|
+
conn.close()
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def ensure_oauth_pending_migrations(open_db: OpenDb) -> None:
|
|
747
|
+
conn = open_db()
|
|
748
|
+
try:
|
|
749
|
+
_migrate_v10_workspace_settings_oauth(conn)
|
|
750
|
+
_migrate_v11_delegation_kanban_boards(conn)
|
|
751
|
+
_migrate_v12_adopt_install_keys_to_owners(conn)
|
|
752
|
+
_migrate_v13_workspace_members_in_spaces(conn)
|
|
753
|
+
conn.commit()
|
|
754
|
+
finally:
|
|
755
|
+
conn.close()
|