@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.
@@ -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
+ }
@@ -13,6 +13,8 @@ export interface Config {
13
13
  maxEvents: number;
14
14
  collectHostMetrics: boolean;
15
15
  redactionEnabled: boolean;
16
+ localEventBuffer: boolean;
17
+ localEventLimit: number;
16
18
  credentials: ShieldCredentials;
17
19
  }
18
20
  export declare const SHIELD_CONFIG_PATH: string;
@@ -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
- return;
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
- running = false;
144
- break;
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);