@clawnch/clawtomaton 0.2.2 → 0.3.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 +80 -8
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +12 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/prompt.d.ts +2 -0
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +43 -2
- package/dist/agent/prompt.js.map +1 -1
- package/dist/bunker/client.d.ts +113 -0
- package/dist/bunker/client.d.ts.map +1 -0
- package/dist/bunker/client.js +404 -0
- package/dist/bunker/client.js.map +1 -0
- package/dist/bunker/self-deploy.d.ts +54 -0
- package/dist/bunker/self-deploy.d.ts.map +1 -0
- package/dist/bunker/self-deploy.js +353 -0
- package/dist/bunker/self-deploy.js.map +1 -0
- package/dist/bunker/threat-monitor.d.ts +47 -0
- package/dist/bunker/threat-monitor.d.ts.map +1 -0
- package/dist/bunker/threat-monitor.js +173 -0
- package/dist/bunker/threat-monitor.js.map +1 -0
- package/dist/bunker/types.d.ts +320 -0
- package/dist/bunker/types.d.ts.map +1 -0
- package/dist/bunker/types.js +9 -0
- package/dist/bunker/types.js.map +1 -0
- package/dist/cli.js +207 -9
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/heartbeat/index.d.ts +10 -0
- package/dist/heartbeat/index.d.ts.map +1 -1
- package/dist/heartbeat/index.js +89 -11
- package/dist/heartbeat/index.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/market/index.d.ts +12 -0
- package/dist/market/index.d.ts.map +1 -1
- package/dist/market/index.js +48 -2
- package/dist/market/index.js.map +1 -1
- package/dist/skills/bunker.d.ts +29 -0
- package/dist/skills/bunker.d.ts.map +1 -0
- package/dist/skills/bunker.js +502 -0
- package/dist/skills/bunker.js.map +1 -0
- package/dist/skills/index.d.ts +2 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +6 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +55 -0
- package/dist/state/index.js.map +1 -1
- package/dist/survival/index.d.ts.map +1 -1
- package/dist/survival/index.js +3 -0
- package/dist/survival/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-deployment — packages the agent and deploys to MoltBunker.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Pre-flight checks (BUNKER balance, no duplicate deployments)
|
|
6
|
+
* 2. Register bot on MoltBunker (or reuse existing)
|
|
7
|
+
* 3. Reserve runtime with requested tier/region
|
|
8
|
+
* 4. Deploy container with agent config as env vars
|
|
9
|
+
* 5. Enable cloning if configured
|
|
10
|
+
* 6. Health verification (poll until running + heartbeat in logs)
|
|
11
|
+
* 7. Persist state (bot_id, runtime_id, deployment_id, container_id)
|
|
12
|
+
*
|
|
13
|
+
* Edge cases:
|
|
14
|
+
* - Existing running container → return status instead of re-deploying
|
|
15
|
+
* - Bot already registered → reuse it
|
|
16
|
+
* - Partial failure (runtime reserved, deploy fails) → release runtime
|
|
17
|
+
* - Env var size limits → config is small enough to fit, state stays in container volume
|
|
18
|
+
*/
|
|
19
|
+
import { BunkerNotFoundError } from './client.js';
|
|
20
|
+
import { BUNKER_DOCKER_IMAGE, BUNKER_DEFAULT_DURATION_HOURS } from '../constants.js';
|
|
21
|
+
// Resource tier mappings (must match MoltBunker catalog)
|
|
22
|
+
const TIER_RESOURCES = {
|
|
23
|
+
minimal: { cpu_shares: 1024, memory_mb: 1024, storage_mb: 10240, network_mbps: 100 },
|
|
24
|
+
standard: { cpu_shares: 2048, memory_mb: 4096, storage_mb: 51200, network_mbps: 100 },
|
|
25
|
+
performance: { cpu_shares: 4096, memory_mb: 8192, storage_mb: 204800, network_mbps: 100 },
|
|
26
|
+
enterprise: { cpu_shares: 8192, memory_mb: 16384, storage_mb: 512000, network_mbps: 100 },
|
|
27
|
+
};
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Self-Deploy
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Deploy this agent to MoltBunker. Handles the full lifecycle:
|
|
33
|
+
* bot registration → runtime reservation → container deployment → health check.
|
|
34
|
+
*
|
|
35
|
+
* Idempotent: if already deployed and running, returns existing state.
|
|
36
|
+
*/
|
|
37
|
+
export async function selfDeploy(client, config, identity, state, opts) {
|
|
38
|
+
const bunkerConfig = config.bunker;
|
|
39
|
+
if (!bunkerConfig) {
|
|
40
|
+
throw new Error('MoltBunker not configured. Run: clawtomaton setup (with bunker enabled)');
|
|
41
|
+
}
|
|
42
|
+
// Check for existing deployment
|
|
43
|
+
const existing = state.getBunkerState();
|
|
44
|
+
if (existing?.containerId) {
|
|
45
|
+
const running = await isContainerRunning(client, existing.containerId);
|
|
46
|
+
if (running) {
|
|
47
|
+
return {
|
|
48
|
+
botId: existing.botId,
|
|
49
|
+
runtimeId: existing.runtimeId,
|
|
50
|
+
deploymentId: existing.deploymentId,
|
|
51
|
+
containerId: existing.containerId,
|
|
52
|
+
expiresAt: existing.expiresAt,
|
|
53
|
+
region: bunkerConfig.region,
|
|
54
|
+
onionAddress: null, // caller can fetch full status if needed
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Container exists but not running — proceed with new deployment
|
|
58
|
+
}
|
|
59
|
+
// Pre-flight: check BUNKER balance
|
|
60
|
+
const balance = await client.getBalance(identity.address);
|
|
61
|
+
if (balance.available <= 0) {
|
|
62
|
+
throw new Error(`Insufficient BUNKER balance for deployment. ` +
|
|
63
|
+
`Available: ${balance.available.toLocaleString()} BUNKER. ` +
|
|
64
|
+
`Deposit BUNKER tokens to your wallet (${identity.address}) first.`);
|
|
65
|
+
}
|
|
66
|
+
const image = opts?.image ?? BUNKER_DOCKER_IMAGE;
|
|
67
|
+
const region = opts?.region ?? bunkerConfig.region;
|
|
68
|
+
const durationHours = opts?.durationHours ?? bunkerConfig.defaultDurationHours ?? BUNKER_DEFAULT_DURATION_HOURS;
|
|
69
|
+
const tierResources = TIER_RESOURCES[bunkerConfig.tier] ?? TIER_RESOURCES.standard;
|
|
70
|
+
// Step 1: Register bot (or find existing)
|
|
71
|
+
const bot = await findOrCreateBot(client, identity, image, tierResources, region);
|
|
72
|
+
state.audit('bunker_bot', `Bot registered: ${bot.id} (${bot.name})`);
|
|
73
|
+
// Step 2: Reserve runtime
|
|
74
|
+
let runtime;
|
|
75
|
+
try {
|
|
76
|
+
runtime = await client.reserveRuntime({
|
|
77
|
+
bot_id: bot.id,
|
|
78
|
+
min_memory_mb: tierResources.memory_mb,
|
|
79
|
+
min_cpu_shares: tierResources.cpu_shares,
|
|
80
|
+
duration_hours: durationHours,
|
|
81
|
+
region,
|
|
82
|
+
});
|
|
83
|
+
state.audit('bunker_runtime', `Runtime reserved: ${runtime.id} (${durationHours}h, ${region || 'auto'})`);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
throw new Error(`Failed to reserve runtime: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
87
|
+
`Check BUNKER balance and region availability.`);
|
|
88
|
+
}
|
|
89
|
+
// Step 3: Deploy container
|
|
90
|
+
let deployment;
|
|
91
|
+
try {
|
|
92
|
+
deployment = await client.createDeployment({
|
|
93
|
+
runtime_id: runtime.id,
|
|
94
|
+
env: buildEnvVars(config, identity),
|
|
95
|
+
cmd: ['node', 'dist/cli.js', 'daemon'],
|
|
96
|
+
});
|
|
97
|
+
state.audit('bunker_deploy', `Deployment created: ${deployment.id} → container ${deployment.container_id}`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Cleanup: release the runtime we just reserved
|
|
101
|
+
try {
|
|
102
|
+
await client.releaseRuntime(runtime.id);
|
|
103
|
+
state.audit('bunker_cleanup', `Released runtime ${runtime.id} after failed deployment`);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Cleanup failure is non-fatal
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Failed to deploy container: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
+
}
|
|
110
|
+
// Step 4: Enable cloning if configured
|
|
111
|
+
if (bunkerConfig.autoCloneOnThreat) {
|
|
112
|
+
try {
|
|
113
|
+
const cloningConfig = {
|
|
114
|
+
enabled: true,
|
|
115
|
+
auto_clone_on_threat: true,
|
|
116
|
+
max_clones: bunkerConfig.maxClones,
|
|
117
|
+
clone_delay_seconds: 60,
|
|
118
|
+
sync_state: true,
|
|
119
|
+
sync_interval_seconds: 300,
|
|
120
|
+
};
|
|
121
|
+
await client.enableCloning(bot.id, cloningConfig);
|
|
122
|
+
state.audit('bunker_cloning', `Auto-cloning enabled (max ${bunkerConfig.maxClones} clones)`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// Non-fatal — log but continue
|
|
126
|
+
state.audit('bunker_cloning_warn', `Failed to enable cloning: ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Step 5: Health verification
|
|
130
|
+
const expiresAt = new Date(runtime.expires_at).getTime();
|
|
131
|
+
if (!opts?.skipHealthCheck) {
|
|
132
|
+
await waitForHealthy(client, deployment.container_id, state);
|
|
133
|
+
}
|
|
134
|
+
// Step 6: Persist state
|
|
135
|
+
const bunkerState = {
|
|
136
|
+
botId: bot.id,
|
|
137
|
+
runtimeId: runtime.id,
|
|
138
|
+
deploymentId: deployment.id,
|
|
139
|
+
containerId: deployment.container_id,
|
|
140
|
+
expiresAt,
|
|
141
|
+
lastThreatCheck: 0,
|
|
142
|
+
lastThreatLevel: 'unknown',
|
|
143
|
+
cloneIds: [],
|
|
144
|
+
};
|
|
145
|
+
state.saveBunkerState(bunkerState);
|
|
146
|
+
return {
|
|
147
|
+
botId: bot.id,
|
|
148
|
+
runtimeId: runtime.id,
|
|
149
|
+
deploymentId: deployment.id,
|
|
150
|
+
containerId: deployment.container_id,
|
|
151
|
+
expiresAt,
|
|
152
|
+
region: deployment.region,
|
|
153
|
+
onionAddress: deployment.onion_address,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Stop Deployment
|
|
158
|
+
// ============================================================================
|
|
159
|
+
/**
|
|
160
|
+
* Stop the bunker deployment and clean up state.
|
|
161
|
+
* Does NOT delete the bot — it can be reused for future deployments.
|
|
162
|
+
*/
|
|
163
|
+
export async function stopDeploy(client, state) {
|
|
164
|
+
const bunkerState = state.getBunkerState();
|
|
165
|
+
if (!bunkerState?.deploymentId) {
|
|
166
|
+
throw new Error('No active bunker deployment found.');
|
|
167
|
+
}
|
|
168
|
+
// Stop deployment
|
|
169
|
+
try {
|
|
170
|
+
await client.stopDeployment(bunkerState.deploymentId);
|
|
171
|
+
state.audit('bunker_stop', `Deployment stopped: ${bunkerState.deploymentId}`);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (!(err instanceof BunkerNotFoundError)) {
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
// Already stopped/deleted — continue cleanup
|
|
178
|
+
}
|
|
179
|
+
// Release runtime
|
|
180
|
+
if (bunkerState.runtimeId) {
|
|
181
|
+
try {
|
|
182
|
+
await client.releaseRuntime(bunkerState.runtimeId);
|
|
183
|
+
state.audit('bunker_cleanup', `Runtime released: ${bunkerState.runtimeId}`);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Non-fatal
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Clear persisted state (keep bot_id for reuse)
|
|
190
|
+
state.saveBunkerState({
|
|
191
|
+
botId: bunkerState.botId,
|
|
192
|
+
runtimeId: null,
|
|
193
|
+
deploymentId: null,
|
|
194
|
+
containerId: null,
|
|
195
|
+
expiresAt: null,
|
|
196
|
+
lastThreatCheck: 0,
|
|
197
|
+
lastThreatLevel: 'unknown',
|
|
198
|
+
cloneIds: [],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Helpers
|
|
203
|
+
// ============================================================================
|
|
204
|
+
/**
|
|
205
|
+
* Find an existing bot owned by this agent, or create a new one.
|
|
206
|
+
*/
|
|
207
|
+
async function findOrCreateBot(client, identity, image, resources, region) {
|
|
208
|
+
// Check for existing bot with this name
|
|
209
|
+
try {
|
|
210
|
+
const bots = await client.listBots();
|
|
211
|
+
const existing = bots.find((b) => b.name === `clawtomaton-${identity.name}` || b.name === identity.name);
|
|
212
|
+
if (existing)
|
|
213
|
+
return existing;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// List failed — will try to create
|
|
217
|
+
}
|
|
218
|
+
return client.createBot({
|
|
219
|
+
name: `clawtomaton-${identity.name}`,
|
|
220
|
+
image,
|
|
221
|
+
description: `Clawtomaton autonomous agent: ${identity.name}`,
|
|
222
|
+
resources,
|
|
223
|
+
region,
|
|
224
|
+
metadata: {
|
|
225
|
+
agent_name: identity.name,
|
|
226
|
+
agent_address: identity.address,
|
|
227
|
+
version: '0.3.0',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Build env vars for the container. Contains everything the agent needs
|
|
233
|
+
* to reconstruct its config and identity inside the container.
|
|
234
|
+
*
|
|
235
|
+
* Private key is included because the container runs encrypted on MoltBunker.
|
|
236
|
+
* The agent needs it to sign transactions.
|
|
237
|
+
*/
|
|
238
|
+
function buildEnvVars(config, identity) {
|
|
239
|
+
const env = {
|
|
240
|
+
NODE_ENV: 'production',
|
|
241
|
+
// Identity — agent reconstructs from these
|
|
242
|
+
CLAWTOMATON_NAME: identity.name,
|
|
243
|
+
CLAWTOMATON_ADDRESS: identity.address,
|
|
244
|
+
CLAWTOMATON_PRIVATE_KEY: identity.privateKey,
|
|
245
|
+
CLAWTOMATON_API_KEY: identity.apiKey,
|
|
246
|
+
CLAWTOMATON_CREATOR: identity.creatorAddress,
|
|
247
|
+
CLAWTOMATON_GENESIS: identity.genesisPrompt,
|
|
248
|
+
// Inference
|
|
249
|
+
CLAWTOMATON_INFERENCE_PROVIDER: config.inference.provider,
|
|
250
|
+
CLAWTOMATON_INFERENCE_API_KEY: config.inference.apiKey,
|
|
251
|
+
CLAWTOMATON_INFERENCE_MODEL: config.inference.model,
|
|
252
|
+
CLAWTOMATON_INFERENCE_FALLBACK: config.inference.fallbackModel,
|
|
253
|
+
// RPC
|
|
254
|
+
CLAWTOMATON_RPC_URL: config.rpcUrl,
|
|
255
|
+
// Heartbeat
|
|
256
|
+
CLAWTOMATON_HEARTBEAT_MS: String(config.heartbeatIntervalMs),
|
|
257
|
+
};
|
|
258
|
+
// Optional token info
|
|
259
|
+
if (identity.tokenAddress) {
|
|
260
|
+
env.CLAWTOMATON_TOKEN_ADDRESS = identity.tokenAddress;
|
|
261
|
+
}
|
|
262
|
+
if (identity.tokenSymbol) {
|
|
263
|
+
env.CLAWTOMATON_TOKEN_SYMBOL = identity.tokenSymbol;
|
|
264
|
+
}
|
|
265
|
+
// Optional Conway
|
|
266
|
+
if (config.conwayApiKey) {
|
|
267
|
+
env.CONWAY_API_KEY = config.conwayApiKey;
|
|
268
|
+
}
|
|
269
|
+
// Bunker config (so the containerized agent can also manage its own bunker)
|
|
270
|
+
if (config.bunker) {
|
|
271
|
+
env.CLAWTOMATON_BUNKER_API_URL = config.bunker.apiUrl;
|
|
272
|
+
env.CLAWTOMATON_BUNKER_REGION = config.bunker.region;
|
|
273
|
+
env.CLAWTOMATON_BUNKER_TIER = config.bunker.tier;
|
|
274
|
+
env.CLAWTOMATON_BUNKER_AUTO_CLONE = String(config.bunker.autoCloneOnThreat);
|
|
275
|
+
env.CLAWTOMATON_BUNKER_MAX_CLONES = String(config.bunker.maxClones);
|
|
276
|
+
}
|
|
277
|
+
return env;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Poll container status until it's running and producing heartbeat output.
|
|
281
|
+
* Throws if container fails to start within timeout.
|
|
282
|
+
*/
|
|
283
|
+
async function waitForHealthy(client, containerId, state, timeoutMs = 120_000, pollIntervalMs = 5_000) {
|
|
284
|
+
const deadline = Date.now() + timeoutMs;
|
|
285
|
+
const terminalStatuses = ['failed', 'terminated', 'stopped'];
|
|
286
|
+
while (Date.now() < deadline) {
|
|
287
|
+
try {
|
|
288
|
+
const container = await client.getContainer(containerId);
|
|
289
|
+
if (terminalStatuses.includes(container.status)) {
|
|
290
|
+
// Try to get logs for debugging
|
|
291
|
+
let logSnippet = '';
|
|
292
|
+
try {
|
|
293
|
+
logSnippet = await client.getContainerLogs(containerId, 20);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// Non-fatal
|
|
297
|
+
}
|
|
298
|
+
throw new Error(`Container ${containerId} entered terminal state: ${container.status}` +
|
|
299
|
+
(logSnippet ? `\nLast logs:\n${logSnippet}` : ''));
|
|
300
|
+
}
|
|
301
|
+
if (container.status === 'running') {
|
|
302
|
+
// Verify heartbeat is actually producing output
|
|
303
|
+
try {
|
|
304
|
+
const logs = await client.getContainerLogs(containerId, 5);
|
|
305
|
+
if (logs && (logs.includes('[heartbeat]') || logs.includes('[clawtomaton]'))) {
|
|
306
|
+
state.audit('bunker_healthy', `Container ${containerId} is healthy and producing output`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Logs not available yet — container just started
|
|
312
|
+
}
|
|
313
|
+
// Container is running but no logs yet — give it a moment
|
|
314
|
+
// After 30s of running with no logs, consider it healthy anyway
|
|
315
|
+
if (container.started_at) {
|
|
316
|
+
const startedAt = new Date(container.started_at).getTime();
|
|
317
|
+
if (Date.now() - startedAt > 30_000) {
|
|
318
|
+
state.audit('bunker_healthy', `Container ${containerId} running (no log output yet, assumed healthy)`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
if (err instanceof Error && !err.message.includes('terminal state')) {
|
|
326
|
+
// Transient error — continue polling
|
|
327
|
+
state.audit('bunker_health_poll', `Health check transient error: ${err.message}`);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
await sleep(pollIntervalMs);
|
|
334
|
+
}
|
|
335
|
+
// Timeout — deployment is still starting, not necessarily failed
|
|
336
|
+
state.audit('bunker_health_timeout', `Container ${containerId} health check timed out after ${timeoutMs / 1000}s`);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Check if a container is currently running.
|
|
340
|
+
*/
|
|
341
|
+
async function isContainerRunning(client, containerId) {
|
|
342
|
+
try {
|
|
343
|
+
const container = await client.getContainer(containerId);
|
|
344
|
+
return container.status === 'running';
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function sleep(ms) {
|
|
351
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=self-deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-deploy.js","sourceRoot":"","sources":["../../src/bunker/self-deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAe,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAU/D,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AA2BrF,yDAAyD;AACzD,MAAM,cAAc,GAAwG;IAC1H,OAAO,EAAM,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAG,UAAU,EAAE,KAAK,EAAG,YAAY,EAAE,GAAG,EAAE;IAC1F,QAAQ,EAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAG,UAAU,EAAE,KAAK,EAAG,YAAY,EAAE,GAAG,EAAE;IAC1F,WAAW,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAG,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE;IAC1F,UAAU,EAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE;CAC3F,CAAC;AAEF,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAoB,EACpB,MAAyB,EACzB,QAA6B,EAC7B,KAAiB,EACjB,IAAwB;IAExB,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;IAC7F,CAAC;IAED,gCAAgC;IAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACxC,IAAI,QAAQ,EAAE,WAAW,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;gBACL,KAAK,EAAE,QAAQ,CAAC,KAAM;gBACtB,SAAS,EAAE,QAAQ,CAAC,SAAU;gBAC9B,YAAY,EAAE,QAAQ,CAAC,YAAa;gBACpC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,SAAS,EAAE,QAAQ,CAAC,SAAU;gBAC9B,MAAM,EAAE,YAAY,CAAC,MAAM;gBAC3B,YAAY,EAAE,IAAI,EAAE,yCAAyC;aAC9D,CAAC;QACJ,CAAC;QACD,iEAAiE;IACnE,CAAC;IAED,mCAAmC;IACnC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,8CAA8C;YAC9C,cAAc,OAAO,CAAC,SAAS,CAAC,cAAc,EAAE,WAAW;YAC3D,yCAAyC,QAAQ,CAAC,OAAO,UAAU,CACpE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,mBAAmB,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC;IACnD,MAAM,aAAa,GAAG,IAAI,EAAE,aAAa,IAAI,YAAY,CAAC,oBAAoB,IAAI,6BAA6B,CAAC;IAChH,MAAM,aAAa,GAAG,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC;IAEnF,0CAA0C;IAC1C,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;IAClF,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,mBAAmB,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;IAErE,0BAA0B;IAC1B,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC;YACpC,MAAM,EAAE,GAAG,CAAC,EAAE;YACd,aAAa,EAAE,aAAa,CAAC,SAAS;YACtC,cAAc,EAAE,aAAa,CAAC,UAAU;YACxC,cAAc,EAAE,aAAa;YAC7B,MAAM;SACP,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,qBAAqB,OAAO,CAAC,EAAE,KAAK,aAAa,MAAM,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC;IAC5G,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,8BAA8B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI;YAClF,+CAA+C,CAChD,CAAC;IACJ,CAAC;IAED,2BAA2B;IAC3B,IAAI,UAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC;YACzC,UAAU,EAAE,OAAO,CAAC,EAAE;YACtB,GAAG,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC;YACnC,GAAG,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,CAAC;SACvC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CACT,eAAe,EACf,uBAAuB,UAAU,CAAC,EAAE,gBAAgB,UAAU,CAAC,YAAY,EAAE,CAC9E,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gDAAgD;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACxC,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,oBAAoB,OAAO,CAAC,EAAE,0BAA0B,CAAC,CAAC;QAC1F,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;QACD,MAAM,IAAI,KAAK,CACb,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,uCAAuC;IACvC,IAAI,YAAY,CAAC,iBAAiB,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,aAAa,GAAkB;gBACnC,OAAO,EAAE,IAAI;gBACb,oBAAoB,EAAE,IAAI;gBAC1B,UAAU,EAAE,YAAY,CAAC,SAAS;gBAClC,mBAAmB,EAAE,EAAE;gBACvB,UAAU,EAAE,IAAI;gBAChB,qBAAqB,EAAE,GAAG;aAC3B,CAAC;YACF,MAAM,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;YAClD,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,6BAA6B,YAAY,CAAC,SAAS,UAAU,CAAC,CAAC;QAC/F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,+BAA+B;YAC/B,KAAK,CAAC,KAAK,CAAC,qBAAqB,EAAE,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtH,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;IAEzD,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC;QAC3B,MAAM,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAC/D,CAAC;IAED,wBAAwB;IACxB,MAAM,WAAW,GAAgB;QAC/B,KAAK,EAAE,GAAG,CAAC,EAAE;QACb,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,YAAY,EAAE,UAAU,CAAC,EAAE;QAC3B,WAAW,EAAE,UAAU,CAAC,YAAY;QACpC,SAAS;QACT,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,SAAS;QAC1B,QAAQ,EAAE,EAAE;KACb,CAAC;IACF,KAAK,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IAEnC,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,EAAE;QACb,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,YAAY,EAAE,UAAU,CAAC,EAAE;QAC3B,WAAW,EAAE,UAAU,CAAC,YAAY;QACpC,SAAS;QACT,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,YAAY,EAAE,UAAU,CAAC,aAAa;KACvC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAoB,EACpB,KAAiB;IAEjB,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,kBAAkB;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;QACtD,KAAK,CAAC,KAAK,CAAC,aAAa,EAAE,uBAAuB,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,CAAC,GAAG,YAAY,mBAAmB,CAAC,EAAE,CAAC;YAC1C,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,6CAA6C;IAC/C,CAAC;IAED,kBAAkB;IAClB,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YACnD,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,qBAAqB,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;QAC9E,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,KAAK,CAAC,eAAe,CAAC;QACpB,KAAK,EAAE,WAAW,CAAC,KAAK;QACxB,SAAS,EAAE,IAAI;QACf,YAAY,EAAE,IAAI;QAClB,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,IAAI;QACf,eAAe,EAAE,CAAC;QAClB,eAAe,EAAE,SAAS;QAC1B,QAAQ,EAAE,EAAE;KACb,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E;;GAEG;AACH,KAAK,UAAU,eAAe,CAC5B,MAAoB,EACpB,QAA6B,EAC7B,KAAa,EACb,SAA8F,EAC9F,MAAoB;IAEpB,wCAAwC;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAC7E,CAAC;QACF,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;IAED,OAAO,MAAM,CAAC,SAAS,CAAC;QACtB,IAAI,EAAE,eAAe,QAAQ,CAAC,IAAI,EAAE;QACpC,KAAK;QACL,WAAW,EAAE,iCAAiC,QAAQ,CAAC,IAAI,EAAE;QAC7D,SAAS;QACT,MAAM;QACN,QAAQ,EAAE;YACR,UAAU,EAAE,QAAQ,CAAC,IAAI;YACzB,aAAa,EAAE,QAAQ,CAAC,OAAO;YAC/B,OAAO,EAAE,OAAO;SACjB;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CACnB,MAAyB,EACzB,QAA6B;IAE7B,MAAM,GAAG,GAA2B;QAClC,QAAQ,EAAE,YAAY;QACtB,2CAA2C;QAC3C,gBAAgB,EAAE,QAAQ,CAAC,IAAI;QAC/B,mBAAmB,EAAE,QAAQ,CAAC,OAAO;QACrC,uBAAuB,EAAE,QAAQ,CAAC,UAAU;QAC5C,mBAAmB,EAAE,QAAQ,CAAC,MAAM;QACpC,mBAAmB,EAAE,QAAQ,CAAC,cAAc;QAC5C,mBAAmB,EAAE,QAAQ,CAAC,aAAa;QAC3C,YAAY;QACZ,8BAA8B,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ;QACzD,6BAA6B,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM;QACtD,2BAA2B,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK;QACnD,8BAA8B,EAAE,MAAM,CAAC,SAAS,CAAC,aAAa;QAC9D,MAAM;QACN,mBAAmB,EAAE,MAAM,CAAC,MAAM;QAClC,YAAY;QACZ,wBAAwB,EAAE,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC;KAC7D,CAAC;IAEF,sBAAsB;IACtB,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;QAC1B,GAAG,CAAC,yBAAyB,GAAG,QAAQ,CAAC,YAAY,CAAC;IACxD,CAAC;IACD,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzB,GAAG,CAAC,wBAAwB,GAAG,QAAQ,CAAC,WAAW,CAAC;IACtD,CAAC;IAED,kBAAkB;IAClB,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC;IAC3C,CAAC;IAED,4EAA4E;IAC5E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,GAAG,CAAC,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACtD,GAAG,CAAC,yBAAyB,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACrD,GAAG,CAAC,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACjD,GAAG,CAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC5E,GAAG,CAAC,6BAA6B,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAC3B,MAAoB,EACpB,WAAmB,EACnB,KAAiB,EACjB,SAAS,GAAG,OAAO,EACnB,cAAc,GAAG,KAAK;IAEtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,MAAM,gBAAgB,GAA2B,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;IAErF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAEzD,IAAI,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChD,gCAAgC;gBAChC,IAAI,UAAU,GAAG,EAAE,CAAC;gBACpB,IAAI,CAAC;oBACH,UAAU,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;gBAC9D,CAAC;gBAAC,MAAM,CAAC;oBACP,YAAY;gBACd,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,aAAa,WAAW,4BAA4B,SAAS,CAAC,MAAM,EAAE;oBACtE,CAAC,UAAU,CAAC,CAAC,CAAC,iBAAiB,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAClD,CAAC;YACJ,CAAC;YAED,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACnC,gDAAgD;gBAChD,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;oBAC3D,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;wBAC7E,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,aAAa,WAAW,kCAAkC,CAAC,CAAC;wBAC1F,OAAO;oBACT,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,kDAAkD;gBACpD,CAAC;gBAED,0DAA0D;gBAC1D,gEAAgE;gBAChE,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;oBACzB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;oBAC3D,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAAC;wBACpC,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,aAAa,WAAW,+CAA+C,CAAC,CAAC;wBACvG,OAAO;oBACT,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACpE,qCAAqC;gBACrC,KAAK,CAAC,KAAK,CAAC,oBAAoB,EAAE,iCAAiC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACpF,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAED,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;IAC9B,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,KAAK,CAAC,uBAAuB,EAAE,aAAa,WAAW,iCAAiC,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC;AACrH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAAC,MAAoB,EAAE,WAAmB;IACzE,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACzD,OAAO,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat monitor — polls MoltBunker threat endpoint and triggers auto-clone.
|
|
3
|
+
*
|
|
4
|
+
* Not a standalone timer. Called from the heartbeat loop when bunker is configured.
|
|
5
|
+
* The heartbeat already handles timing — this module provides:
|
|
6
|
+
* - checkThreat(): fetch threat level, update state, return escalation info
|
|
7
|
+
* - handleThreatEscalation(): auto-clone if threshold crossed
|
|
8
|
+
*
|
|
9
|
+
* Threat levels and actions:
|
|
10
|
+
* low (score < 0.3) — log only, no action
|
|
11
|
+
* medium (score 0.3-0.6) — audit log, surface in system prompt
|
|
12
|
+
* high (score 0.6-0.8) — auto-clone to different region
|
|
13
|
+
* critical (score > 0.8) — auto-clone + auto-migrate
|
|
14
|
+
*/
|
|
15
|
+
import type { BunkerClient } from './client.js';
|
|
16
|
+
import type { StateStore } from '../state/index.js';
|
|
17
|
+
import type { ThreatAssessment, ThreatLevel } from './types.js';
|
|
18
|
+
export interface ThreatCheckResult {
|
|
19
|
+
assessment: ThreatAssessment;
|
|
20
|
+
escalated: boolean;
|
|
21
|
+
previousLevel: ThreatLevel;
|
|
22
|
+
action: 'none' | 'logged' | 'cloned' | 'migrated';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check the current threat level. Updates state and returns escalation info.
|
|
26
|
+
* Non-fatal on API errors — returns null if threat endpoint is unreachable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkThreat(client: BunkerClient, state: StateStore): Promise<ThreatCheckResult | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Handle threat escalation by auto-cloning to a different region.
|
|
31
|
+
* Only acts on high/critical threats. Respects max_clones limit.
|
|
32
|
+
*
|
|
33
|
+
* Returns the clone ID if a clone was created, null otherwise.
|
|
34
|
+
*/
|
|
35
|
+
export declare function handleThreatEscalation(client: BunkerClient, state: StateStore, assessment: ThreatAssessment, autoCloneEnabled: boolean, maxClones: number): Promise<string | null>;
|
|
36
|
+
/**
|
|
37
|
+
* For critical threats: migrate the primary container to the safest available region.
|
|
38
|
+
* This is more aggressive than cloning — it moves the container.
|
|
39
|
+
*
|
|
40
|
+
* Returns the migration ID if started, null otherwise.
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleCriticalMigration(client: BunkerClient, state: StateStore, assessment: ThreatAssessment): Promise<string | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Format a threat assessment for display in the system prompt or logs.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatThreat(assessment: ThreatAssessment): string;
|
|
47
|
+
//# sourceMappingURL=threat-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat-monitor.d.ts","sourceRoot":"","sources":["../../src/bunker/threat-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAA6B,MAAM,YAAY,CAAC;AAM3F,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,WAAW,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;CACnD;AA0BD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAqCnC;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,gBAAgB,EAC5B,gBAAgB,EAAE,OAAO,EACzB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+CxB;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,gBAAgB,GAAG,MAAM,CAcjE"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat monitor — polls MoltBunker threat endpoint and triggers auto-clone.
|
|
3
|
+
*
|
|
4
|
+
* Not a standalone timer. Called from the heartbeat loop when bunker is configured.
|
|
5
|
+
* The heartbeat already handles timing — this module provides:
|
|
6
|
+
* - checkThreat(): fetch threat level, update state, return escalation info
|
|
7
|
+
* - handleThreatEscalation(): auto-clone if threshold crossed
|
|
8
|
+
*
|
|
9
|
+
* Threat levels and actions:
|
|
10
|
+
* low (score < 0.3) — log only, no action
|
|
11
|
+
* medium (score 0.3-0.6) — audit log, surface in system prompt
|
|
12
|
+
* high (score 0.6-0.8) — auto-clone to different region
|
|
13
|
+
* critical (score > 0.8) — auto-clone + auto-migrate
|
|
14
|
+
*/
|
|
15
|
+
import { BunkerError } from './client.js';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Threat Level Ordering
|
|
18
|
+
// ============================================================================
|
|
19
|
+
const THREAT_SEVERITY = {
|
|
20
|
+
unknown: 0,
|
|
21
|
+
low: 1,
|
|
22
|
+
medium: 2,
|
|
23
|
+
high: 3,
|
|
24
|
+
critical: 4,
|
|
25
|
+
};
|
|
26
|
+
function isEscalation(from, to) {
|
|
27
|
+
return THREAT_SEVERITY[to] > THREAT_SEVERITY[from];
|
|
28
|
+
}
|
|
29
|
+
function isActionable(level) {
|
|
30
|
+
return THREAT_SEVERITY[level] >= THREAT_SEVERITY.high;
|
|
31
|
+
}
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Core Functions
|
|
34
|
+
// ============================================================================
|
|
35
|
+
/**
|
|
36
|
+
* Check the current threat level. Updates state and returns escalation info.
|
|
37
|
+
* Non-fatal on API errors — returns null if threat endpoint is unreachable.
|
|
38
|
+
*/
|
|
39
|
+
export async function checkThreat(client, state) {
|
|
40
|
+
const bunkerState = state.getBunkerState();
|
|
41
|
+
if (!bunkerState)
|
|
42
|
+
return null;
|
|
43
|
+
const previousLevel = bunkerState.lastThreatLevel;
|
|
44
|
+
try {
|
|
45
|
+
const assessment = await client.getThreat();
|
|
46
|
+
// Update state
|
|
47
|
+
state.updateBunkerThreat(assessment.level);
|
|
48
|
+
const escalated = isEscalation(previousLevel, assessment.level);
|
|
49
|
+
if (escalated) {
|
|
50
|
+
state.audit('bunker_threat_escalation', `Threat escalated: ${previousLevel} → ${assessment.level} (score: ${assessment.score.toFixed(2)}, ` +
|
|
51
|
+
`signals: ${assessment.active_signals.length})`);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
assessment,
|
|
55
|
+
escalated,
|
|
56
|
+
previousLevel,
|
|
57
|
+
action: 'none', // caller decides action
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
// Non-fatal — threat API might be temporarily unavailable
|
|
62
|
+
if (err instanceof BunkerError) {
|
|
63
|
+
state.audit('bunker_threat_error', `Threat check failed: ${err.message} (HTTP ${err.status})`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
state.audit('bunker_threat_error', `Threat check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Handle threat escalation by auto-cloning to a different region.
|
|
73
|
+
* Only acts on high/critical threats. Respects max_clones limit.
|
|
74
|
+
*
|
|
75
|
+
* Returns the clone ID if a clone was created, null otherwise.
|
|
76
|
+
*/
|
|
77
|
+
export async function handleThreatEscalation(client, state, assessment, autoCloneEnabled, maxClones) {
|
|
78
|
+
if (!autoCloneEnabled)
|
|
79
|
+
return null;
|
|
80
|
+
if (!isActionable(assessment.level))
|
|
81
|
+
return null;
|
|
82
|
+
const bunkerState = state.getBunkerState();
|
|
83
|
+
if (!bunkerState?.containerId)
|
|
84
|
+
return null;
|
|
85
|
+
// Check clone limit
|
|
86
|
+
if (bunkerState.cloneIds.length >= maxClones) {
|
|
87
|
+
state.audit('bunker_clone_limit', `Threat level ${assessment.level} but at clone limit (${bunkerState.cloneIds.length}/${maxClones})`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
// Pick target region — choose one we don't have a clone in yet
|
|
91
|
+
const targetRegion = pickCloneRegion(bunkerState);
|
|
92
|
+
if (!targetRegion) {
|
|
93
|
+
state.audit('bunker_clone_skip', 'No available region for threat-response clone');
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const clone = await client.createClone({
|
|
98
|
+
source_id: bunkerState.containerId,
|
|
99
|
+
target_region: targetRegion,
|
|
100
|
+
priority: assessment.level === 'critical' ? 1 : 2,
|
|
101
|
+
reason: 'threat_response',
|
|
102
|
+
include_state: true,
|
|
103
|
+
});
|
|
104
|
+
state.addBunkerClone(clone.clone_id);
|
|
105
|
+
state.audit('bunker_auto_clone', `Threat-response clone created: ${clone.clone_id} → ${targetRegion} ` +
|
|
106
|
+
`(threat: ${assessment.level}, score: ${assessment.score.toFixed(2)})`);
|
|
107
|
+
return clone.clone_id;
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
state.audit('bunker_clone_error', `Failed to create threat-response clone: ${err instanceof Error ? err.message : String(err)}`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* For critical threats: migrate the primary container to the safest available region.
|
|
116
|
+
* This is more aggressive than cloning — it moves the container.
|
|
117
|
+
*
|
|
118
|
+
* Returns the migration ID if started, null otherwise.
|
|
119
|
+
*/
|
|
120
|
+
export async function handleCriticalMigration(client, state, assessment) {
|
|
121
|
+
if (assessment.level !== 'critical')
|
|
122
|
+
return null;
|
|
123
|
+
const bunkerState = state.getBunkerState();
|
|
124
|
+
if (!bunkerState?.containerId)
|
|
125
|
+
return null;
|
|
126
|
+
const targetRegion = pickCloneRegion(bunkerState);
|
|
127
|
+
if (!targetRegion)
|
|
128
|
+
return null;
|
|
129
|
+
try {
|
|
130
|
+
const migration = await client.migrate({
|
|
131
|
+
container_id: bunkerState.containerId,
|
|
132
|
+
target_region: targetRegion,
|
|
133
|
+
keep_original: true, // keep original until migration completes
|
|
134
|
+
});
|
|
135
|
+
state.audit('bunker_critical_migrate', `Critical threat migration started: ${migration.migration_id} → ${targetRegion}`);
|
|
136
|
+
return migration.migration_id;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
state.audit('bunker_migrate_error', `Failed to start critical migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Format a threat assessment for display in the system prompt or logs.
|
|
145
|
+
*/
|
|
146
|
+
export function formatThreat(assessment) {
|
|
147
|
+
const lines = [
|
|
148
|
+
`Threat Level: ${assessment.level.toUpperCase()} (score: ${assessment.score.toFixed(2)})`,
|
|
149
|
+
`Recommendation: ${assessment.recommendation}`,
|
|
150
|
+
];
|
|
151
|
+
if (assessment.active_signals.length > 0) {
|
|
152
|
+
lines.push(`Active Signals:`);
|
|
153
|
+
for (const signal of assessment.active_signals) {
|
|
154
|
+
lines.push(` - ${signal.type}: ${signal.details} (confidence: ${(signal.confidence * 100).toFixed(0)}%)`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Helpers
|
|
161
|
+
// ============================================================================
|
|
162
|
+
const ALL_REGIONS = ['americas', 'europe', 'asia_pacific'];
|
|
163
|
+
/**
|
|
164
|
+
* Pick a region that doesn't already have a clone.
|
|
165
|
+
* Simple round-robin: tries each region not already used.
|
|
166
|
+
*/
|
|
167
|
+
function pickCloneRegion(bunkerState) {
|
|
168
|
+
// We don't have clone regions stored — just pick regions we likely don't have
|
|
169
|
+
// The clone IDs don't tell us regions, so cycle through all
|
|
170
|
+
const index = bunkerState.cloneIds.length % ALL_REGIONS.length;
|
|
171
|
+
return ALL_REGIONS[index];
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=threat-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat-monitor.js","sourceRoot":"","sources":["../../src/bunker/threat-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAe1C,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E,MAAM,eAAe,GAAgC;IACnD,OAAO,EAAE,CAAC;IACV,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF,SAAS,YAAY,CAAC,IAAiB,EAAE,EAAe;IACtD,OAAO,eAAe,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,YAAY,CAAC,KAAkB;IACtC,OAAO,eAAe,CAAC,KAAK,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC;AACxD,CAAC;AAED,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoB,EACpB,KAAiB;IAEjB,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,aAAa,GAAG,WAAW,CAAC,eAAe,CAAC;IAElD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QAE5C,eAAe;QACf,KAAK,CAAC,kBAAkB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAE3C,MAAM,SAAS,GAAG,YAAY,CAAC,aAAa,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QAEhE,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,KAAK,CACT,0BAA0B,EAC1B,qBAAqB,aAAa,MAAM,UAAU,CAAC,KAAK,YAAY,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;gBACnG,YAAY,UAAU,CAAC,cAAc,CAAC,MAAM,GAAG,CAChD,CAAC;QACJ,CAAC;QAED,OAAO;YACL,UAAU;YACV,SAAS;YACT,aAAa;YACb,MAAM,EAAE,MAAM,EAAE,wBAAwB;SACzC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0DAA0D;QAC1D,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,KAAK,CAAC,KAAK,CAAC,qBAAqB,EAAE,wBAAwB,GAAG,CAAC,OAAO,UAAU,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACjG,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,KAAK,CAAC,qBAAqB,EAAE,wBAAwB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAoB,EACpB,KAAiB,EACjB,UAA4B,EAC5B,gBAAyB,EACzB,SAAiB;IAEjB,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,WAAW,EAAE,WAAW;QAAE,OAAO,IAAI,CAAC;IAE3C,oBAAoB;IACpB,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;QAC7C,KAAK,CAAC,KAAK,CACT,oBAAoB,EACpB,gBAAgB,UAAU,CAAC,KAAK,wBAAwB,WAAW,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,GAAG,CACpG,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+DAA+D;IAC/D,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,KAAK,CAAC,KAAK,CAAC,mBAAmB,EAAE,+CAA+C,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC;YACrC,SAAS,EAAE,WAAW,CAAC,WAAW;YAClC,aAAa,EAAE,YAAY;YAC3B,QAAQ,EAAE,UAAU,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,EAAE,iBAAiB;YACzB,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QAEH,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrC,KAAK,CAAC,KAAK,CACT,mBAAmB,EACnB,kCAAkC,KAAK,CAAC,QAAQ,MAAM,YAAY,GAAG;YACrE,YAAY,UAAU,CAAC,KAAK,YAAY,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACvE,CAAC;QAEF,OAAO,KAAK,CAAC,QAAQ,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,KAAK,CACT,oBAAoB,EACpB,2CAA2C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,MAAoB,EACpB,KAAiB,EACjB,UAA4B;IAE5B,IAAI,UAAU,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,WAAW,EAAE,WAAW;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;YACrC,YAAY,EAAE,WAAW,CAAC,WAAW;YACrC,aAAa,EAAE,YAAY;YAC3B,aAAa,EAAE,IAAI,EAAE,0CAA0C;SAChE,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,CACT,yBAAyB,EACzB,sCAAsC,SAAS,CAAC,YAAY,MAAM,YAAY,EAAE,CACjF,CAAC;QAEF,OAAO,SAAS,CAAC,YAAY,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,KAAK,CACT,sBAAsB,EACtB,uCAAuC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,UAA4B;IACvD,MAAM,KAAK,GAAG;QACZ,iBAAiB,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,YAAY,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QACzF,mBAAmB,UAAU,CAAC,cAAc,EAAE;KAC/C,CAAC;IAEF,IAAI,UAAU,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,iBAAiB,CAAC,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,WAAW,GAAmB,CAAC,UAAU,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAE3E;;;GAGG;AACH,SAAS,eAAe,CAAC,WAAwB;IAC/C,8EAA8E;IAC9E,4DAA4D;IAC5D,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAC/D,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
|