@clawnitor/plugin 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 +60 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +16 -0
- package/dist/config.js.map +1 -0
- package/dist/event-builder.d.ts +15 -0
- package/dist/event-builder.d.ts.map +1 -0
- package/dist/event-builder.js +34 -0
- package/dist/event-builder.js.map +1 -0
- package/dist/hooks/lifecycle.d.ts +18 -0
- package/dist/hooks/lifecycle.d.ts.map +1 -0
- package/dist/hooks/lifecycle.js +79 -0
- package/dist/hooks/lifecycle.js.map +1 -0
- package/dist/hooks/llm.d.ts +11 -0
- package/dist/hooks/llm.d.ts.map +1 -0
- package/dist/hooks/llm.js +35 -0
- package/dist/hooks/llm.js.map +1 -0
- package/dist/hooks/message.d.ts +14 -0
- package/dist/hooks/message.d.ts.map +1 -0
- package/dist/hooks/message.js +52 -0
- package/dist/hooks/message.js.map +1 -0
- package/dist/hooks/tool-call.d.ts +20 -0
- package/dist/hooks/tool-call.d.ts.map +1 -0
- package/dist/hooks/tool-call.js +89 -0
- package/dist/hooks/tool-call.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-switch/kill-state.d.ts +16 -0
- package/dist/kill-switch/kill-state.d.ts.map +1 -0
- package/dist/kill-switch/kill-state.js +25 -0
- package/dist/kill-switch/kill-state.js.map +1 -0
- package/dist/kill-switch/local-failsafe.d.ts +19 -0
- package/dist/kill-switch/local-failsafe.d.ts.map +1 -0
- package/dist/kill-switch/local-failsafe.js +64 -0
- package/dist/kill-switch/local-failsafe.js.map +1 -0
- package/dist/redaction.d.ts +3 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +46 -0
- package/dist/redaction.js.map +1 -0
- package/dist/rule-cache.d.ts +42 -0
- package/dist/rule-cache.d.ts.map +1 -0
- package/dist/rule-cache.js +138 -0
- package/dist/rule-cache.js.map +1 -0
- package/dist/severity.d.ts +8 -0
- package/dist/severity.d.ts.map +1 -0
- package/dist/severity.js +38 -0
- package/dist/severity.js.map +1 -0
- package/dist/transport/https-sender.d.ts +18 -0
- package/dist/transport/https-sender.d.ts.map +1 -0
- package/dist/transport/https-sender.js +62 -0
- package/dist/transport/https-sender.js.map +1 -0
- package/dist/transport/sqlite-cache.d.ts +12 -0
- package/dist/transport/sqlite-cache.d.ts.map +1 -0
- package/dist/transport/sqlite-cache.js +74 -0
- package/dist/transport/sqlite-cache.js.map +1 -0
- package/dist/transport/websocket-client.d.ts +19 -0
- package/dist/transport/websocket-client.d.ts.map +1 -0
- package/dist/transport/websocket-client.js +79 -0
- package/dist/transport/websocket-client.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface KillStateData {
|
|
2
|
+
killed: boolean;
|
|
3
|
+
reason?: string;
|
|
4
|
+
killedAt?: Date;
|
|
5
|
+
}
|
|
6
|
+
declare class KillState {
|
|
7
|
+
private state;
|
|
8
|
+
isKilled(): boolean;
|
|
9
|
+
getReason(): string | undefined;
|
|
10
|
+
setKilled(reason: string): void;
|
|
11
|
+
clearKilled(): void;
|
|
12
|
+
getState(): Readonly<KillStateData>;
|
|
13
|
+
}
|
|
14
|
+
export declare const killState: KillState;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=kill-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-state.d.ts","sourceRoot":"","sources":["../../src/kill-switch/kill-state.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,cAAM,SAAS;IACb,OAAO,CAAC,KAAK,CAAoC;IAEjD,QAAQ,IAAI,OAAO;IAInB,SAAS,IAAI,MAAM,GAAG,SAAS;IAI/B,SAAS,CAAC,MAAM,EAAE,MAAM;IAQxB,WAAW;IAIX,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;CAGpC;AAGD,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class KillState {
|
|
2
|
+
state = { killed: false };
|
|
3
|
+
isKilled() {
|
|
4
|
+
return this.state.killed;
|
|
5
|
+
}
|
|
6
|
+
getReason() {
|
|
7
|
+
return this.state.reason;
|
|
8
|
+
}
|
|
9
|
+
setKilled(reason) {
|
|
10
|
+
this.state = {
|
|
11
|
+
killed: true,
|
|
12
|
+
reason,
|
|
13
|
+
killedAt: new Date(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
clearKilled() {
|
|
17
|
+
this.state = { killed: false };
|
|
18
|
+
}
|
|
19
|
+
getState() {
|
|
20
|
+
return { ...this.state };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Singleton
|
|
24
|
+
export const killState = new KillState();
|
|
25
|
+
//# sourceMappingURL=kill-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-state.js","sourceRoot":"","sources":["../../src/kill-switch/kill-state.ts"],"names":[],"mappings":"AAMA,MAAM,SAAS;IACL,KAAK,GAAkB,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAEjD,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,KAAK,GAAG;YACX,MAAM,EAAE,IAAI;YACZ,MAAM;YACN,QAAQ,EAAE,IAAI,IAAI,EAAE;SACrB,CAAC;IACJ,CAAC;IAED,WAAW;QACT,IAAI,CAAC,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,YAAY;AACZ,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PluginConfig } from "../config.js";
|
|
2
|
+
interface FailsafeResult {
|
|
3
|
+
block: boolean;
|
|
4
|
+
reason: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class LocalFailsafe {
|
|
7
|
+
private sessionSpend;
|
|
8
|
+
private rateCounter;
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: PluginConfig);
|
|
11
|
+
addSpend(costUsd: number): void;
|
|
12
|
+
recordToolCall(): void;
|
|
13
|
+
resetSession(): void;
|
|
14
|
+
check(toolName: string): FailsafeResult;
|
|
15
|
+
getSessionSpend(): number;
|
|
16
|
+
getCallsPerMinute(): number;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=local-failsafe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-failsafe.d.ts","sourceRoot":"","sources":["../../src/kill-switch/local-failsafe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,UAAU,cAAc;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD,qBAAa,aAAa;IACxB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAIhC,QAAQ,CAAC,OAAO,EAAE,MAAM;IAIxB,cAAc;IAId,YAAY;IAIZ,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc;IAiCvC,eAAe,IAAI,MAAM;IAIzB,iBAAiB,IAAI,MAAM;CAG5B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Sliding window rate counter
|
|
2
|
+
class RateCounter {
|
|
3
|
+
timestamps = [];
|
|
4
|
+
record() {
|
|
5
|
+
this.timestamps.push(Date.now());
|
|
6
|
+
}
|
|
7
|
+
getCallsPerMinute() {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const oneMinuteAgo = now - 60_000;
|
|
10
|
+
this.timestamps = this.timestamps.filter((t) => t > oneMinuteAgo);
|
|
11
|
+
return this.timestamps.length;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class LocalFailsafe {
|
|
15
|
+
sessionSpend = 0;
|
|
16
|
+
rateCounter = new RateCounter();
|
|
17
|
+
config;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
addSpend(costUsd) {
|
|
22
|
+
this.sessionSpend += costUsd;
|
|
23
|
+
}
|
|
24
|
+
recordToolCall() {
|
|
25
|
+
this.rateCounter.record();
|
|
26
|
+
}
|
|
27
|
+
resetSession() {
|
|
28
|
+
this.sessionSpend = 0;
|
|
29
|
+
}
|
|
30
|
+
check(toolName) {
|
|
31
|
+
// 1. Spend circuit breaker
|
|
32
|
+
if (this.sessionSpend >= this.config.spendLimit) {
|
|
33
|
+
return {
|
|
34
|
+
block: true,
|
|
35
|
+
reason: `Clawnitor: Spend limit reached ($${this.sessionSpend.toFixed(2)} >= $${this.config.spendLimit}). Agent paused.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// 2. Rate limiter
|
|
39
|
+
const callsPerMin = this.rateCounter.getCallsPerMinute();
|
|
40
|
+
if (callsPerMin >= this.config.rateLimit) {
|
|
41
|
+
return {
|
|
42
|
+
block: true,
|
|
43
|
+
reason: `Clawnitor: Rate limit reached (${callsPerMin} calls/min >= ${this.config.rateLimit}). Agent paused.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// 3. Tool blocklist
|
|
47
|
+
const normalizedTool = toolName.toLowerCase();
|
|
48
|
+
const blocked = this.config.toolBlocklist.some((t) => t.toLowerCase() === normalizedTool);
|
|
49
|
+
if (blocked) {
|
|
50
|
+
return {
|
|
51
|
+
block: true,
|
|
52
|
+
reason: `Clawnitor: Tool "${toolName}" is blocklisted. Agent paused.`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { block: false, reason: "" };
|
|
56
|
+
}
|
|
57
|
+
getSessionSpend() {
|
|
58
|
+
return this.sessionSpend;
|
|
59
|
+
}
|
|
60
|
+
getCallsPerMinute() {
|
|
61
|
+
return this.rateCounter.getCallsPerMinute();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=local-failsafe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-failsafe.js","sourceRoot":"","sources":["../../src/kill-switch/local-failsafe.ts"],"names":[],"mappings":"AAOA,8BAA8B;AAC9B,MAAM,WAAW;IACP,UAAU,GAAa,EAAE,CAAC;IAElC,MAAM;QACJ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,iBAAiB;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,aAAa;IAChB,YAAY,GAAG,CAAC,CAAC;IACjB,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;IAChC,MAAM,CAAe;IAE7B,YAAY,MAAoB;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,QAAQ,CAAC,OAAe;QACtB,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC;IAC/B,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED,YAAY;QACV,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,QAAgB;QACpB,2BAA2B;QAC3B,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAChD,OAAO;gBACL,KAAK,EAAE,IAAI;gBACX,MAAM,EAAE,oCAAoC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,UAAU,kBAAkB;aACzH,CAAC;QACJ,CAAC;QAED,kBAAkB;QAClB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACzD,IAAI,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACzC,OAAO;gBACL,KAAK,EAAE,IAAI;gBACX,MAAM,EAAE,kCAAkC,WAAW,iBAAiB,IAAI,CAAC,MAAM,CAAC,SAAS,kBAAkB;aAC9G,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAC5C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,cAAc,CAC1C,CAAC;QACF,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;gBACL,KAAK,EAAE,IAAI;gBACX,MAAM,EAAE,oBAAoB,QAAQ,iCAAiC;aACtE,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtC,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,iBAAiB;QACf,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,UA+B5B,CAAC;AAEF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAW/D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const DEFAULT_PATTERNS = [
|
|
2
|
+
// OpenAI
|
|
3
|
+
"sk-[a-zA-Z0-9]{20,}",
|
|
4
|
+
"sk-proj-[a-zA-Z0-9]{20,}",
|
|
5
|
+
// Anthropic
|
|
6
|
+
"sk-ant-[a-zA-Z0-9\\-]{20,}",
|
|
7
|
+
// Stripe
|
|
8
|
+
"sk_live_[a-zA-Z0-9]{20,}",
|
|
9
|
+
"sk_test_[a-zA-Z0-9]{20,}",
|
|
10
|
+
"pk_live_[a-zA-Z0-9]{20,}",
|
|
11
|
+
"pk_test_[a-zA-Z0-9]{20,}",
|
|
12
|
+
// Bearer tokens / JWTs
|
|
13
|
+
"Bearer\\s+[a-zA-Z0-9._\\-]{20,}",
|
|
14
|
+
"eyJ[a-zA-Z0-9_\\-]{10,}\\.[a-zA-Z0-9_\\-]{10,}\\.[a-zA-Z0-9_\\-]{10,}",
|
|
15
|
+
// AWS
|
|
16
|
+
"AKIA[0-9A-Z]{16}",
|
|
17
|
+
// GitHub
|
|
18
|
+
"ghp_[a-zA-Z0-9]{36}",
|
|
19
|
+
"gho_[a-zA-Z0-9]{36}",
|
|
20
|
+
"github_pat_[a-zA-Z0-9_]{20,}",
|
|
21
|
+
// Slack
|
|
22
|
+
"xoxb-[a-zA-Z0-9\\-]+",
|
|
23
|
+
"xoxp-[a-zA-Z0-9\\-]+",
|
|
24
|
+
// Private keys
|
|
25
|
+
"-----BEGIN\\s+(RSA\\s+)?PRIVATE\\s+KEY-----",
|
|
26
|
+
// Passwords in URLs/config
|
|
27
|
+
"password\\s*[=:]\\s*\\S+",
|
|
28
|
+
// Database connection strings with passwords
|
|
29
|
+
"(?:postgres|mysql|mongodb)://[^:]+:[^@]+@",
|
|
30
|
+
// Generic secret assignments
|
|
31
|
+
"(?:secret|token|api_key|apikey)\\s*[=:]\\s*\\S{8,}",
|
|
32
|
+
];
|
|
33
|
+
export function redact(text, patterns) {
|
|
34
|
+
let result = text;
|
|
35
|
+
for (const pattern of patterns) {
|
|
36
|
+
try {
|
|
37
|
+
const regex = new RegExp(pattern, "gi");
|
|
38
|
+
result = result.replace(regex, "[REDACTED]");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Skip invalid regex patterns
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,SAAS;IACT,qBAAqB;IACrB,0BAA0B;IAC1B,YAAY;IACZ,4BAA4B;IAC5B,SAAS;IACT,0BAA0B;IAC1B,0BAA0B;IAC1B,0BAA0B;IAC1B,0BAA0B;IAC1B,uBAAuB;IACvB,iCAAiC;IACjC,uEAAuE;IACvE,MAAM;IACN,kBAAkB;IAClB,SAAS;IACT,qBAAqB;IACrB,qBAAqB;IACrB,8BAA8B;IAC9B,QAAQ;IACR,sBAAsB;IACtB,sBAAsB;IACtB,eAAe;IACf,6CAA6C;IAC7C,2BAA2B;IAC3B,0BAA0B;IAC1B,6CAA6C;IAC7C,2CAA2C;IAC3C,6BAA6B;IAC7B,oDAAoD;CACrD,CAAC;AAEF,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,QAAkB;IACrD,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACxC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PluginConfig } from "./config.js";
|
|
2
|
+
export interface CachedRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
rule_type: string;
|
|
6
|
+
config: any;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface RuleCheckResult {
|
|
10
|
+
blocked: boolean;
|
|
11
|
+
ruleName?: string;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
interface EventRecord {
|
|
15
|
+
timestamp: number;
|
|
16
|
+
toolName?: string;
|
|
17
|
+
eventType?: string;
|
|
18
|
+
costUsd?: number;
|
|
19
|
+
action?: string;
|
|
20
|
+
target?: string;
|
|
21
|
+
rawSnippet?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class RuleCache {
|
|
24
|
+
private rules;
|
|
25
|
+
private config;
|
|
26
|
+
private fetchTimer;
|
|
27
|
+
private recentEvents;
|
|
28
|
+
private readonly MAX_RECENT_EVENTS;
|
|
29
|
+
constructor(config: PluginConfig);
|
|
30
|
+
start(): void;
|
|
31
|
+
stop(): void;
|
|
32
|
+
private fetchRules;
|
|
33
|
+
recordEvent(event: EventRecord): void;
|
|
34
|
+
checkBeforeToolCall(toolName: string, params?: Record<string, unknown>): RuleCheckResult;
|
|
35
|
+
private evaluateRule;
|
|
36
|
+
private evaluateKeyword;
|
|
37
|
+
private evaluateRate;
|
|
38
|
+
private evaluateThreshold;
|
|
39
|
+
getRuleCount(): number;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=rule-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-cache.d.ts","sourceRoot":"","sources":["../src/rule-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAO;gBAE7B,MAAM,EAAE,YAAY;IAIhC,KAAK;IAML,IAAI;YAOU,UAAU;IAiBxB,WAAW,CAAC,KAAK,EAAE,WAAW;IAS9B,mBAAmB,CACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,eAAe;IAelB,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,iBAAiB;IA8BzB,YAAY,IAAI,MAAM;CAGvB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export class RuleCache {
|
|
2
|
+
rules = [];
|
|
3
|
+
config;
|
|
4
|
+
fetchTimer = null;
|
|
5
|
+
recentEvents = [];
|
|
6
|
+
MAX_RECENT_EVENTS = 500;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
start() {
|
|
11
|
+
// Fetch immediately, then every 60s
|
|
12
|
+
this.fetchRules();
|
|
13
|
+
this.fetchTimer = setInterval(() => this.fetchRules(), 60_000);
|
|
14
|
+
}
|
|
15
|
+
stop() {
|
|
16
|
+
if (this.fetchTimer) {
|
|
17
|
+
clearInterval(this.fetchTimer);
|
|
18
|
+
this.fetchTimer = null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async fetchRules() {
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(`${this.config.backendUrl}/api/rules`, {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
26
|
+
},
|
|
27
|
+
signal: AbortSignal.timeout(5000),
|
|
28
|
+
});
|
|
29
|
+
if (res.ok) {
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
this.rules = (data.rules || []).filter((r) => r.enabled);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Keep existing cached rules on fetch failure
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
recordEvent(event) {
|
|
39
|
+
this.recentEvents.push(event);
|
|
40
|
+
// Trim to max size
|
|
41
|
+
if (this.recentEvents.length > this.MAX_RECENT_EVENTS) {
|
|
42
|
+
this.recentEvents = this.recentEvents.slice(-this.MAX_RECENT_EVENTS);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Evaluate all cached pattern rules against the current tool call + recent history
|
|
46
|
+
checkBeforeToolCall(toolName, params) {
|
|
47
|
+
const paramsStr = params ? JSON.stringify(params).slice(0, 500) : "";
|
|
48
|
+
for (const rule of this.rules) {
|
|
49
|
+
if (rule.rule_type === "nl")
|
|
50
|
+
continue; // Can't evaluate locally
|
|
51
|
+
const result = this.evaluateRule(rule, toolName, paramsStr);
|
|
52
|
+
if (result.blocked) {
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { blocked: false };
|
|
57
|
+
}
|
|
58
|
+
evaluateRule(rule, toolName, paramsStr) {
|
|
59
|
+
const config = rule.config;
|
|
60
|
+
if (rule.rule_type === "keyword") {
|
|
61
|
+
return this.evaluateKeyword(rule, toolName, paramsStr);
|
|
62
|
+
}
|
|
63
|
+
if (rule.rule_type === "rate") {
|
|
64
|
+
return this.evaluateRate(rule);
|
|
65
|
+
}
|
|
66
|
+
if (rule.rule_type === "threshold") {
|
|
67
|
+
return this.evaluateThreshold(rule);
|
|
68
|
+
}
|
|
69
|
+
return { blocked: false };
|
|
70
|
+
}
|
|
71
|
+
evaluateKeyword(rule, toolName, paramsStr) {
|
|
72
|
+
const config = rule.config;
|
|
73
|
+
const keywords = config.keywords || [];
|
|
74
|
+
const caseSensitive = config.caseSensitive || false;
|
|
75
|
+
const matchMode = config.matchMode || "any";
|
|
76
|
+
const searchable = [toolName, paramsStr].join(" ");
|
|
77
|
+
const text = caseSensitive ? searchable : searchable.toLowerCase();
|
|
78
|
+
const matched = keywords.filter((kw) => {
|
|
79
|
+
const k = caseSensitive ? kw : kw.toLowerCase();
|
|
80
|
+
return text.includes(k);
|
|
81
|
+
});
|
|
82
|
+
const triggered = matchMode === "any" ? matched.length > 0 : matched.length === keywords.length;
|
|
83
|
+
if (triggered) {
|
|
84
|
+
return {
|
|
85
|
+
blocked: true,
|
|
86
|
+
ruleName: rule.name,
|
|
87
|
+
reason: `Clawnitor: Rule "${rule.name}" blocked — keyword match: ${matched.join(", ")}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { blocked: false };
|
|
91
|
+
}
|
|
92
|
+
evaluateRate(rule) {
|
|
93
|
+
const config = rule.config;
|
|
94
|
+
const windowMs = (config.windowMinutes || 10) * 60 * 1000;
|
|
95
|
+
const maxCount = config.maxCount || 100;
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
let filtered = this.recentEvents.filter((e) => e.timestamp > now - windowMs);
|
|
98
|
+
if (config.eventType) {
|
|
99
|
+
filtered = filtered.filter((e) => e.eventType === config.eventType);
|
|
100
|
+
}
|
|
101
|
+
if (config.toolName) {
|
|
102
|
+
filtered = filtered.filter((e) => e.toolName === config.toolName);
|
|
103
|
+
}
|
|
104
|
+
if (filtered.length >= maxCount) {
|
|
105
|
+
return {
|
|
106
|
+
blocked: true,
|
|
107
|
+
ruleName: rule.name,
|
|
108
|
+
reason: `Clawnitor: Rule "${rule.name}" blocked — ${filtered.length} events in ${config.windowMinutes}min (max: ${maxCount})`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return { blocked: false };
|
|
112
|
+
}
|
|
113
|
+
evaluateThreshold(rule) {
|
|
114
|
+
const config = rule.config;
|
|
115
|
+
const windowMs = (config.windowMinutes || 60) * 60 * 1000;
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const recentInWindow = this.recentEvents.filter((e) => e.timestamp > now - windowMs);
|
|
118
|
+
let sum = 0;
|
|
119
|
+
for (const event of recentInWindow) {
|
|
120
|
+
if (config.field === "cost_usd" && event.costUsd) {
|
|
121
|
+
sum += event.costUsd;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const triggered = config.operator === "gt" ? sum > config.value : sum < config.value;
|
|
125
|
+
if (triggered) {
|
|
126
|
+
return {
|
|
127
|
+
blocked: true,
|
|
128
|
+
ruleName: rule.name,
|
|
129
|
+
reason: `Clawnitor: Rule "${rule.name}" blocked — ${config.field} = ${sum.toFixed(4)} (${config.operator} ${config.value})`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { blocked: false };
|
|
133
|
+
}
|
|
134
|
+
getRuleCount() {
|
|
135
|
+
return this.rules.length;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=rule-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-cache.js","sourceRoot":"","sources":["../src/rule-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EventType, SeverityHint } from "@clawnitor/shared";
|
|
2
|
+
export interface RateTracker {
|
|
3
|
+
getRate(): number;
|
|
4
|
+
record(): void;
|
|
5
|
+
}
|
|
6
|
+
export declare function createRateTracker(): RateTracker;
|
|
7
|
+
export declare function assignSeverity(eventType: EventType, metadata: Record<string, unknown>, rateTracker?: RateTracker): SeverityHint;
|
|
8
|
+
//# sourceMappingURL=severity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"severity.d.ts","sourceRoot":"","sources":["../src/severity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAkB/C;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,WAAW,CAAC,EAAE,WAAW,GACxB,YAAY,CAqBd"}
|
package/dist/severity.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function createRateTracker() {
|
|
2
|
+
const window = []; // timestamps
|
|
3
|
+
const WINDOW_MS = 5 * 60 * 1000; // 5 minutes
|
|
4
|
+
return {
|
|
5
|
+
getRate() {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
// Remove entries outside window
|
|
8
|
+
while (window.length > 0 && window[0] < now - WINDOW_MS) {
|
|
9
|
+
window.shift();
|
|
10
|
+
}
|
|
11
|
+
// Convert to per-minute rate
|
|
12
|
+
return window.length / 5;
|
|
13
|
+
},
|
|
14
|
+
record() {
|
|
15
|
+
window.push(Date.now());
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function assignSeverity(eventType, metadata, rateTracker) {
|
|
20
|
+
// Critical: error in metadata
|
|
21
|
+
if (metadata.error) {
|
|
22
|
+
return "elevated";
|
|
23
|
+
}
|
|
24
|
+
// Critical: high spend
|
|
25
|
+
if (typeof metadata.cost_usd === "number" && metadata.cost_usd > 1) {
|
|
26
|
+
return "elevated";
|
|
27
|
+
}
|
|
28
|
+
// Rate-based: flag elevated if tool call rate exceeds 30/min
|
|
29
|
+
// (normal agents do 5-15/min, 30+ suggests a loop or runaway)
|
|
30
|
+
if (rateTracker && eventType === "tool_use") {
|
|
31
|
+
const rate = rateTracker.getRate();
|
|
32
|
+
if (rate > 30) {
|
|
33
|
+
return "elevated";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return "normal";
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=severity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"severity.js","sourceRoot":"","sources":["../src/severity.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC,CAAC,aAAa;IAC1C,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;IAE7C,OAAO;QACL,OAAO;YACL,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,gCAAgC;YAChC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,SAAS,EAAE,CAAC;gBACxD,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YACD,6BAA6B;YAC7B,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM;YACJ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,SAAoB,EACpB,QAAiC,EACjC,WAAyB;IAEzB,8BAA8B;IAC9B,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,uBAAuB;IACvB,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACnE,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,6DAA6D;IAC7D,8DAA8D;IAC9D,IAAI,WAAW,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC;YACd,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ClawnitorEvent } from "@clawnitor/shared";
|
|
2
|
+
import type { PluginConfig } from "../config.js";
|
|
3
|
+
export declare class HttpsSender {
|
|
4
|
+
private buffer;
|
|
5
|
+
private timer;
|
|
6
|
+
private config;
|
|
7
|
+
private onKillState?;
|
|
8
|
+
private onFlushFail?;
|
|
9
|
+
constructor(config: PluginConfig, opts?: {
|
|
10
|
+
onKillState?: (killed: boolean, reason?: string) => void;
|
|
11
|
+
onFlushFail?: (events: ClawnitorEvent[]) => void;
|
|
12
|
+
});
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
enqueue(event: ClawnitorEvent): void;
|
|
16
|
+
private flush;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=https-sender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"https-sender.d.ts","sourceRoot":"","sources":["../../src/transport/https-sender.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AAE9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,WAAW,CAAC,CAA6C;IACjE,OAAO,CAAC,WAAW,CAAC,CAAqC;gBAGvD,MAAM,EAAE,YAAY,EACpB,IAAI,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;QACzD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;KAClD;IAOH,KAAK;IAIL,IAAI;IASJ,OAAO,CAAC,KAAK,EAAE,cAAc;YAOf,KAAK;CA+BpB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EVENT_BATCH_SIZE, EVENT_BATCH_INTERVAL_MS } from "@clawnitor/shared";
|
|
2
|
+
export class HttpsSender {
|
|
3
|
+
buffer = [];
|
|
4
|
+
timer = null;
|
|
5
|
+
config;
|
|
6
|
+
onKillState;
|
|
7
|
+
onFlushFail;
|
|
8
|
+
constructor(config, opts) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.onKillState = opts?.onKillState;
|
|
11
|
+
this.onFlushFail = opts?.onFlushFail;
|
|
12
|
+
}
|
|
13
|
+
start() {
|
|
14
|
+
this.timer = setInterval(() => this.flush(), EVENT_BATCH_INTERVAL_MS);
|
|
15
|
+
}
|
|
16
|
+
stop() {
|
|
17
|
+
if (this.timer) {
|
|
18
|
+
clearInterval(this.timer);
|
|
19
|
+
this.timer = null;
|
|
20
|
+
}
|
|
21
|
+
// Final flush
|
|
22
|
+
this.flush();
|
|
23
|
+
}
|
|
24
|
+
enqueue(event) {
|
|
25
|
+
this.buffer.push(event);
|
|
26
|
+
if (this.buffer.length >= EVENT_BATCH_SIZE) {
|
|
27
|
+
this.flush();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async flush() {
|
|
31
|
+
if (this.buffer.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
const batch = this.buffer.splice(0, EVENT_BATCH_SIZE);
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${this.config.backendUrl}/api/events`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ events: batch }),
|
|
42
|
+
signal: AbortSignal.timeout(10_000),
|
|
43
|
+
});
|
|
44
|
+
if (response.ok) {
|
|
45
|
+
const data = (await response.json());
|
|
46
|
+
if (this.onKillState) {
|
|
47
|
+
this.onKillState(data.kill_state.killed, data.kill_state.reason);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (response.status >= 500) {
|
|
51
|
+
// Server error — cache for retry
|
|
52
|
+
this.onFlushFail?.(batch);
|
|
53
|
+
}
|
|
54
|
+
// 4xx errors are dropped (bad data, auth issues)
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Network error — cache for retry
|
|
58
|
+
this.onFlushFail?.(batch);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=https-sender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"https-sender.js","sourceRoot":"","sources":["../../src/transport/https-sender.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG9E,MAAM,OAAO,WAAW;IACd,MAAM,GAAqB,EAAE,CAAC;IAC9B,KAAK,GAA0C,IAAI,CAAC;IACpD,MAAM,CAAe;IACrB,WAAW,CAA8C;IACzD,WAAW,CAAsC;IAEzD,YACE,MAAoB,EACpB,IAGC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,IAAI,EAAE,WAAW,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,IAAI,EAAE,WAAW,CAAC;IACvC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,uBAAuB,CAAC,CAAC;IACxE,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,cAAc;QACd,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,OAAO,CAAC,KAAqB;QAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;YAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QAEtD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,aAAa,EAAE;gBACnE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;iBAC9C;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;aACpC,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyB,CAAC;gBAC7D,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBACrB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;iBAAM,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAClC,iCAAiC;gBACjC,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,iDAAiD;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;YAClC,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClawnitorEvent } from "@clawnitor/shared";
|
|
2
|
+
export declare class SqliteCache {
|
|
3
|
+
private db;
|
|
4
|
+
constructor(dbPath?: string);
|
|
5
|
+
cache(events: ClawnitorEvent[]): void;
|
|
6
|
+
flush(limit?: number): ClawnitorEvent[];
|
|
7
|
+
count(): number;
|
|
8
|
+
sizeBytes(): number;
|
|
9
|
+
prune(): void;
|
|
10
|
+
close(): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=sqlite-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-cache.d.ts","sourceRoot":"","sources":["../../src/transport/sqlite-cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKxD,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,CAAC,EAAE,MAAM;IAa3B,KAAK,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI;IAarC,KAAK,CAAC,KAAK,GAAE,MAAW,GAAG,cAAc,EAAE;IAkB3C,KAAK,IAAI,MAAM;IAOf,SAAS,IAAI,MAAM;IASnB,KAAK,IAAI,IAAI;IAkBb,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { CACHE_SIZE_LIMIT, CACHE_AGE_LIMIT } from "@clawnitor/shared";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
export class SqliteCache {
|
|
6
|
+
db;
|
|
7
|
+
constructor(dbPath) {
|
|
8
|
+
const path = dbPath || join(tmpdir(), "clawnitor-cache.db");
|
|
9
|
+
this.db = new Database(path);
|
|
10
|
+
this.db.pragma("journal_mode = WAL");
|
|
11
|
+
this.db.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS cached_events (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
payload TEXT NOT NULL,
|
|
15
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
16
|
+
)
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
19
|
+
cache(events) {
|
|
20
|
+
const insert = this.db.prepare("INSERT INTO cached_events (payload) VALUES (?)");
|
|
21
|
+
const tx = this.db.transaction((evts) => {
|
|
22
|
+
for (const event of evts) {
|
|
23
|
+
insert.run(JSON.stringify(event));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
tx(events);
|
|
27
|
+
this.prune();
|
|
28
|
+
}
|
|
29
|
+
flush(limit = 50) {
|
|
30
|
+
const rows = this.db
|
|
31
|
+
.prepare("SELECT id, payload FROM cached_events ORDER BY id ASC LIMIT ?")
|
|
32
|
+
.all(limit);
|
|
33
|
+
if (rows.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const events = rows.map((r) => JSON.parse(r.payload));
|
|
36
|
+
const ids = rows.map((r) => r.id);
|
|
37
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
38
|
+
this.db
|
|
39
|
+
.prepare(`DELETE FROM cached_events WHERE id IN (${placeholders})`)
|
|
40
|
+
.run(...ids);
|
|
41
|
+
return events;
|
|
42
|
+
}
|
|
43
|
+
count() {
|
|
44
|
+
const row = this.db
|
|
45
|
+
.prepare("SELECT COUNT(*) as count FROM cached_events")
|
|
46
|
+
.get();
|
|
47
|
+
return row.count;
|
|
48
|
+
}
|
|
49
|
+
sizeBytes() {
|
|
50
|
+
const row = this.db
|
|
51
|
+
.prepare("SELECT COALESCE(SUM(LENGTH(payload)), 0) as size FROM cached_events")
|
|
52
|
+
.get();
|
|
53
|
+
return row.size;
|
|
54
|
+
}
|
|
55
|
+
prune() {
|
|
56
|
+
// Age-based eviction
|
|
57
|
+
const maxAge = Math.floor((Date.now() - CACHE_AGE_LIMIT) / 1000);
|
|
58
|
+
this.db
|
|
59
|
+
.prepare("DELETE FROM cached_events WHERE created_at < ?")
|
|
60
|
+
.run(maxAge);
|
|
61
|
+
// Size-based eviction
|
|
62
|
+
while (this.sizeBytes() > CACHE_SIZE_LIMIT) {
|
|
63
|
+
const deleted = this.db
|
|
64
|
+
.prepare("DELETE FROM cached_events WHERE id IN (SELECT id FROM cached_events ORDER BY id ASC LIMIT 100)")
|
|
65
|
+
.run();
|
|
66
|
+
if (deleted.changes === 0)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
close() {
|
|
71
|
+
this.db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=sqlite-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-cache.js","sourceRoot":"","sources":["../../src/transport/sqlite-cache.ts"],"names":[],"mappings":"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"}
|