MemberJunction integration layer for the Computer Use browser automation engine, providing seamless integration with MJ's credential management, AI prompts, actions system, and testing framework.
The MJComputerUse package extends the base @memberjunction/computer-use engine with deep MemberJunction integration. It bridges vision-based browser automation with MJ's infrastructure, enabling:
This package is Layer 2 of MJ's Computer Use capabilities - it requires the base @memberjunction/computer-use package and provides the MJ-specific orchestration layer.
Computer Use - Controller / Computer Use - Judge metadata prompts โ which carry both the template and the model selection (see Controller & Judge Defaults)connect field โ see External Browser Attach Guidenpm install @memberjunction/computer-use-engine
Dependencies: This package requires the base Computer Use package and core MJ packages:
npm install @memberjunction/computer-use \
@memberjunction/core \
@memberjunction/core-entities \
@memberjunction/ai \
@memberjunction/aiengine \
@memberjunction/ai-prompts \
@memberjunction/actions \
@memberjunction/testing-engine
The simplest way to use MJ Computer Use is through the registered Computer Use action:
import { RunAction } from '@memberjunction/actions';
const result = await RunAction({
ActionName: 'Computer Use',
Params: {
Goal: "Navigate to news.ycombinator.com and get the top story title",
StartUrl: "https://news.ycombinator.com",
MaxSteps: "10",
Headless: "false",
ControllerPromptName: "Computer Use: Controller",
JudgePromptName: "Computer Use: Judge"
},
ContextUser: contextUser
});
if (result.Success) {
console.log(`Status: ${result.Params.find(p => p.Name === 'Status')?.Value}`);
console.log(`Total Steps: ${result.Params.find(p => p.Name === 'TotalSteps')?.Value}`);
console.log(`Final URL: ${result.Params.find(p => p.Name === 'FinalUrl')?.Value}`);
// Access screenshot
const screenshot = result.Params.find(p => p.Name === 'FinalScreenshot')?.Value;
// screenshot is base64 PNG
}
For programmatic access with full type safety:
import { MJComputerUseEngine, MJRunComputerUseParams, PromptEntityRef } from '@memberjunction/computer-use-engine';
import { Metadata } from '@memberjunction/core';
const engine = new MJComputerUseEngine();
const params = new MJRunComputerUseParams();
// Set goal and basic config
params.Goal = "Login to example.com and navigate to dashboard";
params.StartUrl = "https://example.com/login";
params.MaxSteps = 20;
params.Headless = false;
// Use MJ AI Prompts for controller and judge
params.ControllerPromptRef = new PromptEntityRef();
params.ControllerPromptRef.PromptName = "Computer Use: Controller";
params.JudgePromptRef = new PromptEntityRef();
params.JudgePromptRef.PromptName = "Computer Use: Judge";
// If you pin NEITHER a prompt nor a model, the engine defaults to the stored
// "Computer Use - Controller" / "Computer Use - Judge" prompts (which carry the
// model selection too). Auto-selection of a vision LLM happens only if that
// stored prompt is missing. See "Controller & Judge Defaults" below.
// Provide MJ context
params.ContextUser = contextUser;
params.AgentRunId = agentRunId; // Optional: link to agent execution
const result = await engine.Run(params);
if (result.Success) {
console.log(`Goal completed in ${result.TotalSteps} steps`);
console.log(`Final URL: ${result.FinalUrl}`);
console.log(`Judge verdict: ${result.FinalJudgeVerdict?.Reason}`);
}
// Screenshots are automatically persisted as AIPromptRunMedia entities
import { MJRunComputerUseParams, MJDomainAuthBinding } from '@memberjunction/computer-use-engine';
import { Metadata } from '@memberjunction/core';
const md = new Metadata();
// Load credentials from MJ
const apiKeyCredential = await md.GetEntityObject<CredentialEntity>('Credentials', contextUser);
await apiKeyCredential.Load(`Name='Example API Key'`);
const oauthCredential = await md.GetEntityObject<CredentialEntity>('Credentials', contextUser);
await oauthCredential.Load(`Name='Example OAuth'`);
// Configure auth bindings
const params = new MJRunComputerUseParams();
params.Auth = new ComputerUseAuthConfig();
// API key for main domain
const apiBinding = new MJDomainAuthBinding(
["example.com", "*.example.com"],
undefined // No explicit method - will be resolved from credential
);
apiBinding.Credential = apiKeyCredential;
params.Auth.Bindings.push(apiBinding);
// OAuth for API subdomain
const oauthBinding = new MJDomainAuthBinding(
["api.example.com"],
undefined
);
oauthBinding.Credential = oauthCredential;
params.Auth.Bindings.push(oauthBinding);
// Engine will automatically resolve Credential to appropriate AuthMethod
const result = await engine.Run(params);
Supported Credential Types (mapped automatically):
"API Key" โ APIKeyHeaderAuthMethod"API Key with Endpoint" โ BearerTokenAuthMethod"Basic Auth" โ BasicAuthMethod (FormLogin variant)"OAuth2 Client Credentials" โ OAuthClientCredentialsAuthMethodExpose any MJ Action to the controller LLM as a callable tool:
import { MJRunComputerUseParams, ActionRef } from '@memberjunction/computer-use-engine';
const params = new MJRunComputerUseParams();
params.Goal = "Research competitor pricing and send summary email";
// Add actions as tools
params.Actions = [
{ ActionName: "Send Email" },
{ ActionName: "Create Record" },
{ ActionId: "some-uuid-here" } // Can reference by ID
];
params.ContextUser = contextUser;
const result = await engine.Run(params);
// Check which actions were called
for (const step of result.Steps) {
if (step.ToolCalls) {
for (const toolCall of step.ToolCalls) {
console.log(`Action called: ${toolCall.ToolName}`);
console.log(`Input: ${JSON.stringify(toolCall.Input)}`);
console.log(`Output: ${JSON.stringify(toolCall.Output)}`);
}
}
}
How it works:
ComputerUseTool<TInput, TOutput>Define tests using the MJ testing framework with Computer Use configuration:
import { Metadata } from '@memberjunction/core';
const md = new Metadata();
// Create test entity
const test = await md.GetEntityObject<TestEntity>('Tests', contextUser);
test.Name = "Login Flow Test";
test.TypeID = "computer-use-type-id"; // Reference to Computer Use test type
test.Status = "Draft";
// Configuration (ComputerUseTestConfig JSON)
test.Configuration = JSON.stringify({
headless: true,
maxSteps: 15,
maxExecutionTime: 120000, // 2 minutes
controllerPromptName: "Computer Use: Controller",
judgePromptName: "Computer Use: Judge",
judgeFrequency: "EveryNSteps:3",
viewportWidth: 1280,
viewportHeight: 720,
oracles: [
{
type: "goal-completion",
weight: 0.6,
config: { minConfidence: 0.7 }
},
{
type: "url-match",
weight: 0.4,
config: { pattern: "^https://example\\.com/dashboard" }
}
],
scoringWeights: {
"goal-completion": 0.6,
"url-match": 0.4
}
});
// Input Definition (ComputerUseTestInput JSON)
test.InputDefinition = JSON.stringify({
goal: "Login to example.com with test credentials and navigate to dashboard",
startUrl: "https://example.com/login",
allowedDomains: ["example.com", "*.example.com"],
auth: {
bindings: [
{
domains: ["example.com"],
credentialName: "Test User Credentials"
}
]
}
});
// Expected Outcomes (ComputerUseExpectedOutcomes JSON)
test.ExpectedOutcomes = JSON.stringify({
goalCompleted: true,
finalUrlPattern: "^https://example\\.com/dashboard",
minConfidence: 0.7,
maxSteps: 15
});
await test.Save();
import { ComputerUseTestDriver } from '@memberjunction/computer-use-engine';
import { TestRunnerEngine } from '@memberjunction/testing-engine';
const driver = new ComputerUseTestDriver();
const testRunner = new TestRunnerEngine();
// Execute test
const result = await testRunner.RunTest({
TestId: test.ID,
ContextUser: contextUser
});
console.log(`Test Status: ${result.Status}`);
console.log(`Overall Score: ${result.Score}`);
console.log(`Duration: ${result.DurationMs}ms`);
// Check oracle results
for (const oracleResult of result.OracleResults) {
console.log(`Oracle: ${oracleResult.Type}`);
console.log(` Passed: ${oracleResult.Passed}`);
console.log(` Score: ${oracleResult.Score}`);
console.log(` Message: ${oracleResult.Message}`);
}
Validates that the judge determined the goal was completed with sufficient confidence:
{
"type": "goal-completion",
"weight": 0.6,
"config": {
"minConfidence": 0.7
}
}
Scoring:
Confidence * 0.5Confidence scoreConfidence score (0.5-1.0)Validates final browser URL matches expected regex pattern:
{
"type": "url-match",
"weight": 0.3,
"config": {
"pattern": "^https://example\\.com/dashboard$"
}
}
Scoring:
Alternative: Can also define in ExpectedOutcomes.finalUrlPattern instead of oracle config.
Validates task completed within expected number of steps (efficiency check):
{
"type": "step-count",
"weight": 0.1,
"config": {
"maxSteps": 10
}
}
Scoring:
0.5 + 0.5 * (1 - steps/maxSteps) (efficiency bonus)Alternative: Can also define in ExpectedOutcomes.maxSteps or Configuration.maxSteps.
Register custom oracle implementations via the global oracle registry:
import { OracleRegistry } from '@memberjunction/testing-engine';
import { BaseOracle } from '@memberjunction/testing-engine';
class CustomScreenshotOracle extends BaseOracle {
async Evaluate(actualOutput: Record<string, unknown>, expectedOutcome: Record<string, unknown>): Promise<OracleResult> {
// Your custom evaluation logic
const screenshot = actualOutput.FinalScreenshot as string;
// Example: check if screenshot contains specific visual element
const passed = await this.analyzeScreenshot(screenshot);
return {
Passed: passed,
Score: passed ? 1.0 : 0.0,
Message: passed ? "Screenshot validation passed" : "Expected element not found",
Type: "custom-screenshot"
};
}
}
// Register globally
OracleRegistry.RegisterOracle("custom-screenshot", CustomScreenshotOracle);
Extends RunComputerUseParams from base package with MJ-specific fields:
class MJRunComputerUseParams extends RunComputerUseParams {
// Base fields (inherited)
Goal: string; // Required: natural language goal
StartUrl?: string;
MaxSteps: number = 30;
Headless: boolean = true;
AllowedDomains?: string[];
BlockedDomains?: string[];
// ... see base package for full list
// MJ-specific fields
ControllerPromptRef?: PromptEntityRef; // Reference to MJ AI Prompt entity
JudgePromptRef?: PromptEntityRef; // Reference to MJ AI Prompt entity
Actions?: ActionRef[]; // MJ Actions to expose as tools
ContextUser?: UserInfo; // MJ security context (required)
AgentRunId?: string; // Link to parent agent run
}
Reference to an MJ AI Prompt entity:
class PromptEntityRef {
PromptId?: string; // Takes precedence - direct entity ID
PromptName?: string; // Fallback - lookup by name
}
// Usage
const ref = new PromptEntityRef();
ref.PromptName = "Computer Use: Controller";
// OR
ref.PromptId = "12345678-1234-1234-1234-123456789012";
Reference to an MJ Action entity:
class ActionRef {
ActionId?: string; // Takes precedence - direct entity ID
ActionName?: string; // Fallback - lookup by name
}
// Usage
params.Actions = [
{ ActionName: "Send Email" },
{ ActionName: "Create Record" },
{ ActionId: "specific-action-uuid" }
];
Auth binding that can use MJ Credential entities:
class MJDomainAuthBinding extends DomainAuthBinding {
Credential?: CredentialEntity; // MJ Credential to use for auth
}
// Usage
const binding = new MJDomainAuthBinding(
["example.com"],
undefined // Method will be auto-resolved from Credential
);
binding.Credential = credentialEntity;
Credential Type Mapping:
| Credential Type Name | Resolved AuthMethod |
|---|---|
"API Key" |
APIKeyHeaderAuthMethod |
"API Key with Endpoint" |
BearerTokenAuthMethod |
"Basic Auth" |
BasicAuthMethod (FormLogin) |
"OAuth2 Client Credentials" |
OAuthClientCredentialsAuthMethod |
Test configuration JSON structure (stored in TestEntity.Configuration):
{
headless?: boolean; // Default: true
maxSteps?: number; // Default: 30
maxExecutionTime?: number; // Timeout in ms (default: 300000)
screenshotHistoryDepth?: number; // Ring buffer size
controllerPromptName?: string; // MJ prompt name
judgePromptName?: string; // MJ prompt name
controllerModel?: { // Explicit model override
vendor: string;
model: string;
driverClass?: string;
};
judgeModel?: { // Explicit model override
vendor: string;
model: string;
driverClass?: string;
};
judgeFrequency?: string; // "EveryStep", "EveryNSteps:5", "OnStagnation:3"
viewportWidth?: number; // Default: 1280
viewportHeight?: number; // Default: 720
oracles?: Array<{ // Test evaluation rules
type: string;
weight: number;
config?: Record<string, unknown>;
}>;
scoringWeights?: Record<string, number>; // Oracle weight overrides
agentRunId?: string; // Link to agent run
actions?: Array<{ // MJ Actions as tools
actionName?: string;
actionId?: string;
}>;
}
Test input definition JSON (stored in TestEntity.InputDefinition):
{
goal: string; // Required: natural language goal
startUrl?: string; // Starting URL
allowedDomains?: string[]; // Domain whitelist
blockedDomains?: string[]; // Domain blacklist
auth?: {
bindings?: Array<{
domains: string[];
credentialName?: string;
credentialId?: string;
method?: Record<string, unknown>; // Explicit auth method
}>;
globalCallback?: string; // Not supported in JSON - use code
};
}
Expected test outcomes JSON (stored in TestEntity.ExpectedOutcomes):
{
goalCompleted?: boolean; // Should goal complete? (default: true)
finalUrlPattern?: string; // Regex for final URL validation
minConfidence?: number; // Judge confidence threshold (0.0-1.0)
maxSteps?: number; // Max steps allowed
judgeValidationCriteria?: string[]; // Custom criteria (future use)
}
MJComputerUseEngine.Run defaults the controller and judge to the stored Computer Use - Controller / Computer Use - Judge metadata prompts (exported as DEFAULT_CONTROLLER_PROMPT_NAME / DEFAULT_JUDGE_PROMPT_NAME) whenever the caller pins neither a prompt nor a model. These stored prompts are the golden source of both the prompt text and the model selection โ by default Gemini 3.1 Flash-Lite โ Gemini 3.5 Flash โ Claude Haiku 4.5 โ GPT 5.5 (each configured on two vendors, highest Priority first, for failover). The goal loop therefore routes through AIPromptRunner, getting prompt-run logging and model failover for free.
Resolution order (per role, controller and judge resolved independently):
ControllerPromptRef/ControllerModel (resp. JudgePromptRef/JudgeModel). If you pin either, it wins.resolveDefaultPromptByName(DEFAULT_CONTROLLER_PROMPT_NAME / DEFAULT_JUDGE_PROMPT_NAME). Used when nothing was pinned. This lookup is non-throwing: a missing stored prompt degrades gracefully to the next step rather than erroring.autoSelectControllerModel() (controller only; the base engine carries a built-in default prompt, so it just needs a model). This is the legacy path, reached only if the stored default prompt is also absent (e.g. standalone / no-metadata callers).Changing the default models is a metadata edit, not a code change. The model choice and failover order live entirely on the stored
Computer Use - Controller/Computer Use - Judgeprompts. Re-point them at different models (or reorder byPriority) in metadata and every default run picks up the change โ no recompile.
const params = new MJRunComputerUseParams();
params.Goal = "Analyze this dashboard and extract key metrics";
// No prompt and no model pinned โ engine uses the stored "Computer Use - Controller"
// / "Computer Use - Judge" prompts (Gemini 3.1 Flash-Lite โ Haiku 4.5 โ GPT 5.5).
const result = await engine.Run(params);
Only when no stored default prompt is found (and no ControllerModel is pinned) does MJComputerUseEngine fall back to auto-selecting the highest-power vision-capable model:
const params = new MJRunComputerUseParams();
params.Goal = "Analyze this dashboard and extract key metrics";
// No ControllerModel specified, and the stored default controller prompt is unavailable
// Engine auto-selects a vision-capable controller model (see selection criteria below)
const result = await engine.Run(params);
Selection Criteria (autoSelectControllerModel โ pickHighestPowerVisionLLM):
LLM-type models, prefer the highest-PowerRank model that advertises Image input
modality (AIEngine.ModelSupportsModality(id, 'Image', 'Input')) โ the controller drives from
screenshots, so it must accept image input.AIModelType-inherited modalities not captured as explicit AIModelModality rows), fall back to
AIEngine.GetHighestPowerLLM() so selection never hard-fails.
pickHighestPowerVisionLLM(models, supportsImageInput)is exported as a pure function (no engine state) and unit-tested in isolation.
MJ AI Prompts support Nunjucks templates with context variables:
You are controlling a browser to accomplish this goal: {{goal}}
Current step: {{stepNumber}}/{{maxSteps}}
Current URL: {{url}}
{% if judgeFeedback %}
Previous judge feedback: {{judgeFeedback}}
{% endif %}
{% if credentials %}
Available credentials:
{% for cred in credentials %}
- {{cred.type}}: {{cred.value}}
{% endfor %}
{% endif %}
Analyze the screenshot and determine next action...
Available Template Variables:
{{goal}} - User's natural language goal{{stepNumber}} - Current step number{{maxSteps}} - Maximum steps configured{{url}} - Current page URL{{judgeFeedback}} - Feedback from recent judge verdict{{credentials}} - Auth credentials (for FormLogin){{tools}} - JSON schema of available toolsOverride onStepComplete to implement custom persistence:
import { MJComputerUseEngine } from '@memberjunction/computer-use-engine';
import { Metadata } from '@memberjunction/core';
class CustomMJEngine extends MJComputerUseEngine {
protected override async onStepComplete(
step: StepRecord,
context: RunContext
): Promise<void> {
// Call base implementation (persists to AIPromptRunMedia)
await super.onStepComplete(step, context);
// Your custom persistence
const md = new Metadata();
const customEntity = await md.GetEntityObject('CustomStepLog', this.contextUser);
customEntity.StepNumber = step.StepNumber;
customEntity.Screenshot = step.Screenshot;
customEntity.Reasoning = step.ControllerReasoning;
customEntity.AgentRunId = this.agentRunId;
await customEntity.Save();
}
}
Connect Computer Use operations to agent executions for audit trails:
const params = new MJRunComputerUseParams();
params.Goal = "Research and summarize competitor features";
params.AgentRunId = agentRun.ID; // Link to parent agent run
params.ContextUser = contextUser;
const result = await engine.Run(params);
// All screenshots persisted to AIPromptRunMedia will have:
// - Relationship to AIPromptRun
// - AIPromptRun linked to AgentRunId
// - Complete audit trail from agent โ run โ media
import { MJRunComputerUseParams, MJDomainAuthBinding } from '@memberjunction/computer-use-engine';
import { ComputerUseAuthConfig, CookieInjectionAuthMethod } from '@memberjunction/computer-use';
const params = new MJRunComputerUseParams();
params.Auth = new ComputerUseAuthConfig();
// MJ credential for main domain
const mainBinding = new MJDomainAuthBinding(["example.com"], undefined);
mainBinding.Credential = await loadCredential("Example API Key");
params.Auth.Bindings.push(mainBinding);
// Explicit cookie method for subdomain
const cookieBinding = new MJDomainAuthBinding(
["secure.example.com"],
new CookieInjectionAuthMethod({
Cookies: [{
Name: "session",
Value: "secure-token",
Domain: "secure.example.com",
Path: "/",
Secure: true,
HttpOnly: true
}]
})
);
params.Auth.Bindings.push(cookieBinding);
// MJ OAuth for API
const apiBinding = new MJDomainAuthBinding(["api.example.com"], undefined);
apiBinding.Credential = await loadCredential("Example OAuth");
params.Auth.Bindings.push(apiBinding);
const result = await engine.Run(params);
import { MJComputerUseEngine } from '@memberjunction/computer-use-engine';
const engine = new MJComputerUseEngine();
const params = new MJRunComputerUseParams();
params.Goal = "Long-running research task...";
params.ContextUser = contextUser;
// Start execution
const resultPromise = engine.Run(params);
// Set timeout
const timeout = setTimeout(() => {
engine.Stop(); // Graceful cancellation after current step
}, 120000); // 2 minutes
const result = await resultPromise;
clearTimeout(timeout);
if (result.Status === "Cancelled") {
console.log(`Cancelled after ${result.TotalSteps} steps`);
console.log(`Partial results available in ${result.Steps.length} steps`);
}
| Feature | Base Package | MJ Enhancement |
|---|---|---|
| Prompt Execution | Direct LLM calls | AIPromptRunner with templates and failover |
| Model Selection | Explicit in params | Defaults to the stored controller/judge prompts (which carry the model selection); auto-selects a vision model only as a fallback |
| Authentication | Generic AuthMethod |
MJ Credential entities auto-resolved |
| Tools | Custom JS functions | MJ Actions with metadata-driven schemas |
| Judge | LLMJudge base class |
MJLLMJudge with prompt entity reference |
| Persistence | In-memory only | Screenshots as AIPromptRunMedia entities |
| Testing | Not provided | Full testing-engine integration |
| Agent Tracking | Not supported | AgentRunId parameter for linkage |
ComputerUseEngine (base package)
โ
MJComputerUseEngine (this package)
- Override: executeControllerPrompt() โ AIPromptRunner
- Override: executeJudgePrompt() โ AIPromptRunner
- Override: onStepComplete() โ AIPromptRunMedia persistence
- Override: Run() โ Resolve MJ references
ComputerUseAction.InternalRunAction()
โ
Build MJRunComputerUseParams from string params
โ
MJComputerUseEngine.Run()
โ
Resolve prompt refs โ AIPromptEntityExtended
โ
Default to stored controller/judge prompts if neither prompt nor model pinned
โ
Auto-select controller model (fallback โ only if no stored default prompt)
โ
Resolve credential-backed auth bindings
โ
Wrap MJ Actions as ComputerUseTool instances
โ
Delegate to base ComputerUseEngine.Run()
โ
For each step:
โโ executeControllerPrompt() โ AIPromptRunner
โ โโ Template rendering (Nunjucks)
โ โโ Model selection with failover
โ โโ Prompt run logging
โ โโ Token/cost tracking
โโ Execute browser actions
โโ Execute tool calls (wrapped Actions)
โโ Judge evaluation (if configured)
โโ onStepComplete() โ AIPromptRunMedia persistence
โ
Return ComputerUseResult
โ
Map to ActionResultSimple (if from action)
// MJLLMJudge extends base LLMJudge
class MJLLMJudge extends LLMJudge {
private judgePromptEntity: AIPromptEntityExtended;
get JudgePromptEntity(): AIPromptEntityExtended {
return this.judgePromptEntity;
}
}
// MJComputerUseEngine uses it
const judge = new MJLLMJudge(
(request) => this.executeJudgePrompt(request),
judgePromptEntity,
params.JudgePrompt
);
any types - MJ maintains strict typing throughoutscreenshotHistoryDepth// In your MJ Agent implementation
class ResearchAgent extends BaseAgent {
async Execute(params: AgentParams): Promise<AgentResult> {
const engine = new MJComputerUseEngine();
const cuParams = new MJRunComputerUseParams();
cuParams.Goal = "Research competitor pricing for product X";
cuParams.StartUrl = "https://google.com";
cuParams.MaxSteps = 30;
// Link to this agent run
cuParams.AgentRunId = this.currentRunId;
cuParams.ContextUser = params.ContextUser;
// Use agent's configured prompts
cuParams.ControllerPromptRef = { PromptName: "Research Agent: Browser Controller" };
// Expose tools for data extraction
cuParams.Actions = [
{ ActionName: "Save Research Data" },
{ ActionName: "Create Summary" }
];
const result = await engine.Run(cuParams);
return {
Success: result.Success,
Data: { findings: result.FinalUrl, steps: result.TotalSteps }
};
}
}
// Run all Computer Use tests in CI/CD
import { TestRunnerEngine } from '@memberjunction/testing-engine';
import { RunView } from '@memberjunction/core';
async function runComputerUseTests(contextUser: UserInfo) {
const rv = new RunView();
const testsResult = await rv.RunView<TestEntity>({
EntityName: 'Tests',
ExtraFilter: `TypeID='computer-use-type-id' AND Status='Active'`,
ResultType: 'entity_object'
}, contextUser);
const runner = new TestRunnerEngine();
const results = [];
for (const test of testsResult.Results) {
const result = await runner.RunTest({
TestId: test.ID,
ContextUser: contextUser
});
results.push({
testName: test.Name,
passed: result.Status === 'Passed',
score: result.Score,
duration: result.DurationMs,
oracles: result.OracleResults
});
}
return results;
}
// Update credentials and re-run tests
async function updateCredentialAndTest(credentialId: string, newValue: string, contextUser: UserInfo) {
const md = new Metadata();
// Update credential
const credential = await md.GetEntityObject<CredentialEntity>('Credentials', contextUser);
await credential.Load(`ID='${credentialId}'`);
credential.Value = newValue; // Will be encrypted on save
await credential.Save();
// Re-run tests that use this credential
const tests = await findTestsUsingCredential(credentialId, contextUser);
const runner = new TestRunnerEngine();
for (const test of tests) {
const result = await runner.RunTest({
TestId: test.ID,
ContextUser: contextUser
});
if (!result.Success) {
console.error(`Test ${test.Name} failed after credential update`);
}
}
}
Symptoms: Error "Prompt not found" when running with PromptEntityRef.
Solutions:
SELECT * FROM [AI Prompts] WHERE Name = 'Your Prompt Name'Symptoms: Controller calls action but gets validation error.
Solutions:
Symptoms: Expected AIPromptRunMedia entity not created.
Solutions:
onStepComplete was called (check logs)Symptoms: Test exceeds maxExecutionTime and is cancelled.
Solutions:
maxExecutionTime in test configurationmaxSteps to prevent runaway executionnavigationTimeoutMsSymptoms: Test score doesn't match expected value.
Solutions:
scoringWeights sum to 1.0OracleResultsweight values are set correctlyconfig parametersSymptoms: Error "No vision-capable model found".
Solutions:
LLM-type AI Model is configured in MJAIModelModality row (that's what
ModelSupportsModality(id, 'Image', 'Input') reads) โ otherwise it's only picked up by the
highest-power fallback, not the vision-preferred pathControllerModel as a fallbackautoSelectControllerModel logs the chosen model + whether it
came from the vision-preferred path or the fallback)// Main engine
export { MJComputerUseEngine } from './engine/MJComputerUseEngine';
// Parameter types
export {
MJRunComputerUseParams,
PromptEntityRef,
ActionRef,
MJDomainAuthBinding
} from './types/mj-params';
// MJ Action wrapper
export { ComputerUseAction } from './action/ComputerUseAction';
// Test driver
export { ComputerUseTestDriver } from './test-driver/ComputerUseTestDriver';
// Test configuration types
export type {
ComputerUseTestConfig,
ComputerUseTestInput,
ComputerUseExpectedOutcomes,
ComputerUseOracleConfig
} from './test-driver/types';
// Judge implementation
export { MJLLMJudge } from './judge/MJLLMJudge';
// Utilities
export { parseJudgeFrequency } from './utils/judge-frequency-parser';
// Tree-shaking prevention
export { LoadMJComputerUse } from './index';
We welcome contributions! Please see the main MemberJunction Contributing Guide for details.
# Clone repository
git clone https://github.com/memberjunction/mj.git
cd mj
# Install dependencies
npm install
# Build dependencies
cd packages/AI/ComputerUse
npm run build
cd ../MJComputerUse
npm run build
# Run tests
npm test
BaseOracleEvaluate() methodLoadMJComputerUse() functionThis package is part of the MemberJunction project and is licensed under the MIT License. See LICENSE for details.