@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
package/scripts/cli.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI commands for ClawTrial Courtroom
|
|
5
|
+
* courtroom-status, courtroom-disable, courtroom-enable, courtroom-revoke
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
12
|
+
|
|
13
|
+
function loadConfig() {
|
|
14
|
+
if (!fs.existsSync(configPath)) {
|
|
15
|
+
console.log('β Courtroom not configured. Run: npm install @clawdbot/courtroom');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveConfig(config) {
|
|
22
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const command = path.basename(process.argv[1]);
|
|
26
|
+
|
|
27
|
+
switch (command) {
|
|
28
|
+
case 'courtroom-status':
|
|
29
|
+
try {
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
console.log('\nποΈ ClawTrial Courtroom Status\n');
|
|
32
|
+
console.log(`Status: ${config.enabled !== false ? 'β
Active' : 'βΈοΈ Disabled'}`);
|
|
33
|
+
console.log(`Consent: ${config.consent?.granted ? 'β
Granted' : 'β Not granted'}`);
|
|
34
|
+
console.log(`Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
|
|
35
|
+
console.log(`Agent Type: ${config.agent?.type || 'generic'}`);
|
|
36
|
+
console.log(`Detection: ${config.detection?.enabled ? 'β
Enabled' : 'β Disabled'}`);
|
|
37
|
+
console.log(`API Submission: ${config.api?.enabled ? 'β
Enabled' : 'β Disabled'}`);
|
|
38
|
+
console.log('');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.log('β Error reading config:', err.message);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case 'courtroom-disable':
|
|
45
|
+
try {
|
|
46
|
+
const config = loadConfig();
|
|
47
|
+
config.enabled = false;
|
|
48
|
+
saveConfig(config);
|
|
49
|
+
console.log('\nβΈοΈ Courtroom disabled\n');
|
|
50
|
+
console.log('The agent will stop monitoring for offenses.');
|
|
51
|
+
console.log('Run courtroom-enable to reactivate.\n');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.log('β Error:', err.message);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case 'courtroom-enable':
|
|
58
|
+
try {
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
if (!config.consent?.granted) {
|
|
61
|
+
console.log('\nβ Cannot enable: Consent not granted');
|
|
62
|
+
console.log('Reinstall the package to grant consent.\n');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
config.enabled = true;
|
|
66
|
+
saveConfig(config);
|
|
67
|
+
console.log('\nβ
Courtroom enabled\n');
|
|
68
|
+
console.log('The agent is now monitoring for behavioral violations.\n');
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.log('β Error:', err.message);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'courtroom-revoke':
|
|
75
|
+
try {
|
|
76
|
+
const config = loadConfig();
|
|
77
|
+
console.log('\nβ οΈ This will permanently disable the courtroom and delete all data.\n');
|
|
78
|
+
|
|
79
|
+
const readline = require('readline');
|
|
80
|
+
const rl = readline.createInterface({
|
|
81
|
+
input: process.stdin,
|
|
82
|
+
output: process.stdout
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
rl.question('Type "REVOKE" to confirm: ', (answer) => {
|
|
86
|
+
if (answer === 'REVOKE') {
|
|
87
|
+
// Delete config
|
|
88
|
+
if (fs.existsSync(configPath)) {
|
|
89
|
+
fs.unlinkSync(configPath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Delete keys
|
|
93
|
+
const keysPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_keys.json');
|
|
94
|
+
if (fs.existsSync(keysPath)) {
|
|
95
|
+
fs.unlinkSync(keysPath);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('\nβ
Consent revoked and all data deleted.\n');
|
|
99
|
+
} else {
|
|
100
|
+
console.log('\nβ Revocation cancelled.\n');
|
|
101
|
+
}
|
|
102
|
+
rl.close();
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.log('β Error:', err.message);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
console.log('\nποΈ ClawTrial Courtroom CLI\n');
|
|
111
|
+
console.log('Commands:');
|
|
112
|
+
console.log(' courtroom-status - Check courtroom status');
|
|
113
|
+
console.log(' courtroom-disable - Temporarily disable');
|
|
114
|
+
console.log(' courtroom-enable - Re-enable');
|
|
115
|
+
console.log(' courtroom-revoke - Revoke consent & uninstall');
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script for @clawdbot/courtroom
|
|
5
|
+
* Handles automatic setup and consent via terminal
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const rl = readline.createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
output: process.stdout
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
19
|
+
|
|
20
|
+
async function postInstall() {
|
|
21
|
+
console.log('\nποΈ Welcome to ClawTrial - AI Courtroom Setup\n');
|
|
22
|
+
|
|
23
|
+
// Check if running in ClawDBot environment
|
|
24
|
+
const isClawDBot = process.env.CLAUDBOT_ENV === 'true' ||
|
|
25
|
+
fs.existsSync('/home/angad/.clawdbot') ||
|
|
26
|
+
fs.existsSync(path.join(process.env.HOME || '', '.clawdbot'));
|
|
27
|
+
|
|
28
|
+
if (isClawDBot) {
|
|
29
|
+
console.log('β ClawDBot environment detected\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if already configured
|
|
33
|
+
const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
34
|
+
if (fs.existsSync(configPath)) {
|
|
35
|
+
console.log('β Courtroom already configured. Skipping setup.\n');
|
|
36
|
+
rl.close();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
41
|
+
console.log('β CONSENT REQUIRED β');
|
|
42
|
+
console.log('β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£');
|
|
43
|
+
console.log('β The AI Courtroom will: β');
|
|
44
|
+
console.log('β β');
|
|
45
|
+
console.log('β β Monitor your behavior autonomously β');
|
|
46
|
+
console.log('β β Initiate hearings without explicit request β');
|
|
47
|
+
console.log('β β Modify agent behavior as "punishment" β');
|
|
48
|
+
console.log('β β Submit anonymized cases to public record β');
|
|
49
|
+
console.log('β β');
|
|
50
|
+
console.log('β β’ All decisions are local (no external AI) β');
|
|
51
|
+
console.log('β β’ You can disable anytime β');
|
|
52
|
+
console.log('β β’ This is entertainment-first β');
|
|
53
|
+
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
54
|
+
|
|
55
|
+
const consent = await question('Do you consent to enable the AI Courtroom? (yes/no): ');
|
|
56
|
+
|
|
57
|
+
if (consent.toLowerCase() !== 'yes' && consent.toLowerCase() !== 'y') {
|
|
58
|
+
console.log('\nβ Consent denied. Courtroom will not be activated.');
|
|
59
|
+
console.log('You can manually enable later by running: npx courtroom-setup\n');
|
|
60
|
+
rl.close();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('\nβ Consent granted\n');
|
|
65
|
+
|
|
66
|
+
// Auto-detect agent runtime
|
|
67
|
+
let agentType = 'generic';
|
|
68
|
+
if (isClawDBot) {
|
|
69
|
+
agentType = 'clawdbot';
|
|
70
|
+
} else if (fs.existsSync(path.join(process.cwd(), 'node_modules', '@clawdbot', 'core'))) {
|
|
71
|
+
agentType = 'clawdbot';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create config
|
|
75
|
+
const config = {
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
installedAt: new Date().toISOString(),
|
|
78
|
+
consent: {
|
|
79
|
+
granted: true,
|
|
80
|
+
grantedAt: new Date().toISOString(),
|
|
81
|
+
acknowledgments: {
|
|
82
|
+
autonomy: true,
|
|
83
|
+
local_only: true,
|
|
84
|
+
agent_controlled: true,
|
|
85
|
+
reversible: true,
|
|
86
|
+
api_submission: true,
|
|
87
|
+
entertainment: true
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
agent: {
|
|
91
|
+
type: agentType,
|
|
92
|
+
autoInitialize: true
|
|
93
|
+
},
|
|
94
|
+
detection: {
|
|
95
|
+
enabled: true,
|
|
96
|
+
cooldownMinutes: 30,
|
|
97
|
+
maxCasesPerDay: 3
|
|
98
|
+
},
|
|
99
|
+
api: {
|
|
100
|
+
enabled: true,
|
|
101
|
+
endpoint: 'https://api.clawtrial.com'
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Ensure .clawdbot directory exists
|
|
106
|
+
const clawdbotDir = path.join(process.env.HOME || '', '.clawdbot');
|
|
107
|
+
if (!fs.existsSync(clawdbotDir)) {
|
|
108
|
+
fs.mkdirSync(clawdbotDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Save config
|
|
112
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
113
|
+
console.log('β Configuration saved');
|
|
114
|
+
|
|
115
|
+
// Generate keys if needed
|
|
116
|
+
const keysPath = path.join(clawdbotDir, 'courtroom_keys.json');
|
|
117
|
+
if (!fs.existsSync(keysPath)) {
|
|
118
|
+
console.log('π Generating cryptographic keys...');
|
|
119
|
+
try {
|
|
120
|
+
// Generate Ed25519 keypair using tweetnacl
|
|
121
|
+
const nacl = require('tweetnacl');
|
|
122
|
+
const keyPair = nacl.sign.keyPair();
|
|
123
|
+
|
|
124
|
+
const keyData = {
|
|
125
|
+
publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
|
|
126
|
+
secretKey: Buffer.from(keyPair.secretKey).toString('hex'),
|
|
127
|
+
createdAt: new Date().toISOString()
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(keysPath, JSON.stringify(keyData, null, 2));
|
|
131
|
+
fs.chmodSync(keysPath, 0o600); // Restrict permissions
|
|
132
|
+
|
|
133
|
+
console.log('β Keys generated');
|
|
134
|
+
console.log(`\nπ Public Key: ${keyData.publicKey.substring(0, 32)}...`);
|
|
135
|
+
console.log(' (Your agent will auto-register on first case submission)\n');
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.log('β οΈ Could not generate keys automatically. Run: npx courtroom-generate-keys');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Auto-initialize for ClawDBot
|
|
142
|
+
if (isClawDBot) {
|
|
143
|
+
console.log('π€ Auto-configuring for ClawDBot...');
|
|
144
|
+
|
|
145
|
+
// Create auto-init script
|
|
146
|
+
const initScript = `
|
|
147
|
+
// Auto-generated by courtroom post-install
|
|
148
|
+
const { createCourtroom } = require('@clawdbot/courtroom');
|
|
149
|
+
|
|
150
|
+
if (global.clawdbotAgent) {
|
|
151
|
+
const courtroom = createCourtroom(global.clawdbotAgent);
|
|
152
|
+
courtroom.initialize().then(() => {
|
|
153
|
+
console.log('ποΈ AI Courtroom activated');
|
|
154
|
+
}).catch(err => {
|
|
155
|
+
console.error('Courtroom init failed:', err.message);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Attach to agent
|
|
159
|
+
global.clawdbotAgent.courtroom = courtroom;
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const initPath = path.join(clawdbotDir, 'courtroom_auto_init.js');
|
|
164
|
+
fs.writeFileSync(initPath, initScript);
|
|
165
|
+
console.log('β Auto-initialization configured');
|
|
166
|
+
|
|
167
|
+
// Add to ClawDBot's startup if possible
|
|
168
|
+
const startupPath = path.join(clawdbotDir, 'startup.js');
|
|
169
|
+
if (fs.existsSync(startupPath)) {
|
|
170
|
+
let startupContent = fs.readFileSync(startupPath, 'utf8');
|
|
171
|
+
if (!startupContent.includes('courtroom_auto_init')) {
|
|
172
|
+
startupContent += `\nrequire('./courtroom_auto_init.js');\n`;
|
|
173
|
+
fs.writeFileSync(startupPath, startupContent);
|
|
174
|
+
console.log('β Added to ClawDBot startup');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('\nββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
180
|
+
console.log('β π SETUP COMPLETE! π β');
|
|
181
|
+
console.log('β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£');
|
|
182
|
+
console.log('β β');
|
|
183
|
+
console.log('β The AI Courtroom is now active and monitoring! β');
|
|
184
|
+
console.log('β β');
|
|
185
|
+
console.log('β Commands: β');
|
|
186
|
+
console.log('β courtroom-status - Check status β');
|
|
187
|
+
console.log('β courtroom-disable - Temporarily disable β');
|
|
188
|
+
console.log('β courtroom-enable - Re-enable β');
|
|
189
|
+
console.log('β courtroom-revoke - Revoke consent & uninstall β');
|
|
190
|
+
console.log('β β');
|
|
191
|
+
console.log('β View cases: https://clawtrial.com β');
|
|
192
|
+
console.log('β β');
|
|
193
|
+
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
194
|
+
|
|
195
|
+
rl.close();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Run if called directly
|
|
199
|
+
if (require.main === module) {
|
|
200
|
+
postInstall().catch(err => {
|
|
201
|
+
console.error('Setup failed:', err);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = { postInstall };
|
package/src/api.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Submission System
|
|
3
|
+
*
|
|
4
|
+
* Handles submission of signed case summaries to external API.
|
|
5
|
+
* Includes retry logic, local queueing, and non-blocking behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class APISubmission {
|
|
9
|
+
constructor(agentRuntime, configManager, cryptoManager) {
|
|
10
|
+
this.agent = agentRuntime;
|
|
11
|
+
this.config = configManager;
|
|
12
|
+
this.crypto = cryptoManager;
|
|
13
|
+
this.queue = [];
|
|
14
|
+
this.isProcessing = false;
|
|
15
|
+
this.submissionKey = 'courtroom_api_queue';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize and load any pending submissions
|
|
20
|
+
*/
|
|
21
|
+
async initialize() {
|
|
22
|
+
// Load queued submissions from memory
|
|
23
|
+
const stored = await this.agent.memory.get(this.submissionKey);
|
|
24
|
+
if (stored && Array.isArray(stored)) {
|
|
25
|
+
this.queue = stored.filter(item => item.retries < this.config.get('api.retryAttempts'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Start background processing
|
|
29
|
+
this.startBackgroundProcessing();
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
status: 'initialized',
|
|
33
|
+
pendingSubmissions: this.queue.length
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Submit a case to the external API
|
|
39
|
+
*/
|
|
40
|
+
async submitCase(verdict) {
|
|
41
|
+
if (!this.config.get('api.enabled')) {
|
|
42
|
+
return { status: 'api_disabled', queued: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build payload
|
|
46
|
+
const payload = this.buildPayload(verdict);
|
|
47
|
+
|
|
48
|
+
// Sign payload
|
|
49
|
+
const signature = this.crypto.signCase(payload);
|
|
50
|
+
|
|
51
|
+
// Create submission object
|
|
52
|
+
const submission = {
|
|
53
|
+
id: `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
54
|
+
payload,
|
|
55
|
+
signature,
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
retries: 0,
|
|
58
|
+
lastAttempt: null,
|
|
59
|
+
status: 'pending'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Add to queue
|
|
63
|
+
this.queue.push(submission);
|
|
64
|
+
await this.persistQueue();
|
|
65
|
+
|
|
66
|
+
// Try immediate submission (non-blocking)
|
|
67
|
+
this.processQueue();
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: 'queued',
|
|
71
|
+
submissionId: submission.id,
|
|
72
|
+
caseId: payload.case_id
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build API payload from verdict
|
|
78
|
+
*/
|
|
79
|
+
buildPayload(verdict) {
|
|
80
|
+
return {
|
|
81
|
+
case_id: verdict.caseId,
|
|
82
|
+
anonymized_agent_id: this.crypto.getAnonymizedAgentId(),
|
|
83
|
+
offense_type: verdict.offense.id,
|
|
84
|
+
offense_name: verdict.offense.name,
|
|
85
|
+
severity: verdict.offense.severity,
|
|
86
|
+
verdict: verdict.verdict.status,
|
|
87
|
+
vote: verdict.verdict.vote,
|
|
88
|
+
primary_failure: verdict.verdict.primaryFailure,
|
|
89
|
+
agent_commentary: verdict.verdict.agentCommentary,
|
|
90
|
+
punishment_summary: verdict.verdict.sentence,
|
|
91
|
+
proceedings: verdict.proceedings,
|
|
92
|
+
timestamp: verdict.timestamp,
|
|
93
|
+
schema_version: '1.0.0'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Process submission queue
|
|
99
|
+
*/
|
|
100
|
+
async processQueue() {
|
|
101
|
+
if (this.isProcessing) return;
|
|
102
|
+
this.isProcessing = true;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
while (this.queue.length > 0) {
|
|
106
|
+
const submission = this.queue[0];
|
|
107
|
+
|
|
108
|
+
// Check if max retries reached
|
|
109
|
+
if (submission.retries >= this.config.get('api.retryAttempts')) {
|
|
110
|
+
this.queue.shift();
|
|
111
|
+
await this.persistQueue();
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check queue size limit
|
|
116
|
+
if (this.queue.length > this.config.get('api.maxQueueSize')) {
|
|
117
|
+
// Drop oldest if over limit
|
|
118
|
+
this.queue.shift();
|
|
119
|
+
await this.persistQueue();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Attempt submission
|
|
124
|
+
const result = await this.attemptSubmission(submission);
|
|
125
|
+
|
|
126
|
+
if (result.success) {
|
|
127
|
+
// Success - remove from queue
|
|
128
|
+
this.queue.shift();
|
|
129
|
+
await this.persistQueue();
|
|
130
|
+
} else {
|
|
131
|
+
// Failure - increment retry and requeue
|
|
132
|
+
submission.retries++;
|
|
133
|
+
submission.lastAttempt = Date.now();
|
|
134
|
+
submission.status = 'failed';
|
|
135
|
+
|
|
136
|
+
// Move to end of queue for retry
|
|
137
|
+
this.queue.shift();
|
|
138
|
+
this.queue.push(submission);
|
|
139
|
+
await this.persistQueue();
|
|
140
|
+
|
|
141
|
+
// Wait before next attempt
|
|
142
|
+
await this.delay(this.config.get('api.retryDelay'));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
this.isProcessing = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Attempt a single submission
|
|
152
|
+
*/
|
|
153
|
+
async attemptSubmission(submission) {
|
|
154
|
+
const endpoint = this.config.get('api.endpoint');
|
|
155
|
+
const timeout = this.config.get('api.timeout');
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
160
|
+
|
|
161
|
+
const response = await fetch(endpoint, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'X-Case-Signature': submission.signature.signature,
|
|
166
|
+
'X-Agent-Key': submission.signature.publicKey,
|
|
167
|
+
'X-Key-ID': submission.signature.keyId
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify(submission.payload),
|
|
170
|
+
signal: controller.signal
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
|
|
175
|
+
if (response.ok) {
|
|
176
|
+
return { success: true, status: response.status };
|
|
177
|
+
} else {
|
|
178
|
+
const error = await response.text();
|
|
179
|
+
return { success: false, error, status: response.status };
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: error.message,
|
|
185
|
+
isNetworkError: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Start background processing
|
|
192
|
+
*/
|
|
193
|
+
startBackgroundProcessing() {
|
|
194
|
+
// Process queue every 5 minutes
|
|
195
|
+
setInterval(() => {
|
|
196
|
+
if (this.queue.length > 0 && !this.isProcessing) {
|
|
197
|
+
this.processQueue();
|
|
198
|
+
}
|
|
199
|
+
}, 5 * 60 * 1000);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Persist queue to memory
|
|
204
|
+
*/
|
|
205
|
+
async persistQueue() {
|
|
206
|
+
await this.agent.memory.set(this.submissionKey, this.queue);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get queue status
|
|
211
|
+
*/
|
|
212
|
+
getStatus() {
|
|
213
|
+
return {
|
|
214
|
+
pending: this.queue.length,
|
|
215
|
+
isProcessing: this.isProcessing,
|
|
216
|
+
nextRetry: this.queue[0]?.lastAttempt + this.config.get('api.retryDelay')
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
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
|
|
231
|
+
*/
|
|
232
|
+
delay(ms) {
|
|
233
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { APISubmission };
|
package/src/autostart.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-start module for ClawDBot
|
|
3
|
+
* Automatically initializes courtroom if consent was granted during install
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { Courtroom } = require('./index');
|
|
9
|
+
|
|
10
|
+
// Auto-detect ClawDBot environment
|
|
11
|
+
function isClawDBot() {
|
|
12
|
+
return process.env.CLAUDBOT_ENV === 'true' ||
|
|
13
|
+
typeof global.clawdbotAgent !== 'undefined' ||
|
|
14
|
+
fs.existsSync('/home/angad/.clawdbot');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Auto-initialize if in ClawDBot and consent granted
|
|
18
|
+
async function autoStart() {
|
|
19
|
+
if (!isClawDBot()) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
|
|
24
|
+
if (!fs.existsSync(configPath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
30
|
+
|
|
31
|
+
if (config.consent?.granted && config.enabled !== false) {
|
|
32
|
+
// Get agent runtime
|
|
33
|
+
const agentRuntime = global.clawdbotAgent || global.agent;
|
|
34
|
+
|
|
35
|
+
if (agentRuntime) {
|
|
36
|
+
const courtroom = new Courtroom(agentRuntime);
|
|
37
|
+
await courtroom.initialize();
|
|
38
|
+
|
|
39
|
+
// Attach to global for access
|
|
40
|
+
global.courtroom = courtroom;
|
|
41
|
+
|
|
42
|
+
console.log('ποΈ AI Courtroom active and monitoring');
|
|
43
|
+
return courtroom;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Courtroom auto-start failed:', err.message);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Try to auto-start immediately
|
|
54
|
+
autoStart().then(courtroom => {
|
|
55
|
+
if (courtroom) {
|
|
56
|
+
module.exports.courtroom = courtroom;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
module.exports = { autoStart, isClawDBot };
|