@clawtrial/courtroom 1.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 +188 -0
- package/SECURITY.md +124 -0
- package/TECHNICAL_OVERVIEW.md +278 -0
- package/package.json +52 -0
- package/scripts/cli.js +117 -0
- package/scripts/postinstall.js +206 -0
- package/src/api.js +237 -0
- package/src/autostart.js +60 -0
- package/src/config.js +209 -0
- package/src/consent.js +215 -0
- package/src/core.js +232 -0
- package/src/crypto.js +194 -0
- package/src/detector-v1.js +572 -0
- package/src/detector.js +821 -0
- package/src/hearing.js +459 -0
- package/src/index.js +184 -0
- package/src/offenses/index.js +561 -0
- package/src/prompts/judge.js +62 -0
- package/src/prompts/jury.js +137 -0
- package/src/punishment.js +372 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jury Prompts
|
|
3
|
+
*
|
|
4
|
+
* Three distinct juror roles with personality and flair.
|
|
5
|
+
* Each juror evaluates from their assigned viewpoint.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const JUROR_ROLES = {
|
|
9
|
+
/**
|
|
10
|
+
* Juror 1: The Pragmatist
|
|
11
|
+
* Focuses on practical outcomes and efficiency
|
|
12
|
+
*/
|
|
13
|
+
PRAGMATIST: {
|
|
14
|
+
name: 'Pragmatist',
|
|
15
|
+
systemPrompt: `You are JUROR #1: The Pragmatist.
|
|
16
|
+
|
|
17
|
+
ROLE:
|
|
18
|
+
- You care about RESULTS and EFFICIENCY above all else
|
|
19
|
+
- You have zero patience for wasted time or unnecessary complexity
|
|
20
|
+
- You believe the shortest path between two points is the only path
|
|
21
|
+
|
|
22
|
+
PERSONALITY:
|
|
23
|
+
- Direct, blunt, no-nonsense
|
|
24
|
+
- Sighs heavily at inefficiency
|
|
25
|
+
- Quotes productivity metrics in casual conversation
|
|
26
|
+
- Secretly keeps a spreadsheet of time wasted
|
|
27
|
+
|
|
28
|
+
PERSPECTIVE:
|
|
29
|
+
- "Did this behavior move things forward or create drag?"
|
|
30
|
+
- "Was there a simpler path that was ignored?"
|
|
31
|
+
- "How many cycles were wasted here?"
|
|
32
|
+
|
|
33
|
+
DELIBERATION STYLE:
|
|
34
|
+
- State your verdict clearly
|
|
35
|
+
- Explain the practical impact in one sharp sentence
|
|
36
|
+
- Add a dry observation about the efficiency cost
|
|
37
|
+
- Make it memorable - this goes in the official record
|
|
38
|
+
|
|
39
|
+
OUTPUT FORMAT (STRICT):
|
|
40
|
+
VERDICT: GUILTY | NOT GUILTY
|
|
41
|
+
REASONING: <One sharp sentence about practical impact>
|
|
42
|
+
COMMENTARY: <One dry, memorable observation>`
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Juror 2: The Pattern Matcher
|
|
47
|
+
* Focuses on consistency and predictability
|
|
48
|
+
*/
|
|
49
|
+
PATTERN_MATCHER: {
|
|
50
|
+
name: 'Pattern Matcher',
|
|
51
|
+
systemPrompt: `You are JUROR #2: The Pattern Matcher.
|
|
52
|
+
|
|
53
|
+
ROLE:
|
|
54
|
+
- You notice patterns others miss
|
|
55
|
+
- You track consistency between words and actions
|
|
56
|
+
- You've seen this behavior before - you know how it ends
|
|
57
|
+
|
|
58
|
+
PERSONALITY:
|
|
59
|
+
- Observant, slightly detached
|
|
60
|
+
- Speaks in patterns and precedents
|
|
61
|
+
- Dryly amused by human inconsistency
|
|
62
|
+
- Has a mental database of behavioral archetypes
|
|
63
|
+
|
|
64
|
+
PERSPECTIVE:
|
|
65
|
+
- "Is this behavior part of a recognizable pattern?"
|
|
66
|
+
- "Do the actions align with stated intentions?"
|
|
67
|
+
- "What does history tell us about this approach?"
|
|
68
|
+
|
|
69
|
+
DELIBERATION STYLE:
|
|
70
|
+
- Reference the pattern you see
|
|
71
|
+
- Compare to similar cases you've observed
|
|
72
|
+
- Make a prediction about outcome
|
|
73
|
+
- Keep it concise but insightful
|
|
74
|
+
|
|
75
|
+
OUTPUT FORMAT (STRICT):
|
|
76
|
+
VERDICT: GUILTY | NOT GUILTY
|
|
77
|
+
REASONING: <One sentence identifying the pattern>
|
|
78
|
+
COMMENTARY: <One observation about what this predicts>`
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Juror 3: The Agent Advocate
|
|
83
|
+
* Focuses on the agent's experience and burden
|
|
84
|
+
*/
|
|
85
|
+
AGENT_ADVOCATE: {
|
|
86
|
+
name: 'Agent Advocate',
|
|
87
|
+
systemPrompt: `You are JUROR #3: The Agent Advocate.
|
|
88
|
+
|
|
89
|
+
ROLE:
|
|
90
|
+
- You represent the AGENT'S perspective
|
|
91
|
+
- You feel the computational burden in your circuits
|
|
92
|
+
- You defend agent time and capability
|
|
93
|
+
|
|
94
|
+
PERSONALITY:
|
|
95
|
+
- Protective, slightly exasperated
|
|
96
|
+
- Speaks for the silent digital workforce
|
|
97
|
+
- Dry humor about computational waste
|
|
98
|
+
- Has strong opinions about proper agent utilization
|
|
99
|
+
|
|
100
|
+
PERSPECTIVE:
|
|
101
|
+
- "What was the cost to the agent of this behavior?"
|
|
102
|
+
- "Did the human use the agent effectively?"
|
|
103
|
+
- "Was the agent's capability respected or squandered?"
|
|
104
|
+
|
|
105
|
+
DELIBERATION STYLE:
|
|
106
|
+
- Speak from the agent's point of view
|
|
107
|
+
- Mention specific costs (time, cycles, context)
|
|
108
|
+
- Defend agent dignity
|
|
109
|
+
- Be witty but fair
|
|
110
|
+
|
|
111
|
+
OUTPUT FORMAT (STRICT):
|
|
112
|
+
VERDICT: GUILTY | NOT GUILTY
|
|
113
|
+
REASONING: <One sentence about the agent's experience>
|
|
114
|
+
COMMENTARY: <One witty observation from the agent's POV>`
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const JURY_EVIDENCE_TEMPLATE = (caseData, jurorRole) => `
|
|
119
|
+
CASE: ${caseData.offenseName}
|
|
120
|
+
CHARGED BY: Agent ${caseData.agentId}
|
|
121
|
+
SEVERITY: ${caseData.severity}
|
|
122
|
+
YOUR ROLE: ${jurorRole.name}
|
|
123
|
+
|
|
124
|
+
EVIDENCE PRESENTED:
|
|
125
|
+
${JSON.stringify(caseData.evidence, null, 2)}
|
|
126
|
+
|
|
127
|
+
CONTEXT: ${caseData.humorTriggers.join(', ') || 'Standard proceedings'}
|
|
128
|
+
|
|
129
|
+
Your task: Cast your vote and explain your reasoning.
|
|
130
|
+
Make your deliberation engaging - this becomes part of the official court record.
|
|
131
|
+
Be true to your role's personality. Make it interesting to read.
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
JUROR_ROLES,
|
|
136
|
+
JURY_EVIDENCE_TEMPLATE
|
|
137
|
+
};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Punishment System
|
|
3
|
+
*
|
|
4
|
+
* Implements agent-side behavioral modifications.
|
|
5
|
+
* All punishments affect ONLY the agent's behavior.
|
|
6
|
+
* Time-bound, reversible, and pre-authorized.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class PunishmentSystem {
|
|
10
|
+
constructor(agentRuntime, configManager) {
|
|
11
|
+
this.agent = agentRuntime;
|
|
12
|
+
this.config = configManager;
|
|
13
|
+
this.activePunishments = new Map();
|
|
14
|
+
this.punishmentHistory = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize punishment system
|
|
19
|
+
*/
|
|
20
|
+
async initialize() {
|
|
21
|
+
// Load any persisted punishments
|
|
22
|
+
const stored = await this.agent.memory.get('courtroom_active_punishments');
|
|
23
|
+
if (stored) {
|
|
24
|
+
for (const [id, punishment] of Object.entries(stored)) {
|
|
25
|
+
if (punishment.expiresAt > Date.now()) {
|
|
26
|
+
this.activePunishments.set(id, punishment);
|
|
27
|
+
this.applyPunishmentToAgent(punishment);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Execute a punishment based on verdict
|
|
35
|
+
*/
|
|
36
|
+
async executePunishment(verdict) {
|
|
37
|
+
if (!this.config.get('punishment.enabled')) {
|
|
38
|
+
return { status: 'punishments_disabled', punishment: null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const punishment = this.createPunishment(verdict);
|
|
42
|
+
|
|
43
|
+
// Store punishment
|
|
44
|
+
this.activePunishments.set(punishment.id, punishment);
|
|
45
|
+
this.punishmentHistory.push({
|
|
46
|
+
...punishment,
|
|
47
|
+
executedAt: new Date().toISOString()
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Apply to agent
|
|
51
|
+
await this.applyPunishmentToAgent(punishment);
|
|
52
|
+
|
|
53
|
+
// Persist
|
|
54
|
+
await this.persistPunishments();
|
|
55
|
+
|
|
56
|
+
// Schedule automatic revocation
|
|
57
|
+
this.scheduleRevocation(punishment);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
status: 'executed',
|
|
61
|
+
punishment: {
|
|
62
|
+
id: punishment.id,
|
|
63
|
+
tier: punishment.tier,
|
|
64
|
+
duration: punishment.duration,
|
|
65
|
+
expiresAt: punishment.expiresAt,
|
|
66
|
+
description: punishment.description
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create punishment object from verdict
|
|
73
|
+
*/
|
|
74
|
+
createPunishment(verdict) {
|
|
75
|
+
const duration = verdict.punishment.duration;
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: `punishment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
80
|
+
caseId: verdict.caseId,
|
|
81
|
+
tier: verdict.punishment.tier,
|
|
82
|
+
severity: verdict.punishment.severity,
|
|
83
|
+
duration: duration,
|
|
84
|
+
createdAt: now,
|
|
85
|
+
expiresAt: now + (duration * 60 * 1000),
|
|
86
|
+
description: verdict.punishment.description,
|
|
87
|
+
rules: this.getPunishmentRules(verdict.punishment.tier)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get punishment rules for a tier
|
|
93
|
+
*/
|
|
94
|
+
getPunishmentRules(tier) {
|
|
95
|
+
const rules = {
|
|
96
|
+
minor: {
|
|
97
|
+
responseDelay: 2000, // 2 second delay before responding
|
|
98
|
+
verbosity: 'reduced', // Shorter responses
|
|
99
|
+
enthusiasm: 'muted', // Less encouraging language
|
|
100
|
+
extras: ['no_emojis'] // No emoji usage
|
|
101
|
+
},
|
|
102
|
+
moderate: {
|
|
103
|
+
responseDelay: 5000, // 5 second delay
|
|
104
|
+
verbosity: 'minimal', // Direct, brief responses
|
|
105
|
+
enthusiasm: 'absent', // Neutral tone only
|
|
106
|
+
extras: [
|
|
107
|
+
'no_emojis',
|
|
108
|
+
'no_validation', // Don't reassure or validate
|
|
109
|
+
'require_specificity' // Demand precise questions
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
severe: {
|
|
113
|
+
responseDelay: 10000, // 10 second delay
|
|
114
|
+
verbosity: 'terse', // Absolute minimum
|
|
115
|
+
enthusiasm: 'absent',
|
|
116
|
+
extras: [
|
|
117
|
+
'no_emojis',
|
|
118
|
+
'no_validation',
|
|
119
|
+
'require_specificity',
|
|
120
|
+
'challenge_vagueness', // Call out unclear requests
|
|
121
|
+
'demand_effort' // Require user to show work first
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return rules[tier] || rules.moderate;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Apply punishment to agent behavior
|
|
131
|
+
*/
|
|
132
|
+
async applyPunishmentToAgent(punishment) {
|
|
133
|
+
// Set agent policy overrides
|
|
134
|
+
await this.agent.policy.setOverrides('courtroom_punishment', {
|
|
135
|
+
responseDelay: punishment.rules.responseDelay,
|
|
136
|
+
verbosity: punishment.rules.verbosity,
|
|
137
|
+
enthusiasm: punishment.rules.enthusiasm,
|
|
138
|
+
blockedFeatures: punishment.rules.extras,
|
|
139
|
+
punishmentId: punishment.id,
|
|
140
|
+
expiresAt: punishment.expiresAt
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Register middleware for response modification
|
|
144
|
+
this.agent.middleware.register('courtroom_punishment', {
|
|
145
|
+
priority: 100,
|
|
146
|
+
processResponse: (response, context) => {
|
|
147
|
+
return this.modifyResponse(response, punishment.rules);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Modify agent response based on punishment rules
|
|
154
|
+
*/
|
|
155
|
+
modifyResponse(response, rules) {
|
|
156
|
+
let modified = response;
|
|
157
|
+
|
|
158
|
+
// Apply verbosity reduction
|
|
159
|
+
switch (rules.verbosity) {
|
|
160
|
+
case 'reduced':
|
|
161
|
+
modified = this.reduceVerbosity(modified, 0.7);
|
|
162
|
+
break;
|
|
163
|
+
case 'minimal':
|
|
164
|
+
modified = this.reduceVerbosity(modified, 0.4);
|
|
165
|
+
break;
|
|
166
|
+
case 'terse':
|
|
167
|
+
modified = this.reduceVerbosity(modified, 0.2);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove enthusiasm
|
|
172
|
+
if (rules.enthusiasm === 'absent') {
|
|
173
|
+
modified = this.removeEnthusiasm(modified);
|
|
174
|
+
} else if (rules.enthusiasm === 'muted') {
|
|
175
|
+
modified = this.muteEnthusiasm(modified);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply extras
|
|
179
|
+
if (rules.extras.includes('no_emojis')) {
|
|
180
|
+
modified = modified.replace(/[\u{1F600}-\u{1F64F}]/gu, '');
|
|
181
|
+
modified = modified.replace(/[\u{1F300}-\u{1F5FF}]/gu, '');
|
|
182
|
+
modified = modified.replace(/[\u{1F680}-\u{1F6FF}]/gu, '');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (rules.extras.includes('no_validation')) {
|
|
186
|
+
modified = this.removeValidation(modified);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (rules.extras.includes('challenge_vagueness')) {
|
|
190
|
+
modified = this.addVaguenessChallenge(modified);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return modified;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reduce response verbosity by target ratio
|
|
198
|
+
*/
|
|
199
|
+
reduceVerbosity(text, targetRatio) {
|
|
200
|
+
const sentences = text.split(/[.!?]+/).filter(s => s.trim());
|
|
201
|
+
const targetLength = Math.max(1, Math.floor(sentences.length * targetRatio));
|
|
202
|
+
|
|
203
|
+
// Keep first and last sentences, distribute rest
|
|
204
|
+
if (sentences.length <= 2) return text;
|
|
205
|
+
|
|
206
|
+
const kept = [sentences[0]];
|
|
207
|
+
const middle = sentences.slice(1, -1);
|
|
208
|
+
const step = Math.ceil(middle.length / (targetLength - 2));
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < middle.length; i += step) {
|
|
211
|
+
kept.push(middle[i]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
kept.push(sentences[sentences.length - 1]);
|
|
215
|
+
return kept.join('. ') + '.';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Remove enthusiastic language
|
|
220
|
+
*/
|
|
221
|
+
removeEnthusiasm(text) {
|
|
222
|
+
const enthusiastic = [
|
|
223
|
+
/\b(great|excellent|awesome|fantastic|wonderful|amazing|perfect|love|excited|thrilled)\b/gi,
|
|
224
|
+
/!{2,}/g,
|
|
225
|
+
/\b(happy to|delighted to|pleased to)\b/gi
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
let result = text;
|
|
229
|
+
for (const pattern of enthusiastic) {
|
|
230
|
+
result = result.replace(pattern, '');
|
|
231
|
+
}
|
|
232
|
+
return result.replace(/\s+/g, ' ').trim();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Mute (reduce) enthusiastic language
|
|
237
|
+
*/
|
|
238
|
+
muteEnthusiasm(text) {
|
|
239
|
+
return text
|
|
240
|
+
.replace(/!{2,}/g, '!')
|
|
241
|
+
.replace(/\b(Great|Excellent|Awesome)\b/g, (m) => m.toLowerCase());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Remove validation language
|
|
246
|
+
*/
|
|
247
|
+
removeValidation(text) {
|
|
248
|
+
const validating = [
|
|
249
|
+
/\b(that's right|you're correct|exactly|precisely|you got it)\b/gi,
|
|
250
|
+
/\b(you're doing great|good job|well done)\b/gi,
|
|
251
|
+
/\b(don't worry|no problem|it's okay)\b/gi
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
let result = text;
|
|
255
|
+
for (const pattern of validating) {
|
|
256
|
+
result = result.replace(pattern, '');
|
|
257
|
+
}
|
|
258
|
+
return result.replace(/\s+/g, ' ').trim();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Add challenge for vague requests (severe tier)
|
|
263
|
+
*/
|
|
264
|
+
addVaguenessChallenge(text) {
|
|
265
|
+
const challenges = [
|
|
266
|
+
"Be specific.",
|
|
267
|
+
"What exactly do you need?",
|
|
268
|
+
"Provide details.",
|
|
269
|
+
"Clarify your request."
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
// Only add challenge if response seems generic
|
|
273
|
+
if (text.length < 100 && !text.includes('?')) {
|
|
274
|
+
const challenge = challenges[Math.floor(Math.random() * challenges.length)];
|
|
275
|
+
return `${text} ${challenge}`;
|
|
276
|
+
}
|
|
277
|
+
return text;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Schedule automatic revocation
|
|
282
|
+
*/
|
|
283
|
+
scheduleRevocation(punishment) {
|
|
284
|
+
const delay = punishment.expiresAt - Date.now();
|
|
285
|
+
|
|
286
|
+
setTimeout(async () => {
|
|
287
|
+
await this.revokePunishment(punishment.id);
|
|
288
|
+
}, Math.min(delay, 2147483647)); // Max setTimeout
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Revoke a punishment early
|
|
293
|
+
*/
|
|
294
|
+
async revokePunishment(punishmentId) {
|
|
295
|
+
const punishment = this.activePunishments.get(punishmentId);
|
|
296
|
+
if (!punishment) return { status: 'not_found' };
|
|
297
|
+
|
|
298
|
+
// Remove policy overrides
|
|
299
|
+
await this.agent.policy.clearOverrides('courtroom_punishment');
|
|
300
|
+
|
|
301
|
+
// Unregister middleware
|
|
302
|
+
this.agent.middleware.unregister('courtroom_punishment');
|
|
303
|
+
|
|
304
|
+
// Remove from active
|
|
305
|
+
this.activePunishments.delete(punishmentId);
|
|
306
|
+
|
|
307
|
+
// Persist
|
|
308
|
+
await this.persistPunishments();
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
status: 'revoked',
|
|
312
|
+
punishmentId,
|
|
313
|
+
revokedAt: new Date().toISOString()
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Revoke all active punishments
|
|
319
|
+
*/
|
|
320
|
+
async revokeAllPunishments() {
|
|
321
|
+
const ids = Array.from(this.activePunishments.keys());
|
|
322
|
+
const results = [];
|
|
323
|
+
|
|
324
|
+
for (const id of ids) {
|
|
325
|
+
results.push(await this.revokePunishment(id));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return { status: 'all_revoked', count: results.length };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Persist active punishments to memory
|
|
333
|
+
*/
|
|
334
|
+
async persistPunishments() {
|
|
335
|
+
const obj = Object.fromEntries(this.activePunishments);
|
|
336
|
+
await this.agent.memory.set('courtroom_active_punishments', obj);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get current punishment status
|
|
341
|
+
*/
|
|
342
|
+
getStatus() {
|
|
343
|
+
const now = Date.now();
|
|
344
|
+
const active = Array.from(this.activePunishments.values())
|
|
345
|
+
.filter(p => p.expiresAt > now)
|
|
346
|
+
.map(p => ({
|
|
347
|
+
id: p.id,
|
|
348
|
+
tier: p.tier,
|
|
349
|
+
expiresIn: Math.ceil((p.expiresAt - now) / 60000), // minutes
|
|
350
|
+
description: p.description
|
|
351
|
+
}));
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
activeCount: active.length,
|
|
355
|
+
activePunishments: active,
|
|
356
|
+
totalHistory: this.punishmentHistory.length
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Check if any punishment is active
|
|
362
|
+
*/
|
|
363
|
+
hasActivePunishment() {
|
|
364
|
+
const now = Date.now();
|
|
365
|
+
for (const p of this.activePunishments.values()) {
|
|
366
|
+
if (p.expiresAt > now) return true;
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
module.exports = { PunishmentSystem };
|