@mmmbuto/masix 0.4.0 → 0.4.2
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 +18 -14
- package/install.js +53 -27
- package/package.json +4 -3
- package/packages/plugin-base/codex-backend/0.1.4/SHA256SUMS +3 -0
- package/packages/plugin-base/codex-backend/0.1.4/codex-backend-android-aarch64-termux.pkg +0 -0
- package/packages/plugin-base/codex-backend/0.1.4/codex-backend-linux-x86_64.pkg +0 -0
- package/packages/plugin-base/codex-backend/0.1.4/codex-backend-macos-aarch64.pkg +0 -0
- package/packages/plugin-base/codex-backend/0.1.4/manifest.json +33 -0
- package/packages/plugin-base/codex-backend/CHANGELOG.md +17 -0
- package/packages/plugin-base/codex-backend/README.md +33 -0
- package/packages/plugin-base/codex-backend/source/Cargo.toml +25 -0
- package/packages/plugin-base/codex-backend/source/README-PACKAGE.txt +54 -0
- package/packages/plugin-base/codex-backend/source/plugin.manifest.json +103 -0
- package/packages/plugin-base/codex-backend/source/src/error.rs +60 -0
- package/packages/plugin-base/codex-backend/source/src/exec.rs +436 -0
- package/packages/plugin-base/codex-backend/source/src/http_backend.rs +1198 -0
- package/packages/plugin-base/codex-backend/source/src/lib.rs +328 -0
- package/packages/plugin-base/codex-backend/source/src/patch.rs +767 -0
- package/packages/plugin-base/codex-backend/source/src/policy.rs +297 -0
- package/packages/plugin-base/codex-backend/source/src/tools.rs +72 -0
- package/packages/plugin-base/codex-backend/source/src/workspace.rs +433 -0
- package/packages/plugin-base/codex-tools/0.1.3/SHA256SUMS +3 -0
- package/packages/plugin-base/codex-tools/0.1.3/codex-tools-android-aarch64-termux.pkg +0 -0
- package/packages/plugin-base/codex-tools/0.1.3/codex-tools-linux-x86_64.pkg +0 -0
- package/packages/plugin-base/codex-tools/0.1.3/codex-tools-macos-aarch64.pkg +0 -0
- package/packages/plugin-base/codex-tools/0.1.3/manifest.json +33 -0
- package/packages/plugin-base/codex-tools/CHANGELOG.md +17 -0
- package/packages/plugin-base/codex-tools/README.md +33 -0
- package/packages/plugin-base/codex-tools/source/Cargo.toml +23 -0
- package/packages/plugin-base/codex-tools/source/plugin.manifest.json +124 -0
- package/packages/plugin-base/codex-tools/source/src/main.rs +995 -0
- package/packages/plugin-base/discovery/0.2.4/SHA256SUMS +3 -0
- package/packages/plugin-base/discovery/0.2.4/discovery-android-aarch64-termux.pkg +0 -0
- package/packages/plugin-base/discovery/0.2.4/discovery-linux-x86_64.pkg +0 -0
- package/packages/plugin-base/discovery/0.2.4/discovery-macos-aarch64.pkg +0 -0
- package/packages/plugin-base/discovery/0.2.4/manifest.json +31 -0
- package/packages/plugin-base/discovery/CHANGELOG.md +17 -0
- package/packages/plugin-base/discovery/README.md +48 -0
- package/packages/plugin-base/discovery/source/Cargo.toml +14 -0
- package/packages/plugin-base/discovery/source/plugin.manifest.json +30 -0
- package/packages/plugin-base/discovery/source/src/main.rs +2570 -0
- package/prebuilt/masix +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
//! Admin policy enforcement for codex-backend module
|
|
2
|
+
//!
|
|
3
|
+
//! This module enforces admin-only execution for the coding backend.
|
|
4
|
+
//! The policy is designed to integrate with MasiX's role-based permission system.
|
|
5
|
+
|
|
6
|
+
use crate::CodingError;
|
|
7
|
+
|
|
8
|
+
/// Permission levels matching MasiX core
|
|
9
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
10
|
+
pub enum PermissionLevel {
|
|
11
|
+
#[default]
|
|
12
|
+
Readonly,
|
|
13
|
+
User,
|
|
14
|
+
Admin,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl PermissionLevel {
|
|
18
|
+
pub fn as_str(&self) -> &'static str {
|
|
19
|
+
match self {
|
|
20
|
+
PermissionLevel::Readonly => "readonly",
|
|
21
|
+
PermissionLevel::User => "user",
|
|
22
|
+
PermissionLevel::Admin => "admin",
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn from_str(s: &str) -> Option<Self> {
|
|
27
|
+
match s.to_lowercase().as_str() {
|
|
28
|
+
"readonly" | "read-only" | "read_only" => Some(PermissionLevel::Readonly),
|
|
29
|
+
"user" => Some(PermissionLevel::User),
|
|
30
|
+
"admin" | "administrator" => Some(PermissionLevel::Admin),
|
|
31
|
+
_ => None,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Returns true if this level has admin privileges
|
|
36
|
+
pub fn is_admin(&self) -> bool {
|
|
37
|
+
matches!(self, PermissionLevel::Admin)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Policy context provided by the runtime
|
|
42
|
+
#[derive(Debug, Clone, Default)]
|
|
43
|
+
pub struct PolicyContext {
|
|
44
|
+
/// User's permission level
|
|
45
|
+
pub permission_level: PermissionLevel,
|
|
46
|
+
/// User ID (for logging)
|
|
47
|
+
pub user_id: Option<String>,
|
|
48
|
+
/// Chat ID (for logging)
|
|
49
|
+
pub chat_id: Option<i64>,
|
|
50
|
+
/// Account tag (for multi-bot isolation)
|
|
51
|
+
pub account_tag: Option<String>,
|
|
52
|
+
/// Whether policy enforcement is enabled
|
|
53
|
+
pub enforcement_enabled: bool,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl PolicyContext {
|
|
57
|
+
/// Create a new policy context with the given permission level
|
|
58
|
+
pub fn new(level: PermissionLevel) -> Self {
|
|
59
|
+
Self {
|
|
60
|
+
permission_level: level,
|
|
61
|
+
enforcement_enabled: true,
|
|
62
|
+
..Default::default()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Create an admin context (for testing or privileged execution)
|
|
67
|
+
pub fn admin() -> Self {
|
|
68
|
+
Self {
|
|
69
|
+
permission_level: PermissionLevel::Admin,
|
|
70
|
+
enforcement_enabled: true,
|
|
71
|
+
..Default::default()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Create a readonly context
|
|
76
|
+
pub fn readonly() -> Self {
|
|
77
|
+
Self {
|
|
78
|
+
permission_level: PermissionLevel::Readonly,
|
|
79
|
+
enforcement_enabled: true,
|
|
80
|
+
..Default::default()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Create a user context
|
|
85
|
+
pub fn user() -> Self {
|
|
86
|
+
Self {
|
|
87
|
+
permission_level: PermissionLevel::User,
|
|
88
|
+
enforcement_enabled: true,
|
|
89
|
+
..Default::default()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Disable policy enforcement (for local/CLI use)
|
|
94
|
+
pub fn unenforced() -> Self {
|
|
95
|
+
Self {
|
|
96
|
+
enforcement_enabled: false,
|
|
97
|
+
..Default::default()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Set user ID
|
|
102
|
+
pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
|
|
103
|
+
self.user_id = Some(user_id.into());
|
|
104
|
+
self
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Set chat ID
|
|
108
|
+
pub fn with_chat_id(mut self, chat_id: i64) -> Self {
|
|
109
|
+
self.chat_id = Some(chat_id);
|
|
110
|
+
self
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Set account tag
|
|
114
|
+
pub fn with_account_tag(mut self, tag: impl Into<String>) -> Self {
|
|
115
|
+
self.account_tag = Some(tag.into());
|
|
116
|
+
self
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// Admin policy checker for codex-backend
|
|
121
|
+
#[derive(Debug, Clone, Default)]
|
|
122
|
+
pub struct AdminPolicy {
|
|
123
|
+
/// Whether admin is required (default: true)
|
|
124
|
+
require_admin: bool,
|
|
125
|
+
/// Whether enforcement is enabled
|
|
126
|
+
enforcement_enabled: bool,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl AdminPolicy {
|
|
130
|
+
/// Create a new admin policy with enforcement enabled
|
|
131
|
+
pub fn new() -> Self {
|
|
132
|
+
Self {
|
|
133
|
+
require_admin: true,
|
|
134
|
+
enforcement_enabled: true,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Create a policy that doesn't require admin (for testing)
|
|
139
|
+
pub fn permissive() -> Self {
|
|
140
|
+
Self {
|
|
141
|
+
require_admin: false,
|
|
142
|
+
enforcement_enabled: true,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Create a policy with enforcement disabled (for CLI/local use)
|
|
147
|
+
pub fn disabled() -> Self {
|
|
148
|
+
Self {
|
|
149
|
+
require_admin: true,
|
|
150
|
+
enforcement_enabled: false,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Check if a task is allowed based on policy context
|
|
155
|
+
pub fn check(&self, context: Option<&PolicyContext>) -> Result<(), CodingError> {
|
|
156
|
+
// If enforcement is disabled, always allow
|
|
157
|
+
if !self.enforcement_enabled {
|
|
158
|
+
return Ok(());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If admin is not required, always allow
|
|
162
|
+
if !self.require_admin {
|
|
163
|
+
return Ok(());
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If no context provided, deny (fail-safe)
|
|
167
|
+
let ctx = context.ok_or_else(|| {
|
|
168
|
+
CodingError::PolicyError("Policy context required but not provided".into())
|
|
169
|
+
})?;
|
|
170
|
+
|
|
171
|
+
// If context has enforcement disabled, allow
|
|
172
|
+
if !ctx.enforcement_enabled {
|
|
173
|
+
return Ok(());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check permission level
|
|
177
|
+
if ctx.permission_level.is_admin() {
|
|
178
|
+
return Ok(());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Deny with detailed error
|
|
182
|
+
Err(CodingError::PolicyError(format!(
|
|
183
|
+
"Admin privileges required. User '{}' has level '{}'",
|
|
184
|
+
ctx.user_id.as_deref().unwrap_or("unknown"),
|
|
185
|
+
ctx.permission_level.as_str()
|
|
186
|
+
)))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Returns true if admin is required
|
|
190
|
+
pub fn requires_admin(&self) -> bool {
|
|
191
|
+
self.require_admin && self.enforcement_enabled
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[cfg(test)]
|
|
196
|
+
mod tests {
|
|
197
|
+
use super::*;
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
fn test_permission_level_is_admin() {
|
|
201
|
+
assert!(PermissionLevel::Admin.is_admin());
|
|
202
|
+
assert!(!PermissionLevel::User.is_admin());
|
|
203
|
+
assert!(!PermissionLevel::Readonly.is_admin());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn test_permission_level_from_str() {
|
|
208
|
+
assert_eq!(
|
|
209
|
+
PermissionLevel::from_str("admin"),
|
|
210
|
+
Some(PermissionLevel::Admin)
|
|
211
|
+
);
|
|
212
|
+
assert_eq!(
|
|
213
|
+
PermissionLevel::from_str("user"),
|
|
214
|
+
Some(PermissionLevel::User)
|
|
215
|
+
);
|
|
216
|
+
assert_eq!(
|
|
217
|
+
PermissionLevel::from_str("readonly"),
|
|
218
|
+
Some(PermissionLevel::Readonly)
|
|
219
|
+
);
|
|
220
|
+
assert_eq!(PermissionLevel::from_str("invalid"), None);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[test]
|
|
224
|
+
fn test_policy_context_admin() {
|
|
225
|
+
let ctx = PolicyContext::admin();
|
|
226
|
+
assert!(ctx.permission_level.is_admin());
|
|
227
|
+
assert!(ctx.enforcement_enabled);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[test]
|
|
231
|
+
fn test_admin_policy_check_admin() {
|
|
232
|
+
let policy = AdminPolicy::new();
|
|
233
|
+
let ctx = PolicyContext::admin();
|
|
234
|
+
assert!(policy.check(Some(&ctx)).is_ok());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test]
|
|
238
|
+
fn test_admin_policy_check_user_denied() {
|
|
239
|
+
let policy = AdminPolicy::new();
|
|
240
|
+
let ctx = PolicyContext::user();
|
|
241
|
+
assert!(policy.check(Some(&ctx)).is_err());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#[test]
|
|
245
|
+
fn test_admin_policy_check_readonly_denied() {
|
|
246
|
+
let policy = AdminPolicy::new();
|
|
247
|
+
let ctx = PolicyContext::readonly();
|
|
248
|
+
let result = policy.check(Some(&ctx));
|
|
249
|
+
assert!(result.is_err());
|
|
250
|
+
if let Err(CodingError::PolicyError(msg)) = result {
|
|
251
|
+
assert!(msg.contains("Admin privileges required"));
|
|
252
|
+
} else {
|
|
253
|
+
panic!("Expected PolicyError");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[test]
|
|
258
|
+
fn test_admin_policy_check_no_context_denied() {
|
|
259
|
+
let policy = AdminPolicy::new();
|
|
260
|
+
let result = policy.check(None);
|
|
261
|
+
assert!(result.is_err());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn test_admin_policy_permissive() {
|
|
266
|
+
let policy = AdminPolicy::permissive();
|
|
267
|
+
let ctx = PolicyContext::readonly();
|
|
268
|
+
assert!(policy.check(Some(&ctx)).is_ok());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn test_admin_policy_disabled() {
|
|
273
|
+
let policy = AdminPolicy::disabled();
|
|
274
|
+
assert!(policy.check(None).is_ok());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[test]
|
|
278
|
+
fn test_policy_context_unenforced() {
|
|
279
|
+
let ctx = PolicyContext::unenforced();
|
|
280
|
+
assert!(!ctx.enforcement_enabled);
|
|
281
|
+
|
|
282
|
+
let policy = AdminPolicy::new();
|
|
283
|
+
assert!(policy.check(Some(&ctx)).is_ok());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#[test]
|
|
287
|
+
fn test_policy_context_builder() {
|
|
288
|
+
let ctx = PolicyContext::admin()
|
|
289
|
+
.with_user_id("test_user")
|
|
290
|
+
.with_chat_id(12345)
|
|
291
|
+
.with_account_tag("test_account");
|
|
292
|
+
|
|
293
|
+
assert_eq!(ctx.user_id, Some("test_user".to_string()));
|
|
294
|
+
assert_eq!(ctx.chat_id, Some(12345));
|
|
295
|
+
assert_eq!(ctx.account_tag, Some("test_account".to_string()));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
|
|
3
|
+
pub fn get_read_tools() -> Vec<serde_json::Value> {
|
|
4
|
+
vec![
|
|
5
|
+
json!({
|
|
6
|
+
"type": "function",
|
|
7
|
+
"function": {
|
|
8
|
+
"name": "read_file",
|
|
9
|
+
"description": "Read the contents of a file",
|
|
10
|
+
"parameters": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"path": {"type": "string", "description": "File path relative to repo root"}
|
|
14
|
+
},
|
|
15
|
+
"required": ["path"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}),
|
|
19
|
+
json!({
|
|
20
|
+
"type": "function",
|
|
21
|
+
"function": {
|
|
22
|
+
"name": "list_dir",
|
|
23
|
+
"description": "List contents of a directory",
|
|
24
|
+
"parameters": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"path": {"type": "string", "description": "Directory path relative to repo root"}
|
|
28
|
+
},
|
|
29
|
+
"required": ["path"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn get_write_tools() -> Vec<serde_json::Value> {
|
|
37
|
+
let mut tools = get_read_tools();
|
|
38
|
+
tools.push(json!({
|
|
39
|
+
"type": "function",
|
|
40
|
+
"function": {
|
|
41
|
+
"name": "write_file",
|
|
42
|
+
"description": "Write content to a file (creates or overwrites)",
|
|
43
|
+
"parameters": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"path": {"type": "string", "description": "File path relative to repo root"},
|
|
47
|
+
"content": {"type": "string", "description": "Content to write"}
|
|
48
|
+
},
|
|
49
|
+
"required": ["path", "content"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
tools
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn tools_to_anthropic_format(tools: &[serde_json::Value]) -> Vec<serde_json::Value> {
|
|
57
|
+
tools
|
|
58
|
+
.iter()
|
|
59
|
+
.filter_map(|t| {
|
|
60
|
+
let func = t.get("function")?;
|
|
61
|
+
Some(json!({
|
|
62
|
+
"name": func["name"],
|
|
63
|
+
"description": func["description"],
|
|
64
|
+
"input_schema": func["parameters"]
|
|
65
|
+
}))
|
|
66
|
+
})
|
|
67
|
+
.collect()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn tools_to_openai_format(tools: &[serde_json::Value]) -> Vec<serde_json::Value> {
|
|
71
|
+
tools.to_vec()
|
|
72
|
+
}
|