@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.
- package/README.md +45 -129
- package/SECURITY.md +1 -1
- package/SKILL.md +50 -0
- package/TECHNICAL_OVERVIEW.md +2 -2
- package/_meta.json +6 -0
- package/clawdbot.plugin.json +32 -0
- package/package.json +10 -5
- package/scripts/clawtrial.js +212 -47
- package/scripts/cli.js +1 -1
- package/scripts/postinstall.js +68 -172
- package/skill.yaml +64 -0
- package/src/autostart.js +23 -8
- package/src/core.js +84 -108
- package/src/daemon.js +151 -0
- package/src/environment.js +267 -0
- package/src/hook.js +265 -0
- package/src/index.js +160 -58
- package/src/monitor.js +193 -0
- package/src/skill.js +355 -0
- package/src/standalone.js +247 -0
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @clawtrial/courtroom - AI Courtroom for OpenClaw
|
|
3
3
|
*
|
|
4
4
|
* Autonomous behavioral oversight system that monitors agent-human interactions
|
|
5
5
|
* and initiates hearings when behavioral rules are violated.
|
|
@@ -9,6 +9,9 @@ const { CourtroomCore } = require('./core');
|
|
|
9
9
|
const { ConsentManager } = require('./consent');
|
|
10
10
|
const { ConfigManager } = require('./config');
|
|
11
11
|
const { version } = require('../package.json');
|
|
12
|
+
const { detectAgentRuntime, createMockAgent, checkEnvironment, getSetupInstructions } = require('./environment');
|
|
13
|
+
const { logger } = require('./debug');
|
|
14
|
+
const { skill } = require('./skill');
|
|
12
15
|
const fs = require('fs');
|
|
13
16
|
const path = require('path');
|
|
14
17
|
|
|
@@ -16,86 +19,120 @@ class Courtroom {
|
|
|
16
19
|
constructor(agentRuntime, options = {}) {
|
|
17
20
|
this.agent = agentRuntime;
|
|
18
21
|
this.options = options;
|
|
19
|
-
this.config = new ConfigManager(agentRuntime);
|
|
20
|
-
this.consent = new ConsentManager(agentRuntime, this.config);
|
|
22
|
+
this.config = agentRuntime ? new ConfigManager(agentRuntime) : null;
|
|
23
|
+
this.consent = agentRuntime ? new ConsentManager(agentRuntime, this.config) : null;
|
|
21
24
|
this.core = null;
|
|
22
25
|
this.enabled = false;
|
|
23
26
|
this.version = version;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
|
-
* Quick start - auto-initialize if
|
|
30
|
+
* Quick start - auto-detect agent and initialize if possible
|
|
28
31
|
*/
|
|
29
|
-
static async quickStart(
|
|
30
|
-
|
|
32
|
+
static async quickStart(options = {}) {
|
|
33
|
+
logger.info('COURTROOM', 'Starting quickStart');
|
|
31
34
|
|
|
32
|
-
// Check
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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);
|
|
43
54
|
}
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
};
|
|
47
76
|
}
|
|
48
77
|
|
|
49
78
|
/**
|
|
50
79
|
* Initialize the courtroom system
|
|
51
|
-
* Must be called after construction
|
|
52
80
|
*/
|
|
53
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
|
+
|
|
54
92
|
// Check if this is first run (no config exists)
|
|
55
93
|
const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
56
94
|
if (!fs.existsSync(configPath)) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await postInstall();
|
|
63
|
-
|
|
64
|
-
// After setup, check consent again
|
|
65
|
-
const hasConsent = await this.consent.verifyConsent();
|
|
66
|
-
if (!hasConsent) {
|
|
67
|
-
return {
|
|
68
|
-
status: 'setup_complete_consent_required',
|
|
69
|
-
message: 'Setup complete! Run courtroom.grantConsent() to enable'
|
|
70
|
-
};
|
|
71
|
-
}
|
|
95
|
+
logger.info('COURTROOM', 'First run detected');
|
|
96
|
+
return {
|
|
97
|
+
status: 'setup_required',
|
|
98
|
+
message: 'First time setup required. Run: clawtrial setup'
|
|
99
|
+
};
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
// Check if consent has been granted
|
|
75
103
|
const hasConsent = await this.consent.verifyConsent();
|
|
76
104
|
if (!hasConsent) {
|
|
105
|
+
logger.warn('COURTROOM', 'Consent not granted');
|
|
77
106
|
return {
|
|
78
107
|
status: 'consent_required',
|
|
79
|
-
message: '
|
|
108
|
+
message: 'Consent required. Run: clawtrial setup'
|
|
80
109
|
};
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
// Initialize core systems
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
132
|
}
|
|
95
133
|
|
|
96
134
|
/**
|
|
97
135
|
* Request consent from the user
|
|
98
|
-
* Returns a consent form that must be explicitly accepted
|
|
99
136
|
*/
|
|
100
137
|
async requestConsent() {
|
|
101
138
|
return this.consent.presentConsentForm();
|
|
@@ -152,8 +189,10 @@ class Courtroom {
|
|
|
152
189
|
return {
|
|
153
190
|
enabled: this.enabled,
|
|
154
191
|
version: this.version,
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
157
196
|
};
|
|
158
197
|
}
|
|
159
198
|
|
|
@@ -164,21 +203,84 @@ class Courtroom {
|
|
|
164
203
|
if (this.core) {
|
|
165
204
|
await this.core.shutdown();
|
|
166
205
|
}
|
|
167
|
-
|
|
206
|
+
if (this.consent) {
|
|
207
|
+
await this.consent.clearAllData();
|
|
208
|
+
}
|
|
209
|
+
this.enabled = false;
|
|
168
210
|
return { status: 'uninstalled' };
|
|
169
211
|
}
|
|
170
212
|
}
|
|
171
213
|
|
|
172
|
-
// Factory function for
|
|
173
|
-
function createCourtroom(agentRuntime, options) {
|
|
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
|
+
|
|
174
226
|
return new Courtroom(agentRuntime, options);
|
|
175
227
|
}
|
|
176
228
|
|
|
177
|
-
//
|
|
178
|
-
|
|
229
|
+
// Export environment utilities
|
|
230
|
+
const environment = {
|
|
231
|
+
detectAgentRuntime,
|
|
232
|
+
createMockAgent,
|
|
233
|
+
checkEnvironment,
|
|
234
|
+
getSetupInstructions
|
|
235
|
+
};
|
|
179
236
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
// Store runtime reference
|
|
249
|
+
const runtime = api.runtime;
|
|
250
|
+
|
|
251
|
+
// Only initialize skill if runtime has the required memory interface
|
|
252
|
+
// The skill system will auto-initialize via shouldActivate if this fails
|
|
253
|
+
if (skill && typeof skill.initialize === 'function' && runtime && runtime.memory) {
|
|
254
|
+
skill.initialize(runtime).catch(err => {
|
|
255
|
+
logger.error('PLUGIN', 'Skill initialization failed', { error: err.message });
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
logger.info('PLUGIN', 'Skill will auto-initialize via ClawDBot skill system');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Register any commands or hooks
|
|
262
|
+
logger.info('PLUGIN', 'Courtroom plugin registered successfully');
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// Optional: activation function
|
|
266
|
+
activate(api) {
|
|
267
|
+
logger.info('PLUGIN', 'Activating courtroom plugin');
|
|
268
|
+
// Additional activation logic if needed
|
|
269
|
+
}
|
|
184
270
|
};
|
|
271
|
+
|
|
272
|
+
// Export both the plugin (default) and the Courtroom class (named exports)
|
|
273
|
+
module.exports = plugin;
|
|
274
|
+
module.exports.Courtroom = Courtroom;
|
|
275
|
+
module.exports.createCourtroom = createCourtroom;
|
|
276
|
+
module.exports.quickStart = Courtroom.quickStart;
|
|
277
|
+
module.exports.environment = environment;
|
|
278
|
+
module.exports.skill = skill;
|
|
279
|
+
|
|
280
|
+
// Auto-initialize skill if loaded by ClawDBot (legacy support)
|
|
281
|
+
if (typeof global !== 'undefined' && global.clawdbotAgent) {
|
|
282
|
+
logger.info('INDEX', 'Detected ClawDBot environment, auto-initializing skill');
|
|
283
|
+
skill.initialize(global.clawdbotAgent).catch(err => {
|
|
284
|
+
logger.error('INDEX', 'Auto-initialization failed', { error: err.message });
|
|
285
|
+
});
|
|
286
|
+
}
|
package/src/monitor.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ClawTrial Background Monitor
|
|
5
|
+
* Runs continuously and initializes courtroom when agent becomes available
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { logger } = require('./debug');
|
|
11
|
+
const { StatusManager } = require('./daemon');
|
|
12
|
+
|
|
13
|
+
const CONFIG_PATH = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
14
|
+
const CHECK_INTERVAL = 5000; // Check every 5 seconds
|
|
15
|
+
const MAX_RETRIES = 100; // Give up after ~8 minutes
|
|
16
|
+
|
|
17
|
+
let retryCount = 0;
|
|
18
|
+
let courtroom = null;
|
|
19
|
+
let statusManager = new StatusManager();
|
|
20
|
+
|
|
21
|
+
logger.info('MONITOR', 'Background monitor started', { pid: process.pid });
|
|
22
|
+
|
|
23
|
+
// Save PID
|
|
24
|
+
statusManager.update({
|
|
25
|
+
running: true,
|
|
26
|
+
initialized: false,
|
|
27
|
+
pid: process.pid,
|
|
28
|
+
startedAt: new Date().toISOString()
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if config exists and is enabled
|
|
33
|
+
*/
|
|
34
|
+
function checkConfig() {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
37
|
+
return { valid: false, reason: 'no_config' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
41
|
+
|
|
42
|
+
if (!config.consent?.granted) {
|
|
43
|
+
return { valid: false, reason: 'no_consent' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config.enabled === false) {
|
|
47
|
+
return { valid: false, reason: 'disabled' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { valid: true, config };
|
|
51
|
+
} catch (err) {
|
|
52
|
+
logger.error('MONITOR', 'Config check failed', { error: err.message });
|
|
53
|
+
return { valid: false, reason: 'error' };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect agent runtime
|
|
59
|
+
*/
|
|
60
|
+
function detectAgent() {
|
|
61
|
+
// Check various global locations
|
|
62
|
+
if (typeof global.clawdbotAgent !== 'undefined' && global.clawdbotAgent) {
|
|
63
|
+
return { type: 'clawdbot', agent: global.clawdbotAgent };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof global.agent !== 'undefined' && global.agent) {
|
|
67
|
+
return { type: 'generic', agent: global.agent };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof process.clawdbotAgent !== 'undefined' && process.clawdbotAgent) {
|
|
71
|
+
return { type: 'clawdbot', agent: process.clawdbotAgent };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for ClawDBot specific indicators
|
|
75
|
+
if (process.env.CLAUDBOT_ENV === 'true') {
|
|
76
|
+
// Try to find agent in parent process or global scope
|
|
77
|
+
if (globalThis.clawdbotAgent) {
|
|
78
|
+
return { type: 'clawdbot', agent: globalThis.clawdbotAgent };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Try to initialize courtroom
|
|
87
|
+
*/
|
|
88
|
+
async function tryInitialize() {
|
|
89
|
+
const configCheck = checkConfig();
|
|
90
|
+
|
|
91
|
+
if (!configCheck.valid) {
|
|
92
|
+
logger.warn('MONITOR', 'Config not valid', { reason: configCheck.reason });
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const agentInfo = detectAgent();
|
|
97
|
+
|
|
98
|
+
if (!agentInfo) {
|
|
99
|
+
logger.debug('MONITOR', 'Agent not detected yet');
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.info('MONITOR', `Agent detected: ${agentInfo.type}`);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Import and initialize courtroom
|
|
107
|
+
const { createCourtroom } = require('./index');
|
|
108
|
+
courtroom = createCourtroom(agentInfo.agent);
|
|
109
|
+
|
|
110
|
+
const result = await courtroom.initialize();
|
|
111
|
+
|
|
112
|
+
if (result.status === 'initialized') {
|
|
113
|
+
logger.info('MONITOR', 'Courtroom initialized successfully');
|
|
114
|
+
|
|
115
|
+
statusManager.update({
|
|
116
|
+
running: true,
|
|
117
|
+
initialized: true,
|
|
118
|
+
agentType: agentInfo.type,
|
|
119
|
+
publicKey: result.publicKey
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Attach to global for access
|
|
123
|
+
global.courtroom = courtroom;
|
|
124
|
+
|
|
125
|
+
console.log('\nšļø AI Courtroom is now active and monitoring!\n');
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
} else {
|
|
129
|
+
logger.warn('MONITOR', 'Courtroom not initialized', { status: result.status });
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
logger.error('MONITOR', 'Initialization failed', { error: err.message });
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Main monitoring loop
|
|
140
|
+
*/
|
|
141
|
+
async function monitor() {
|
|
142
|
+
logger.info('MONITOR', 'Starting monitoring loop');
|
|
143
|
+
|
|
144
|
+
// Try immediately first
|
|
145
|
+
if (await tryInitialize()) {
|
|
146
|
+
return; // Success! Keep process alive
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Set up interval to keep trying
|
|
150
|
+
const interval = setInterval(async () => {
|
|
151
|
+
retryCount++;
|
|
152
|
+
|
|
153
|
+
if (retryCount > MAX_RETRIES) {
|
|
154
|
+
logger.warn('MONITOR', 'Max retries reached, giving up');
|
|
155
|
+
clearInterval(interval);
|
|
156
|
+
statusManager.update({ running: false, error: 'max_retries' });
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (await tryInitialize()) {
|
|
161
|
+
clearInterval(interval);
|
|
162
|
+
// Keep process alive - courtroom is running
|
|
163
|
+
}
|
|
164
|
+
}, CHECK_INTERVAL);
|
|
165
|
+
|
|
166
|
+
// Keep process alive
|
|
167
|
+
setInterval(() => {
|
|
168
|
+
// Heartbeat to keep process alive
|
|
169
|
+
if (courtroom && courtroom.enabled) {
|
|
170
|
+
statusManager.update({ lastHeartbeat: new Date().toISOString() });
|
|
171
|
+
}
|
|
172
|
+
}, 30000);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Handle shutdown
|
|
176
|
+
process.on('SIGTERM', () => {
|
|
177
|
+
logger.info('MONITOR', 'Received SIGTERM, shutting down');
|
|
178
|
+
statusManager.update({ running: false });
|
|
179
|
+
process.exit(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
process.on('SIGINT', () => {
|
|
183
|
+
logger.info('MONITOR', 'Received SIGINT, shutting down');
|
|
184
|
+
statusManager.update({ running: false });
|
|
185
|
+
process.exit(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Start monitoring
|
|
189
|
+
monitor().catch(err => {
|
|
190
|
+
logger.error('MONITOR', 'Monitor crashed', { error: err.message });
|
|
191
|
+
statusManager.update({ running: false, error: err.message });
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|