@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/README.md +64 -41
- package/package.json +20 -25
- package/scripts/postinstall.js +27 -99
- package/skills/courtroom/SKILL.md +49 -0
- package/src/api.js +12 -11
- package/src/crypto.js +5 -5
- package/src/debug.js +49 -121
- package/src/detector.js +40 -38
- package/src/hearing.js +246 -75
- package/src/plugin.js +435 -0
- package/src/punishment.js +13 -13
- package/src/storage.js +35 -119
- package/AGENT_CONFIG.md +0 -66
- package/OPENCLAW_FIX.md +0 -127
- package/OPENCLAW_INSTALL.md +0 -63
- package/SECURITY.md +0 -124
- package/SKILL.md +0 -91
- package/SUBAGENT_APPROACH.md +0 -124
- package/TECHNICAL_OVERVIEW.md +0 -278
- package/_meta.json +0 -14
- package/clawdbot.plugin.json +0 -32
- package/icon.txt +0 -1
- package/scripts/check-and-trigger.js +0 -139
- package/scripts/clawtrial.js +0 -968
- package/scripts/clawtrial.js.bak +0 -531
- package/scripts/cli.js +0 -184
- package/scripts/optimized-cron-check.js +0 -137
- package/scripts/setup-cron.js +0 -118
- package/scripts/trigger-evaluation.js +0 -86
- package/skill.yaml +0 -28
- package/src/autostart.js +0 -175
- package/src/config.js +0 -207
- package/src/consent.js +0 -217
- package/src/core.js +0 -208
- package/src/daemon.js +0 -152
- package/src/detector-v1.js +0 -572
- package/src/environment.js +0 -344
- package/src/evaluator.js +0 -277
- package/src/hook.js +0 -266
- package/src/index.js +0 -373
- package/src/monitor.js +0 -194
- package/src/skill.js +0 -372
- package/src/standalone.js +0 -248
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
|
-
};
|