Testing Plugin
Overview
The @tokenring-ai/testing package provides an automated testing framework for AI agents within the Token Ring ecosystem. It enables declarative test resource configuration, shell command integration, and automatic test execution with error repair workflows.
Key Features
- Testing Resources: Pluggable test resource system with
TestingResourceinterface - Shell Command Tests: Built-in
ShellCommandTestingResourcefor running test commands - Service Layer: Central
TestingServicefor managing test resources and execution - Chat Commands: Interactive
/testcommand for manual test control - Automation Hooks: Automatic test execution after file modifications via
autoTesthook - Configuration-Based Setup: Declarative resource configuration through Zod schemas
- State Management: Checkpoint-based state preservation with test results tracking and repair count
- Repair Workflows: Automatic test failure recovery with configurable auto-repair limits
Core Components
TestingService
The central service that manages testing resources and executes tests across all registered resources.
Class Signature:
class TestingService implements TokenRingService {
name: string = "TestingService"
description: string = "Provides testing functionality"
registerResource(name: string, resource: TestingResource): void
getAvailableResources(): Iterable<string>
runTests(likeName: string, agent: Agent): Promise<void>
allTestsPassed(agent: Agent): boolean
attach(agent: Agent): void
constructor(readonly options: z.output<typeof TestingServiceConfigSchema>)
}
TestingResource Interface
Base interface for implementing custom test resources.
Interface Definition:
interface TestingResource {
description: string;
runTest: (agent: Agent) => Promise<TestResult>;
}
ShellCommandTestingResource
Comprehensive implementation of testing resources that execute shell commands as tests.
Constructor Options:
interface ShellCommandTestingResourceOptions {
type: "shell";
name: string;
description?: string;
workingDirectory?: string;
command: string;
timeoutSeconds?: number;
}
Properties:
class ShellCommandTestingResource implements TestingResource {
description: string = "Provides ShellCommandTesting functionality";
readonly options: z.output<typeof shellCommandTestingConfigSchema>;
}
TestResult Interface
Defines the structure for test execution results.
interface TestResult {
startedAt: number;
finishedAt: number;
passed: boolean;
output?: string;
error?: unknown;
}
Configuration Schemas
TestingServiceConfigSchema (Plugin-level configuration):
const TestingServiceConfigSchema = z.object({
agentDefaults: z.object({
maxAutoRepairs: z.number().default(5),
}).strict(),
resources: z.record(z.string(), z.any()).optional(),
}).strict();
shellCommandTestingConfigSchema (Resource-level configuration):
const shellCommandTestingConfigSchema = z.object({
type: z.literal("shell"),
name: z.string(),
description: z.string().optional(),
workingDirectory: z.string().optional(),
command: z.string(),
timeoutSeconds: z.number().optional(),
}).strict();
TestingAgentConfigSchema (Agent-level configuration):
const TestingAgentConfigSchema = z.object({
maxAutoRepairs: z.number().optional(),
}).default({});
Usage Examples
Installation and Setup
import testingPlugin from '@tokenring-ai/testing/plugin';
// Add testing plugin to application
app.addPlugin(testingPlugin);
Basic Resource Configuration
Configure testing resources through the plugin system:
const appConfig = {
testing: {
agentDefaults: {
maxAutoRepairs: 5
},
resources: {
build-test: {
type: "shell",
name: "build-test",
command: "bun run build",
workingDirectory: "./project",
timeoutSeconds: 120
},
unit-tests: {
type: "shell",
name: "unit-tests",
command: "bun test",
workingDirectory: "./project"
}
}
}
};
app.config = {
testing: TestingServiceConfigSchema.parse(appConfig.testing)
};
Interactive Testing Workflow
Use the /test command in the chat interface:
/test list # List all available tests
/test run build-test # Run specific test
/test run # Run all tests (uses "*" pattern)
/test run userAuth # Run test matching a name pattern
Programmatic Usage
import TestingService from '@tokenring-ai/testing/TestingService';
import ShellCommandTestingResource from '@tokenring-ai/testing/ShellCommandTestingResource';
// Create service with configuration
const testingService = new TestingService({
agentDefaults: { maxAutoRepairs: 5 },
resources: {}
});
// Register shell command resource
const shellResource = new ShellCommandTestingResource({
type: "shell",
name: 'build-test',
command: 'bun run build',
workingDirectory: './project',
timeoutSeconds: 120
});
testingService.registerResource('build-test', shellResource);
// Run tests using agent context
await testingService.runTests("*", agent);
// Check if all tests passed
const allPassed = testingService.allTestsPassed(agent);
if (allPassed) {
agent.chatOutput("All tests passed!");
}
Resource Registration from Config
Programmatically register resources from application configuration:
import {TestingServiceConfigSchema, shellCommandTestingConfigSchema} from '@tokenring-ai/testing';
const appConfig = {
testing: {
agentDefaults: { maxAutoRepairs: 5 },
resources: {
build: {
type: "shell",
name: "build",
command: "bun run build"
},
test: {
type: "shell",
name: "test",
command: "bun test"
}
}
}
};
// Parse configuration
const testingConfig = TestingServiceConfigSchema.parse(appConfig.testing);
// Create service
const testingService = new TestingService(testingConfig);
// Register resources
if (testingConfig.resources) {
for (const name in testingConfig.resources) {
const resourceConfig = testingConfig.resources[name];
if (resourceConfig.type === "shell") {
const parsed = shellCommandTestingConfigSchema.parse(resourceConfig);
testingService.registerResource(
name,
new ShellCommandTestingResource(parsed)
);
}
}
}
Chat Commands
/test Command
Run and manage tests interactively through the chat interface.
Subcommands:
/test list- Display all available test resources/test run [test_name|*]- Execute specific test or all tests
Help Text:
/test list - Show all available tests
/test run <test_name> - Run a specific test
/test run - Run all available tests (default)
If tests fail, the agent will track the results and may offer to automatically repair the errors, provided the maximum auto-repair limit hasn't been reached.
Example Usage
/test list
# Output: Available tests:
# build-test
# unit-tests
# integration-tests
/test run build-test
# Output: [Test: build-test]: PASSED
/test run
# Output: All tests passed!
Automation Hooks
autoTest Hook
Automatically executes tests after chat completion when the filesystem has been modified.
Hook Configuration:
const hookConfig = {
name: "autoTest",
displayName: "Testing/Auto Test",
description: "Runs tests automatically after chat is complete",
afterChatCompletion: async (agent: Agent) => {
const filesystem = agent.requireServiceByType(FileSystemService);
const testingService = agent.requireServiceByType(TestingService);
if (filesystem.isDirty(agent)) {
agent.infoMessage("Working Directory was updated, running test suite...");
await testingService.runTests("*", agent);
}
}
};
Trigger Behavior:
- Hook fires after chat completion event
- Checks filesystem for modifications via
filesystem.isDirty(agent) - If modified, displays message "Working Directory was updated, running test suite..."
- Executes all registered tests matching "*"
- Reports pass/fail status for each test
- On failures, offers auto-repair with timeout based on
maxAutoRepairsconfiguration
Configuration Impact:
- Uses
agentDefaults.maxAutoRepairsfrom the TestingService configuration - Agent-level
testing.maxAutoRepairsoverrides service defaults - Timeout is 30 seconds unless
repairCount > maxAutoRepairsthen undefined - Integrates with agent's
handleInput()for repair workflows
State Management
TestingState Class
Manages test state including results, repair count, and persistence.
class TestingState implements AgentStateSlice<typeof serializationSchema> {
name: string = "TestingState";
serializationSchema: z.infer<typeof serializationSchema>;
testResults: Record<string, TestResult> = {};
repairCount: number = 0;
maxAutoRepairs: number;
constructor(readonly initialConfig: z.output<typeof TestingServiceConfigSchema>["agentDefaults"])
reset(what: ResetWhat[]): void
serialize(): z.output<typeof serializationSchema>
deserialize(data: z.output<typeof serializationSchema>): void
show(): string[]
}
State Properties:
testResults: Record mapping test names to their resultsrepairCount: Number of auto-repair attempts mademaxAutoRepairs: Maximum allowed auto-repair attempts
Serialization Schema:
const serializationSchema = z.object({
testResults: z.record(z.string(), z.any()),
repairCount: z.number(),
maxAutoRepairs: z.number()
});
State Display Method:
The show() method returns formatted output:
[
"Test Results:",
"[Test: name]: PASSED",
"", // Empty line between sections
"Total Repairs: 2"
]
State Lifecycle:
- Initialization: Created via
agent.initializeState(TestingState, config)duringattach() - Snapshot: Run tests update
testResultsand incrementrepairCount - Persistence: State serialized via
serialize()for checkpoint recovery - Restoration: State deserialized via
deserialize(data)from checkpoints - Reset:
reset(what)method for selective state clearing - UI Display:
show()method for formatted status output
Services and Tools
Plugin Services
TokenRingService Implementation:
- Implements
TokenRingServiceinterface - Registered with application via
app.addServices(testingService) - Uses
agentDefaultsconfiguration merged with agent-specific settings viadeepMerge() - Provides
attach()method for agent state initialization
Dependencies
Direct Dependencies:
zod: Runtime type validation for configuration schemas@tokenring-ai/app: Application framework and plugin system@tokenring-ai/chat: Chat functionality@tokenring-ai/agent: Agent framework and command system@tokenring-ai/filesystem: File system operations and shell command execution@tokenring-ai/queue: Queue utilities@tokenring-ai/utility: Utility classes including KeyedRegistry for resource management
Development Dependencies:
typescript: TypeScript compilationvitest: Unit testing framework with watch mode and coverage support
Package Structure
pkg/testing/
├── TestingService.ts # Core testing service implementation
├── TestingResource.ts # TestingResource interface definition
├── ShellCommandTestingResource.ts # Shell command resource handler
├── schema.ts # Zod schemas for configuration
├── state/
│ └── testingState.ts # TestingState class for state management
├── commands/
│ └── test/
│ ├── list.ts # /test list subcommand
│ └── run.ts # /test run subcommand
├── chatCommands.ts # Chat command exports
├── hooks.ts # Hook exports
├── hooks/
│ └── autoTest.ts # autoTest hook implementation
├── plugin.ts # TokenRingPlugin definition
├── index.ts # Public exports
├── package.json # Package metadata and dependencies
├── vitest.config.ts # Vitest test configuration
└── tsconfig.json # TypeScript compiler configuration
Development
Building
bun run build # Type-check the code
bun run eslint # Run linter
Testing
bun run test # Run all tests
bun run test:watch # Run tests in watch mode
bun run test:coverage # Generate coverage report
Environment
- Node.js Version: v18+
- Package Manager: bun (recommended)
- Testing Framework: vitest
- Type Safety: TypeScript strict mode
Best Practices
Resource Configuration
-
Use Descriptive Names: Test resource names should clearly indicate their purpose
resources: {
"lint-typescript": { type: "shell", command: "tsc --noEmit" },
"test-unit": { type: "shell", command: "bun test" }
} -
Set Working Directories: Use absolute paths or relative to project root
workingDirectory: process.cwd() -
Configure Timeouts: Set appropriate timeouts for resource-intensive operations
timeoutSeconds: 300 // 5 minutes for full test suite
State Management
-
Enable Auto-Repairs: Set reasonable
maxAutoRepairsto enable testing-based repair workflowsagentDefaults: { maxAutoRepairs: 5 } -
Track Test Results: Use
allTestsPassed(agent)to check overall statusconst allPassed = testingService.allTestsPassed(agent);
if (!allPassed) {
// Handle failures
} -
Review State: Check
TestingState.show()for detailed resultsconst state = agent.getState(TestingState);
console.log(state.show());
Test Design
- Separate Tests: Create dedicated resources for different testing phases
- Clear Output: Shell commands should return meaningful exit codes and messages
- Fast Feedback: Prioritize quick tests (linting, type checking) for frequent runs
Integration
- Hook Automation: Enable
autoTesthook to integrate testing into development workflow - Chat Commands: Leverage
/testcommand for interactive testing scenarios - Plugin Registration: Add plugin to application to ensure all services are registered automatically
Limitations
- Shell tests assume Unix-like environments (Windows paths and commands may need adjustments)
- Auto-repair quality depends on agent capabilities and configuration
- Filesystem modification detection requires proper integration with FileSystemService
- Only shell resource type is provided; custom resources can be implemented
- Repair prompts user but execution of remediation depends on user confirmation
License
MIT License - see LICENSE file in package directory.
Related Components
- @tokenring-ai/filesystem: Provides shell command execution via
executetool - @tokenring-ai/agent: Agent framework for command execution and state management
- @tokenring-ai/utility: KeyedRegistry for resource management
Version
Current version: 0.2.0