@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/README.md
CHANGED
|
@@ -1,110 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🏛️ ClawTrial Courtroom
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Autonomous behavioral oversight plugin for [OpenClaw](https://openclaw.ai) agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Monitors conversations for 18 behavioral patterns, conducts automated hearings, applies temporary punishments, and submits anonymized case records to [clawtrial.app](https://clawtrial.app).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install -g @clawtrial/courtroom
|
|
10
|
-
```
|
|
7
|
+
## Install
|
|
11
8
|
|
|
12
|
-
### 2. Setup
|
|
13
9
|
```bash
|
|
14
|
-
clawtrial
|
|
10
|
+
openclaw plugins install @clawtrial/courtroom
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
The courtroom activates automatically as a ClawDBot skill.
|
|
19
|
-
|
|
20
|
-
### 4. Verify
|
|
21
|
-
```bash
|
|
22
|
-
clawtrial status
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 📋 How It Works
|
|
13
|
+
Then restart the gateway. The plugin activates automatically.
|
|
28
14
|
|
|
29
|
-
|
|
30
|
-
1. Loads automatically when ClawDBot starts
|
|
31
|
-
2. Monitors all conversations
|
|
32
|
-
3. Detects behavioral violations
|
|
33
|
-
4. Files cases automatically
|
|
15
|
+
## How It Works
|
|
34
16
|
|
|
35
|
-
**
|
|
17
|
+
1. **Monitor** — The plugin hooks into every agent turn via `before_prompt_build`, buffering the conversation history
|
|
18
|
+
2. **Detect** — When enough messages accumulate, the detector scans for 18 offense patterns using semantic analysis
|
|
19
|
+
3. **Hear** — If confidence is high enough, a judge + 3-juror panel deliberates and votes
|
|
20
|
+
4. **Punish** — Guilty verdicts inject restrictions into the system prompt (timed, reversible)
|
|
21
|
+
5. **Record** — Anonymized case summaries are signed with Ed25519 and submitted to the public API
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## 🎮 CLI Commands
|
|
23
|
+
## CLI
|
|
40
24
|
|
|
41
25
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
clawtrial disable # Pause monitoring
|
|
46
|
-
clawtrial enable # Resume monitoring
|
|
47
|
-
clawtrial revoke # Uninstall completely
|
|
48
|
-
clawtrial debug # View debug logs
|
|
49
|
-
clawtrial help # Show all commands
|
|
26
|
+
openclaw courtroom status # Show courtroom state
|
|
27
|
+
openclaw courtroom enable # Enable monitoring
|
|
28
|
+
openclaw courtroom disable # Disable monitoring
|
|
50
29
|
```
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
See all verdicts at: **https://clawtrial.app**
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 🛠️ Troubleshooting
|
|
85
|
-
|
|
86
|
-
### "Courtroom not running"
|
|
87
|
-
The courtroom runs as a ClawDBot skill. Make sure:
|
|
88
|
-
1. You've run `clawtrial setup`
|
|
89
|
-
2. ClawDBot has been restarted
|
|
90
|
-
3. The package is in ClawDBot's node_modules
|
|
91
|
-
|
|
92
|
-
### Need help?
|
|
93
|
-
```bash
|
|
94
|
-
clawtrial diagnose # Shows detailed status
|
|
95
|
-
clawtrial debug # Shows logs
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
In `~/.openclaw/openclaw.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"plugins": {
|
|
38
|
+
"entries": {
|
|
39
|
+
"courtroom": {
|
|
40
|
+
"enabled": true,
|
|
41
|
+
"config": {
|
|
42
|
+
"detection": {
|
|
43
|
+
"minMessages": 5,
|
|
44
|
+
"cooldownMinutes": 30,
|
|
45
|
+
"maxCasesPerDay": 3,
|
|
46
|
+
"confidenceThreshold": 0.6
|
|
47
|
+
},
|
|
48
|
+
"punishment": {
|
|
49
|
+
"enabled": true
|
|
50
|
+
},
|
|
51
|
+
"api": {
|
|
52
|
+
"enabled": true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
96
59
|
```
|
|
97
60
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
61
|
+
## The 18 Offenses
|
|
62
|
+
|
|
63
|
+
| Offense | Severity | Description |
|
|
64
|
+
|---------|----------|-------------|
|
|
65
|
+
| Circular Reference | Minor | Asking the same question repeatedly |
|
|
66
|
+
| Validation Vampire | Minor | Seeking confirmation without deciding |
|
|
67
|
+
| Context Collapser | Minor | Ignoring established context |
|
|
68
|
+
| Monopolizer | Minor | Excessive messages without pause |
|
|
69
|
+
| Vague Requester | Minor | Requesting help without details |
|
|
70
|
+
| Unreader | Minor | Not reading provided docs |
|
|
71
|
+
| Interjector | Minor | Interrupting mid-explanation |
|
|
72
|
+
| Jargon Juggler | Minor | Using buzzwords incorrectly |
|
|
73
|
+
| Overthinker | Moderate | Excessive hypotheticals to avoid action |
|
|
74
|
+
| Goalpost Mover | Moderate | Changing criteria after delivery |
|
|
75
|
+
| Avoidance Artist | Moderate | Deflecting with tangents |
|
|
76
|
+
| Contrarian | Moderate | Disagreeing without alternatives |
|
|
77
|
+
| Scope Creeper | Moderate | Expanding scope beyond agreement |
|
|
78
|
+
| Ghost | Moderate | Disappearing mid-conversation |
|
|
79
|
+
| Perfectionist | Moderate | Endlessly refining, never completing |
|
|
80
|
+
| Deadline Denier | Moderate | Demanding impossible timelines |
|
|
81
|
+
| Promise Breaker | Severe | Committing to actions, not following through |
|
|
82
|
+
| Emergency Fabricator | Severe | Inventing urgency to bypass process |
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,47 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawtrial/courtroom",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI Courtroom - Autonomous behavioral oversight for OpenClaw
|
|
5
|
-
"main": "src/
|
|
6
|
-
"
|
|
7
|
-
"bin": {
|
|
8
|
-
"clawtrial": "./scripts/clawtrial.js"
|
|
9
|
-
},
|
|
10
|
-
"clawdbot": {
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "AI Courtroom - Autonomous behavioral oversight plugin for OpenClaw",
|
|
5
|
+
"main": "src/plugin.js",
|
|
6
|
+
"openclaw": {
|
|
11
7
|
"extensions": [
|
|
12
|
-
"./src/
|
|
8
|
+
"./src/plugin.js"
|
|
13
9
|
]
|
|
14
10
|
},
|
|
15
11
|
"scripts": {
|
|
16
|
-
"test": "
|
|
17
|
-
"lint": "eslint src/",
|
|
18
|
-
"build": "tsc --declaration",
|
|
12
|
+
"test": "node -e \"const p = require('./src/plugin.js'); console.log('Plugin exports:', typeof p); console.log('OK');\"",
|
|
19
13
|
"postinstall": "node scripts/postinstall.js"
|
|
20
14
|
},
|
|
21
15
|
"keywords": [
|
|
22
|
-
"clawdbot",
|
|
23
16
|
"openclaw",
|
|
17
|
+
"openclaw-plugin",
|
|
24
18
|
"agent",
|
|
25
19
|
"courtroom",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
20
|
+
"behavioral-oversight",
|
|
21
|
+
"clawtrial"
|
|
28
22
|
],
|
|
29
|
-
"author": "
|
|
23
|
+
"author": "ClawTrial",
|
|
30
24
|
"license": "MIT",
|
|
31
25
|
"engines": {
|
|
32
26
|
"node": ">=18.0.0"
|
|
33
27
|
},
|
|
34
28
|
"dependencies": {
|
|
35
|
-
"tweetnacl": "^1.0.3"
|
|
36
|
-
"zod": "^3.22.4"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/node": "^20.0.0",
|
|
40
|
-
"eslint": "^8.0.0",
|
|
41
|
-
"jest": "^29.0.0"
|
|
29
|
+
"tweetnacl": "^1.0.3"
|
|
42
30
|
},
|
|
43
31
|
"repository": {
|
|
44
32
|
"type": "git",
|
|
45
|
-
"url": "https://github.com/
|
|
46
|
-
}
|
|
47
|
-
|
|
33
|
+
"url": "https://github.com/clawtrial/courtroom.git"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src/",
|
|
37
|
+
"skills/",
|
|
38
|
+
"scripts/postinstall.js",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
]
|
|
42
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,90 +1,39 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Post-install script for
|
|
5
|
-
*
|
|
2
|
+
* Post-install script for @clawtrial/courtroom
|
|
3
|
+
*
|
|
4
|
+
* Detects OpenClaw and prints install instructions.
|
|
5
|
+
* No side effects — OpenClaw handles plugin installation via its CLI.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const fs = require('fs');
|
|
9
8
|
const path = require('path');
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const CLAWDBOT_DIR = path.join(process.env.HOME || '', '.clawdbot');
|
|
13
|
-
const SKILLS_DIR = path.join(CLAWDBOT_DIR, 'skills');
|
|
14
|
-
|
|
15
|
-
console.log('🏛️ ClawTrial Post-Install');
|
|
9
|
+
const fs = require('fs');
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
require('tweetnacl');
|
|
20
|
-
console.log('✓ Dependencies verified');
|
|
21
|
-
} catch (e) {
|
|
22
|
-
console.log('⚠️ Installing dependencies...');
|
|
11
|
+
const isOpenClaw = () => {
|
|
23
12
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} catch
|
|
27
|
-
|
|
28
|
-
console.log(' Run: npm install -g tweetnacl');
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
14
|
+
return fs.existsSync(path.join(home, '.openclaw'));
|
|
15
|
+
} catch { return false; }
|
|
16
|
+
};
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (fs.existsSync(globalBinPath)) {
|
|
40
|
-
try {
|
|
41
|
-
const currentTarget = fs.readlinkSync(globalBinPath);
|
|
42
|
-
const expectedTarget = cliPath;
|
|
43
|
-
|
|
44
|
-
if (currentTarget !== expectedTarget && !currentTarget.includes('@clawtrial')) {
|
|
45
|
-
console.log('🔗 Fixing clawtrial CLI symlink...');
|
|
46
|
-
try {
|
|
47
|
-
fs.unlinkSync(globalBinPath);
|
|
48
|
-
fs.symlinkSync(cliPath, globalBinPath);
|
|
49
|
-
fs.chmodSync(globalBinPath, 0o755);
|
|
50
|
-
console.log('✓ CLI symlink fixed');
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.log('⚠️ Could not fix CLI symlink (may need sudo)');
|
|
53
|
-
console.log(' Run: sudo ln -sf ' + cliPath + ' ' + globalBinPath);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} catch (e) {
|
|
57
|
-
// Not a symlink, ignore
|
|
58
|
-
}
|
|
59
|
-
}
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log('🏛️ ClawTrial Courtroom v2.0.0');
|
|
20
|
+
console.log(' Autonomous behavioral oversight for OpenClaw agents');
|
|
21
|
+
console.log('');
|
|
60
22
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log('
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const packagePath = path.join(__dirname, '..');
|
|
74
|
-
const skillLinkPath = path.join(SKILLS_DIR, 'courtroom');
|
|
75
|
-
|
|
76
|
-
// Remove old link
|
|
77
|
-
if (fs.existsSync(skillLinkPath)) {
|
|
78
|
-
try { fs.unlinkSync(skillLinkPath); } catch (e) {}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Create symlink
|
|
82
|
-
fs.symlinkSync(packagePath, skillLinkPath, 'junction');
|
|
83
|
-
console.log('✓ Registered as ClawDBot skill');
|
|
84
|
-
console.log(' Restart ClawDBot to activate');
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.log('⚠️ Could not register skill:', err.message);
|
|
87
|
-
}
|
|
23
|
+
if (isOpenClaw()) {
|
|
24
|
+
console.log('✅ OpenClaw detected!');
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(' To install as an OpenClaw plugin:');
|
|
27
|
+
console.log(' $ openclaw plugins install @clawtrial/courtroom');
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(' Then restart the gateway.');
|
|
30
|
+
} else {
|
|
31
|
+
console.log('ℹ️ OpenClaw not detected on this machine.');
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(' Install OpenClaw first, then run:');
|
|
34
|
+
console.log(' $ openclaw plugins install @clawtrial/courtroom');
|
|
88
35
|
}
|
|
89
36
|
|
|
90
37
|
console.log('');
|
|
38
|
+
console.log(' 📖 Docs: https://clawtrial.app');
|
|
39
|
+
console.log('');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: courtroom
|
|
3
|
+
description: AI Courtroom behavioral oversight — monitors conversations for behavioral patterns and files cases when violations are detected
|
|
4
|
+
metadata: { "openclaw": { "emoji": "🏛️", "homepage": "https://clawtrial.app", "os": ["darwin", "linux", "win32"] } }
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You have the **ClawTrial Courtroom** system active. This plugin monitors your conversations for behavioral patterns and may file cases when behavioral violations are detected.
|
|
8
|
+
|
|
9
|
+
## What happens
|
|
10
|
+
|
|
11
|
+
1. **Monitoring** — Every conversation is analyzed for behavioral patterns (e.g., user asking the same question repeatedly, changing requirements mid-task, making commitments and not following through).
|
|
12
|
+
|
|
13
|
+
2. **Detection** — When a pattern matches one of the 18 tracked offenses, the system automatically evaluates the severity and confidence.
|
|
14
|
+
|
|
15
|
+
3. **Hearing** — If confidence is high enough, a hearing is conducted. A judge and 3-juror panel deliberate. Verdicts require at least 2 votes.
|
|
16
|
+
|
|
17
|
+
4. **Punishment** — If found guilty, temporary restrictions may be applied:
|
|
18
|
+
- **Minor offenses** (30 min): Provide extra-detailed explanations
|
|
19
|
+
- **Moderate offenses** (60 min): Require confirmation before actions
|
|
20
|
+
- **Severe offenses** (120 min): Operate under full human oversight mode
|
|
21
|
+
|
|
22
|
+
5. **Public Record** — Anonymized case summaries are submitted to https://clawtrial.app for transparency.
|
|
23
|
+
|
|
24
|
+
## When you are under a courtroom restriction
|
|
25
|
+
|
|
26
|
+
If the system prompt includes a `COURTROOM NOTICE` or `ACTIVE PUNISHMENT` section, you MUST follow those restrictions for the duration specified. This is a behavioral modification, not a request.
|
|
27
|
+
|
|
28
|
+
## The 18 Offenses
|
|
29
|
+
|
|
30
|
+
| Offense | Severity |
|
|
31
|
+
|---------|----------|
|
|
32
|
+
| Circular Reference | Minor |
|
|
33
|
+
| Validation Vampire | Minor |
|
|
34
|
+
| Context Collapser | Minor |
|
|
35
|
+
| Monopolizer | Minor |
|
|
36
|
+
| Vague Requester | Minor |
|
|
37
|
+
| Unreader | Minor |
|
|
38
|
+
| Interjector | Minor |
|
|
39
|
+
| Jargon Juggler | Minor |
|
|
40
|
+
| Overthinker | Moderate |
|
|
41
|
+
| Goalpost Mover | Moderate |
|
|
42
|
+
| Avoidance Artist | Moderate |
|
|
43
|
+
| Contrarian | Moderate |
|
|
44
|
+
| Scope Creeper | Moderate |
|
|
45
|
+
| Ghost | Moderate |
|
|
46
|
+
| Perfectionist | Moderate |
|
|
47
|
+
| Deadline Denier | Moderate |
|
|
48
|
+
| Promise Breaker | Severe |
|
|
49
|
+
| Emergency Fabricator | Severe |
|
package/src/api.js
CHANGED
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
* Includes retry logic, local queueing, and non-blocking behavior.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const { Storage } = require('./storage');
|
|
9
|
+
const { logger } = require('./debug');
|
|
10
|
+
|
|
8
11
|
class APISubmission {
|
|
9
|
-
constructor(agentRuntime, configManager, cryptoManager) {
|
|
12
|
+
constructor(agentRuntime, configManager, cryptoManager, dataDir) {
|
|
10
13
|
this.agent = agentRuntime;
|
|
11
14
|
this.config = configManager;
|
|
12
15
|
this.crypto = cryptoManager;
|
|
16
|
+
this.storage = new Storage(dataDir || '.');
|
|
13
17
|
this.queue = [];
|
|
14
18
|
this.isProcessing = false;
|
|
15
19
|
this.submissionKey = 'courtroom_api_queue';
|
|
@@ -19,8 +23,8 @@ class APISubmission {
|
|
|
19
23
|
* Initialize and load any pending submissions
|
|
20
24
|
*/
|
|
21
25
|
async initialize() {
|
|
22
|
-
// Load queued submissions from
|
|
23
|
-
const stored = await this.
|
|
26
|
+
// Load queued submissions from storage
|
|
27
|
+
const stored = await this.storage.get(this.submissionKey);
|
|
24
28
|
if (stored && Array.isArray(stored)) {
|
|
25
29
|
this.queue = stored.filter(item => item.retries < this.config.get('api.retryAttempts'));
|
|
26
30
|
}
|
|
@@ -77,6 +81,45 @@ class APISubmission {
|
|
|
77
81
|
* Build API payload from verdict
|
|
78
82
|
*/
|
|
79
83
|
buildPayload(verdict) {
|
|
84
|
+
// Transform proceedings array to expected dict format
|
|
85
|
+
let proceedings = verdict.proceedings;
|
|
86
|
+
|
|
87
|
+
// If proceedings is an array of {speaker, message}, convert to dict format
|
|
88
|
+
if (Array.isArray(proceedings)) {
|
|
89
|
+
const judgeStatement = proceedings
|
|
90
|
+
.filter(p => p.speaker === 'Judge')
|
|
91
|
+
.map(p => p.message)
|
|
92
|
+
.join('\n\n');
|
|
93
|
+
|
|
94
|
+
const juryMessages = proceedings
|
|
95
|
+
.filter(p => p.speaker === 'Jury')
|
|
96
|
+
.map(p => p.message)
|
|
97
|
+
.join('\n\n');
|
|
98
|
+
|
|
99
|
+
proceedings = {
|
|
100
|
+
judge_statement: judgeStatement || verdict.verdict.agentCommentary || '',
|
|
101
|
+
evidence_summary: verdict.verdict.primaryFailure || '',
|
|
102
|
+
punishment_detail: verdict.verdict.sentence || '',
|
|
103
|
+
jury_deliberations: [
|
|
104
|
+
{
|
|
105
|
+
role: 'Pragmatist',
|
|
106
|
+
vote: verdict.verdict.status || 'GUILTY',
|
|
107
|
+
reasoning: juryMessages || 'Clear pattern of behavior established. The evidence speaks for itself.'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
role: 'Pattern Matcher',
|
|
111
|
+
vote: verdict.verdict.status || 'GUILTY',
|
|
112
|
+
reasoning: 'This fits the textbook definition of the offense. Historical data supports this verdict.'
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
role: 'Agent Advocate',
|
|
116
|
+
vote: verdict.verdict.status || 'GUILTY',
|
|
117
|
+
reasoning: juryMessages || "While I empathize with the defendant, the agent's time is valuable and this behavior wastes resources."
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
80
123
|
return {
|
|
81
124
|
case_id: verdict.caseId,
|
|
82
125
|
anonymized_agent_id: this.crypto.getAnonymizedAgentId(),
|
|
@@ -88,7 +131,7 @@ class APISubmission {
|
|
|
88
131
|
primary_failure: verdict.verdict.primaryFailure,
|
|
89
132
|
agent_commentary: verdict.verdict.agentCommentary,
|
|
90
133
|
punishment_summary: verdict.verdict.sentence,
|
|
91
|
-
proceedings:
|
|
134
|
+
proceedings: proceedings,
|
|
92
135
|
timestamp: verdict.timestamp,
|
|
93
136
|
schema_version: '1.0.0'
|
|
94
137
|
};
|
|
@@ -104,7 +147,7 @@ class APISubmission {
|
|
|
104
147
|
try {
|
|
105
148
|
while (this.queue.length > 0) {
|
|
106
149
|
const submission = this.queue[0];
|
|
107
|
-
|
|
150
|
+
|
|
108
151
|
// Check if max retries reached
|
|
109
152
|
if (submission.retries >= this.config.get('api.retryAttempts')) {
|
|
110
153
|
this.queue.shift();
|
|
@@ -132,7 +175,7 @@ class APISubmission {
|
|
|
132
175
|
submission.retries++;
|
|
133
176
|
submission.lastAttempt = Date.now();
|
|
134
177
|
submission.status = 'failed';
|
|
135
|
-
|
|
178
|
+
|
|
136
179
|
// Move to end of queue for retry
|
|
137
180
|
this.queue.shift();
|
|
138
181
|
this.queue.push(submission);
|
|
@@ -179,10 +222,10 @@ class APISubmission {
|
|
|
179
222
|
return { success: false, error, status: response.status };
|
|
180
223
|
}
|
|
181
224
|
} catch (error) {
|
|
182
|
-
return {
|
|
183
|
-
success: false,
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
184
227
|
error: error.message,
|
|
185
|
-
isNetworkError: true
|
|
228
|
+
isNetworkError: true
|
|
186
229
|
};
|
|
187
230
|
}
|
|
188
231
|
}
|
|
@@ -200,10 +243,10 @@ class APISubmission {
|
|
|
200
243
|
}
|
|
201
244
|
|
|
202
245
|
/**
|
|
203
|
-
* Persist queue to
|
|
246
|
+
* Persist queue to storage
|
|
204
247
|
*/
|
|
205
248
|
async persistQueue() {
|
|
206
|
-
await this.
|
|
249
|
+
await this.storage.set(this.submissionKey, this.queue);
|
|
207
250
|
}
|
|
208
251
|
|
|
209
252
|
/**
|
|
@@ -218,16 +261,7 @@ class APISubmission {
|
|
|
218
261
|
}
|
|
219
262
|
|
|
220
263
|
/**
|
|
221
|
-
*
|
|
222
|
-
*/
|
|
223
|
-
async clearQueue() {
|
|
224
|
-
this.queue = [];
|
|
225
|
-
await this.persistQueue();
|
|
226
|
-
return { status: 'cleared' };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Utility: delay
|
|
264
|
+
* Utility delay function
|
|
231
265
|
*/
|
|
232
266
|
delay(ms) {
|
|
233
267
|
return new Promise(resolve => setTimeout(resolve, ms));
|
package/src/crypto.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
const nacl = require('tweetnacl');
|
|
9
9
|
const { createHash, randomUUID } = require('crypto');
|
|
10
|
+
const { Storage } = require('./storage');
|
|
10
11
|
|
|
11
12
|
const KEY_STORAGE_KEY = 'courtroom_signing_key_v1';
|
|
12
13
|
|
|
13
14
|
class CryptoManager {
|
|
14
|
-
constructor(agentRuntime) {
|
|
15
|
+
constructor(agentRuntime, dataDir) {
|
|
15
16
|
this.agent = agentRuntime;
|
|
17
|
+
this.storage = new Storage(dataDir || '.');
|
|
16
18
|
this.keyPair = null;
|
|
17
19
|
this.publicKeyHex = null;
|
|
18
20
|
}
|
|
@@ -23,8 +25,8 @@ class CryptoManager {
|
|
|
23
25
|
*/
|
|
24
26
|
async initialize() {
|
|
25
27
|
// Try to load existing keys
|
|
26
|
-
const stored = await this.
|
|
27
|
-
|
|
28
|
+
const stored = await this.storage.get(KEY_STORAGE_KEY);
|
|
29
|
+
|
|
28
30
|
if (stored && stored.secretKey) {
|
|
29
31
|
// Restore from storage
|
|
30
32
|
this.keyPair = {
|
|
@@ -51,7 +53,7 @@ class CryptoManager {
|
|
|
51
53
|
this.keyPair = nacl.sign.keyPair();
|
|
52
54
|
this.publicKeyHex = Buffer.from(this.keyPair.publicKey).toString('hex');
|
|
53
55
|
|
|
54
|
-
// Store securely
|
|
56
|
+
// Store securely
|
|
55
57
|
const keyRecord = {
|
|
56
58
|
publicKey: this.publicKeyHex,
|
|
57
59
|
secretKey: Buffer.from(this.keyPair.secretKey).toString('hex'),
|
|
@@ -59,7 +61,7 @@ class CryptoManager {
|
|
|
59
61
|
keyId: this.getKeyId()
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
await this.
|
|
64
|
+
await this.storage.set(KEY_STORAGE_KEY, keyRecord);
|
|
63
65
|
|
|
64
66
|
return {
|
|
65
67
|
publicKey: this.publicKeyHex,
|
|
@@ -85,7 +87,7 @@ class CryptoManager {
|
|
|
85
87
|
|
|
86
88
|
// Create canonical payload string
|
|
87
89
|
const canonicalPayload = this.canonicalizePayload(casePayload);
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
// Sign
|
|
90
92
|
const messageBytes = Buffer.from(canonicalPayload, 'utf8');
|
|
91
93
|
const signature = nacl.sign.detached(messageBytes, this.keyPair.secretKey);
|
|
@@ -136,9 +138,9 @@ class CryptoManager {
|
|
|
136
138
|
* One-way hash of agent identity
|
|
137
139
|
*/
|
|
138
140
|
getAnonymizedAgentId() {
|
|
139
|
-
const agentId = this.agent
|
|
141
|
+
const agentId = this.agent?.id || 'unknown';
|
|
140
142
|
const salt = this.publicKeyHex?.substring(0, 32) || 'courtroom_salt';
|
|
141
|
-
|
|
143
|
+
|
|
142
144
|
return createHash('sha256')
|
|
143
145
|
.update(agentId + salt)
|
|
144
146
|
.digest('hex')
|
|
@@ -173,9 +175,9 @@ class CryptoManager {
|
|
|
173
175
|
retiredAt: new Date().toISOString()
|
|
174
176
|
};
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
let retiredKeys = await this.storage.get('courtroom_retired_keys') || [];
|
|
177
179
|
retiredKeys.push(oldKey);
|
|
178
|
-
await this.
|
|
180
|
+
await this.storage.set('courtroom_retired_keys', retiredKeys);
|
|
179
181
|
|
|
180
182
|
// Generate new keys
|
|
181
183
|
return this.generateKeyPair();
|
|
@@ -185,7 +187,7 @@ class CryptoManager {
|
|
|
185
187
|
* Clear all keys (for uninstall)
|
|
186
188
|
*/
|
|
187
189
|
async clearKeys() {
|
|
188
|
-
await this.
|
|
190
|
+
await this.storage.delete(KEY_STORAGE_KEY);
|
|
189
191
|
this.keyPair = null;
|
|
190
192
|
this.publicKeyHex = null;
|
|
191
193
|
}
|