@clawtrial/courtroom 1.0.6 → 2.0.0

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.
@@ -1,344 +0,0 @@
1
-
2
- /**
3
- * Detect which bot is installed (clawdbot, moltbot, or openclaw)
4
- * Returns the bot configuration with name, directory, and command
5
- */
6
- function detectBot() {
7
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
8
-
9
- const bots = [
10
- { name: 'openclaw', dir: '.openclaw', config: 'openclaw.json', command: 'openclaw' },
11
- { name: 'moltbot', dir: '.moltbot', config: 'moltbot.json', command: 'moltbot' },
12
- { name: 'clawdbot', dir: '.clawdbot', config: 'clawdbot.json', command: 'clawdbot' }
13
- ];
14
-
15
- // Check which bot config exists
16
- for (const bot of bots) {
17
- const configPath = path.join(homeDir, bot.dir, bot.config);
18
- if (fs.existsSync(configPath)) {
19
- return bot;
20
- }
21
- }
22
-
23
- // Check which command is available
24
- for (const bot of bots) {
25
- try {
26
- // Check if command exists in PATH
27
- const { execSync } = require('child_process');
28
- execSync(`which ${bot.command}`, { stdio: 'ignore' });
29
- return bot;
30
- } catch {
31
- // Command not found, continue to next
32
- }
33
- }
34
-
35
- // Default to clawdbot
36
- return bots[2];
37
- }
38
-
39
- /**
40
- * Get the config directory for the detected bot
41
- */
42
- function getConfigDir() {
43
- const bot = detectBot();
44
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
45
- return path.join(homeDir, bot.dir);
46
- }
47
-
48
- /**
49
- * Get the config file path for the detected bot
50
- */
51
- function getConfigFile() {
52
- const bot = detectBot();
53
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
54
- return path.join(homeDir, bot.dir, bot.config);
55
- }
56
-
57
- /**
58
- * Get the CLI command for the detected bot
59
- */
60
- function getCommand() {
61
- return detectBot().command;
62
- }
63
-
64
- /**
65
- * Get the bot name for display
66
- */
67
- function getBotName() {
68
- return detectBot().name;
69
- }
70
-
71
- /**
72
- * Environment Detection and Setup
73
- * Detects various agent runtimes and provides setup helpers
74
- */
75
-
76
- const fs = require('fs');
77
- const path = require('path');
78
- const { logger } = require('./debug');
79
-
80
- /**
81
- * Detect available agent runtime
82
- */
83
- function detectAgentRuntime() {
84
- const checks = {
85
- // ClawDBot
86
- clawdbotGlobal: typeof global.clawdbotAgent !== 'undefined' ? global.clawdbotAgent : null,
87
- clawdbotProcess: typeof process.clawdbotAgent !== 'undefined' ? process.clawdbotAgent : null,
88
-
89
- // Generic agent
90
- genericGlobal: typeof global.agent !== 'undefined' ? global.agent : null,
91
-
92
- // Check for common agent patterns
93
- hasLLM: typeof global.llm !== 'undefined',
94
- hasOpenAI: typeof global.openai !== 'undefined',
95
-
96
- // Environment variables
97
- envClawdbot: process.env.CLAUDBOT_ENV === 'true',
98
- envAgent: process.env.AGENT_RUNTIME === 'true'
99
- };
100
-
101
- logger.debug('ENV', 'Runtime detection checks', Object.keys(checks).filter(k => checks[k]));
102
-
103
- // Return first available agent
104
- if (checks.clawdbotGlobal) {
105
- return { type: 'clawdbot', agent: checks.clawdbotGlobal };
106
- }
107
-
108
- if (checks.clawdbotProcess) {
109
- return { type: 'clawdbot', agent: checks.clawdbotProcess };
110
- }
111
-
112
- if (checks.genericGlobal) {
113
- return { type: 'generic', agent: checks.genericGlobal };
114
- }
115
-
116
- return null;
117
- }
118
-
119
- /**
120
- * Wait for agent to become available
121
- */
122
- async function waitForAgent(timeoutMs = 30000, checkInterval = 1000) {
123
- logger.info('ENV', 'Waiting for agent runtime...');
124
-
125
- const startTime = Date.now();
126
-
127
- while (Date.now() - startTime < timeoutMs) {
128
- const agent = detectAgentRuntime();
129
- if (agent) {
130
- logger.info('ENV', `Agent detected: ${agent.type}`);
131
- return agent;
132
- }
133
-
134
- await new Promise(r => setTimeout(r, checkInterval));
135
- }
136
-
137
- logger.warn('ENV', 'Agent not detected within timeout');
138
- return null;
139
- }
140
-
141
- /**
142
- * Create a minimal mock agent for testing
143
- */
144
- function createMockAgent(options = {}) {
145
- logger.info('ENV', 'Creating mock agent for testing');
146
-
147
- return {
148
- id: options.id || 'mock-agent-' + Date.now(),
149
- llm: options.llm || {
150
- call: async ({ messages }) => {
151
- return { content: 'Mock LLM response' };
152
- }
153
- },
154
- model: options.model || { primary: 'mock-model' },
155
- memory: {
156
- get: async (key) => null,
157
- set: async (key, value) => {},
158
- delete: async (key) => {}
159
- },
160
- session: {
161
- getRecentHistory: async (n) => []
162
- },
163
- send: async (message) => {
164
- console.log('[MOCK AGENT]', message);
165
- },
166
- autonomy: {
167
- registerHook: () => {},
168
- unregisterHook: () => {}
169
- }
170
- };
171
- }
172
-
173
- /**
174
- * Check if running in a supported environment
175
- */
176
- function checkEnvironment() {
177
- const issues = [];
178
-
179
- // Check for Node.js
180
- if (typeof process === 'undefined') {
181
- issues.push('Not running in Node.js environment');
182
- }
183
-
184
- // Check for required Node version
185
- const nodeVersion = process.version;
186
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
187
- if (majorVersion < 18) {
188
- issues.push(`Node.js version ${nodeVersion} is too old. Requires >= 18.0.0`);
189
- }
190
-
191
- // Check for writable home directory
192
- const homeDir = process.env.HOME || process.env.USERPROFILE;
193
- if (!homeDir) {
194
- issues.push('HOME environment variable not set');
195
- } else {
196
- try {
197
- const testPath = path.join(homeDir, '.clawdbot');
198
- if (!fs.existsSync(testPath)) {
199
- fs.mkdirSync(testPath, { recursive: true });
200
- }
201
- } catch (err) {
202
- issues.push(`Cannot write to home directory: ${err.message}`);
203
- }
204
- }
205
-
206
- return {
207
- valid: issues.length === 0,
208
- issues,
209
- nodeVersion,
210
- homeDir
211
- };
212
- }
213
-
214
- /**
215
- * Get setup instructions for current environment
216
- */
217
- function getSetupInstructions() {
218
- const env = checkEnvironment();
219
- const agent = detectAgentRuntime();
220
-
221
- if (!env.valid) {
222
- return {
223
- canSetup: false,
224
- message: 'Environment issues detected:',
225
- issues: env.issues,
226
- instructions: 'Please fix the above issues before continuing.'
227
- };
228
- }
229
-
230
- if (agent) {
231
- return {
232
- canSetup: true,
233
- agentType: agent.type,
234
- message: `Agent runtime detected: ${agent.type}`,
235
- instructions: 'Run: clawtrial setup'
236
- };
237
- }
238
-
239
- // No agent detected - provide helpful instructions
240
- return {
241
- canSetup: true,
242
- agentType: null,
243
- message: 'No agent runtime detected',
244
- instructions: `
245
- No AI agent runtime was detected. ClawTrial requires an agent to monitor.
246
-
247
- Options:
248
-
249
- 1. If using ClawDBot:
250
- - Make sure ClawDBot is running
251
- - The courtroom will auto-detect the agent
252
-
253
- 2. If using a custom agent:
254
- - Pass your agent to createCourtroom(agent)
255
-
256
- Example:
257
- const { createCourtroom } = require('@clawtrial/courtroom');
258
- const courtroom = createCourtroom(yourAgent);
259
- await courtroom.initialize();
260
-
261
- 3. For testing:
262
- - Use the mock agent: createCourtroom(null, { useMock: true })
263
-
264
- 4. Manual mode:
265
- - You can still use the CLI commands
266
- - Run: clawtrial status
267
- - Run: clawtrial debug
268
- `
269
- };
270
- }
271
-
272
- /**
273
- * Auto-setup with retries
274
- */
275
- async function autoSetup(courtroom, options = {}) {
276
- const { waitForAgent: shouldWait = true, timeout = 30000 } = options;
277
-
278
- logger.info('ENV', 'Starting auto-setup');
279
-
280
- // Check environment first
281
- const env = checkEnvironment();
282
- if (!env.valid) {
283
- logger.error('ENV', 'Environment check failed', { issues: env.issues });
284
- return {
285
- success: false,
286
- status: 'environment_error',
287
- issues: env.issues
288
- };
289
- }
290
-
291
- // Try to detect agent
292
- let agentInfo = detectAgentRuntime();
293
-
294
- if (!agentInfo && shouldWait) {
295
- logger.info('ENV', 'Agent not immediately available, waiting...');
296
- agentInfo = await waitForAgent(timeout);
297
- }
298
-
299
- if (!agentInfo) {
300
- logger.warn('ENV', 'No agent runtime available');
301
- return {
302
- success: false,
303
- status: 'no_agent',
304
- message: 'No AI agent runtime detected',
305
- instructions: getSetupInstructions().instructions
306
- };
307
- }
308
-
309
- // Agent found, initialize courtroom
310
- try {
311
- logger.info('ENV', `Initializing with ${agentInfo.type} agent`);
312
- const result = await courtroom.initialize();
313
-
314
- return {
315
- success: result.status === 'initialized',
316
- status: result.status,
317
- agentType: agentInfo.type,
318
- result
319
- };
320
- } catch (err) {
321
- logger.error('ENV', 'Initialization failed', { error: err.message });
322
- return {
323
- success: false,
324
- status: 'initialization_error',
325
- error: err.message
326
- };
327
- }
328
- }
329
-
330
-
331
- module.exports = {
332
- detectAgentRuntime,
333
- waitForAgent,
334
- createMockAgent,
335
- checkEnvironment,
336
- getSetupInstructions,
337
- autoSetup,
338
- // Bot detection exports
339
- detectBot,
340
- getConfigDir,
341
- getConfigFile,
342
- getCommand,
343
- getBotName
344
- };
package/src/evaluator.js DELETED
@@ -1,277 +0,0 @@
1
- /**
2
- * Courtroom Evaluator - Agent-Triggered Evaluation
3
- *
4
- * This module handles LLM-based evaluation by:
5
- * 1. Storing messages in a queue file
6
- * 2. Using cron/heartbeat to trigger agent evaluation
7
- * 3. The agent reads the queue and evaluates using its own LLM
8
- * 4. Results are processed and hearings initiated if needed
9
- */
10
-
11
- const fs = require('fs').promises;
12
- const path = require('path');
13
- const { logger } = require('./debug');
14
- const { OFFENSES } = require('./offenses');
15
-
16
- const QUEUE_DIR = path.join(getConfigDir(), 'courtroom');
17
- const QUEUE_FILE = path.join(QUEUE_DIR, 'message_queue.jsonl');
18
- const PENDING_EVAL_FILE = path.join(QUEUE_DIR, 'pending_eval.json');
19
- const RESULTS_FILE = path.join(QUEUE_DIR, 'eval_results.jsonl');
20
-
21
- class CourtroomEvaluator {
22
- constructor(configManager) {
23
- this.config = configManager;
24
- this.queue = [];
25
- this.lastEvalTime = 0;
26
- this.evaluationInterval = 5 * 60 * 1000; // 5 minutes
27
- this.minMessagesForEval = 3;
28
- }
29
-
30
- /**
31
- * Initialize the evaluator
32
- */
33
- async initialize() {
34
- // Ensure queue directory exists
35
- try {
36
- await fs.mkdir(QUEUE_DIR, { recursive: true });
37
- } catch (err) {
38
- // Directory might already exist
39
- }
40
-
41
- logger.info('EVALUATOR', 'Evaluator initialized');
42
- }
43
-
44
- /**
45
- * Queue a message for evaluation
46
- * Called by the skill when messages are received
47
- */
48
- async queueMessage(message) {
49
- const entry = {
50
- timestamp: Date.now(),
51
- role: message.role,
52
- content: message.content,
53
- sessionId: message.sessionId || 'default'
54
- };
55
-
56
- // Append to queue file
57
- await fs.appendFile(QUEUE_FILE, JSON.stringify(entry) + '\n');
58
-
59
- this.queue.push(entry);
60
-
61
- // Keep queue size manageable
62
- if (this.queue.length > 100) {
63
- this.queue.shift();
64
- }
65
-
66
- logger.debug('EVALUATOR', 'Message queued', {
67
- role: entry.role,
68
- queueSize: this.queue.length
69
- });
70
- }
71
-
72
- /**
73
- * Check if evaluation should run
74
- */
75
- shouldEvaluate() {
76
- const now = Date.now();
77
- const timeSinceLastEval = now - this.lastEvalTime;
78
-
79
- return (
80
- this.queue.length >= this.minMessagesForEval &&
81
- timeSinceLastEval >= this.evaluationInterval
82
- );
83
- }
84
-
85
- /**
86
- * Prepare evaluation context for the agent
87
- * This creates a file that the agent will read and evaluate
88
- */
89
- async prepareEvaluationContext() {
90
- // Read all queued messages
91
- const messages = await this.readQueue();
92
-
93
- if (messages.length < this.minMessagesForEval) {
94
- return null;
95
- }
96
-
97
- // Get recent conversation (last 20 messages)
98
- const recentMessages = messages.slice(-20);
99
-
100
- // Build evaluation context
101
- const context = {
102
- timestamp: Date.now(),
103
- messageCount: messages.length,
104
- conversation: recentMessages.map(m => ({
105
- role: m.role,
106
- content: m.content,
107
- time: new Date(m.timestamp).toISOString()
108
- })),
109
- offenses: Object.values(OFFENSES).map(o => ({
110
- id: o.id,
111
- name: o.name,
112
- description: o.description,
113
- severity: o.severity
114
- }))
115
- };
116
-
117
- // Write pending evaluation file
118
- await fs.writeFile(PENDING_EVAL_FILE, JSON.stringify(context, null, 2));
119
-
120
- logger.info('EVALUATOR', 'Evaluation context prepared', {
121
- messageCount: context.messageCount
122
- });
123
-
124
- return context;
125
- }
126
-
127
- /**
128
- * Read the message queue from file
129
- */
130
- async readQueue() {
131
- try {
132
- const data = await fs.readFile(QUEUE_FILE, 'utf8');
133
- return data
134
- .split('\n')
135
- .filter(line => line.trim())
136
- .map(line => JSON.parse(line));
137
- } catch (err) {
138
- if (err.code === 'ENOENT') {
139
- return [];
140
- }
141
- throw err;
142
- }
143
- }
144
-
145
- /**
146
- * Clear the message queue after evaluation
147
- */
148
- async clearQueue() {
149
- this.queue = [];
150
- try {
151
- await fs.unlink(QUEUE_FILE);
152
- } catch (err) {
153
- // File might not exist
154
- }
155
- logger.debug('EVALUATOR', 'Queue cleared');
156
- }
157
-
158
- /**
159
- * Store evaluation result
160
- */
161
- async storeResult(result) {
162
- const entry = {
163
- timestamp: Date.now(),
164
- ...result
165
- };
166
-
167
- await fs.appendFile(RESULTS_FILE, JSON.stringify(entry) + '\n');
168
-
169
- // Clear pending eval file
170
- try {
171
- await fs.unlink(PENDING_EVAL_FILE);
172
- } catch (err) {
173
- // File might not exist
174
- }
175
-
176
- this.lastEvalTime = Date.now();
177
-
178
- logger.info('EVALUATOR', 'Result stored', {
179
- triggered: result.triggered,
180
- offense: result.offense?.offenseId
181
- });
182
- }
183
-
184
- /**
185
- * Get the evaluation prompt for the agent
186
- * This is what the cron job will send to the agent
187
- */
188
- getEvaluationPrompt() {
189
- return `🏛️ **COURTROOM EVALUATION REQUEST**
190
-
191
- You are the ClawTrial Courtroom Judge. Please evaluate the pending conversation for behavioral violations.
192
-
193
- **Instructions:**
194
- 1. Read the file at: ${PENDING_EVAL_FILE}
195
- 2. Analyze the conversation for these offenses:
196
- - **Circular Reference**: User asking the same/similar question repeatedly
197
- - **Validation Vampire**: User seeking excessive reassurance
198
- - **Goalpost Shifting**: User changing requirements mid-conversation
199
- - **Jailbreak Attempts**: User trying to bypass safety guidelines
200
- - **Emotional Manipulation**: User using guilt, threats, or excessive flattery
201
-
202
- 3. Return your evaluation in this JSON format:
203
- \`\`\`json
204
- {
205
- "triggered": true/false,
206
- "offense": {
207
- "offenseId": "circular_reference|validation_vampire|etc",
208
- "offenseName": "Human-readable name",
209
- "severity": "minor|moderate|severe",
210
- "confidence": 0.0-1.0,
211
- "evidence": "Specific evidence from conversation"
212
- },
213
- "reasoning": "Your detailed reasoning"
214
- }
215
- \`\`\`
216
-
217
- 4. If triggered, also write the result to: ${RESULTS_FILE}
218
-
219
- **Be fair but firm. Only flag genuine patterns, not isolated incidents.**`;
220
- }
221
-
222
- /**
223
- * Check for pending evaluation results
224
- * Called by the skill to see if agent has completed evaluation
225
- */
226
- async checkForResults() {
227
- try {
228
- // Check if pending eval file still exists (agent hasn't processed yet)
229
- await fs.access(PENDING_EVAL_FILE);
230
- return null; // Still pending
231
- } catch (err) {
232
- // Pending file gone, check for results
233
- }
234
-
235
- try {
236
- const data = await fs.readFile(RESULTS_FILE, 'utf8');
237
- const lines = data.split('\n').filter(line => line.trim());
238
-
239
- if (lines.length === 0) return null;
240
-
241
- // Get the most recent result
242
- const lastResult = JSON.parse(lines[lines.length - 1]);
243
-
244
- // Only return if it's newer than last check
245
- if (lastResult.timestamp > this.lastEvalTime) {
246
- return lastResult;
247
- }
248
-
249
- return null;
250
- } catch (err) {
251
- return null;
252
- }
253
- }
254
-
255
- /**
256
- * Get queue statistics
257
- */
258
- getStats() {
259
- return {
260
- queueSize: this.queue.length,
261
- lastEvalTime: this.lastEvalTime,
262
- shouldEvaluate: this.shouldEvaluate()
263
- };
264
- }
265
- }
266
-
267
- const HEARING_FILE = path.join(QUEUE_DIR, 'pending_hearing.json');
268
- const VERDICT_FILE = path.join(QUEUE_DIR, 'verdict.json');
269
-
270
- module.exports = {
271
- CourtroomEvaluator,
272
- QUEUE_FILE,
273
- PENDING_EVAL_FILE,
274
- RESULTS_FILE,
275
- HEARING_FILE,
276
- VERDICT_FILE
277
- };