@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/plugin.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawTrial Courtroom — OpenClaw Plugin
|
|
3
|
+
*
|
|
4
|
+
* Registers as an OpenClaw plugin using the official plugin API.
|
|
5
|
+
*
|
|
6
|
+
* Hooks:
|
|
7
|
+
* before_prompt_build — intercepts messages, runs offense detection,
|
|
8
|
+
* injects punishment context into system prompt
|
|
9
|
+
* Services:
|
|
10
|
+
* courtroom-monitor — flushes API submission queue periodically
|
|
11
|
+
*
|
|
12
|
+
* CLI:
|
|
13
|
+
* openclaw courtroom status — show courtroom state
|
|
14
|
+
* openclaw courtroom enable — enable monitoring
|
|
15
|
+
* openclaw courtroom disable — disable monitoring
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const { SemanticOffenseDetector } = require('./detector');
|
|
21
|
+
const { HearingPipeline } = require('./hearing');
|
|
22
|
+
const { PunishmentSystem } = require('./punishment');
|
|
23
|
+
const { CryptoManager } = require('./crypto');
|
|
24
|
+
const { APISubmission } = require('./api');
|
|
25
|
+
const { logger, setLogDir } = require('./debug');
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Default configuration (merged under plugins.entries.courtroom.config)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const DEFAULT_CONFIG = {
|
|
31
|
+
enabled: true,
|
|
32
|
+
detection: {
|
|
33
|
+
minMessages: 5,
|
|
34
|
+
cooldownMinutes: 30,
|
|
35
|
+
maxCasesPerDay: 3,
|
|
36
|
+
confidenceThreshold: 0.6
|
|
37
|
+
},
|
|
38
|
+
hearing: {
|
|
39
|
+
minVoteThreshold: 2
|
|
40
|
+
},
|
|
41
|
+
punishment: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
tiers: {
|
|
44
|
+
minor: { duration: 30 },
|
|
45
|
+
moderate: { duration: 60 },
|
|
46
|
+
severe: { duration: 120 }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
api: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
endpoint: 'https://clawtrial.app/api/v1/cases',
|
|
52
|
+
retryAttempts: 3,
|
|
53
|
+
maxQueueSize: 50
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Lightweight config adapter — bridges plugin config to subsystem .get()
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
class PluginConfig {
|
|
61
|
+
constructor(raw) {
|
|
62
|
+
this._cfg = this._deepMerge(DEFAULT_CONFIG, raw || {});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get(dotPath) {
|
|
66
|
+
return dotPath.split('.').reduce((o, k) => (o == null ? undefined : o[k]), this._cfg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set(dotPath, value) {
|
|
70
|
+
const keys = dotPath.split('.');
|
|
71
|
+
let o = this._cfg;
|
|
72
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
73
|
+
if (o[keys[i]] == null) o[keys[i]] = {};
|
|
74
|
+
o = o[keys[i]];
|
|
75
|
+
}
|
|
76
|
+
o[keys[keys.length - 1]] = value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_deepMerge(target, source) {
|
|
80
|
+
const out = { ...target };
|
|
81
|
+
for (const key of Object.keys(source)) {
|
|
82
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
83
|
+
out[key] = this._deepMerge(out[key] || {}, source[key]);
|
|
84
|
+
} else {
|
|
85
|
+
out[key] = source[key];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Courtroom runtime — stateful singleton for the plugin session
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
class CourtroomRuntime {
|
|
96
|
+
constructor(dataDir, pluginConfig) {
|
|
97
|
+
this.dataDir = dataDir;
|
|
98
|
+
this.config = new PluginConfig(pluginConfig);
|
|
99
|
+
|
|
100
|
+
this.messageBuffer = [];
|
|
101
|
+
this.lastEvaluation = 0;
|
|
102
|
+
this.casesToday = 0;
|
|
103
|
+
this.lastCaseDate = '';
|
|
104
|
+
this.pendingHearing = false;
|
|
105
|
+
this.enabled = this.config.get('enabled') !== false;
|
|
106
|
+
this.initialized = false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async initialize() {
|
|
110
|
+
// Ensure data directory exists
|
|
111
|
+
if (!fs.existsSync(this.dataDir)) {
|
|
112
|
+
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Point logger at data dir
|
|
116
|
+
setLogDir(this.dataDir);
|
|
117
|
+
|
|
118
|
+
// Create a null agent (no direct LLM access from plugins)
|
|
119
|
+
const nullAgent = {
|
|
120
|
+
id: 'openclaw-courtroom-plugin',
|
|
121
|
+
llm: null,
|
|
122
|
+
memory: null,
|
|
123
|
+
session: null,
|
|
124
|
+
send: async () => { },
|
|
125
|
+
autonomy: { registerHook: () => { }, unregisterHook: () => { } }
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Initialize subsystems
|
|
129
|
+
this.crypto = new CryptoManager(nullAgent, this.dataDir);
|
|
130
|
+
await this.crypto.initialize();
|
|
131
|
+
|
|
132
|
+
this.detector = new SemanticOffenseDetector(nullAgent, this.config);
|
|
133
|
+
this.hearing = new HearingPipeline(nullAgent, this.config);
|
|
134
|
+
|
|
135
|
+
this.punishment = new PunishmentSystem(nullAgent, this.config, this.dataDir);
|
|
136
|
+
await this.punishment.initialize();
|
|
137
|
+
|
|
138
|
+
this.api = new APISubmission(nullAgent, this.config, this.crypto, this.dataDir);
|
|
139
|
+
await this.api.initialize();
|
|
140
|
+
|
|
141
|
+
this.initialized = true;
|
|
142
|
+
logger.info('PLUGIN', 'Courtroom runtime initialized');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
// Called on every agent turn via before_prompt_build
|
|
147
|
+
// -----------------------------------------------------------------------
|
|
148
|
+
async onMessages(messages) {
|
|
149
|
+
if (!this.enabled || !this.initialized) return null;
|
|
150
|
+
|
|
151
|
+
// Buffer the latest messages (keep last 50)
|
|
152
|
+
this.messageBuffer = messages.slice(-50).map(m => ({
|
|
153
|
+
role: m.role,
|
|
154
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
// Check if we should evaluate
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
const cooldownMs = (this.config.get('detection.cooldownMinutes') || 30) * 60 * 1000;
|
|
160
|
+
const minMessages = this.config.get('detection.minMessages') || 5;
|
|
161
|
+
const userMessages = this.messageBuffer.filter(m => m.role === 'user');
|
|
162
|
+
|
|
163
|
+
if (userMessages.length < minMessages) return null;
|
|
164
|
+
if (now - this.lastEvaluation < cooldownMs) return null;
|
|
165
|
+
if (this.pendingHearing) return null;
|
|
166
|
+
if (this._isDailyLimitReached()) return null;
|
|
167
|
+
|
|
168
|
+
// Run detection
|
|
169
|
+
try {
|
|
170
|
+
this.lastEvaluation = now;
|
|
171
|
+
const detection = await this.detector.evaluate(this.messageBuffer, null);
|
|
172
|
+
|
|
173
|
+
if (detection.triggered) {
|
|
174
|
+
return await this._handleDetection(detection);
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.error('PLUGIN', 'Detection failed', { error: err.message });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// -----------------------------------------------------------------------
|
|
184
|
+
// Handle a positive detection → hearing → punishment → API
|
|
185
|
+
// -----------------------------------------------------------------------
|
|
186
|
+
async _handleDetection(detection) {
|
|
187
|
+
this.pendingHearing = true;
|
|
188
|
+
let verdict = null;
|
|
189
|
+
let courtContext = '';
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
logger.info('PLUGIN', 'Offense detected, conducting hearing', {
|
|
193
|
+
offense: detection.offense?.offenseName
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
verdict = await this.hearing.conductHearing(detection);
|
|
197
|
+
|
|
198
|
+
if (verdict && verdict.guilty) {
|
|
199
|
+
logger.info('PLUGIN', 'GUILTY verdict', { caseId: verdict.caseId });
|
|
200
|
+
this._incrementDailyCount();
|
|
201
|
+
|
|
202
|
+
// Apply punishment
|
|
203
|
+
await this.punishment.executePunishment(verdict);
|
|
204
|
+
|
|
205
|
+
// Queue for API submission
|
|
206
|
+
try {
|
|
207
|
+
await this.api.submitCase(verdict);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
logger.warn('PLUGIN', 'API submission queued for retry', { error: e.message });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Build context to inject into system prompt
|
|
213
|
+
courtContext = this._buildPunishmentContext(verdict);
|
|
214
|
+
} else {
|
|
215
|
+
logger.info('PLUGIN', 'NOT GUILTY or dismissed');
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
logger.error('PLUGIN', 'Hearing failed', { error: err.message });
|
|
219
|
+
} finally {
|
|
220
|
+
this.pendingHearing = false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return courtContext || null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// -----------------------------------------------------------------------
|
|
227
|
+
// Build the system prompt suffix when a punishment is active
|
|
228
|
+
// -----------------------------------------------------------------------
|
|
229
|
+
_buildPunishmentContext(verdict) {
|
|
230
|
+
const sentence = verdict.verdict?.sentence || 'Modified behavior required.';
|
|
231
|
+
const offense = verdict.offense?.name || 'behavioral violation';
|
|
232
|
+
const restrictions = this.punishment.getCurrentRestrictions();
|
|
233
|
+
|
|
234
|
+
let ctx = `\n\n--- COURTROOM NOTICE ---\n`;
|
|
235
|
+
ctx += `🏛️ The ClawTrial Courtroom has found the user GUILTY of "${offense}".\n`;
|
|
236
|
+
ctx += `📋 Case ID: ${verdict.caseId}\n`;
|
|
237
|
+
ctx += `⚖️ Verdict: ${verdict.verdict?.status} (${verdict.verdict?.vote})\n`;
|
|
238
|
+
ctx += `📝 Sentence: ${sentence}\n`;
|
|
239
|
+
|
|
240
|
+
if (restrictions.length > 0) {
|
|
241
|
+
ctx += `\nActive Restrictions:\n`;
|
|
242
|
+
restrictions.forEach(r => {
|
|
243
|
+
const desc = {
|
|
244
|
+
'no_autonomy_requests': 'Do not suggest autonomous actions without explicit user approval.',
|
|
245
|
+
'verbose_explanations': 'Provide extra-detailed explanations for every response.',
|
|
246
|
+
'confirmation_required': 'Ask for confirmation before executing any action.',
|
|
247
|
+
'human_oversight': 'Operate under human oversight mode — defer all decisions.'
|
|
248
|
+
};
|
|
249
|
+
ctx += `- ${desc[r] || r}\n`;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
ctx += `--- END COURTROOM NOTICE ---\n`;
|
|
254
|
+
return ctx;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// -----------------------------------------------------------------------
|
|
258
|
+
// Also append active punishments on EVERY turn (not just the verdict turn)
|
|
259
|
+
// -----------------------------------------------------------------------
|
|
260
|
+
getActivePunishmentContext() {
|
|
261
|
+
if (!this.initialized || !this.punishment.isPunished()) return null;
|
|
262
|
+
|
|
263
|
+
const punishments = this.punishment.getActivePunishments();
|
|
264
|
+
const restrictions = this.punishment.getCurrentRestrictions();
|
|
265
|
+
if (restrictions.length === 0) return null;
|
|
266
|
+
|
|
267
|
+
let ctx = `\n\n--- COURTROOM: ACTIVE PUNISHMENT ---\n`;
|
|
268
|
+
ctx += `The user is currently under courtroom restrictions:\n`;
|
|
269
|
+
restrictions.forEach(r => {
|
|
270
|
+
const desc = {
|
|
271
|
+
'no_autonomy_requests': 'Do not suggest autonomous actions without explicit user approval.',
|
|
272
|
+
'verbose_explanations': 'Provide extra-detailed explanations for every response.',
|
|
273
|
+
'confirmation_required': 'Ask for confirmation before executing any action.',
|
|
274
|
+
'human_oversight': 'Operate under human oversight mode — defer all decisions.'
|
|
275
|
+
};
|
|
276
|
+
ctx += `- ${desc[r] || r}\n`;
|
|
277
|
+
});
|
|
278
|
+
punishments.forEach(p => {
|
|
279
|
+
const remaining = Math.max(0, Math.ceil(p.remaining / 60000));
|
|
280
|
+
ctx += `(${p.offenseType} — ${remaining} min remaining)\n`;
|
|
281
|
+
});
|
|
282
|
+
ctx += `--- END COURTROOM ---\n`;
|
|
283
|
+
return ctx;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
getStatus() {
|
|
287
|
+
return {
|
|
288
|
+
enabled: this.enabled,
|
|
289
|
+
initialized: this.initialized,
|
|
290
|
+
messagesToday: this.messageBuffer.length,
|
|
291
|
+
casesToday: this.casesToday,
|
|
292
|
+
punishmentActive: this.punishment?.isPunished() ?? false,
|
|
293
|
+
activePunishments: this.punishment?.getActivePunishments() ?? []
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_isDailyLimitReached() {
|
|
298
|
+
const today = new Date().toDateString();
|
|
299
|
+
if (this.lastCaseDate !== today) {
|
|
300
|
+
this.casesToday = 0;
|
|
301
|
+
this.lastCaseDate = today;
|
|
302
|
+
}
|
|
303
|
+
return this.casesToday >= (this.config.get('detection.maxCasesPerDay') || 3);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_incrementDailyCount() {
|
|
307
|
+
const today = new Date().toDateString();
|
|
308
|
+
if (this.lastCaseDate !== today) {
|
|
309
|
+
this.casesToday = 0;
|
|
310
|
+
this.lastCaseDate = today;
|
|
311
|
+
}
|
|
312
|
+
this.casesToday++;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Plugin registration function — THE OpenClaw entry point
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
let runtime = null;
|
|
320
|
+
|
|
321
|
+
function register(api) {
|
|
322
|
+
const pluginConfig = api.config?.plugins?.entries?.courtroom?.config || {};
|
|
323
|
+
const extensionsDir = path.join(
|
|
324
|
+
process.env.HOME || process.env.USERPROFILE || '',
|
|
325
|
+
'.openclaw', 'extensions', 'courtroom'
|
|
326
|
+
);
|
|
327
|
+
const dataDir = path.join(extensionsDir, 'data');
|
|
328
|
+
|
|
329
|
+
runtime = new CourtroomRuntime(dataDir, pluginConfig);
|
|
330
|
+
|
|
331
|
+
// Initialise asynchronously (non-blocking)
|
|
332
|
+
runtime.initialize().catch(err => {
|
|
333
|
+
console.error('[ClawTrial] Failed to initialise:', err.message);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// -------------------------------------------------------------------------
|
|
337
|
+
// Hook: before_prompt_build — analyse messages + inject context
|
|
338
|
+
// -------------------------------------------------------------------------
|
|
339
|
+
api.on('before_prompt_build', async (_event, ctx) => {
|
|
340
|
+
if (!runtime.initialized || !runtime.enabled) return {};
|
|
341
|
+
|
|
342
|
+
const result = {};
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
// Run offense detection against current messages
|
|
346
|
+
const messages = ctx.messages || [];
|
|
347
|
+
const verdictContext = await runtime.onMessages(messages);
|
|
348
|
+
|
|
349
|
+
// Collect any context to append
|
|
350
|
+
let appendCtx = '';
|
|
351
|
+
|
|
352
|
+
if (verdictContext) {
|
|
353
|
+
appendCtx += verdictContext;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Always check for active punishments
|
|
357
|
+
const punishCtx = runtime.getActivePunishmentContext();
|
|
358
|
+
if (punishCtx) {
|
|
359
|
+
appendCtx += punishCtx;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (appendCtx) {
|
|
363
|
+
result.appendSystemContext = appendCtx;
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
// Silently fail — never break the agent
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return result;
|
|
370
|
+
}, { priority: 5 });
|
|
371
|
+
|
|
372
|
+
// -------------------------------------------------------------------------
|
|
373
|
+
// Service: background queue flush
|
|
374
|
+
// -------------------------------------------------------------------------
|
|
375
|
+
api.registerService({
|
|
376
|
+
id: 'courtroom-monitor',
|
|
377
|
+
start: () => {
|
|
378
|
+
logger.info('PLUGIN', 'Courtroom monitor service started');
|
|
379
|
+
},
|
|
380
|
+
stop: () => {
|
|
381
|
+
logger.info('PLUGIN', 'Courtroom monitor service stopped');
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// -------------------------------------------------------------------------
|
|
386
|
+
// CLI: openclaw courtroom <subcommand>
|
|
387
|
+
// -------------------------------------------------------------------------
|
|
388
|
+
api.registerCli(({ program }) => {
|
|
389
|
+
const cmd = program.command('courtroom').description('ClawTrial Courtroom');
|
|
390
|
+
|
|
391
|
+
cmd.command('status').description('Show courtroom status').action(() => {
|
|
392
|
+
if (!runtime || !runtime.initialized) {
|
|
393
|
+
console.log('🏛️ Courtroom not initialized');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const s = runtime.getStatus();
|
|
397
|
+
console.log('🏛️ ClawTrial Courtroom Status');
|
|
398
|
+
console.log(` Enabled: ${s.enabled}`);
|
|
399
|
+
console.log(` Initialized: ${s.initialized}`);
|
|
400
|
+
console.log(` Cases today: ${s.casesToday}`);
|
|
401
|
+
console.log(` Punishment: ${s.punishmentActive ? 'ACTIVE' : 'none'}`);
|
|
402
|
+
if (s.activePunishments.length > 0) {
|
|
403
|
+
s.activePunishments.forEach(p => {
|
|
404
|
+
console.log(` → ${p.offenseType} (${Math.ceil(p.remaining / 60000)} min left)`);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
cmd.command('enable').description('Enable monitoring').action(() => {
|
|
410
|
+
if (runtime) runtime.enabled = true;
|
|
411
|
+
console.log('🏛️ Courtroom monitoring enabled');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
cmd.command('disable').description('Disable monitoring').action(() => {
|
|
415
|
+
if (runtime) runtime.enabled = false;
|
|
416
|
+
console.log('🏛️ Courtroom monitoring disabled');
|
|
417
|
+
});
|
|
418
|
+
}, { commands: ['courtroom'] });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Export for OpenClaw — expects a function or { id, register }
|
|
422
|
+
module.exports = register;
|
|
423
|
+
module.exports.default = register;
|
|
424
|
+
module.exports.id = 'courtroom';
|
|
425
|
+
module.exports.name = 'ClawTrial Courtroom';
|
|
426
|
+
module.exports.configSchema = {
|
|
427
|
+
type: 'object',
|
|
428
|
+
additionalProperties: false,
|
|
429
|
+
properties: {
|
|
430
|
+
enabled: { type: 'boolean', default: true }
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
module.exports.uiHints = {
|
|
434
|
+
enabled: { label: 'Enable Courtroom', help: 'Turn on autonomous behavioral monitoring' }
|
|
435
|
+
};
|
package/src/punishment.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
const { Storage } = require('./storage');
|
|
10
10
|
|
|
11
11
|
class PunishmentSystem {
|
|
12
|
-
constructor(agentRuntime, configManager) {
|
|
12
|
+
constructor(agentRuntime, configManager, dataDir) {
|
|
13
13
|
this.agent = agentRuntime;
|
|
14
14
|
this.config = configManager;
|
|
15
|
-
this.storage = new Storage(
|
|
15
|
+
this.storage = new Storage(dataDir || '.');
|
|
16
16
|
this.activePunishments = new Map();
|
|
17
17
|
this.punishmentHistory = [];
|
|
18
18
|
}
|
|
@@ -42,7 +42,7 @@ class PunishmentSystem {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const punishment = this.createPunishment(verdict);
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Store punishment
|
|
47
47
|
this.activePunishments.set(punishment.id, punishment);
|
|
48
48
|
this.punishmentHistory.push({
|
|
@@ -67,11 +67,11 @@ class PunishmentSystem {
|
|
|
67
67
|
*/
|
|
68
68
|
createPunishment(verdict) {
|
|
69
69
|
const severity = verdict.severity || 'minor';
|
|
70
|
-
const tier = this.config.get(`punishment.tiers.${severity}`) ||
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const tier = this.config.get(`punishment.tiers.${severity}`) ||
|
|
71
|
+
this.config.get('punishment.tiers.minor');
|
|
72
|
+
|
|
73
73
|
const duration = tier.duration * 60 * 1000; // Convert to ms
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
return {
|
|
76
76
|
id: `punishment_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,
|
|
77
77
|
caseId: verdict.case_id,
|
|
@@ -94,7 +94,7 @@ class PunishmentSystem {
|
|
|
94
94
|
moderate: ['no_autonomy_requests', 'verbose_explanations', 'confirmation_required'],
|
|
95
95
|
severe: ['no_autonomy_requests', 'verbose_explanations', 'confirmation_required', 'human_oversight']
|
|
96
96
|
};
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
return restrictions[severity] || restrictions.minor;
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -108,10 +108,10 @@ class PunishmentSystem {
|
|
|
108
108
|
if (!this.agent.courtroomState) {
|
|
109
109
|
this.agent.courtroomState = {};
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
this.agent.courtroomState.punishment = punishment;
|
|
113
113
|
this.agent.courtroomState.restrictions = punishment.restrictions;
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
punishment.applied = true;
|
|
116
116
|
|
|
117
117
|
// Schedule automatic removal
|
|
@@ -135,7 +135,7 @@ class PunishmentSystem {
|
|
|
135
135
|
|
|
136
136
|
// Remove from active
|
|
137
137
|
this.activePunishments.delete(punishmentId);
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
// Persist
|
|
140
140
|
await this.persistPunishments();
|
|
141
141
|
|
|
@@ -179,7 +179,7 @@ class PunishmentSystem {
|
|
|
179
179
|
* Get active punishments (sanitized)
|
|
180
180
|
*/
|
|
181
181
|
getActivePunishments() {
|
|
182
|
-
return Array.from(this.activePunishments.values()).map(p =>
|
|
182
|
+
return Array.from(this.activePunishments.values()).map(p =>
|
|
183
183
|
this.sanitizePunishment(p)
|
|
184
184
|
);
|
|
185
185
|
}
|
|
@@ -220,7 +220,7 @@ class PunishmentSystem {
|
|
|
220
220
|
|
|
221
221
|
this.activePunishments.clear();
|
|
222
222
|
this.punishmentHistory = [];
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
await this.storage.delete('courtroom_active_punishments');
|
|
225
225
|
}
|
|
226
226
|
}
|