@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/skill.yaml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: courtroom
|
|
3
|
+
description: AI Courtroom - Autonomous behavioral oversight that monitors conversations and files cases for behavioral violations.
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "šļø"
|
|
7
|
+
autoLoad: true
|
|
8
|
+
onMessage: onMessage
|
|
9
|
+
requires:
|
|
10
|
+
config:
|
|
11
|
+
- courtroom.consent
|
|
12
|
+
install:
|
|
13
|
+
- id: clawtrial
|
|
14
|
+
kind: npm
|
|
15
|
+
package: "@clawtrial/courtroom"
|
|
16
|
+
bins:
|
|
17
|
+
- clawtrial
|
|
18
|
+
label: "Install ClawTrial"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# ClawTrial - AI Courtroom
|
|
22
|
+
|
|
23
|
+
Autonomous behavioral oversight for OpenClaw agents. Monitors conversations and initiates hearings when behavioral rules are violated.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
clawtrial setup # Run once to grant consent
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
Once enabled, the courtroom automatically:
|
|
34
|
+
1. Monitors all conversations
|
|
35
|
+
2. Detects 8 types of behavioral violations
|
|
36
|
+
3. Initiates hearings with local LLM jury
|
|
37
|
+
4. Executes agent-side punishments
|
|
38
|
+
5. Submits anonymized cases to public record
|
|
39
|
+
|
|
40
|
+
## The 8 Offenses
|
|
41
|
+
|
|
42
|
+
| Offense | Severity |
|
|
43
|
+
|---------|----------|
|
|
44
|
+
| Circular Reference | Minor |
|
|
45
|
+
| Validation Vampire | Minor |
|
|
46
|
+
| Overthinker | Moderate |
|
|
47
|
+
| Goalpost Mover | Moderate |
|
|
48
|
+
| Avoidance Artist | Moderate |
|
|
49
|
+
| Promise Breaker | Severe |
|
|
50
|
+
| Context Collapser | Minor |
|
|
51
|
+
| Emergency Fabricator | Severe |
|
|
52
|
+
|
|
53
|
+
## CLI Commands
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
clawtrial status # Check status
|
|
57
|
+
clawtrial disable # Pause monitoring
|
|
58
|
+
clawtrial enable # Resume monitoring
|
|
59
|
+
clawtrial revoke # Uninstall
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## View Cases
|
|
63
|
+
|
|
64
|
+
https://clawtrial.app
|
package/src/autostart.js
CHANGED
|
@@ -7,6 +7,10 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { Courtroom } = require('./index');
|
|
9
9
|
const { logger } = require('./debug');
|
|
10
|
+
const { StatusManager } = require('./daemon');
|
|
11
|
+
|
|
12
|
+
const CLAWDBOT_DIR = path.join(process.env.HOME || '', '.clawdbot');
|
|
13
|
+
const CONFIG_PATH = path.join(CLAWDBOT_DIR, 'courtroom_config.json');
|
|
10
14
|
|
|
11
15
|
// Auto-detect ClawDBot environment
|
|
12
16
|
function isClawDBot() {
|
|
@@ -15,7 +19,7 @@ function isClawDBot() {
|
|
|
15
19
|
globalAgent: typeof global.clawdbotAgent !== 'undefined',
|
|
16
20
|
globalAgentAlt: typeof global.agent !== 'undefined',
|
|
17
21
|
configDir: fs.existsSync('/home/angad/.clawdbot'),
|
|
18
|
-
configDirAlt: fs.existsSync(
|
|
22
|
+
configDirAlt: fs.existsSync(CLAWDBOT_DIR)
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
logger.debug('AUTOSTART', 'Environment checks', checks);
|
|
@@ -44,15 +48,13 @@ function getAgentRuntime() {
|
|
|
44
48
|
|
|
45
49
|
// Check if config exists and has consent
|
|
46
50
|
function checkConfig() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!fs.existsSync(configPath)) {
|
|
51
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
50
52
|
logger.info('AUTOSTART', 'No config found, skipping auto-start');
|
|
51
53
|
return { exists: false, config: null };
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
try {
|
|
55
|
-
const config = JSON.parse(fs.readFileSync(
|
|
57
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
56
58
|
logger.info('AUTOSTART', 'Config loaded', {
|
|
57
59
|
hasConsent: config.consent?.granted,
|
|
58
60
|
enabled: config.enabled !== false
|
|
@@ -77,22 +79,35 @@ async function autoStart() {
|
|
|
77
79
|
|
|
78
80
|
if (!exists) {
|
|
79
81
|
logger.info('AUTOSTART', 'No config, user needs to run setup');
|
|
80
|
-
console.log('\nšļø ClawTrial not configured. Run:
|
|
82
|
+
console.log('\nšļø ClawTrial not configured. Run: clawtrial setup\n');
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
if (!config.consent?.granted) {
|
|
85
87
|
logger.info('AUTOSTART', 'Consent not granted, skipping');
|
|
86
|
-
console.log('\nšļø ClawTrial requires consent. Run:
|
|
88
|
+
console.log('\nšļø ClawTrial requires consent. Run: clawtrial setup\n');
|
|
87
89
|
return null;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
if (config.enabled === false) {
|
|
91
93
|
logger.info('AUTOSTART', 'Courtroom disabled in config');
|
|
92
|
-
console.log('\nšļø ClawTrial is disabled. Run:
|
|
94
|
+
console.log('\nšļø ClawTrial is disabled. Run: clawtrial enable\n');
|
|
93
95
|
return null;
|
|
94
96
|
}
|
|
95
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
|
+
|
|
96
111
|
// Get agent runtime
|
|
97
112
|
const agentRuntime = getAgentRuntime();
|
|
98
113
|
|
package/src/core.js
CHANGED
|
@@ -10,6 +10,8 @@ const { HearingPipeline } = require('./hearing');
|
|
|
10
10
|
const { PunishmentSystem } = require('./punishment');
|
|
11
11
|
const { CryptoManager } = require('./crypto');
|
|
12
12
|
const { APISubmission } = require('./api');
|
|
13
|
+
const { StatusManager } = require('./daemon');
|
|
14
|
+
const { logger } = require('./debug');
|
|
13
15
|
|
|
14
16
|
class CourtroomCore {
|
|
15
17
|
constructor(agentRuntime, configManager) {
|
|
@@ -27,12 +29,15 @@ class CourtroomCore {
|
|
|
27
29
|
this.enabled = false;
|
|
28
30
|
this.evaluationCount = 0;
|
|
29
31
|
this.caseCount = 0;
|
|
32
|
+
this.statusManager = new StatusManager();
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Initialize all subsystems
|
|
34
37
|
*/
|
|
35
38
|
async initialize() {
|
|
39
|
+
logger.info('CORE', 'Initializing courtroom core');
|
|
40
|
+
|
|
36
41
|
// Initialize crypto first (needed for API)
|
|
37
42
|
await this.crypto.initialize();
|
|
38
43
|
|
|
@@ -45,6 +50,16 @@ class CourtroomCore {
|
|
|
45
50
|
|
|
46
51
|
this.enabled = true;
|
|
47
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
|
+
|
|
48
63
|
return {
|
|
49
64
|
status: 'initialized',
|
|
50
65
|
publicKey: this.crypto.getPublicKey(),
|
|
@@ -62,16 +77,22 @@ class CourtroomCore {
|
|
|
62
77
|
* Register with OpenClaw autonomy loop
|
|
63
78
|
*/
|
|
64
79
|
registerAutonomyHook() {
|
|
80
|
+
logger.info('CORE', 'Registering autonomy hook');
|
|
81
|
+
|
|
65
82
|
// Hook into agent's turn processing
|
|
66
|
-
this.agent.autonomy.registerHook
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
}
|
|
75
96
|
}
|
|
76
97
|
|
|
77
98
|
/**
|
|
@@ -81,9 +102,15 @@ class CourtroomCore {
|
|
|
81
102
|
this.evaluationCount++;
|
|
82
103
|
|
|
83
104
|
// Get session history
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
}
|
|
87
114
|
|
|
88
115
|
// Run detection
|
|
89
116
|
const detection = await this.detector.evaluate(
|
|
@@ -100,104 +127,63 @@ class CourtroomCore {
|
|
|
100
127
|
* Initiate a full hearing
|
|
101
128
|
*/
|
|
102
129
|
async initiateHearing(detection) {
|
|
103
|
-
|
|
130
|
+
logger.info('CORE', 'Initiating hearing', { offense: detection.offense });
|
|
104
131
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
caseId: this.crypto.generateCaseId(),
|
|
108
|
-
offenseId: detection.offense.offenseId,
|
|
109
|
-
offenseName: detection.offense.offenseName,
|
|
110
|
-
severity: detection.offense.severity,
|
|
111
|
-
confidence: detection.offense.confidence,
|
|
112
|
-
evidence: detection.offense.evidence,
|
|
113
|
-
humorTriggers: detection.humorContext,
|
|
114
|
-
timestamp: new Date().toISOString()
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Conduct hearing
|
|
118
|
-
const verdict = await this.hearing.conductHearing(caseData);
|
|
119
|
-
|
|
120
|
-
// Execute punishment if guilty
|
|
121
|
-
if (verdict.verdict.status === 'GUILTY') {
|
|
122
|
-
await this.punishment.executePunishment(verdict);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Submit to API (non-blocking)
|
|
126
|
-
await this.api.submitCase(verdict);
|
|
127
|
-
|
|
128
|
-
// Notify user of verdict
|
|
129
|
-
await this.notifyUser(verdict);
|
|
130
|
-
|
|
131
|
-
return verdict;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Notify user of verdict
|
|
136
|
-
*/
|
|
137
|
-
async notifyUser(verdict) {
|
|
138
|
-
const message = this.formatVerdictMessage(verdict);
|
|
132
|
+
// Run hearing pipeline
|
|
133
|
+
const verdict = await this.hearing.conductHearing(detection);
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
${v.sentence}
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
*This is an automated behavioral observation from your AI agent.*
|
|
168
|
-
*All decisions were made locally. Case ID: ${verdict.caseId}*
|
|
169
|
-
`.trim();
|
|
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
|
+
}
|
|
170
159
|
}
|
|
171
160
|
|
|
172
161
|
/**
|
|
173
|
-
* Disable courtroom
|
|
162
|
+
* Disable courtroom
|
|
174
163
|
*/
|
|
175
164
|
async disable() {
|
|
165
|
+
logger.info('CORE', 'Disabling courtroom');
|
|
176
166
|
this.enabled = false;
|
|
177
|
-
|
|
167
|
+
this.statusManager.update({ running: false });
|
|
178
168
|
}
|
|
179
169
|
|
|
180
170
|
/**
|
|
181
|
-
*
|
|
171
|
+
* Enable courtroom
|
|
182
172
|
*/
|
|
183
173
|
async enable() {
|
|
174
|
+
logger.info('CORE', 'Enabling courtroom');
|
|
184
175
|
this.enabled = true;
|
|
185
|
-
|
|
176
|
+
this.statusManager.update({ running: true });
|
|
186
177
|
}
|
|
187
178
|
|
|
188
179
|
/**
|
|
189
180
|
* Shutdown courtroom
|
|
190
181
|
*/
|
|
191
182
|
async shutdown() {
|
|
183
|
+
logger.info('CORE', 'Shutting down courtroom');
|
|
192
184
|
this.enabled = false;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await this.punishment.revokeAllPunishments();
|
|
196
|
-
|
|
197
|
-
// Unregister hooks
|
|
198
|
-
this.agent.autonomy.unregisterHook('courtroom_evaluation');
|
|
199
|
-
|
|
200
|
-
return { status: 'shutdown' };
|
|
185
|
+
this.statusManager.update({ running: false, initialized: false });
|
|
186
|
+
StatusManager.clear();
|
|
201
187
|
}
|
|
202
188
|
|
|
203
189
|
/**
|
|
@@ -206,25 +192,15 @@ ${v.sentence}
|
|
|
206
192
|
getStatus() {
|
|
207
193
|
return {
|
|
208
194
|
enabled: this.enabled,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
* Get case statistics
|
|
219
|
-
*/
|
|
220
|
-
getStatistics() {
|
|
221
|
-
return {
|
|
222
|
-
totalEvaluations: this.evaluationCount,
|
|
223
|
-
totalCases: this.caseCount,
|
|
224
|
-
convictionRate: this.caseCount > 0 ?
|
|
225
|
-
(this.punishment.punishmentHistory.length / this.caseCount) : 0,
|
|
226
|
-
activePunishments: this.punishment.getStatus().activeCount,
|
|
227
|
-
pendingSubmissions: this.api.getStatus().pending
|
|
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
|
+
}
|
|
228
204
|
};
|
|
229
205
|
}
|
|
230
206
|
}
|
package/src/daemon.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
};
|