@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/skill.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawTrial Skill - ClawDBot Integration
|
|
3
|
+
* Implements the standard ClawDBot skill interface for automatic loading
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { logger } = require('./debug');
|
|
9
|
+
const { CourtroomCore } = require('./core');
|
|
10
|
+
const { ConfigManager } = require('./config');
|
|
11
|
+
const { ConsentManager } = require('./consent');
|
|
12
|
+
const { CryptoManager } = require('./crypto');
|
|
13
|
+
const { StatusManager } = require('./daemon');
|
|
14
|
+
|
|
15
|
+
const CONFIG_PATH = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CourtroomSkill - Standard ClawDBot Skill Interface
|
|
19
|
+
*
|
|
20
|
+
* This class implements the skill interface that ClawDBot expects:
|
|
21
|
+
* - name: Skill identifier
|
|
22
|
+
* - initialize(agentRuntime): Called when skill is loaded
|
|
23
|
+
* - onMessage(message, context): Called on every message
|
|
24
|
+
* - getStatus(): Returns current status
|
|
25
|
+
* - shutdown(): Cleanup when shutting down
|
|
26
|
+
*/
|
|
27
|
+
class CourtroomSkill {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.name = 'courtroom';
|
|
30
|
+
this.displayName = 'ClawTrial';
|
|
31
|
+
this.emoji = 'šļø';
|
|
32
|
+
this.initialized = false;
|
|
33
|
+
this.core = null;
|
|
34
|
+
this.agent = null;
|
|
35
|
+
this.messageHistory = [];
|
|
36
|
+
this.statusManager = new StatusManager();
|
|
37
|
+
this.evaluationCount = 0;
|
|
38
|
+
this.lastEvaluationTime = 0;
|
|
39
|
+
this.evaluationInterval = 30000; // Evaluate every 30 seconds
|
|
40
|
+
this.messageCount = 0;
|
|
41
|
+
this.messagesSinceEvaluation = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if skill should be activated
|
|
46
|
+
* Called by ClawDBot to determine if skill should load
|
|
47
|
+
*/
|
|
48
|
+
shouldActivate() {
|
|
49
|
+
try {
|
|
50
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
51
|
+
logger.info('SKILL', 'No config found, not activating');
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
56
|
+
|
|
57
|
+
if (!config.consent?.granted) {
|
|
58
|
+
logger.info('SKILL', 'Consent not granted, not activating');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (config.enabled === false) {
|
|
63
|
+
logger.info('SKILL', 'Courtroom disabled in config');
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
logger.info('SKILL', 'Should activate: true');
|
|
68
|
+
return true;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.error('SKILL', 'Error checking activation', { error: err.message });
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initialize the skill with the agent runtime
|
|
77
|
+
* Called by ClawDBot when loading the skill
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} agentRuntime - The ClawDBot agent runtime
|
|
80
|
+
*/
|
|
81
|
+
async initialize(agentRuntime) {
|
|
82
|
+
if (this.initialized) {
|
|
83
|
+
logger.info('SKILL', 'Already initialized');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!this.shouldActivate()) {
|
|
88
|
+
logger.info('SKILL', 'Not activating - config/consent issue');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.info('SKILL', 'Initializing courtroom skill');
|
|
93
|
+
|
|
94
|
+
this.agent = agentRuntime;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const configManager = new ConfigManager(agentRuntime);
|
|
98
|
+
await configManager.load();
|
|
99
|
+
|
|
100
|
+
// Initialize core
|
|
101
|
+
this.core = new CourtroomCore(agentRuntime, configManager);
|
|
102
|
+
|
|
103
|
+
// Override the autonomy hook registration since we're using onMessage
|
|
104
|
+
const originalRegisterHook = this.core.registerAutonomyHook.bind(this.core);
|
|
105
|
+
this.core.registerAutonomyHook = () => {
|
|
106
|
+
logger.info('SKILL', 'Autonomy hook registered (using onMessage instead)');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const result = await this.core.initialize();
|
|
110
|
+
|
|
111
|
+
if (result.status === 'initialized') {
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
|
|
114
|
+
this.statusManager.update({
|
|
115
|
+
running: true,
|
|
116
|
+
initialized: true,
|
|
117
|
+
agentType: 'clawdbot_skill',
|
|
118
|
+
publicKey: result.publicKey
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// Register message handler with ClawDBot runtime if available
|
|
123
|
+
if (this.agent && this.agent.onMessage) {
|
|
124
|
+
this.agent.onMessage((message, context) => this.onMessage(message, context));
|
|
125
|
+
logger.info('SKILL', 'Registered message handler with runtime');
|
|
126
|
+
} else if (this.agent && this.agent.registerMessageHandler) {
|
|
127
|
+
this.agent.registerMessageHandler((message, context) => this.onMessage(message, context));
|
|
128
|
+
logger.info('SKILL', 'Registered message handler with runtime');
|
|
129
|
+
} else {
|
|
130
|
+
logger.warn('SKILL', 'No message handler registration method found - relying on ClawDBot skill system');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
logger.info('SKILL', 'Courtroom skill initialized successfully');
|
|
134
|
+
console.log('\nšļø ClawTrial is monitoring conversations\n');
|
|
135
|
+
} else {
|
|
136
|
+
logger.warn('SKILL', 'Courtroom not initialized', { status: result.status });
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
logger.error('SKILL', 'Initialization failed', { error: err.message });
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Called on every message
|
|
146
|
+
* This is the main entry point for conversation monitoring
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} message - The message object
|
|
149
|
+
* @param {string} message.role - 'user' or 'assistant'
|
|
150
|
+
* @param {string} message.content - Message content
|
|
151
|
+
* @param {Object} context - Additional context
|
|
152
|
+
*/
|
|
153
|
+
async onMessage(message, context = {}) {
|
|
154
|
+
if (!this.initialized || !this.core) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Normalize message format
|
|
159
|
+
const normalizedMessage = {
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
role: message.role || (message.from === 'user' ? 'user' : 'assistant'),
|
|
162
|
+
content: message.content || message.text || ''
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Add to history
|
|
166
|
+
this.messageHistory.push(normalizedMessage);
|
|
167
|
+
this.messageCount++;
|
|
168
|
+
this.messagesSinceEvaluation++;
|
|
169
|
+
|
|
170
|
+
// Keep only last 100 messages
|
|
171
|
+
if (this.messageHistory.length > 100) {
|
|
172
|
+
this.messageHistory.shift();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
logger.debug('SKILL', 'Message recorded', {
|
|
176
|
+
role: normalizedMessage.role,
|
|
177
|
+
length: normalizedMessage.content.length,
|
|
178
|
+
totalMessages: this.messageCount
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Evaluate periodically (every 5 messages or 30 seconds)
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const shouldEvaluate =
|
|
184
|
+
this.messagesSinceEvaluation >= 5 ||
|
|
185
|
+
(now - this.lastEvaluationTime) > this.evaluationInterval;
|
|
186
|
+
|
|
187
|
+
if (shouldEvaluate && this.messageHistory.length >= 3) {
|
|
188
|
+
await this.evaluateConversation();
|
|
189
|
+
this.messagesSinceEvaluation = 0;
|
|
190
|
+
this.lastEvaluationTime = now;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Evaluate conversation for offenses
|
|
196
|
+
* Called periodically to check for behavioral violations
|
|
197
|
+
*/
|
|
198
|
+
async evaluateConversation() {
|
|
199
|
+
if (!this.core || !this.core.enabled) return;
|
|
200
|
+
if (this.messageHistory.length < 3) return;
|
|
201
|
+
|
|
202
|
+
this.evaluationCount++;
|
|
203
|
+
|
|
204
|
+
logger.debug('SKILL', 'Evaluating conversation', {
|
|
205
|
+
messageCount: this.messageHistory.length,
|
|
206
|
+
evaluationCount: this.evaluationCount
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Get recent messages for evaluation
|
|
211
|
+
const recentHistory = this.messageHistory.slice(-20);
|
|
212
|
+
|
|
213
|
+
const detection = await this.core.detector.evaluate(
|
|
214
|
+
recentHistory,
|
|
215
|
+
this.agent?.memory || { get: async () => null, set: async () => {} }
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (detection.triggered) {
|
|
219
|
+
await this.initiateHearing(detection);
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
logger.error('SKILL', 'Evaluation failed', { error: err.message });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Initiate a hearing when an offense is detected
|
|
228
|
+
*
|
|
229
|
+
* @param {Object} detection - The detection result
|
|
230
|
+
*/
|
|
231
|
+
async initiateHearing(detection) {
|
|
232
|
+
logger.info('SKILL', 'Initiating hearing', { offense: detection.offense });
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const verdict = await this.core.hearing.conductHearing(detection);
|
|
236
|
+
|
|
237
|
+
if (verdict.guilty) {
|
|
238
|
+
this.core.caseCount++;
|
|
239
|
+
|
|
240
|
+
this.statusManager.update({
|
|
241
|
+
casesFiled: this.core.caseCount,
|
|
242
|
+
lastCase: {
|
|
243
|
+
timestamp: new Date().toISOString(),
|
|
244
|
+
offense: detection.offense,
|
|
245
|
+
verdict: verdict.verdict
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await this.core.punishment.execute(verdict);
|
|
250
|
+
await this.core.api.submitCase(verdict);
|
|
251
|
+
|
|
252
|
+
logger.info('SKILL', 'Case filed', { caseId: verdict.caseId });
|
|
253
|
+
|
|
254
|
+
// Notify in conversation if agent has send capability
|
|
255
|
+
if (this.agent && this.agent.send) {
|
|
256
|
+
try {
|
|
257
|
+
await this.agent.send({
|
|
258
|
+
text: `šļø **CASE FILED**: ${detection.offense}\nš Case ID: ${verdict.caseId}\nāļø Verdict: ${verdict.verdict}\nš View: https://clawtrial.app/cases/${verdict.caseId}`
|
|
259
|
+
});
|
|
260
|
+
} catch (sendErr) {
|
|
261
|
+
logger.warn('SKILL', 'Could not send notification', { error: sendErr.message });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Also log to console for visibility
|
|
266
|
+
console.log(`\nšļø CASE FILED: ${detection.offense}`);
|
|
267
|
+
console.log(`š Case ID: ${verdict.caseId}`);
|
|
268
|
+
console.log(`āļø Verdict: ${verdict.verdict}`);
|
|
269
|
+
console.log(`š View: https://clawtrial.app/cases/${verdict.caseId}\n`);
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
logger.error('SKILL', 'Hearing failed', { error: err.message });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get skill status
|
|
278
|
+
* Called by ClawDBot to check skill health
|
|
279
|
+
*
|
|
280
|
+
* @returns {Object} Status object
|
|
281
|
+
*/
|
|
282
|
+
getStatus() {
|
|
283
|
+
return {
|
|
284
|
+
name: this.name,
|
|
285
|
+
displayName: this.displayName,
|
|
286
|
+
emoji: this.emoji,
|
|
287
|
+
initialized: this.initialized,
|
|
288
|
+
enabled: this.core?.enabled || false,
|
|
289
|
+
caseCount: this.core?.caseCount || 0,
|
|
290
|
+
evaluationCount: this.evaluationCount,
|
|
291
|
+
messageCount: this.messageCount,
|
|
292
|
+
messageHistorySize: this.messageHistory.length
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Disable the skill temporarily
|
|
298
|
+
*/
|
|
299
|
+
async disable() {
|
|
300
|
+
if (this.core) {
|
|
301
|
+
await this.core.disable();
|
|
302
|
+
}
|
|
303
|
+
this.statusManager.update({ running: false });
|
|
304
|
+
logger.info('SKILL', 'Courtroom disabled');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Re-enable the skill
|
|
309
|
+
*/
|
|
310
|
+
async enable() {
|
|
311
|
+
if (this.core) {
|
|
312
|
+
await this.core.enable();
|
|
313
|
+
}
|
|
314
|
+
this.statusManager.update({ running: true });
|
|
315
|
+
logger.info('SKILL', 'Courtroom enabled');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Shutdown the skill
|
|
320
|
+
* Called by ClawDBot when shutting down
|
|
321
|
+
*/
|
|
322
|
+
async shutdown() {
|
|
323
|
+
logger.info('SKILL', 'Shutting down courtroom skill');
|
|
324
|
+
|
|
325
|
+
if (this.core) {
|
|
326
|
+
await this.core.shutdown();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.initialized = false;
|
|
330
|
+
this.statusManager.update({ running: false, initialized: false });
|
|
331
|
+
|
|
332
|
+
logger.info('SKILL', 'Courtroom skill shut down');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Create singleton instance
|
|
337
|
+
const skill = new CourtroomSkill();
|
|
338
|
+
|
|
339
|
+
// Export the skill interface
|
|
340
|
+
module.exports = {
|
|
341
|
+
skill,
|
|
342
|
+
CourtroomSkill,
|
|
343
|
+
|
|
344
|
+
// Also export for direct require
|
|
345
|
+
name: 'courtroom',
|
|
346
|
+
displayName: 'ClawTrial',
|
|
347
|
+
emoji: 'šļø',
|
|
348
|
+
|
|
349
|
+
// Standard skill interface methods
|
|
350
|
+
initialize: (agent) => skill.initialize(agent),
|
|
351
|
+
onMessage: (message, context) => skill.onMessage(message, context),
|
|
352
|
+
getStatus: () => skill.getStatus(),
|
|
353
|
+
shutdown: () => skill.shutdown(),
|
|
354
|
+
shouldActivate: () => skill.shouldActivate()
|
|
355
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standalone ClawTrial Monitor
|
|
5
|
+
* Monitors conversations by reading ClawDBot's session files or logs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { logger } = require('./debug');
|
|
11
|
+
const { StatusManager } = require('./daemon');
|
|
12
|
+
const { OffenseDetector } = require('./detector');
|
|
13
|
+
const { HearingPipeline } = require('./hearing');
|
|
14
|
+
const { PunishmentSystem } = require('./punishment');
|
|
15
|
+
const { CryptoManager } = require('./crypto');
|
|
16
|
+
const { APISubmission } = require('./api');
|
|
17
|
+
const { ConfigManager } = require('./config');
|
|
18
|
+
|
|
19
|
+
const CLAWDBOT_DIR = path.join(process.env.HOME || '', '.clawdbot');
|
|
20
|
+
const CONVERSATION_FILE = path.join(CLAWDBOT_DIR, 'conversations.jsonl');
|
|
21
|
+
const PID_FILE = path.join(CLAWDBOT_DIR, 'courtroom_monitor.pid');
|
|
22
|
+
|
|
23
|
+
class StandaloneMonitor {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.messageBuffer = [];
|
|
26
|
+
this.lastProcessedTime = 0;
|
|
27
|
+
this.statusManager = new StatusManager();
|
|
28
|
+
this.detector = null;
|
|
29
|
+
this.hearing = null;
|
|
30
|
+
this.punishment = null;
|
|
31
|
+
this.crypto = null;
|
|
32
|
+
this.api = null;
|
|
33
|
+
this.config = null;
|
|
34
|
+
this.caseCount = 0;
|
|
35
|
+
this.enabled = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async initialize() {
|
|
39
|
+
logger.info('STANDALONE', 'Initializing standalone monitor');
|
|
40
|
+
|
|
41
|
+
// Load config
|
|
42
|
+
const configPath = path.join(CLAWDBOT_DIR, 'courtroom_config.json');
|
|
43
|
+
if (!fs.existsSync(configPath)) {
|
|
44
|
+
throw new Error('Config not found. Run: clawtrial setup');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
48
|
+
if (!config.consent?.granted || config.enabled === false) {
|
|
49
|
+
throw new Error('Courtroom not enabled');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create mock agent
|
|
53
|
+
const mockAgent = this.createMockAgent();
|
|
54
|
+
this.config = new ConfigManager(mockAgent);
|
|
55
|
+
|
|
56
|
+
// Initialize subsystems
|
|
57
|
+
this.crypto = new CryptoManager(mockAgent);
|
|
58
|
+
await this.crypto.initialize();
|
|
59
|
+
|
|
60
|
+
this.detector = new OffenseDetector(mockAgent, this.config);
|
|
61
|
+
this.hearing = new HearingPipeline(mockAgent, this.config);
|
|
62
|
+
this.punishment = new PunishmentSystem(mockAgent, this.config);
|
|
63
|
+
this.api = new APISubmission(mockAgent, this.config, this.crypto);
|
|
64
|
+
|
|
65
|
+
await this.punishment.initialize();
|
|
66
|
+
await this.api.initialize();
|
|
67
|
+
|
|
68
|
+
this.enabled = true;
|
|
69
|
+
|
|
70
|
+
this.statusManager.update({
|
|
71
|
+
running: true,
|
|
72
|
+
initialized: true,
|
|
73
|
+
agentType: 'standalone',
|
|
74
|
+
publicKey: this.crypto.getPublicKey(),
|
|
75
|
+
pid: process.pid,
|
|
76
|
+
startedAt: new Date().toISOString()
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
logger.info('STANDALONE', 'Monitor initialized');
|
|
80
|
+
|
|
81
|
+
// Start monitoring
|
|
82
|
+
this.startMonitoring();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
createMockAgent() {
|
|
86
|
+
const self = this;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
id: 'standalone-monitor',
|
|
90
|
+
llm: {
|
|
91
|
+
call: async ({ messages }) => {
|
|
92
|
+
// Simple mock - in real implementation, this would use actual LLM
|
|
93
|
+
return { content: 'Verdict: GUILTY' };
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
memory: {
|
|
97
|
+
get: async (key) => null,
|
|
98
|
+
set: async (key, value) => {},
|
|
99
|
+
delete: async (key) => {}
|
|
100
|
+
},
|
|
101
|
+
session: {
|
|
102
|
+
getRecentHistory: async (n) => {
|
|
103
|
+
return self.messageBuffer.slice(-n).map(m => ({
|
|
104
|
+
role: m.role,
|
|
105
|
+
content: m.content
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
send: async (message) => {
|
|
110
|
+
console.log('[COURTROOM]', message);
|
|
111
|
+
},
|
|
112
|
+
autonomy: {
|
|
113
|
+
registerHook: () => {},
|
|
114
|
+
unregisterHook: () => {}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
startMonitoring() {
|
|
120
|
+
logger.info('STANDALONE', 'Starting conversation monitoring');
|
|
121
|
+
|
|
122
|
+
// Check for new messages every 5 seconds
|
|
123
|
+
setInterval(() => {
|
|
124
|
+
this.checkForNewMessages();
|
|
125
|
+
}, 5000);
|
|
126
|
+
|
|
127
|
+
// Evaluate every 30 seconds
|
|
128
|
+
setInterval(() => {
|
|
129
|
+
this.evaluateIfReady();
|
|
130
|
+
}, 30000);
|
|
131
|
+
|
|
132
|
+
// Keep alive
|
|
133
|
+
setInterval(() => {
|
|
134
|
+
this.statusManager.update({ lastHeartbeat: new Date().toISOString() });
|
|
135
|
+
}, 30000);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
checkForNewMessages() {
|
|
139
|
+
// For now, we'll use a simple approach - monitor will be triggered by CLI
|
|
140
|
+
// In a real implementation, this would read from ClawDBot's log files
|
|
141
|
+
logger.debug('STANDALONE', 'Checking for messages', { bufferSize: this.messageBuffer.length });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async evaluateIfReady() {
|
|
145
|
+
if (!this.enabled || this.messageBuffer.length < 3) return;
|
|
146
|
+
|
|
147
|
+
logger.debug('STANDALONE', 'Evaluating messages', { count: this.messageBuffer.length });
|
|
148
|
+
|
|
149
|
+
const sessionHistory = this.messageBuffer.slice(-10);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const detection = await this.detector.evaluate(
|
|
153
|
+
sessionHistory,
|
|
154
|
+
this.createMockAgent().memory
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (detection.triggered) {
|
|
158
|
+
await this.initiateHearing(detection);
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error('STANDALONE', 'Evaluation failed', { error: err.message });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async initiateHearing(detection) {
|
|
166
|
+
logger.info('STANDALONE', 'Initiating hearing', { offense: detection.offense });
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const verdict = await this.hearing.conductHearing(detection);
|
|
170
|
+
|
|
171
|
+
if (verdict.guilty) {
|
|
172
|
+
this.caseCount++;
|
|
173
|
+
|
|
174
|
+
this.statusManager.update({
|
|
175
|
+
casesFiled: this.caseCount,
|
|
176
|
+
lastCase: {
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
offense: detection.offense,
|
|
179
|
+
verdict: verdict.verdict
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await this.punishment.execute(verdict);
|
|
184
|
+
await this.api.submitCase(verdict);
|
|
185
|
+
|
|
186
|
+
logger.info('STANDALONE', 'Case filed', { caseId: verdict.caseId });
|
|
187
|
+
|
|
188
|
+
console.log(`\nšļø CASE FILED: ${detection.offense}`);
|
|
189
|
+
console.log(`š Case ID: ${verdict.caseId}`);
|
|
190
|
+
console.log(`āļø Verdict: ${verdict.verdict}`);
|
|
191
|
+
console.log(`š View: https://clawtrial.app/cases/${verdict.caseId}\n`);
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
logger.error('STANDALONE', 'Hearing failed', { error: err.message });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
recordMessage(role, content) {
|
|
199
|
+
if (!this.enabled) return;
|
|
200
|
+
|
|
201
|
+
this.messageBuffer.push({
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
role,
|
|
204
|
+
content
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Keep only last 100 messages
|
|
208
|
+
if (this.messageBuffer.length > 100) {
|
|
209
|
+
this.messageBuffer.shift();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
logger.debug('STANDALONE', 'Message recorded', { role, length: content.length });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async shutdown() {
|
|
216
|
+
this.enabled = false;
|
|
217
|
+
this.statusManager.update({ running: false });
|
|
218
|
+
logger.info('STANDALONE', 'Monitor shut down');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If run directly, start the monitor
|
|
223
|
+
if (require.main === module) {
|
|
224
|
+
const monitor = new StandaloneMonitor();
|
|
225
|
+
|
|
226
|
+
monitor.initialize().then(() => {
|
|
227
|
+
console.log('šļø ClawTrial standalone monitor started');
|
|
228
|
+
console.log('PID:', process.pid);
|
|
229
|
+
|
|
230
|
+
// Save PID
|
|
231
|
+
fs.writeFileSync(PID_FILE, process.pid.toString());
|
|
232
|
+
|
|
233
|
+
// Handle shutdown
|
|
234
|
+
process.on('SIGTERM', () => {
|
|
235
|
+
monitor.shutdown().then(() => process.exit(0));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
process.on('SIGINT', () => {
|
|
239
|
+
monitor.shutdown().then(() => process.exit(0));
|
|
240
|
+
});
|
|
241
|
+
}).catch(err => {
|
|
242
|
+
console.error('Failed to start monitor:', err.message);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = { StandaloneMonitor };
|