blitzstrike 1.0.53 → 1.0.55

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.js +114 -12
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,37 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [1.0.55] — 2026-09-13
9
+
10
+ ### Added — Autonomous Security Engine (phase 15: termination engine)
11
+
12
+ - **Termination Engine** (`src/mission/termination.ts`, §24):
13
+ - `evaluateTermination()` — ordered hard stops: user_interrupted →
14
+ budget_exhausted → safety_violation → scope_violation → max_iterations →
15
+ no_progress.
16
+ - `objectiveComplete()` — all tasks in a terminal non-failed state.
17
+ - `applyTermination()` — records the reason + detail into `decision_log` and
18
+ sets `completed`/`terminated`.
19
+ - `NO_PROGRESS_LIMIT` — iterations without progress before declaring stuck.
20
+ - Mission Control now terminates on ANY traceable reason (not just budget) and
21
+ logs the termination decision with detail.
22
+
23
+ ## [1.0.54] — 2026-09-13
24
+
25
+ ### Added — Autonomous Security Engine (phase 14: budget controller)
26
+
27
+ - **Budget Controller** (`src/mission/budget.ts`, §22):
28
+ - `budgetStatus()` — tracks time / tasks / requests / tokens / cost with
29
+ per-dimension overrun flags.
30
+ - `budgetOverrun()` — detects overruns and returns a specific reason
31
+ (e.g. `budget overrun: tasks, requests`).
32
+ - `consumeTasks()` / `consumeTokens()` — increment consumption counters.
33
+ - `perTaskOverrun()` — enforces the per-task request budget.
34
+ - Extended the `Budget` type with `max_tokens`, `max_cost_usd`,
35
+ `per_task_budget`, `started_at`, and `used_tokens`/`used_cost_usd`.
36
+ - Mission Control now terminates gracefully on ANY budget dimension overrun
37
+ (not just tasks) and records a final budget snapshot in the decision log.
38
+
8
39
  ## [1.0.53] — 2026-09-13
9
40
 
10
41
  ### Added — Autonomous Security Engine (phase 13: model router)
package/dist/index.js CHANGED
@@ -18097,10 +18097,16 @@ function newMission(id, target, scope, mode, budget) {
18097
18097
  max_time_seconds: 3600,
18098
18098
  max_tasks: 200,
18099
18099
  max_requests: 1000,
18100
+ max_tokens: 2000000,
18101
+ max_cost_usd: 5,
18100
18102
  max_parallel_workers: 4,
18101
18103
  risk: "medium",
18104
+ per_task_budget: { max_time_seconds: 60, max_requests: 50 },
18105
+ started_at: Date.now(),
18102
18106
  used_tasks: 0,
18103
18107
  used_requests: 0,
18108
+ used_tokens: 0,
18109
+ used_cost_usd: 0,
18104
18110
  ...budget ?? {}
18105
18111
  },
18106
18112
  discovered_assets: [],
@@ -19134,6 +19140,94 @@ var init_router = __esm(() => {
19134
19140
  };
19135
19141
  });
19136
19142
 
19143
+ // src/mission/budget.ts
19144
+ function clampRatio(used, max) {
19145
+ if (max <= 0)
19146
+ return 0;
19147
+ return Math.max(0, Math.min(1, used / max));
19148
+ }
19149
+ function budgetStatus(budget, now = Date.now()) {
19150
+ const elapsed = Math.max(0, (now - budget.started_at) / 1000);
19151
+ const dims = [
19152
+ ["time", "time (s)", elapsed, budget.max_time_seconds],
19153
+ ["tasks", "tasks", budget.used_tasks, budget.max_tasks],
19154
+ ["requests", "requests", budget.used_requests, budget.max_requests],
19155
+ ["tokens", "tokens", budget.used_tokens, budget.max_tokens],
19156
+ ["cost", "cost (USD)", budget.used_cost_usd, budget.max_cost_usd]
19157
+ ];
19158
+ return dims.map(([key, label, used, max]) => ({
19159
+ key,
19160
+ label,
19161
+ used: Number(used.toFixed(2)),
19162
+ max,
19163
+ overrun: used >= max,
19164
+ ratio: clampRatio(used, max)
19165
+ }));
19166
+ }
19167
+ function budgetOverrun(budget, now = Date.now()) {
19168
+ const dims = budgetStatus(budget, now).filter((d) => d.overrun);
19169
+ if (dims.length === 0)
19170
+ return { overrun: false, reason: null, dimensions: [] };
19171
+ return {
19172
+ overrun: true,
19173
+ reason: `budget overrun: ${dims.map((d) => d.key).join(", ")}`,
19174
+ dimensions: dims.map((d) => d.key)
19175
+ };
19176
+ }
19177
+ function consumeTasks(budget, tasks, requests) {
19178
+ return {
19179
+ ...budget,
19180
+ used_tasks: budget.used_tasks + tasks,
19181
+ used_requests: budget.used_requests + requests
19182
+ };
19183
+ }
19184
+
19185
+ // src/mission/termination.ts
19186
+ function evaluateTermination(mission, state) {
19187
+ if (state.user_interrupted) {
19188
+ return { terminate: true, reason: "user_interrupted", detail: "user requested stop" };
19189
+ }
19190
+ const overrun = budgetOverrun(mission.budget);
19191
+ if (overrun.overrun) {
19192
+ return { terminate: true, reason: "budget_exhausted", detail: overrun.reason ?? "budget limit" };
19193
+ }
19194
+ if (state.safety_violated) {
19195
+ return { terminate: true, reason: "safety_violation", detail: "a safety invariant was violated" };
19196
+ }
19197
+ if (state.scope_violated) {
19198
+ return { terminate: true, reason: "scope_violation", detail: "an agent attempted to leave scope" };
19199
+ }
19200
+ if (state.iterations >= state.max_iterations) {
19201
+ return { terminate: true, reason: "max_iterations", detail: `reached ${state.max_iterations} iterations` };
19202
+ }
19203
+ if (state.iterations - state.last_progress_iteration >= NO_PROGRESS_LIMIT) {
19204
+ return { terminate: true, reason: "no_progress", detail: `no progress for ${NO_PROGRESS_LIMIT} iterations` };
19205
+ }
19206
+ return { terminate: false, reason: null, detail: "" };
19207
+ }
19208
+ function objectiveComplete(mission) {
19209
+ const done = mission.tasks.every((t) => t.status === "success" || t.status === "skipped" || t.status === "cancelled");
19210
+ if (done && mission.tasks.length > 0) {
19211
+ return { terminate: true, reason: "objective_complete", detail: "all tasks resolved" };
19212
+ }
19213
+ return { terminate: false, reason: null, detail: "" };
19214
+ }
19215
+ function applyTermination(mission, decision) {
19216
+ if (!decision.reason)
19217
+ return mission;
19218
+ return {
19219
+ ...mission,
19220
+ status: decision.reason === "objective_complete" ? "completed" : "terminated",
19221
+ termination_reason: decision.reason,
19222
+ decision_log: [
19223
+ ...mission.decision_log,
19224
+ { at: new Date().toISOString(), agent: "termination", decision: `${decision.reason}: ${decision.detail}` }
19225
+ ]
19226
+ };
19227
+ }
19228
+ var NO_PROGRESS_LIMIT = 3;
19229
+ var init_termination = () => {};
19230
+
19137
19231
  // src/mission/control.ts
19138
19232
  import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
19139
19233
  function scopeAllowed(mission) {
@@ -19203,9 +19297,6 @@ async function executeTask(task, mission, scope) {
19203
19297
  return { task, ok: false, output: {}, hypotheses: [], graph: { nodes: [], edges: [] }, error: String(e) };
19204
19298
  }
19205
19299
  }
19206
- function budgetExhausted(mission) {
19207
- return mission.budget.used_tasks >= mission.budget.max_tasks;
19208
- }
19209
19300
  async function runMission(mission) {
19210
19301
  if (/^https?:\/\//i.test(mission.target) && !scopeAllowed(mission)) {
19211
19302
  return saveMission({ ...mission, status: "terminated", termination_reason: "scope_required" });
@@ -19221,11 +19312,13 @@ async function runMission(mission) {
19221
19312
  m = saveMission(m);
19222
19313
  const scope = m.scope.join(",");
19223
19314
  let iterations = 0;
19315
+ let lastProgress = 0;
19224
19316
  const maxIterations = m.budget.max_tasks + 10;
19225
19317
  while (iterations < maxIterations) {
19226
19318
  iterations += 1;
19227
- if (budgetExhausted(m)) {
19228
- m = { ...m, termination_reason: "budget_exhausted" };
19319
+ const hard = evaluateTermination(m, { iterations, max_iterations: maxIterations, last_progress_iteration: lastProgress });
19320
+ if (hard.terminate && hard.reason) {
19321
+ m = applyTermination(m, hard);
19229
19322
  break;
19230
19323
  }
19231
19324
  const { ready, blocked } = schedule(m.tasks);
@@ -19236,8 +19329,13 @@ async function runMission(mission) {
19236
19329
  m = saveMission(m);
19237
19330
  continue;
19238
19331
  }
19239
- if (blocked.length > 0) {
19240
- m = { ...m, termination_reason: `blocked: ${blocked.length} task(s) with failed dependencies` };
19332
+ const done = objectiveComplete(m);
19333
+ if (done.terminate) {
19334
+ m = applyTermination(m, done);
19335
+ } else if (blocked.length > 0) {
19336
+ m = applyTermination(m, { terminate: true, reason: "no_progress", detail: `blocked: ${blocked.length} task(s) with failed dependencies` });
19337
+ } else {
19338
+ m = applyTermination(m, { terminate: true, reason: "objective_complete", detail: "no ready tasks remain" });
19241
19339
  }
19242
19340
  break;
19243
19341
  }
@@ -19286,17 +19384,18 @@ async function runMission(mission) {
19286
19384
  }
19287
19385
  }
19288
19386
  }
19289
- m.budget.used_tasks += batch.length;
19387
+ m.budget = consumeTasks(m.budget, batch.length, 0);
19388
+ if (results.some((r) => r.ok))
19389
+ lastProgress = iterations;
19290
19390
  m = saveMission(m);
19291
19391
  }
19292
19392
  return finalize2(m);
19293
19393
  }
19294
19394
  function finalize2(mission) {
19295
19395
  let m = mission;
19296
- if (budgetExhausted(m)) {
19297
- m = { ...m, status: "terminated", termination_reason: "budget_exhausted" };
19298
- } else {
19299
- m = { ...m, status: "completed", termination_reason: "all tasks resolved" };
19396
+ if (!m.termination_reason) {
19397
+ const done = objectiveComplete(m);
19398
+ m = done.terminate ? applyTermination(m, done) : applyTermination(m, { terminate: true, reason: "max_iterations", detail: "iteration cap reached" });
19300
19399
  }
19301
19400
  m.coverage.dimensions = {
19302
19401
  ...m.coverage.dimensions,
@@ -19307,6 +19406,8 @@ function finalize2(mission) {
19307
19406
  if (mem.saved > 0) {
19308
19407
  m = { ...m, memory_refs: [...m.memory_refs, `persisted:${mem.saved} (${mem.deduped} deduped)`] };
19309
19408
  }
19409
+ const budgetSummary = budgetStatus(m.budget).map((d) => `${d.key}=${d.used}/${d.max}`).join(", ");
19410
+ m = { ...m, decision_log: [...m.decision_log, { at: new Date().toISOString(), agent: "budget", decision: `final budget: ${budgetSummary}` }] };
19310
19411
  return saveMission(m);
19311
19412
  }
19312
19413
  async function startMission(target, mode, opts = {}) {
@@ -19331,6 +19432,7 @@ var init_control = __esm(() => {
19331
19432
  init_recovery();
19332
19433
  init_memory2();
19333
19434
  init_router();
19435
+ init_termination();
19334
19436
  MODE_PROFILES = {
19335
19437
  web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
19336
19438
  api: { agents: ["recon", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.53",
3
+ "version": "1.0.55",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {