openclaw-aegis 1.5.1 → 1.5.3

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/dist/cli/index.js CHANGED
@@ -116,6 +116,7 @@ var aegisConfigSchema = import_zod.z.object({
116
116
  l1BackoffMultiplier: import_zod.z.number().min(1).default(3),
117
117
  l2MaxAttempts: import_zod.z.number().int().min(1).default(2),
118
118
  l2CooldownMs: import_zod.z.number().int().min(1e3).default(6e4),
119
+ l3Enabled: import_zod.z.boolean().default(false),
119
120
  l3MaxAttempts: import_zod.z.number().int().min(1).default(2),
120
121
  l3CooldownMs: import_zod.z.number().int().min(1e3).default(3e4),
121
122
  l3SafeModeArgs: import_zod.z.array(import_zod.z.string()).default(["--no-plugins", "--default-routes"]),
@@ -3912,6 +3913,13 @@ var RecoveryOrchestrator = class extends import_node_events3.EventEmitter {
3912
3913
  getCircuitBreaker() {
3913
3914
  return this.circuitBreaker;
3914
3915
  }
3916
+ /**
3917
+ * Public entry point for on-demand L3 deep repair (e.g. from /repair bot command).
3918
+ * Runs L3 patterns regardless of l3Enabled config — caller is responsible for confirmation.
3919
+ */
3920
+ async triggerL3() {
3921
+ return this.attemptL3();
3922
+ }
3915
3923
  async recover(_healthScore) {
3916
3924
  if (this.recovering) return [];
3917
3925
  this.recovering = true;
@@ -3931,24 +3939,36 @@ var RecoveryOrchestrator = class extends import_node_events3.EventEmitter {
3931
3939
  actions.push(...retryL1.actions);
3932
3940
  if (retryL1.success) return actions;
3933
3941
  }
3934
- const l3Result = await this.attemptL3();
3935
- actions.push(...l3Result.actions);
3936
- if (l3Result.success) {
3937
- const retryL1 = await this.attemptL1();
3938
- actions.push(...retryL1.actions);
3939
- if (retryL1.success) return actions;
3942
+ let l3Success = false;
3943
+ if (this.config.recovery.l3Enabled) {
3944
+ const l3Result = await this.attemptL3();
3945
+ actions.push(...l3Result.actions);
3946
+ l3Success = l3Result.success;
3947
+ if (l3Success) {
3948
+ const retryL1 = await this.attemptL1();
3949
+ actions.push(...retryL1.actions);
3950
+ if (retryL1.success) return actions;
3951
+ }
3952
+ } else {
3953
+ this.emitEvent({ type: "L3_DISABLED" });
3940
3954
  }
3941
- if (!l2Result.success && l2Result.noMatch && !l3Result.success) {
3955
+ if (!l2Result.success && l2Result.noMatch && !l3Success) {
3956
+ const l3Note = this.config.recovery.l3Enabled ? "" : " L3 deep repair is DISABLED \u2014 enable it with l3Enabled = true in [recovery] config if you want Aegis to attempt network repair, dependency rebuild, safe mode, and disk cleanup.";
3942
3957
  this.emitEvent({
3943
3958
  type: "FAST_PATH_L4",
3944
- reason: "L1+L2+L3 exhausted \u2014 no auto-repair available"
3959
+ reason: `L1+L2 exhausted \u2014 no auto-repair available.${l3Note}`
3945
3960
  });
3946
3961
  }
3947
3962
  this.circuitBreaker.recordFailedCycle();
3948
3963
  if (this.circuitBreaker.isTripped()) {
3949
3964
  this.emitEvent({ type: "CIRCUIT_BREAKER_TRIPPED" });
3950
3965
  }
3951
- const reason = !l2Result.success && l2Result.noMatch && !l3Result.success ? "No matching failure pattern \u2014 cannot auto-repair" : "Recovery exhausted \u2014 L1+L2+L3 failed";
3966
+ let reason;
3967
+ if (!this.config.recovery.l3Enabled) {
3968
+ reason = !l2Result.success && l2Result.noMatch ? "No matching failure pattern \u2014 cannot auto-repair. L3 deep repair is DISABLED (enable with l3Enabled = true to allow network repair, dependency rebuild, safe mode boot, and disk cleanup)" : "Recovery exhausted \u2014 L1+L2 failed. L3 deep repair is DISABLED (enable with l3Enabled = true)";
3969
+ } else {
3970
+ reason = !l2Result.success && l2Result.noMatch && !l3Success ? "No matching failure pattern \u2014 cannot auto-repair" : "Recovery exhausted \u2014 L1+L2+L3 failed";
3971
+ }
3952
3972
  this.emitEvent({ type: "L4_ALERT", reason, actions });
3953
3973
  return actions;
3954
3974
  } finally {
@@ -4298,16 +4318,19 @@ var BotCommandHandler = class {
4298
4318
  this.register("alerts", "Alert channel status", this.cmdAlerts.bind(this));
4299
4319
  this.register("version", "Version, uptime, platform", this.cmdVersion.bind(this));
4300
4320
  this.register("help", "List available commands", this.cmdHelp.bind(this));
4321
+ this.register("repair", "Attempt L3 deep repair (requires confirmation)", this.cmdRepair.bind(this));
4301
4322
  }
4302
4323
  commands = /* @__PURE__ */ new Map();
4303
4324
  register(name, description, handler) {
4304
4325
  this.commands.set(name, { description, handler });
4305
4326
  }
4306
4327
  async handle(input) {
4307
- const cmd = input.trim().toLowerCase().replace(/^\//, "");
4328
+ const parts = input.trim().replace(/^\//, "").split(/\s+/);
4329
+ const cmd = (parts[0] ?? "").toLowerCase();
4330
+ const args = parts.slice(1).join(" ");
4308
4331
  const entry = this.commands.get(cmd);
4309
4332
  if (!entry) return null;
4310
- return entry.handler();
4333
+ return entry.handler(args);
4311
4334
  }
4312
4335
  getCommandList() {
4313
4336
  return Array.from(this.commands.entries()).map(([name, { description }]) => ({
@@ -4316,7 +4339,7 @@ var BotCommandHandler = class {
4316
4339
  }));
4317
4340
  }
4318
4341
  // --- Commands ---
4319
- async cmdHealth() {
4342
+ async cmdHealth(_args) {
4320
4343
  const score = this.deps.monitor.getLastScore() ?? await this.deps.monitor.runAllProbes();
4321
4344
  const passed = score.probeResults.filter((p) => p.healthy).length;
4322
4345
  const failed = score.probeResults.filter((p) => !p.healthy);
@@ -4332,7 +4355,7 @@ Probes: ${passed}/${score.probeResults.length} passed`;
4332
4355
  }
4333
4356
  return { text };
4334
4357
  }
4335
- async cmdStatus() {
4358
+ async cmdStatus(_args) {
4336
4359
  const score = this.deps.monitor.getLastScore() ?? await this.deps.monitor.runAllProbes();
4337
4360
  const icon = score.band === "healthy" ? "\u2705" : score.band === "degraded" ? "\u26A0\uFE0F" : "\u{1F6A8}";
4338
4361
  let text = `${icon} Health: ${score.band.toUpperCase()} (score: ${score.total})
@@ -4345,7 +4368,7 @@ Probes: ${passed}/${score.probeResults.length} passed`;
4345
4368
  }
4346
4369
  return { text };
4347
4370
  }
4348
- cmdIncidents() {
4371
+ cmdIncidents(_args) {
4349
4372
  if (!this.deps.incidents) {
4350
4373
  return { text: "No incident logger available." };
4351
4374
  }
@@ -4375,7 +4398,7 @@ Avg MTTR: ${(stats.averageMttrMs / 1e3).toFixed(1)}s`;
4375
4398
  }
4376
4399
  return { text };
4377
4400
  }
4378
- cmdRecovery() {
4401
+ cmdRecovery(_args) {
4379
4402
  if (!this.deps.recovery) {
4380
4403
  return { text: "Recovery: idle (orchestrator not active)" };
4381
4404
  }
@@ -4398,7 +4421,7 @@ Dead man's switch: ${state}`;
4398
4421
  }
4399
4422
  return { text };
4400
4423
  }
4401
- cmdBackups() {
4424
+ cmdBackups(_args) {
4402
4425
  if (!this.deps.backup) {
4403
4426
  return { text: "Backup manager not available." };
4404
4427
  }
@@ -4417,7 +4440,7 @@ Checksum: ${latest.checksum.slice(0, 12)}...`;
4417
4440
  }
4418
4441
  return { text };
4419
4442
  }
4420
- cmdAlerts() {
4443
+ cmdAlerts(_args) {
4421
4444
  const channels = this.deps.config.alerts.channels;
4422
4445
  if (channels.length === 0) {
4423
4446
  return { text: "No alert channels configured. Run 'aegis init' to add one." };
@@ -4430,7 +4453,7 @@ Checksum: ${latest.checksum.slice(0, 12)}...`;
4430
4453
  }
4431
4454
  return { text };
4432
4455
  }
4433
- cmdVersion() {
4456
+ cmdVersion(_args) {
4434
4457
  const uptimeMs = Date.now() - this.deps.startedAt;
4435
4458
  const hours = Math.floor(uptimeMs / 36e5);
4436
4459
  const minutes = Math.floor(uptimeMs % 36e5 / 6e4);
@@ -4445,7 +4468,7 @@ Uptime: ${hours}h ${minutes}m`;
4445
4468
  Host: ${os8.hostname()}`;
4446
4469
  return { text };
4447
4470
  }
4448
- cmdHelp() {
4471
+ cmdHelp(_args) {
4449
4472
  let text = "Aegis Bot Commands:\n";
4450
4473
  for (const [name, { description }] of this.commands) {
4451
4474
  text += `
@@ -4453,6 +4476,41 @@ Host: ${os8.hostname()}`;
4453
4476
  }
4454
4477
  return { text };
4455
4478
  }
4479
+ async cmdRepair(args) {
4480
+ if (!this.deps.recovery) {
4481
+ return { text: "Recovery orchestrator not available." };
4482
+ }
4483
+ if (args.trim().toLowerCase() !== "confirm") {
4484
+ let text2 = "\u26A0\uFE0F L3 DEEP REPAIR \u2014 DESTRUCTIVE OPERATION\n";
4485
+ text2 += "\nThis will attempt the following repairs on your server:\n";
4486
+ text2 += "\n 1. Network repair \u2014 flush DNS cache, reset TUN interface, check default routes";
4487
+ text2 += "\n 2. Process resurrection \u2014 reinstall gateway binary via npm install -g openclaw";
4488
+ text2 += "\n 3. Dependency rebuild \u2014 delete and rebuild node_modules with npm install --production";
4489
+ text2 += "\n 4. Safe mode boot \u2014 start gateway with minimal config (no plugins, default routes)";
4490
+ text2 += "\n 5. Disk cleanup \u2014 truncate oversized logs, delete rotated logs, clear temp files";
4491
+ text2 += "\n\n\u{1F6A8} These actions CAN affect other services on this server.";
4492
+ text2 += "\nOnly proceed if normal recovery (L1 restart + L2 targeted repair) has failed.\n";
4493
+ text2 += "\nTo proceed, send: /repair confirm";
4494
+ return { text: text2 };
4495
+ }
4496
+ let text = "\u{1F527} Running L3 deep repair...\n";
4497
+ try {
4498
+ const result = await this.deps.recovery.triggerL3();
4499
+ if (result.actions.length === 0) {
4500
+ text += "\nNo matching failure patterns detected \u2014 nothing to repair.";
4501
+ return { text };
4502
+ }
4503
+ for (const action of result.actions) {
4504
+ const mark = action.result === "success" ? "\u2705" : "\u274C";
4505
+ text += `
4506
+ ${mark} ${action.action} (${action.level}, ${action.durationMs}ms)`;
4507
+ }
4508
+ text += result.success ? "\n\n\u2705 L3 repair succeeded. Gateway should recover shortly." : "\n\n\u274C L3 repair did not resolve the issue. Manual intervention required.";
4509
+ } catch {
4510
+ text += "\n\u274C L3 repair failed with an unexpected error. Check logs for details.";
4511
+ }
4512
+ return { text };
4513
+ }
4456
4514
  };
4457
4515
 
4458
4516
  // src/bot/telegram.ts