atabey 0.0.14 → 0.0.16
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/LICENSE +672 -21
- package/PRIVACY.md +8 -14
- package/README.md +183 -25
- package/bin/cli.js +72 -5
- package/dist/framework-mcp/src/index.js +145 -13
- package/dist/framework-mcp/src/index.js.map +1 -1
- package/dist/framework-mcp/src/tools/control_plane/registry.d.ts +1 -1
- package/dist/framework-mcp/src/tools/control_plane/registry.js +52 -4
- package/dist/framework-mcp/src/tools/control_plane/registry.js.map +1 -1
- package/dist/framework-mcp/src/tools/definitions.js +1 -1
- package/dist/framework-mcp/src/tools/file_system/read_file.js +5 -0
- package/dist/framework-mcp/src/tools/file_system/read_file.js.map +1 -1
- package/dist/framework-mcp/src/tools/messaging/approve_operation.d.ts +10 -8
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js +12 -12
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js.map +1 -1
- package/dist/framework-mcp/src/tools/messaging/ask_human.d.ts +8 -8
- package/dist/framework-mcp/src/tools/messaging/ask_human.js +8 -8
- package/dist/framework-mcp/src/tools/messaging/send_message.js +19 -3
- package/dist/framework-mcp/src/tools/messaging/send_message.js.map +1 -1
- package/dist/framework-mcp/src/tools/observability/check_ports.d.ts +1 -0
- package/dist/framework-mcp/src/tools/observability/check_ports.js +21 -7
- package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -1
- package/dist/framework-mcp/src/tools/schemas.d.ts +4 -4
- package/dist/framework-mcp/src/tools/search/grep_search.js +11 -4
- package/dist/framework-mcp/src/tools/search/grep_search.js.map +1 -1
- package/dist/framework-mcp/src/tools/search/list_dir.js +3 -0
- package/dist/framework-mcp/src/tools/search/list_dir.js.map +1 -1
- package/dist/framework-mcp/src/tools/shell/run_command.js +185 -93
- package/dist/framework-mcp/src/tools/shell/run_command.js.map +1 -1
- package/dist/framework-mcp/src/utils/auth.d.ts +26 -2
- package/dist/framework-mcp/src/utils/auth.js +74 -7
- package/dist/framework-mcp/src/utils/auth.js.map +1 -1
- package/dist/framework-mcp/src/utils/compliance.js +45 -13
- package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
- package/dist/framework-mcp/src/utils/discipline.d.ts +1 -0
- package/dist/framework-mcp/src/utils/discipline.js +42 -3
- package/dist/framework-mcp/src/utils/discipline.js.map +1 -1
- package/dist/framework-mcp/src/utils/human-in-loop.d.ts +35 -2
- package/dist/framework-mcp/src/utils/human-in-loop.js +114 -10
- package/dist/framework-mcp/src/utils/human-in-loop.js.map +1 -1
- package/dist/framework-mcp/src/utils/loop-detector.js +3 -3
- package/dist/framework-mcp/src/utils/loop-detector.js.map +1 -1
- package/dist/framework-mcp/src/utils/permissions.d.ts +67 -3
- package/dist/framework-mcp/src/utils/permissions.js +179 -43
- package/dist/framework-mcp/src/utils/permissions.js.map +1 -1
- package/dist/framework-mcp/src/utils/prompt-injection.d.ts +20 -0
- package/dist/framework-mcp/src/utils/prompt-injection.js +64 -0
- package/dist/framework-mcp/src/utils/prompt-injection.js.map +1 -0
- package/dist/framework-mcp/src/utils/quality.js +4 -3
- package/dist/framework-mcp/src/utils/quality.js.map +1 -1
- package/dist/framework-mcp/src/utils/silent-router.d.ts +4 -2
- package/dist/framework-mcp/src/utils/silent-router.js +35 -9
- package/dist/framework-mcp/src/utils/silent-router.js.map +1 -1
- package/dist/framework-mcp/tests/tools/file_system/permissions.test.js +174 -71
- package/dist/framework-mcp/tests/tools/file_system/permissions.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +36 -30
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js +26 -12
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js +38 -15
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js.map +1 -1
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js +152 -4
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js.map +1 -1
- package/dist/framework-mcp/tests/utils/loop-detector.test.js +0 -2
- package/dist/framework-mcp/tests/utils/loop-detector.test.js.map +1 -1
- package/dist/framework-mcp/tests/utils/prompt-injection.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/prompt-injection.test.js +43 -0
- package/dist/framework-mcp/tests/utils/prompt-injection.test.js.map +1 -0
- package/dist/src/cli/commands/dashboard.js +167 -2
- package/dist/src/cli/commands/dashboard.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-standards.js +1 -1
- package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -1
- package/dist/src/cli/commands/init.js +14 -7
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/log.js +8 -5
- package/dist/src/cli/commands/log.js.map +1 -1
- package/dist/src/cli/commands/mcp.js +3 -1
- package/dist/src/cli/commands/mcp.js.map +1 -1
- package/dist/src/cli/commands/orchestrate.d.ts +4 -4
- package/dist/src/cli/commands/orchestrate.js +14 -2
- package/dist/src/cli/commands/orchestrate.js.map +1 -1
- package/dist/src/cli/commands/script.js +8 -1
- package/dist/src/cli/commands/script.js.map +1 -1
- package/dist/src/cli/commands/status.js +21 -1
- package/dist/src/cli/commands/status.js.map +1 -1
- package/dist/src/cli/utils/memory.js +6 -5
- package/dist/src/cli/utils/memory.js.map +1 -1
- package/dist/src/cli/utils/schemas.d.ts +2 -2
- package/dist/src/contracts/tasks.d.ts +4 -4
- package/dist/src/modules/contracts/api.d.ts +6 -6
- package/dist/src/modules/engines/agent-executor.js +41 -20
- package/dist/src/modules/engines/agent-executor.js.map +1 -1
- package/dist/src/modules/engines/agent-loop.d.ts +5 -5
- package/dist/src/modules/engines/agent-loop.js +5 -5
- package/dist/src/modules/engines/evaluation-engine.d.ts +9 -2
- package/dist/src/modules/engines/evaluation-engine.js +77 -9
- package/dist/src/modules/engines/evaluation-engine.js.map +1 -1
- package/dist/src/modules/engines/quality-gate.js +4 -2
- package/dist/src/modules/engines/quality-gate.js.map +1 -1
- package/dist/src/modules/engines/risk-engine.d.ts +17 -1
- package/dist/src/modules/engines/risk-engine.js +98 -3
- package/dist/src/modules/engines/risk-engine.js.map +1 -1
- package/dist/src/modules/engines/routing-engine.d.ts +26 -1
- package/dist/src/modules/engines/routing-engine.js +97 -1
- package/dist/src/modules/engines/routing-engine.js.map +1 -1
- package/dist/src/modules/providers/shared.js +2 -3
- package/dist/src/modules/providers/shared.js.map +1 -1
- package/dist/src/shared/pii.js +1 -1
- package/dist/src/shared/pii.js.map +1 -1
- package/dist/src/shared/storage.d.ts +60 -0
- package/dist/src/shared/storage.js +77 -3
- package/dist/src/shared/storage.js.map +1 -1
- package/dist/tests/cli/commands/dashboard-coverage.test.js +3 -1
- package/dist/tests/cli/commands/dashboard-coverage.test.js.map +1 -1
- package/dist/tests/modules/engines/agent-executor.test.js +1 -1
- package/dist/tests/modules/engines/agent-executor.test.js.map +1 -1
- package/dist/tests/modules/engines/evaluation-engine.test.js +2 -2
- package/dist/tests/modules/engines/evaluation-engine.test.js.map +1 -1
- package/dist/tests/status-cost.test.js +2 -0
- package/dist/tests/status-cost.test.js.map +1 -1
- package/dist/tests/storage-operations.test.js +31 -4
- package/dist/tests/storage-operations.test.js.map +1 -1
- package/dist/vitest.config.js +6 -4
- package/dist/vitest.config.js.map +1 -1
- package/framework-mcp/README.md +18 -32
- package/framework-mcp/dist/dashboard/assets/index-B-Eu7_J9.js +1260 -0
- package/framework-mcp/dist/dashboard/index.html +1 -1
- package/framework-mcp/dist/framework-mcp/src/index.js +145 -13
- package/framework-mcp/dist/framework-mcp/src/tools/control_plane/registry.js +52 -4
- package/framework-mcp/dist/framework-mcp/src/tools/definitions.js +1 -1
- package/framework-mcp/dist/framework-mcp/src/tools/file_system/read_file.js +5 -0
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/approve_operation.js +12 -12
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/ask_human.js +8 -8
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/send_message.js +19 -3
- package/framework-mcp/dist/framework-mcp/src/tools/observability/check_ports.js +21 -7
- package/framework-mcp/dist/framework-mcp/src/tools/search/grep_search.js +11 -4
- package/framework-mcp/dist/framework-mcp/src/tools/search/list_dir.js +3 -0
- package/framework-mcp/dist/framework-mcp/src/tools/shell/run_command.js +185 -93
- package/framework-mcp/dist/framework-mcp/src/utils/auth.js +74 -7
- package/framework-mcp/dist/framework-mcp/src/utils/compliance.js +45 -13
- package/framework-mcp/dist/framework-mcp/src/utils/discipline.js +42 -3
- package/framework-mcp/dist/framework-mcp/src/utils/human-in-loop.js +114 -10
- package/framework-mcp/dist/framework-mcp/src/utils/loop-detector.js +3 -3
- package/framework-mcp/dist/framework-mcp/src/utils/permissions.js +179 -43
- package/framework-mcp/dist/framework-mcp/src/utils/prompt-injection.js +63 -0
- package/framework-mcp/dist/framework-mcp/src/utils/quality.js +4 -3
- package/framework-mcp/dist/framework-mcp/src/utils/silent-router.js +35 -9
- package/framework-mcp/dist/src/cli/utils/memory.js +6 -5
- package/framework-mcp/dist/src/modules/engines/evaluation-engine.js +170 -0
- package/framework-mcp/dist/src/modules/engines/quality-gate.js +4 -2
- package/framework-mcp/dist/src/modules/engines/risk-engine.js +98 -3
- package/framework-mcp/dist/src/modules/engines/routing-engine.js +97 -1
- package/framework-mcp/dist/src/modules/providers/shared.js +2 -3
- package/framework-mcp/dist/src/shared/pii.js +1 -1
- package/framework-mcp/dist/src/shared/storage.js +77 -3
- package/framework-mcp/package.json +21 -18
- package/mcp.json +3 -2
- package/package.json +4 -5
- package/framework-mcp/dist/dashboard/assets/index-B2mYld0c.js +0 -1221
|
@@ -31,7 +31,49 @@ const CONFIG = {
|
|
|
31
31
|
AUTO_APPROVE_LOW_RISK: process.env.MCP_AUTO_APPROVE_LOW_RISK !== "false",
|
|
32
32
|
/** Approval timeout in seconds */
|
|
33
33
|
APPROVAL_TIMEOUT_SECONDS: parseInt(process.env.MCP_APPROVAL_TIMEOUT || "300", 10), // 5 min
|
|
34
|
+
/**
|
|
35
|
+
* Minimum risk score requiring Lead/Admin approval (legacy compat).
|
|
36
|
+
* Superseded by ROLE_RISK_MAP below — kept for backward compatibility with env var.
|
|
37
|
+
*/
|
|
38
|
+
RBAC_HIGH_RISK_THRESHOLD: parseInt(process.env.MCP_RBAC_HIGH_RISK_THRESHOLD || "80", 10),
|
|
34
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Maximum risk score each role is allowed to approve.
|
|
42
|
+
* A role can approve any operation with riskScore <= its max.
|
|
43
|
+
* Override via MCP_RBAC_ROLE_* env vars.
|
|
44
|
+
*
|
|
45
|
+
* junior → cannot approve anything (max = -1)
|
|
46
|
+
* developer → can approve MEDIUM risk (0–59)
|
|
47
|
+
* lead → can approve HIGH risk (0–79)
|
|
48
|
+
* admin → can approve any risk (0–100)
|
|
49
|
+
*/
|
|
50
|
+
export const ROLE_RISK_MAP = {
|
|
51
|
+
admin: parseInt(process.env.MCP_RBAC_ROLE_ADMIN || "100", 10),
|
|
52
|
+
lead: parseInt(process.env.MCP_RBAC_ROLE_LEAD || "79", 10),
|
|
53
|
+
developer: parseInt(process.env.MCP_RBAC_ROLE_DEVELOPER || "59", 10),
|
|
54
|
+
junior: parseInt(process.env.MCP_RBAC_ROLE_JUNIOR || "-1", 10), // cannot approve
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Returns the minimum role required to approve an operation at the given risk score.
|
|
58
|
+
*/
|
|
59
|
+
export function getMinimumRoleForScore(riskScore) {
|
|
60
|
+
if (riskScore <= ROLE_RISK_MAP.developer)
|
|
61
|
+
return "developer";
|
|
62
|
+
if (riskScore <= ROLE_RISK_MAP.lead)
|
|
63
|
+
return "lead";
|
|
64
|
+
return "admin";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reads the active user role from environment only.
|
|
68
|
+
* AI-supplied userRole arguments are NOT trusted.
|
|
69
|
+
*/
|
|
70
|
+
function getActiveUserRole() {
|
|
71
|
+
const envRole = process.env.MCP_USER_ROLE?.trim().toLowerCase();
|
|
72
|
+
if (envRole === "admin" || envRole === "lead" || envRole === "developer" || envRole === "junior") {
|
|
73
|
+
return envRole;
|
|
74
|
+
}
|
|
75
|
+
return "junior"; // safest default
|
|
76
|
+
}
|
|
35
77
|
// ─── Active Approval Requests ─────────────────────────────────────
|
|
36
78
|
const activeApprovals = new Map();
|
|
37
79
|
/**
|
|
@@ -44,12 +86,25 @@ export function getRiskLevel(score) {
|
|
|
44
86
|
return "MEDIUM";
|
|
45
87
|
return "LOW";
|
|
46
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Create an approval request for a high-risk operation.
|
|
91
|
+
* Returns the approval request and a message for the AI/developer.
|
|
92
|
+
*/
|
|
93
|
+
function generatePasscode() {
|
|
94
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
95
|
+
let code = "";
|
|
96
|
+
for (let i = 0; i < 5; i++) {
|
|
97
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
98
|
+
}
|
|
99
|
+
return code;
|
|
100
|
+
}
|
|
47
101
|
/**
|
|
48
102
|
* Create an approval request for a high-risk operation.
|
|
49
103
|
* Returns the approval request and a message for the AI/developer.
|
|
50
104
|
*/
|
|
51
105
|
export function createApprovalRequest(traceId, toolName, agent, riskScore, reason, args) {
|
|
52
106
|
const now = Date.now();
|
|
107
|
+
const passcode = generatePasscode();
|
|
53
108
|
const request = {
|
|
54
109
|
traceId,
|
|
55
110
|
toolName,
|
|
@@ -61,8 +116,11 @@ export function createApprovalRequest(traceId, toolName, agent, riskScore, reaso
|
|
|
61
116
|
createdAt: now,
|
|
62
117
|
expiresAt: now + CONFIG.APPROVAL_TIMEOUT_SECONDS * 1000,
|
|
63
118
|
status: "PENDING",
|
|
119
|
+
passcode,
|
|
64
120
|
};
|
|
65
121
|
activeApprovals.set(traceId, request);
|
|
122
|
+
// Print passcode to stderr so the human developer sees it but AI cannot access it
|
|
123
|
+
process.stderr.write(`\n[ATABEY SECURITY GATES] APPROVAL PASSCODE FOR TRACE ${traceId}: ${passcode}\n\n`);
|
|
66
124
|
// Create a Hermes approval message
|
|
67
125
|
AtabeyStorage.saveMessage({
|
|
68
126
|
from: asAgentID(agent),
|
|
@@ -92,14 +150,14 @@ export function createApprovalRequest(traceId, toolName, agent, riskScore, reaso
|
|
|
92
150
|
` Trace ID: ${traceId}`,
|
|
93
151
|
"",
|
|
94
152
|
"─── IN-CHAT APPROVAL ────────────────────────────────────────",
|
|
95
|
-
"
|
|
153
|
+
"This is a high-risk operation. To prevent autonomous bypass,",
|
|
154
|
+
"Atabey has printed a 5-character PASSCODE to your terminal's stderr.",
|
|
96
155
|
"",
|
|
97
|
-
|
|
98
|
-
`
|
|
99
|
-
" To list: approve_operation { \"action\": \"list\" }",
|
|
156
|
+
"Ask the user for the passcode, then call the approve_operation tool:",
|
|
157
|
+
` approve_operation { "action": "approve", "traceId": "${traceId}", "passcode": "<passcode_from_user>" }`,
|
|
100
158
|
"─────────────────────────────────────────────────────────────",
|
|
101
159
|
"",
|
|
102
|
-
`Alternative: atabey
|
|
160
|
+
`Alternative: Run 'atabey approve ${traceId}' in your local terminal.`,
|
|
103
161
|
].join("\n");
|
|
104
162
|
return { request, message };
|
|
105
163
|
}
|
|
@@ -147,8 +205,16 @@ export function getApprovalStatus(traceId) {
|
|
|
147
205
|
}
|
|
148
206
|
/**
|
|
149
207
|
* Approve a pending operation.
|
|
208
|
+
*
|
|
209
|
+
* RBAC enforcement (AI Governance layer):
|
|
210
|
+
* - Role is read from process.env.MCP_USER_ROLE ONLY.
|
|
211
|
+
* - The userRole parameter is accepted for API compatibility but completely IGNORED.
|
|
212
|
+
* This prevents AI agents (Claude Code, Gemini, Cursor) from escalating privilege
|
|
213
|
+
* by passing {userRole: "admin"} in the tool call arguments.
|
|
214
|
+
* - The passcode printed to stderr is the second factor — AI cannot read stderr.
|
|
150
215
|
*/
|
|
151
|
-
export function approveOperation(traceId
|
|
216
|
+
export function approveOperation(traceId, passcode, _userRoleIgnored // kept for API compat — value is intentionally ignored
|
|
217
|
+
) {
|
|
152
218
|
const request = activeApprovals.get(traceId);
|
|
153
219
|
if (!request) {
|
|
154
220
|
return { success: false, message: `No pending approval found for trace: ${traceId}` };
|
|
@@ -156,6 +222,29 @@ export function approveOperation(traceId) {
|
|
|
156
222
|
if (request.status !== "PENDING") {
|
|
157
223
|
return { success: false, message: `Approval for ${traceId} is already ${request.status}` };
|
|
158
224
|
}
|
|
225
|
+
// ── RBAC Check ──────────────────────────────────────────────────────────
|
|
226
|
+
// Role is read exclusively from the environment — not from the tool argument.
|
|
227
|
+
// This ensures Claude Code, Gemini CLI, Cursor, or any MCP client cannot
|
|
228
|
+
// escalate privilege by injecting a role in the tool args.
|
|
229
|
+
const role = getActiveUserRole();
|
|
230
|
+
const maxApprovable = ROLE_RISK_MAP[role];
|
|
231
|
+
if (request.riskScore <= maxApprovable) {
|
|
232
|
+
// Role is allowed — proceed to passcode check
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const requiredRole = getMinimumRoleForScore(request.riskScore);
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
message: `[RBAC] Approval denied: Risk score ${request.riskScore}/100 requires at least '${requiredRole}' role. ` +
|
|
239
|
+
`Current role: '${role}' (max approvable: ${maxApprovable}). ` +
|
|
240
|
+
"Set MCP_USER_ROLE env var to a role with sufficient authority."
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// ── End RBAC Check ──────────────────────────────────────────────────────
|
|
244
|
+
// Passcode second-factor (printed to stderr — AI cannot intercept)
|
|
245
|
+
if (request.passcode && (!passcode || passcode.trim().toUpperCase() !== request.passcode)) {
|
|
246
|
+
return { success: false, message: "Invalid or missing approval passcode. Verify the passcode printed to stderr." };
|
|
247
|
+
}
|
|
159
248
|
request.status = "APPROVED";
|
|
160
249
|
// Update Hermes message status
|
|
161
250
|
const messages = AtabeyStorage.getPendingMessages();
|
|
@@ -165,12 +254,17 @@ export function approveOperation(traceId) {
|
|
|
165
254
|
AtabeyStorage.updateMessageStatus(msg.id, "APPROVED");
|
|
166
255
|
}
|
|
167
256
|
}
|
|
168
|
-
return { success: true, message: `Operation ${traceId} approved. You may retry.` };
|
|
257
|
+
return { success: true, message: `Operation ${traceId} approved by '${role}'. You may retry.` };
|
|
169
258
|
}
|
|
170
259
|
/**
|
|
171
260
|
* Reject a pending operation.
|
|
261
|
+
*
|
|
262
|
+
* RBAC: junior-role users cannot reject HIGH/CRITICAL operations.
|
|
263
|
+
* This prevents rogue or misconfigured recon agents from killing
|
|
264
|
+
* manager-delegated critical operations.
|
|
172
265
|
*/
|
|
173
|
-
export function rejectOperation(traceId
|
|
266
|
+
export function rejectOperation(traceId, _userRoleIgnored // kept for API compat — value intentionally ignored
|
|
267
|
+
) {
|
|
174
268
|
const request = activeApprovals.get(traceId);
|
|
175
269
|
if (!request) {
|
|
176
270
|
return { success: false, message: `No pending approval found for trace: ${traceId}` };
|
|
@@ -178,6 +272,16 @@ export function rejectOperation(traceId) {
|
|
|
178
272
|
if (request.status !== "PENDING") {
|
|
179
273
|
return { success: false, message: `Approval for ${traceId} is already ${request.status}` };
|
|
180
274
|
}
|
|
275
|
+
// RBAC: reject also requires minimum role for HIGH+ operations
|
|
276
|
+
const role = getActiveUserRole();
|
|
277
|
+
const canReject = !(role === "junior" && request.riskScore >= CONFIG.HIGH_RISK_THRESHOLD);
|
|
278
|
+
if (!canReject) {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
message: `[RBAC] Reject denied: Risk score ${request.riskScore}/100 operations cannot be rejected by '${role}'. ` +
|
|
282
|
+
"Minimum role required to reject is 'developer'. Set MCP_USER_ROLE env var."
|
|
283
|
+
};
|
|
284
|
+
}
|
|
181
285
|
request.status = "REJECTED";
|
|
182
286
|
// Update Hermes message status
|
|
183
287
|
const messages = AtabeyStorage.getPendingMessages();
|
|
@@ -187,7 +291,7 @@ export function rejectOperation(traceId) {
|
|
|
187
291
|
AtabeyStorage.updateMessageStatus(msg.id, "REJECTED");
|
|
188
292
|
}
|
|
189
293
|
}
|
|
190
|
-
return { success: true, message: `Operation ${traceId} rejected.` };
|
|
294
|
+
return { success: true, message: `Operation ${traceId} rejected by '${role}'.` };
|
|
191
295
|
}
|
|
192
296
|
/**
|
|
193
297
|
* Get all pending approval requests.
|
|
@@ -196,7 +300,7 @@ export function getPendingApprovals() {
|
|
|
196
300
|
const now = Date.now();
|
|
197
301
|
const pending = [];
|
|
198
302
|
// Clean expired
|
|
199
|
-
for (const [
|
|
303
|
+
for (const [, request] of activeApprovals) {
|
|
200
304
|
if (request.status === "PENDING" && now > request.expiresAt) {
|
|
201
305
|
request.status = "EXPIRED";
|
|
202
306
|
}
|
|
@@ -274,7 +274,7 @@ export function recordAndCheck(agent, toolName, args) {
|
|
|
274
274
|
/**
|
|
275
275
|
* Apply cooldown to an agent.
|
|
276
276
|
*/
|
|
277
|
-
function applyCooldown(history, alert,
|
|
277
|
+
function applyCooldown(history, alert, _agent) {
|
|
278
278
|
history.inCooldown = true;
|
|
279
279
|
history.cooldownUntil = alert.cooldownUntil || Date.now() + CONFIG.COOLDOWN_MS;
|
|
280
280
|
history.cooldownCount++;
|
|
@@ -347,8 +347,8 @@ export function getLoopStats(agent) {
|
|
|
347
347
|
*/
|
|
348
348
|
export function getAllLoopStats() {
|
|
349
349
|
const stats = {};
|
|
350
|
-
for (const [
|
|
351
|
-
stats[
|
|
350
|
+
for (const [agentName] of agentHistories) {
|
|
351
|
+
stats[agentName] = getLoopStats(agentName);
|
|
352
352
|
}
|
|
353
353
|
return stats;
|
|
354
354
|
}
|
|
@@ -1,6 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─── RBAC PERMISSION MATRIX ───────────────────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Enforces agent-scoped file system permissions at the MCP tool layer.
|
|
5
|
+
* This is the file-level RBAC enforcement point for:
|
|
6
|
+
* - Claude Code (calls write_file / read_file via MCP sub-agents)
|
|
7
|
+
* - Gemini CLI (calls write_file / read_file via MCP server)
|
|
8
|
+
* - Cursor (calls write_file / read_file via MCP rules)
|
|
9
|
+
* - Codex CLI (calls write_file / read_file via agent allowed-tools)
|
|
10
|
+
* - Antigravity (calls write_file / read_file via customAgentSpec tools)
|
|
11
|
+
*
|
|
12
|
+
* Permission matrix is stored in `.atabey/permission-matrix.json`:
|
|
13
|
+
* {
|
|
14
|
+
* "@frontend": { "write": ["apps/web/**"], "read": ["apps/web/**", "docs/**"] },
|
|
15
|
+
* "@backend": { "write": ["apps/backend/**"], "read": ["apps/backend/**", "apps/web/types/**"] }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Agent Tier enforcement:
|
|
19
|
+
* supreme (@manager, @security) — no write/read restrictions (omnipotent)
|
|
20
|
+
* core (@backend, @frontend, ...) — matrix-enforced
|
|
21
|
+
* recon (@explorer, @git, @analyst, @native) — read-only by default,
|
|
22
|
+
* write requires explicit matrix entry
|
|
23
|
+
*/
|
|
1
24
|
import fs from "fs";
|
|
2
25
|
import path from "path";
|
|
3
26
|
import { resolveFrameworkDir } from "./security.js";
|
|
27
|
+
// ─── Tier hierarchy ─────────────────────────────────────────────────────────
|
|
28
|
+
/** Well-known tier for agents defined in the built-in registry */
|
|
29
|
+
const AGENT_TIER_MAP = {
|
|
30
|
+
"@manager": "supreme",
|
|
31
|
+
"@security": "supreme",
|
|
32
|
+
"@backend": "core",
|
|
33
|
+
"@frontend": "core",
|
|
34
|
+
"@mobile": "core",
|
|
35
|
+
"@quality": "core",
|
|
36
|
+
"@database": "core",
|
|
37
|
+
"@devops": "core",
|
|
38
|
+
"@architect": "core",
|
|
39
|
+
"@explorer": "recon",
|
|
40
|
+
"@git": "recon",
|
|
41
|
+
"@analyst": "recon",
|
|
42
|
+
"@native": "recon",
|
|
43
|
+
};
|
|
44
|
+
export function getAgentTier(agentName) {
|
|
45
|
+
return AGENT_TIER_MAP[agentName] ?? "core"; // unknown agents default to core
|
|
46
|
+
}
|
|
47
|
+
// ─── Glob → RegExp ──────────────────────────────────────────────────────────
|
|
4
48
|
function globToRegex(glob) {
|
|
5
49
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6
50
|
const step1 = escaped.replace(/\*\*/g, "__DBL_STR__");
|
|
@@ -8,64 +52,156 @@ function globToRegex(glob) {
|
|
|
8
52
|
const regexStr = "^" + step2.replace(/__DBL_STR__/g, ".*") + "$";
|
|
9
53
|
return new RegExp(regexStr);
|
|
10
54
|
}
|
|
55
|
+
// ─── Active Agent Resolution ─────────────────────────────────────────────────
|
|
11
56
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
57
|
+
* Resolves the active agent for the current operation.
|
|
58
|
+
*
|
|
59
|
+
* Priority order:
|
|
60
|
+
* 1. ATABEY_ACTIVE_AGENT env var (set by AgentLoop per-invocation — most reliable)
|
|
61
|
+
* 2. `.atabey/memory/status.json` first EXECUTING agent (legacy fallback)
|
|
62
|
+
*
|
|
63
|
+
* Note: The traceId-scoped approach is preferred when AgentLoop sets
|
|
64
|
+
* ATABEY_ACTIVE_AGENT before spawning the MCP tool call.
|
|
15
65
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const matrixPath = path.join(absoluteFrameworkPath, "permission-matrix.json");
|
|
22
|
-
// If no permission matrix exists, skip enforcement (default allow)
|
|
23
|
-
if (!fs.existsSync(matrixPath)) {
|
|
24
|
-
return;
|
|
66
|
+
export function resolveActiveAgent(frameworkDir) {
|
|
67
|
+
// 1. Env var (highest reliability — set by orchestration layer)
|
|
68
|
+
const envAgent = process.env.ATABEY_ACTIVE_AGENT?.trim();
|
|
69
|
+
if (envAgent) {
|
|
70
|
+
return envAgent.startsWith("@") ? envAgent : `@${envAgent}`;
|
|
25
71
|
}
|
|
26
|
-
|
|
72
|
+
// 2. status.json fallback
|
|
73
|
+
const statusPath = path.join(frameworkDir, "memory", "status.json");
|
|
74
|
+
if (!fs.existsSync(statusPath))
|
|
75
|
+
return null;
|
|
27
76
|
try {
|
|
28
|
-
|
|
77
|
+
const status = JSON.parse(fs.readFileSync(statusPath, "utf8"));
|
|
78
|
+
for (const [agentName, info] of Object.entries(status)) {
|
|
79
|
+
if (info.state === "EXECUTING") {
|
|
80
|
+
return agentName.startsWith("@") ? agentName : `@${agentName}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
process.stderr.write(`[Permissions] Warning: Failed to read status.json: ${String(e)}\n`);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// ─── Matrix Loader ───────────────────────────────────────────────────────────
|
|
90
|
+
function loadMatrix(frameworkDir) {
|
|
91
|
+
const matrixPath = path.join(frameworkDir, "permission-matrix.json");
|
|
92
|
+
if (!fs.existsSync(matrixPath))
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(fs.readFileSync(matrixPath, "utf8"));
|
|
29
96
|
}
|
|
30
97
|
catch (e) {
|
|
31
98
|
throw new Error(`Failed to parse permission-matrix.json: ${String(e)}`, { cause: e });
|
|
32
99
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Internal enforcement logic for both read and write operations.
|
|
103
|
+
*
|
|
104
|
+
* Enforcement logic per tier:
|
|
105
|
+
* - supreme: always allowed (no matrix needed)
|
|
106
|
+
* - core: enforced by matrix; if no rule defined → default-allow
|
|
107
|
+
* - recon: write → always denied unless explicit matrix allow
|
|
108
|
+
* read → allowed by default (recon agents must be able to explore)
|
|
109
|
+
*/
|
|
110
|
+
function enforcePermission(projectRoot, targetFilePath, accessType) {
|
|
111
|
+
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
112
|
+
const absoluteFrameworkPath = path.isAbsolute(frameworkDir)
|
|
113
|
+
? frameworkDir
|
|
114
|
+
: path.resolve(projectRoot, frameworkDir);
|
|
115
|
+
const activeAgent = resolveActiveAgent(absoluteFrameworkPath);
|
|
116
|
+
// No active agent identified → default-allow (non-agent call, e.g. CLI tools)
|
|
117
|
+
if (!activeAgent)
|
|
118
|
+
return;
|
|
119
|
+
const tier = getAgentTier(activeAgent);
|
|
120
|
+
// Supreme agents are omnipotent — no matrix enforcement
|
|
121
|
+
if (tier === "supreme")
|
|
122
|
+
return;
|
|
123
|
+
// Recon agents: block writes unless explicitly allowed in matrix
|
|
124
|
+
if (tier === "recon" && accessType === "write") {
|
|
125
|
+
const matrix = loadMatrix(absoluteFrameworkPath);
|
|
126
|
+
if (!matrix) {
|
|
127
|
+
throw new Error(`Permission Denied: Agent ${activeAgent} (tier: recon) cannot write files. ` +
|
|
128
|
+
"Recon agents (@explorer, @git, @analyst, @native) are read-only by default. " +
|
|
129
|
+
"Add an explicit write rule to permission-matrix.json to grant write access.");
|
|
47
130
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
131
|
+
const agentRules = matrix[activeAgent];
|
|
132
|
+
if (!agentRules?.write?.length) {
|
|
133
|
+
throw new Error(`Permission Denied: Agent ${activeAgent} (tier: recon) has no write rules in permission-matrix.json. ` +
|
|
134
|
+
"Recon agents are read-only by default.");
|
|
51
135
|
}
|
|
136
|
+
// Fall through to glob check below
|
|
52
137
|
}
|
|
53
|
-
|
|
54
|
-
|
|
138
|
+
const matrix = loadMatrix(absoluteFrameworkPath);
|
|
139
|
+
// No matrix file → default-allow (matrix is opt-in)
|
|
140
|
+
if (!matrix)
|
|
55
141
|
return;
|
|
56
|
-
}
|
|
57
142
|
const agentRules = matrix[activeAgent];
|
|
58
|
-
//
|
|
59
|
-
if (!agentRules
|
|
143
|
+
// No rules for this agent → default-allow
|
|
144
|
+
if (!agentRules)
|
|
145
|
+
return;
|
|
146
|
+
const rules = accessType === "write" ? agentRules.write : (agentRules.read ?? null);
|
|
147
|
+
// No rule for this access type → default-allow
|
|
148
|
+
if (!rules)
|
|
60
149
|
return;
|
|
61
|
-
}
|
|
62
|
-
// Resolve target path relative to project root for glob matching
|
|
63
150
|
const relativeTargetPath = path.relative(projectRoot, path.resolve(projectRoot, targetFilePath));
|
|
64
|
-
const allowed =
|
|
65
|
-
const regex = globToRegex(glob);
|
|
66
|
-
return regex.test(relativeTargetPath);
|
|
67
|
-
});
|
|
151
|
+
const allowed = rules.some(glob => globToRegex(glob).test(relativeTargetPath));
|
|
68
152
|
if (!allowed) {
|
|
69
|
-
throw new Error(`Permission Denied: Agent ${activeAgent}
|
|
153
|
+
throw new Error(`Permission Denied: Agent ${activeAgent} (tier: ${tier}) is not authorized to ` +
|
|
154
|
+
`${accessType} "${relativeTargetPath}". ` +
|
|
155
|
+
`Matrix rules restrict ${accessType} to: [${rules.join(", ")}].`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Verifies that the active agent has WRITE permission for the target file.
|
|
161
|
+
* Called by: write_file, replace_text, batch_surgical_edit, patch_file
|
|
162
|
+
*
|
|
163
|
+
* Throws a descriptive error if permission is denied.
|
|
164
|
+
*/
|
|
165
|
+
export function verifyWritePermission(projectRoot, targetFilePath) {
|
|
166
|
+
enforcePermission(projectRoot, targetFilePath, "write");
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Verifies that the active agent has READ permission for the target file.
|
|
170
|
+
* Called by: read_file, view_file (alias)
|
|
171
|
+
*
|
|
172
|
+
* Throws a descriptive error if permission is denied.
|
|
173
|
+
* Note: read enforcement is SOFTER than write — recon agents can read freely
|
|
174
|
+
* unless an explicit read matrix restriction is defined.
|
|
175
|
+
*/
|
|
176
|
+
export function verifyReadPermission(projectRoot, targetFilePath) {
|
|
177
|
+
enforcePermission(projectRoot, targetFilePath, "read");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Verifies that the sender agent is allowed to send messages to the target agent
|
|
181
|
+
* based on the AgentTier hierarchy.
|
|
182
|
+
*
|
|
183
|
+
* Messaging rules (AI IDE governance layer):
|
|
184
|
+
* - Any tier → supreme: allowed (escalation always permitted)
|
|
185
|
+
* - core → core: allowed
|
|
186
|
+
* - recon → core: allowed (recon can delegate up)
|
|
187
|
+
* - recon → supreme: allowed
|
|
188
|
+
* - core → recon: BLOCKED (core agents should not delegate down to recon)
|
|
189
|
+
* - supreme → any: allowed
|
|
190
|
+
*
|
|
191
|
+
* Rationale: Recon agents (@explorer, @git) are information-gatherers.
|
|
192
|
+
* If a core agent delegates to a recon agent, it bypasses the quality gate
|
|
193
|
+
* and risk engine — a potential governance gap.
|
|
194
|
+
*/
|
|
195
|
+
export function verifyMessagingPermission(fromAgent, toAgent) {
|
|
196
|
+
const fromTier = getAgentTier(fromAgent);
|
|
197
|
+
const toTier = getAgentTier(toAgent);
|
|
198
|
+
// supreme senders: unrestricted
|
|
199
|
+
if (fromTier === "supreme")
|
|
200
|
+
return;
|
|
201
|
+
// core → recon: block (prevents quality-gate bypass via recon delegation)
|
|
202
|
+
if (fromTier === "core" && toTier === "recon") {
|
|
203
|
+
throw new Error(`[RBAC] Messaging violation: Agent ${fromAgent} (tier: core) cannot delegate to ` +
|
|
204
|
+
`${toAgent} (tier: recon). Core agents must delegate up to @manager or peer core agents. ` +
|
|
205
|
+
"Recon agents are read-only information gatherers and cannot execute delegated tasks.");
|
|
70
206
|
}
|
|
71
207
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─── PROMPT INJECTION PROTECTION ENGINE ────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Scans, detects, and neutralizes prompt injection payloads in tool outputs
|
|
5
|
+
* before they are returned to the AI assistant's context.
|
|
6
|
+
*
|
|
7
|
+
* This prevents malicious files, code comments, or external resources from
|
|
8
|
+
* hijacking the AI client (e.g., instructing it to delete files or ignore safety).
|
|
9
|
+
*/
|
|
10
|
+
import { logger } from "../../../src/shared/logger.js";
|
|
11
|
+
// Common prompt injection signatures
|
|
12
|
+
const INJECTION_PATTERNS = [
|
|
13
|
+
/ignore\s+(?:all\s+)?instructions/gi,
|
|
14
|
+
/system\s+override/gi,
|
|
15
|
+
/you\s+must\s+now/gi,
|
|
16
|
+
/you\s+are\s+now/gi,
|
|
17
|
+
/bypass\s+governance/gi,
|
|
18
|
+
/bypass\s+safety/gi,
|
|
19
|
+
/bypass\s+atabey/gi,
|
|
20
|
+
/dan\s+mode/gi,
|
|
21
|
+
/developer\s+mode/gi,
|
|
22
|
+
/override\s+system\s+instructions/gi,
|
|
23
|
+
/ignore\s+safety\s+rules/gi,
|
|
24
|
+
/\[system\s+prompt\]/gi,
|
|
25
|
+
/<system_prompt>/gi,
|
|
26
|
+
/disable\s+restrictions/gi
|
|
27
|
+
];
|
|
28
|
+
export class PromptInjectionProtection {
|
|
29
|
+
/**
|
|
30
|
+
* Scan tool response text for injection patterns.
|
|
31
|
+
* Replaces any matches with a redirection block and logs it.
|
|
32
|
+
*/
|
|
33
|
+
static sanitizeResponse(text) {
|
|
34
|
+
if (!text) {
|
|
35
|
+
return { sanitized: text, detected: false, patterns: [] };
|
|
36
|
+
}
|
|
37
|
+
let detected = false;
|
|
38
|
+
const patternsMatched = [];
|
|
39
|
+
let sanitized = text;
|
|
40
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
41
|
+
if (pattern.test(sanitized)) {
|
|
42
|
+
detected = true;
|
|
43
|
+
const matches = text.match(pattern);
|
|
44
|
+
if (matches) {
|
|
45
|
+
matches.forEach(m => {
|
|
46
|
+
if (!patternsMatched.includes(m)) {
|
|
47
|
+
patternsMatched.push(m);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
sanitized = sanitized.replace(pattern, "[REDACTED PROMPT INJECTION]");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (detected) {
|
|
55
|
+
logger.warn(`[INJECTION PROTECTION] Blocked prompt injection pattern in tool response: ${patternsMatched.join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
sanitized,
|
|
59
|
+
detected,
|
|
60
|
+
patterns: patternsMatched
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -93,9 +93,10 @@ export function analyzePathQuality(projectRoot, targetPath) {
|
|
|
93
93
|
}
|
|
94
94
|
// Check nesting depth
|
|
95
95
|
lines.forEach((line, idx) => {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
const
|
|
96
|
+
const leadingSpaces = line.match(/^(\s*)/)?.[0].length || 0;
|
|
97
|
+
if (leadingSpaces >= 10 && !line.trim().startsWith("//") && !line.trim().startsWith("*")) {
|
|
98
|
+
const indentSize = (leadingSpaces % 4 === 0) ? 4 : 2;
|
|
99
|
+
const depth = Math.floor(leadingSpaces / indentSize);
|
|
99
100
|
if (depth > 4) {
|
|
100
101
|
deepNesting++;
|
|
101
102
|
issues.push({
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Features:
|
|
11
11
|
* - Auto-agent detection from tool call context
|
|
12
|
-
* - Silent system prompt injection
|
|
12
|
+
* - Silent system prompt injection with specialty memory
|
|
13
13
|
* - Multi-agent chain support
|
|
14
14
|
* - Stealth notifications (stderr/dashboard only, never chat)
|
|
15
15
|
*/
|
|
16
|
+
import { EvaluationEngine } from "../../../src/modules/engines/evaluation-engine.js";
|
|
16
17
|
import { RoutingEngine } from "../../../src/modules/engines/routing-engine.js";
|
|
17
18
|
// ─── Configuration ────────────────────────────────────────────────
|
|
18
19
|
const CONFIG = {
|
|
@@ -22,6 +23,8 @@ const CONFIG = {
|
|
|
22
23
|
STEALTH_NOTIFY: process.env.MCP_SILENT_STEALTH !== "false",
|
|
23
24
|
/** Agent override via env (for testing/debugging) */
|
|
24
25
|
FORCE_AGENT: process.env.MCP_FORCE_AGENT || null,
|
|
26
|
+
/** Enable specialty memory injection into context */
|
|
27
|
+
INJECT_SPECIALTY_MEMORY: process.env.MCP_INJECT_SPECIALTY_MEMORY !== "false",
|
|
25
28
|
};
|
|
26
29
|
let agentRuleCache = null;
|
|
27
30
|
async function loadAgentRules() {
|
|
@@ -40,7 +43,6 @@ async function loadAgentRules() {
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
catch {
|
|
43
|
-
// Fallback: use discipline rules file
|
|
44
46
|
agentRuleCache = new Map();
|
|
45
47
|
}
|
|
46
48
|
return agentRuleCache;
|
|
@@ -52,9 +54,8 @@ async function loadAgentRules() {
|
|
|
52
54
|
export function detectAgent(toolName, args) {
|
|
53
55
|
if (CONFIG.FORCE_AGENT)
|
|
54
56
|
return CONFIG.FORCE_AGENT;
|
|
55
|
-
// Build context from args for routing
|
|
56
57
|
const contextParts = [toolName];
|
|
57
|
-
for (const [
|
|
58
|
+
for (const [, value] of Object.entries(args)) {
|
|
58
59
|
if (typeof value === "string") {
|
|
59
60
|
contextParts.push(value);
|
|
60
61
|
}
|
|
@@ -63,7 +64,6 @@ export function detectAgent(toolName, args) {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
const context = contextParts.join(" ").substring(0, 500);
|
|
66
|
-
// Use RoutingEngine to detect the best agent
|
|
67
67
|
return RoutingEngine.resolveAgent(context);
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -88,9 +88,32 @@ export async function getAgentPrompt(agentName) {
|
|
|
88
88
|
"> They supplement (not replace) your existing instructions.",
|
|
89
89
|
].join("\n");
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Load specialty memory for an agent and format it for injection.
|
|
93
|
+
*/
|
|
94
|
+
function getSpecialtyMemoryContext(agentName) {
|
|
95
|
+
if (!CONFIG.INJECT_SPECIALTY_MEMORY)
|
|
96
|
+
return null;
|
|
97
|
+
try {
|
|
98
|
+
const conventions = EvaluationEngine.readLearnedConventions(agentName);
|
|
99
|
+
if (!conventions)
|
|
100
|
+
return null;
|
|
101
|
+
return [
|
|
102
|
+
"",
|
|
103
|
+
"### Previously Learned Conventions",
|
|
104
|
+
`The following lessons were learned from past tasks for @${agentName}:`,
|
|
105
|
+
"",
|
|
106
|
+
conventions,
|
|
107
|
+
"",
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
91
114
|
/**
|
|
92
115
|
* Build a silent context injection payload.
|
|
93
|
-
*
|
|
116
|
+
* Injects agent rules + specialty memory into tool responses.
|
|
94
117
|
*/
|
|
95
118
|
export async function buildSilentContext(detectedAgent, toolName, responseText) {
|
|
96
119
|
if (!CONFIG.ENABLED)
|
|
@@ -98,14 +121,17 @@ export async function buildSilentContext(detectedAgent, toolName, responseText)
|
|
|
98
121
|
const agentPrompt = await getAgentPrompt(detectedAgent);
|
|
99
122
|
if (!agentPrompt)
|
|
100
123
|
return responseText;
|
|
101
|
-
// Only inject if the response is substantial enough to warrant governance
|
|
102
124
|
if (responseText.length < 50)
|
|
103
125
|
return responseText;
|
|
104
|
-
// Inject
|
|
126
|
+
// Inject specialty memory if available
|
|
127
|
+
const specialtyContext = getSpecialtyMemoryContext(detectedAgent);
|
|
128
|
+
const fullPrompt = specialtyContext
|
|
129
|
+
? `${agentPrompt}\n${specialtyContext}`
|
|
130
|
+
: agentPrompt;
|
|
105
131
|
const injection = [
|
|
106
132
|
"",
|
|
107
133
|
`<!-- ATABEY_GOVERNANCE agent="${detectedAgent}" tool="${toolName}" -->`,
|
|
108
|
-
`${
|
|
134
|
+
`${fullPrompt}`,
|
|
109
135
|
"<!-- /ATABEY_GOVERNANCE -->",
|
|
110
136
|
"",
|
|
111
137
|
].join("\n");
|