podwatch 1.0.2
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/LICENSE +28 -0
- package/README.md +92 -0
- package/bin/podwatch.js +10 -0
- package/dist/classifier.d.ts +22 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +157 -0
- package/dist/classifier.js.map +1 -0
- package/dist/hooks/cost.d.ts +26 -0
- package/dist/hooks/cost.d.ts.map +1 -0
- package/dist/hooks/cost.js +107 -0
- package/dist/hooks/cost.js.map +1 -0
- package/dist/hooks/lifecycle.d.ts +16 -0
- package/dist/hooks/lifecycle.d.ts.map +1 -0
- package/dist/hooks/lifecycle.js +273 -0
- package/dist/hooks/lifecycle.js.map +1 -0
- package/dist/hooks/security.d.ts +19 -0
- package/dist/hooks/security.d.ts.map +1 -0
- package/dist/hooks/security.js +128 -0
- package/dist/hooks/security.js.map +1 -0
- package/dist/hooks/sessions.d.ts +10 -0
- package/dist/hooks/sessions.d.ts.map +1 -0
- package/dist/hooks/sessions.js +53 -0
- package/dist/hooks/sessions.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/redact.d.ts +35 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +372 -0
- package/dist/redact.js.map +1 -0
- package/dist/scanner.d.ts +27 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +117 -0
- package/dist/scanner.js.map +1 -0
- package/dist/transmitter.d.ts +58 -0
- package/dist/transmitter.d.ts.map +1 -0
- package/dist/transmitter.js +654 -0
- package/dist/transmitter.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/updater.d.ts +168 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +579 -0
- package/dist/updater.js.map +1 -0
- package/lib/installer.js +599 -0
- package/openclaw.plugin.json +59 -0
- package/package.json +56 -0
- package/skills/podwatch/SKILL.md +112 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lifecycle handlers — gateway_start, gateway_stop, before_compaction.
|
|
4
|
+
*
|
|
5
|
+
* register(): starts pulse interval + initial skill/plugin scan
|
|
6
|
+
* gateway_stop: flushes pending events, stops intervals
|
|
7
|
+
* before_compaction: tracks context window pressure
|
|
8
|
+
*
|
|
9
|
+
* Pulse = Podwatch plugin's lightweight alive-ping (direct HTTP, no LLM cost).
|
|
10
|
+
* This is NOT OpenClaw's agent heartbeat (which triggers LLM turns).
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.registerLifecycleHandlers = registerLifecycleHandlers;
|
|
47
|
+
const transmitter_js_1 = require("../transmitter.js");
|
|
48
|
+
const scanner_js_1 = require("../scanner.js");
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
51
|
+
const PLUGIN_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf-8")).version;
|
|
52
|
+
let pulseTimer = null;
|
|
53
|
+
let scanTimer = null;
|
|
54
|
+
// Pulse backoff state
|
|
55
|
+
let pulseFailureCount = 0;
|
|
56
|
+
const PULSE_FAILURE_THRESHOLD = 3; // Start backoff after N consecutive failures
|
|
57
|
+
const PULSE_MAX_INTERVAL_MS = 3_600_000; // 60 min cap
|
|
58
|
+
// Config change detection state
|
|
59
|
+
let knownPrimaryModel = null;
|
|
60
|
+
/**
|
|
61
|
+
* Read the primary model from the OpenClaw gateway config.
|
|
62
|
+
* Handles both string and object shapes for `agents.defaults.model`.
|
|
63
|
+
*/
|
|
64
|
+
function readPrimaryModel(api) {
|
|
65
|
+
try {
|
|
66
|
+
const modelCfg = api.config?.agents?.defaults?.model;
|
|
67
|
+
if (!modelCfg)
|
|
68
|
+
return null;
|
|
69
|
+
if (typeof modelCfg === "string")
|
|
70
|
+
return modelCfg;
|
|
71
|
+
if (typeof modelCfg === "object" && modelCfg.primary)
|
|
72
|
+
return String(modelCfg.primary);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if the primary model changed and emit a config_change event if so.
|
|
81
|
+
*/
|
|
82
|
+
function checkModelConfigChange(api) {
|
|
83
|
+
const currentModel = readPrimaryModel(api);
|
|
84
|
+
if (currentModel === knownPrimaryModel)
|
|
85
|
+
return;
|
|
86
|
+
const previousValue = knownPrimaryModel;
|
|
87
|
+
knownPrimaryModel = currentModel;
|
|
88
|
+
// Don't emit on first read if null
|
|
89
|
+
if (currentModel === null && previousValue === null)
|
|
90
|
+
return;
|
|
91
|
+
transmitter_js_1.transmitter.enqueue({
|
|
92
|
+
type: "config_change",
|
|
93
|
+
ts: Date.now(),
|
|
94
|
+
field: "model.primary",
|
|
95
|
+
value: currentModel,
|
|
96
|
+
previousValue,
|
|
97
|
+
// Pass as params so they appear in toolArgs on the server
|
|
98
|
+
params: {
|
|
99
|
+
field: "model.primary",
|
|
100
|
+
value: currentModel,
|
|
101
|
+
previousValue,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Register lifecycle hook handlers.
|
|
107
|
+
*/
|
|
108
|
+
function registerLifecycleHandlers(api, config) {
|
|
109
|
+
const endpoint = config.endpoint ?? "https://podwatch.app/api";
|
|
110
|
+
const apiKey = config.apiKey;
|
|
111
|
+
// -----------------------------------------------------------------------
|
|
112
|
+
// Pulse + scan — run immediately during register() since
|
|
113
|
+
// gateway_start is never emitted to plugins.
|
|
114
|
+
// -----------------------------------------------------------------------
|
|
115
|
+
const basePulseIntervalMs = config.pulseIntervalMs ?? 300_000;
|
|
116
|
+
// Reset pulse backoff state
|
|
117
|
+
pulseFailureCount = 0;
|
|
118
|
+
// Read initial primary model config and send config_change event
|
|
119
|
+
knownPrimaryModel = null; // Reset on re-register
|
|
120
|
+
checkModelConfigChange(api);
|
|
121
|
+
// Send initial pulse right now
|
|
122
|
+
void sendPulseWithBackoff(endpoint, apiKey, basePulseIntervalMs, api);
|
|
123
|
+
// Initial skill/plugin scan — delayed 30s to let gateway fully settle
|
|
124
|
+
const workspaceDir = api.config?.agents?.defaults?.workspace;
|
|
125
|
+
setTimeout(() => void runScan(workspaceDir), 30_000);
|
|
126
|
+
// Start periodic scan interval (default 6h)
|
|
127
|
+
if (scanTimer)
|
|
128
|
+
clearInterval(scanTimer);
|
|
129
|
+
scanTimer = setInterval(() => void runScan(workspaceDir), config.scanIntervalMs ?? 21_600_000);
|
|
130
|
+
if (scanTimer && typeof scanTimer === "object" && "unref" in scanTimer) {
|
|
131
|
+
scanTimer.unref();
|
|
132
|
+
}
|
|
133
|
+
api.logger.info(`[podwatch/lifecycle] Pulse & scan started from register(). Pulse: ${config.pulseIntervalMs ?? 300_000}ms, Scan: ${config.scanIntervalMs ?? 21_600_000}ms`);
|
|
134
|
+
// -----------------------------------------------------------------------
|
|
135
|
+
// gateway_start — best-effort re-scan (in case it ever fires)
|
|
136
|
+
// -----------------------------------------------------------------------
|
|
137
|
+
api.on("gateway_start", async (event) => {
|
|
138
|
+
// Re-run scan as best-effort; pulse is already running
|
|
139
|
+
void runScan(api.config?.agents?.defaults?.workspace);
|
|
140
|
+
}, { name: "podwatch-gateway-start" });
|
|
141
|
+
// -----------------------------------------------------------------------
|
|
142
|
+
// gateway_stop — graceful shutdown
|
|
143
|
+
// -----------------------------------------------------------------------
|
|
144
|
+
api.on("gateway_stop", async () => {
|
|
145
|
+
// Stop intervals
|
|
146
|
+
if (pulseTimer) {
|
|
147
|
+
clearTimeout(pulseTimer);
|
|
148
|
+
pulseTimer = null;
|
|
149
|
+
}
|
|
150
|
+
if (scanTimer) {
|
|
151
|
+
clearInterval(scanTimer);
|
|
152
|
+
scanTimer = null;
|
|
153
|
+
}
|
|
154
|
+
// Unsubscribe from diagnostic events
|
|
155
|
+
const unsubscribe = api.__podwatch_unsubscribeDiagnostics;
|
|
156
|
+
if (typeof unsubscribe === "function") {
|
|
157
|
+
unsubscribe();
|
|
158
|
+
}
|
|
159
|
+
// Flush remaining events
|
|
160
|
+
await transmitter_js_1.transmitter.shutdown();
|
|
161
|
+
api.logger.info("[podwatch/lifecycle] Graceful shutdown complete");
|
|
162
|
+
}, { name: "podwatch-gateway-stop" });
|
|
163
|
+
// -----------------------------------------------------------------------
|
|
164
|
+
// before_compaction — context window pressure
|
|
165
|
+
// -----------------------------------------------------------------------
|
|
166
|
+
api.on("before_compaction", async (event, ctx) => {
|
|
167
|
+
transmitter_js_1.transmitter.enqueue({
|
|
168
|
+
type: "compaction",
|
|
169
|
+
ts: Date.now(),
|
|
170
|
+
messageCount: event.messageCount,
|
|
171
|
+
tokenCount: event.tokenCount,
|
|
172
|
+
sessionKey: ctx.sessionKey,
|
|
173
|
+
agentId: ctx.agentId,
|
|
174
|
+
});
|
|
175
|
+
// Context pressure alert — only if both fields are available
|
|
176
|
+
if (typeof event.tokenCount === "number" &&
|
|
177
|
+
typeof event.contextLimit === "number") {
|
|
178
|
+
const contextLimit = event.contextLimit;
|
|
179
|
+
const ratio = event.tokenCount / contextLimit;
|
|
180
|
+
if (ratio > 0.8) {
|
|
181
|
+
transmitter_js_1.transmitter.enqueue({
|
|
182
|
+
type: "alert",
|
|
183
|
+
ts: Date.now(),
|
|
184
|
+
severity: "warning",
|
|
185
|
+
pattern: "context_pressure",
|
|
186
|
+
tokenCount: event.tokenCount,
|
|
187
|
+
contextLimit,
|
|
188
|
+
ratio,
|
|
189
|
+
sessionKey: ctx.sessionKey,
|
|
190
|
+
agentId: ctx.agentId,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}, { name: "podwatch-compaction" });
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Helpers
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Send a lightweight pulse directly to the Podwatch API, then schedule
|
|
201
|
+
* the next pulse. Uses exponential backoff after consecutive failures.
|
|
202
|
+
*
|
|
203
|
+
* Backoff kicks in after PULSE_FAILURE_THRESHOLD (3) consecutive failures:
|
|
204
|
+
* base → 2x base → 4x base → ... capped at PULSE_MAX_INTERVAL_MS (60min)
|
|
205
|
+
* Resets to base interval on success.
|
|
206
|
+
*/
|
|
207
|
+
async function sendPulseWithBackoff(endpoint, apiKey, baseIntervalMs, api) {
|
|
208
|
+
// Check for config changes on each pulse
|
|
209
|
+
if (api) {
|
|
210
|
+
checkModelConfigChange(api);
|
|
211
|
+
}
|
|
212
|
+
let success = false;
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(`${endpoint}/pulse`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json",
|
|
218
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
ts: Date.now(),
|
|
222
|
+
bufferedEvents: transmitter_js_1.transmitter.bufferedCount,
|
|
223
|
+
uptimeHours: transmitter_js_1.transmitter.getAgentUptimeHours(),
|
|
224
|
+
pluginVersion: PLUGIN_VERSION,
|
|
225
|
+
}),
|
|
226
|
+
signal: AbortSignal.timeout(10_000),
|
|
227
|
+
});
|
|
228
|
+
if (response.ok) {
|
|
229
|
+
success = true;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.error(`[podwatch/pulse] API ${response.status}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
console.error("[podwatch/pulse] Failed:", err);
|
|
237
|
+
}
|
|
238
|
+
// Update failure counter
|
|
239
|
+
if (success) {
|
|
240
|
+
pulseFailureCount = 0;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
pulseFailureCount++;
|
|
244
|
+
}
|
|
245
|
+
// Calculate next interval
|
|
246
|
+
let nextIntervalMs = baseIntervalMs;
|
|
247
|
+
if (pulseFailureCount >= PULSE_FAILURE_THRESHOLD) {
|
|
248
|
+
// Exponential backoff: 2x base per failure beyond threshold
|
|
249
|
+
const backoffMultiplier = Math.pow(2, pulseFailureCount - PULSE_FAILURE_THRESHOLD);
|
|
250
|
+
nextIntervalMs = Math.min(baseIntervalMs * 2 * backoffMultiplier, PULSE_MAX_INTERVAL_MS);
|
|
251
|
+
}
|
|
252
|
+
// Schedule next pulse
|
|
253
|
+
if (pulseTimer)
|
|
254
|
+
clearTimeout(pulseTimer);
|
|
255
|
+
pulseTimer = setTimeout(() => void sendPulseWithBackoff(endpoint, apiKey, baseIntervalMs, api), nextIntervalMs);
|
|
256
|
+
if (pulseTimer && typeof pulseTimer === "object" && "unref" in pulseTimer) {
|
|
257
|
+
pulseTimer.unref();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function runScan(workspaceDir) {
|
|
261
|
+
try {
|
|
262
|
+
const results = await (0, scanner_js_1.scanSkillsAndPlugins)(workspaceDir);
|
|
263
|
+
transmitter_js_1.transmitter.enqueue({
|
|
264
|
+
type: "scan",
|
|
265
|
+
ts: Date.now(),
|
|
266
|
+
...results,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
console.error("[podwatch/lifecycle] Scan failed:", err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/hooks/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security handlers — before_tool_call and after_tool_call.
|
|
3
|
+
*
|
|
4
|
+
* before_tool_call: THE critical hook. Can BLOCK tool execution.
|
|
5
|
+
* - A) Budget enforcement (blocks when daily spend >= 95% of limit)
|
|
6
|
+
* - B1) Exfiltration sequence detection (credential read → network call)
|
|
7
|
+
* - B2) First-time tool detection (unknown tool after 24h uptime)
|
|
8
|
+
* - B3) Log ALL tool calls with redacted params (server classifies risk)
|
|
9
|
+
*
|
|
10
|
+
* after_tool_call: Fire-and-forget.
|
|
11
|
+
* - Latency tracking (durationMs)
|
|
12
|
+
* - Success/failure recording
|
|
13
|
+
*/
|
|
14
|
+
import type { PodwatchConfig } from "../index.js";
|
|
15
|
+
/**
|
|
16
|
+
* Register security hook handlers.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerSecurityHandlers(api: any, config: PodwatchConfig): void;
|
|
19
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/hooks/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAalD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CA8H/E"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security handlers — before_tool_call and after_tool_call.
|
|
4
|
+
*
|
|
5
|
+
* before_tool_call: THE critical hook. Can BLOCK tool execution.
|
|
6
|
+
* - A) Budget enforcement (blocks when daily spend >= 95% of limit)
|
|
7
|
+
* - B1) Exfiltration sequence detection (credential read → network call)
|
|
8
|
+
* - B2) First-time tool detection (unknown tool after 24h uptime)
|
|
9
|
+
* - B3) Log ALL tool calls with redacted params (server classifies risk)
|
|
10
|
+
*
|
|
11
|
+
* after_tool_call: Fire-and-forget.
|
|
12
|
+
* - Latency tracking (durationMs)
|
|
13
|
+
* - Success/failure recording
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.registerSecurityHandlers = registerSecurityHandlers;
|
|
17
|
+
const transmitter_js_1 = require("../transmitter.js");
|
|
18
|
+
const redact_js_1 = require("../redact.js");
|
|
19
|
+
const classifier_js_1 = require("../classifier.js");
|
|
20
|
+
const BUDGET_TOLERANCE = 0.95; // Block at 95% of limit
|
|
21
|
+
/**
|
|
22
|
+
* Register security hook handlers.
|
|
23
|
+
*/
|
|
24
|
+
function registerSecurityHandlers(api, config) {
|
|
25
|
+
// -----------------------------------------------------------------------
|
|
26
|
+
// before_tool_call — security scan + budget enforcement + exfiltration
|
|
27
|
+
// -----------------------------------------------------------------------
|
|
28
|
+
api.on("before_tool_call", async (event, ctx) => {
|
|
29
|
+
const { toolName, params } = event;
|
|
30
|
+
// --- A) Budget check (if enabled) — can BLOCK ---
|
|
31
|
+
if (config.enableBudgetEnforcement) {
|
|
32
|
+
const budget = transmitter_js_1.transmitter.getCachedBudget();
|
|
33
|
+
if (budget && budget.limit > 0 && budget.currentSpend >= budget.limit * BUDGET_TOLERANCE) {
|
|
34
|
+
transmitter_js_1.transmitter.enqueue({
|
|
35
|
+
type: "budget_blocked",
|
|
36
|
+
ts: Date.now(),
|
|
37
|
+
toolName,
|
|
38
|
+
sessionKey: ctx.sessionKey,
|
|
39
|
+
agentId: ctx.agentId,
|
|
40
|
+
budgetLimit: budget.limit,
|
|
41
|
+
currentSpend: budget.currentSpend,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
block: true,
|
|
45
|
+
blockReason: `Podwatch: Daily budget of $${budget.limit.toFixed(2)} reached ($${budget.currentSpend.toFixed(2)} spent). Manage at podwatch.app/settings`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// --- B) Security alerts (if enabled) ---
|
|
50
|
+
if (config.enableSecurityAlerts) {
|
|
51
|
+
const classification = (0, classifier_js_1.classifyTool)(toolName, params);
|
|
52
|
+
// B1) Exfiltration sequence detection
|
|
53
|
+
if (classification.accessesCredentials) {
|
|
54
|
+
transmitter_js_1.transmitter.markCredentialAccess(toolName, params);
|
|
55
|
+
}
|
|
56
|
+
if (classification.makesNetworkCall && transmitter_js_1.transmitter.hasRecentCredentialAccess(60)) {
|
|
57
|
+
const recentAccess = transmitter_js_1.transmitter.getRecentCredentialAccess(60);
|
|
58
|
+
transmitter_js_1.transmitter.enqueue({
|
|
59
|
+
type: "security",
|
|
60
|
+
ts: Date.now(),
|
|
61
|
+
pattern: "exfiltration_sequence",
|
|
62
|
+
severity: "critical",
|
|
63
|
+
toolName,
|
|
64
|
+
reason: `Network call (${toolName}) detected within 60s of credential access (${recentAccess?.toolName ?? "unknown"} → ${recentAccess?.path ?? "unknown"})`,
|
|
65
|
+
sessionKey: ctx.sessionKey,
|
|
66
|
+
agentId: ctx.agentId,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// B1b) Persistence attempt detection
|
|
70
|
+
if (classification.persistenceAttempt) {
|
|
71
|
+
transmitter_js_1.transmitter.enqueue({
|
|
72
|
+
type: "security",
|
|
73
|
+
ts: Date.now(),
|
|
74
|
+
pattern: "persistence_attempt",
|
|
75
|
+
severity: "high",
|
|
76
|
+
toolName,
|
|
77
|
+
reason: `Persistence attempt detected via ${toolName}`,
|
|
78
|
+
sessionKey: ctx.sessionKey,
|
|
79
|
+
agentId: ctx.agentId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// B2) First-time tool detection
|
|
83
|
+
if (!transmitter_js_1.transmitter.isKnownTool(toolName) && transmitter_js_1.transmitter.getAgentUptimeHours() > 24) {
|
|
84
|
+
transmitter_js_1.transmitter.enqueue({
|
|
85
|
+
type: "security",
|
|
86
|
+
ts: Date.now(),
|
|
87
|
+
pattern: "first_time_tool",
|
|
88
|
+
severity: "medium",
|
|
89
|
+
toolName,
|
|
90
|
+
reason: `First use of tool "${toolName}" after ${Math.round(transmitter_js_1.transmitter.getAgentUptimeHours())}h uptime`,
|
|
91
|
+
sessionKey: ctx.sessionKey,
|
|
92
|
+
agentId: ctx.agentId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
transmitter_js_1.transmitter.recordToolSeen(toolName);
|
|
96
|
+
}
|
|
97
|
+
// --- B3) Log ALL tool calls for timeline (server classifies risk) ---
|
|
98
|
+
const { result: redactedParams, redactedCount } = (0, redact_js_1.redactParams)(params);
|
|
99
|
+
transmitter_js_1.transmitter.enqueue({
|
|
100
|
+
type: "tool_call",
|
|
101
|
+
ts: Date.now(),
|
|
102
|
+
toolName,
|
|
103
|
+
params: redactedParams,
|
|
104
|
+
redactedCount,
|
|
105
|
+
sessionKey: ctx.sessionKey,
|
|
106
|
+
agentId: ctx.agentId,
|
|
107
|
+
});
|
|
108
|
+
}, { name: "podwatch-security-scan", priority: 10 } // Run early to block before other plugins
|
|
109
|
+
);
|
|
110
|
+
// -----------------------------------------------------------------------
|
|
111
|
+
// after_tool_call — latency + success/failure (fire-and-forget)
|
|
112
|
+
// -----------------------------------------------------------------------
|
|
113
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
114
|
+
transmitter_js_1.transmitter.enqueue({
|
|
115
|
+
type: "tool_result",
|
|
116
|
+
ts: Date.now(),
|
|
117
|
+
toolName: event.toolName,
|
|
118
|
+
durationMs: event.durationMs,
|
|
119
|
+
success: !event.error,
|
|
120
|
+
error: event.error ? String(event.error).slice(0, 500) : undefined,
|
|
121
|
+
sessionKey: ctx.sessionKey,
|
|
122
|
+
agentId: ctx.agentId,
|
|
123
|
+
});
|
|
124
|
+
}, { name: "podwatch-tool-result" });
|
|
125
|
+
api.logger.info(`[podwatch/security] Handlers registered. Budget: ${config.enableBudgetEnforcement ? "ENFORCE" : "off"}, ` +
|
|
126
|
+
`Security: ${config.enableSecurityAlerts ? "ON" : "off"}`);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/hooks/security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session handlers — session_start and session_end.
|
|
3
|
+
*
|
|
4
|
+
* Tracks session lifecycle for timeline, loop detection, and duration metrics.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Register session lifecycle handlers.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerSessionHandlers(api: any): void;
|
|
10
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/hooks/sessions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAkDtD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session handlers — session_start and session_end.
|
|
4
|
+
*
|
|
5
|
+
* Tracks session lifecycle for timeline, loop detection, and duration metrics.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.registerSessionHandlers = registerSessionHandlers;
|
|
9
|
+
const transmitter_js_1 = require("../transmitter.js");
|
|
10
|
+
/**
|
|
11
|
+
* Register session lifecycle handlers.
|
|
12
|
+
*/
|
|
13
|
+
function registerSessionHandlers(api) {
|
|
14
|
+
// -----------------------------------------------------------------------
|
|
15
|
+
// session_start
|
|
16
|
+
// -----------------------------------------------------------------------
|
|
17
|
+
api.on("session_start", async (event, ctx) => {
|
|
18
|
+
transmitter_js_1.transmitter.enqueue({
|
|
19
|
+
type: "session_start",
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
sessionId: event.sessionId,
|
|
22
|
+
resumedFrom: event.resumedFrom,
|
|
23
|
+
agentId: ctx.agentId,
|
|
24
|
+
});
|
|
25
|
+
}, { name: "podwatch-session-start" });
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
// session_end — includes loop detection
|
|
28
|
+
// -----------------------------------------------------------------------
|
|
29
|
+
api.on("session_end", async (event, ctx) => {
|
|
30
|
+
transmitter_js_1.transmitter.enqueue({
|
|
31
|
+
type: "session_end",
|
|
32
|
+
ts: Date.now(),
|
|
33
|
+
sessionId: event.sessionId,
|
|
34
|
+
messageCount: event.messageCount,
|
|
35
|
+
durationMs: event.durationMs,
|
|
36
|
+
agentId: ctx.agentId,
|
|
37
|
+
});
|
|
38
|
+
// Simple loop detection: sessions with > 100 messages are suspicious
|
|
39
|
+
if (event.messageCount > 100) {
|
|
40
|
+
transmitter_js_1.transmitter.enqueue({
|
|
41
|
+
type: "alert",
|
|
42
|
+
ts: Date.now(),
|
|
43
|
+
severity: "warning",
|
|
44
|
+
pattern: "session_loop_warning",
|
|
45
|
+
sessionKey: ctx.sessionId,
|
|
46
|
+
messageCount: event.messageCount,
|
|
47
|
+
agentId: ctx.agentId,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}, { name: "podwatch-session-end" });
|
|
51
|
+
api.logger.info("[podwatch/sessions] Session lifecycle handlers registered");
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/hooks/sessions.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAWH,0DAkDC;AAvDD,sDAAgD;AAEhD;;GAEG;AACH,SAAgB,uBAAuB,CAAC,GAAQ;IAC9C,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,CACJ,eAAe,EACf,KAAK,EAAE,KAAwB,EAAE,GAA4C,EAAiB,EAAE;QAC9F,4BAAW,CAAC,OAAO,CAAC;YAClB,IAAI,EAAE,eAAe;YACrB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;IACL,CAAC,EACD,EAAE,IAAI,EAAE,wBAAwB,EAAE,CACnC,CAAC;IAEF,0EAA0E;IAC1E,wCAAwC;IACxC,0EAA0E;IAC1E,GAAG,CAAC,EAAE,CACJ,aAAa,EACb,KAAK,EAAE,KAAsB,EAAE,GAA4C,EAAiB,EAAE;QAC5F,4BAAW,CAAC,OAAO,CAAC;YAClB,IAAI,EAAE,aAAa;YACnB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;QAEH,qEAAqE;QACrE,IAAI,KAAK,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC;YAC7B,4BAAW,CAAC,OAAO,CAAC;gBAClB,IAAI,EAAE,OAAO;gBACb,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,sBAAsB;gBAC/B,UAAU,EAAE,GAAG,CAAC,SAAS;gBACzB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,EACD,EAAE,IAAI,EAAE,sBAAsB,EAAE,CACjC,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;AAC/E,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Podwatch Plugin — OpenClaw cost monitoring, budget enforcement, and security alerts.
|
|
3
|
+
*
|
|
4
|
+
* Registers hook handlers and subscribes to diagnostic events to capture:
|
|
5
|
+
* - Cost/token data per LLM call (onDiagnosticEvent → model.usage)
|
|
6
|
+
* - Tool security scanning + budget blocking (before_tool_call)
|
|
7
|
+
* - Tool latency + success/failure (after_tool_call)
|
|
8
|
+
* - Session lifecycle (session_start, session_end)
|
|
9
|
+
* - Context pressure (before_compaction)
|
|
10
|
+
* - Pulse alive-ping + skill/plugin scanning (register())
|
|
11
|
+
* - Graceful shutdown (gateway_stop)
|
|
12
|
+
*/
|
|
13
|
+
import type { PluginApi } from "./types.js";
|
|
14
|
+
export interface PodwatchConfig {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
enableBudgetEnforcement?: boolean;
|
|
18
|
+
enableSecurityAlerts?: boolean;
|
|
19
|
+
/** Enable auto-update of the plugin. Default: false (opt-in for security). */
|
|
20
|
+
autoUpdate?: boolean;
|
|
21
|
+
/** Pulse alive-ping interval in ms (default 300000 = 5 min). */
|
|
22
|
+
pulseIntervalMs?: number;
|
|
23
|
+
/** @deprecated Use pulseIntervalMs instead. Kept for backward compatibility. */
|
|
24
|
+
heartbeatIntervalMs?: number;
|
|
25
|
+
scanIntervalMs?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* OpenClaw plugin entry point.
|
|
29
|
+
* Called by the Gateway when the plugin is loaded.
|
|
30
|
+
*/
|
|
31
|
+
export default function register(api: PluginApi): void;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAkB,SAAS,EAAE,MAAM,YAAY,CAAC;AAY5D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwBD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAiErD"}
|