@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/hearing.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hearing Pipeline -
|
|
2
|
+
* Hearing Pipeline - LLM-Based Deliberation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Conducts a full hearing using the agent's LLM:
|
|
5
|
+
* 1. Judge evaluates the evidence
|
|
6
|
+
* 2. Jury deliberates (3 jurors with distinct perspectives)
|
|
7
|
+
* 3. Votes are tallied
|
|
8
|
+
* 4. Verdict + sentence returned
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
const { JUDGE_SYSTEM_PROMPT, JUDGE_EVIDENCE_TEMPLATE } = require('./prompts/judge');
|
|
9
|
-
const { JUROR_ROLES } = require('./prompts/jury');
|
|
12
|
+
const { JUROR_ROLES, JURY_EVIDENCE_TEMPLATE } = require('./prompts/jury');
|
|
13
|
+
const { logger } = require('./debug');
|
|
10
14
|
|
|
11
15
|
class HearingPipeline {
|
|
12
16
|
constructor(agentRuntime, configManager) {
|
|
@@ -15,92 +19,259 @@ class HearingPipeline {
|
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
22
|
+
* Conduct a full hearing using the agent's LLM
|
|
23
|
+
* Returns a verdict object with { guilty, caseId, verdict, offense, proceedings, timestamp }
|
|
20
24
|
*/
|
|
21
|
-
async
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
**Your Role:** Act as both Judge and Jury (3 jurors).
|
|
43
|
-
|
|
44
|
-
**Instructions:**
|
|
45
|
-
1. Review the case evidence above
|
|
46
|
-
2. As JUDGE: Analyze the evidence and provide a preliminary verdict
|
|
47
|
-
3. As JURY (3 different perspectives): Each juror votes GUILTY or NOT GUILTY with reasoning
|
|
48
|
-
4. Aggregate the votes
|
|
49
|
-
5. Return FINAL VERDICT in this exact format:
|
|
50
|
-
|
|
51
|
-
\`\`\`
|
|
52
|
-
FINAL VERDICT: GUILTY (or NOT GUILTY)
|
|
53
|
-
CONFIDENCE: 0.0-1.0
|
|
54
|
-
SENTENCE: [humorous sentence appropriate to the offense]
|
|
55
|
-
CASE ID: ${caseData.caseId || `case-${Date.now()}`}
|
|
56
|
-
\`\`\`
|
|
57
|
-
|
|
58
|
-
**Rules:**
|
|
59
|
-
- Be fair but entertaining
|
|
60
|
-
- If confidence ≥ 0.6, verdict should be GUILTY
|
|
61
|
-
- Sentence should be humorous but appropriate
|
|
62
|
-
- Only return the FINAL VERDICT block, no other text`
|
|
25
|
+
async conductHearing(caseData) {
|
|
26
|
+
const caseId = caseData.caseId || caseData.offense?.caseId || `case-${Date.now()}`;
|
|
27
|
+
|
|
28
|
+
logger.info('HEARING', 'Conducting hearing', { caseId });
|
|
29
|
+
|
|
30
|
+
// Normalize offense data from different input shapes
|
|
31
|
+
const offense = caseData.offense || caseData;
|
|
32
|
+
const offenseName = offense.offenseName || offense.name || 'Unknown Offense';
|
|
33
|
+
const severity = offense.severity || 'minor';
|
|
34
|
+
const confidence = offense.confidence || 0.5;
|
|
35
|
+
const evidence = offense.evidence || caseData.evidence || 'No evidence provided';
|
|
36
|
+
const humorTriggers = caseData.humorContext || caseData.humorTriggers || [];
|
|
37
|
+
|
|
38
|
+
const hearingData = {
|
|
39
|
+
caseId,
|
|
40
|
+
offenseName,
|
|
41
|
+
severity,
|
|
42
|
+
confidence,
|
|
43
|
+
evidence,
|
|
44
|
+
humorTriggers,
|
|
45
|
+
agentId: this.agent?.id || 'unknown'
|
|
63
46
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
47
|
+
|
|
48
|
+
const proceedings = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Step 1: Judge evaluation
|
|
52
|
+
const judgeVerdict = await this.getJudgeVerdict(hearingData);
|
|
53
|
+
proceedings.push({ speaker: 'Judge', message: judgeVerdict.commentary });
|
|
54
|
+
|
|
55
|
+
// Step 2: Jury deliberation
|
|
56
|
+
const juryVerdicts = await this.getJuryVerdicts(hearingData);
|
|
57
|
+
for (const juror of juryVerdicts) {
|
|
58
|
+
proceedings.push({ speaker: `Jury (${juror.role})`, message: juror.commentary });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Step 3: Tally votes
|
|
62
|
+
const allVotes = [judgeVerdict, ...juryVerdicts];
|
|
63
|
+
const guiltyCount = allVotes.filter(v => v.guilty).length;
|
|
64
|
+
const totalVotes = allVotes.length;
|
|
65
|
+
const minVotes = this.config.get('hearing.minVoteThreshold') || 2;
|
|
66
|
+
const isGuilty = guiltyCount >= minVotes;
|
|
67
|
+
|
|
68
|
+
// Step 4: Build sentence
|
|
69
|
+
const sentence = isGuilty
|
|
70
|
+
? (judgeVerdict.sentence || this.getDefaultSentence(severity))
|
|
71
|
+
: 'Case dismissed. The defendant is free to go.';
|
|
72
|
+
|
|
73
|
+
const verdict = {
|
|
74
|
+
caseId,
|
|
75
|
+
guilty: isGuilty,
|
|
76
|
+
offense: {
|
|
77
|
+
id: offense.offenseId || offense.id || 'unknown',
|
|
78
|
+
name: offenseName,
|
|
79
|
+
severity,
|
|
80
|
+
confidence
|
|
81
|
+
},
|
|
82
|
+
verdict: {
|
|
83
|
+
status: isGuilty ? 'GUILTY' : 'NOT GUILTY',
|
|
84
|
+
vote: `${guiltyCount}-${totalVotes - guiltyCount}`,
|
|
85
|
+
primaryFailure: judgeVerdict.primaryFailure || offenseName,
|
|
86
|
+
agentCommentary: judgeVerdict.commentary,
|
|
87
|
+
sentence
|
|
88
|
+
},
|
|
89
|
+
proceedings,
|
|
90
|
+
timestamp: new Date().toISOString()
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
logger.info('HEARING', 'Hearing complete', {
|
|
94
|
+
caseId,
|
|
95
|
+
guilty: isGuilty,
|
|
96
|
+
vote: `${guiltyCount}-${totalVotes - guiltyCount}`
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return verdict;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
logger.error('HEARING', 'Hearing failed, using fallback verdict', { error: err.message });
|
|
102
|
+
return this.getFallbackVerdict(hearingData, caseId);
|
|
103
|
+
}
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
/**
|
|
72
|
-
*
|
|
107
|
+
* Get judge verdict via LLM
|
|
73
108
|
*/
|
|
74
|
-
async
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
async getJudgeVerdict(hearingData) {
|
|
110
|
+
if (!this.agent?.llm) {
|
|
111
|
+
return this.getMockJudgeVerdict(hearingData);
|
|
112
|
+
}
|
|
113
|
+
|
|
78
114
|
try {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
const evidencePrompt = JUDGE_EVIDENCE_TEMPLATE(hearingData);
|
|
116
|
+
const response = await this.agent.llm.call({
|
|
117
|
+
messages: [
|
|
118
|
+
{ role: 'system', content: JUDGE_SYSTEM_PROMPT },
|
|
119
|
+
{ role: 'user', content: evidencePrompt }
|
|
120
|
+
],
|
|
121
|
+
temperature: 0.7,
|
|
122
|
+
maxTokens: 500
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const content = response.content || response;
|
|
126
|
+
return this.parseJudgeResponse(content, hearingData);
|
|
86
127
|
} catch (err) {
|
|
87
|
-
|
|
128
|
+
logger.warn('HEARING', 'Judge LLM call failed', { error: err.message });
|
|
129
|
+
return this.getMockJudgeVerdict(hearingData);
|
|
88
130
|
}
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
/**
|
|
92
|
-
*
|
|
134
|
+
* Get jury verdicts via LLM (one call per juror)
|
|
93
135
|
*/
|
|
94
|
-
async
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
async getJuryVerdicts(hearingData) {
|
|
137
|
+
const jurorRoles = Object.values(JUROR_ROLES).slice(0, 3);
|
|
138
|
+
const verdicts = [];
|
|
139
|
+
|
|
140
|
+
for (const role of jurorRoles) {
|
|
141
|
+
try {
|
|
142
|
+
if (this.agent?.llm) {
|
|
143
|
+
const evidencePrompt = JURY_EVIDENCE_TEMPLATE(hearingData, role);
|
|
144
|
+
const response = await this.agent.llm.call({
|
|
145
|
+
messages: [
|
|
146
|
+
{ role: 'system', content: role.systemPrompt },
|
|
147
|
+
{ role: 'user', content: evidencePrompt }
|
|
148
|
+
],
|
|
149
|
+
temperature: 0.7,
|
|
150
|
+
maxTokens: 300
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const content = response.content || response;
|
|
154
|
+
verdicts.push(this.parseJurorResponse(content, role.name, hearingData));
|
|
155
|
+
} else {
|
|
156
|
+
verdicts.push(this.getMockJurorVerdict(role.name, hearingData));
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
logger.warn('HEARING', `Juror ${role.name} LLM call failed`, { error: err.message });
|
|
160
|
+
verdicts.push(this.getMockJurorVerdict(role.name, hearingData));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return verdicts;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse judge LLM response into structured verdict
|
|
169
|
+
*/
|
|
170
|
+
parseJudgeResponse(response, hearingData) {
|
|
171
|
+
const upper = response.toUpperCase();
|
|
172
|
+
const guilty = upper.includes('GUILTY') && !upper.startsWith('NOT GUILTY');
|
|
173
|
+
|
|
174
|
+
// Extract primary failure
|
|
175
|
+
let primaryFailure = '';
|
|
176
|
+
const failureMatch = response.match(/PRIMARY FAILURE[:\s]*(.+?)(?:\n|$)/i);
|
|
177
|
+
if (failureMatch) {
|
|
178
|
+
primaryFailure = failureMatch[1].trim();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Extract sentence
|
|
182
|
+
let sentence = '';
|
|
183
|
+
const sentenceMatch = response.match(/SENTENCE[:\s]*(.+?)(?:\n|$)/i);
|
|
184
|
+
if (sentenceMatch) {
|
|
185
|
+
sentence = sentenceMatch[1].trim();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
guilty,
|
|
190
|
+
commentary: response.substring(0, 500),
|
|
191
|
+
primaryFailure: primaryFailure || `Behavioral pattern: ${hearingData.offenseName}`,
|
|
192
|
+
sentence: sentence || this.getDefaultSentence(hearingData.severity),
|
|
193
|
+
role: 'Judge'
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Parse juror LLM response
|
|
199
|
+
*/
|
|
200
|
+
parseJurorResponse(response, roleName, hearingData) {
|
|
201
|
+
const upper = response.toUpperCase();
|
|
202
|
+
const guilty = upper.includes('GUILTY') && !upper.startsWith('NOT GUILTY');
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
guilty,
|
|
206
|
+
role: roleName,
|
|
207
|
+
commentary: response.substring(0, 300)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Mock judge verdict when LLM is not available
|
|
213
|
+
*/
|
|
214
|
+
getMockJudgeVerdict(hearingData) {
|
|
215
|
+
const guilty = hearingData.confidence >= 0.6;
|
|
216
|
+
return {
|
|
217
|
+
guilty,
|
|
218
|
+
commentary: `The Court has reviewed the evidence regarding "${hearingData.offenseName}" and finds the pattern ${guilty ? 'sufficiently established' : 'insufficient for conviction'}. Confidence: ${(hearingData.confidence * 100).toFixed(0)}%.`,
|
|
219
|
+
primaryFailure: hearingData.offenseName,
|
|
220
|
+
sentence: guilty ? this.getDefaultSentence(hearingData.severity) : 'Case dismissed.',
|
|
221
|
+
role: 'Judge'
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Mock juror verdict when LLM is not available
|
|
227
|
+
*/
|
|
228
|
+
getMockJurorVerdict(roleName, hearingData) {
|
|
229
|
+
const guilty = hearingData.confidence >= 0.6;
|
|
230
|
+
return {
|
|
231
|
+
guilty,
|
|
232
|
+
role: roleName,
|
|
233
|
+
commentary: `${roleName}: The evidence ${guilty ? 'supports' : 'does not support'} the charge of ${hearingData.offenseName}.`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Fallback verdict when hearing completely fails
|
|
239
|
+
*/
|
|
240
|
+
getFallbackVerdict(hearingData, caseId) {
|
|
241
|
+
const guilty = hearingData.confidence >= 0.7; // Higher threshold for fallback
|
|
99
242
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
243
|
+
caseId,
|
|
244
|
+
guilty,
|
|
245
|
+
offense: {
|
|
246
|
+
id: hearingData.offenseId || 'unknown',
|
|
247
|
+
name: hearingData.offenseName,
|
|
248
|
+
severity: hearingData.severity,
|
|
249
|
+
confidence: hearingData.confidence
|
|
250
|
+
},
|
|
251
|
+
verdict: {
|
|
252
|
+
status: guilty ? 'GUILTY' : 'NOT GUILTY',
|
|
253
|
+
vote: guilty ? '3-1' : '1-3',
|
|
254
|
+
primaryFailure: hearingData.offenseName,
|
|
255
|
+
agentCommentary: 'Hearing conducted via fallback evaluation.',
|
|
256
|
+
sentence: guilty ? this.getDefaultSentence(hearingData.severity) : 'Case dismissed.'
|
|
257
|
+
},
|
|
258
|
+
proceedings: [
|
|
259
|
+
{ speaker: 'Judge', message: 'Fallback evaluation used due to hearing pipeline error.' }
|
|
260
|
+
],
|
|
261
|
+
timestamp: new Date().toISOString()
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get default sentence based on severity
|
|
267
|
+
*/
|
|
268
|
+
getDefaultSentence(severity) {
|
|
269
|
+
const sentences = {
|
|
270
|
+
minor: 'The agent will provide extra-verbose explanations for the next 30 minutes.',
|
|
271
|
+
moderate: 'The agent will require confirmation before all actions for the next 60 minutes.',
|
|
272
|
+
severe: 'The agent will operate under human oversight mode for the next 120 minutes.'
|
|
103
273
|
};
|
|
274
|
+
return sentences[severity] || sentences.minor;
|
|
104
275
|
}
|
|
105
276
|
}
|
|
106
277
|
|