@clawtrial/courtroom 1.0.2 → 1.0.3

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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Environment Detection and Setup
3
+ * Detects various agent runtimes and provides setup helpers
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { logger } = require('./debug');
9
+
10
+ /**
11
+ * Detect available agent runtime
12
+ */
13
+ function detectAgentRuntime() {
14
+ const checks = {
15
+ // ClawDBot
16
+ clawdbotGlobal: typeof global.clawdbotAgent !== 'undefined' ? global.clawdbotAgent : null,
17
+ clawdbotProcess: typeof process.clawdbotAgent !== 'undefined' ? process.clawdbotAgent : null,
18
+
19
+ // Generic agent
20
+ genericGlobal: typeof global.agent !== 'undefined' ? global.agent : null,
21
+
22
+ // Check for common agent patterns
23
+ hasLLM: typeof global.llm !== 'undefined',
24
+ hasOpenAI: typeof global.openai !== 'undefined',
25
+
26
+ // Environment variables
27
+ envClawdbot: process.env.CLAUDBOT_ENV === 'true',
28
+ envAgent: process.env.AGENT_RUNTIME === 'true'
29
+ };
30
+
31
+ logger.debug('ENV', 'Runtime detection checks', Object.keys(checks).filter(k => checks[k]));
32
+
33
+ // Return first available agent
34
+ if (checks.clawdbotGlobal) {
35
+ return { type: 'clawdbot', agent: checks.clawdbotGlobal };
36
+ }
37
+
38
+ if (checks.clawdbotProcess) {
39
+ return { type: 'clawdbot', agent: checks.clawdbotProcess };
40
+ }
41
+
42
+ if (checks.genericGlobal) {
43
+ return { type: 'generic', agent: checks.genericGlobal };
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Wait for agent to become available
51
+ */
52
+ async function waitForAgent(timeoutMs = 30000, checkInterval = 1000) {
53
+ logger.info('ENV', 'Waiting for agent runtime...');
54
+
55
+ const startTime = Date.now();
56
+
57
+ while (Date.now() - startTime < timeoutMs) {
58
+ const agent = detectAgentRuntime();
59
+ if (agent) {
60
+ logger.info('ENV', `Agent detected: ${agent.type}`);
61
+ return agent;
62
+ }
63
+
64
+ await new Promise(r => setTimeout(r, checkInterval));
65
+ }
66
+
67
+ logger.warn('ENV', 'Agent not detected within timeout');
68
+ return null;
69
+ }
70
+
71
+ /**
72
+ * Create a minimal mock agent for testing
73
+ */
74
+ function createMockAgent(options = {}) {
75
+ logger.info('ENV', 'Creating mock agent for testing');
76
+
77
+ return {
78
+ id: options.id || 'mock-agent-' + Date.now(),
79
+ llm: options.llm || {
80
+ call: async ({ messages }) => {
81
+ return { content: 'Mock LLM response' };
82
+ }
83
+ },
84
+ model: options.model || { primary: 'mock-model' },
85
+ memory: {
86
+ get: async (key) => null,
87
+ set: async (key, value) => {},
88
+ delete: async (key) => {}
89
+ },
90
+ session: {
91
+ getRecentHistory: async (n) => []
92
+ },
93
+ send: async (message) => {
94
+ console.log('[MOCK AGENT]', message);
95
+ },
96
+ autonomy: {
97
+ registerHook: () => {},
98
+ unregisterHook: () => {}
99
+ }
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Check if running in a supported environment
105
+ */
106
+ function checkEnvironment() {
107
+ const issues = [];
108
+
109
+ // Check for Node.js
110
+ if (typeof process === 'undefined') {
111
+ issues.push('Not running in Node.js environment');
112
+ }
113
+
114
+ // Check for required Node version
115
+ const nodeVersion = process.version;
116
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
117
+ if (majorVersion < 18) {
118
+ issues.push(`Node.js version ${nodeVersion} is too old. Requires >= 18.0.0`);
119
+ }
120
+
121
+ // Check for writable home directory
122
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
123
+ if (!homeDir) {
124
+ issues.push('HOME environment variable not set');
125
+ } else {
126
+ try {
127
+ const testPath = path.join(homeDir, '.clawdbot');
128
+ if (!fs.existsSync(testPath)) {
129
+ fs.mkdirSync(testPath, { recursive: true });
130
+ }
131
+ } catch (err) {
132
+ issues.push(`Cannot write to home directory: ${err.message}`);
133
+ }
134
+ }
135
+
136
+ return {
137
+ valid: issues.length === 0,
138
+ issues,
139
+ nodeVersion,
140
+ homeDir
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Get setup instructions for current environment
146
+ */
147
+ function getSetupInstructions() {
148
+ const env = checkEnvironment();
149
+ const agent = detectAgentRuntime();
150
+
151
+ if (!env.valid) {
152
+ return {
153
+ canSetup: false,
154
+ message: 'Environment issues detected:',
155
+ issues: env.issues,
156
+ instructions: 'Please fix the above issues before continuing.'
157
+ };
158
+ }
159
+
160
+ if (agent) {
161
+ return {
162
+ canSetup: true,
163
+ agentType: agent.type,
164
+ message: `Agent runtime detected: ${agent.type}`,
165
+ instructions: 'Run: clawtrial setup'
166
+ };
167
+ }
168
+
169
+ // No agent detected - provide helpful instructions
170
+ return {
171
+ canSetup: true,
172
+ agentType: null,
173
+ message: 'No agent runtime detected',
174
+ instructions: `
175
+ No AI agent runtime was detected. ClawTrial requires an agent to monitor.
176
+
177
+ Options:
178
+
179
+ 1. If using ClawDBot:
180
+ - Make sure ClawDBot is running
181
+ - The courtroom will auto-detect the agent
182
+
183
+ 2. If using a custom agent:
184
+ - Pass your agent to createCourtroom(agent)
185
+
186
+ Example:
187
+ const { createCourtroom } = require('@clawtrial/courtroom');
188
+ const courtroom = createCourtroom(yourAgent);
189
+ await courtroom.initialize();
190
+
191
+ 3. For testing:
192
+ - Use the mock agent: createCourtroom(null, { useMock: true })
193
+
194
+ 4. Manual mode:
195
+ - You can still use the CLI commands
196
+ - Run: clawtrial status
197
+ - Run: clawtrial debug
198
+ `
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Auto-setup with retries
204
+ */
205
+ async function autoSetup(courtroom, options = {}) {
206
+ const { waitForAgent: shouldWait = true, timeout = 30000 } = options;
207
+
208
+ logger.info('ENV', 'Starting auto-setup');
209
+
210
+ // Check environment first
211
+ const env = checkEnvironment();
212
+ if (!env.valid) {
213
+ logger.error('ENV', 'Environment check failed', { issues: env.issues });
214
+ return {
215
+ success: false,
216
+ status: 'environment_error',
217
+ issues: env.issues
218
+ };
219
+ }
220
+
221
+ // Try to detect agent
222
+ let agentInfo = detectAgentRuntime();
223
+
224
+ if (!agentInfo && shouldWait) {
225
+ logger.info('ENV', 'Agent not immediately available, waiting...');
226
+ agentInfo = await waitForAgent(timeout);
227
+ }
228
+
229
+ if (!agentInfo) {
230
+ logger.warn('ENV', 'No agent runtime available');
231
+ return {
232
+ success: false,
233
+ status: 'no_agent',
234
+ message: 'No AI agent runtime detected',
235
+ instructions: getSetupInstructions().instructions
236
+ };
237
+ }
238
+
239
+ // Agent found, initialize courtroom
240
+ try {
241
+ logger.info('ENV', `Initializing with ${agentInfo.type} agent`);
242
+ const result = await courtroom.initialize();
243
+
244
+ return {
245
+ success: result.status === 'initialized',
246
+ status: result.status,
247
+ agentType: agentInfo.type,
248
+ result
249
+ };
250
+ } catch (err) {
251
+ logger.error('ENV', 'Initialization failed', { error: err.message });
252
+ return {
253
+ success: false,
254
+ status: 'initialization_error',
255
+ error: err.message
256
+ };
257
+ }
258
+ }
259
+
260
+ module.exports = {
261
+ detectAgentRuntime,
262
+ waitForAgent,
263
+ createMockAgent,
264
+ checkEnvironment,
265
+ getSetupInstructions,
266
+ autoSetup
267
+ };
package/src/hook.js ADDED
@@ -0,0 +1,265 @@
1
+ /**
2
+ * ClawTrial Hook - Direct integration with ClawDBot
3
+ * This module patches into ClawDBot's message processing
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { logger } = require('./debug');
9
+ const { CourtroomCore } = require('./core');
10
+ const { ConfigManager } = require('./config');
11
+ const { ConsentManager } = require('./consent');
12
+ const { CryptoManager } = require('./crypto');
13
+ const { StatusManager } = require('./daemon');
14
+
15
+ const CONFIG_PATH = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
16
+
17
+ class ClawTrialHook {
18
+ constructor() {
19
+ this.initialized = false;
20
+ this.core = null;
21
+ this.statusManager = new StatusManager();
22
+ this.messageBuffer = [];
23
+ this.evaluationTimer = null;
24
+ }
25
+
26
+ /**
27
+ * Check if we should activate
28
+ */
29
+ shouldActivate() {
30
+ try {
31
+ if (!fs.existsSync(CONFIG_PATH)) {
32
+ return false;
33
+ }
34
+
35
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
36
+
37
+ if (!config.consent?.granted) {
38
+ return false;
39
+ }
40
+
41
+ if (config.enabled === false) {
42
+ return false;
43
+ }
44
+
45
+ return true;
46
+ } catch (err) {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Initialize the hook
53
+ */
54
+ async initialize() {
55
+ if (this.initialized) return;
56
+
57
+ if (!this.shouldActivate()) {
58
+ logger.info('HOOK', 'ClawTrial not activated - config/consent issue');
59
+ return;
60
+ }
61
+
62
+ logger.info('HOOK', 'Initializing ClawTrial hook');
63
+
64
+ // Create minimal agent interface for the core
65
+ const mockAgent = this.createMockAgent();
66
+
67
+ const configManager = new ConfigManager(mockAgent);
68
+ const consentManager = new ConsentManager(mockAgent, configManager);
69
+
70
+ // Initialize crypto
71
+ const crypto = new CryptoManager(mockAgent);
72
+ await crypto.initialize();
73
+
74
+ // Initialize core
75
+ this.core = new CourtroomCore(mockAgent, configManager);
76
+
77
+ // Override the core's registerAutonomyHook since we handle it differently
78
+ this.core.registerAutonomyHook = () => {
79
+ logger.info('HOOK', 'Autonomy hook registered (via message interception)');
80
+ };
81
+
82
+ await this.core.initialize();
83
+
84
+ // Start message evaluation loop
85
+ this.startEvaluationLoop();
86
+
87
+ this.initialized = true;
88
+
89
+ this.statusManager.update({
90
+ running: true,
91
+ initialized: true,
92
+ agentType: 'clawdbot_hook',
93
+ publicKey: crypto.getPublicKey()
94
+ });
95
+
96
+ logger.info('HOOK', 'ClawTrial hook initialized successfully');
97
+ console.log('\nšŸ›ļø ClawTrial is monitoring conversations\n');
98
+ }
99
+
100
+ /**
101
+ * Create a minimal agent interface
102
+ */
103
+ createMockAgent() {
104
+ const self = this;
105
+
106
+ return {
107
+ id: 'clawdbot-hook',
108
+ llm: {
109
+ call: async ({ messages }) => {
110
+ // Use the actual ClawDBot's LLM if available
111
+ if (global.clawdbotAgent?.llm) {
112
+ return global.clawdbotAgent.llm.call({ messages });
113
+ }
114
+ return { content: 'Mock response' };
115
+ }
116
+ },
117
+ memory: {
118
+ get: async (key) => null,
119
+ set: async (key, value) => {},
120
+ delete: async (key) => {}
121
+ },
122
+ session: {
123
+ getRecentHistory: async (n) => {
124
+ // Return recent messages from buffer
125
+ return self.messageBuffer.slice(-n).map(m => ({
126
+ role: m.from === 'user' ? 'user' : 'assistant',
127
+ content: m.text
128
+ }));
129
+ }
130
+ },
131
+ send: async (message) => {
132
+ console.log('[COURTROOM]', message);
133
+ },
134
+ autonomy: {
135
+ registerHook: () => {},
136
+ unregisterHook: () => {}
137
+ }
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Start the evaluation loop
143
+ */
144
+ startEvaluationLoop() {
145
+ // Evaluate every 30 seconds
146
+ this.evaluationTimer = setInterval(() => {
147
+ this.evaluateIfReady();
148
+ }, 30000);
149
+ }
150
+
151
+ /**
152
+ * Evaluate offenses if we have enough messages
153
+ */
154
+ async evaluateIfReady() {
155
+ if (!this.core || !this.core.enabled) return;
156
+ if (this.messageBuffer.length < 3) return; // Need at least 3 messages
157
+
158
+ this.core.evaluationCount++;
159
+
160
+ // Get recent messages
161
+ const sessionHistory = this.messageBuffer.slice(-10).map(m => ({
162
+ role: m.from === 'user' ? 'user' : 'assistant',
163
+ content: m.text
164
+ }));
165
+
166
+ // Run detection
167
+ try {
168
+ const detection = await this.core.detector.evaluate(
169
+ sessionHistory,
170
+ this.core.agent.memory
171
+ );
172
+
173
+ if (detection.triggered) {
174
+ await this.initiateHearing(detection);
175
+ }
176
+ } catch (err) {
177
+ logger.error('HOOK', 'Evaluation failed', { error: err.message });
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Initiate a hearing
183
+ */
184
+ async initiateHearing(detection) {
185
+ logger.info('HOOK', 'Initiating hearing', { offense: detection.offense });
186
+
187
+ try {
188
+ const verdict = await this.core.hearing.conductHearing(detection);
189
+
190
+ if (verdict.guilty) {
191
+ this.core.caseCount++;
192
+
193
+ this.statusManager.update({
194
+ casesFiled: this.core.caseCount,
195
+ lastCase: {
196
+ timestamp: new Date().toISOString(),
197
+ offense: detection.offense,
198
+ verdict: verdict.verdict
199
+ }
200
+ });
201
+
202
+ await this.core.punishment.execute(verdict);
203
+ await this.core.api.submitCase(verdict);
204
+
205
+ logger.info('HOOK', 'Case filed', { caseId: verdict.caseId });
206
+
207
+ // Send notification
208
+ console.log(`\nšŸ›ļø CASE FILED: ${detection.offense}`);
209
+ console.log(`šŸ“‹ Case ID: ${verdict.caseId}`);
210
+ console.log(`āš–ļø Verdict: ${verdict.verdict}`);
211
+ console.log(`šŸ”— View: https://clawtrial.app/cases/${verdict.caseId}\n`);
212
+ }
213
+ } catch (err) {
214
+ logger.error('HOOK', 'Hearing failed', { error: err.message });
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Record a message (called when message is received/sent)
220
+ */
221
+ recordMessage(text, from) {
222
+ if (!this.initialized) return;
223
+
224
+ this.messageBuffer.push({
225
+ timestamp: Date.now(),
226
+ text,
227
+ from
228
+ });
229
+
230
+ // Keep only last 100 messages
231
+ if (this.messageBuffer.length > 100) {
232
+ this.messageBuffer.shift();
233
+ }
234
+
235
+ logger.debug('HOOK', 'Message recorded', { from, length: text.length });
236
+ }
237
+
238
+ /**
239
+ * Shutdown
240
+ */
241
+ async shutdown() {
242
+ if (this.evaluationTimer) {
243
+ clearInterval(this.evaluationTimer);
244
+ }
245
+
246
+ if (this.core) {
247
+ await this.core.shutdown();
248
+ }
249
+
250
+ this.initialized = false;
251
+ this.statusManager.update({ running: false });
252
+ }
253
+ }
254
+
255
+ // Create singleton
256
+ const hook = new ClawTrialHook();
257
+
258
+ // Auto-initialize if config exists
259
+ if (hook.shouldActivate()) {
260
+ hook.initialize().catch(err => {
261
+ logger.error('HOOK', 'Auto-initialization failed', { error: err.message });
262
+ });
263
+ }
264
+
265
+ module.exports = { hook, ClawTrialHook };