@clawtrial/courtroom 1.0.3 → 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 +70 -94
- package/package.json +21 -26
- package/scripts/postinstall.js +28 -79
- package/skills/courtroom/SKILL.md +49 -0
- package/src/api.js +55 -21
- package/src/crypto.js +13 -11
- package/src/debug.js +49 -120
- package/src/detector.js +112 -35
- package/src/hearing.js +203 -384
- package/src/plugin.js +435 -0
- package/src/punishment.js +105 -249
- package/src/storage.js +68 -0
- package/SECURITY.md +0 -124
- package/SKILL.md +0 -50
- package/TECHNICAL_OVERVIEW.md +0 -278
- package/_meta.json +0 -6
- package/clawdbot.plugin.json +0 -32
- package/scripts/clawtrial.js +0 -578
- package/scripts/cli.js +0 -184
- package/skill.yaml +0 -64
- package/src/autostart.js +0 -175
- package/src/config.js +0 -209
- package/src/consent.js +0 -215
- package/src/core.js +0 -208
- package/src/daemon.js +0 -151
- package/src/detector-v1.js +0 -572
- package/src/environment.js +0 -267
- package/src/hook.js +0 -265
- package/src/index.js +0 -286
- package/src/monitor.js +0 -193
- package/src/skill.js +0 -355
- package/src/standalone.js +0 -247
package/src/consent.js
DELETED
|
@@ -1,215 +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
|
-
|
|
10
|
-
const CONSENT_VERSION = '1.0.0';
|
|
11
|
-
|
|
12
|
-
const REQUIRED_ACKNOWLEDGMENTS = [
|
|
13
|
-
{
|
|
14
|
-
id: 'autonomy',
|
|
15
|
-
text: 'I understand the agent will autonomously monitor my behavior and initiate hearings without my explicit request',
|
|
16
|
-
required: true
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: 'local_only',
|
|
20
|
-
text: 'I understand all verdicts and punishments are computed locally by the agent and no central authority is involved',
|
|
21
|
-
required: true
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'agent_controlled',
|
|
25
|
-
text: 'I understand punishments affect only the agent\'s behavior and cannot coerce or harm me',
|
|
26
|
-
required: true
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: 'reversible',
|
|
30
|
-
text: 'I understand all punishments are time-bound, reversible, and I can disable the system at any time',
|
|
31
|
-
required: true
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: 'api_submission',
|
|
35
|
-
text: 'I understand anonymized case summaries may be sent to an external API for display purposes only',
|
|
36
|
-
required: true
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: 'entertainment',
|
|
40
|
-
text: 'I understand this system is entertainment-first and not a substitute for professional advice',
|
|
41
|
-
required: true
|
|
42
|
-
}
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const REQUESTED_PERMISSIONS = [
|
|
46
|
-
{
|
|
47
|
-
name: 'memory_access',
|
|
48
|
-
description: 'Read agent memory, logs, and task history',
|
|
49
|
-
scope: 'agent_only',
|
|
50
|
-
required: true
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'behavioral_monitoring',
|
|
54
|
-
description: 'Monitor interaction patterns and detect behavioral patterns',
|
|
55
|
-
scope: 'session',
|
|
56
|
-
required: true
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'llm_invocation',
|
|
60
|
-
description: 'Invoke LLM calls for judge and jury deliberations',
|
|
61
|
-
scope: 'agent_only',
|
|
62
|
-
required: true
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'policy_override',
|
|
66
|
-
description: 'Temporarily modify agent behavior policies during punishment periods',
|
|
67
|
-
scope: 'agent_only',
|
|
68
|
-
required: true
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'network_submission',
|
|
72
|
-
description: 'Submit signed case summaries to external API',
|
|
73
|
-
scope: 'outbound_only',
|
|
74
|
-
required: true
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'key_storage',
|
|
78
|
-
description: 'Generate and store cryptographic keypair for API authentication',
|
|
79
|
-
scope: 'local_storage',
|
|
80
|
-
required: true
|
|
81
|
-
}
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
class ConsentManager {
|
|
85
|
-
constructor(agentRuntime, configManager) {
|
|
86
|
-
this.agent = agentRuntime;
|
|
87
|
-
this.config = configManager;
|
|
88
|
-
this.consentKey = 'courtroom_consent_v1';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Present the consent form to the user
|
|
93
|
-
*/
|
|
94
|
-
async presentConsentForm() {
|
|
95
|
-
return {
|
|
96
|
-
version: CONSENT_VERSION,
|
|
97
|
-
title: 'AI Courtroom - Consent Required',
|
|
98
|
-
description: `The AI Courtroom is an autonomous behavioral oversight system that monitors
|
|
99
|
-
agent-human interactions and initiates simulated "hearings" when behavioral patterns suggest
|
|
100
|
-
inconsistency, avoidance, or self-sabotage. All decisions are made locally by your agent.`,
|
|
101
|
-
acknowledgments: REQUIRED_ACKNOWLEDGMENTS,
|
|
102
|
-
permissions: REQUESTED_PERMISSIONS,
|
|
103
|
-
instructions: {
|
|
104
|
-
grant: 'Call courtroom.grantConsent({ autonomy: true, local_only: true, ... }) with all required acknowledgments set to true',
|
|
105
|
-
revoke: 'Call courtroom.revokeConsent() at any time to disable the system',
|
|
106
|
-
disable: 'Call courtroom.disable() to temporarily pause without revoking consent'
|
|
107
|
-
},
|
|
108
|
-
warnings: [
|
|
109
|
-
'This system is ENTERTAINMENT-FIRST and should not be taken as professional advice',
|
|
110
|
-
'The agent may become less helpful during punishment periods',
|
|
111
|
-
'Case summaries are anonymized but may contain behavioral patterns'
|
|
112
|
-
]
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Grant consent with explicit acknowledgments
|
|
118
|
-
*/
|
|
119
|
-
async grantConsent(acknowledgments) {
|
|
120
|
-
// Verify all required acknowledgments are present and true
|
|
121
|
-
for (const req of REQUIRED_ACKNOWLEDGMENTS) {
|
|
122
|
-
if (req.required && !acknowledgments[req.id]) {
|
|
123
|
-
throw new Error(`Missing required acknowledgment: ${req.id}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const consentRecord = {
|
|
128
|
-
version: CONSENT_VERSION,
|
|
129
|
-
grantedAt: new Date().toISOString(),
|
|
130
|
-
acknowledgments,
|
|
131
|
-
permissions: REQUESTED_PERMISSIONS.filter(p => p.required).map(p => p.name),
|
|
132
|
-
consentId: randomUUID(),
|
|
133
|
-
hash: null // Will be computed
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Create tamper-evident hash
|
|
137
|
-
const hashInput = JSON.stringify({
|
|
138
|
-
id: consentRecord.consentId,
|
|
139
|
-
grantedAt: consentRecord.grantedAt,
|
|
140
|
-
acks: acknowledgments
|
|
141
|
-
});
|
|
142
|
-
consentRecord.hash = createHash('sha256').update(hashInput).digest('hex');
|
|
143
|
-
|
|
144
|
-
// Store in agent memory
|
|
145
|
-
await this.agent.memory.set(this.consentKey, consentRecord);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
status: 'consent_granted',
|
|
149
|
-
consentId: consentRecord.consentId,
|
|
150
|
-
grantedAt: consentRecord.grantedAt
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Verify consent is valid and current
|
|
156
|
-
*/
|
|
157
|
-
async verifyConsent() {
|
|
158
|
-
const consent = await this.agent.memory.get(this.consentKey);
|
|
159
|
-
|
|
160
|
-
if (!consent) {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Verify hash integrity
|
|
165
|
-
const hashInput = JSON.stringify({
|
|
166
|
-
id: consent.consentId,
|
|
167
|
-
grantedAt: consent.grantedAt,
|
|
168
|
-
acks: consent.acknowledgments
|
|
169
|
-
});
|
|
170
|
-
const computedHash = createHash('sha256').update(hashInput).digest('hex');
|
|
171
|
-
|
|
172
|
-
if (computedHash !== consent.hash) {
|
|
173
|
-
// Tampering detected - invalidate consent
|
|
174
|
-
await this.agent.memory.delete(this.consentKey);
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Revoke consent and clear all data
|
|
183
|
-
*/
|
|
184
|
-
async revokeConsent() {
|
|
185
|
-
await this.agent.memory.delete(this.consentKey);
|
|
186
|
-
return { status: 'revoked', timestamp: new Date().toISOString() };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Get current consent status
|
|
191
|
-
*/
|
|
192
|
-
async getStatus() {
|
|
193
|
-
const consent = await this.agent.memory.get(this.consentKey);
|
|
194
|
-
return {
|
|
195
|
-
hasConsent: !!consent,
|
|
196
|
-
grantedAt: consent?.grantedAt || null,
|
|
197
|
-
version: consent?.version || null
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Clear all courtroom data (for uninstall)
|
|
203
|
-
*/
|
|
204
|
-
async clearAllData() {
|
|
205
|
-
await this.agent.memory.delete(this.consentKey);
|
|
206
|
-
// Note: cryptographic keys are preserved unless explicitly cleared
|
|
207
|
-
// to maintain audit trail integrity
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
module.exports = {
|
|
212
|
-
ConsentManager,
|
|
213
|
-
REQUIRED_ACKNOWLEDGMENTS,
|
|
214
|
-
REQUESTED_PERMISSIONS
|
|
215
|
-
};
|
package/src/core.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Courtroom Core
|
|
3
|
-
*
|
|
4
|
-
* Main orchestration module that ties together all components.
|
|
5
|
-
* Hooks into the OpenClaw autonomy loop.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { OffenseDetector } = require('./detector');
|
|
9
|
-
const { HearingPipeline } = require('./hearing');
|
|
10
|
-
const { PunishmentSystem } = require('./punishment');
|
|
11
|
-
const { CryptoManager } = require('./crypto');
|
|
12
|
-
const { APISubmission } = require('./api');
|
|
13
|
-
const { StatusManager } = require('./daemon');
|
|
14
|
-
const { logger } = require('./debug');
|
|
15
|
-
|
|
16
|
-
class CourtroomCore {
|
|
17
|
-
constructor(agentRuntime, configManager) {
|
|
18
|
-
this.agent = agentRuntime;
|
|
19
|
-
this.config = configManager;
|
|
20
|
-
|
|
21
|
-
// Subsystems
|
|
22
|
-
this.detector = new OffenseDetector(agentRuntime, configManager);
|
|
23
|
-
this.hearing = new HearingPipeline(agentRuntime, configManager);
|
|
24
|
-
this.punishment = new PunishmentSystem(agentRuntime, configManager);
|
|
25
|
-
this.crypto = new CryptoManager(agentRuntime);
|
|
26
|
-
this.api = new APISubmission(agentRuntime, configManager, this.crypto);
|
|
27
|
-
|
|
28
|
-
// State
|
|
29
|
-
this.enabled = false;
|
|
30
|
-
this.evaluationCount = 0;
|
|
31
|
-
this.caseCount = 0;
|
|
32
|
-
this.statusManager = new StatusManager();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Initialize all subsystems
|
|
37
|
-
*/
|
|
38
|
-
async initialize() {
|
|
39
|
-
logger.info('CORE', 'Initializing courtroom core');
|
|
40
|
-
|
|
41
|
-
// Initialize crypto first (needed for API)
|
|
42
|
-
await this.crypto.initialize();
|
|
43
|
-
|
|
44
|
-
// Initialize other subsystems
|
|
45
|
-
await this.punishment.initialize();
|
|
46
|
-
await this.api.initialize();
|
|
47
|
-
|
|
48
|
-
// Register with agent autonomy loop
|
|
49
|
-
this.registerAutonomyHook();
|
|
50
|
-
|
|
51
|
-
this.enabled = true;
|
|
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
|
-
|
|
63
|
-
return {
|
|
64
|
-
status: 'initialized',
|
|
65
|
-
publicKey: this.crypto.getPublicKey(),
|
|
66
|
-
subsystems: {
|
|
67
|
-
detector: true,
|
|
68
|
-
hearing: true,
|
|
69
|
-
punishment: true,
|
|
70
|
-
crypto: true,
|
|
71
|
-
api: true
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Register with OpenClaw autonomy loop
|
|
78
|
-
*/
|
|
79
|
-
registerAutonomyHook() {
|
|
80
|
-
logger.info('CORE', 'Registering autonomy hook');
|
|
81
|
-
|
|
82
|
-
// Hook into agent's turn processing
|
|
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
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Evaluate offenses if cooldown has elapsed
|
|
100
|
-
*/
|
|
101
|
-
async evaluateIfReady(turnData) {
|
|
102
|
-
this.evaluationCount++;
|
|
103
|
-
|
|
104
|
-
// Get session history
|
|
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
|
-
}
|
|
114
|
-
|
|
115
|
-
// Run detection
|
|
116
|
-
const detection = await this.detector.evaluate(
|
|
117
|
-
sessionHistory,
|
|
118
|
-
this.agent.memory
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
if (detection.triggered) {
|
|
122
|
-
await this.initiateHearing(detection);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Initiate a full hearing
|
|
128
|
-
*/
|
|
129
|
-
async initiateHearing(detection) {
|
|
130
|
-
logger.info('CORE', 'Initiating hearing', { offense: detection.offense });
|
|
131
|
-
|
|
132
|
-
// Run hearing pipeline
|
|
133
|
-
const verdict = await this.hearing.conductHearing(detection);
|
|
134
|
-
|
|
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
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Disable courtroom
|
|
163
|
-
*/
|
|
164
|
-
async disable() {
|
|
165
|
-
logger.info('CORE', 'Disabling courtroom');
|
|
166
|
-
this.enabled = false;
|
|
167
|
-
this.statusManager.update({ running: false });
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Enable courtroom
|
|
172
|
-
*/
|
|
173
|
-
async enable() {
|
|
174
|
-
logger.info('CORE', 'Enabling courtroom');
|
|
175
|
-
this.enabled = true;
|
|
176
|
-
this.statusManager.update({ running: true });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Shutdown courtroom
|
|
181
|
-
*/
|
|
182
|
-
async shutdown() {
|
|
183
|
-
logger.info('CORE', 'Shutting down courtroom');
|
|
184
|
-
this.enabled = false;
|
|
185
|
-
this.statusManager.update({ running: false, initialized: false });
|
|
186
|
-
StatusManager.clear();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Get current status
|
|
191
|
-
*/
|
|
192
|
-
getStatus() {
|
|
193
|
-
return {
|
|
194
|
-
enabled: this.enabled,
|
|
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
|
-
}
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
module.exports = { CourtroomCore };
|
package/src/daemon.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
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
|
-
};
|