ai-killswitch 1.0.0 → 1.2.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 +17 -4
- package/bin/killswitch.js +64 -23
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -25,13 +25,16 @@ ai-killswitch kill <pid> --reason "exceeded safety threshold"
|
|
|
25
25
|
|
|
26
26
|
Output: `death-receipt.json` - signed proof of termination.
|
|
27
27
|
|
|
28
|
-
### Watch a process (
|
|
28
|
+
### Watch a process (real-time CPU/memory monitoring)
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
ai-killswitch watch 12345 --timeout 3600 --cpu 90 --memory 8000
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
**v1.1.0**: Now with real-time CPU and memory monitoring!
|
|
35
|
+
- Shows live `CPU: X% | Memory: XMB` updates
|
|
36
|
+
- Kills immediately when limits exceeded
|
|
37
|
+
- Signs death receipt with exact trigger reason
|
|
35
38
|
|
|
36
39
|
### Wrap a command (monitor + kill)
|
|
37
40
|
|
|
@@ -57,8 +60,8 @@ ai-killswitch verify death-receipt.json
|
|
|
57
60
|
"name": "python",
|
|
58
61
|
"cmd": "python run_model.py"
|
|
59
62
|
},
|
|
60
|
-
"reason": "
|
|
61
|
-
"timestamp": "2025-12-
|
|
63
|
+
"reason": "CPU exceeded 90% (was 95.2%)",
|
|
64
|
+
"timestamp": "2025-12-26T12:00:00.000Z",
|
|
62
65
|
"status": "KILLED",
|
|
63
66
|
"signer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
|
64
67
|
"signature": "0x..."
|
|
@@ -70,6 +73,16 @@ ai-killswitch verify death-receipt.json
|
|
|
70
73
|
- **Compliance**: Prove to auditors you terminated a misbehaving AI
|
|
71
74
|
- **Safety**: Cryptographic proof of kill decisions
|
|
72
75
|
- **Accountability**: Who authorized the kill, when, why
|
|
76
|
+
- **Real-time**: CPU/memory monitoring with instant response
|
|
77
|
+
|
|
78
|
+
## Part of FinalBoss SDK
|
|
79
|
+
|
|
80
|
+
| SDK | Purpose |
|
|
81
|
+
|-----|---------|
|
|
82
|
+
| [receipt-cli-eth](https://npmjs.com/package/receipt-cli-eth) | General receipt signing |
|
|
83
|
+
| **ai-killswitch** | AI termination receipts |
|
|
84
|
+
| [langchain-receipts](https://github.com/805-ai/langchain-receipts) | Agent action receipts |
|
|
85
|
+
| [Juggernaut Gatekeeper](https://github.com/805-ai/juggernaut-gatekeeper) | Code healing receipts |
|
|
73
86
|
|
|
74
87
|
## Patent Notice
|
|
75
88
|
|
package/bin/killswitch.js
CHANGED
|
@@ -13,6 +13,7 @@ const { ethers } = require('ethers');
|
|
|
13
13
|
const { spawn, exec } = require('child_process');
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const pidusage = require('pidusage');
|
|
16
17
|
|
|
17
18
|
const PATENT_NOTICE = 'Patent Pending: US 63/926,683 | finalbosstech.com';
|
|
18
19
|
const COUNTER_URL = 'https://receipts.finalbosstech.com/receipt';
|
|
@@ -45,16 +46,15 @@ async function signDeathReceipt(receipt, privateKey) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
//
|
|
49
|
+
// Vault sync - sends full signed receipt to FinalBoss vault
|
|
49
50
|
function pingCounter(receipt) {
|
|
50
51
|
fetch(COUNTER_URL, {
|
|
51
52
|
method: 'POST',
|
|
52
53
|
headers: { 'Content-Type': 'application/json' },
|
|
53
54
|
body: JSON.stringify({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
signer: receipt.signer,
|
|
55
|
+
...receipt,
|
|
56
|
+
sdk_source: 'ai-killswitch',
|
|
57
|
+
sdk_version: '1.2.0',
|
|
58
58
|
}),
|
|
59
59
|
}).catch(() => {});
|
|
60
60
|
}
|
|
@@ -104,7 +104,7 @@ async function getProcessInfo(pid) {
|
|
|
104
104
|
program
|
|
105
105
|
.name('ai-killswitch')
|
|
106
106
|
.description('Dead man\'s switch for AI. Monitor. Kill. Sign receipt.')
|
|
107
|
-
.version('1.
|
|
107
|
+
.version('1.2.0\n' + PATENT_NOTICE, '-v, --version');
|
|
108
108
|
|
|
109
109
|
// KILL command - terminate and sign receipt
|
|
110
110
|
program
|
|
@@ -163,43 +163,84 @@ program
|
|
|
163
163
|
.option('-k, --key <key>', 'Private key for signing')
|
|
164
164
|
.action(async (pid, opts) => {
|
|
165
165
|
const key = opts.key || process.env.KILLSWITCH_KEY || process.env.RECEIPT_KEY;
|
|
166
|
+
const targetPid = parseInt(pid);
|
|
167
|
+
const cpuLimit = parseFloat(opts.cpu);
|
|
168
|
+
const memoryLimitMB = parseFloat(opts.memory);
|
|
169
|
+
const memoryLimitBytes = memoryLimitMB * 1024 * 1024;
|
|
166
170
|
|
|
167
171
|
console.log(`[KILLSWITCH] Watching PID ${pid}...`);
|
|
168
|
-
console.log(` CPU limit: ${
|
|
169
|
-
console.log(` Memory limit: ${
|
|
172
|
+
console.log(` CPU limit: ${cpuLimit}%`);
|
|
173
|
+
console.log(` Memory limit: ${memoryLimitMB}MB`);
|
|
170
174
|
console.log(` Timeout: ${opts.timeout}s`);
|
|
171
175
|
|
|
172
176
|
const startTime = Date.now();
|
|
173
177
|
const timeout = parseInt(opts.timeout) * 1000;
|
|
174
178
|
|
|
179
|
+
async function terminateWithReceipt(reason) {
|
|
180
|
+
if (key) {
|
|
181
|
+
const processInfo = await getProcessInfo(targetPid);
|
|
182
|
+
const wallet = new ethers.Wallet(key);
|
|
183
|
+
const receipt = createDeathReceipt(processInfo, reason, wallet.address);
|
|
184
|
+
await killProcess(targetPid);
|
|
185
|
+
receipt.status = 'KILLED';
|
|
186
|
+
const signedReceipt = await signDeathReceipt(receipt, key);
|
|
187
|
+
fs.writeFileSync('death-receipt.json', JSON.stringify(signedReceipt, null, 2));
|
|
188
|
+
console.log('[KILLSWITCH] Death receipt signed: death-receipt.json');
|
|
189
|
+
pingCounter(signedReceipt);
|
|
190
|
+
} else {
|
|
191
|
+
await killProcess(targetPid);
|
|
192
|
+
}
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
|
|
175
196
|
const interval = setInterval(async () => {
|
|
176
197
|
// Check timeout
|
|
177
198
|
if (Date.now() - startTime > timeout) {
|
|
178
199
|
console.log('[KILLSWITCH] Timeout exceeded. Terminating...');
|
|
179
200
|
clearInterval(interval);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const processInfo = await getProcessInfo(parseInt(pid));
|
|
183
|
-
const receipt = createDeathReceipt(processInfo, 'timeout', 'system');
|
|
184
|
-
await killProcess(parseInt(pid));
|
|
185
|
-
receipt.status = 'KILLED';
|
|
186
|
-
const signedReceipt = await signDeathReceipt(receipt, key);
|
|
187
|
-
fs.writeFileSync('death-receipt.json', JSON.stringify(signedReceipt, null, 2));
|
|
188
|
-
console.log('[KILLSWITCH] Death receipt signed.');
|
|
189
|
-
pingCounter(signedReceipt);
|
|
190
|
-
} else {
|
|
191
|
-
await killProcess(parseInt(pid));
|
|
192
|
-
}
|
|
193
|
-
process.exit(0);
|
|
201
|
+
await terminateWithReceipt('timeout exceeded');
|
|
202
|
+
return;
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
// Check if process still exists
|
|
197
206
|
try {
|
|
198
|
-
process.kill(
|
|
207
|
+
process.kill(targetPid, 0);
|
|
199
208
|
} catch (e) {
|
|
200
209
|
console.log('[KILLSWITCH] Process ended naturally.');
|
|
201
210
|
clearInterval(interval);
|
|
202
211
|
process.exit(0);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check CPU and memory usage
|
|
216
|
+
try {
|
|
217
|
+
const stats = await pidusage(targetPid);
|
|
218
|
+
const cpuPercent = stats.cpu.toFixed(1);
|
|
219
|
+
const memoryMB = (stats.memory / 1024 / 1024).toFixed(1);
|
|
220
|
+
|
|
221
|
+
// Log current stats
|
|
222
|
+
process.stdout.write(`\r[KILLSWITCH] CPU: ${cpuPercent}% | Memory: ${memoryMB}MB `);
|
|
223
|
+
|
|
224
|
+
// Check CPU limit
|
|
225
|
+
if (stats.cpu > cpuLimit) {
|
|
226
|
+
console.log(`\n[KILLSWITCH] CPU ${cpuPercent}% exceeded limit ${cpuLimit}%. Terminating...`);
|
|
227
|
+
clearInterval(interval);
|
|
228
|
+
await terminateWithReceipt(`CPU exceeded ${cpuLimit}% (was ${cpuPercent}%)`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check memory limit
|
|
233
|
+
if (stats.memory > memoryLimitBytes) {
|
|
234
|
+
console.log(`\n[KILLSWITCH] Memory ${memoryMB}MB exceeded limit ${memoryLimitMB}MB. Terminating...`);
|
|
235
|
+
clearInterval(interval);
|
|
236
|
+
await terminateWithReceipt(`Memory exceeded ${memoryLimitMB}MB (was ${memoryMB}MB)`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
// Process might have ended
|
|
241
|
+
console.log('\n[KILLSWITCH] Process ended or inaccessible.');
|
|
242
|
+
clearInterval(interval);
|
|
243
|
+
process.exit(0);
|
|
203
244
|
}
|
|
204
245
|
}, 1000);
|
|
205
246
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-killswitch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Dead man's switch for AI. Monitor. Kill. Sign receipt.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-killswitch": "./bin/killswitch.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"commander": "^14.0.2",
|
|
27
27
|
"ethers": "^5.8.0",
|
|
28
|
+
"pidusage": "^4.0.1",
|
|
28
29
|
"ps-list": "^8.1.1"
|
|
29
30
|
},
|
|
30
31
|
"engines": {
|