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
package/README.md
CHANGED
package/bin/create-workframe.js
CHANGED
|
@@ -535,7 +535,7 @@ services:
|
|
|
535
535
|
context: \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-supervisor
|
|
536
536
|
volumes:
|
|
537
537
|
- \${WORKFRAME_HOST_PROJECT_ROOT}/Agents:/opt/data
|
|
538
|
-
- \${WORKFRAME_HOST_PROJECT_ROOT}
|
|
538
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}:/compose
|
|
539
539
|
|
|
540
540
|
workframe:
|
|
541
541
|
volumes:
|
|
@@ -2527,7 +2527,7 @@ async function main() {
|
|
|
2527
2527
|
copyWorkframeUiTemplate(target, [nativeSlug]);
|
|
2528
2528
|
writeText(
|
|
2529
2529
|
path.join(target, 'workframe-ui', 'public', 'workframe-config.json'),
|
|
2530
|
-
`${JSON.stringify({ project_name: safeName, native_profile: nativeSlug }, null, 2)}\n`,
|
|
2530
|
+
`${JSON.stringify({ project_name: safeName, native_profile: nativeSlug, package_version: PKG_VERSION }, null, 2)}\n`,
|
|
2531
2531
|
);
|
|
2532
2532
|
|
|
2533
2533
|
writeText(path.join(target, 'SETUP.md'), onboardingDoc({
|
package/bin/workframe.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* workframe — lifecycle CLI for generated Workframe projects.
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* workframe doctor [--repair] Diagnose stack; --
|
|
6
|
+
* workframe doctor [--repair] [--json] Diagnose stack; --json mutation-free report
|
|
7
7
|
* workframe setup Open Hermes setup (credentials)
|
|
8
8
|
* workframe stop Stop all stack containers
|
|
9
9
|
* workframe start Start the full stack (docker compose up -d)
|
|
@@ -53,9 +53,137 @@ function doctorAgentDmRuntimes(root, { repair = false } = {}) {
|
|
|
53
53
|
return dockerCompose(root, ['exec', '-T', 'workframe-api', 'python3', '-c', py], { stdio: 'pipe' });
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function redactPath(root, absolutePath) {
|
|
57
|
+
try {
|
|
58
|
+
const rel = path.relative(root, absolutePath);
|
|
59
|
+
if (!rel || rel.startsWith('..')) return path.basename(absolutePath);
|
|
60
|
+
return rel.split(path.sep).join('/');
|
|
61
|
+
} catch {
|
|
62
|
+
return path.basename(String(absolutePath || ''));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nodeVersion() {
|
|
67
|
+
const res = spawnSync(process.execPath, ['-v'], { encoding: 'utf8' });
|
|
68
|
+
if (res.status !== 0) return null;
|
|
69
|
+
return (res.stdout || res.stderr || '').trim() || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildDoctorJsonReport(root, manifest, issues, checks) {
|
|
73
|
+
const decision = issues.length === 0
|
|
74
|
+
? 'healthy'
|
|
75
|
+
: checks.docker?.status === 'fail'
|
|
76
|
+
? 'needs_user_action'
|
|
77
|
+
: 'unhealthy';
|
|
78
|
+
return {
|
|
79
|
+
schema_version: '0.1',
|
|
80
|
+
advisory_only: true,
|
|
81
|
+
decision,
|
|
82
|
+
project: {
|
|
83
|
+
name: manifest?.project_name || path.basename(root),
|
|
84
|
+
package_version: manifest?.package_version || null,
|
|
85
|
+
},
|
|
86
|
+
checks,
|
|
87
|
+
issues,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function collectDoctorChecks(root, manifest) {
|
|
92
|
+
const checks = {};
|
|
93
|
+
const issues = [];
|
|
94
|
+
|
|
95
|
+
const dockerCheck = spawnSync('docker', ['info'], { encoding: 'utf8' });
|
|
96
|
+
if (dockerCheck.status !== 0) {
|
|
97
|
+
checks.docker = { status: 'fail', note: 'Docker not running or not installed' };
|
|
98
|
+
issues.push('Docker is not running or not installed.');
|
|
99
|
+
} else {
|
|
100
|
+
checks.docker = { status: 'ok', note: 'docker info succeeded' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const nodeVer = nodeVersion();
|
|
104
|
+
checks.node = {
|
|
105
|
+
status: nodeVer ? 'ok' : 'skip',
|
|
106
|
+
version: nodeVer,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (!manifest) {
|
|
110
|
+
checks.manifest = { status: 'fail', stack: null };
|
|
111
|
+
issues.push('No workframe-manifest.json found.');
|
|
112
|
+
} else {
|
|
113
|
+
checks.manifest = {
|
|
114
|
+
status: 'ok',
|
|
115
|
+
stack: manifest.docker?.stack || null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const layout = { workspace: 'missing', runtime: 'missing' };
|
|
120
|
+
for (const dir of ['Files', 'Agents']) {
|
|
121
|
+
if (fs.existsSync(path.join(root, dir))) {
|
|
122
|
+
layout[dir === 'Files' ? 'workspace' : 'runtime'] = 'ok';
|
|
123
|
+
} else {
|
|
124
|
+
issues.push(`${dir}/ directory missing.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
checks.layout = { status: layout.workspace === 'ok' && layout.runtime === 'ok' ? 'ok' : 'fail', ...layout };
|
|
128
|
+
|
|
129
|
+
checks.cell = {
|
|
130
|
+
deployment_mode: manifest?.security?.deployment_mode || manifest?.deployment_mode || null,
|
|
131
|
+
install_id_redacted: manifest?.install_id ? String(manifest.install_id).slice(0, 8) + '…' : null,
|
|
132
|
+
manifest_path: redactPath(root, path.join(root, 'workframe-manifest.json')),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const runtimeCandidates = [];
|
|
136
|
+
if (manifest && checks.docker?.status === 'ok') {
|
|
137
|
+
const ps = dockerCompose(root, ['ps', '--format', 'json'], { stdio: 'pipe' });
|
|
138
|
+
if (ps.status === 0 && ps.stdout) {
|
|
139
|
+
try {
|
|
140
|
+
const raw = ps.stdout.trim();
|
|
141
|
+
const containers = raw.startsWith('[')
|
|
142
|
+
? JSON.parse(raw)
|
|
143
|
+
: raw.split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
144
|
+
for (const name of ['gateway', 'workframe-api', 'workframe']) {
|
|
145
|
+
const container = containers.find((c) => c.Name?.includes(name) || c.Service === name);
|
|
146
|
+
runtimeCandidates.push({
|
|
147
|
+
kind: name === 'gateway' ? 'hermes_managed' : 'workframe_service',
|
|
148
|
+
service: name,
|
|
149
|
+
status: container ? (container.State === 'running' ? 'running' : container.State || 'unknown') : 'stopped',
|
|
150
|
+
});
|
|
151
|
+
if (!container || container.State !== 'running') {
|
|
152
|
+
issues.push(`${name} container not running.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
runtimeCandidates.push({ kind: 'hermes_managed', service: 'gateway', status: 'unknown' });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
runtimeCandidates.push({ kind: 'hermes_managed', service: 'gateway', status: 'unknown', note: 'docker unavailable' });
|
|
161
|
+
}
|
|
162
|
+
checks.runtime_candidates = runtimeCandidates;
|
|
163
|
+
|
|
164
|
+
if (!fs.existsSync(path.join(root, '.env'))) {
|
|
165
|
+
issues.push('.env file missing.');
|
|
166
|
+
}
|
|
167
|
+
if (!fs.existsSync(path.join(root, 'docker-compose.yml'))) {
|
|
168
|
+
issues.push('docker-compose.yml missing.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { checks, issues };
|
|
172
|
+
}
|
|
173
|
+
|
|
56
174
|
function cmdDoctor(root, extraArgs = []) {
|
|
57
175
|
const repair = extraArgs.includes('--repair');
|
|
176
|
+
const jsonMode = extraArgs.includes('--json');
|
|
58
177
|
const manifest = readManifest(root);
|
|
178
|
+
|
|
179
|
+
if (jsonMode) {
|
|
180
|
+
const { checks, issues } = collectDoctorChecks(root, manifest);
|
|
181
|
+
const report = buildDoctorJsonReport(root, manifest, issues, checks);
|
|
182
|
+
console.log(JSON.stringify(report, null, 2));
|
|
183
|
+
process.exit(report.decision === 'healthy' ? 0 : 1);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
59
187
|
const issues = [];
|
|
60
188
|
|
|
61
189
|
console.log(repair ? 'workframe doctor --repair' : 'workframe doctor');
|
|
@@ -287,7 +415,7 @@ function usage() {
|
|
|
287
415
|
console.log(`workframe — lifecycle CLI for Workframe projects
|
|
288
416
|
|
|
289
417
|
Usage:
|
|
290
|
-
workframe doctor [--repair]
|
|
418
|
+
workframe doctor [--repair] [--json] Diagnose stack; --json mutation-free report
|
|
291
419
|
workframe setup Open Hermes setup (credentials)
|
|
292
420
|
workframe start Start the full stack (docker compose up -d)
|
|
293
421
|
workframe stop Stop all stack containers
|
package/docs/security.md
CHANGED
|
@@ -53,7 +53,7 @@ In `public_multi_user`, credential-mediated traffic must not depend on agent coo
|
|
|
53
53
|
2. Runtime profiles use **lease tokens** (`wf_rt_*`) and internal proxy base URLs for LLM calls.
|
|
54
54
|
3. Integrated action providers use `/internal/action/*` with the same lease model.
|
|
55
55
|
4. Gateway must **not** join the supervisor `control-net` (already enforced in compose).
|
|
56
|
-
5. Optional strict profile (`WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true`)
|
|
56
|
+
5. Optional strict profile (`WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true`) blocks **direct** outbound connections to known provider API hostnames via the `gateway-egress-guard` iptables sidecar (`docker-compose.egress-broker.yml`) so traffic must use the broker — general internet stays open.
|
|
57
57
|
|
|
58
58
|
This is **routing and secret placement**, not disabling `terminal` or adding human approval before each tool call. Lease validation on brokered requests is in-process (milliseconds), not an operator gate.
|
|
59
59
|
|
|
@@ -63,7 +63,7 @@ This is **routing and secret placement**, not disabling `terminal` or adding hum
|
|
|
63
63
|
|------|----------------|----------------------|
|
|
64
64
|
| `trusted_team` / dogfood | Unrestricted | Config + vault + leases (today) |
|
|
65
65
|
| `public_multi_user` | Unrestricted (agents stay capable) | Same broker path; verify script reports posture |
|
|
66
|
-
| `public_multi_user` + `WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true` | Unrestricted except provider-host deny
|
|
66
|
+
| `public_multi_user` + `WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true` | Unrestricted except provider-host deny via egress-guard sidecar | Network-enforced broker path |
|
|
67
67
|
|
|
68
68
|
See also [Audit 0027 — Agent Vault comparison](../audits/0027-agent-vault-comparison.md) (advisory; Infisical Agent Vault as a reference pattern, not a required dependency).
|
|
69
69
|
|
package/package.json
CHANGED
|
@@ -59,6 +59,15 @@ if (!fs.existsSync(path.join(dist, 'index.html'))) {
|
|
|
59
59
|
console.log(`Copying ${dist} -> ${UI_DEST}`);
|
|
60
60
|
copyTree(dist, UI_DEST);
|
|
61
61
|
|
|
62
|
+
const pkgVersion = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
63
|
+
const gitRef = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8', cwd: REPO_ROOT });
|
|
64
|
+
const buildStamp = {
|
|
65
|
+
package_version: pkgVersion,
|
|
66
|
+
bundled_at: new Date().toISOString(),
|
|
67
|
+
git_ref: gitRef.status === 0 ? gitRef.stdout.trim() : '',
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(path.join(UI_DEST, 'workframe-build.json'), `${JSON.stringify(buildStamp, null, 2)}\n`);
|
|
70
|
+
|
|
62
71
|
const avatarShared = path.join(PKG_ROOT, 'shared', 'agent-avatars');
|
|
63
72
|
|
|
64
73
|
function copyPresetPngs(src, dst, { keepCatalog = false } = {}) {
|
|
@@ -110,6 +110,7 @@ const applyScripts = [
|
|
|
110
110
|
'setup-stack-secrets.sh',
|
|
111
111
|
'bootstrap-workspace-link.sh',
|
|
112
112
|
'verify-public-deploy.sh',
|
|
113
|
+
'gateway-egress-iptables.sh',
|
|
113
114
|
'fix-zk-encryption-key.sh',
|
|
114
115
|
'set-compose-public-url.mjs',
|
|
115
116
|
'ensure-compose-host-paths.mjs',
|
|
@@ -20,9 +20,10 @@ gateway_container_name() {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
report_egress_posture() {
|
|
23
|
-
local gw force_broker gw_on_control internal_control
|
|
23
|
+
local gw force_broker gw_on_control internal_control egress_overlay
|
|
24
24
|
gw="$(gateway_container_name)"
|
|
25
25
|
force_broker="$(env_val WORKFRAME_FORCE_AGENT_EGRESS_BROKER)"
|
|
26
|
+
egress_overlay="$COMPOSE_DIR/docker-compose.egress-broker.yml"
|
|
26
27
|
|
|
27
28
|
if grep -A30 '^ gateway:' "$COMPOSE_FILE" | grep -q 'control-net'; then
|
|
28
29
|
gw_on_control=yes
|
|
@@ -48,7 +49,15 @@ report_egress_posture() {
|
|
|
48
49
|
if echo "$nets" | grep -qi 'control'; then
|
|
49
50
|
fail "running gateway is attached to a control network"
|
|
50
51
|
fi
|
|
51
|
-
|
|
52
|
+
if [[ "$force_broker" =~ ^(1|true|yes|on)$ ]]; then
|
|
53
|
+
if docker inspect workframe-gateway-egress-guard >/dev/null 2>&1; then
|
|
54
|
+
info "egress gateway_egress_posture=provider_hosts_blocked (egress-guard sidecar)"
|
|
55
|
+
else
|
|
56
|
+
fail "WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true but gateway-egress-guard is not running"
|
|
57
|
+
fi
|
|
58
|
+
else
|
|
59
|
+
info "egress gateway_egress_posture=unrestricted_general (broker for LLM/action providers)"
|
|
60
|
+
fi
|
|
52
61
|
else
|
|
53
62
|
info "egress gateway_egress_posture=unknown (gateway container not running)"
|
|
54
63
|
fi
|
|
@@ -58,11 +67,60 @@ report_egress_posture() {
|
|
|
58
67
|
info "egress broker_latency=per-request lease validation (no human approval gate)"
|
|
59
68
|
|
|
60
69
|
if [[ "$force_broker" =~ ^(1|true|yes|on)$ ]]; then
|
|
61
|
-
|
|
70
|
+
[[ -f "$egress_overlay" ]] || fail "missing $egress_overlay (required when WORKFRAME_FORCE_AGENT_EGRESS_BROKER=true)"
|
|
71
|
+
grep -q 'gateway-egress-guard' "$egress_overlay" || fail "egress overlay missing gateway-egress-guard service"
|
|
72
|
+
ok "forced broker egress overlay present"
|
|
62
73
|
fi
|
|
63
74
|
ok "egress posture reported"
|
|
64
75
|
}
|
|
65
76
|
|
|
77
|
+
report_supervisor_constraints() {
|
|
78
|
+
local public_overlay="$COMPOSE_DIR/docker-compose.public.yml"
|
|
79
|
+
if [[ -f "$public_overlay" ]]; then
|
|
80
|
+
grep -q 'WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC=0' "$public_overlay" \
|
|
81
|
+
|| fail "docker-compose.public.yml must set WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC=0"
|
|
82
|
+
if grep -A30 '^ workframe-api:' "$public_overlay" | grep -q '/var/run/docker.sock'; then
|
|
83
|
+
fail "docker-compose.public.yml must not mount docker.sock on workframe-api"
|
|
84
|
+
fi
|
|
85
|
+
ok "supervisor public overlay constraints"
|
|
86
|
+
else
|
|
87
|
+
warn "docker-compose.public.yml missing — skipping supervisor overlay checks"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
report_broker_audit_schema() {
|
|
92
|
+
local data_dir
|
|
93
|
+
data_dir="$(cd "$ROOT" && pwd)/runtime/workframe-api-data"
|
|
94
|
+
if [[ ! -d "$data_dir" ]]; then
|
|
95
|
+
info "broker_audit schema=skipped (no runtime/workframe-api-data)"
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
python3 - "$data_dir" <<'PY' || fail "broker_audit_events table missing (run API once or apply migration)"
|
|
99
|
+
import sqlite3, sys
|
|
100
|
+
from pathlib import Path
|
|
101
|
+
db = Path(sys.argv[1]) / "workframe.db"
|
|
102
|
+
if not db.is_file():
|
|
103
|
+
print("skip: no workframe.db yet")
|
|
104
|
+
raise SystemExit(0)
|
|
105
|
+
conn = sqlite3.connect(str(db))
|
|
106
|
+
row = conn.execute(
|
|
107
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='broker_audit_events'"
|
|
108
|
+
).fetchone()
|
|
109
|
+
conn.close()
|
|
110
|
+
if not row:
|
|
111
|
+
raise SystemExit(1)
|
|
112
|
+
PY
|
|
113
|
+
ok "broker audit schema"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
report_backup_restore_evidence() {
|
|
117
|
+
local backup_doc="$ROOT/infra/compose/workframe/PUBLIC_DEPLOY.md"
|
|
118
|
+
[[ -f "$backup_doc" ]] || fail "missing PUBLIC_DEPLOY.md backup section"
|
|
119
|
+
grep -qi 'backup' "$backup_doc" || fail "PUBLIC_DEPLOY.md missing backup guidance"
|
|
120
|
+
grep -qi 'rollback\|restore' "$backup_doc" || fail "PUBLIC_DEPLOY.md missing restore/rollback guidance"
|
|
121
|
+
ok "backup/restore documented"
|
|
122
|
+
}
|
|
123
|
+
|
|
66
124
|
[[ -f "$ENV_FILE" ]] || fail "missing $ENV_FILE"
|
|
67
125
|
[[ -f "$COMPOSE_FILE" ]] || fail "missing $COMPOSE_FILE"
|
|
68
126
|
|
|
@@ -158,4 +216,8 @@ fi
|
|
|
158
216
|
|
|
159
217
|
report_egress_posture
|
|
160
218
|
|
|
219
|
+
report_supervisor_constraints
|
|
220
|
+
report_broker_audit_schema
|
|
221
|
+
report_backup_restore_evidence
|
|
222
|
+
|
|
161
223
|
ok "public_multi_user preflight passed"
|
|
@@ -9,8 +9,8 @@ import urllib.request
|
|
|
9
9
|
from http.server import BaseHTTPRequestHandler
|
|
10
10
|
from typing import Any, Callable
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import credential_broker
|
|
13
|
+
import internal_proxy_auth
|
|
14
14
|
|
|
15
15
|
UPSTREAM_BASE: dict[str, str] = {
|
|
16
16
|
"github": "https://api.github.com",
|
|
@@ -21,6 +21,15 @@ UPSTREAM_BASE: dict[str, str] = {
|
|
|
21
21
|
PROXY_PATH_RE = re.compile(r"^/internal/action/([a-z0-9_-]+)(/.*)?$", re.IGNORECASE)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _upstream_host(provider: str) -> str:
|
|
25
|
+
base = UPSTREAM_BASE.get(str(provider or "").strip().lower(), "")
|
|
26
|
+
return str(base).split("://", 1)[-1].split("/", 1)[0].lower()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def authorize_internal_proxy(handler: BaseHTTPRequestHandler) -> tuple[bool, str]:
|
|
30
|
+
return internal_proxy_auth.authorize_internal_proxy(handler)
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
def upstream_auth_header(provider: str, secret: str) -> dict[str, str]:
|
|
25
34
|
provider = str(provider or "").strip().lower()
|
|
26
35
|
secret = str(secret or "").strip()
|
|
@@ -45,24 +54,21 @@ def forward_request(
|
|
|
45
54
|
if not base:
|
|
46
55
|
return 404, {"Content-Type": "application/json"}, json.dumps({"error": "unknown provider"}).encode()
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if not ok_profile:
|
|
57
|
+
auth = credential_broker.authorize_broker_lease(
|
|
58
|
+
provider,
|
|
59
|
+
headers,
|
|
60
|
+
resolve_secret=resolve_secret,
|
|
61
|
+
broker_kind="action",
|
|
62
|
+
upstream_host=_upstream_host(provider),
|
|
63
|
+
)
|
|
64
|
+
if not auth.ok:
|
|
57
65
|
return (
|
|
58
|
-
|
|
66
|
+
auth.status,
|
|
59
67
|
{"Content-Type": "application/json"},
|
|
60
|
-
|
|
68
|
+
credential_broker.broker_error_body(auth),
|
|
61
69
|
)
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
if not secret:
|
|
65
|
-
return 402, {"Content-Type": "application/json"}, json.dumps({"error": "no credential"}).encode()
|
|
71
|
+
secret = auth.secret
|
|
66
72
|
|
|
67
73
|
path = subpath if subpath.startswith("/") else f"/{subpath}"
|
|
68
74
|
url = f"{base.rstrip('/')}{path}"
|
|
@@ -100,7 +106,7 @@ def handle_proxy_request(
|
|
|
100
106
|
*,
|
|
101
107
|
resolve_secret: Callable[[str, str, str, str], tuple[str, str]],
|
|
102
108
|
) -> bool:
|
|
103
|
-
ok, err =
|
|
109
|
+
ok, err = authorize_internal_proxy(handler)
|
|
104
110
|
if not ok:
|
|
105
111
|
handler.send_response(403)
|
|
106
112
|
handler.send_header("Content-Type", "application/json")
|