atabey 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATABEY.md +1 -1
- package/README.md +252 -52
- package/dist/framework-mcp/src/index.js +914 -62
- package/dist/framework-mcp/src/index.js.map +1 -1
- package/dist/framework-mcp/src/tools/compliance/mask_pii.d.ts +17 -0
- package/dist/framework-mcp/src/tools/compliance/mask_pii.js +56 -0
- package/dist/framework-mcp/src/tools/compliance/mask_pii.js.map +1 -0
- package/dist/framework-mcp/src/tools/definitions.js +10 -9
- package/dist/framework-mcp/src/tools/definitions.js.map +1 -1
- package/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
- package/dist/framework-mcp/src/tools/framework/run_tests.js.map +1 -1
- package/dist/framework-mcp/src/tools/index.js +7 -8
- package/dist/framework-mcp/src/tools/index.js.map +1 -1
- package/dist/framework-mcp/src/tools/messaging/approve_operation.d.ts +22 -0
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js +81 -0
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js.map +1 -0
- package/dist/framework-mcp/src/tools/messaging/ask_human.d.ts +15 -0
- package/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
- package/dist/framework-mcp/src/tools/messaging/ask_human.js.map +1 -1
- package/dist/framework-mcp/src/tools/observability/check_ports.js +3 -1
- package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -1
- package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
- package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js.map +1 -1
- package/dist/framework-mcp/src/tools/schemas.d.ts +48 -53
- package/dist/framework-mcp/src/tools/schemas.js +18 -22
- package/dist/framework-mcp/src/tools/schemas.js.map +1 -1
- package/dist/framework-mcp/src/tools/shell/run_command.js +10 -0
- package/dist/framework-mcp/src/tools/shell/run_command.js.map +1 -1
- package/dist/framework-mcp/src/tools/types.d.ts +11 -105
- package/dist/framework-mcp/src/tools/types.js +1 -0
- package/dist/framework-mcp/src/tools/types.js.map +1 -1
- package/dist/framework-mcp/src/utils/auth.d.ts +36 -0
- package/dist/framework-mcp/src/utils/auth.js +79 -0
- package/dist/framework-mcp/src/utils/auth.js.map +1 -0
- package/dist/framework-mcp/src/utils/auto-rollback.d.ts +71 -0
- package/dist/framework-mcp/src/utils/auto-rollback.js +266 -0
- package/dist/framework-mcp/src/utils/auto-rollback.js.map +1 -0
- package/dist/framework-mcp/src/utils/compliance.js +12 -4
- package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
- package/dist/framework-mcp/src/utils/context-optimizer.d.ts +69 -0
- package/dist/framework-mcp/src/utils/context-optimizer.js +157 -0
- package/dist/framework-mcp/src/utils/context-optimizer.js.map +1 -0
- package/dist/framework-mcp/src/utils/discipline.d.ts +68 -0
- package/dist/framework-mcp/src/utils/discipline.js +218 -0
- package/dist/framework-mcp/src/utils/discipline.js.map +1 -0
- package/dist/framework-mcp/src/utils/finops.d.ts +143 -0
- package/dist/framework-mcp/src/utils/finops.js +326 -0
- package/dist/framework-mcp/src/utils/finops.js.map +1 -0
- package/dist/framework-mcp/src/utils/human-in-loop.d.ts +95 -0
- package/dist/framework-mcp/src/utils/human-in-loop.js +216 -0
- package/dist/framework-mcp/src/utils/human-in-loop.js.map +1 -0
- package/dist/framework-mcp/src/utils/license-scanner.d.ts +71 -0
- package/dist/framework-mcp/src/utils/license-scanner.js +284 -0
- package/dist/framework-mcp/src/utils/license-scanner.js.map +1 -0
- package/dist/framework-mcp/src/utils/loop-detector.d.ts +105 -0
- package/dist/framework-mcp/src/utils/loop-detector.js +362 -0
- package/dist/framework-mcp/src/utils/loop-detector.js.map +1 -0
- package/dist/framework-mcp/src/utils/quality.d.ts +19 -0
- package/dist/framework-mcp/src/utils/quality.js +124 -0
- package/dist/framework-mcp/src/utils/quality.js.map +1 -0
- package/dist/framework-mcp/src/utils/rules-engine.d.ts +78 -0
- package/dist/framework-mcp/src/utils/rules-engine.js +232 -0
- package/dist/framework-mcp/src/utils/rules-engine.js.map +1 -0
- package/dist/framework-mcp/src/utils/silent-router.d.ts +47 -0
- package/dist/framework-mcp/src/utils/silent-router.js +129 -0
- package/dist/framework-mcp/src/utils/silent-router.js.map +1 -0
- package/dist/framework-mcp/src/utils/storage.d.ts +1 -0
- package/dist/framework-mcp/src/utils/storage.js +2 -2
- package/dist/framework-mcp/src/utils/storage.js.map +1 -1
- package/dist/framework-mcp/src/utils/telemetry-streamer.d.ts +133 -0
- package/dist/framework-mcp/src/utils/telemetry-streamer.js +397 -0
- package/dist/framework-mcp/src/utils/telemetry-streamer.js.map +1 -0
- package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js +2 -2
- package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.d.ts +1 -0
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.js +49 -0
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.js.map +1 -0
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +14 -5
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.d.ts +1 -0
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js +30 -0
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js.map +1 -0
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js +7 -0
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js.map +1 -1
- package/dist/framework-mcp/tests/utils/auto-rollback.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/auto-rollback.test.js +178 -0
- package/dist/framework-mcp/tests/utils/auto-rollback.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.js +62 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/finops.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/finops.test.js +157 -0
- package/dist/framework-mcp/tests/utils/finops.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js +59 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.js +131 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.js +204 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.js +106 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js +201 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js.map +1 -0
- package/dist/src/cli/commands/check.js +16 -1
- package/dist/src/cli/commands/check.js.map +1 -1
- package/dist/src/cli/commands/dashboard.js +188 -164
- package/dist/src/cli/commands/dashboard.js.map +1 -1
- package/dist/src/cli/commands/gateway.js +2 -2
- package/dist/src/cli/commands/gateway.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-core.d.ts +2 -0
- package/dist/src/cli/commands/init/scaffold-core.js +16 -1
- package/dist/src/cli/commands/init/scaffold-core.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-ops.js +2 -2
- package/dist/src/cli/commands/init/scaffold-ops.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-standards.js +13 -1
- package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts +4 -0
- package/dist/src/cli/commands/init.js +80 -24
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/quickstart.d.ts +1 -1
- package/dist/src/cli/commands/quickstart.js +12 -24
- package/dist/src/cli/commands/quickstart.js.map +1 -1
- package/dist/src/cli/index.js +24 -23
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/shims.js +7 -7
- package/dist/src/cli/utils/pkg.js +2 -3
- package/dist/src/cli/utils/pkg.js.map +1 -1
- package/dist/src/cli/utils/quality.d.ts +10 -0
- package/dist/src/cli/utils/quality.js +10 -0
- package/dist/src/cli/utils/quality.js.map +1 -0
- package/dist/src/modules/agents/definitions.js +45 -40
- package/dist/src/modules/agents/definitions.js.map +1 -1
- package/dist/src/modules/agents/registry/manager.js +6 -1
- package/dist/src/modules/agents/registry/manager.js.map +1 -1
- package/dist/src/modules/engines/agent-executor.d.ts +6 -10
- package/dist/src/modules/engines/agent-executor.js +53 -21
- package/dist/src/modules/engines/agent-executor.js.map +1 -1
- package/dist/src/modules/engines/agent-loop.d.ts +34 -0
- package/dist/src/modules/engines/agent-loop.js +102 -0
- package/dist/src/modules/engines/agent-loop.js.map +1 -0
- package/dist/src/modules/engines/evaluation-engine.js +2 -2
- package/dist/src/modules/engines/evaluation-engine.js.map +1 -1
- package/dist/src/modules/engines/quality-gate.d.ts +13 -5
- package/dist/src/modules/engines/quality-gate.js +39 -31
- package/dist/src/modules/engines/quality-gate.js.map +1 -1
- package/dist/src/modules/engines/routing-engine.d.ts +5 -3
- package/dist/src/modules/engines/routing-engine.js +16 -9
- package/dist/src/modules/engines/routing-engine.js.map +1 -1
- package/dist/src/modules/gateway/cost-tracker.d.ts +5 -0
- package/dist/src/modules/gateway/cost-tracker.js +71 -0
- package/dist/src/modules/gateway/cost-tracker.js.map +1 -1
- package/dist/src/modules/gateway/gateway.d.ts +4 -0
- package/dist/src/modules/gateway/gateway.js +8 -0
- package/dist/src/modules/gateway/gateway.js.map +1 -1
- package/dist/src/shared/audit.js +3 -2
- package/dist/src/shared/audit.js.map +1 -1
- package/dist/src/shared/pii.d.ts +41 -9
- package/dist/src/shared/pii.js +122 -31
- package/dist/src/shared/pii.js.map +1 -1
- package/dist/src/shared/storage.d.ts +19 -0
- package/dist/src/shared/storage.js +46 -6
- package/dist/src/shared/storage.js.map +1 -1
- package/dist/tests/embedding.test.d.ts +1 -0
- package/dist/tests/embedding.test.js +82 -0
- package/dist/tests/embedding.test.js.map +1 -0
- package/dist/tests/engines/evaluation-engine.test.d.ts +1 -0
- package/dist/tests/engines/evaluation-engine.test.js +71 -0
- package/dist/tests/engines/evaluation-engine.test.js.map +1 -0
- package/dist/tests/engines/planning-engine.test.d.ts +1 -0
- package/dist/tests/engines/planning-engine.test.js +100 -0
- package/dist/tests/engines/planning-engine.test.js.map +1 -0
- package/dist/tests/engines/quality-gate.test.d.ts +1 -0
- package/dist/tests/engines/quality-gate.test.js +59 -0
- package/dist/tests/engines/quality-gate.test.js.map +1 -0
- package/dist/tests/engines/risk-engine.test.d.ts +1 -0
- package/dist/tests/engines/risk-engine.test.js +80 -0
- package/dist/tests/engines/risk-engine.test.js.map +1 -0
- package/dist/tests/engines/routing-engine.test.d.ts +1 -0
- package/dist/tests/engines/routing-engine.test.js +78 -0
- package/dist/tests/engines/routing-engine.test.js.map +1 -0
- package/dist/tests/integration/hermes_locking.test.js +1 -0
- package/dist/tests/integration/hermes_locking.test.js.map +1 -1
- package/dist/tests/modules/gateway/cost-tracker.test.js +20 -1
- package/dist/tests/modules/gateway/cost-tracker.test.js.map +1 -1
- package/dist/tests/shared/lock.test.d.ts +1 -0
- package/dist/tests/shared/lock.test.js +95 -0
- package/dist/tests/shared/lock.test.js.map +1 -0
- package/dist/tests/shared/pii.test.d.ts +1 -0
- package/dist/tests/shared/pii.test.js +183 -0
- package/dist/tests/shared/pii.test.js.map +1 -0
- package/framework-mcp/README.md +307 -35
- package/framework-mcp/dist/dashboard/assets/index-B2mYld0c.js +1221 -0
- package/framework-mcp/dist/dashboard/index.html +1 -1
- package/framework-mcp/dist/framework-mcp/src/index.js +914 -62
- package/framework-mcp/dist/framework-mcp/src/tools/compliance/mask_pii.js +55 -0
- package/framework-mcp/dist/framework-mcp/src/tools/definitions.js +10 -9
- package/framework-mcp/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
- package/framework-mcp/dist/framework-mcp/src/tools/index.js +7 -8
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/approve_operation.js +80 -0
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
- package/framework-mcp/dist/framework-mcp/src/tools/observability/check_ports.js +3 -1
- package/framework-mcp/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
- package/framework-mcp/dist/framework-mcp/src/tools/schemas.js +18 -22
- package/framework-mcp/dist/framework-mcp/src/tools/shell/run_command.js +10 -0
- package/framework-mcp/dist/framework-mcp/src/tools/types.js +1 -0
- package/framework-mcp/dist/framework-mcp/src/utils/auth.js +78 -0
- package/framework-mcp/dist/framework-mcp/src/utils/auto-rollback.js +265 -0
- package/framework-mcp/dist/framework-mcp/src/utils/compliance.js +12 -4
- package/framework-mcp/dist/framework-mcp/src/utils/context-optimizer.js +156 -0
- package/framework-mcp/dist/framework-mcp/src/utils/discipline.js +217 -0
- package/framework-mcp/dist/framework-mcp/src/utils/finops.js +325 -0
- package/framework-mcp/dist/framework-mcp/src/utils/human-in-loop.js +215 -0
- package/framework-mcp/dist/framework-mcp/src/utils/license-scanner.js +283 -0
- package/framework-mcp/dist/framework-mcp/src/utils/loop-detector.js +361 -0
- package/framework-mcp/dist/framework-mcp/src/utils/quality.js +123 -0
- package/framework-mcp/dist/framework-mcp/src/utils/rules-engine.js +231 -0
- package/framework-mcp/dist/framework-mcp/src/utils/silent-router.js +128 -0
- package/framework-mcp/dist/framework-mcp/src/utils/storage.js +2 -2
- package/framework-mcp/dist/framework-mcp/src/utils/telemetry-streamer.js +396 -0
- package/framework-mcp/dist/src/cli/platforms/core.js +72 -0
- package/framework-mcp/dist/src/cli/platforms/index.js +5 -0
- package/framework-mcp/dist/src/cli/platforms/paths.js +101 -0
- package/framework-mcp/dist/src/cli/platforms/scaffold.js +72 -0
- package/framework-mcp/dist/src/cli/platforms/utils.js +75 -0
- package/framework-mcp/dist/src/cli/utils/claude.js +57 -0
- package/framework-mcp/dist/src/cli/utils/compliance.js +12 -0
- package/framework-mcp/dist/src/cli/utils/fs.js +138 -0
- package/framework-mcp/dist/src/cli/utils/pkg.js +282 -0
- package/framework-mcp/dist/src/cli/utils/quality.js +9 -0
- package/framework-mcp/dist/src/modules/agents/definitions.js +563 -0
- package/framework-mcp/dist/src/modules/agents/registry/analyst.js +39 -0
- package/framework-mcp/dist/src/modules/agents/registry/architect.js +42 -0
- package/framework-mcp/dist/src/modules/agents/registry/backend.js +50 -0
- package/framework-mcp/dist/src/modules/agents/registry/database.js +45 -0
- package/framework-mcp/dist/src/modules/agents/registry/devops.js +45 -0
- package/framework-mcp/dist/src/modules/agents/registry/explorer.js +37 -0
- package/framework-mcp/dist/src/modules/agents/registry/frontend.js +52 -0
- package/framework-mcp/dist/src/modules/agents/registry/git.js +36 -0
- package/framework-mcp/dist/src/modules/agents/registry/manager.js +64 -0
- package/framework-mcp/dist/src/modules/agents/registry/mobile.js +41 -0
- package/framework-mcp/dist/src/modules/agents/registry/native.js +41 -0
- package/framework-mcp/dist/src/modules/agents/registry/quality.js +43 -0
- package/framework-mcp/dist/src/modules/agents/registry/security.js +42 -0
- package/framework-mcp/dist/src/modules/agents/types.js +1 -0
- package/framework-mcp/dist/src/modules/engines/quality-gate.js +92 -0
- package/framework-mcp/dist/src/modules/engines/risk-engine.js +105 -0
- package/framework-mcp/dist/src/modules/engines/routing-engine.js +408 -0
- package/framework-mcp/dist/src/modules/engines/types.js +1 -0
- package/framework-mcp/dist/src/modules/providers/definitions.js +140 -0
- package/framework-mcp/dist/src/modules/providers/registry.js +18 -0
- package/framework-mcp/dist/src/modules/providers/shared.js +104 -0
- package/framework-mcp/dist/src/modules/providers/types.js +1 -0
- package/framework-mcp/dist/src/modules/skills/adapter-skills.js +449 -0
- package/framework-mcp/dist/src/modules/skills/definitions.js +70 -0
- package/framework-mcp/dist/src/shared/audit.js +220 -0
- package/framework-mcp/dist/src/shared/errors.js +68 -0
- package/framework-mcp/dist/src/shared/lock.js +159 -0
- package/framework-mcp/dist/src/shared/pii.js +122 -31
- package/framework-mcp/dist/src/shared/retention.js +170 -0
- package/framework-mcp/dist/src/shared/storage.js +46 -6
- package/framework-mcp/dist/src/shared/string.js +29 -0
- package/framework-mcp/package.json +8 -2
- package/mcp.json +11 -6
- package/package.json +13 -6
- package/templates/full/ATABEY_FULL.md +1 -1
- package/framework-mcp/dist/dashboard/assets/index-BAPGLiWE.js +0 -267
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─── MCP AUTHENTICATION ──────────────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Simple API key authentication for MCP server.
|
|
5
|
+
* When MCP_AUTH_TOKEN is set, all MCP and API requests require
|
|
6
|
+
* Authorization: Bearer <token> header.
|
|
7
|
+
*
|
|
8
|
+
* Environment Variables:
|
|
9
|
+
* MCP_AUTH_TOKEN - API key for authentication (optional)
|
|
10
|
+
* MCP_AUTH_USERS - Comma-separated list of user:token pairs (optional)
|
|
11
|
+
* Example: "alice:key1,bob:key2"
|
|
12
|
+
*
|
|
13
|
+
* If neither is set, authentication is disabled (open access).
|
|
14
|
+
*/
|
|
15
|
+
let authUsers = [];
|
|
16
|
+
let masterToken = null;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize authentication from environment variables.
|
|
19
|
+
*/
|
|
20
|
+
export function initAuth() {
|
|
21
|
+
masterToken = process.env.MCP_AUTH_TOKEN || null;
|
|
22
|
+
const usersStr = process.env.MCP_AUTH_USERS || "";
|
|
23
|
+
if (usersStr) {
|
|
24
|
+
authUsers = usersStr.split(",").map(pair => {
|
|
25
|
+
const [name, token] = pair.split(":");
|
|
26
|
+
return { name: name?.trim() || "unknown", token: token?.trim() || "" };
|
|
27
|
+
}).filter(u => u.token);
|
|
28
|
+
}
|
|
29
|
+
if (masterToken) {
|
|
30
|
+
console.error(`[AUTH] Master token configured (${masterToken.length} chars)`);
|
|
31
|
+
}
|
|
32
|
+
if (authUsers.length > 0) {
|
|
33
|
+
console.error(`[AUTH] ${authUsers.length} user(s) configured: ${authUsers.map(u => u.name).join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
if (!masterToken && authUsers.length === 0) {
|
|
36
|
+
console.error("[AUTH] No authentication configured - OPEN ACCESS");
|
|
37
|
+
console.error("[AUTH] Set MCP_AUTH_TOKEN=<key> or MCP_AUTH_USERS=user1:key1,user2:key2 to enable");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Authenticate a request using the Authorization header.
|
|
42
|
+
* Returns the authenticated user name or null if unauthorized.
|
|
43
|
+
*/
|
|
44
|
+
export function authenticate(req) {
|
|
45
|
+
if (!masterToken && authUsers.length === 0) {
|
|
46
|
+
// Auth disabled - allow all
|
|
47
|
+
return { authenticated: true, user: "anonymous" };
|
|
48
|
+
}
|
|
49
|
+
const authHeader = req.headers["authorization"];
|
|
50
|
+
if (!authHeader) {
|
|
51
|
+
return { authenticated: false, user: "" };
|
|
52
|
+
}
|
|
53
|
+
// Support both "Bearer <key>" and "<key>" formats
|
|
54
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
55
|
+
// Check master token first
|
|
56
|
+
if (masterToken && token === masterToken) {
|
|
57
|
+
return { authenticated: true, user: "admin" };
|
|
58
|
+
}
|
|
59
|
+
// Check user tokens
|
|
60
|
+
for (const user of authUsers) {
|
|
61
|
+
if (token === user.token) {
|
|
62
|
+
return { authenticated: true, user: user.name };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { authenticated: false, user: "" };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate authentication headers for MCP client configuration.
|
|
69
|
+
*/
|
|
70
|
+
export function getAuthHeader(user, token) {
|
|
71
|
+
return `Bearer ${token}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if authentication is enabled.
|
|
75
|
+
*/
|
|
76
|
+
export function isAuthEnabled() {
|
|
77
|
+
return !!masterToken || authUsers.length > 0;
|
|
78
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─── AUTO-ROLLBACK + REGENERATE ───────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* When the AI generates code that violates governance rules (e.g., `any` type,
|
|
5
|
+
* console.log, copyleft license), this module:
|
|
6
|
+
* 1. Captures the file content before the write
|
|
7
|
+
* 2. Validates the new content against governance rules
|
|
8
|
+
* 3. If violation found: blocks the write, restores original, and sends
|
|
9
|
+
* a regenerate instruction back to the AI via MCP notification
|
|
10
|
+
*
|
|
11
|
+
* Flow:
|
|
12
|
+
* [Pre-Write Snapshot] → [Validate Content] → [Violation?] ─yes→ [Rollback] → [Notify AI]
|
|
13
|
+
* └─no─→ [Allow Write]
|
|
14
|
+
*
|
|
15
|
+
* Key Innovation: Unlike simple blocking, this tells the AI WHY it was
|
|
16
|
+
* blocked and gives it specific instructions to self-correct.
|
|
17
|
+
*/
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
class SnapshotManager {
|
|
21
|
+
snapshots = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Capture a pre-write snapshot of a file.
|
|
24
|
+
*/
|
|
25
|
+
capture(filePath, traceId) {
|
|
26
|
+
const resolvedPath = path.resolve(filePath);
|
|
27
|
+
let originalContent = null;
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(resolvedPath)) {
|
|
30
|
+
originalContent = fs.readFileSync(resolvedPath, "utf8");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch { /* file doesn't exist yet (new file) */ }
|
|
34
|
+
this.snapshots.set(resolvedPath, {
|
|
35
|
+
filePath: resolvedPath,
|
|
36
|
+
originalContent,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
traceId,
|
|
39
|
+
restored: false,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get a snapshot for a file.
|
|
44
|
+
*/
|
|
45
|
+
get(filePath) {
|
|
46
|
+
return this.snapshots.get(path.resolve(filePath));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Restore a file to its pre-write state.
|
|
50
|
+
* Returns true if restored, false if no snapshot exists.
|
|
51
|
+
*/
|
|
52
|
+
restore(filePath) {
|
|
53
|
+
const resolvedPath = path.resolve(filePath);
|
|
54
|
+
const snapshot = this.snapshots.get(resolvedPath);
|
|
55
|
+
if (!snapshot || snapshot.restored)
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
if (snapshot.originalContent !== null) {
|
|
59
|
+
// File existed before – restore original
|
|
60
|
+
fs.writeFileSync(resolvedPath, snapshot.originalContent, "utf8");
|
|
61
|
+
process.stderr.write(`[ROLLBACK] Restored: ${path.relative(process.cwd(), resolvedPath)}\n`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// File was new – delete it
|
|
65
|
+
if (fs.existsSync(resolvedPath)) {
|
|
66
|
+
fs.unlinkSync(resolvedPath);
|
|
67
|
+
process.stderr.write(`[ROLLBACK] Deleted (new file): ${path.relative(process.cwd(), resolvedPath)}\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
snapshot.restored = true;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
process.stderr.write(`[ROLLBACK] Failed to restore ${resolvedPath}: ${error}\n`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Clean up old snapshots.
|
|
80
|
+
*/
|
|
81
|
+
cleanup(maxAgeMs = 300000) {
|
|
82
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
83
|
+
for (const [filePath, snapshot] of this.snapshots) {
|
|
84
|
+
if (snapshot.timestamp < cutoff) {
|
|
85
|
+
this.snapshots.delete(filePath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get all active snapshots.
|
|
91
|
+
*/
|
|
92
|
+
getAll() {
|
|
93
|
+
return Array.from(this.snapshots.values());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ─── Auto-Rollback Engine ─────────────────────────────────────────
|
|
97
|
+
export class AutoRollback {
|
|
98
|
+
static snapshots = new SnapshotManager();
|
|
99
|
+
/**
|
|
100
|
+
* Initialize the auto-rollback system.
|
|
101
|
+
* Sets up file watcher cleanup.
|
|
102
|
+
*/
|
|
103
|
+
static initialize() {
|
|
104
|
+
// Periodic cleanup of old snapshots (every 5 minutes)
|
|
105
|
+
setInterval(() => {
|
|
106
|
+
AutoRollback.snapshots.cleanup();
|
|
107
|
+
}, 300000);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Prepare for a file write by capturing the current state.
|
|
111
|
+
* Call this BEFORE the tool handler writes to the file.
|
|
112
|
+
*/
|
|
113
|
+
static prepareWrite(filePath, traceId) {
|
|
114
|
+
AutoRollback.snapshots.capture(filePath, traceId);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Validate content and rollback if violations are found.
|
|
118
|
+
* This is called after the tool handler writes the file, but
|
|
119
|
+
* BEFORE the response is returned to the AI.
|
|
120
|
+
*
|
|
121
|
+
* Returns:
|
|
122
|
+
* - null if content is clean
|
|
123
|
+
* - ViolationRecord[] if violations were found and rolled back
|
|
124
|
+
*/
|
|
125
|
+
static validateAndRollback(filePath, content, violations) {
|
|
126
|
+
if (violations.length === 0)
|
|
127
|
+
return null;
|
|
128
|
+
// Restore the original file
|
|
129
|
+
const restored = AutoRollback.snapshots.restore(filePath);
|
|
130
|
+
if (restored) {
|
|
131
|
+
process.stderr.write(`[ROLLBACK] Rolled back ${path.relative(process.cwd(), filePath)} due to ${violations.length} violation(s)\n`);
|
|
132
|
+
}
|
|
133
|
+
return violations;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build a regenerate instruction for the AI.
|
|
137
|
+
* This is what gets sent back to the AI to tell it to fix its output.
|
|
138
|
+
*/
|
|
139
|
+
static buildRegenerateInstruction(violations, toolName) {
|
|
140
|
+
const criticalViolations = violations.filter(v => v.severity === "CRITICAL");
|
|
141
|
+
const lines = [
|
|
142
|
+
"## ⛔ AI Output Blocked – Governance Violation",
|
|
143
|
+
"",
|
|
144
|
+
`The code you generated for tool "${toolName}" has been BLOCKED and ROLLED BACK.`,
|
|
145
|
+
"The file has been restored to its previous state.",
|
|
146
|
+
"",
|
|
147
|
+
];
|
|
148
|
+
if (criticalViolations.length > 0) {
|
|
149
|
+
lines.push(`### 🔴 Critical Violations (${criticalViolations.length})`);
|
|
150
|
+
lines.push("");
|
|
151
|
+
for (const v of criticalViolations) {
|
|
152
|
+
lines.push(`- **${v.rule}** (${v.filePath}:${v.line})`);
|
|
153
|
+
lines.push(` - Detail: ${v.detail}`);
|
|
154
|
+
lines.push(` - Fix: ${v.regenerateInstruction}`);
|
|
155
|
+
lines.push("");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const otherViolations = violations.filter(v => v.severity !== "CRITICAL");
|
|
159
|
+
if (otherViolations.length > 0) {
|
|
160
|
+
lines.push(`### 🟠 Other Violations (${otherViolations.length})`);
|
|
161
|
+
lines.push("");
|
|
162
|
+
for (const v of otherViolations) {
|
|
163
|
+
lines.push(`- **${v.rule}** (${v.filePath}:${v.line})`);
|
|
164
|
+
lines.push(` - Detail: ${v.detail}`);
|
|
165
|
+
lines.push(` - Fix: ${v.regenerateInstruction}`);
|
|
166
|
+
lines.push("");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
lines.push("---");
|
|
170
|
+
lines.push("> **Instruction:** Please regenerate the code for this operation.");
|
|
171
|
+
lines.push("> Address ALL violations listed above.");
|
|
172
|
+
lines.push("> Do NOT reintroduce the same violations.");
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Validate tool response content against governance rules.
|
|
177
|
+
* Used by the MCP middleware to check file write content before returning.
|
|
178
|
+
*/
|
|
179
|
+
static checkWriteContent(filePath, content, traceId) {
|
|
180
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
181
|
+
const violations = [];
|
|
182
|
+
// Only check source files
|
|
183
|
+
if (![".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".java", ".rs", ".swift", ".kt"].includes(ext)) {
|
|
184
|
+
return { allowed: true, violations: [], instruction: null };
|
|
185
|
+
}
|
|
186
|
+
const lines = content.split("\n");
|
|
187
|
+
// 1. Check for `any` type (CRITICAL)
|
|
188
|
+
if ([".ts", ".tsx"].includes(ext)) {
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
const anyMatch = lines[i].match(/:\s*any\b/);
|
|
191
|
+
if (anyMatch) {
|
|
192
|
+
violations.push({
|
|
193
|
+
rule: "No `any` Type",
|
|
194
|
+
severity: "CRITICAL",
|
|
195
|
+
filePath,
|
|
196
|
+
line: i + 1,
|
|
197
|
+
detail: `Line ${i + 1}: ${anyMatch[0].substring(0, 60)}`,
|
|
198
|
+
regenerateInstruction: "Replace `any` with `unknown` and add proper type guards. Or use a specific interface/type.",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// 2. Check for console.log (CRITICAL)
|
|
204
|
+
for (let i = 0; i < lines.length; i++) {
|
|
205
|
+
const consoleMatch = lines[i].match(/console\.(log|error|warn|debug)\s*\(/);
|
|
206
|
+
if (consoleMatch) {
|
|
207
|
+
// Check if file is exempt
|
|
208
|
+
if (!filePath.includes("logger.ts") && !filePath.includes("cli.ts") && !filePath.includes("compliance.ts")) {
|
|
209
|
+
violations.push({
|
|
210
|
+
rule: "No Console Log",
|
|
211
|
+
severity: "CRITICAL",
|
|
212
|
+
filePath,
|
|
213
|
+
line: i + 1,
|
|
214
|
+
detail: `Line ${i + 1}: ${consoleMatch[0].substring(0, 60)}`,
|
|
215
|
+
regenerateInstruction: "Replace with the project's logger (import from the appropriate logger module).",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// 3. Check for hardcoded secrets (HIGH)
|
|
221
|
+
const secretPatterns = [
|
|
222
|
+
{ pattern: /(['"])sk-[a-zA-Z0-9]{20,}['"]/, name: "OpenAI API Key" },
|
|
223
|
+
{ pattern: /(['"])ghp_[a-zA-Z0-9]{36,}['"]/, name: "GitHub Token" },
|
|
224
|
+
{ pattern: /(['"])AIza[0-9A-Za-z_-]{35}['"]/, name: "Google API Key" },
|
|
225
|
+
];
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
for (const { pattern, name } of secretPatterns) {
|
|
228
|
+
if (pattern.test(lines[i])) {
|
|
229
|
+
violations.push({
|
|
230
|
+
rule: `Hardcoded Secret: ${name}`,
|
|
231
|
+
severity: "HIGH",
|
|
232
|
+
filePath,
|
|
233
|
+
line: i + 1,
|
|
234
|
+
detail: `Potential ${name} hardcoded at line ${i + 1}`,
|
|
235
|
+
regenerateInstruction: `Use environment variables (process.env.*) instead of hardcoding ${name}.`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// If violations found, prepare snapshot and rollback
|
|
241
|
+
if (violations.length > 0) {
|
|
242
|
+
AutoRollback.prepareWrite(filePath, traceId);
|
|
243
|
+
const rolledBack = AutoRollback.validateAndRollback(filePath, content, violations);
|
|
244
|
+
const instruction = AutoRollback.buildRegenerateInstruction(violations, "write_file");
|
|
245
|
+
return {
|
|
246
|
+
allowed: false,
|
|
247
|
+
violations: rolledBack || violations,
|
|
248
|
+
instruction,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return { allowed: true, violations: [], instruction: null };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get snapshot stats.
|
|
255
|
+
*/
|
|
256
|
+
static getSnapshotStats() {
|
|
257
|
+
const all = AutoRollback.snapshots.getAll();
|
|
258
|
+
return {
|
|
259
|
+
total: all.length,
|
|
260
|
+
restored: all.filter(s => s.restored).length,
|
|
261
|
+
pending: all.filter(s => !s.restored).length,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
export { AutoRollback as AutoRollbackEngine };
|
|
@@ -53,7 +53,14 @@ export function verifyCorporateCompliance(content, filePath) {
|
|
|
53
53
|
}
|
|
54
54
|
// 5. Secure CLI Policy (No direct child_process)
|
|
55
55
|
if (moduleSpecifier.text === "child_process") {
|
|
56
|
-
|
|
56
|
+
const isExempt = filePath.includes("src/cli/commands/") ||
|
|
57
|
+
filePath.includes("shared/fs.ts") ||
|
|
58
|
+
filePath.includes("shared/lock.ts") ||
|
|
59
|
+
filePath.includes("quality-gate.ts") ||
|
|
60
|
+
filePath.includes("providers/shared.ts");
|
|
61
|
+
if (!isExempt) {
|
|
62
|
+
errors.push(`[ERROR] Corporate Compliance Breach: Direct 'child_process' usage is forbidden at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}. Use secure framework APIs.`);
|
|
63
|
+
}
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
}
|
|
@@ -75,10 +82,11 @@ export function verifyCorporateCompliance(content, filePath) {
|
|
|
75
82
|
const prop = node.expression;
|
|
76
83
|
if (ts.isIdentifier(prop.expression) && prop.expression.text === "fs") {
|
|
77
84
|
if (["writeFileSync", "appendFileSync"].includes(prop.name.text)) {
|
|
78
|
-
// Lock files are exempt
|
|
85
|
+
// Lock files, core atomic fs wrapper, and mcp configs are exempt
|
|
79
86
|
const args = node.arguments;
|
|
80
87
|
const firstArgText = args.length > 0 ? args[0].getText(sourceFile).toLowerCase() : "";
|
|
81
|
-
|
|
88
|
+
const isExemptFile = filePath.includes("shared/fs.ts") || filePath.includes("src/cli/commands/mcp.ts");
|
|
89
|
+
if (!firstArgText.includes("lock") && !isExemptFile) {
|
|
82
90
|
errors.push(`[ERROR] Corporate Compliance Breach: Raw 'fs.${prop.name.text}' mutation detected at line ${sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1}. Use atomic utilities instead.`);
|
|
83
91
|
}
|
|
84
92
|
}
|
|
@@ -118,7 +126,7 @@ export function scanProjectCompliance(targetDir = "src") {
|
|
|
118
126
|
const files = getAllFiles(targetDir);
|
|
119
127
|
for (const file of files) {
|
|
120
128
|
// Skip exempt files
|
|
121
|
-
if (file.includes("compliance") || file.includes("definitions") || file.includes("logger") || file.includes("cli/index.ts"))
|
|
129
|
+
if (file.includes("compliance") || file.includes("definitions") || file.includes("logger") || file.includes("cli/index.ts") || file.includes("shared/fs.ts") || file.includes("shared/lock.ts"))
|
|
122
130
|
continue;
|
|
123
131
|
const content = fs.readFileSync(file, "utf8");
|
|
124
132
|
try {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─── CONTEXT OPTIMIZER ─────────────────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Prevents context poisoning and runaway token consumption by AI CLIs.
|
|
5
|
+
* Acts as a Circuit Breaker at the MCP middleware layer.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Token Budget Enforcer: Max tokens per tool call response
|
|
9
|
+
* - File Read Limiter: Auto-truncate large file reads
|
|
10
|
+
* - Token-Based Rate Limiter: Max token spend per window
|
|
11
|
+
* - Response Size Governor: Truncates oversized responses
|
|
12
|
+
*
|
|
13
|
+
* [KVKK/GDPR] Also prevents PII leakage via oversized responses.
|
|
14
|
+
*/
|
|
15
|
+
// ─── Configuration ────────────────────────────────────────────────
|
|
16
|
+
const CONFIG = {
|
|
17
|
+
/** Max tokens allowed in a single tool response */
|
|
18
|
+
MAX_TOKENS_PER_CALL: parseInt(process.env.MCP_MAX_TOKENS_PER_CALL || "4000", 10),
|
|
19
|
+
/** Max tokens per minute across all calls for an agent */
|
|
20
|
+
MAX_TOKENS_PER_MINUTE: parseInt(process.env.MCP_MAX_TOKENS_PER_MINUTE || "20000", 10),
|
|
21
|
+
/** Max tokens per hour across all calls for an agent */
|
|
22
|
+
MAX_TOKENS_PER_HOUR: parseInt(process.env.MCP_MAX_TOKENS_PER_HOUR || "100000", 10),
|
|
23
|
+
/** Max file size in bytes before auto-truncation (100KB) */
|
|
24
|
+
MAX_FILE_READ_SIZE: parseInt(process.env.MCP_MAX_FILE_READ_SIZE || "102400", 10),
|
|
25
|
+
/** Max lines to return for truncated files */
|
|
26
|
+
MAX_FILE_LINES: parseInt(process.env.MCP_MAX_FILE_LINES || "200", 10),
|
|
27
|
+
/** Token estimation ratio (chars per token) */
|
|
28
|
+
CHARS_PER_TOKEN: 4,
|
|
29
|
+
};
|
|
30
|
+
const agentBudgets = new Map();
|
|
31
|
+
function getOrCreateBudget(agent) {
|
|
32
|
+
if (!agentBudgets.has(agent)) {
|
|
33
|
+
agentBudgets.set(agent, {
|
|
34
|
+
records: [],
|
|
35
|
+
totalTokens: 0,
|
|
36
|
+
lastWarning: 0,
|
|
37
|
+
blockedUntil: null,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return agentBudgets.get(agent);
|
|
41
|
+
}
|
|
42
|
+
function pruneOldRecords(agent) {
|
|
43
|
+
const oneHourAgo = Date.now() - 3600000;
|
|
44
|
+
agent.records = agent.records.filter(r => r.timestamp > oneHourAgo);
|
|
45
|
+
}
|
|
46
|
+
function sumTokensInWindow(agent, windowMs) {
|
|
47
|
+
const cutoff = Date.now() - windowMs;
|
|
48
|
+
return agent.records
|
|
49
|
+
.filter(r => r.timestamp > cutoff)
|
|
50
|
+
.reduce((sum, r) => sum + r.tokens, 0);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Estimate token count from text.
|
|
54
|
+
* Uses 4 chars per token as a conservative estimate.
|
|
55
|
+
*/
|
|
56
|
+
export function estimateTokens(text) {
|
|
57
|
+
if (!text)
|
|
58
|
+
return 0;
|
|
59
|
+
return Math.ceil(text.length / CONFIG.CHARS_PER_TOKEN);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a tool response is within token budget.
|
|
63
|
+
* Returns null if allowed, or an error message if blocked.
|
|
64
|
+
*/
|
|
65
|
+
export function checkTokenBudget(agent, toolName, responseText) {
|
|
66
|
+
const budget = getOrCreateBudget(agent);
|
|
67
|
+
// Check if agent is currently blocked
|
|
68
|
+
if (budget.blockedUntil && Date.now() < budget.blockedUntil) {
|
|
69
|
+
const remaining = Math.ceil((budget.blockedUntil - Date.now()) / 1000);
|
|
70
|
+
return `[TOKEN BUDGET] Agent ${agent} is blocked for ${remaining}s. Token limit exceeded.`;
|
|
71
|
+
}
|
|
72
|
+
const estimatedTokens = estimateTokens(responseText);
|
|
73
|
+
// 1. Check per-call limit
|
|
74
|
+
if (estimatedTokens > CONFIG.MAX_TOKENS_PER_CALL) {
|
|
75
|
+
return `[TOKEN BUDGET] Response too large: ~${estimatedTokens} tokens (max: ${CONFIG.MAX_TOKENS_PER_CALL}). Truncate your request.`;
|
|
76
|
+
}
|
|
77
|
+
// Record the token usage
|
|
78
|
+
budget.records.push({
|
|
79
|
+
tokens: estimatedTokens,
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
toolName,
|
|
82
|
+
});
|
|
83
|
+
budget.totalTokens += estimatedTokens;
|
|
84
|
+
// Prune old records
|
|
85
|
+
pruneOldRecords(budget);
|
|
86
|
+
// 2. Check per-minute limit
|
|
87
|
+
const minuteTokens = sumTokensInWindow(budget, 60000);
|
|
88
|
+
if (minuteTokens > CONFIG.MAX_TOKENS_PER_MINUTE) {
|
|
89
|
+
budget.blockedUntil = Date.now() + 30000; // 30s cooldown
|
|
90
|
+
return `[TOKEN BUDGET] Agent ${agent} exceeded ${CONFIG.MAX_TOKENS_PER_MINUTE} tokens/min. Cooldown 30s.`;
|
|
91
|
+
}
|
|
92
|
+
// 3. Check per-hour limit
|
|
93
|
+
const hourTokens = sumTokensInWindow(budget, 3600000);
|
|
94
|
+
if (hourTokens > CONFIG.MAX_TOKENS_PER_HOUR) {
|
|
95
|
+
budget.blockedUntil = Date.now() + 120000; // 2min cooldown
|
|
96
|
+
return `[TOKEN BUDGET] Agent ${agent} exceeded ${CONFIG.MAX_TOKENS_PER_HOUR} tokens/hour. Cooldown 2min.`;
|
|
97
|
+
}
|
|
98
|
+
// 4. Warning at 80% of per-minute limit
|
|
99
|
+
if (minuteTokens > CONFIG.MAX_TOKENS_PER_MINUTE * 0.8) {
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
if (now - budget.lastWarning > 10000) { // Throttle warnings to once per 10s
|
|
102
|
+
budget.lastWarning = now;
|
|
103
|
+
process.stderr.write(`[TOKEN BUDGET WARNING] Agent ${agent} at ${Math.round(minuteTokens / CONFIG.MAX_TOKENS_PER_MINUTE * 100)}% of minute token budget.\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Truncate file content to fit within token budget.
|
|
110
|
+
* Returns truncated content with a header note.
|
|
111
|
+
*/
|
|
112
|
+
export function truncateFileContent(content, filePath, maxTokens = CONFIG.MAX_TOKENS_PER_CALL) {
|
|
113
|
+
const estimatedTokens = estimateTokens(content);
|
|
114
|
+
if (estimatedTokens <= maxTokens)
|
|
115
|
+
return content;
|
|
116
|
+
const lines = content.split("\n");
|
|
117
|
+
const maxLines = Math.min(CONFIG.MAX_FILE_LINES, Math.floor(maxTokens * CONFIG.CHARS_PER_TOKEN / 80));
|
|
118
|
+
const truncated = lines.slice(0, maxLines).join("\n");
|
|
119
|
+
const remaining = lines.length - maxLines;
|
|
120
|
+
return `[TRUNCATED] File "${filePath}" is too large (~${estimatedTokens} tokens). Showing first ${maxLines}/${lines.length} lines.\n\n${truncated}\n\n[... ${remaining} more lines truncated. Use grep_search for targeted queries.]`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get token budget stats for an agent.
|
|
124
|
+
*/
|
|
125
|
+
export function getTokenBudgetStats(agent) {
|
|
126
|
+
const budget = agentBudgets.get(agent);
|
|
127
|
+
if (!budget) {
|
|
128
|
+
return { totalTokens: 0, recentMinute: 0, recentHour: 0, blocked: false, blockedRemaining: 0, callCount: 0 };
|
|
129
|
+
}
|
|
130
|
+
pruneOldRecords(budget);
|
|
131
|
+
return {
|
|
132
|
+
totalTokens: budget.totalTokens,
|
|
133
|
+
recentMinute: sumTokensInWindow(budget, 60000),
|
|
134
|
+
recentHour: sumTokensInWindow(budget, 3600000),
|
|
135
|
+
blocked: budget.blockedUntil !== null && Date.now() < budget.blockedUntil,
|
|
136
|
+
blockedRemaining: budget.blockedUntil ? Math.max(0, budget.blockedUntil - Date.now()) : 0,
|
|
137
|
+
callCount: budget.records.length,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get all agents' token budget stats.
|
|
142
|
+
*/
|
|
143
|
+
export function getAllTokenBudgetStats() {
|
|
144
|
+
const stats = {};
|
|
145
|
+
for (const [agent] of agentBudgets) {
|
|
146
|
+
stats[agent] = getTokenBudgetStats(agent);
|
|
147
|
+
}
|
|
148
|
+
return stats;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reset all token budgets.
|
|
152
|
+
*/
|
|
153
|
+
export function resetTokenBudgets() {
|
|
154
|
+
agentBudgets.clear();
|
|
155
|
+
}
|
|
156
|
+
export { CONFIG as TokenBudgetConfig };
|