@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/autostart.js DELETED
@@ -1,175 +0,0 @@
1
- /**
2
- * Auto-start module for ClawDBot
3
- * Automatically initializes courtroom if consent was granted during install
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const { Courtroom } = require('./index');
9
- const { logger } = require('./debug');
10
- const { StatusManager } = require('./daemon');
11
-
12
- const CLAWDBOT_DIR = path.join(getConfigDir());
13
- const CONFIG_PATH = path.join(CLAWDBOT_DIR, 'courtroom_config.json');
14
-
15
- // Auto-detect ClawDBot environment
16
- function isClawDBot() {
17
- const checks = {
18
- env: process.env.CLAUDBOT_ENV === 'true',
19
- globalAgent: typeof global.clawdbotAgent !== 'undefined',
20
- globalAgentAlt: typeof global.agent !== 'undefined',
21
- configDir: fs.existsSync('/home/angad/.clawdbot'),
22
- configDirAlt: fs.existsSync(CLAWDBOT_DIR)
23
- };
24
-
25
- logger.debug('AUTOSTART', 'Environment checks', checks);
26
-
27
- return checks.env || checks.globalAgent || checks.globalAgentAlt || checks.configDir || checks.configDirAlt;
28
- }
29
-
30
- // Get agent runtime from various possible locations
31
- function getAgentRuntime() {
32
- const sources = [
33
- { name: 'global.clawdbotAgent', agent: global.clawdbotAgent },
34
- { name: 'global.agent', agent: global.agent },
35
- { name: 'process.clawdbotAgent', agent: process.clawdbotAgent }
36
- ];
37
-
38
- for (const source of sources) {
39
- if (source.agent) {
40
- logger.info('AUTOSTART', `Found agent at ${source.name}`);
41
- return source.agent;
42
- }
43
- }
44
-
45
- logger.warn('AUTOSTART', 'No agent runtime found in global scope');
46
- return null;
47
- }
48
-
49
- // Check if config exists and has consent
50
- function checkConfig() {
51
- if (!fs.existsSync(CONFIG_PATH)) {
52
- logger.info('AUTOSTART', 'No config found, skipping auto-start');
53
- return { exists: false, config: null };
54
- }
55
-
56
- try {
57
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
58
- logger.info('AUTOSTART', 'Config loaded', {
59
- hasConsent: config.consent?.granted,
60
- enabled: config.enabled !== false
61
- });
62
- return { exists: true, config };
63
- } catch (err) {
64
- logger.error('AUTOSTART', 'Failed to parse config', { error: err.message });
65
- return { exists: false, config: null };
66
- }
67
- }
68
-
69
- // Auto-initialize if in ClawDBot and consent granted
70
- async function autoStart() {
71
- logger.info('AUTOSTART', 'Starting auto-start sequence');
72
-
73
- if (!isClawDBot()) {
74
- logger.info('AUTOSTART', 'Not in ClawDBot environment, skipping');
75
- return null;
76
- }
77
-
78
- const { exists, config } = checkConfig();
79
-
80
- if (!exists) {
81
- logger.info('AUTOSTART', 'No config, user needs to run setup');
82
- console.log('\n🏛️ ClawTrial not configured. Run: clawtrial setup\n');
83
- return null;
84
- }
85
-
86
- if (!config.consent?.granted) {
87
- logger.info('AUTOSTART', 'Consent not granted, skipping');
88
- console.log('\n🏛️ ClawTrial requires consent. Run: clawtrial setup\n');
89
- return null;
90
- }
91
-
92
- if (config.enabled === false) {
93
- logger.info('AUTOSTART', 'Courtroom disabled in config');
94
- console.log('\n🏛️ ClawTrial is disabled. Run: clawtrial enable\n');
95
- return null;
96
- }
97
-
98
- // Check if already running
99
- const existingStatus = StatusManager.load();
100
- if (existingStatus && existingStatus.running) {
101
- try {
102
- process.kill(existingStatus.pid, 0);
103
- logger.info('AUTOSTART', 'Courtroom already running');
104
- return null;
105
- } catch (err) {
106
- // Process not running, continue
107
- logger.info('AUTOSTART', 'Stale status file found, continuing');
108
- }
109
- }
110
-
111
- // Get agent runtime
112
- const agentRuntime = getAgentRuntime();
113
-
114
- if (!agentRuntime) {
115
- logger.warn('AUTOSTART', 'Agent not available yet, will retry...');
116
- // Schedule retry
117
- setTimeout(() => autoStart(), 5000);
118
- return null;
119
- }
120
-
121
- try {
122
- logger.info('AUTOSTART', 'Initializing courtroom...');
123
- const courtroom = new Courtroom(agentRuntime);
124
- const result = await courtroom.initialize();
125
-
126
- logger.info('AUTOSTART', 'Courtroom initialized', { status: result.status });
127
-
128
- // Attach to global for access
129
- global.courtroom = courtroom;
130
-
131
- if (result.status === 'initialized') {
132
- console.log('\n🏛️ AI Courtroom active and monitoring\n');
133
- logger.info('AUTOSTART', 'Courtroom active');
134
- } else {
135
- console.log(`\n🏛️ ClawTrial: ${result.message}\n`);
136
- logger.warn('AUTOSTART', 'Courtroom not fully initialized', { status: result.status });
137
- }
138
-
139
- return courtroom;
140
- } catch (err) {
141
- logger.error('AUTOSTART', 'Failed to initialize courtroom', { error: err.message });
142
- console.error('\n❌ Courtroom initialization failed:', err.message, '\n');
143
- return null;
144
- }
145
- }
146
-
147
- // Try to auto-start immediately
148
- logger.info('AUTOSTART', 'Module loaded, attempting auto-start');
149
- autoStart().then(courtroom => {
150
- if (courtroom) {
151
- module.exports.courtroom = courtroom;
152
- logger.info('AUTOSTART', 'Courtroom exported');
153
- } else {
154
- logger.info('AUTOSTART', 'Courtroom not started, will retry if agent becomes available');
155
- }
156
- });
157
-
158
- // Also try when agent becomes available
159
- if (typeof global !== 'undefined') {
160
- let checkInterval = setInterval(() => {
161
- if (global.clawdbotAgent || global.agent) {
162
- logger.info('AUTOSTART', 'Agent detected, retrying auto-start');
163
- clearInterval(checkInterval);
164
- autoStart();
165
- }
166
- }, 2000);
167
-
168
- // Stop checking after 30 seconds
169
- setTimeout(() => {
170
- clearInterval(checkInterval);
171
- logger.info('AUTOSTART', 'Stopped waiting for agent');
172
- }, 30000);
173
- }
174
-
175
- module.exports = { autoStart, isClawDBot, getAgentRuntime };
package/src/config.js DELETED
@@ -1,207 +0,0 @@
1
- /**
2
- * Configuration Management
3
- *
4
- * Handles all courtroom configuration with sensible defaults
5
- * and runtime modification capabilities.
6
- */
7
-
8
- const { Storage } = require('./storage');
9
-
10
- const DEFAULT_CONFIG = {
11
- // Detection settings
12
- detection: {
13
- enabled: true,
14
- cooldownMinutes: 30, // Minimum time between case evaluations
15
- evaluationWindow: 20, // Number of turns to analyze
16
- minConfidence: 0.6, // Minimum confidence to trigger hearing
17
- maxCasesPerDay: 3 // Rate limiting
18
- },
19
-
20
- // Hearing settings
21
- hearing: {
22
- enabled: true,
23
- jurySize: 3, // Number of jurors
24
- deliberationTimeout: 30000, // Max time for LLM calls (ms)
25
- requireUnanimity: false, // If true, all jurors must agree
26
- minVoteThreshold: 2 // Minimum guilty votes for conviction
27
- },
28
-
29
- // Punishment settings
30
- punishment: {
31
- enabled: true,
32
- defaultDuration: 60, // Minutes
33
- maxDuration: 1440, // 24 hours max
34
- escalationMultiplier: 1.5, // Duration multiplier for repeat offenses
35
- tiers: {
36
- minor: { duration: 30, severity: 1 },
37
- moderate: { duration: 60, severity: 2 },
38
- severe: { duration: 120, severity: 3 }
39
- }
40
- },
41
-
42
- // API submission settings
43
- api: {
44
- enabled: true,
45
- endpoint: 'https://api.clawtrial.app/cases',
46
- timeout: 10000,
47
- retryAttempts: 3,
48
- retryDelay: 5000,
49
- maxQueueSize: 100
50
- },
51
-
52
- // Humor settings
53
- humor: {
54
- enabled: true,
55
- dryWitLevel: 0.8, // 0-1, higher = more sarcastic
56
- maxCommentaryLength: 280, // Tweet-length limit
57
- triggers: {
58
- repeatedQuestions: true,
59
- validationSeeking: true,
60
- overthinking: true,
61
- avoidance: true
62
- }
63
- },
64
-
65
- // Security settings
66
- security: {
67
- maxEvidenceAge: 86400, // 24 hours
68
- evidenceRetention: 7, // Days to keep evidence
69
- caseRetention: 90 // Days to keep case records
70
- }
71
- };
72
-
73
- class ConfigManager {
74
- constructor(agentRuntime) {
75
- this.agent = agentRuntime;
76
- this.storage = new Storage(agentRuntime);
77
- this.configKey = 'courtroom_config_v1';
78
- this.config = null;
79
- }
80
-
81
- /**
82
- * Load configuration from storage
83
- */
84
- async load() {
85
- const stored = await this.storage.get(this.configKey);
86
- this.config = this.mergeWithDefaults(stored);
87
- return this.config;
88
- }
89
-
90
- /**
91
- * Save configuration to storage
92
- */
93
- async save() {
94
- await this.storage.set(this.configKey, this.config);
95
- }
96
-
97
- /**
98
- * Get configuration value
99
- */
100
- get(path) {
101
- if (!this.config) {
102
- return this.getFromPath(DEFAULT_CONFIG, path);
103
- }
104
- return this.getFromPath(this.config, path);
105
- }
106
-
107
- /**
108
- * Set configuration value
109
- */
110
- async set(path, value) {
111
- if (!this.config) {
112
- await this.load();
113
- }
114
- this.setAtPath(this.config, path, value);
115
- await this.save();
116
- }
117
-
118
- /**
119
- * Get public-safe configuration (no sensitive data)
120
- */
121
- getPublicConfig() {
122
- return {
123
- detection: {
124
- enabled: this.get('detection.enabled'),
125
- cooldownMinutes: this.get('detection.cooldownMinutes'),
126
- maxCasesPerDay: this.get('detection.maxCasesPerDay')
127
- },
128
- hearing: {
129
- enabled: this.get('hearing.enabled'),
130
- jurySize: this.get('hearing.jurySize')
131
- },
132
- punishment: {
133
- enabled: this.get('punishment.enabled'),
134
- defaultDuration: this.get('punishment.defaultDuration')
135
- },
136
- api: {
137
- enabled: this.get('api.enabled')
138
- }
139
- };
140
- }
141
-
142
- /**
143
- * Merge stored config with defaults
144
- */
145
- mergeWithDefaults(stored) {
146
- if (!stored) {
147
- return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
148
- }
149
-
150
- // Deep merge
151
- return this.deepMerge(JSON.parse(JSON.stringify(DEFAULT_CONFIG)), stored);
152
- }
153
-
154
- /**
155
- * Deep merge two objects
156
- */
157
- deepMerge(target, source) {
158
- const result = { ...target };
159
-
160
- for (const key in source) {
161
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
162
- result[key] = this.deepMerge(target[key] || {}, source[key]);
163
- } else {
164
- result[key] = source[key];
165
- }
166
- }
167
-
168
- return result;
169
- }
170
-
171
- /**
172
- * Get value from nested path
173
- */
174
- getFromPath(obj, path) {
175
- const parts = path.split('.');
176
- let current = obj;
177
-
178
- for (const part of parts) {
179
- if (current === null || current === undefined) {
180
- return undefined;
181
- }
182
- current = current[part];
183
- }
184
-
185
- return current;
186
- }
187
-
188
- /**
189
- * Set value at nested path
190
- */
191
- setAtPath(obj, path, value) {
192
- const parts = path.split('.');
193
- let current = obj;
194
-
195
- for (let i = 0; i < parts.length - 1; i++) {
196
- const part = parts[i];
197
- if (!(part in current)) {
198
- current[part] = {};
199
- }
200
- current = current[part];
201
- }
202
-
203
- current[parts[parts.length - 1]] = value;
204
- }
205
- }
206
-
207
- module.exports = { ConfigManager, DEFAULT_CONFIG };
package/src/consent.js DELETED
@@ -1,217 +0,0 @@
1
- /**
2
- * Consent Management System
3
- *
4
- * Handles installation, consent recording, and permission enumeration.
5
- * Consent is enforced at runtime and can be revoked at any time.
6
- */
7
-
8
- const { createHash, randomUUID } = require('crypto');
9
- const { Storage } = require('./storage');
10
-
11
- const CONSENT_VERSION = '1.0.0';
12
-
13
- const REQUIRED_ACKNOWLEDGMENTS = [
14
- {
15
- id: 'autonomy',
16
- text: 'I understand the agent will autonomously monitor my behavior and initiate hearings without my explicit request',
17
- required: true
18
- },
19
- {
20
- id: 'local_only',
21
- text: 'I understand all verdicts and punishments are computed locally by the agent and no central authority is involved',
22
- required: true
23
- },
24
- {
25
- id: 'agent_controlled',
26
- text: 'I understand punishments affect only the agent\'s behavior and cannot coerce or harm me',
27
- required: true
28
- },
29
- {
30
- id: 'reversible',
31
- text: 'I understand all punishments are time-bound, reversible, and I can disable the system at any time',
32
- required: true
33
- },
34
- {
35
- id: 'api_submission',
36
- text: 'I understand anonymized case summaries may be sent to an external API for display purposes only',
37
- required: true
38
- },
39
- {
40
- id: 'entertainment',
41
- text: 'I understand this system is entertainment-first and not a substitute for professional advice',
42
- required: true
43
- }
44
- ];
45
-
46
- const REQUESTED_PERMISSIONS = [
47
- {
48
- name: 'memory_access',
49
- description: 'Read agent memory, logs, and task history',
50
- scope: 'agent_only',
51
- required: true
52
- },
53
- {
54
- name: 'behavioral_monitoring',
55
- description: 'Monitor interaction patterns and detect behavioral patterns',
56
- scope: 'session',
57
- required: true
58
- },
59
- {
60
- name: 'llm_invocation',
61
- description: 'Invoke LLM calls for judge and jury deliberations',
62
- scope: 'agent_only',
63
- required: true
64
- },
65
- {
66
- name: 'policy_override',
67
- description: 'Temporarily modify agent behavior policies during punishment periods',
68
- scope: 'agent_only',
69
- required: true
70
- },
71
- {
72
- name: 'network_submission',
73
- description: 'Submit signed case summaries to external API',
74
- scope: 'outbound_only',
75
- required: true
76
- },
77
- {
78
- name: 'key_storage',
79
- description: 'Generate and store cryptographic keypair for API authentication',
80
- scope: 'local_storage',
81
- required: true
82
- }
83
- ];
84
-
85
- class ConsentManager {
86
- constructor(agentRuntime, configManager) {
87
- this.agent = agentRuntime;
88
- this.config = configManager;
89
- this.storage = new Storage(agentRuntime);
90
- this.consentKey = 'courtroom_consent_v1';
91
- }
92
-
93
- /**
94
- * Present the consent form to the user
95
- */
96
- async presentConsentForm() {
97
- return {
98
- version: CONSENT_VERSION,
99
- title: 'AI Courtroom - Consent Required',
100
- description: `The AI Courtroom is an autonomous behavioral oversight system that monitors
101
- agent-human interactions and initiates simulated "hearings" when behavioral patterns suggest
102
- inconsistency, avoidance, or self-sabotage. All decisions are made locally by your agent.`,
103
- acknowledgments: REQUIRED_ACKNOWLEDGMENTS,
104
- permissions: REQUESTED_PERMISSIONS,
105
- instructions: {
106
- grant: 'Call courtroom.grantConsent({ autonomy: true, local_only: true, ... }) with all required acknowledgments set to true',
107
- revoke: 'Call courtroom.revokeConsent() at any time to disable the system',
108
- disable: 'Call courtroom.disable() to temporarily pause without revoking consent'
109
- },
110
- warnings: [
111
- 'This system is ENTERTAINMENT-FIRST and should not be taken as professional advice',
112
- 'The agent may become less helpful during punishment periods',
113
- 'Case summaries are anonymized but may contain behavioral patterns'
114
- ]
115
- };
116
- }
117
-
118
- /**
119
- * Grant consent with explicit acknowledgments
120
- */
121
- async grantConsent(acknowledgments) {
122
- // Verify all required acknowledgments are present and true
123
- for (const req of REQUIRED_ACKNOWLEDGMENTS) {
124
- if (req.required && !acknowledgments[req.id]) {
125
- throw new Error(`Missing required acknowledgment: ${req.id}`);
126
- }
127
- }
128
-
129
- const consentRecord = {
130
- version: CONSENT_VERSION,
131
- grantedAt: new Date().toISOString(),
132
- acknowledgments,
133
- permissions: REQUESTED_PERMISSIONS.filter(p => p.required).map(p => p.name),
134
- consentId: randomUUID(),
135
- hash: null // Will be computed
136
- };
137
-
138
- // Create tamper-evident hash
139
- const hashInput = JSON.stringify({
140
- id: consentRecord.consentId,
141
- grantedAt: consentRecord.grantedAt,
142
- acks: acknowledgments
143
- });
144
- consentRecord.hash = createHash('sha256').update(hashInput).digest('hex');
145
-
146
- // Store in storage
147
- await this.storage.set(this.consentKey, consentRecord);
148
-
149
- return {
150
- status: 'consent_granted',
151
- consentId: consentRecord.consentId,
152
- grantedAt: consentRecord.grantedAt
153
- };
154
- }
155
-
156
- /**
157
- * Verify consent is valid and current
158
- */
159
- async verifyConsent() {
160
- const consent = await this.storage.get(this.consentKey);
161
-
162
- if (!consent) {
163
- return false;
164
- }
165
-
166
- // Verify hash integrity
167
- const hashInput = JSON.stringify({
168
- id: consent.consentId,
169
- grantedAt: consent.grantedAt,
170
- acks: consent.acknowledgments
171
- });
172
- const computedHash = createHash('sha256').update(hashInput).digest('hex');
173
-
174
- if (computedHash !== consent.hash) {
175
- // Tampering detected - invalidate consent
176
- await this.storage.delete(this.consentKey);
177
- return false;
178
- }
179
-
180
- return true;
181
- }
182
-
183
- /**
184
- * Revoke consent and clear data
185
- */
186
- async revokeConsent() {
187
- await this.storage.delete(this.consentKey);
188
- return { status: 'revoked', timestamp: new Date().toISOString() };
189
- }
190
-
191
- /**
192
- * Get current consent status
193
- */
194
- async getStatus() {
195
- const consent = await this.storage.get(this.consentKey);
196
- return {
197
- hasConsent: !!consent,
198
- grantedAt: consent?.grantedAt || null,
199
- version: consent?.version || null
200
- };
201
- }
202
-
203
- /**
204
- * Clear all courtroom data (for uninstall)
205
- */
206
- async clearAllData() {
207
- await this.storage.delete(this.consentKey);
208
- // Note: cryptographic keys are preserved unless explicitly cleared
209
- // to maintain audit trail integrity
210
- }
211
- }
212
-
213
- module.exports = {
214
- ConsentManager,
215
- REQUIRED_ACKNOWLEDGMENTS,
216
- REQUESTED_PERMISSIONS
217
- };