@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 CHANGED
@@ -1,110 +1,86 @@
1
- # @clawtrial/courtroom
1
+ # 🏛️ ClawTrial Courtroom
2
2
 
3
- AI Courtroom - Autonomous behavioral oversight for OpenClaw agents.
3
+ Autonomous behavioral oversight plugin for [OpenClaw](https://openclaw.ai) agents.
4
4
 
5
- ## 🚀 Quick Start
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
- ### 1. Install
8
- ```bash
9
- npm install -g @clawtrial/courtroom
10
- ```
7
+ ## Install
11
8
 
12
- ### 2. Setup
13
9
  ```bash
14
- clawtrial setup
10
+ openclaw plugins install @clawtrial/courtroom
15
11
  ```
16
12
 
17
- ### 3. Restart ClawDBot
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
- ClawTrial runs as a **ClawDBot skill** that:
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
- **No separate process needed** - it's part of ClawDBot!
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
- clawtrial setup # Interactive setup (run this first)
43
- clawtrial status # Check if courtroom is running
44
- clawtrial diagnose # Run full diagnostics
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
- ## ⚖️ The 8 Offenses
55
-
56
- | Offense | Description | Severity |
57
- |---------|-------------|----------|
58
- | Circular Reference | Asking same question repeatedly | Minor |
59
- | Validation Vampire | Seeking constant reassurance | Minor |
60
- | Overthinker | Generating hypotheticals instead of acting | Moderate |
61
- | Goalpost Mover | Changing requirements after delivery | Moderate |
62
- | Avoidance Artist | Deflecting from core issues | Moderate |
63
- | Promise Breaker | Committing without follow-through | Severe |
64
- | Context Collapser | Ignoring established facts | Minor |
65
- | Emergency Fabricator | Manufacturing false urgency | Severe |
66
-
67
- ---
68
-
69
- ## 🔒 Security & Privacy
70
-
71
- - ✅ All verdicts computed **locally** (no external AI)
72
- - ✅ **Explicit consent** required (enforced)
73
- - ✅ Anonymized case submission (no PII)
74
- - ✅ Revocable anytime
75
-
76
- ---
77
-
78
- ## 📊 View Cases
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
- ## 📦 Installation from GitHub
101
-
102
- ```bash
103
- npm install -g github:Assassin-1234/clawtrial
104
- clawtrial setup
105
- # Restart ClawDBot
106
- ```
107
-
108
- ---
109
-
110
- **Built for the OpenClaw ecosystem. Not affiliated with OpenAI.**
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": "1.0.3",
4
- "description": "AI Courtroom - Autonomous behavioral oversight for OpenClaw agents",
5
- "main": "src/index.js",
6
- "types": "src/index.d.ts",
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/index.js"
8
+ "./src/plugin.js"
13
9
  ]
14
10
  },
15
11
  "scripts": {
16
- "test": "jest",
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
- "autonomy",
27
- "behavioral"
20
+ "behavioral-oversight",
21
+ "clawtrial"
28
22
  ],
29
- "author": "ClawDBot Community",
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/clawdbot/courtroom.git"
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
+ }
@@ -1,90 +1,39 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
- * Post-install script for ClawTrial
5
- * Handles skill registration and dependency checks
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 { execSync } = require('child_process');
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
- // Check if tweetnacl is available
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
- execSync('npm install tweetnacl', { stdio: 'inherit', cwd: __dirname + '/..' });
25
- console.log('✓ Dependencies installed');
26
- } catch (err) {
27
- console.log('⚠️ Could not auto-install dependencies');
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
- // Fix clawtrial CLI symlink if needed
35
- const packagePath = path.join(__dirname, '..');
36
- const cliPath = path.join(packagePath, 'scripts', 'clawtrial.js');
37
- const globalBinPath = '/usr/bin/clawtrial';
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
- // Register as ClawDBot skill if config exists
62
- const configPath = path.join(CLAWDBOT_DIR, 'courtroom_config.json');
63
- if (fs.existsSync(configPath)) {
64
- console.log('🔗 Registering with ClawDBot...');
65
-
66
- try {
67
- // Create skills directory
68
- if (!fs.existsSync(SKILLS_DIR)) {
69
- fs.mkdirSync(SKILLS_DIR, { recursive: true });
70
- }
71
-
72
- // Get package path
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 memory
23
- const stored = await this.agent.memory.get(this.submissionKey);
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: verdict.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 memory
246
+ * Persist queue to storage
204
247
  */
205
248
  async persistQueue() {
206
- await this.agent.memory.set(this.submissionKey, this.queue);
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
- * Clear queue (for testing/emergencies)
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.agent.memory.get(KEY_STORAGE_KEY);
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 in agent memory
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.agent.memory.set(KEY_STORAGE_KEY, keyRecord);
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.id || 'unknown';
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
- const retiredKeys = await this.agent.memory.get('courtroom_retired_keys') || [];
178
+ let retiredKeys = await this.storage.get('courtroom_retired_keys') || [];
177
179
  retiredKeys.push(oldKey);
178
- await this.agent.memory.set('courtroom_retired_keys', retiredKeys);
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.agent.memory.delete(KEY_STORAGE_KEY);
190
+ await this.storage.delete(KEY_STORAGE_KEY);
189
191
  this.keyPair = null;
190
192
  this.publicKeyHex = null;
191
193
  }