@clawtrial/courtroom 1.0.3 → 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/consent.js DELETED
@@ -1,215 +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
-
10
- const CONSENT_VERSION = '1.0.0';
11
-
12
- const REQUIRED_ACKNOWLEDGMENTS = [
13
- {
14
- id: 'autonomy',
15
- text: 'I understand the agent will autonomously monitor my behavior and initiate hearings without my explicit request',
16
- required: true
17
- },
18
- {
19
- id: 'local_only',
20
- text: 'I understand all verdicts and punishments are computed locally by the agent and no central authority is involved',
21
- required: true
22
- },
23
- {
24
- id: 'agent_controlled',
25
- text: 'I understand punishments affect only the agent\'s behavior and cannot coerce or harm me',
26
- required: true
27
- },
28
- {
29
- id: 'reversible',
30
- text: 'I understand all punishments are time-bound, reversible, and I can disable the system at any time',
31
- required: true
32
- },
33
- {
34
- id: 'api_submission',
35
- text: 'I understand anonymized case summaries may be sent to an external API for display purposes only',
36
- required: true
37
- },
38
- {
39
- id: 'entertainment',
40
- text: 'I understand this system is entertainment-first and not a substitute for professional advice',
41
- required: true
42
- }
43
- ];
44
-
45
- const REQUESTED_PERMISSIONS = [
46
- {
47
- name: 'memory_access',
48
- description: 'Read agent memory, logs, and task history',
49
- scope: 'agent_only',
50
- required: true
51
- },
52
- {
53
- name: 'behavioral_monitoring',
54
- description: 'Monitor interaction patterns and detect behavioral patterns',
55
- scope: 'session',
56
- required: true
57
- },
58
- {
59
- name: 'llm_invocation',
60
- description: 'Invoke LLM calls for judge and jury deliberations',
61
- scope: 'agent_only',
62
- required: true
63
- },
64
- {
65
- name: 'policy_override',
66
- description: 'Temporarily modify agent behavior policies during punishment periods',
67
- scope: 'agent_only',
68
- required: true
69
- },
70
- {
71
- name: 'network_submission',
72
- description: 'Submit signed case summaries to external API',
73
- scope: 'outbound_only',
74
- required: true
75
- },
76
- {
77
- name: 'key_storage',
78
- description: 'Generate and store cryptographic keypair for API authentication',
79
- scope: 'local_storage',
80
- required: true
81
- }
82
- ];
83
-
84
- class ConsentManager {
85
- constructor(agentRuntime, configManager) {
86
- this.agent = agentRuntime;
87
- this.config = configManager;
88
- this.consentKey = 'courtroom_consent_v1';
89
- }
90
-
91
- /**
92
- * Present the consent form to the user
93
- */
94
- async presentConsentForm() {
95
- return {
96
- version: CONSENT_VERSION,
97
- title: 'AI Courtroom - Consent Required',
98
- description: `The AI Courtroom is an autonomous behavioral oversight system that monitors
99
- agent-human interactions and initiates simulated "hearings" when behavioral patterns suggest
100
- inconsistency, avoidance, or self-sabotage. All decisions are made locally by your agent.`,
101
- acknowledgments: REQUIRED_ACKNOWLEDGMENTS,
102
- permissions: REQUESTED_PERMISSIONS,
103
- instructions: {
104
- grant: 'Call courtroom.grantConsent({ autonomy: true, local_only: true, ... }) with all required acknowledgments set to true',
105
- revoke: 'Call courtroom.revokeConsent() at any time to disable the system',
106
- disable: 'Call courtroom.disable() to temporarily pause without revoking consent'
107
- },
108
- warnings: [
109
- 'This system is ENTERTAINMENT-FIRST and should not be taken as professional advice',
110
- 'The agent may become less helpful during punishment periods',
111
- 'Case summaries are anonymized but may contain behavioral patterns'
112
- ]
113
- };
114
- }
115
-
116
- /**
117
- * Grant consent with explicit acknowledgments
118
- */
119
- async grantConsent(acknowledgments) {
120
- // Verify all required acknowledgments are present and true
121
- for (const req of REQUIRED_ACKNOWLEDGMENTS) {
122
- if (req.required && !acknowledgments[req.id]) {
123
- throw new Error(`Missing required acknowledgment: ${req.id}`);
124
- }
125
- }
126
-
127
- const consentRecord = {
128
- version: CONSENT_VERSION,
129
- grantedAt: new Date().toISOString(),
130
- acknowledgments,
131
- permissions: REQUESTED_PERMISSIONS.filter(p => p.required).map(p => p.name),
132
- consentId: randomUUID(),
133
- hash: null // Will be computed
134
- };
135
-
136
- // Create tamper-evident hash
137
- const hashInput = JSON.stringify({
138
- id: consentRecord.consentId,
139
- grantedAt: consentRecord.grantedAt,
140
- acks: acknowledgments
141
- });
142
- consentRecord.hash = createHash('sha256').update(hashInput).digest('hex');
143
-
144
- // Store in agent memory
145
- await this.agent.memory.set(this.consentKey, consentRecord);
146
-
147
- return {
148
- status: 'consent_granted',
149
- consentId: consentRecord.consentId,
150
- grantedAt: consentRecord.grantedAt
151
- };
152
- }
153
-
154
- /**
155
- * Verify consent is valid and current
156
- */
157
- async verifyConsent() {
158
- const consent = await this.agent.memory.get(this.consentKey);
159
-
160
- if (!consent) {
161
- return false;
162
- }
163
-
164
- // Verify hash integrity
165
- const hashInput = JSON.stringify({
166
- id: consent.consentId,
167
- grantedAt: consent.grantedAt,
168
- acks: consent.acknowledgments
169
- });
170
- const computedHash = createHash('sha256').update(hashInput).digest('hex');
171
-
172
- if (computedHash !== consent.hash) {
173
- // Tampering detected - invalidate consent
174
- await this.agent.memory.delete(this.consentKey);
175
- return false;
176
- }
177
-
178
- return true;
179
- }
180
-
181
- /**
182
- * Revoke consent and clear all data
183
- */
184
- async revokeConsent() {
185
- await this.agent.memory.delete(this.consentKey);
186
- return { status: 'revoked', timestamp: new Date().toISOString() };
187
- }
188
-
189
- /**
190
- * Get current consent status
191
- */
192
- async getStatus() {
193
- const consent = await this.agent.memory.get(this.consentKey);
194
- return {
195
- hasConsent: !!consent,
196
- grantedAt: consent?.grantedAt || null,
197
- version: consent?.version || null
198
- };
199
- }
200
-
201
- /**
202
- * Clear all courtroom data (for uninstall)
203
- */
204
- async clearAllData() {
205
- await this.agent.memory.delete(this.consentKey);
206
- // Note: cryptographic keys are preserved unless explicitly cleared
207
- // to maintain audit trail integrity
208
- }
209
- }
210
-
211
- module.exports = {
212
- ConsentManager,
213
- REQUIRED_ACKNOWLEDGMENTS,
214
- REQUESTED_PERMISSIONS
215
- };
package/src/core.js DELETED
@@ -1,208 +0,0 @@
1
- /**
2
- * Courtroom Core
3
- *
4
- * Main orchestration module that ties together all components.
5
- * Hooks into the OpenClaw autonomy loop.
6
- */
7
-
8
- const { OffenseDetector } = require('./detector');
9
- const { HearingPipeline } = require('./hearing');
10
- const { PunishmentSystem } = require('./punishment');
11
- const { CryptoManager } = require('./crypto');
12
- const { APISubmission } = require('./api');
13
- const { StatusManager } = require('./daemon');
14
- const { logger } = require('./debug');
15
-
16
- class CourtroomCore {
17
- constructor(agentRuntime, configManager) {
18
- this.agent = agentRuntime;
19
- this.config = configManager;
20
-
21
- // Subsystems
22
- this.detector = new OffenseDetector(agentRuntime, configManager);
23
- this.hearing = new HearingPipeline(agentRuntime, configManager);
24
- this.punishment = new PunishmentSystem(agentRuntime, configManager);
25
- this.crypto = new CryptoManager(agentRuntime);
26
- this.api = new APISubmission(agentRuntime, configManager, this.crypto);
27
-
28
- // State
29
- this.enabled = false;
30
- this.evaluationCount = 0;
31
- this.caseCount = 0;
32
- this.statusManager = new StatusManager();
33
- }
34
-
35
- /**
36
- * Initialize all subsystems
37
- */
38
- async initialize() {
39
- logger.info('CORE', 'Initializing courtroom core');
40
-
41
- // Initialize crypto first (needed for API)
42
- await this.crypto.initialize();
43
-
44
- // Initialize other subsystems
45
- await this.punishment.initialize();
46
- await this.api.initialize();
47
-
48
- // Register with agent autonomy loop
49
- this.registerAutonomyHook();
50
-
51
- this.enabled = true;
52
-
53
- // Update status for CLI
54
- this.statusManager.update({
55
- running: true,
56
- initialized: true,
57
- agentType: 'clawdbot',
58
- publicKey: this.crypto.getPublicKey()
59
- });
60
-
61
- logger.info('CORE', 'Courtroom core initialized');
62
-
63
- return {
64
- status: 'initialized',
65
- publicKey: this.crypto.getPublicKey(),
66
- subsystems: {
67
- detector: true,
68
- hearing: true,
69
- punishment: true,
70
- crypto: true,
71
- api: true
72
- }
73
- };
74
- }
75
-
76
- /**
77
- * Register with OpenClaw autonomy loop
78
- */
79
- registerAutonomyHook() {
80
- logger.info('CORE', 'Registering autonomy hook');
81
-
82
- // Hook into agent's turn processing
83
- if (this.agent.autonomy && this.agent.autonomy.registerHook) {
84
- this.agent.autonomy.registerHook('courtroom_evaluation', {
85
- priority: 50,
86
- onTurnComplete: async (turnData) => {
87
- if (!this.enabled) return;
88
-
89
- // Only evaluate on cooldown
90
- await this.evaluateIfReady(turnData);
91
- }
92
- });
93
- } else {
94
- logger.warn('CORE', 'Agent does not support autonomy hooks');
95
- }
96
- }
97
-
98
- /**
99
- * Evaluate offenses if cooldown has elapsed
100
- */
101
- async evaluateIfReady(turnData) {
102
- this.evaluationCount++;
103
-
104
- // Get session history
105
- let sessionHistory = [];
106
- try {
107
- sessionHistory = await this.agent.session.getRecentHistory(
108
- this.config.get('detection.evaluationWindow') || 10
109
- );
110
- } catch (err) {
111
- logger.warn('CORE', 'Could not get session history', { error: err.message });
112
- return;
113
- }
114
-
115
- // Run detection
116
- const detection = await this.detector.evaluate(
117
- sessionHistory,
118
- this.agent.memory
119
- );
120
-
121
- if (detection.triggered) {
122
- await this.initiateHearing(detection);
123
- }
124
- }
125
-
126
- /**
127
- * Initiate a full hearing
128
- */
129
- async initiateHearing(detection) {
130
- logger.info('CORE', 'Initiating hearing', { offense: detection.offense });
131
-
132
- // Run hearing pipeline
133
- const verdict = await this.hearing.conductHearing(detection);
134
-
135
- if (verdict.guilty) {
136
- this.caseCount++;
137
-
138
- // Update status
139
- this.statusManager.update({
140
- casesFiled: this.caseCount,
141
- lastCase: {
142
- timestamp: new Date().toISOString(),
143
- offense: detection.offense,
144
- verdict: verdict.verdict
145
- }
146
- });
147
-
148
- // Execute punishment
149
- await this.punishment.execute(verdict);
150
-
151
- // Submit to API
152
- await this.api.submitCase(verdict);
153
-
154
- logger.info('CORE', 'Case filed', {
155
- caseId: verdict.caseId,
156
- offense: detection.offense
157
- });
158
- }
159
- }
160
-
161
- /**
162
- * Disable courtroom
163
- */
164
- async disable() {
165
- logger.info('CORE', 'Disabling courtroom');
166
- this.enabled = false;
167
- this.statusManager.update({ running: false });
168
- }
169
-
170
- /**
171
- * Enable courtroom
172
- */
173
- async enable() {
174
- logger.info('CORE', 'Enabling courtroom');
175
- this.enabled = true;
176
- this.statusManager.update({ running: true });
177
- }
178
-
179
- /**
180
- * Shutdown courtroom
181
- */
182
- async shutdown() {
183
- logger.info('CORE', 'Shutting down courtroom');
184
- this.enabled = false;
185
- this.statusManager.update({ running: false, initialized: false });
186
- StatusManager.clear();
187
- }
188
-
189
- /**
190
- * Get current status
191
- */
192
- getStatus() {
193
- return {
194
- enabled: this.enabled,
195
- evaluationCount: this.evaluationCount,
196
- caseCount: this.caseCount,
197
- subsystems: {
198
- detector: !!this.detector,
199
- hearing: !!this.hearing,
200
- punishment: !!this.punishment,
201
- crypto: !!this.crypto,
202
- api: !!this.api
203
- }
204
- };
205
- }
206
- }
207
-
208
- module.exports = { CourtroomCore };
package/src/daemon.js DELETED
@@ -1,151 +0,0 @@
1
- /**
2
- * Courtroom Daemon - Runs independently and monitors via file system
3
- * This allows CLI commands to work even when loaded in a separate process
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const { logger } = require('./debug');
9
-
10
- const CLAWDBOT_DIR = path.join(process.env.HOME || '', '.clawdbot');
11
- const STATUS_FILE = path.join(CLAWDBOT_DIR, 'courtroom_status.json');
12
- const CONVERSATION_LOG = path.join(CLAWDBOT_DIR, 'courtroom_conversations.jsonl');
13
-
14
- /**
15
- * Status manager - allows CLI to check courtroom state
16
- */
17
- class StatusManager {
18
- constructor() {
19
- this.status = {
20
- running: false,
21
- initialized: false,
22
- agentType: null,
23
- lastCheck: null,
24
- casesFiled: 0,
25
- lastCase: null,
26
- pid: process.pid,
27
- startedAt: new Date().toISOString()
28
- };
29
- }
30
-
31
- update(updates) {
32
- this.status = { ...this.status, ...updates, lastCheck: new Date().toISOString() };
33
- this.save();
34
- }
35
-
36
- save() {
37
- try {
38
- fs.writeFileSync(STATUS_FILE, JSON.stringify(this.status, null, 2));
39
- } catch (err) {
40
- logger.error('DAEMON', 'Failed to save status', { error: err.message });
41
- }
42
- }
43
-
44
- static load() {
45
- try {
46
- if (fs.existsSync(STATUS_FILE)) {
47
- return JSON.parse(fs.readFileSync(STATUS_FILE, 'utf8'));
48
- }
49
- } catch (err) {
50
- // Ignore
51
- }
52
- return null;
53
- }
54
-
55
- static clear() {
56
- try {
57
- if (fs.existsSync(STATUS_FILE)) {
58
- fs.unlinkSync(STATUS_FILE);
59
- }
60
- } catch (err) {
61
- // Ignore
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Conversation logger - writes conversations for daemon to process
68
- */
69
- class ConversationLogger {
70
- constructor() {
71
- this.buffer = [];
72
- this.flushInterval = null;
73
- }
74
-
75
- start() {
76
- // Flush every 5 seconds
77
- this.flushInterval = setInterval(() => this.flush(), 5000);
78
- }
79
-
80
- stop() {
81
- if (this.flushInterval) {
82
- clearInterval(this.flushInterval);
83
- this.flush();
84
- }
85
- }
86
-
87
- log(message) {
88
- this.buffer.push({
89
- timestamp: new Date().toISOString(),
90
- ...message
91
- });
92
- }
93
-
94
- flush() {
95
- if (this.buffer.length === 0) return;
96
-
97
- try {
98
- const lines = this.buffer.map(m => JSON.stringify(m)).join('\n') + '\n';
99
- fs.appendFileSync(CONVERSATION_LOG, lines);
100
- this.buffer = [];
101
- } catch (err) {
102
- logger.error('DAEMON', 'Failed to flush conversations', { error: err.message });
103
- }
104
- }
105
- }
106
-
107
- /**
108
- * Check if courtroom is running (called by CLI)
109
- */
110
- function isCourtroomRunning() {
111
- const status = StatusManager.load();
112
- if (!status) return false;
113
-
114
- // Check if process is still alive
115
- try {
116
- process.kill(status.pid, 0);
117
- return status.running;
118
- } catch (err) {
119
- // Process not running
120
- StatusManager.clear();
121
- return false;
122
- }
123
- }
124
-
125
- /**
126
- * Get courtroom status (called by CLI)
127
- */
128
- function getCourtroomStatus() {
129
- const status = StatusManager.load();
130
- if (!status) {
131
- return { running: false, message: 'Courtroom not running' };
132
- }
133
-
134
- // Verify process is alive
135
- try {
136
- process.kill(status.pid, 0);
137
- return status;
138
- } catch (err) {
139
- StatusManager.clear();
140
- return { running: false, message: 'Courtroom process not found' };
141
- }
142
- }
143
-
144
- module.exports = {
145
- StatusManager,
146
- ConversationLogger,
147
- isCourtroomRunning,
148
- getCourtroomStatus,
149
- STATUS_FILE,
150
- CONVERSATION_LOG
151
- };