@upx-us/shield 0.3.29 → 0.4.36
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/CHANGELOG.md +462 -0
- package/README.md +75 -14
- package/dist/index.js +180 -16
- package/dist/src/case-monitor.d.ts +24 -0
- package/dist/src/case-monitor.js +193 -0
- package/dist/src/cli-cases.d.ts +1 -0
- package/dist/src/cli-cases.js +184 -0
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +2 -0
- package/dist/src/event-store.d.ts +31 -0
- package/dist/src/event-store.js +163 -0
- package/dist/src/index.js +43 -5
- package/dist/src/inventory.d.ts +26 -0
- package/dist/src/inventory.js +191 -0
- package/dist/src/rpc/client.d.ts +12 -0
- package/dist/src/rpc/client.js +105 -0
- package/dist/src/rpc/handlers.d.ts +57 -0
- package/dist/src/rpc/handlers.js +141 -0
- package/dist/src/rpc/index.d.ts +10 -0
- package/dist/src/rpc/index.js +13 -0
- package/dist/src/safe-io.d.ts +2 -0
- package/dist/src/safe-io.js +78 -0
- package/dist/src/transformer.d.ts +1 -0
- package/dist/src/transformer.js +59 -20
- package/dist/src/updater.js +8 -9
- package/openclaw.plugin.json +80 -75
- package/package.json +17 -8
- package/skills/shield/README.md +39 -0
- package/skills/shield/SKILL.md +66 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.initCaseMonitor = initCaseMonitor;
|
|
37
|
+
exports.notifyCaseMonitorActivity = notifyCaseMonitorActivity;
|
|
38
|
+
exports.getCaseMonitorStatus = getCaseMonitorStatus;
|
|
39
|
+
exports.checkForNewCases = checkForNewCases;
|
|
40
|
+
exports.getPendingCases = getPendingCases;
|
|
41
|
+
exports.acknowledgeCases = acknowledgeCases;
|
|
42
|
+
exports.formatCaseNotification = formatCaseNotification;
|
|
43
|
+
exports._resetForTesting = _resetForTesting;
|
|
44
|
+
const safe_io_1 = require("./safe-io");
|
|
45
|
+
const client_1 = require("./rpc/client");
|
|
46
|
+
const log = __importStar(require("./log"));
|
|
47
|
+
const path_1 = require("path");
|
|
48
|
+
const MIN_CHECK_INTERVAL_MS = 60_000;
|
|
49
|
+
const MAX_CHECK_INTERVAL_MS = 900_000;
|
|
50
|
+
const RECENT_EVENT_THRESHOLD_MS = 300_000;
|
|
51
|
+
const MAX_ACKNOWLEDGED_IDS = 500;
|
|
52
|
+
const STATE_FILE = 'case-monitor-state.json';
|
|
53
|
+
let _dataDir = null;
|
|
54
|
+
let _lastCheckAt = 0;
|
|
55
|
+
let _lastEventAt = 0;
|
|
56
|
+
let _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
57
|
+
function computeInterval() {
|
|
58
|
+
if (_lastCheckAt === 0)
|
|
59
|
+
return MIN_CHECK_INTERVAL_MS;
|
|
60
|
+
if (_lastEventAt === 0)
|
|
61
|
+
return MAX_CHECK_INTERVAL_MS;
|
|
62
|
+
const elapsed = Date.now() - _lastEventAt;
|
|
63
|
+
if (elapsed <= RECENT_EVENT_THRESHOLD_MS)
|
|
64
|
+
return MIN_CHECK_INTERVAL_MS;
|
|
65
|
+
const rampMs = 600_000;
|
|
66
|
+
const progress = Math.min((elapsed - RECENT_EVENT_THRESHOLD_MS) / rampMs, 1);
|
|
67
|
+
return Math.round(MIN_CHECK_INTERVAL_MS + progress * (MAX_CHECK_INTERVAL_MS - MIN_CHECK_INTERVAL_MS));
|
|
68
|
+
}
|
|
69
|
+
function initCaseMonitor(dataDir) {
|
|
70
|
+
_dataDir = dataDir;
|
|
71
|
+
_currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
72
|
+
log.info('case-monitor', 'Initialized (adaptive: 1m active → 15m idle)');
|
|
73
|
+
}
|
|
74
|
+
function notifyCaseMonitorActivity() {
|
|
75
|
+
_lastEventAt = Date.now();
|
|
76
|
+
}
|
|
77
|
+
function getCaseMonitorStatus() {
|
|
78
|
+
const interval = computeInterval();
|
|
79
|
+
const nextCheckIn = Math.max(0, (_lastCheckAt + interval) - Date.now());
|
|
80
|
+
return { intervalMs: interval, nextCheckIn, lastCheckAt: _lastCheckAt };
|
|
81
|
+
}
|
|
82
|
+
async function checkForNewCases(config) {
|
|
83
|
+
if (!_dataDir)
|
|
84
|
+
return;
|
|
85
|
+
if (!config.apiUrl)
|
|
86
|
+
return;
|
|
87
|
+
_currentIntervalMs = computeInterval();
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
if (now - _lastCheckAt < _currentIntervalMs)
|
|
90
|
+
return;
|
|
91
|
+
_lastCheckAt = now;
|
|
92
|
+
const stateFile = (0, path_1.join)(_dataDir, STATE_FILE);
|
|
93
|
+
const state = (0, safe_io_1.readJsonSafe)(stateFile, {
|
|
94
|
+
lastCheckAt: null,
|
|
95
|
+
pendingCases: [],
|
|
96
|
+
acknowledgedIds: [],
|
|
97
|
+
}, 'case-monitor');
|
|
98
|
+
try {
|
|
99
|
+
const params = {
|
|
100
|
+
status: 'open',
|
|
101
|
+
limit: 20,
|
|
102
|
+
};
|
|
103
|
+
if (state.lastCheckAt) {
|
|
104
|
+
params.since = state.lastCheckAt;
|
|
105
|
+
}
|
|
106
|
+
const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', params, 'GET');
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
if (!result.error?.includes('not configured')) {
|
|
109
|
+
log.warn('case-monitor', `Failed to check cases: ${result.error}`);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const cases = result.data?.cases ?? [];
|
|
114
|
+
const ackSet = new Set(state.acknowledgedIds);
|
|
115
|
+
const pendingSet = new Set(state.pendingCases.map(c => c.id));
|
|
116
|
+
const newCases = cases.filter(c => !ackSet.has(c.id) && !pendingSet.has(c.id));
|
|
117
|
+
if (newCases.length > 0) {
|
|
118
|
+
state.pendingCases = [...state.pendingCases, ...newCases];
|
|
119
|
+
log.info('case-monitor', `${newCases.length} new case(s) pending notification`);
|
|
120
|
+
}
|
|
121
|
+
state.lastCheckAt = new Date().toISOString();
|
|
122
|
+
(0, safe_io_1.writeJsonSafe)(stateFile, state);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
log.warn('case-monitor', `Check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function getPendingCases() {
|
|
129
|
+
if (!_dataDir)
|
|
130
|
+
return [];
|
|
131
|
+
const stateFile = (0, path_1.join)(_dataDir, STATE_FILE);
|
|
132
|
+
const state = (0, safe_io_1.readJsonSafe)(stateFile, {
|
|
133
|
+
lastCheckAt: null,
|
|
134
|
+
pendingCases: [],
|
|
135
|
+
acknowledgedIds: [],
|
|
136
|
+
}, 'case-monitor');
|
|
137
|
+
return state.pendingCases;
|
|
138
|
+
}
|
|
139
|
+
function acknowledgeCases(caseIds) {
|
|
140
|
+
if (!_dataDir)
|
|
141
|
+
return;
|
|
142
|
+
const stateFile = (0, path_1.join)(_dataDir, STATE_FILE);
|
|
143
|
+
const state = (0, safe_io_1.readJsonSafe)(stateFile, {
|
|
144
|
+
lastCheckAt: null,
|
|
145
|
+
pendingCases: [],
|
|
146
|
+
acknowledgedIds: [],
|
|
147
|
+
}, 'case-monitor');
|
|
148
|
+
const ackSet = new Set(caseIds);
|
|
149
|
+
state.pendingCases = state.pendingCases.filter(c => !ackSet.has(c.id));
|
|
150
|
+
state.acknowledgedIds = [...state.acknowledgedIds, ...caseIds].slice(-MAX_ACKNOWLEDGED_IDS);
|
|
151
|
+
(0, safe_io_1.writeJsonSafe)(stateFile, state);
|
|
152
|
+
log.info('case-monitor', `Acknowledged ${caseIds.length} case(s), ${state.pendingCases.length} still pending`);
|
|
153
|
+
}
|
|
154
|
+
function formatCaseNotification(c) {
|
|
155
|
+
const severityEmoji = {
|
|
156
|
+
CRITICAL: '🔴',
|
|
157
|
+
HIGH: '🟠',
|
|
158
|
+
MEDIUM: '🟡',
|
|
159
|
+
LOW: '🔵',
|
|
160
|
+
INFO: 'ℹ️',
|
|
161
|
+
};
|
|
162
|
+
const emoji = severityEmoji[c.severity] || '⚠️';
|
|
163
|
+
const ago = getTimeAgo(c.created_at);
|
|
164
|
+
return [
|
|
165
|
+
`${emoji} **Shield Alert — New Case**`,
|
|
166
|
+
``,
|
|
167
|
+
`**Rule:** ${c.rule_title}`,
|
|
168
|
+
`**Severity:** ${c.severity}`,
|
|
169
|
+
`**Time:** ${ago}`,
|
|
170
|
+
`**Events:** ${c.event_count}`,
|
|
171
|
+
``,
|
|
172
|
+
`> ${c.summary}`,
|
|
173
|
+
``,
|
|
174
|
+
`Ask me to investigate or resolve this case.`,
|
|
175
|
+
].join('\n');
|
|
176
|
+
}
|
|
177
|
+
function getTimeAgo(isoDate) {
|
|
178
|
+
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
179
|
+
const mins = Math.floor(diffMs / 60000);
|
|
180
|
+
if (mins < 1)
|
|
181
|
+
return 'just now';
|
|
182
|
+
if (mins < 60)
|
|
183
|
+
return `${mins}m ago`;
|
|
184
|
+
const hours = Math.floor(mins / 60);
|
|
185
|
+
if (hours < 24)
|
|
186
|
+
return `${hours}h ago`;
|
|
187
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
188
|
+
}
|
|
189
|
+
function _resetForTesting() {
|
|
190
|
+
_dataDir = null;
|
|
191
|
+
_lastCheckAt = 0;
|
|
192
|
+
_currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
193
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerCasesCli(shieldCommand: any): void;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerCasesCli = registerCasesCli;
|
|
37
|
+
const config_1 = require("./config");
|
|
38
|
+
function hasValidCreds(creds) {
|
|
39
|
+
return !!(creds.apiUrl && creds.instanceId && creds.hmacSecret);
|
|
40
|
+
}
|
|
41
|
+
function buildConfig(creds) {
|
|
42
|
+
return { apiUrl: creds.apiUrl, instanceId: creds.instanceId, hmacSecret: creds.hmacSecret };
|
|
43
|
+
}
|
|
44
|
+
function registerCasesCli(shieldCommand) {
|
|
45
|
+
const cases = shieldCommand.command('cases')
|
|
46
|
+
.description('List and manage Shield security cases');
|
|
47
|
+
cases.action(async () => {
|
|
48
|
+
await listCases({ status: 'open', limit: '20', format: 'table' });
|
|
49
|
+
});
|
|
50
|
+
cases
|
|
51
|
+
.command('list')
|
|
52
|
+
.description('List security cases')
|
|
53
|
+
.option('--status <status>', 'Filter: open, resolved, all', 'open')
|
|
54
|
+
.option('--limit <n>', 'Max results', '20')
|
|
55
|
+
.option('--format <fmt>', 'Output format: table or json', 'table')
|
|
56
|
+
.action(listCases);
|
|
57
|
+
cases
|
|
58
|
+
.command('show')
|
|
59
|
+
.description('Show full case detail')
|
|
60
|
+
.argument('<id>', 'Case ID')
|
|
61
|
+
.option('--format <fmt>', 'Output format: table or json', 'table')
|
|
62
|
+
.action(showCase);
|
|
63
|
+
cases
|
|
64
|
+
.command('resolve')
|
|
65
|
+
.description('Resolve/close a security case')
|
|
66
|
+
.argument('<id>', 'Case ID')
|
|
67
|
+
.requiredOption('--resolution <value>', 'true_positive, false_positive, benign, duplicate')
|
|
68
|
+
.requiredOption('--root-cause <value>', 'user_initiated, misconfiguration, expected_behavior, actual_threat, testing, unknown')
|
|
69
|
+
.option('--comment <text>', 'Optional comment')
|
|
70
|
+
.action(resolveCase);
|
|
71
|
+
}
|
|
72
|
+
async function callApi(method, path, body) {
|
|
73
|
+
const creds = (0, config_1.loadCredentials)();
|
|
74
|
+
if (!hasValidCreds(creds)) {
|
|
75
|
+
console.error('Shield is not activated. Run: openclaw shield activate <KEY>');
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const { callPlatformApi } = await Promise.resolve().then(() => __importStar(require('./rpc/client')));
|
|
79
|
+
const result = await callPlatformApi(buildConfig(creds), path, body, method);
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
console.error(result.error || 'Platform API error');
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return result.data;
|
|
85
|
+
}
|
|
86
|
+
async function listCases(opts) {
|
|
87
|
+
const result = await callApi('GET', `/v1/agent/cases?status=${opts.status}&limit=${opts.limit}`);
|
|
88
|
+
if (!result)
|
|
89
|
+
return;
|
|
90
|
+
if (!result.cases) {
|
|
91
|
+
console.log('Platform unavailable or no cases found.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (opts.format === 'json') {
|
|
95
|
+
console.log(JSON.stringify(result, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (result.cases.length === 0) {
|
|
99
|
+
console.log('No open cases. \u2705');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log(`Cases (${result.cases.length} of ${result.total}):\n`);
|
|
103
|
+
for (const c of result.cases) {
|
|
104
|
+
const sev = (c.severity || '').padEnd(8);
|
|
105
|
+
const time = new Date(c.created_at).toLocaleString();
|
|
106
|
+
console.log(` [${sev}] ${c.id}`);
|
|
107
|
+
console.log(` ${c.rule_title || c.rule_id}`);
|
|
108
|
+
console.log(` ${c.summary || ''}`);
|
|
109
|
+
console.log(` Created: ${time} Events: ${c.event_count || 0}\n`);
|
|
110
|
+
}
|
|
111
|
+
if (result.has_more)
|
|
112
|
+
console.log(' Use --limit to see more.');
|
|
113
|
+
}
|
|
114
|
+
async function showCase(id, opts) {
|
|
115
|
+
const result = await callApi('GET', `/v1/agent/cases/${id}`);
|
|
116
|
+
if (!result)
|
|
117
|
+
return;
|
|
118
|
+
if (!result.id) {
|
|
119
|
+
console.error('Case not found.');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (opts.format === 'json') {
|
|
123
|
+
console.log(JSON.stringify(result, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(`Case: ${result.id}`);
|
|
127
|
+
console.log(`Status: ${result.status} Severity: ${result.severity}`);
|
|
128
|
+
console.log(`Rule: ${result.rule_title || result.rule_id}`);
|
|
129
|
+
console.log(`Summary: ${result.summary || '\u2014'}`);
|
|
130
|
+
console.log(`Created: ${new Date(result.created_at).toLocaleString()}`);
|
|
131
|
+
console.log(`Events: ${result.event_count || 0}`);
|
|
132
|
+
if (result.attribution) {
|
|
133
|
+
console.log(`\n${result.attribution}`);
|
|
134
|
+
}
|
|
135
|
+
if (result.rule) {
|
|
136
|
+
console.log(`\nRule: ${result.rule.description || '\u2014'}`);
|
|
137
|
+
if (result.rule.mitre_tactic)
|
|
138
|
+
console.log(`MITRE: ${result.rule.mitre_tactic} / ${result.rule.mitre_technique || '\u2014'}`);
|
|
139
|
+
}
|
|
140
|
+
if (result.playbook) {
|
|
141
|
+
console.log(`\nPlaybook: ${result.playbook.name || '\u2014'}`);
|
|
142
|
+
if (result.playbook.steps) {
|
|
143
|
+
for (const step of result.playbook.steps) {
|
|
144
|
+
console.log(` \u2192 ${step}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (result.events && result.events.length > 0) {
|
|
149
|
+
console.log(`\nEvents:`);
|
|
150
|
+
for (const e of result.events) {
|
|
151
|
+
console.log(` ${new Date(e.timestamp).toLocaleTimeString()} ${e.event_type} ${e.summary || ''}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (result.resolution) {
|
|
155
|
+
console.log(`\nResolution: ${result.resolution} Root cause: ${result.root_cause}`);
|
|
156
|
+
if (result.comment)
|
|
157
|
+
console.log(`Comment: ${result.comment}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function resolveCase(id, opts) {
|
|
161
|
+
const validResolutions = ['true_positive', 'false_positive', 'benign', 'duplicate'];
|
|
162
|
+
const validRootCauses = ['user_initiated', 'misconfiguration', 'expected_behavior', 'actual_threat', 'testing', 'unknown'];
|
|
163
|
+
if (!validResolutions.includes(opts.resolution)) {
|
|
164
|
+
console.error(`Invalid resolution: ${opts.resolution}\nValid: ${validResolutions.join(', ')}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!validRootCauses.includes(opts.rootCause)) {
|
|
168
|
+
console.error(`Invalid root cause: ${opts.rootCause}\nValid: ${validRootCauses.join(', ')}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const body = { resolution: opts.resolution, root_cause: opts.rootCause, comment: opts.comment };
|
|
172
|
+
const result = await callApi('POST', `/v1/agent/cases/${id}/resolve`, body);
|
|
173
|
+
if (!result)
|
|
174
|
+
return;
|
|
175
|
+
if (result.status === 'resolved') {
|
|
176
|
+
console.log(`\u2705 Case ${id} resolved as ${opts.resolution} (root cause: ${opts.rootCause})`);
|
|
177
|
+
}
|
|
178
|
+
else if (result.error) {
|
|
179
|
+
console.error(`\u274C ${result.error}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log('Response:', JSON.stringify(result, null, 2));
|
|
183
|
+
}
|
|
184
|
+
}
|
package/dist/src/config.d.ts
CHANGED
package/dist/src/config.js
CHANGED
|
@@ -140,6 +140,8 @@ function loadConfig(overrides) {
|
|
|
140
140
|
maxEvents: safeParseInt(process.env.MAX_EVENTS, 0),
|
|
141
141
|
collectHostMetrics: overrides?.collectHostMetrics ?? (process.env.COLLECT_HOST_METRICS === 'true'),
|
|
142
142
|
redactionEnabled: overrides?.redactionEnabled ?? (process.env.REDACTION_ENABLED !== 'false'),
|
|
143
|
+
localEventBuffer: process.env.SHIELD_LOCAL_EVENT_BUFFER !== 'false',
|
|
144
|
+
localEventLimit: safeParseInt(process.env.SHIELD_LOCAL_EVENT_LIMIT, 123),
|
|
143
145
|
credentials,
|
|
144
146
|
};
|
|
145
147
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface EventSummary {
|
|
2
|
+
ts: string;
|
|
3
|
+
type: string;
|
|
4
|
+
tool: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
session: string;
|
|
7
|
+
model: string;
|
|
8
|
+
redacted: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface EventStoreConfig {
|
|
11
|
+
filePath: string;
|
|
12
|
+
maxEvents: number;
|
|
13
|
+
maxAgeMs: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function initEventStore(dataDir: string, opts?: Partial<Pick<EventStoreConfig, 'maxEvents' | 'maxAgeMs'>>): void;
|
|
16
|
+
export declare function appendEvents(summaries: EventSummary[]): void;
|
|
17
|
+
export declare function queryEvents(opts?: {
|
|
18
|
+
limit?: number;
|
|
19
|
+
type?: string;
|
|
20
|
+
sinceMs?: number;
|
|
21
|
+
}): EventSummary[];
|
|
22
|
+
export declare function summarizeEvents(sinceMs?: number): {
|
|
23
|
+
total: number;
|
|
24
|
+
byType: Record<string, number>;
|
|
25
|
+
byTool: Record<string, number>;
|
|
26
|
+
oldest: string | null;
|
|
27
|
+
newest: string | null;
|
|
28
|
+
redactedCount: number;
|
|
29
|
+
};
|
|
30
|
+
export declare function _resetForTesting(): void;
|
|
31
|
+
export declare function _getConfigForTesting(): EventStoreConfig | null;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.initEventStore = initEventStore;
|
|
37
|
+
exports.appendEvents = appendEvents;
|
|
38
|
+
exports.queryEvents = queryEvents;
|
|
39
|
+
exports.summarizeEvents = summarizeEvents;
|
|
40
|
+
exports._resetForTesting = _resetForTesting;
|
|
41
|
+
exports._getConfigForTesting = _getConfigForTesting;
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const path_1 = require("path");
|
|
44
|
+
const log = __importStar(require("./log"));
|
|
45
|
+
const DEFAULT_MAX_EVENTS = 123;
|
|
46
|
+
const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
47
|
+
let _config = null;
|
|
48
|
+
function initEventStore(dataDir, opts) {
|
|
49
|
+
const filePath = `${dataDir}/event-log.jsonl`;
|
|
50
|
+
const dir = (0, path_1.dirname)(filePath);
|
|
51
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
52
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
53
|
+
_config = {
|
|
54
|
+
filePath,
|
|
55
|
+
maxEvents: opts?.maxEvents ?? DEFAULT_MAX_EVENTS,
|
|
56
|
+
maxAgeMs: opts?.maxAgeMs ?? DEFAULT_MAX_AGE_MS,
|
|
57
|
+
};
|
|
58
|
+
log.info('event-store', `Initialized: ${filePath} (max ${_config.maxEvents} events, ${Math.round(_config.maxAgeMs / 3600000)}h retention)`);
|
|
59
|
+
}
|
|
60
|
+
function appendEvents(summaries) {
|
|
61
|
+
if (!_config)
|
|
62
|
+
return;
|
|
63
|
+
if (summaries.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
const lines = summaries.map(s => JSON.stringify(s)).join('\n') + '\n';
|
|
67
|
+
(0, fs_1.appendFileSync)(_config.filePath, lines);
|
|
68
|
+
trimIfNeeded();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
log.warn('event-store', `Failed to append ${summaries.length} events: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function queryEvents(opts) {
|
|
75
|
+
if (!_config)
|
|
76
|
+
return [];
|
|
77
|
+
const limit = opts?.limit ?? 20;
|
|
78
|
+
const events = readAll();
|
|
79
|
+
let filtered = events;
|
|
80
|
+
if (opts?.type) {
|
|
81
|
+
const t = opts.type.toUpperCase();
|
|
82
|
+
filtered = filtered.filter(e => e.type === t || e.tool === opts.type);
|
|
83
|
+
}
|
|
84
|
+
if (opts?.sinceMs) {
|
|
85
|
+
const cutoff = new Date(Date.now() - opts.sinceMs);
|
|
86
|
+
filtered = filtered.filter(e => new Date(e.ts) >= cutoff);
|
|
87
|
+
}
|
|
88
|
+
return filtered.slice(-limit);
|
|
89
|
+
}
|
|
90
|
+
function summarizeEvents(sinceMs) {
|
|
91
|
+
const events = sinceMs
|
|
92
|
+
? queryEvents({ sinceMs, limit: 10000 })
|
|
93
|
+
: readAll();
|
|
94
|
+
const byType = {};
|
|
95
|
+
const byTool = {};
|
|
96
|
+
let redactedCount = 0;
|
|
97
|
+
for (const e of events) {
|
|
98
|
+
byType[e.type] = (byType[e.type] || 0) + 1;
|
|
99
|
+
byTool[e.tool] = (byTool[e.tool] || 0) + 1;
|
|
100
|
+
if (e.redacted)
|
|
101
|
+
redactedCount++;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
total: events.length,
|
|
105
|
+
byType,
|
|
106
|
+
byTool,
|
|
107
|
+
oldest: events.length > 0 ? events[0].ts : null,
|
|
108
|
+
newest: events.length > 0 ? events[events.length - 1].ts : null,
|
|
109
|
+
redactedCount,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function readAll() {
|
|
113
|
+
if (!_config || !(0, fs_1.existsSync)(_config.filePath))
|
|
114
|
+
return [];
|
|
115
|
+
try {
|
|
116
|
+
const raw = (0, fs_1.readFileSync)(_config.filePath, 'utf8');
|
|
117
|
+
return raw
|
|
118
|
+
.split('\n')
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.map(line => {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(line);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.filter((e) => e !== null);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
log.warn('event-store', `Failed to read events: ${err instanceof Error ? err.message : String(err)}`);
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function trimIfNeeded() {
|
|
136
|
+
if (!_config)
|
|
137
|
+
return;
|
|
138
|
+
try {
|
|
139
|
+
let events = readAll();
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const before = events.length;
|
|
142
|
+
events = events.filter(e => now - new Date(e.ts).getTime() < _config.maxAgeMs);
|
|
143
|
+
if (events.length > _config.maxEvents) {
|
|
144
|
+
events = events.slice(-_config.maxEvents);
|
|
145
|
+
}
|
|
146
|
+
if (events.length < before) {
|
|
147
|
+
const lines = events.map(e => JSON.stringify(e)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
148
|
+
const tmp = _config.filePath + '.tmp';
|
|
149
|
+
(0, fs_1.writeFileSync)(tmp, lines);
|
|
150
|
+
(0, fs_1.renameSync)(tmp, _config.filePath);
|
|
151
|
+
log.debug('event-store', `Trimmed ${before} → ${events.length} events`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
log.warn('event-store', `Trim failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function _resetForTesting() {
|
|
159
|
+
_config = null;
|
|
160
|
+
}
|
|
161
|
+
function _getConfigForTesting() {
|
|
162
|
+
return _config;
|
|
163
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const os = __importStar(require("os"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
36
38
|
const updater_1 = require("./updater");
|
|
37
39
|
const config_1 = require("./config");
|
|
38
40
|
const log = __importStar(require("./log"));
|
|
@@ -43,6 +45,9 @@ const redactor_1 = require("./redactor");
|
|
|
43
45
|
const validator_1 = require("./validator");
|
|
44
46
|
const fs_1 = require("fs");
|
|
45
47
|
const version_1 = require("./version");
|
|
48
|
+
const event_store_1 = require("./event-store");
|
|
49
|
+
const case_monitor_1 = require("./case-monitor");
|
|
50
|
+
const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
|
|
46
51
|
let running = true;
|
|
47
52
|
let lastTelemetryAt = 0;
|
|
48
53
|
let consecutiveFailures = 0;
|
|
@@ -61,6 +66,10 @@ async function poll() {
|
|
|
61
66
|
if (config.redactionEnabled) {
|
|
62
67
|
(0, redactor_1.init)();
|
|
63
68
|
}
|
|
69
|
+
(0, case_monitor_1.initCaseMonitor)(SHIELD_DATA_DIR);
|
|
70
|
+
if (config.localEventBuffer) {
|
|
71
|
+
(0, event_store_1.initEventStore)(SHIELD_DATA_DIR, { maxEvents: config.localEventLimit });
|
|
72
|
+
}
|
|
64
73
|
log.info('bridge', `Starting — dryRun=${config.dryRun} poll=${config.pollIntervalMs}ms maxEvents=${config.maxEvents || 'unlimited'} redaction=${config.redactionEnabled} logLevel=${process.env.LOG_LEVEL || 'info'}`);
|
|
65
74
|
const autoUpdateMode = process.env.SHIELD_AUTO_UPDATE ?? true;
|
|
66
75
|
log.info('updater', `Startup update check (autoUpdate=${autoUpdateMode}, current=${version_1.VERSION})`);
|
|
@@ -70,8 +79,10 @@ async function poll() {
|
|
|
70
79
|
}
|
|
71
80
|
else if (startupUpdate.action === "updated") {
|
|
72
81
|
log.info("updater", startupUpdate.message);
|
|
73
|
-
(0, updater_1.requestGatewayRestart)();
|
|
74
|
-
|
|
82
|
+
const restarted = (0, updater_1.requestGatewayRestart)();
|
|
83
|
+
if (restarted)
|
|
84
|
+
return;
|
|
85
|
+
log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
|
|
75
86
|
}
|
|
76
87
|
else if (startupUpdate.action === "notify") {
|
|
77
88
|
log.info("updater", startupUpdate.message);
|
|
@@ -139,9 +150,12 @@ async function poll() {
|
|
|
139
150
|
}
|
|
140
151
|
else if (updateResult.action === "updated") {
|
|
141
152
|
log.info("updater", updateResult.message);
|
|
142
|
-
(0, updater_1.requestGatewayRestart)();
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
const restarted = (0, updater_1.requestGatewayRestart)();
|
|
154
|
+
if (restarted) {
|
|
155
|
+
running = false;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
|
|
145
159
|
}
|
|
146
160
|
else if (updateResult.action === "rollback") {
|
|
147
161
|
log.warn("updater", updateResult.message);
|
|
@@ -178,6 +192,18 @@ async function poll() {
|
|
|
178
192
|
log.error('bridge', `Result: FAILED status=${r.statusCode} events=${r.eventCount} body=${r.body?.slice(0, 200)}`);
|
|
179
193
|
}
|
|
180
194
|
}
|
|
195
|
+
if (config.localEventBuffer && results.some(r => r.success)) {
|
|
196
|
+
const summaries = envelopes.map(env => ({
|
|
197
|
+
ts: env.event.timestamp,
|
|
198
|
+
type: env.event.event_type || 'UNKNOWN',
|
|
199
|
+
tool: env.event.tool_name || 'unknown',
|
|
200
|
+
summary: env.event.tool_metadata?.["openclaw.display_summary"] || env.event.target?.command_line || env.event.tool_name || 'event',
|
|
201
|
+
session: env.event.session_id || '?',
|
|
202
|
+
model: env.event.model || '?',
|
|
203
|
+
redacted: !!env.source?.plugin?.redaction_applied,
|
|
204
|
+
}));
|
|
205
|
+
(0, event_store_1.appendEvents)(summaries);
|
|
206
|
+
}
|
|
181
207
|
if (results.some(r => r.needsRegistration)) {
|
|
182
208
|
consecutiveFailures++;
|
|
183
209
|
registrationOk = false;
|
|
@@ -223,6 +249,18 @@ async function poll() {
|
|
|
223
249
|
consecutiveFailures++;
|
|
224
250
|
log.error('bridge', 'Poll error', err);
|
|
225
251
|
}
|
|
252
|
+
try {
|
|
253
|
+
const creds = (0, config_1.loadCredentials)();
|
|
254
|
+
const platformConfig = {
|
|
255
|
+
apiUrl: config.platformApiUrl ?? null,
|
|
256
|
+
instanceId: creds?.instanceId || '',
|
|
257
|
+
hmacSecret: creds?.hmacSecret || '',
|
|
258
|
+
};
|
|
259
|
+
await (0, case_monitor_1.checkForNewCases)(platformConfig);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
log.debug('case-monitor', `Check error: ${err instanceof Error ? err.message : String(err)}`);
|
|
263
|
+
}
|
|
226
264
|
if (!running)
|
|
227
265
|
break;
|
|
228
266
|
const interval = getBackoffInterval(config.pollIntervalMs);
|