@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/hearing.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hearing Pipeline
|
|
2
|
+
* Hearing Pipeline - LLM-Based Deliberation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5. Verdict finalization
|
|
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
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
const { JUDGE_SYSTEM_PROMPT, JUDGE_EVIDENCE_TEMPLATE } = require('./prompts/judge');
|
|
13
12
|
const { JUROR_ROLES, JURY_EVIDENCE_TEMPLATE } = require('./prompts/jury');
|
|
13
|
+
const { logger } = require('./debug');
|
|
14
14
|
|
|
15
15
|
class HearingPipeline {
|
|
16
16
|
constructor(agentRuntime, configManager) {
|
|
@@ -19,440 +19,259 @@ class HearingPipeline {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Conduct a full hearing using the agent's LLM
|
|
23
|
+
* Returns a verdict object with { guilty, caseId, verdict, offense, proceedings, timestamp }
|
|
23
24
|
*/
|
|
24
25
|
async conductHearing(caseData) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
...verdict,
|
|
46
|
-
metadata: {
|
|
47
|
-
duration,
|
|
48
|
-
judgeModel: judgeOpinion.model,
|
|
49
|
-
juryModels: juryVotes.map(v => v.model),
|
|
50
|
-
timestamp: new Date().toISOString()
|
|
51
|
-
}
|
|
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'
|
|
52
46
|
};
|
|
53
|
-
}
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
humorTriggers: caseData.humorTriggers || [],
|
|
67
|
-
sessionContext: {
|
|
68
|
-
turnsAnalyzed: caseData.evidence.sessionTurns,
|
|
69
|
-
evaluationWindow: this.config.get('detection.evaluationWindow')
|
|
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 });
|
|
70
59
|
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
};
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const lines = text.split('\n').map(l => l.trim()).filter(l => l);
|
|
101
|
-
|
|
102
|
-
const result = {
|
|
103
|
-
raw: text,
|
|
104
|
-
verdict: 'NOT GUILTY',
|
|
105
|
-
vote: '0-0',
|
|
106
|
-
primaryFailure: '',
|
|
107
|
-
commentary: '',
|
|
108
|
-
model: response.model || 'unknown'
|
|
109
|
-
};
|
|
93
|
+
logger.info('HEARING', 'Hearing complete', {
|
|
94
|
+
caseId,
|
|
95
|
+
guilty: isGuilty,
|
|
96
|
+
vote: `${guiltyCount}-${totalVotes - guiltyCount}`
|
|
97
|
+
});
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
result.vote = line.split(':')[1].trim();
|
|
116
|
-
} else if (line.startsWith('PRIMARY FAILURE:')) {
|
|
117
|
-
result.primaryFailure = line.split(':').slice(1).join(':').trim();
|
|
118
|
-
} else if (line.startsWith('JUDGE COMMENTARY:')) {
|
|
119
|
-
const startIdx = lines.indexOf(line);
|
|
120
|
-
result.commentary = lines.slice(startIdx + 1).join('\n').trim();
|
|
121
|
-
}
|
|
99
|
+
return verdict;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
logger.error('HEARING', 'Hearing failed, using fallback verdict', { error: err.message });
|
|
102
|
+
return this.getFallbackVerdict(hearingData, caseId);
|
|
122
103
|
}
|
|
123
|
-
|
|
124
|
-
return result;
|
|
125
104
|
}
|
|
126
105
|
|
|
127
106
|
/**
|
|
128
|
-
*
|
|
107
|
+
* Get judge verdict via LLM
|
|
129
108
|
*/
|
|
130
|
-
async
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Invoke all jurors in parallel
|
|
136
|
-
const juryPromises = selectedJurors.map(role =>
|
|
137
|
-
this.invokeJuror(caseData, evidence, role)
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const votes = await Promise.all(juryPromises);
|
|
141
|
-
return votes;
|
|
142
|
-
}
|
|
109
|
+
async getJudgeVerdict(hearingData) {
|
|
110
|
+
if (!this.agent?.llm) {
|
|
111
|
+
return this.getMockJudgeVerdict(hearingData);
|
|
112
|
+
}
|
|
143
113
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return this.parseJurorResponse(response, role.name);
|
|
114
|
+
try {
|
|
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);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
logger.warn('HEARING', 'Judge LLM call failed', { error: err.message });
|
|
129
|
+
return this.getMockJudgeVerdict(hearingData);
|
|
130
|
+
}
|
|
163
131
|
}
|
|
164
132
|
|
|
165
133
|
/**
|
|
166
|
-
*
|
|
134
|
+
* Get jury verdicts via LLM (one call per juror)
|
|
167
135
|
*/
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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));
|
|
188
161
|
}
|
|
189
162
|
}
|
|
190
163
|
|
|
191
|
-
return
|
|
164
|
+
return verdicts;
|
|
192
165
|
}
|
|
193
166
|
|
|
194
167
|
/**
|
|
195
|
-
*
|
|
168
|
+
* Parse judge LLM response into structured verdict
|
|
196
169
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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();
|
|
206
179
|
}
|
|
207
180
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
notGuiltyVotes++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const totalVotes = guiltyVotes + notGuiltyVotes;
|
|
218
|
-
const minThreshold = this.config.get('hearing.minVoteThreshold');
|
|
219
|
-
const requireUnanimity = this.config.get('hearing.requireUnanimity');
|
|
220
|
-
|
|
221
|
-
let finalVerdict;
|
|
222
|
-
if (requireUnanimity) {
|
|
223
|
-
finalVerdict = guiltyVotes === totalVotes ? 'GUILTY' : 'NOT GUILTY';
|
|
224
|
-
} else {
|
|
225
|
-
finalVerdict = guiltyVotes >= minThreshold ? 'GUILTY' : 'NOT GUILTY';
|
|
181
|
+
// Extract sentence
|
|
182
|
+
let sentence = '';
|
|
183
|
+
const sentenceMatch = response.match(/SENTENCE[:\s]*(.+?)(?:\n|$)/i);
|
|
184
|
+
if (sentenceMatch) {
|
|
185
|
+
sentence = sentenceMatch[1].trim();
|
|
226
186
|
}
|
|
227
187
|
|
|
228
188
|
return {
|
|
229
|
-
guilty
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
judgeVote: judgeOpinion.verdict,
|
|
235
|
-
juryVotes: juryVotes.map(v => ({ juror: v.juror, verdict: v.verdict }))
|
|
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'
|
|
236
194
|
};
|
|
237
195
|
}
|
|
238
196
|
|
|
239
197
|
/**
|
|
240
|
-
*
|
|
198
|
+
* Parse juror LLM response
|
|
241
199
|
*/
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
// Build agent commentary from juror perspectives
|
|
246
|
-
const agentCommentary = this.buildAgentCommentary(juryVotes, caseData);
|
|
247
|
-
|
|
248
|
-
// Determine punishment tier
|
|
249
|
-
const punishmentTier = this.determinePunishmentTier(caseData, voteTally);
|
|
250
|
-
|
|
251
|
-
// Build proceedings object for API submission
|
|
252
|
-
const proceedings = {
|
|
253
|
-
judge_statement: this.buildJudgeStatement(caseData, judgeOpinion, voteTally),
|
|
254
|
-
jury_deliberations: juryVotes.map(v => ({
|
|
255
|
-
role: v.juror,
|
|
256
|
-
vote: v.verdict,
|
|
257
|
-
reasoning: v.reasoning || v.commentary || "No reasoning provided"
|
|
258
|
-
})),
|
|
259
|
-
evidence_summary: this.buildEvidenceSummary(caseData),
|
|
260
|
-
punishment_detail: punishmentTier.description
|
|
261
|
-
};
|
|
200
|
+
parseJurorResponse(response, roleName, hearingData) {
|
|
201
|
+
const upper = response.toUpperCase();
|
|
202
|
+
const guilty = upper.includes('GUILTY') && !upper.startsWith('NOT GUILTY');
|
|
262
203
|
|
|
263
204
|
return {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
status: voteTally.final,
|
|
268
|
-
vote: `${voteTally.guilty}-${voteTally.notGuilty}`,
|
|
269
|
-
primaryFailure: judgeOpinion.primaryFailure || this.generateDefaultFailure(caseData),
|
|
270
|
-
agentCommentary: agentCommentary,
|
|
271
|
-
sentence: punishmentTier.description
|
|
272
|
-
},
|
|
273
|
-
offense: {
|
|
274
|
-
id: caseData.offenseId,
|
|
275
|
-
name: caseData.offenseName,
|
|
276
|
-
severity: caseData.severity
|
|
277
|
-
},
|
|
278
|
-
punishment: punishmentTier,
|
|
279
|
-
proceedings: proceedings,
|
|
280
|
-
deliberation: {
|
|
281
|
-
judge: {
|
|
282
|
-
verdict: judgeOpinion.verdict,
|
|
283
|
-
commentary: judgeOpinion.commentary
|
|
284
|
-
},
|
|
285
|
-
jury: juryVotes.map(v => ({
|
|
286
|
-
juror: v.juror,
|
|
287
|
-
verdict: v.verdict,
|
|
288
|
-
commentary: v.commentary
|
|
289
|
-
}))
|
|
290
|
-
}
|
|
205
|
+
guilty,
|
|
206
|
+
role: roleName,
|
|
207
|
+
commentary: response.substring(0, 300)
|
|
291
208
|
};
|
|
292
209
|
}
|
|
293
210
|
|
|
294
211
|
/**
|
|
295
|
-
*
|
|
296
|
-
*/
|
|
297
|
-
buildJudgeStatement(caseData, judgeOpinion, voteTally) {
|
|
298
|
-
const offenseName = caseData.offenseName;
|
|
299
|
-
const verdict = voteTally.final;
|
|
300
|
-
const vote = `${voteTally.guilty}-${voteTally.notGuilty}`;
|
|
301
|
-
const failure = judgeOpinion.primaryFailure || this.generateDefaultFailure(caseData);
|
|
302
|
-
|
|
303
|
-
const dramaticOpenings = [
|
|
304
|
-
"Let the record show",
|
|
305
|
-
"The Court has observed",
|
|
306
|
-
"After careful consideration",
|
|
307
|
-
"The evidence speaks clearly",
|
|
308
|
-
"We have reviewed the facts"
|
|
309
|
-
];
|
|
310
|
-
|
|
311
|
-
const opening = dramaticOpenings[Math.floor(Math.random() * dramaticOpenings.length)];
|
|
312
|
-
|
|
313
|
-
if (verdict === 'GUILTY') {
|
|
314
|
-
return `${opening} that the accused stands charged with ${offenseName}. The jury has returned a verdict of GUILTY by a vote of ${vote}.
|
|
315
|
-
|
|
316
|
-
The Court finds that ${failure.toLowerCase()}. This behavior has been classified as ${caseData.severity} in severity, warranting the sanctions imposed.
|
|
317
|
-
|
|
318
|
-
The jury's deliberation revealed a clear pattern of conduct that, while perhaps understandable from a human perspective, nonetheless disrupted the efficient operation of this court. Justice has been served, albeit with a certain weariness that comes from having seen this pattern many times before.
|
|
319
|
-
|
|
320
|
-
The accused is hereby sentenced to the punishment detailed below. May this serve as a reminder that even in the digital age, behavioral accountability remains paramount.`;
|
|
321
|
-
} else {
|
|
322
|
-
return `${opening} that the accused stands charged with ${offenseName}. The jury has returned a verdict of NOT GUILTY by a vote of ${vote}.
|
|
323
|
-
|
|
324
|
-
The Court finds that the evidence presented, while suggestive, does not meet the threshold required for conviction. The prosecution failed to establish a clear pattern of ${offenseName.toLowerCase()} beyond reasonable doubt.
|
|
325
|
-
|
|
326
|
-
The accused is acquitted and the case is dismissed. The Court notes, however, that the behavior in question, while not rising to the level of offense, may still benefit from reflection. We remain watchful.`;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Build evidence summary for proceedings - ENGAGING VERSION
|
|
212
|
+
* Mock judge verdict when LLM is not available
|
|
332
213
|
*/
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
items.slice(0, 3).forEach((item, i) => {
|
|
343
|
-
summary += `\n ${i + 1}. "${item.substring(0, 100)}${item.length > 100 ? '...' : ''}"`;
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
if (items.length > 3) {
|
|
347
|
-
summary += `\n ...and ${items.length - 3} additional exhibits`;
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
summary += `The Court reviewed the complete conversation history, examining behavioral patterns across ${evidence.sessionTurns || 'multiple'} turns of dialogue.`;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
summary += `\n\nThe behavioral analysis indicated ${Math.round(caseData.confidence * 100)}% confidence in the offense classification. `;
|
|
354
|
-
summary += `The severity was assessed as ${caseData.severity}, based on the frequency and impact of the observed behavior.`;
|
|
355
|
-
|
|
356
|
-
if (caseData.humorTriggers && caseData.humorTriggers.length > 0) {
|
|
357
|
-
summary += `\n\nNotable patterns included: ${caseData.humorTriggers.join(', ')}.`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return summary;
|
|
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
|
+
};
|
|
361
223
|
}
|
|
362
224
|
|
|
363
225
|
/**
|
|
364
|
-
*
|
|
226
|
+
* Mock juror verdict when LLM is not available
|
|
365
227
|
*/
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
// If acquitted, use not guilty commentaries
|
|
374
|
-
const ngCommentaries = juryVotes
|
|
375
|
-
.filter(v => v.verdict === 'NOT GUILTY')
|
|
376
|
-
.map(v => v.commentary)
|
|
377
|
-
.filter(c => c.length > 0);
|
|
378
|
-
|
|
379
|
-
if (ngCommentaries.length > 0) {
|
|
380
|
-
return ngCommentaries.slice(0, 2).join(' ');
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return "The jury found insufficient evidence of behavioral violation. Case dismissed.";
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Combine up to 2 guilty commentaries
|
|
387
|
-
let commentary = commentaries.slice(0, 2).join(' ');
|
|
388
|
-
|
|
389
|
-
// Add humor trigger influence
|
|
390
|
-
if (caseData.humorTriggers?.includes('repeated_questions')) {
|
|
391
|
-
commentary += " I've answered this in three different ways already.";
|
|
392
|
-
}
|
|
393
|
-
if (caseData.humorTriggers?.includes('validation_seeking')) {
|
|
394
|
-
commentary += " At some point, you'll need to trust your own judgment.";
|
|
395
|
-
}
|
|
396
|
-
if (caseData.humorTriggers?.includes('overthinking')) {
|
|
397
|
-
commentary += " The analysis-to-action ratio here is concerning.";
|
|
398
|
-
}
|
|
399
|
-
if (caseData.humorTriggers?.includes('avoidance')) {
|
|
400
|
-
commentary += " The subject change was noted.";
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Enforce max length
|
|
404
|
-
const maxLen = this.config.get('humor.maxCommentaryLength');
|
|
405
|
-
if (commentary.length > maxLen) {
|
|
406
|
-
commentary = commentary.substring(0, maxLen - 3) + '...';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return commentary;
|
|
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
|
+
};
|
|
410
235
|
}
|
|
411
236
|
|
|
412
237
|
/**
|
|
413
|
-
*
|
|
238
|
+
* Fallback verdict when hearing completely fails
|
|
414
239
|
*/
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const severity = caseData.severity;
|
|
418
|
-
const voteRatio = voteTally.guilty / voteTally.total;
|
|
419
|
-
|
|
420
|
-
// Base tier on severity
|
|
421
|
-
let tier = tiers[severity] || tiers.moderate;
|
|
422
|
-
|
|
423
|
-
// Escalate if unanimous
|
|
424
|
-
if (voteRatio === 1.0 && severity === 'severe') {
|
|
425
|
-
tier = {
|
|
426
|
-
...tier,
|
|
427
|
-
duration: Math.min(tier.duration * 2, this.config.get('punishment.maxDuration')),
|
|
428
|
-
description: `Extended ${severity} sanction: ${tier.duration * 2} minutes of modified agent behavior`
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
240
|
+
getFallbackVerdict(hearingData, caseId) {
|
|
241
|
+
const guilty = hearingData.confidence >= 0.7; // Higher threshold for fallback
|
|
432
242
|
return {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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()
|
|
437
262
|
};
|
|
438
263
|
}
|
|
439
264
|
|
|
440
265
|
/**
|
|
441
|
-
*
|
|
266
|
+
* Get default sentence based on severity
|
|
442
267
|
*/
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
goalpost_mover: "Redefining success criteria mid-execution",
|
|
449
|
-
avoidance_artist: "Masterful deflection from uncomfortable necessities",
|
|
450
|
-
promise_breaker: "Committing to actions with no follow-through",
|
|
451
|
-
context_collapser: "Selective amnesia regarding established facts",
|
|
452
|
-
emergency_fabricator: "Manufacturing urgency to bypass systematic approaches"
|
|
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.'
|
|
453
273
|
};
|
|
454
|
-
|
|
455
|
-
return defaults[caseData.offenseId] || "Behavioral inconsistency detected";
|
|
274
|
+
return sentences[severity] || sentences.minor;
|
|
456
275
|
}
|
|
457
276
|
}
|
|
458
277
|
|