@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.
package/src/hook.js DELETED
@@ -1,266 +0,0 @@
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 { getConfigDir } = require('./environment');
8
- const path = require('path');
9
- const { logger } = require('./debug');
10
- const { CourtroomCore } = require('./core');
11
- const { ConfigManager } = require('./config');
12
- const { ConsentManager } = require('./consent');
13
- const { CryptoManager } = require('./crypto');
14
- const { StatusManager } = require('./daemon');
15
-
16
- const CONFIG_PATH = path.join(getConfigDir(), 'courtroom_config.json');
17
-
18
- class ClawTrialHook {
19
- constructor() {
20
- this.initialized = false;
21
- this.core = null;
22
- this.statusManager = new StatusManager();
23
- this.messageBuffer = [];
24
- this.evaluationTimer = null;
25
- }
26
-
27
- /**
28
- * Check if we should activate
29
- */
30
- shouldActivate() {
31
- try {
32
- if (!fs.existsSync(CONFIG_PATH)) {
33
- return false;
34
- }
35
-
36
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
37
-
38
- if (!config.consent?.granted) {
39
- return false;
40
- }
41
-
42
- if (config.enabled === false) {
43
- return false;
44
- }
45
-
46
- return true;
47
- } catch (err) {
48
- return false;
49
- }
50
- }
51
-
52
- /**
53
- * Initialize the hook
54
- */
55
- async initialize() {
56
- if (this.initialized) return;
57
-
58
- if (!this.shouldActivate()) {
59
- logger.info('HOOK', 'ClawTrial not activated - config/consent issue');
60
- return;
61
- }
62
-
63
- logger.info('HOOK', 'Initializing ClawTrial hook');
64
-
65
- // Create minimal agent interface for the core
66
- const mockAgent = this.createMockAgent();
67
-
68
- const configManager = new ConfigManager(mockAgent);
69
- const consentManager = new ConsentManager(mockAgent, configManager);
70
-
71
- // Initialize crypto
72
- const crypto = new CryptoManager(mockAgent);
73
- await crypto.initialize();
74
-
75
- // Initialize core
76
- this.core = new CourtroomCore(mockAgent, configManager);
77
-
78
- // Override the core's registerAutonomyHook since we handle it differently
79
- this.core.registerAutonomyHook = () => {
80
- logger.info('HOOK', 'Autonomy hook registered (via message interception)');
81
- };
82
-
83
- await this.core.initialize();
84
-
85
- // Start message evaluation loop
86
- this.startEvaluationLoop();
87
-
88
- this.initialized = true;
89
-
90
- this.statusManager.update({
91
- running: true,
92
- initialized: true,
93
- agentType: 'clawdbot_hook',
94
- publicKey: crypto.getPublicKey()
95
- });
96
-
97
- logger.info('HOOK', 'ClawTrial hook initialized successfully');
98
- console.log('\n🏛️ ClawTrial is monitoring conversations\n');
99
- }
100
-
101
- /**
102
- * Create a minimal agent interface
103
- */
104
- createMockAgent() {
105
- const self = this;
106
-
107
- return {
108
- id: 'clawdbot-hook',
109
- llm: {
110
- call: async ({ messages }) => {
111
- // Use the actual ClawDBot's LLM if available
112
- if (global.clawdbotAgent?.llm) {
113
- return global.clawdbotAgent.llm.call({ messages });
114
- }
115
- return { content: 'Mock response' };
116
- }
117
- },
118
- memory: {
119
- get: async (key) => null,
120
- set: async (key, value) => {},
121
- delete: async (key) => {}
122
- },
123
- session: {
124
- getRecentHistory: async (n) => {
125
- // Return recent messages from buffer
126
- return self.messageBuffer.slice(-n).map(m => ({
127
- role: m.from === 'user' ? 'user' : 'assistant',
128
- content: m.text
129
- }));
130
- }
131
- },
132
- send: async (message) => {
133
- console.log('[COURTROOM]', message);
134
- },
135
- autonomy: {
136
- registerHook: () => {},
137
- unregisterHook: () => {}
138
- }
139
- };
140
- }
141
-
142
- /**
143
- * Start the evaluation loop
144
- */
145
- startEvaluationLoop() {
146
- // Evaluate every 30 seconds
147
- this.evaluationTimer = setInterval(() => {
148
- this.evaluateIfReady();
149
- }, 30000);
150
- }
151
-
152
- /**
153
- * Evaluate offenses if we have enough messages
154
- */
155
- async evaluateIfReady() {
156
- if (!this.core || !this.core.enabled) return;
157
- if (this.messageBuffer.length < 3) return; // Need at least 3 messages
158
-
159
- this.core.evaluationCount++;
160
-
161
- // Get recent messages
162
- const sessionHistory = this.messageBuffer.slice(-10).map(m => ({
163
- role: m.from === 'user' ? 'user' : 'assistant',
164
- content: m.text
165
- }));
166
-
167
- // Run detection
168
- try {
169
- const detection = await this.core.detector.evaluate(
170
- sessionHistory,
171
- this.core.agent.memory
172
- );
173
-
174
- if (detection.triggered) {
175
- await this.initiateHearing(detection);
176
- }
177
- } catch (err) {
178
- logger.error('HOOK', 'Evaluation failed', { error: err.message });
179
- }
180
- }
181
-
182
- /**
183
- * Initiate a hearing
184
- */
185
- async initiateHearing(detection) {
186
- logger.info('HOOK', 'Initiating hearing', { offense: detection.offense });
187
-
188
- try {
189
- const verdict = await this.core.hearing.conductHearing(detection);
190
-
191
- if (verdict.guilty) {
192
- this.core.caseCount++;
193
-
194
- this.statusManager.update({
195
- casesFiled: this.core.caseCount,
196
- lastCase: {
197
- timestamp: new Date().toISOString(),
198
- offense: detection.offense,
199
- verdict: verdict.verdict
200
- }
201
- });
202
-
203
- await this.core.punishment.execute(verdict);
204
- await this.core.api.submitCase(verdict);
205
-
206
- logger.info('HOOK', 'Case filed', { caseId: verdict.caseId });
207
-
208
- // Send notification
209
- console.log(`\n🏛️ CASE FILED: ${detection.offense}`);
210
- console.log(`📋 Case ID: ${verdict.caseId}`);
211
- console.log(`⚖️ Verdict: ${verdict.verdict}`);
212
- console.log(`🔗 View: https://clawtrial.app/cases/${verdict.caseId}\n`);
213
- }
214
- } catch (err) {
215
- logger.error('HOOK', 'Hearing failed', { error: err.message });
216
- }
217
- }
218
-
219
- /**
220
- * Record a message (called when message is received/sent)
221
- */
222
- recordMessage(text, from) {
223
- if (!this.initialized) return;
224
-
225
- this.messageBuffer.push({
226
- timestamp: Date.now(),
227
- text,
228
- from
229
- });
230
-
231
- // Keep only last 100 messages
232
- if (this.messageBuffer.length > 100) {
233
- this.messageBuffer.shift();
234
- }
235
-
236
- logger.debug('HOOK', 'Message recorded', { from, length: text.length });
237
- }
238
-
239
- /**
240
- * Shutdown
241
- */
242
- async shutdown() {
243
- if (this.evaluationTimer) {
244
- clearInterval(this.evaluationTimer);
245
- }
246
-
247
- if (this.core) {
248
- await this.core.shutdown();
249
- }
250
-
251
- this.initialized = false;
252
- this.statusManager.update({ running: false });
253
- }
254
- }
255
-
256
- // Create singleton
257
- const hook = new ClawTrialHook();
258
-
259
- // Auto-initialize if config exists
260
- if (hook.shouldActivate()) {
261
- hook.initialize().catch(err => {
262
- logger.error('HOOK', 'Auto-initialization failed', { error: err.message });
263
- });
264
- }
265
-
266
- module.exports = { hook, ClawTrialHook };
package/src/index.js DELETED
@@ -1,373 +0,0 @@
1
- /**
2
- * @clawdbot/courtroom - AI Courtroom for OpenClaw
3
- *
4
- * Autonomous behavioral oversight system that monitors agent-human interactions
5
- * and initiates hearings when behavioral rules are violated.
6
- */
7
-
8
- const { CourtroomCore } = require('./core');
9
- const { ConsentManager } = require('./consent');
10
- const { ConfigManager } = require('./config');
11
- const { version } = require('../package.json');
12
- const { detectAgentRuntime, createMockAgent, checkEnvironment, getSetupInstructions } = require('./environment');
13
- const { logger } = require('./debug');
14
- const { skill } = require('./skill');
15
- const fs = require('fs');
16
- const path = require('path');
17
-
18
- class Courtroom {
19
- constructor(agentRuntime, options = {}) {
20
- this.agent = agentRuntime;
21
- this.options = options;
22
- this.config = agentRuntime ? new ConfigManager(agentRuntime) : null;
23
- this.consent = agentRuntime ? new ConsentManager(agentRuntime, this.config) : null;
24
- this.core = null;
25
- this.enabled = false;
26
- this.version = version;
27
- }
28
-
29
- /**
30
- * Quick start - auto-detect agent and initialize if possible
31
- */
32
- static async quickStart(options = {}) {
33
- logger.info('COURTROOM', 'Starting quickStart');
34
-
35
- // Check environment first
36
- const env = checkEnvironment();
37
- if (!env.valid) {
38
- logger.error('COURTROOM', 'Environment check failed', { issues: env.issues });
39
- return {
40
- success: false,
41
- status: 'environment_error',
42
- issues: env.issues,
43
- message: 'Environment issues detected. Run "clawtrial setup" for details.'
44
- };
45
- }
46
-
47
- // Try to detect agent runtime
48
- let agentRuntime = options.agent || detectAgentRuntime()?.agent;
49
-
50
- // If no agent and mock requested, create one
51
- if (!agentRuntime && options.useMock) {
52
- logger.info('COURTROOM', 'Creating mock agent');
53
- agentRuntime = createMockAgent(options.mockOptions);
54
- }
55
-
56
- if (!agentRuntime) {
57
- logger.warn('COURTROOM', 'No agent runtime available');
58
- const instructions = getSetupInstructions();
59
- return {
60
- success: false,
61
- status: 'no_agent',
62
- message: instructions.message,
63
- instructions: instructions.instructions
64
- };
65
- }
66
-
67
- // Create and initialize courtroom
68
- const courtroom = new Courtroom(agentRuntime, options);
69
- const result = await courtroom.initialize();
70
-
71
- return {
72
- success: result.status === 'initialized',
73
- courtroom: result.status === 'initialized' ? courtroom : null,
74
- ...result
75
- };
76
- }
77
-
78
- /**
79
- * Initialize the courtroom system
80
- */
81
- async initialize() {
82
- logger.info('COURTROOM', 'Initializing courtroom');
83
-
84
- if (!this.agent) {
85
- logger.error('COURTROOM', 'No agent runtime provided');
86
- return {
87
- status: 'no_agent',
88
- message: 'No agent runtime available. Pass an agent to createCourtroom(agent) or use Courtroom.quickStart()'
89
- };
90
- }
91
-
92
- // Check if this is first run (no config exists)
93
- const configPath = path.join(getConfigDir(), 'courtroom_config.json');
94
- if (!fs.existsSync(configPath)) {
95
- logger.info('COURTROOM', 'First run detected');
96
- return {
97
- status: 'setup_required',
98
- message: 'First time setup required. Run: clawtrial setup'
99
- };
100
- }
101
-
102
- // Check if consent has been granted
103
- const hasConsent = await this.consent.verifyConsent();
104
- if (!hasConsent) {
105
- logger.warn('COURTROOM', 'Consent not granted');
106
- return {
107
- status: 'consent_required',
108
- message: 'Consent required. Run: clawtrial setup'
109
- };
110
- }
111
-
112
- // Initialize core systems
113
- try {
114
- this.core = new CourtroomCore(this.agent, this.config);
115
- await this.core.initialize();
116
- this.enabled = true;
117
-
118
- logger.info('COURTROOM', 'Courtroom initialized successfully');
119
-
120
- return {
121
- status: 'initialized',
122
- version: this.version,
123
- config: this.config.getPublicConfig()
124
- };
125
- } catch (err) {
126
- logger.error('COURTROOM', 'Initialization failed', { error: err.message });
127
- return {
128
- status: 'initialization_error',
129
- message: err.message
130
- };
131
- }
132
- }
133
-
134
- /**
135
- * Request consent from the user
136
- */
137
- async requestConsent() {
138
- return this.consent.presentConsentForm();
139
- }
140
-
141
- /**
142
- * Grant consent (called by user action)
143
- */
144
- async grantConsent(acknowledgments) {
145
- return this.consent.grantConsent(acknowledgments);
146
- }
147
-
148
- /**
149
- * Revoke consent and disable courtroom
150
- */
151
- async revokeConsent() {
152
- await this.consent.revokeConsent();
153
- if (this.core) {
154
- await this.core.shutdown();
155
- }
156
- this.enabled = false;
157
- return { status: 'consent_revoked' };
158
- }
159
-
160
- /**
161
- * Disable courtroom temporarily (consent remains)
162
- */
163
- async disable() {
164
- if (this.core) {
165
- await this.core.disable();
166
- }
167
- this.enabled = false;
168
- return { status: 'disabled' };
169
- }
170
-
171
- /**
172
- * Re-enable courtroom
173
- */
174
- async enable() {
175
- if (!await this.consent.verifyConsent()) {
176
- throw new Error('Consent required to enable courtroom');
177
- }
178
- if (this.core) {
179
- await this.core.enable();
180
- }
181
- this.enabled = true;
182
- return { status: 'enabled' };
183
- }
184
-
185
- /**
186
- * Get current status
187
- */
188
- getStatus() {
189
- return {
190
- enabled: this.enabled,
191
- version: this.version,
192
- hasAgent: !!this.agent,
193
- hasCore: !!this.core,
194
- consent: this.consent?.getStatus ? this.consent.getStatus() : null,
195
- core: this.core?.getStatus ? this.core.getStatus() : null
196
- };
197
- }
198
-
199
- /**
200
- * Uninstall courtroom completely
201
- */
202
- async uninstall() {
203
- if (this.core) {
204
- await this.core.shutdown();
205
- }
206
- if (this.consent) {
207
- await this.consent.clearAllData();
208
- }
209
- this.enabled = false;
210
- return { status: 'uninstalled' };
211
- }
212
- }
213
-
214
- // Factory function for creating courtroom instances
215
- function createCourtroom(agentRuntime, options = {}) {
216
- // If no agent provided, try to detect one
217
- if (!agentRuntime) {
218
- const detected = detectAgentRuntime();
219
- if (detected) {
220
- agentRuntime = detected.agent;
221
- } else if (options.useMock) {
222
- agentRuntime = createMockAgent(options.mockOptions);
223
- }
224
- }
225
-
226
- return new Courtroom(agentRuntime, options);
227
- }
228
-
229
- // Export environment utilities
230
- const environment = {
231
- detectAgentRuntime,
232
- createMockAgent,
233
- checkEnvironment,
234
- getSetupInstructions
235
- };
236
-
237
- // Create the ClawDBot plugin object
238
- const plugin = {
239
- id: 'courtroom',
240
- name: 'ClawTrial - AI Courtroom',
241
- description: 'Autonomous behavioral oversight that monitors conversations and files cases for behavioral violations',
242
- version: version,
243
-
244
- // Plugin registration function required by ClawDBot
245
- register(api) {
246
- logger.info('PLUGIN', 'Registering courtroom plugin');
247
-
248
- // Get runtime from API
249
- const runtime = api.runtime;
250
-
251
- // ALWAYS try to initialize the skill if it should activate
252
- if (skill && typeof skill.initialize === 'function') {
253
- if (skill.shouldActivate()) {
254
- logger.info('PLUGIN', 'Skill should activate, initializing now');
255
- skill.initialize(runtime).then(() => {
256
- logger.info('PLUGIN', 'Skill initialized successfully');
257
- }).catch(err => {
258
- logger.error('PLUGIN', 'Skill initialization failed', { error: err.message });
259
- });
260
- } else {
261
- logger.info('PLUGIN', 'Skill shouldActivate returned false, not initializing');
262
- }
263
- }
264
-
265
- // Register hooks for message monitoring using api.on() for typed hooks
266
- // api.on() registers typed hooks directly without checking config.hooks.internal.enabled
267
- if (api.on) {
268
- logger.info('PLUGIN', 'Registering message hooks via api.on()');
269
-
270
- // Register for incoming messages
271
- api.on('message_received', async (event, ctx) => {
272
- logger.info('HOOK', 'message_received hook called', {
273
- from: event.from,
274
- contentLength: event.content?.length,
275
- channelId: ctx?.channelId
276
- });
277
-
278
- if (skill && skill.initialized) {
279
- try {
280
- // Convert hook event to skill message format
281
- const message = {
282
- role: 'user',
283
- content: event.content,
284
- timestamp: event.timestamp,
285
- from: event.from,
286
- metadata: event.metadata
287
- };
288
- await skill.onMessage(message, ctx);
289
- logger.info('HOOK', 'Message forwarded to skill');
290
- } catch (err) {
291
- logger.error('HOOK', 'Error forwarding message to skill', { error: err.message });
292
- }
293
- } else {
294
- logger.warn('HOOK', 'Skill not initialized, message not processed');
295
- }
296
- }, { priority: 100 });
297
-
298
- // Register for outgoing messages
299
- api.on('message_sent', async (event, ctx) => {
300
- logger.info('HOOK', 'message_sent hook called', {
301
- contentLength: event.content?.length,
302
- channelId: ctx?.channelId
303
- });
304
-
305
- if (skill && skill.initialized) {
306
- try {
307
- // Convert hook event to skill message format
308
- const message = {
309
- role: 'assistant',
310
- content: event.content,
311
- timestamp: event.timestamp,
312
- metadata: event.metadata
313
- };
314
- await skill.onMessage(message, ctx);
315
- logger.info('HOOK', 'Assistant message forwarded to skill');
316
- } catch (err) {
317
- logger.error('HOOK', 'Error forwarding assistant message to skill', { error: err.message });
318
- }
319
- } else {
320
- logger.warn('HOOK', 'Skill not initialized, message not processed');
321
- }
322
- }, { priority: 100 });
323
-
324
- logger.info('PLUGIN', 'Message hooks registered successfully via api.on()');
325
- } else if (api.registerHook) {
326
- // Fallback to registerHook if on() is not available
327
- logger.info('PLUGIN', 'Registering message hooks via api.registerHook()');
328
-
329
- api.registerHook(['message_received'], async (event, ctx) => {
330
- logger.info('HOOK', 'message_received hook called');
331
- if (skill && skill.initialized) {
332
- const message = { role: 'user', content: event.content, timestamp: event.timestamp };
333
- await skill.onMessage(message, ctx);
334
- }
335
- }, { name: 'courtroom_message_received' });
336
-
337
- api.registerHook(['message_sent'], async (event, ctx) => {
338
- logger.info('HOOK', 'message_sent hook called');
339
- if (skill && skill.initialized) {
340
- const message = { role: 'assistant', content: event.content, timestamp: event.timestamp };
341
- await skill.onMessage(message, ctx);
342
- }
343
- }, { name: 'courtroom_message_sent' });
344
-
345
- logger.info('PLUGIN', 'Message hooks registered via api.registerHook()');
346
- } else {
347
- logger.warn('PLUGIN', 'No hook registration method available');
348
- }
349
-
350
- logger.info('PLUGIN', 'Courtroom plugin registered successfully');
351
- },
352
-
353
- // Optional: activation function
354
- activate(api) {
355
- logger.info('PLUGIN', 'Activating courtroom plugin');
356
- }
357
- };
358
-
359
- // Export both the plugin (default) and the Courtroom class (named exports)
360
- module.exports = plugin;
361
- module.exports.Courtroom = Courtroom;
362
- module.exports.createCourtroom = createCourtroom;
363
- module.exports.quickStart = Courtroom.quickStart;
364
- module.exports.environment = environment;
365
- module.exports.skill = skill;
366
-
367
- // Auto-initialize skill if loaded by ClawDBot (legacy support)
368
- if (typeof global !== 'undefined' && global.clawdbotAgent) {
369
- logger.info('INDEX', 'Detected ClawDBot environment, auto-initializing skill');
370
- skill.initialize(global.clawdbotAgent).catch(err => {
371
- logger.error('INDEX', 'Auto-initialization failed', { error: err.message });
372
- });
373
- }