pentesting 0.73.2 → 0.73.4

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.
@@ -46,12 +46,16 @@ import {
46
46
  debugLog,
47
47
  ensureDirExists,
48
48
  generateId,
49
+ generatePrefixedId,
49
50
  getActiveSessionRuntime,
50
51
  getErrorMessage,
51
52
  getProcessOutput,
52
53
  getTorBrowserArgs,
53
54
  getUsedPorts,
54
55
  listBackgroundProcesses,
56
+ llmNodeCooldownPolicy,
57
+ llmNodeOutputParsing,
58
+ llmNodeSystemPrompt,
55
59
  promoteToShell,
56
60
  readFileContent,
57
61
  runCommand,
@@ -60,11 +64,12 @@ import {
60
64
  startBackgroundProcess,
61
65
  stopBackgroundProcess,
62
66
  writeFileContent
63
- } from "./chunk-KBJPZDIL.js";
67
+ } from "./chunk-7E2VUIFU.js";
64
68
  import {
65
69
  DETECTION_PATTERNS,
66
70
  HEALTH_CONFIG,
67
71
  PROCESS_ACTIONS,
72
+ PROCESS_EVENTS,
68
73
  PROCESS_ICONS,
69
74
  PROCESS_LIMITS,
70
75
  PROCESS_ROLES,
@@ -72,10 +77,8 @@ import {
72
77
  SYSTEM_LIMITS,
73
78
  __require,
74
79
  getProcessEventLog,
75
- llmNodeCooldownPolicy,
76
- llmNodeOutputParsing,
77
- llmNodeSystemPrompt
78
- } from "./chunk-YFDJI3GO.js";
80
+ logEvent
81
+ } from "./chunk-I52SWXYV.js";
79
82
 
80
83
  // src/shared/utils/config/env.ts
81
84
  var ENV_KEYS = {
@@ -363,88 +366,99 @@ async function makeRequest(baseUrl, apiKey, body, signal) {
363
366
 
364
367
  // src/engine/llm-client/stream-processor.ts
365
368
  function processStreamEvent(event, requestId, context) {
366
- const { toolCallsMap, callbacks, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
367
369
  switch (event.type) {
368
370
  case LLM_SSE_EVENT.CONTENT_BLOCK_START:
369
- if (event.content_block) {
370
- const blockType = event.content_block.type;
371
- if (blockType === LLM_BLOCK_TYPE.TEXT) {
372
- currentBlockRef.value = LLM_BLOCK_TYPE.TEXT;
373
- context.onTextStart?.();
374
- callbacks?.onOutputStart?.();
375
- } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
376
- currentBlockRef.value = LLM_BLOCK_TYPE.REASONING;
377
- context.onReasoningStart?.();
378
- callbacks?.onReasoningStart?.();
379
- } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
380
- currentBlockRef.value = LLM_BLOCK_TYPE.TOOL_USE;
381
- toolCallsMap.set(event.content_block.id || "", {
382
- id: event.content_block.id || "",
383
- name: event.content_block.name || "",
384
- input: {},
385
- _pendingJson: "",
386
- _index: event.index
387
- });
388
- }
389
- }
371
+ handleContentBlockStart(event, context);
390
372
  break;
391
373
  case LLM_SSE_EVENT.CONTENT_BLOCK_DELTA:
392
- if (event.delta) {
393
- if (event.delta.type === LLM_DELTA_TYPE.TEXT_DELTA && event.delta.text) {
394
- onContent(event.delta.text);
395
- callbacks?.onOutputDelta?.(event.delta.text);
396
- } else if (
397
- // Anthropic: thinking_delta / GLM-DeepSeek: reasoning_delta
398
- event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking || event.delta.type === LLM_DELTA_TYPE.REASONING_DELTA && event.delta.reasoning
399
- ) {
400
- const chunk = event.delta.thinking || event.delta.reasoning || "";
401
- onReasoning(chunk);
402
- callbacks?.onReasoningDelta?.(chunk);
403
- } else if (event.delta.type === LLM_DELTA_TYPE.INPUT_JSON_DELTA && event.delta.partial_json) {
404
- const index = event.index;
405
- if (index !== void 0) {
406
- const toolCall = Array.from(toolCallsMap.values()).find((t) => t._index === index);
407
- if (toolCall) {
408
- toolCall._pendingJson = (toolCall._pendingJson || "") + event.delta.partial_json;
409
- debugLog("llm", `[${requestId}] JSON DELTA applied`, { index, toolId: toolCall.id, json: event.delta.partial_json });
410
- } else {
411
- debugLog("llm", `[${requestId}] JSON DELTA no tool found for index`, { index });
412
- }
413
- }
414
- }
415
- const estimatedOutput = Math.ceil(getTotalChars() / LLM_LIMITS.charsPerTokenEstimate);
416
- callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: estimatedOutput });
417
- }
374
+ handleContentBlockDelta(event, requestId, context);
418
375
  break;
419
- case LLM_SSE_EVENT.CONTENT_BLOCK_STOP: {
420
- const stoppedType = currentBlockRef.value;
421
- currentBlockRef.value = null;
422
- if (stoppedType === LLM_BLOCK_TYPE.TEXT) {
423
- context.onTextEnd?.();
424
- callbacks?.onOutputEnd?.();
425
- } else if (stoppedType === LLM_BLOCK_TYPE.REASONING) {
426
- context.onReasoningEnd?.();
427
- callbacks?.onReasoningEnd?.();
428
- }
376
+ case LLM_SSE_EVENT.CONTENT_BLOCK_STOP:
377
+ handleContentBlockStop(context);
429
378
  break;
430
- }
431
379
  case LLM_SSE_EVENT.MESSAGE_START:
432
- if (event.message?.usage) {
433
- onUsage({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
434
- callbacks?.onUsageUpdate?.({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
435
- }
380
+ handleMessageStart(event, context);
436
381
  break;
437
382
  case LLM_SSE_EVENT.MESSAGE_DELTA:
438
- if (event.usage) {
439
- onUsage({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
440
- callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
441
- }
383
+ handleMessageDelta(event, context);
442
384
  break;
443
385
  case LLM_SSE_EVENT.ERROR:
444
386
  if (event.error) throw new Error(`${event.error.type}: ${event.error.message}`);
445
387
  break;
446
388
  }
447
389
  }
390
+ function handleContentBlockStart(event, context) {
391
+ if (!event.content_block) return;
392
+ const { toolCallsMap, callbacks, currentBlockRef } = context;
393
+ const blockType = event.content_block.type;
394
+ if (blockType === LLM_BLOCK_TYPE.TEXT) {
395
+ currentBlockRef.value = LLM_BLOCK_TYPE.TEXT;
396
+ context.onTextStart?.();
397
+ callbacks?.onOutputStart?.();
398
+ } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
399
+ currentBlockRef.value = LLM_BLOCK_TYPE.REASONING;
400
+ context.onReasoningStart?.();
401
+ callbacks?.onReasoningStart?.();
402
+ } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
403
+ currentBlockRef.value = LLM_BLOCK_TYPE.TOOL_USE;
404
+ toolCallsMap.set(event.content_block.id || "", {
405
+ id: event.content_block.id || "",
406
+ name: event.content_block.name || "",
407
+ input: {},
408
+ _pendingJson: "",
409
+ _index: event.index
410
+ });
411
+ }
412
+ }
413
+ function handleContentBlockDelta(event, requestId, context) {
414
+ if (!event.delta) return;
415
+ const { toolCallsMap, callbacks, onContent, onReasoning, getTotalChars } = context;
416
+ if (event.delta.type === LLM_DELTA_TYPE.TEXT_DELTA && event.delta.text) {
417
+ onContent(event.delta.text);
418
+ callbacks?.onOutputDelta?.(event.delta.text);
419
+ } else if (event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking || event.delta.type === LLM_DELTA_TYPE.REASONING_DELTA && event.delta.reasoning) {
420
+ const chunk = event.delta.thinking || event.delta.reasoning || "";
421
+ onReasoning(chunk);
422
+ callbacks?.onReasoningDelta?.(chunk);
423
+ } else if (event.delta.type === LLM_DELTA_TYPE.INPUT_JSON_DELTA && event.delta.partial_json) {
424
+ const index = event.index;
425
+ if (index !== void 0) {
426
+ const toolCall = Array.from(toolCallsMap.values()).find((t) => t._index === index);
427
+ if (toolCall) {
428
+ toolCall._pendingJson = (toolCall._pendingJson || "") + event.delta.partial_json;
429
+ debugLog("llm", `[${requestId}] JSON DELTA applied`, { index, toolId: toolCall.id, json: event.delta.partial_json });
430
+ } else {
431
+ debugLog("llm", `[${requestId}] JSON DELTA no tool found for index`, { index });
432
+ }
433
+ }
434
+ }
435
+ const estimatedOutput = Math.ceil(getTotalChars() / LLM_LIMITS.charsPerTokenEstimate);
436
+ callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: estimatedOutput });
437
+ }
438
+ function handleContentBlockStop(context) {
439
+ const { callbacks, currentBlockRef } = context;
440
+ const stoppedType = currentBlockRef.value;
441
+ currentBlockRef.value = null;
442
+ if (stoppedType === LLM_BLOCK_TYPE.TEXT) {
443
+ context.onTextEnd?.();
444
+ callbacks?.onOutputEnd?.();
445
+ } else if (stoppedType === LLM_BLOCK_TYPE.REASONING) {
446
+ context.onReasoningEnd?.();
447
+ callbacks?.onReasoningEnd?.();
448
+ }
449
+ }
450
+ function handleMessageStart(event, context) {
451
+ if (!event.message?.usage) return;
452
+ const { onUsage, callbacks } = context;
453
+ onUsage({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
454
+ callbacks?.onUsageUpdate?.({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
455
+ }
456
+ function handleMessageDelta(event, context) {
457
+ if (!event.usage) return;
458
+ const { onUsage, callbacks } = context;
459
+ onUsage({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
460
+ callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
461
+ }
448
462
  function createStreamContext(callbacks) {
449
463
  let fullContent = "";
450
464
  let fullReasoning = "";
@@ -476,7 +490,6 @@ var LLMClient = class {
476
490
  apiKey;
477
491
  baseUrl;
478
492
  model;
479
- requestCount = 0;
480
493
  constructor() {
481
494
  this.apiKey = getApiKey();
482
495
  this.baseUrl = getBaseUrl() || LLM_API.DEFAULT_BASE_URL;
@@ -493,6 +506,7 @@ var LLMClient = class {
493
506
  return this.model;
494
507
  }
495
508
  async executeNonStream(messages, tools, systemPrompt, callbacks) {
509
+ const requestId = incrementGlobalRequestCount();
496
510
  const thinking = isThinkingEnabled() ? { type: "enabled", budget_tokens: getThinkingBudget() } : void 0;
497
511
  const requestBody = {
498
512
  model: this.model,
@@ -502,13 +516,14 @@ var LLMClient = class {
502
516
  tools,
503
517
  ...thinking && { thinking }
504
518
  };
505
- debugLog("llm", "Non-stream request", { model: this.model, toolCount: tools?.length, thinking: !!thinking });
519
+ debugLog("llm", `[${requestId}] Non-stream request START`, { model: this.model, toolCount: tools?.length, thinking: !!thinking });
506
520
  const response = await makeRequest(this.baseUrl, this.apiKey, requestBody, callbacks?.abortSignal);
507
521
  const data = await response.json();
508
522
  const textBlock = data.content.find((b) => b.type === LLM_BLOCK_TYPE.TEXT);
509
523
  const toolBlocks = data.content.filter((b) => b.type === LLM_BLOCK_TYPE.TOOL_USE);
510
- debugLog("llm", "Non-stream response", { toolCount: toolBlocks.length, tools: toolBlocks.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
524
+ debugLog("llm", `[${requestId}] Non-stream response`, { toolCount: toolBlocks.length, tools: toolBlocks.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
511
525
  const usage = { input_tokens: data.usage?.input_tokens || 0, output_tokens: data.usage?.output_tokens || 0 };
526
+ addGlobalTokenUsage(usage);
512
527
  callbacks?.onUsageUpdate?.(usage);
513
528
  return {
514
529
  content: textBlock?.text || "",
@@ -518,8 +533,7 @@ var LLMClient = class {
518
533
  };
519
534
  }
520
535
  async executeStream(messages, tools, systemPrompt, callbacks) {
521
- this.requestCount++;
522
- const requestId = this.requestCount;
536
+ const requestId = incrementGlobalRequestCount();
523
537
  const thinking = isThinkingEnabled() ? { type: LLM_THINKING_MODE.ENABLED, budget_tokens: getThinkingBudget() } : void 0;
524
538
  const requestBody = {
525
539
  model: this.model,
@@ -560,6 +574,9 @@ var LLMClient = class {
560
574
  }
561
575
  const toolCalls = resolveToolCalls(toolCallsMap);
562
576
  const stripped = stripThinkTags(contentChunks.join(""), reasoningChunks.join(""));
577
+ if (usage.input_tokens > 0 || usage.output_tokens > 0) {
578
+ addGlobalTokenUsage(usage);
579
+ }
563
580
  return {
564
581
  content: stripped.cleanText,
565
582
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
@@ -621,12 +638,27 @@ function resolveToolCalls(toolCallsMap) {
621
638
 
622
639
  // src/engine/llm-client/factory.ts
623
640
  var llmInstance = null;
641
+ var globalRequestCount = 0;
642
+ var globalTokenUsage = { input_tokens: 0, output_tokens: 0 };
624
643
  function getLLMClient() {
625
644
  if (!llmInstance) {
626
645
  llmInstance = new LLMClient();
627
646
  }
628
647
  return llmInstance;
629
648
  }
649
+ function incrementGlobalRequestCount() {
650
+ return ++globalRequestCount;
651
+ }
652
+ function getGlobalRequestCount() {
653
+ return globalRequestCount;
654
+ }
655
+ function addGlobalTokenUsage(usage) {
656
+ globalTokenUsage.input_tokens += usage.input_tokens || 0;
657
+ globalTokenUsage.output_tokens += usage.output_tokens || 0;
658
+ }
659
+ function getGlobalTokenUsage() {
660
+ return globalTokenUsage;
661
+ }
630
662
 
631
663
  // src/engine/auxiliary-llm/auxiliary-llm-base.ts
632
664
  var AuxiliaryLLMBase = class {
@@ -1706,19 +1738,8 @@ var CHALLENGE_TYPE_SIGNALS = {
1706
1738
  "escape"
1707
1739
  ]
1708
1740
  };
1709
- function analyzeChallenge(reconData) {
1710
- if (!reconData || reconData.trim().length === 0) {
1711
- return {
1712
- primaryType: "unknown",
1713
- secondaryTypes: [],
1714
- confidence: 0,
1715
- matchedSignals: [],
1716
- recommendedTechniques: TYPE_TECHNIQUE_MAP.unknown,
1717
- recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP.unknown
1718
- };
1719
- }
1720
- const lowerData = reconData.toLowerCase();
1721
- const scores = {
1741
+ function initializeScores() {
1742
+ return {
1722
1743
  web: { score: 0, signals: [] },
1723
1744
  pwn: { score: 0, signals: [] },
1724
1745
  crypto: { score: 0, signals: [] },
@@ -1728,6 +1749,8 @@ function analyzeChallenge(reconData) {
1728
1749
  network: { score: 0, signals: [] },
1729
1750
  unknown: { score: 0, signals: [] }
1730
1751
  };
1752
+ }
1753
+ function matchKeywordSignals(lowerData, scores) {
1731
1754
  for (const [type, signals] of Object.entries(CHALLENGE_TYPE_SIGNALS)) {
1732
1755
  const challengeType = type;
1733
1756
  if (!(challengeType in scores)) continue;
@@ -1738,6 +1761,8 @@ function analyzeChallenge(reconData) {
1738
1761
  }
1739
1762
  }
1740
1763
  }
1764
+ }
1765
+ function applyRegexBoosts(reconData, lowerData, scores) {
1741
1766
  if (WEB_PORT_PATTERN.test(reconData)) {
1742
1767
  scores.web.score += 2;
1743
1768
  scores.web.signals.push("web-port");
@@ -1758,6 +1783,8 @@ function analyzeChallenge(reconData) {
1758
1783
  scores.crypto.score += 3;
1759
1784
  scores.crypto.signals.push("crypto-file");
1760
1785
  }
1786
+ }
1787
+ function calculateAnalysisResult(scores) {
1761
1788
  const sorted = Object.entries(scores).filter(([type]) => type !== "unknown").sort(([, a], [, b]) => b.score - a.score);
1762
1789
  const [primaryType, primaryData] = sorted[0];
1763
1790
  const totalSignals = sorted.reduce((sum, [, data]) => sum + data.score, 0);
@@ -1773,6 +1800,16 @@ function analyzeChallenge(reconData) {
1773
1800
  recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP[primaryType] || TYPE_PHASE_PROMPT_MAP.unknown
1774
1801
  };
1775
1802
  }
1803
+ function analyzeChallenge(reconData) {
1804
+ if (!reconData || reconData.trim().length === 0) {
1805
+ return calculateAnalysisResult(initializeScores());
1806
+ }
1807
+ const lowerData = reconData.toLowerCase();
1808
+ const scores = initializeScores();
1809
+ matchKeywordSignals(lowerData, scores);
1810
+ applyRegexBoosts(reconData, lowerData, scores);
1811
+ return calculateAnalysisResult(scores);
1812
+ }
1776
1813
 
1777
1814
  // src/shared/utils/knowledge/challenge/formatter.ts
1778
1815
  function formatChallengeAnalysis(analysis) {
@@ -2746,13 +2783,13 @@ var CoreAgent = class _CoreAgent {
2746
2783
  maxIterations;
2747
2784
  abortController = null;
2748
2785
  toolExecutor = null;
2749
- constructor(agentType, state, events, toolRegistry, maxIterations) {
2750
- this.agentType = agentType;
2751
- this.state = state;
2752
- this.events = events;
2753
- this.toolRegistry = toolRegistry;
2786
+ constructor(options) {
2787
+ this.agentType = options.agentType;
2788
+ this.state = options.state;
2789
+ this.events = options.events;
2790
+ this.toolRegistry = options.toolRegistry;
2754
2791
  this.llm = getLLMClient();
2755
- this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
2792
+ this.maxIterations = options.maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
2756
2793
  }
2757
2794
  setToolRegistry(registry) {
2758
2795
  this.toolRegistry = registry;
@@ -2772,14 +2809,19 @@ var CoreAgent = class _CoreAgent {
2772
2809
  // Over 4+ hour sessions this can accumulate GB of tool results in memory.
2773
2810
  // This cap ensures messages never exceed a safe size regardless of extractor health.
2774
2811
  static MAX_MESSAGES_HARD_CAP = 50;
2812
+ static MAX_MESSAGES_CHAR_CAP = 1e6;
2775
2813
  trimMessagesIfNeeded(messages) {
2776
- if (messages.length <= _CoreAgent.MAX_MESSAGES_HARD_CAP) return;
2777
- const firstMsg = messages[0];
2778
- const keptCount = _CoreAgent.MAX_MESSAGES_HARD_CAP - 1;
2779
- const recentMessages = messages.slice(-keptCount);
2780
- messages.length = 0;
2781
- if (firstMsg) messages.push(firstMsg);
2782
- messages.push(...recentMessages);
2814
+ let totalChars = messages.reduce((sum, m) => {
2815
+ const len = typeof m.content === "string" ? m.content.length : JSON.stringify(m.content).length;
2816
+ return sum + len;
2817
+ }, 0);
2818
+ if (messages.length <= _CoreAgent.MAX_MESSAGES_HARD_CAP && totalChars <= _CoreAgent.MAX_MESSAGES_CHAR_CAP) return;
2819
+ while (messages.length > 2 && (messages.length > _CoreAgent.MAX_MESSAGES_HARD_CAP || totalChars > _CoreAgent.MAX_MESSAGES_CHAR_CAP)) {
2820
+ const removed = messages.splice(1, 1)[0];
2821
+ if (removed) {
2822
+ totalChars -= typeof removed.content === "string" ? removed.content.length : JSON.stringify(removed.content).length;
2823
+ }
2824
+ }
2783
2825
  }
2784
2826
  /** The core loop: Think → Act → Observe */
2785
2827
  async run(task, systemPrompt) {
@@ -2845,7 +2887,7 @@ var CoreAgent = class _CoreAgent {
2845
2887
  );
2846
2888
  return { output: "", toolsExecuted, isCompleted: false };
2847
2889
  }
2848
- // ─── AgentController Methods for Dynamic YAML Pipeline ────────────────────
2890
+ // ─── AgentController Methods for Dynamic Runtime Pipeline ─────────────────
2849
2891
  async runLLMInference(ctx, systemPrompt) {
2850
2892
  const iteration = ctx.memory.iteration || 0;
2851
2893
  const progress = ctx.memory.progress;
@@ -3232,10 +3274,268 @@ auto-proxied by the system \u2014 no extra work needed for those.`,
3232
3274
  execute: async (params) => writeFileContent(params.path, params.content)
3233
3275
  };
3234
3276
 
3277
+ // src/engine/tools/system/shell-tools.ts
3278
+ function createShellTool(name, description, parameters, required, execute) {
3279
+ return {
3280
+ name,
3281
+ description,
3282
+ parameters,
3283
+ required,
3284
+ execute
3285
+ };
3286
+ }
3287
+ function getShellCheckCommand(profile) {
3288
+ switch (profile) {
3289
+ case "stability":
3290
+ return "echo $TERM && tty && stty -a";
3291
+ case "post":
3292
+ return "id && whoami && hostname && ip a | head -n 20";
3293
+ case "environment":
3294
+ return "pwd && uname -a && cat /etc/os-release 2>/dev/null | head -n 5";
3295
+ case "identity":
3296
+ default:
3297
+ return "id && whoami && hostname";
3298
+ }
3299
+ }
3300
+ function getShellUpgradeCommand(method) {
3301
+ switch (method || "python_pty") {
3302
+ case "python_pty":
3303
+ return `python3 -c 'import pty; pty.spawn("/bin/bash")' || python -c 'import pty; pty.spawn("/bin/bash")'`;
3304
+ case "script":
3305
+ return "script -q /dev/null -c /bin/bash";
3306
+ case "perl":
3307
+ return q(`perl -e 'exec "/bin/bash";'`);
3308
+ case "ruby":
3309
+ return q(`ruby -e 'exec "/bin/bash"'`);
3310
+ case "socat_probe":
3311
+ return "command -v socat && socat -h | head -n 5";
3312
+ case "stty_probe":
3313
+ return "echo $TERM && tty && stty -a";
3314
+ default:
3315
+ return null;
3316
+ }
3317
+ }
3318
+ function outputLooksStabilized(output) {
3319
+ const normalized = output.toLowerCase();
3320
+ return normalized.includes("xterm") || normalized.includes("/dev/pts/") || normalized.includes("rows") || normalized.includes("columns");
3321
+ }
3322
+ function q(value) {
3323
+ return value;
3324
+ }
3325
+ async function executeShellUpgrade(processId, method, waitMs) {
3326
+ const command = getShellUpgradeCommand(method);
3327
+ if (!command) {
3328
+ return {
3329
+ success: false,
3330
+ output: "",
3331
+ error: `Unknown shell upgrade method: ${method}. Use: python_pty, script, perl, ruby, socat_probe, stty_probe`
3332
+ };
3333
+ }
3334
+ logEvent(processId, PROCESS_EVENTS.SHELL_UPGRADE_ATTEMPTED, `Shell upgrade method: ${method || "python_pty"}`);
3335
+ return handleInteractAction(processId, command, waitMs);
3336
+ }
3337
+ function buildListenerCommand(port, bind) {
3338
+ return bind ? `nc -lvnp ${port} -s ${bind}` : `nc -lvnp ${port}`;
3339
+ }
3340
+ function executeListenerStart(port, bind, purpose) {
3341
+ if (!Number.isFinite(port) || port <= 0) {
3342
+ return {
3343
+ success: false,
3344
+ output: "",
3345
+ error: "Invalid port. Provide a positive numeric port."
3346
+ };
3347
+ }
3348
+ const usedPorts = getUsedPorts();
3349
+ if (usedPorts.includes(port)) {
3350
+ return {
3351
+ success: false,
3352
+ output: `[X] PORT CONFLICT: Port ${port} is already in use.
3353
+ Used ports: ${usedPorts.join(", ")}`,
3354
+ error: `Port ${port} already in use`
3355
+ };
3356
+ }
3357
+ const command = buildListenerCommand(port, bind);
3358
+ const portMatch = command.match(DETECTION_PATTERNS.LISTENER);
3359
+ if (!portMatch) {
3360
+ return {
3361
+ success: false,
3362
+ output: "",
3363
+ error: `Failed to build listener command for port ${port}`
3364
+ };
3365
+ }
3366
+ const proc = startBackgroundProcess(command, {
3367
+ description: `listener ${port}`,
3368
+ purpose: purpose || `Reverse shell listener on port ${port}`
3369
+ });
3370
+ return {
3371
+ success: true,
3372
+ output: `Listener started.
3373
+ Process ID: ${proc.id}
3374
+ PID: ${proc.pid}
3375
+ Port: ${proc.listeningPort || port}
3376
+ Role: ${proc.role}
3377
+ Purpose: ${proc.purpose}`
3378
+ };
3379
+ }
3380
+ var shellTools = [
3381
+ createShellTool(
3382
+ TOOL_NAMES.LISTENER_START,
3383
+ "Start exactly one reverse-shell listener as a tracked background process.",
3384
+ {
3385
+ port: { type: "number", description: "Listener port" },
3386
+ bind: { type: "string", description: "Optional local bind address" },
3387
+ purpose: { type: "string", description: "Optional purpose for resource tracking" }
3388
+ },
3389
+ ["port"],
3390
+ async (params) => executeListenerStart(
3391
+ Number(params.port),
3392
+ params.bind,
3393
+ params.purpose
3394
+ )
3395
+ ),
3396
+ createShellTool(
3397
+ TOOL_NAMES.LISTENER_STATUS,
3398
+ "Check a reverse-shell listener in detail. Use this instead of bg_process status when supervising callbacks.",
3399
+ {
3400
+ process_id: { type: "string", description: "Listener process ID" }
3401
+ },
3402
+ ["process_id"],
3403
+ async (params) => handleStatusAction(params.process_id)
3404
+ ),
3405
+ createShellTool(
3406
+ TOOL_NAMES.SHELL_PROMOTE,
3407
+ "Promote a listener with a confirmed callback into the active shell role.",
3408
+ {
3409
+ process_id: { type: "string", description: "Listener process ID" },
3410
+ description: { type: "string", description: "Optional shell description" }
3411
+ },
3412
+ ["process_id"],
3413
+ async (params) => handlePromoteAction(params.process_id, params.description)
3414
+ ),
3415
+ createShellTool(
3416
+ TOOL_NAMES.SHELL_EXEC,
3417
+ "Execute a command through an active reverse shell and return new output.",
3418
+ {
3419
+ process_id: { type: "string", description: "Active shell process ID" },
3420
+ command: { type: "string", description: "Command to execute through the shell" },
3421
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3422
+ },
3423
+ ["process_id", "command"],
3424
+ async (params) => handleInteractAction(
3425
+ params.process_id,
3426
+ params.command,
3427
+ params.wait_ms
3428
+ )
3429
+ ),
3430
+ createShellTool(
3431
+ TOOL_NAMES.SHELL_CHECK,
3432
+ "Run a bounded shell quality or environment check. Profiles: identity, stability, environment, post.",
3433
+ {
3434
+ process_id: { type: "string", description: "Shell process ID" },
3435
+ profile: { type: "string", description: "identity | stability | environment | post" },
3436
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3437
+ },
3438
+ ["process_id"],
3439
+ async (params) => {
3440
+ const processId = params.process_id;
3441
+ const profile = params.profile;
3442
+ const result = await handleInteractAction(
3443
+ processId,
3444
+ getShellCheckCommand(profile),
3445
+ params.wait_ms
3446
+ );
3447
+ if (result.success && profile === "stability") {
3448
+ logEvent(processId, PROCESS_EVENTS.SHELL_STABILITY_CHECKED, "Shell stability probe executed by shell_check");
3449
+ }
3450
+ if (result.success && profile === "stability" && outputLooksStabilized(result.output)) {
3451
+ logEvent(processId, PROCESS_EVENTS.SHELL_STABILIZED, "Shell stability confirmed by shell_check");
3452
+ }
3453
+ if (result.success && profile === "stability" && !outputLooksStabilized(result.output)) {
3454
+ logEvent(
3455
+ processId,
3456
+ PROCESS_EVENTS.SHELL_STABILITY_INCOMPLETE,
3457
+ "Shell stability probe did not confirm a stable PTY"
3458
+ );
3459
+ }
3460
+ if (result.success && profile === "post") {
3461
+ logEvent(processId, PROCESS_EVENTS.POST_EXPLOITATION_ACTIVITY, "Post-exploitation probe executed by shell_check");
3462
+ }
3463
+ return result;
3464
+ }
3465
+ ),
3466
+ createShellTool(
3467
+ TOOL_NAMES.SHELL_UPGRADE,
3468
+ "Send a bounded shell-upgrade attempt or upgrade probe through an active shell.",
3469
+ {
3470
+ process_id: { type: "string", description: "Shell process ID" },
3471
+ method: { type: "string", description: "python_pty | script | perl | ruby | socat_probe | stty_probe" },
3472
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3473
+ },
3474
+ ["process_id"],
3475
+ async (params) => executeShellUpgrade(
3476
+ params.process_id,
3477
+ params.method,
3478
+ params.wait_ms
3479
+ )
3480
+ )
3481
+ ];
3482
+
3483
+ // src/engine/tools/system/offensive-bounded-tools.ts
3484
+ async function runBoundedForegroundCommand(command, timeout) {
3485
+ return runCommand(command, [], timeout ? { timeout } : {});
3486
+ }
3487
+ function createBoundedCommandTool(name, description) {
3488
+ return {
3489
+ name,
3490
+ description,
3491
+ parameters: {
3492
+ command: { type: "string", description: "Bounded foreground command for this phase-specific operation" },
3493
+ timeout: { type: "number", description: "Optional timeout in ms" }
3494
+ },
3495
+ required: ["command"],
3496
+ execute: async (params) => runBoundedForegroundCommand(
3497
+ params.command,
3498
+ params.timeout
3499
+ )
3500
+ };
3501
+ }
3502
+ var offensiveBoundedTools = [
3503
+ createBoundedCommandTool(
3504
+ TOOL_NAMES.EXPLOIT_CREDENTIAL_CHECK,
3505
+ "Run a bounded exploit credential/token validation probe. Use after a chain dumps credentials or tokens."
3506
+ ),
3507
+ createBoundedCommandTool(
3508
+ TOOL_NAMES.EXPLOIT_ARTIFACT_CHECK,
3509
+ "Run a bounded exploit artifact validation probe. Use to verify the current artifact before replacing it."
3510
+ ),
3511
+ createBoundedCommandTool(
3512
+ TOOL_NAMES.EXPLOIT_FOOTHOLD_CHECK,
3513
+ "Run a bounded foothold confirmation probe after an exploit chain appears to land access."
3514
+ ),
3515
+ createBoundedCommandTool(
3516
+ TOOL_NAMES.EXPLOIT_VECTOR_CHECK,
3517
+ "Run a bounded exploit vector reachability or service confirmation probe before changing vectors."
3518
+ ),
3519
+ createBoundedCommandTool(
3520
+ TOOL_NAMES.PWN_CRASH_REPRO,
3521
+ "Run a bounded pwn crash reproduction command from the preserved crash state."
3522
+ ),
3523
+ createBoundedCommandTool(
3524
+ TOOL_NAMES.PWN_OFFSET_CHECK,
3525
+ "Run a bounded pwn offset verification command when control primitives are believed to be known."
3526
+ ),
3527
+ createBoundedCommandTool(
3528
+ TOOL_NAMES.PWN_PAYLOAD_SMOKE,
3529
+ "Run a bounded pwn payload smoke-test command against the latest exploit revision."
3530
+ )
3531
+ ];
3532
+
3235
3533
  // src/engine/tools/system/index.ts
3236
3534
  var systemTools = [
3237
3535
  runCmdTool,
3238
3536
  bgProcessTool,
3537
+ ...shellTools,
3538
+ ...offensiveBoundedTools,
3239
3539
  readFileTool,
3240
3540
  writeFileTool
3241
3541
  ];
@@ -4253,9 +4553,7 @@ function isTransientHttpError(status) {
4253
4553
  async function parseNmap(xmlPath) {
4254
4554
  try {
4255
4555
  const fileResult = await readFileContent(xmlPath);
4256
- if (!fileResult.success) {
4257
- return fileResult;
4258
- }
4556
+ if (!fileResult.success) return fileResult;
4259
4557
  const xmlContent = fileResult.output;
4260
4558
  const results = {
4261
4559
  targets: [],
@@ -4264,31 +4562,7 @@ async function parseNmap(xmlPath) {
4264
4562
  const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
4265
4563
  const hosts = xmlContent.match(hostRegex) || [];
4266
4564
  for (const hostBlock of hosts) {
4267
- const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
4268
- const ip = ipMatch ? ipMatch[1] : "";
4269
- const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
4270
- const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
4271
- const ports = [];
4272
- const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
4273
- let portMatch;
4274
- while ((portMatch = portRegex.exec(hostBlock)) !== null) {
4275
- const protocol = portMatch[1];
4276
- const port = parseInt(portMatch[2]);
4277
- const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
4278
- const state = stateMatch ? stateMatch[1] : "";
4279
- const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
4280
- const service = serviceMatch ? serviceMatch[1] : void 0;
4281
- const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
4282
- if (state === PORT_STATE.OPEN) {
4283
- ports.push({ port, protocol, state, service, version });
4284
- results.summary.openPorts++;
4285
- if (service) results.summary.servicesFound++;
4286
- }
4287
- }
4288
- if (ip) {
4289
- results.targets.push({ ip, hostname, ports });
4290
- results.summary.totalTargets++;
4291
- }
4565
+ parseHost(hostBlock, results);
4292
4566
  }
4293
4567
  return {
4294
4568
  success: true,
@@ -4302,6 +4576,37 @@ async function parseNmap(xmlPath) {
4302
4576
  };
4303
4577
  }
4304
4578
  }
4579
+ function parseHost(hostBlock, results) {
4580
+ const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
4581
+ const ip = ipMatch ? ipMatch[1] : "";
4582
+ const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
4583
+ const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
4584
+ const ports = parsePorts(hostBlock, results);
4585
+ if (ip) {
4586
+ results.targets.push({ ip, hostname, ports });
4587
+ results.summary.totalTargets++;
4588
+ }
4589
+ }
4590
+ function parsePorts(hostBlock, results) {
4591
+ const ports = [];
4592
+ const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
4593
+ let portMatch;
4594
+ while ((portMatch = portRegex.exec(hostBlock)) !== null) {
4595
+ const protocol = portMatch[1];
4596
+ const port = parseInt(portMatch[2]);
4597
+ const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
4598
+ const state = stateMatch ? stateMatch[1] : "";
4599
+ const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
4600
+ const service = serviceMatch ? serviceMatch[1] : void 0;
4601
+ const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
4602
+ if (state === PORT_STATE.OPEN) {
4603
+ ports.push({ port, protocol, state, service, version });
4604
+ results.summary.openPorts++;
4605
+ if (service) results.summary.servicesFound++;
4606
+ }
4607
+ }
4608
+ return ports;
4609
+ }
4305
4610
 
4306
4611
  // src/engine/tools/intel-utils/cve-search.ts
4307
4612
  import { execFileSync } from "child_process";
@@ -4861,54 +5166,57 @@ function getStorageAndAuthDumpSnippet(capturedVarName = "_capturedHeaders", safe
4861
5166
  }
4862
5167
 
4863
5168
  // src/engine/tools/web-browser/scripts/builder.ts
4864
- function buildBrowseScript(url, options, screenshotPath, sessionPath) {
4865
- const safeUrl = safeJsString(url);
4866
- const safeUserAgent = safeJsString(options.userAgent || BROWSER_CONFIG.DEFAULT_USER_AGENT);
4867
- const safeScreenshotPath = screenshotPath ? safeJsString(screenshotPath) : "null";
4868
- const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
4869
- const playwrightPath = getPlaywrightPath();
4870
- const safePlaywrightPath = safeJsString(playwrightPath);
4871
- const safeSessionPath = sessionPath ? safeJsString(sessionPath) : null;
4872
- return `
4873
- const { chromium } = require(${safePlaywrightPath});
5169
+ function buildCommonHeader(safePlaywrightPath) {
5170
+ return `const { chromium } = require(${safePlaywrightPath});
4874
5171
  const fs = require('fs');
4875
5172
 
4876
5173
  (async () => {
4877
5174
  const browser = await chromium.launch({
4878
5175
  headless: true,
4879
5176
  args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
4880
- });
4881
-
4882
- // \xA74 Browser Session: load existing session if requested
5177
+ });`;
5178
+ }
5179
+ function buildSessionContext(options, safeSessionPath) {
5180
+ const safeUserAgent = options.userAgent ? safeJsString(options.userAgent) : "undefined";
5181
+ const safeExtraHeaders = options.extraHeaders ? JSON.stringify(options.extraHeaders) : "{}";
5182
+ const viewportStr = options.viewport ? `viewport: { width: ${options.viewport.width}, height: ${options.viewport.height} },` : "";
5183
+ return `
4883
5184
  const contextOptions = {
4884
- userAgent: ${safeUserAgent},
4885
- viewport: { width: ${options.viewport.width}, height: ${options.viewport.height} },
4886
- extraHTTPHeaders: ${safeExtraHeaders}
5185
+ ${options.userAgent ? `userAgent: ${safeUserAgent},` : ""}
5186
+ ${viewportStr}
5187
+ ${options.extraHeaders ? `extraHTTPHeaders: ${safeExtraHeaders},` : ""}
4887
5188
  };
4888
5189
  ${safeSessionPath && options.useSession ? `
4889
5190
  if (fs.existsSync(${safeSessionPath})) {
4890
5191
  contextOptions.storageState = ${safeSessionPath};
4891
5192
  }` : ""}
4892
-
4893
5193
  const context = await browser.newContext(contextOptions);
5194
+ const page = await context.newPage();`;
5195
+ }
5196
+ function buildSessionSave(saveSession, safeSessionPath, targetObj, authHeadersProp) {
5197
+ if (!safeSessionPath || !saveSession) return "";
5198
+ return `
5199
+ await context.storageState({ path: ${safeSessionPath} });
5200
+ ${targetObj}.sessionSaved = ${safeSessionPath};
4894
5201
 
4895
- const page = await context.newPage();
4896
-
4897
- ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
4898
-
4899
- try {
4900
- await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${options.timeout} });
4901
-
4902
- // Wait for dynamic content
4903
- await page.waitForTimeout(${options.waitAfterLoad});
4904
-
4905
- const result = { _interceptedAuth: _capturedHeaders };
4906
-
4907
- // Extract title
4908
- result.title = await page.title();
4909
-
4910
- // Extract text content
4911
- ${options.extractContent ? `
5202
+ ${getStorageAndAuthDumpSnippet(authHeadersProp, safeSessionPath)}
5203
+ ${targetObj}.authHeadersSaved = _authHeadersPath;
5204
+ ${targetObj}.authHeadersNote = 'auth-headers.json contains intercepted network headers + full storage dump. LLM should inspect _storage field for unlabeled tokens.';
5205
+ `;
5206
+ }
5207
+ function buildCommonFooter() {
5208
+ return `
5209
+ } catch (error) {
5210
+ console.log(JSON.stringify({ error: error.message }));
5211
+ } finally {
5212
+ await browser.close();
5213
+ }
5214
+ })();`;
5215
+ }
5216
+ function buildBrowseExtraction(options) {
5217
+ let extraction = "";
5218
+ if (options.extractContent) {
5219
+ extraction += `
4912
5220
  result.text = await page.evaluate(() => {
4913
5221
  const body = document.body;
4914
5222
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
@@ -4919,19 +5227,19 @@ const fs = require('fs');
4919
5227
  if (content) text += content + '\\n';
4920
5228
  }
4921
5229
  return text.slice(0, ${BROWSER_LIMITS.MAX_TEXT_EXTRACTION});
4922
- });` : ""}
4923
-
4924
- // Extract links
4925
- ${options.extractLinks ? `
5230
+ });`;
5231
+ }
5232
+ if (options.extractLinks) {
5233
+ extraction += `
4926
5234
  result.links = await page.evaluate(() => {
4927
5235
  return Array.from(document.querySelectorAll('a[href]')).map(a => ({
4928
5236
  href: a.href,
4929
5237
  text: a.textContent.trim().slice(0, ${DISPLAY_LIMITS.LINK_TEXT_PREVIEW})
4930
5238
  })).slice(0, ${BROWSER_LIMITS.MAX_LINKS_EXTRACTION});
4931
- });` : ""}
4932
-
4933
- // Extract forms
4934
- ${options.extractForms ? `
5239
+ });`;
5240
+ }
5241
+ if (options.extractForms) {
5242
+ extraction += `
4935
5243
  result.forms = await page.evaluate(() => {
4936
5244
  return Array.from(document.querySelectorAll('form')).map(form => ({
4937
5245
  action: form.action,
@@ -4944,69 +5252,42 @@ const fs = require('fs');
4944
5252
  isRequired: input.required
4945
5253
  }))
4946
5254
  }));
4947
- });` : ""}
4948
-
4949
- // Take screenshot
4950
- ${screenshotPath ? `await page.screenshot({ path: ${safeScreenshotPath}, fullPage: false });` : ""}
5255
+ });`;
5256
+ }
5257
+ return extraction;
5258
+ }
5259
+ function buildBrowseScript(url, options, screenshotPath, sessionPath) {
5260
+ const safeUrl = safeJsString(url);
5261
+ const safeScreenshotPath = screenshotPath ? safeJsString(screenshotPath) : "null";
5262
+ const safePlaywrightPath = safeJsString(getPlaywrightPath());
5263
+ const safeSessionPath = sessionPath ? safeJsString(sessionPath) : null;
5264
+ options.userAgent = options.userAgent || BROWSER_CONFIG.DEFAULT_USER_AGENT;
5265
+ return `
5266
+ ${buildCommonHeader(safePlaywrightPath)}
5267
+ ${buildSessionContext(options, safeSessionPath)}
5268
+ ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
5269
+ try {
5270
+ await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${options.timeout} });
5271
+ await page.waitForTimeout(${options.waitAfterLoad});
4951
5272
 
4952
- // \xA74 Browser Session: save state + capture auth headers (LLM-independent, no hardcoded keys)
4953
- ${safeSessionPath && options.saveSession ? `
4954
- await context.storageState({ path: ${safeSessionPath} });
4955
- result.sessionSaved = ${safeSessionPath};
5273
+ const result = { _interceptedAuth: _capturedHeaders };
5274
+ result.title = await page.title();
4956
5275
 
4957
- ${getStorageAndAuthDumpSnippet("result._interceptedAuth", safeSessionPath)}
4958
- result.authHeadersSaved = _authHeadersPath;
4959
- result.authHeadersNote = 'auth-headers.json contains intercepted network headers + full storage dump. LLM should inspect _storage field for unlabeled tokens.';
4960
- ` : ""}
5276
+ ${buildBrowseExtraction(options)}
5277
+ ${screenshotPath ? `await page.screenshot({ path: ${safeScreenshotPath}, fullPage: false });` : ""}
5278
+ ${buildSessionSave(!!options.saveSession, safeSessionPath, "result", "result._interceptedAuth")}
4961
5279
 
4962
5280
  console.log(JSON.stringify(result));
4963
- } catch (error) {
4964
- console.log(JSON.stringify({ error: error.message }));
4965
- } finally {
4966
- await browser.close();
4967
- }
4968
- })();
5281
+ ${buildCommonFooter()}
4969
5282
  `;
4970
5283
  }
4971
- function buildFormScript(params) {
4972
- const { safeUrl, safeFormData, safePlaywrightPath, timeout, useSession, saveSession, safeSessionPath } = params;
5284
+ function buildFormFillingLogic(safeFormData) {
4973
5285
  return `
4974
- const { chromium } = require(${safePlaywrightPath});
4975
- const fs = require('fs');
4976
-
4977
- (async () => {
4978
- const browser = await chromium.launch({
4979
- headless: true,
4980
- args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
4981
- });
4982
-
4983
- // \xA74 Session: load existing session if requested
4984
- const contextOptions = {};
4985
- ${safeSessionPath && useSession ? `
4986
- if (fs.existsSync(${safeSessionPath})) {
4987
- contextOptions.storageState = ${safeSessionPath};
4988
- }` : ""}
4989
- const context = await browser.newContext(contextOptions);
4990
- const page = await context.newPage();
4991
-
4992
- ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
4993
-
4994
- try {
4995
- await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${timeout} });
4996
-
4997
- // \xA7LLM-AUTONOMY: Semantic form field matching via Accessibility Tree + multiple strategies.
4998
- // No hardcoded CSS selector rules. Tries in priority order:
4999
- // 1. name/id attribute (exact)
5000
- // 2. aria-label / placeholder semantic match (case-insensitive)
5001
- // 3. input[type] semantic inference (username\u2192text, password\u2192password)
5002
5286
  const formData = ${safeFormData};
5003
5287
  for (const [fieldKey, fieldValue] of Object.entries(formData)) {
5004
5288
  const lk = fieldKey.toLowerCase();
5005
-
5006
- // Strategy 1: exact name/id/aria-label attribute
5007
5289
  let el = await page.$(\`[name="\${fieldKey}"], #\${fieldKey}\`);
5008
5290
 
5009
- // Strategy 2: semantic aria-label or placeholder match
5010
5291
  if (!el) {
5011
5292
  el = await page.evaluateHandle((key) => {
5012
5293
  const inputs = [...document.querySelectorAll('input, textarea, select')];
@@ -5019,22 +5300,16 @@ const fs = require('fs');
5019
5300
  }, lk).then(h => h.asElement());
5020
5301
  }
5021
5302
 
5022
- // Strategy 3: type-based inference (username/email/user \u2192 type=text/email, password \u2192 type=password)
5023
5303
  if (!el) {
5024
5304
  let inferredType = null;
5025
5305
  if (['username', 'user', 'email', 'login'].includes(lk)) inferredType = ['text', 'email'];
5026
5306
  if (['password', 'pass', 'pwd'].includes(lk)) inferredType = ['password'];
5027
- if (inferredType) {
5028
- el = await page.$(\`input[type="\${inferredType[0]}"\${inferredType[1] ? \`, input[type="\${inferredType[1]}"\` : ''}]\`);
5029
- }
5307
+ if (inferredType) el = await page.$(\`input[type="\${inferredType[0]}"\${inferredType[1] ? \`, input[type="\${inferredType[1]}"\` : ''}]\`);
5030
5308
  }
5031
5309
 
5032
- if (el) {
5033
- await el.fill(String(fieldValue));
5034
- }
5310
+ if (el) await el.fill(String(fieldValue));
5035
5311
  }
5036
5312
 
5037
- // \xA7LLM-AUTONOMY: Submit via semantic search \u2014 finds any element that looks like a submit action.
5038
5313
  const submitEl = await page.$([
5039
5314
  'button[type="submit"]',
5040
5315
  'input[type="submit"]',
@@ -5047,27 +5322,29 @@ const fs = require('fs');
5047
5322
  await submitEl.click();
5048
5323
  await page.waitForLoadState('networkidle').catch(() => {});
5049
5324
  }
5325
+ `;
5326
+ }
5327
+ function buildFormScript(params) {
5328
+ const { safeUrl, safeFormData, safePlaywrightPath, timeout, useSession, saveSession, safeSessionPath } = params;
5329
+ return `
5330
+ ${buildCommonHeader(safePlaywrightPath)}
5331
+ ${buildSessionContext({ useSession }, safeSessionPath)}
5332
+ ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
5333
+ try {
5334
+ await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${timeout} });
5050
5335
 
5051
- // \xA74 Session: save state + capture real auth headers (no hardcoded keys)
5052
- ${safeSessionPath && saveSession ? `
5053
- await context.storageState({ path: ${safeSessionPath} });
5054
-
5055
- ${getStorageAndAuthDumpSnippet("_capturedHeaders", safeSessionPath)}
5056
- ` : ""}
5336
+ ${buildFormFillingLogic(safeFormData)}
5057
5337
 
5058
5338
  const result = {
5059
5339
  url: page.url(),
5060
5340
  title: await page.title(),
5061
- ${safeSessionPath && saveSession ? `authHeadersSaved: ${safeSessionPath}.replace('${BROWSER_PATHS.SESSION_FILE}', '${BROWSER_PATHS.AUTH_HEADERS_FILE}'),` : ""}
5341
+ _interceptedAuth: _capturedHeaders
5062
5342
  };
5063
5343
 
5344
+ ${buildSessionSave(saveSession, safeSessionPath, "result", "result._interceptedAuth")}
5345
+
5064
5346
  console.log(JSON.stringify(result));
5065
- } catch (error) {
5066
- console.log(JSON.stringify({ error: error.message }));
5067
- } finally {
5068
- await browser.close();
5069
- }
5070
- })();
5347
+ ${buildCommonFooter()}
5071
5348
  `;
5072
5349
  }
5073
5350
 
@@ -7950,6 +8227,7 @@ var SessionState = class {
7950
8227
  };
7951
8228
 
7952
8229
  // src/engine/state/core/shared-state.ts
8230
+ var MAX_DELEGATED_TASKS = 50;
7953
8231
  var SharedState = class {
7954
8232
  attackGraph = new AttackGraph();
7955
8233
  targetState;
@@ -7985,6 +8263,7 @@ var SharedState = class {
7985
8263
  * Current objective for flexible task management.
7986
8264
  */
7987
8265
  currentObjective = null;
8266
+ delegatedTasks = [];
7988
8267
  constructor() {
7989
8268
  this.targetState = new TargetState(this.attackGraph);
7990
8269
  this.findingState = new FindingState();
@@ -8010,6 +8289,7 @@ var SharedState = class {
8010
8289
  this.dynamicTechniques.clear();
8011
8290
  this.artifacts = [];
8012
8291
  this.currentObjective = null;
8292
+ this.delegatedTasks = [];
8013
8293
  this.lastReflection = "";
8014
8294
  this.lastTriageMemo = "";
8015
8295
  }
@@ -8127,6 +8407,38 @@ var SharedState = class {
8127
8407
  getTimeStatus() {
8128
8408
  return this.sessionState.getTimeStatus();
8129
8409
  }
8410
+ recordDelegatedTask(task) {
8411
+ const now = Date.now();
8412
+ const parentTask = task.resumeTaskId ? this.delegatedTasks.find((existing) => existing.id === task.resumeTaskId) : void 0;
8413
+ const record = {
8414
+ ...task,
8415
+ parentTaskId: task.resumeTaskId || void 0,
8416
+ rootTaskId: parentTask ? parentTask.rootTaskId || parentTask.id : task.resumeTaskId || void 0,
8417
+ id: generatePrefixedId("delegated_task"),
8418
+ createdAt: now,
8419
+ updatedAt: now
8420
+ };
8421
+ this.delegatedTasks.push(record);
8422
+ if (this.delegatedTasks.length > MAX_DELEGATED_TASKS) {
8423
+ this.delegatedTasks = this.delegatedTasks.slice(this.delegatedTasks.length - MAX_DELEGATED_TASKS);
8424
+ }
8425
+ return record;
8426
+ }
8427
+ restoreDelegatedTask(task) {
8428
+ this.delegatedTasks.push(task);
8429
+ if (this.delegatedTasks.length > MAX_DELEGATED_TASKS) {
8430
+ this.delegatedTasks = this.delegatedTasks.slice(this.delegatedTasks.length - MAX_DELEGATED_TASKS);
8431
+ }
8432
+ }
8433
+ getDelegatedTasks() {
8434
+ return [...this.delegatedTasks];
8435
+ }
8436
+ getActiveDelegatedTasks() {
8437
+ return this.delegatedTasks.filter((task) => task.status === "waiting" || task.status === "running");
8438
+ }
8439
+ getDelegatedTask(taskId) {
8440
+ return this.delegatedTasks.find((task) => task.id === taskId);
8441
+ }
8130
8442
  };
8131
8443
 
8132
8444
  // src/shared/utils/policy/document.ts
@@ -8168,6 +8480,37 @@ function writePolicyDocument(content) {
8168
8480
  );
8169
8481
  }
8170
8482
 
8483
+ // src/engine/auxiliary-llm/strategist-prompt.ts
8484
+ function buildStrategistPrompt(input) {
8485
+ const state = input.state;
8486
+ const sections = ["## Engagement State", StateSerializer.toPrompt(state)];
8487
+ const failures = state.workingMemory.toPrompt();
8488
+ if (failures) sections.push("", "## Failed Attempts (DO NOT REPEAT THESE)", failures);
8489
+ try {
8490
+ const journalSummary = readJournalSummary();
8491
+ if (journalSummary) sections.push("", "## Session Journal (past turns summary)", journalSummary);
8492
+ } catch {
8493
+ }
8494
+ const policyDocument = readPolicyDocument();
8495
+ if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
8496
+ const graph = state.attackGraph.toPrompt();
8497
+ if (graph) sections.push("", "## Attack Graph", graph);
8498
+ const timeline = state.episodicMemory.toPrompt();
8499
+ if (timeline) sections.push("", "## Recent Actions", timeline);
8500
+ const techniques = state.dynamicTechniques.toPrompt();
8501
+ if (techniques) sections.push("", "## Learned Techniques", techniques);
8502
+ sections.push("", "## Time", state.getTimeStatus());
8503
+ const analysis = state.getChallengeAnalysis?.();
8504
+ if (analysis && analysis.primaryType !== "unknown") {
8505
+ sections.push(
8506
+ "",
8507
+ "## Challenge Type",
8508
+ `${analysis.primaryType.toUpperCase()} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`
8509
+ );
8510
+ }
8511
+ return sections.join("\n");
8512
+ }
8513
+
8171
8514
  // src/engine/auxiliary-llm/strategist.ts
8172
8515
  var Strategist = class extends AuxiliaryLLMBase {
8173
8516
  lastDirective = null;
@@ -8184,33 +8527,7 @@ var Strategist = class extends AuxiliaryLLMBase {
8184
8527
  }
8185
8528
  // ─── Abstract impl ───────────────────────────────────────────────────────
8186
8529
  formatInput(input) {
8187
- const state = input.state;
8188
- const sections = ["## Engagement State", StateSerializer.toPrompt(state)];
8189
- const failures = state.workingMemory.toPrompt();
8190
- if (failures) sections.push("", "## Failed Attempts (DO NOT REPEAT THESE)", failures);
8191
- try {
8192
- const journalSummary = readJournalSummary();
8193
- if (journalSummary) sections.push("", "## Session Journal (past turns summary)", journalSummary);
8194
- } catch {
8195
- }
8196
- const policyDocument = readPolicyDocument();
8197
- if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
8198
- const graph = state.attackGraph.toPrompt();
8199
- if (graph) sections.push("", "## Attack Graph", graph);
8200
- const timeline = state.episodicMemory.toPrompt();
8201
- if (timeline) sections.push("", "## Recent Actions", timeline);
8202
- const techniques = state.dynamicTechniques.toPrompt();
8203
- if (techniques) sections.push("", "## Learned Techniques", techniques);
8204
- sections.push("", "## Time", state.getTimeStatus());
8205
- const analysis = state.getChallengeAnalysis?.();
8206
- if (analysis && analysis.primaryType !== "unknown") {
8207
- sections.push(
8208
- "",
8209
- "## Challenge Type",
8210
- `${analysis.primaryType.toUpperCase()} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`
8211
- );
8212
- }
8213
- return sections.join("\n");
8530
+ return buildStrategistPrompt(input);
8214
8531
  }
8215
8532
  parseOutput(_response) {
8216
8533
  throw new Error("Unused \u2014 execute() is overridden in Strategist");
@@ -8631,7 +8948,7 @@ function isPortArray(value) {
8631
8948
  (item) => typeof item === "object" && item !== null && typeof item.port === "number"
8632
8949
  );
8633
8950
  }
8634
- function parsePorts(value) {
8951
+ function parsePorts2(value) {
8635
8952
  return isPortArray(value) ? value : [];
8636
8953
  }
8637
8954
  function isValidSeverity(value) {
@@ -8663,7 +8980,7 @@ var SPRAY_LOOT_TYPES = /* @__PURE__ */ new Set([
8663
8980
  // src/domains/engagement/handlers/target.ts
8664
8981
  async function executeAddTarget(p, state) {
8665
8982
  const ip = p.ip;
8666
- const ports = parsePorts(p.ports);
8983
+ const ports = parsePorts2(p.ports);
8667
8984
  const os = p.os;
8668
8985
  const hostname = p.hostname;
8669
8986
  const existing = state.getTarget(ip);
@@ -9172,99 +9489,70 @@ function getContextRecommendations(context, variantCount) {
9172
9489
  }
9173
9490
 
9174
9491
  // src/shared/utils/payload-mutator/core.ts
9492
+ var TRANSFORM_STRATEGIES = {
9493
+ [TRANSFORM_TYPE.URL]: (p, v) => {
9494
+ v.push({ payload: urlEncode(p), transform: "url", description: "URL encoded (special chars only)" });
9495
+ v.push({ payload: urlEncodeAll(p), transform: "url_full", description: "URL encoded (all chars)" });
9496
+ },
9497
+ [TRANSFORM_TYPE.DOUBLE_URL]: (p, v) => v.push({ payload: doubleUrlEncode(p), transform: "double_url", description: "Double URL encoded" }),
9498
+ [TRANSFORM_TYPE.TRIPLE_URL]: (p, v) => v.push({ payload: tripleUrlEncode(p), transform: "triple_url", description: "Triple URL encoded" }),
9499
+ [TRANSFORM_TYPE.UNICODE]: (p, v) => v.push({ payload: unicodeEncode(p), transform: "unicode", description: "Unicode escape sequences" }),
9500
+ [TRANSFORM_TYPE.HTML_ENTITY_DEC]: (p, v) => v.push({ payload: htmlEntityDec(p), transform: "html_entity_dec", description: "HTML decimal entities" }),
9501
+ [TRANSFORM_TYPE.HTML_ENTITY_HEX]: (p, v) => v.push({ payload: htmlEntityHex(p), transform: "html_entity_hex", description: "HTML hex entities" }),
9502
+ [TRANSFORM_TYPE.HEX]: (p, v) => v.push({ payload: hexEncode(p), transform: "hex", description: "Hex encoded" }),
9503
+ [TRANSFORM_TYPE.OCTAL]: (p, v) => v.push({ payload: octalEncode(p), transform: "octal", description: "Octal encoded" }),
9504
+ [TRANSFORM_TYPE.BASE64]: (p, v) => v.push({ payload: base64Encode(p), transform: "base64", description: "Base64 encoded" }),
9505
+ [TRANSFORM_TYPE.CASE_SWAP]: (p, v) => {
9506
+ for (let i = 0; i < 3; i++) v.push({ payload: caseSwap(p), transform: "case_swap", description: `Case variation ${i + 1}` });
9507
+ },
9508
+ [TRANSFORM_TYPE.COMMENT_INSERT]: (p, v) => v.push({ payload: commentInsert(p), transform: "comment_insert", description: "SQL comments in keywords" }),
9509
+ [TRANSFORM_TYPE.WHITESPACE_ALT]: (p, v) => {
9510
+ v.push({ payload: whitespaceAlt(p), transform: "whitespace_alt", description: "Alternative whitespace chars" });
9511
+ v.push({ payload: p.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
9512
+ v.push({ payload: p.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
9513
+ },
9514
+ [TRANSFORM_TYPE.CHAR_FUNCTION]: (p, v) => {
9515
+ v.push({ payload: charFunction(p, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
9516
+ v.push({ payload: charFunction(p, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
9517
+ v.push({ payload: charFunction(p, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
9518
+ },
9519
+ [TRANSFORM_TYPE.CONCAT_SPLIT]: (p, v) => v.push({ payload: concatSplit(p), transform: "concat_split", description: "String split via CONCAT" }),
9520
+ [TRANSFORM_TYPE.NULL_BYTE]: (p, v) => {
9521
+ v.push({ payload: nullByteAppend(p), transform: "null_byte", description: "Null byte appended" });
9522
+ v.push({ payload: p + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
9523
+ },
9524
+ [TRANSFORM_TYPE.REVERSE]: (p, v) => v.push({ payload: reversePayload(p), transform: "reverse", description: "Reversed (use with rev | sh)" }),
9525
+ [TRANSFORM_TYPE.UTF8_OVERLONG]: (p, v) => v.push({ payload: utf8Overlong(p), transform: "utf8_overlong", description: "UTF-8 overlong sequences" }),
9526
+ [TRANSFORM_TYPE.MIXED_ENCODING]: (p, v) => v.push({ payload: mixedEncoding(p), transform: "mixed_encoding", description: "Mixed encoding (partial)" }),
9527
+ [TRANSFORM_TYPE.TAG_ALTERNATIVE]: (p, v) => v.push(...generateXssAlternatives(p)),
9528
+ [TRANSFORM_TYPE.EVENT_HANDLER]: (p, v) => v.push(...generateXssAlternatives(p)),
9529
+ [TRANSFORM_TYPE.JS_ALTERNATIVE]: (p, v) => v.push(...generateXssAlternatives(p)),
9530
+ [TRANSFORM_TYPE.KEYWORD_BYPASS]: (p, v) => {
9531
+ v.push({ payload: keywordBypass(p), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
9532
+ v.push({ payload: spaceBypass(p), transform: "space_bypass", description: "$IFS space bypass" });
9533
+ },
9534
+ [TRANSFORM_TYPE.SPACE_BYPASS]: (p, v) => {
9535
+ v.push({ payload: p.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
9536
+ v.push({ payload: p.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
9537
+ v.push({ payload: p.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
9538
+ const parts = p.split(" ");
9539
+ if (parts.length >= 2) v.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
9540
+ }
9541
+ };
9175
9542
  function mutatePayload(request) {
9176
9543
  const { payload, transforms, context = "generic", maxVariants = 20 } = request;
9177
9544
  const variants = [];
9178
- const recommendations = [];
9179
9545
  const activeTransforms = transforms || getDefaultTransforms(context);
9180
9546
  for (const transform of activeTransforms) {
9181
- switch (transform) {
9182
- case TRANSFORM_TYPE.URL:
9183
- variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
9184
- variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
9185
- break;
9186
- case TRANSFORM_TYPE.DOUBLE_URL:
9187
- variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
9188
- break;
9189
- case TRANSFORM_TYPE.TRIPLE_URL:
9190
- variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
9191
- break;
9192
- case TRANSFORM_TYPE.UNICODE:
9193
- variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
9194
- break;
9195
- case TRANSFORM_TYPE.HTML_ENTITY_DEC:
9196
- variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
9197
- break;
9198
- case TRANSFORM_TYPE.HTML_ENTITY_HEX:
9199
- variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
9200
- break;
9201
- case TRANSFORM_TYPE.HEX:
9202
- variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
9203
- break;
9204
- case TRANSFORM_TYPE.OCTAL:
9205
- variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
9206
- break;
9207
- case TRANSFORM_TYPE.BASE64:
9208
- variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
9209
- break;
9210
- case TRANSFORM_TYPE.CASE_SWAP:
9211
- for (let i = 0; i < 3; i++) {
9212
- variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
9213
- }
9214
- break;
9215
- case TRANSFORM_TYPE.COMMENT_INSERT:
9216
- variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
9217
- break;
9218
- case TRANSFORM_TYPE.WHITESPACE_ALT:
9219
- variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
9220
- variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
9221
- variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
9222
- break;
9223
- case TRANSFORM_TYPE.CHAR_FUNCTION:
9224
- variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
9225
- variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
9226
- variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
9227
- break;
9228
- case TRANSFORM_TYPE.CONCAT_SPLIT:
9229
- variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
9230
- break;
9231
- case TRANSFORM_TYPE.NULL_BYTE:
9232
- variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
9233
- variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
9234
- break;
9235
- case TRANSFORM_TYPE.REVERSE:
9236
- variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
9237
- break;
9238
- case TRANSFORM_TYPE.UTF8_OVERLONG:
9239
- variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
9240
- break;
9241
- case TRANSFORM_TYPE.MIXED_ENCODING:
9242
- variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
9243
- break;
9244
- case TRANSFORM_TYPE.TAG_ALTERNATIVE:
9245
- case TRANSFORM_TYPE.EVENT_HANDLER:
9246
- case TRANSFORM_TYPE.JS_ALTERNATIVE:
9247
- variants.push(...generateXssAlternatives(payload));
9248
- break;
9249
- case TRANSFORM_TYPE.KEYWORD_BYPASS:
9250
- variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
9251
- variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
9252
- break;
9253
- case TRANSFORM_TYPE.SPACE_BYPASS:
9254
- variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
9255
- variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
9256
- variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
9257
- const parts = payload.split(" ");
9258
- if (parts.length >= 2) {
9259
- variants.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
9260
- }
9261
- break;
9547
+ const handler = TRANSFORM_STRATEGIES[transform];
9548
+ if (handler) {
9549
+ handler(payload, variants);
9262
9550
  }
9263
9551
  }
9264
9552
  if (context.startsWith("sql")) {
9265
9553
  variants.push(...generateSqlAlternatives(payload));
9266
9554
  }
9267
- recommendations.push(...getContextRecommendations(context, variants.length));
9555
+ const recommendations = getContextRecommendations(context, variants.length);
9268
9556
  return {
9269
9557
  variants: variants.slice(0, maxVariants),
9270
9558
  recommendations
@@ -9324,8 +9612,8 @@ async function executeGetWordlists(p) {
9324
9612
  };
9325
9613
  const matchesSearch = (filePath, fileName) => {
9326
9614
  if (!search) return true;
9327
- const q = search.toLowerCase();
9328
- return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
9615
+ const q2 = search.toLowerCase();
9616
+ return fileName.toLowerCase().includes(q2) || filePath.toLowerCase().includes(q2);
9329
9617
  };
9330
9618
  const results = ["# Available Wordlists on System\n"];
9331
9619
  let totalCount = 0;
@@ -10653,7 +10941,7 @@ async function executeGoogleDork(args) {
10653
10941
  const output = [
10654
10942
  `=== Google Dork Queries for ${d} ===`,
10655
10943
  "",
10656
- ...selected.map((q, i) => `${i + 1}. https://www.google.com/search?q=${encodeURIComponent(q)}`),
10944
+ ...selected.map((q2, i) => `${i + 1}. https://www.google.com/search?q=${encodeURIComponent(q2)}`),
10657
10945
  "",
10658
10946
  "Paste these URLs into a browser to search manually.",
10659
10947
  "Tip: Also try: https://pentest-tools.com/information-gathering/google-hacking"
@@ -10908,6 +11196,51 @@ function logSuccessfulAction(state, toolCall, approval, result) {
10908
11196
  });
10909
11197
  }
10910
11198
 
11199
+ // src/engine/parsers/security.ts
11200
+ function extractCredentials(output) {
11201
+ const creds = [];
11202
+ const patterns = [
11203
+ // user:password from hydra, medusa, etc.
11204
+ { regex: /login:\s*(\S+)\s+password:\s*(\S+)/gi, type: "password" },
11205
+ // user:hash from hashdump, secretsdump, etc.
11206
+ { regex: /(\S+):(\d+):([a-f0-9]{32}):([a-f0-9]{32})/g, type: "hash" },
11207
+ // Generic user:pass pattern
11208
+ { regex: /(?:username|user|login)\s*[:=]\s*['"]?(\S+?)['"]?\s+(?:password|pass|pwd)\s*[:=]\s*['"]?(\S+?)['"]?/gi, type: "password" },
11209
+ // Database connection strings
11210
+ { regex: /(?:postgres|mysql|mongodb):\/\/([^:]+):([^@]+)@/g, type: "password" },
11211
+ // API tokens/keys
11212
+ { regex: /(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: "token" }
11213
+ ];
11214
+ for (const { regex, type } of patterns) {
11215
+ let match;
11216
+ while ((match = regex.exec(output)) !== null) {
11217
+ const username = match[1] || "unknown";
11218
+ const credential = match[2] || match[1];
11219
+ if (credential && !creds.some((c) => c.username === username && c.credential === credential)) {
11220
+ creds.push({ username, credential, type, source: "auto-extracted" });
11221
+ }
11222
+ }
11223
+ }
11224
+ return creds;
11225
+ }
11226
+ function extractVulnerabilities(output) {
11227
+ const vulns = [];
11228
+ const cveRegex = /CVE-\d{4}-\d{4,}/g;
11229
+ let match;
11230
+ while ((match = cveRegex.exec(output)) !== null) {
11231
+ const cveId = match[0];
11232
+ if (!vulns.some((v) => v.id === cveId)) {
11233
+ vulns.push({
11234
+ id: cveId,
11235
+ severity: "unknown",
11236
+ description: `${cveId} referenced in output`,
11237
+ hasExploit: /exploit|poc|proof.of.concept/i.test(output)
11238
+ });
11239
+ }
11240
+ }
11241
+ return vulns;
11242
+ }
11243
+
10911
11244
  // src/engine/parsers/types.ts
10912
11245
  var PORT_STATE2 = {
10913
11246
  OPEN: "open",
@@ -10977,51 +11310,6 @@ function extractFuzzStructured(output) {
10977
11310
  };
10978
11311
  }
10979
11312
 
10980
- // src/engine/parsers/security.ts
10981
- function extractCredentials(output) {
10982
- const creds = [];
10983
- const patterns = [
10984
- // user:password from hydra, medusa, etc.
10985
- { regex: /login:\s*(\S+)\s+password:\s*(\S+)/gi, type: "password" },
10986
- // user:hash from hashdump, secretsdump, etc.
10987
- { regex: /(\S+):(\d+):([a-f0-9]{32}):([a-f0-9]{32})/g, type: "hash" },
10988
- // Generic user:pass pattern
10989
- { regex: /(?:username|user|login)\s*[:=]\s*['"]?(\S+?)['"]?\s+(?:password|pass|pwd)\s*[:=]\s*['"]?(\S+?)['"]?/gi, type: "password" },
10990
- // Database connection strings
10991
- { regex: /(?:postgres|mysql|mongodb):\/\/([^:]+):([^@]+)@/g, type: "password" },
10992
- // API tokens/keys
10993
- { regex: /(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: "token" }
10994
- ];
10995
- for (const { regex, type } of patterns) {
10996
- let match;
10997
- while ((match = regex.exec(output)) !== null) {
10998
- const username = match[1] || "unknown";
10999
- const credential = match[2] || match[1];
11000
- if (credential && !creds.some((c) => c.username === username && c.credential === credential)) {
11001
- creds.push({ username, credential, type, source: "auto-extracted" });
11002
- }
11003
- }
11004
- }
11005
- return creds;
11006
- }
11007
- function extractVulnerabilities(output) {
11008
- const vulns = [];
11009
- const cveRegex = /CVE-\d{4}-\d{4,}/g;
11010
- let match;
11011
- while ((match = cveRegex.exec(output)) !== null) {
11012
- const cveId = match[0];
11013
- if (!vulns.some((v) => v.id === cveId)) {
11014
- vulns.push({
11015
- id: cveId,
11016
- severity: "unknown",
11017
- description: `${cveId} referenced in output`,
11018
- hasExploit: /exploit|poc|proof.of.concept/i.test(output)
11019
- });
11020
- }
11021
- }
11022
- return vulns;
11023
- }
11024
-
11025
11313
  // src/shared/utils/binary-analysis/types.ts
11026
11314
  var RELRO_STATUS = {
11027
11315
  NO: "no",
@@ -11124,45 +11412,42 @@ function formatBinaryAnalysis(info) {
11124
11412
  return lines.join("\n");
11125
11413
  }
11126
11414
 
11127
- // src/engine/parsers/index.ts
11128
- function autoExtractStructured(toolName, output) {
11129
- if (!output || output.length < 10) return null;
11130
- const data = {};
11131
- let hasData = false;
11132
- const creds = extractCredentials(output);
11133
- if (creds && creds.length > 0) {
11134
- data.credentials = creds;
11135
- hasData = true;
11136
- }
11137
- const vulns = extractVulnerabilities(output);
11138
- if (vulns && vulns.length > 0) {
11139
- data.vulnerabilities = vulns;
11140
- hasData = true;
11141
- }
11415
+ // src/engine/parsers/auto-extractors.ts
11416
+ function extractNmapData(toolName, output, data) {
11142
11417
  if (toolName === TOOL_NAMES.PARSE_NMAP || /nmap scan report/i.test(output)) {
11143
11418
  const nmap = extractNmapStructured(output);
11144
11419
  if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
11145
11420
  data.openPorts = nmap.structured.openPorts;
11146
11421
  data.hosts = nmap.structured.hosts;
11147
- hasData = true;
11422
+ return true;
11148
11423
  }
11149
11424
  }
11425
+ return false;
11426
+ }
11427
+ function extractFuzzData(output, data) {
11150
11428
  if (/gobuster|ffuf|feroxbuster|dirbuster/i.test(output)) {
11151
11429
  const fuzz = extractFuzzStructured(output);
11152
11430
  if (fuzz.structured.paths && fuzz.structured.paths.length > 0) {
11153
11431
  data.paths = fuzz.structured.paths;
11154
- hasData = true;
11432
+ return true;
11155
11433
  }
11156
11434
  }
11435
+ return false;
11436
+ }
11437
+ function extractBinaryData(output, data) {
11157
11438
  if (/canary|RELRO|NX|PIE|Stack|FORTIFY/i.test(output) && /enabled|disabled|found|no /i.test(output)) {
11158
11439
  const binaryInfo = parseChecksec(output);
11159
11440
  if (binaryInfo.arch || binaryInfo.canary !== void 0 || binaryInfo.nx !== void 0) {
11160
11441
  data.binaryInfo = binaryInfo;
11161
11442
  data.binaryAnalysisSummary = formatBinaryAnalysis(binaryInfo);
11162
- hasData = true;
11443
+ return true;
11163
11444
  }
11164
11445
  }
11446
+ return false;
11447
+ }
11448
+ function extractOsintData(toolName, output, data) {
11165
11449
  if (toolName === TOOL_NAMES.WHOIS_LOOKUP || toolName === TOOL_NAMES.DNS_RECON || toolName === TOOL_NAMES.SUBDOMAIN_ENUM || toolName === TOOL_NAMES.HARVESTER || toolName === TOOL_NAMES.CERT_TRANSPARENCY) {
11450
+ let hasData = false;
11166
11451
  const ipMatches = output.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g);
11167
11452
  if (ipMatches && ipMatches.length > 0) {
11168
11453
  data.discoveredIPs = [...new Set(ipMatches)].slice(0, 50);
@@ -11173,21 +11458,52 @@ function autoExtractStructured(toolName, output) {
11173
11458
  data.discoveredSubdomains = [...new Set(subMatches)].slice(0, 100);
11174
11459
  hasData = true;
11175
11460
  }
11461
+ return hasData;
11176
11462
  }
11463
+ return false;
11464
+ }
11465
+ function extractCryptoData(toolName, output, data) {
11177
11466
  if (toolName === TOOL_NAMES.RSA_ANALYZE || toolName === TOOL_NAMES.HASH_IDENTIFY || toolName === TOOL_NAMES.PADDING_ORACLE_CHECK) {
11178
11467
  const weaknessMatches = output.match(/(?:FERMAT_FACTORED|SMALL_E|WEAK_N|Padding Oracle|hash type|cracked)[^\n]*/gi);
11179
11468
  if (weaknessMatches && weaknessMatches.length > 0) {
11180
11469
  data.cryptoWeaknesses = weaknessMatches.slice(0, 10);
11181
- hasData = true;
11470
+ return true;
11182
11471
  }
11183
11472
  }
11473
+ return false;
11474
+ }
11475
+ function extractForensicsData(toolName, output, data) {
11184
11476
  if (toolName === TOOL_NAMES.BINWALK_ANALYZE || toolName === TOOL_NAMES.STEGHIDE_EXTRACT || toolName === TOOL_NAMES.ZSTEG_ANALYZE || toolName === TOOL_NAMES.STEGSEEK) {
11185
11477
  const stegoMatches = output.match(/(?:JPEG|PNG|ZIP|embedded|hidden|extracted|passphrase|found)[^\n]*/gi);
11186
11478
  if (stegoMatches && stegoMatches.length > 0) {
11187
11479
  data.stegoHints = stegoMatches.slice(0, 10);
11188
- hasData = true;
11480
+ return true;
11189
11481
  }
11190
11482
  }
11483
+ return false;
11484
+ }
11485
+
11486
+ // src/engine/parsers/index.ts
11487
+ function autoExtractStructured(toolName, output) {
11488
+ if (!output || output.length < 10) return null;
11489
+ const data = {};
11490
+ let hasData = false;
11491
+ const creds = extractCredentials(output);
11492
+ if (creds && creds.length > 0) {
11493
+ data.credentials = creds;
11494
+ hasData = true;
11495
+ }
11496
+ const vulns = extractVulnerabilities(output);
11497
+ if (vulns && vulns.length > 0) {
11498
+ data.vulnerabilities = vulns;
11499
+ hasData = true;
11500
+ }
11501
+ if (extractNmapData(toolName, output, data)) hasData = true;
11502
+ if (extractFuzzData(output, data)) hasData = true;
11503
+ if (extractBinaryData(output, data)) hasData = true;
11504
+ if (extractOsintData(toolName, output, data)) hasData = true;
11505
+ if (extractCryptoData(toolName, output, data)) hasData = true;
11506
+ if (extractForensicsData(toolName, output, data)) hasData = true;
11191
11507
  return hasData ? data : null;
11192
11508
  }
11193
11509
 
@@ -11345,7 +11661,7 @@ function runPostExecutionPipeline(state, toolCall, result) {
11345
11661
  // src/engine/tool-registry/core.ts
11346
11662
  function validateRequiredParams(tool, input) {
11347
11663
  if (!tool.required || tool.required.length === 0) return [];
11348
- return tool.required.filter((param2) => !(param2 in input) || input[param2] === void 0);
11664
+ return tool.required.filter((param2) => !(param2 in input) || input[param2] == null);
11349
11665
  }
11350
11666
  var ToolRegistry = class {
11351
11667
  constructor(state, scopeGuard, approvalGate, events) {
@@ -11553,7 +11869,16 @@ After completion: record key loot/findings from the sub-agent output to canonica
11553
11869
  task: { type: "string", description: "Delegated task goal" },
11554
11870
  target: { type: "string", description: "Optional target IP/URL/path" },
11555
11871
  context: { type: "string", description: "Optional extra context" },
11556
- time_limit_min: { type: "number", description: "Optional hint only" }
11872
+ time_limit_min: { type: "number", description: "Optional hint only" },
11873
+ worker_type: {
11874
+ type: "string",
11875
+ enum: ["general", "shell-supervisor", "exploit", "pwn"],
11876
+ description: "Optional worker specialization for the delegated task"
11877
+ },
11878
+ resume_task_id: {
11879
+ type: "string",
11880
+ description: "Optional delegated task ID to resume/continue instead of creating a fresh chain"
11881
+ }
11557
11882
  },
11558
11883
  required: ["task"],
11559
11884
  execute: async (params) => {
@@ -11568,18 +11893,43 @@ After completion: record key loot/findings from the sub-agent output to canonica
11568
11893
  task: params["task"],
11569
11894
  target: params["target"],
11570
11895
  context: params["context"],
11571
- timeLimitMin: params["time_limit_min"]
11896
+ timeLimitMin: params["time_limit_min"],
11897
+ workerType: params["worker_type"],
11898
+ resumeTaskId: params["resume_task_id"]
11572
11899
  };
11573
- const { AgentTool } = await import("./agent-tool-HYQGTZC4.js");
11900
+ const { AgentTool } = await import("./agent-tool-MP274HWD.js");
11574
11901
  const executor = new AgentTool(state, events, scopeGuard, approvalGate);
11575
11902
  const result = await executor.execute(input);
11903
+ state.recordDelegatedTask({
11904
+ task: input.task,
11905
+ target: input.target,
11906
+ context: input.context,
11907
+ resumeTaskId: input.resumeTaskId,
11908
+ workerType: input.workerType,
11909
+ status: result.status,
11910
+ summary: result.summary,
11911
+ tried: result.tried,
11912
+ findings: result.findings,
11913
+ loot: result.loot,
11914
+ sessions: result.sessions,
11915
+ assets: result.assets,
11916
+ suggestedNext: result.suggestedNext,
11917
+ waitingOn: result.waitingOn,
11918
+ resumeHint: result.resumeHint,
11919
+ nextWorkerType: result.nextWorkerType
11920
+ });
11576
11921
  const lines = [
11577
11922
  `[Status] ${result.status}`,
11578
11923
  `[Summary] ${result.summary}`,
11579
11924
  result.findings.length ? `[Findings] ${result.findings.join(" | ")}` : "",
11580
11925
  result.loot.length ? `[Loot] ${result.loot.join(" | ")}` : "",
11581
11926
  result.sessions.length ? `[Sessions] ${result.sessions.join(", ")}` : "",
11927
+ result.assets.length ? `[Assets] ${result.assets.join(" | ")}` : "",
11582
11928
  result.tried.length ? `[Tried] ${result.tried.join(" | ")}` : "",
11929
+ input.resumeTaskId ? `[ResumedFrom] ${input.resumeTaskId}` : "",
11930
+ result.waitingOn ? `[Waiting] ${result.waitingOn}` : "",
11931
+ result.resumeHint ? `[Resume] ${result.resumeHint}` : "",
11932
+ result.nextWorkerType ? `[NextWorker] ${result.nextWorkerType}` : "",
11583
11933
  result.suggestedNext ? `[Next] ${result.suggestedNext}` : ""
11584
11934
  ].filter(Boolean);
11585
11935
  return { success: true, output: lines.join("\n") };
@@ -11618,6 +11968,18 @@ var CategorizedToolRegistry = class extends ToolRegistry {
11618
11968
  TOOL_NAMES.READ_FILE,
11619
11969
  TOOL_NAMES.WRITE_FILE,
11620
11970
  TOOL_NAMES.BG_PROCESS,
11971
+ TOOL_NAMES.LISTENER_START,
11972
+ TOOL_NAMES.LISTENER_STATUS,
11973
+ TOOL_NAMES.SHELL_PROMOTE,
11974
+ TOOL_NAMES.SHELL_EXEC,
11975
+ TOOL_NAMES.SHELL_CHECK,
11976
+ TOOL_NAMES.SHELL_UPGRADE,
11977
+ TOOL_NAMES.EXPLOIT_CREDENTIAL_CHECK,
11978
+ TOOL_NAMES.EXPLOIT_ARTIFACT_CHECK,
11979
+ TOOL_NAMES.EXPLOIT_FOOTHOLD_CHECK,
11980
+ TOOL_NAMES.PWN_CRASH_REPRO,
11981
+ TOOL_NAMES.PWN_OFFSET_CHECK,
11982
+ TOOL_NAMES.PWN_PAYLOAD_SMOKE,
11621
11983
  TOOL_NAMES.PARSE_NMAP,
11622
11984
  TOOL_NAMES.SEARCH_CVE,
11623
11985
  TOOL_NAMES.WEB_SEARCH,
@@ -11666,6 +12028,127 @@ var CategorizedToolRegistry = class extends ToolRegistry {
11666
12028
  }
11667
12029
  };
11668
12030
 
12031
+ // src/engine/agent-tool/shell-supervisor-lifecycle.ts
12032
+ function hasConnectionSignal(processId, stdout) {
12033
+ if (DETECTION_PATTERNS.CONNECTION.some((pattern) => pattern.test(stdout))) {
12034
+ return true;
12035
+ }
12036
+ return getProcessEventLog().some(
12037
+ (event) => event.processId === processId && event.event === PROCESS_EVENTS.CONNECTION_DETECTED
12038
+ );
12039
+ }
12040
+ function getRecentCommandDetails(processId) {
12041
+ return getProcessEventLog().filter((event) => event.processId === processId && event.event === PROCESS_EVENTS.COMMAND_SENT).slice(-10).map((event) => event.detail.toLowerCase());
12042
+ }
12043
+ function hasEvent(processId, eventName) {
12044
+ return getProcessEventLog().some(
12045
+ (event) => event.processId === processId && event.event === eventName
12046
+ );
12047
+ }
12048
+ function getLatestEventTimestamp(processId, eventName) {
12049
+ return getProcessEventLog().filter((event) => event.processId === processId && event.event === eventName).reduce((latest, event) => Math.max(latest, event.timestamp), 0);
12050
+ }
12051
+ function isPtyUpgradeCommand(detail) {
12052
+ return detail.includes("pty.spawn(") || detail.includes("import pty; pty.spawn(") || detail.includes("script -qc") || detail.includes("script -q /dev/null -c /bin/bash") || detail.includes("script /dev/null -c bash") || detail.includes("stty raw -echo") || detail.includes("export term=") || detail.includes("export shell=") || detail.includes("stty rows") || detail.includes("stty columns") || detail.includes("tty") || detail.includes("/usr/bin/expect -c") || detail.includes('exec "/bin/bash"');
12053
+ }
12054
+ function isShellStabilized(stdout, commandDetails) {
12055
+ const normalized = stdout.toLowerCase();
12056
+ return normalized.includes("xterm") || normalized.includes("rows") || normalized.includes("columns") || commandDetails.some(
12057
+ (detail) => detail.includes("export term=") || detail.includes("export shell=") || detail.includes("stty rows") || detail.includes("stty columns")
12058
+ );
12059
+ }
12060
+ function isPostExploitationCommand(detail) {
12061
+ return detail.includes("sudo -l") || detail.includes("ip a") || detail.includes("ip route") || detail.includes("ps aux") || detail.includes("ss -tlnp") || detail.includes("netstat -tlnp") || detail.includes("env | grep") || detail.includes("find / -perm -4000") || detail.includes("getcap -r /") || detail.includes("cat /etc/os-release") || detail.includes("uname -a") || detail.includes("whoami && hostname");
12062
+ }
12063
+ function hasRecentEvent(processId, eventName) {
12064
+ return getProcessEventLog().some(
12065
+ (event) => event.processId === processId && event.event === eventName
12066
+ );
12067
+ }
12068
+ function getShellSupervisorLifecycleSnapshot() {
12069
+ const processes = listBackgroundProcesses().filter(
12070
+ (process2) => process2.isRunning && (process2.role === PROCESS_ROLES.ACTIVE_SHELL || process2.role === PROCESS_ROLES.LISTENER)
12071
+ );
12072
+ const activeShell = processes.find((process2) => process2.role === PROCESS_ROLES.ACTIVE_SHELL);
12073
+ if (activeShell) {
12074
+ const output = getProcessOutput(activeShell.id);
12075
+ const commandDetails = getRecentCommandDetails(activeShell.id);
12076
+ const lastStabilizedAt = getLatestEventTimestamp(activeShell.id, PROCESS_EVENTS.SHELL_STABILIZED);
12077
+ const lastIncompleteAt = getLatestEventTimestamp(activeShell.id, PROCESS_EVENTS.SHELL_STABILITY_INCOMPLETE);
12078
+ if (hasRecentEvent(activeShell.id, PROCESS_EVENTS.POST_EXPLOITATION_ACTIVITY) || commandDetails.some(isPostExploitationCommand)) {
12079
+ return {
12080
+ phase: "post_exploitation_active",
12081
+ activeShellId: activeShell.id,
12082
+ recommendation: `Active shell ${activeShell.id} is already in post-exploitation flow. Keep reusing it for controlled enumeration, privilege escalation checks, and pivot preparation.`
12083
+ };
12084
+ }
12085
+ if (hasEvent(activeShell.id, PROCESS_EVENTS.SHELL_STABILIZED) || output && isShellStabilized(output.stdout, commandDetails)) {
12086
+ if (lastIncompleteAt > lastStabilizedAt) {
12087
+ return {
12088
+ phase: "active_shell_stabilizing",
12089
+ activeShellId: activeShell.id,
12090
+ recommendation: `Active shell ${activeShell.id} lost stable PTY confirmation after the last probe. Re-run shell upgrade and verify TERM/TTY quality again before broad enumeration.`
12091
+ };
12092
+ }
12093
+ return {
12094
+ phase: "active_shell_stabilized",
12095
+ activeShellId: activeShell.id,
12096
+ recommendation: `Active shell ${activeShell.id} appears stabilized. Reuse it for controlled enumeration and follow-up operations.`
12097
+ };
12098
+ }
12099
+ if (hasEvent(activeShell.id, PROCESS_EVENTS.SHELL_UPGRADE_ATTEMPTED) || hasRecentEvent(activeShell.id, PROCESS_EVENTS.SHELL_STABILITY_INCOMPLETE) || hasRecentEvent(activeShell.id, PROCESS_EVENTS.SHELL_STABILITY_CHECKED) || commandDetails.some(isPtyUpgradeCommand)) {
12100
+ return {
12101
+ phase: "active_shell_stabilizing",
12102
+ activeShellId: activeShell.id,
12103
+ recommendation: `Active shell ${activeShell.id} is in PTY stabilization flow. Verify TERM/TTY quality, then continue controlled enumeration.`
12104
+ };
12105
+ }
12106
+ return {
12107
+ phase: "active_shell_ready",
12108
+ activeShellId: activeShell.id,
12109
+ recommendation: `Reuse active shell ${activeShell.id}, validate stability, and upgrade PTY before broad enumeration.`
12110
+ };
12111
+ }
12112
+ const listeners = processes.filter((process2) => process2.role === PROCESS_ROLES.LISTENER);
12113
+ for (const listener of listeners) {
12114
+ const output = getProcessOutput(listener.id);
12115
+ if (!output) continue;
12116
+ if (hasConnectionSignal(listener.id, output.stdout)) {
12117
+ return {
12118
+ phase: "listener_callback_detected",
12119
+ listenerId: listener.id,
12120
+ recommendation: `Listener ${listener.id} has a callback signal. Confirm status, promote immediately, then validate and stabilize the shell.`
12121
+ };
12122
+ }
12123
+ }
12124
+ if (listeners.length > 0) {
12125
+ return {
12126
+ phase: "listener_waiting",
12127
+ listenerId: listeners[0]?.id,
12128
+ recommendation: `Reuse listener ${listeners[0]?.id} and keep polling for a callback instead of opening a duplicate listener.`
12129
+ };
12130
+ }
12131
+ return {
12132
+ phase: "no_asset",
12133
+ recommendation: "No shell or listener asset is active. Create exactly one listener only if the callback path still requires it."
12134
+ };
12135
+ }
12136
+ function buildShellSupervisorLifecycleSection() {
12137
+ const snapshot = getShellSupervisorLifecycleSnapshot();
12138
+ const lines = [
12139
+ "## Shell Supervisor Lifecycle",
12140
+ `Current phase: ${snapshot.phase}`,
12141
+ `Recommendation: ${snapshot.recommendation}`
12142
+ ];
12143
+ if (snapshot.activeShellId) {
12144
+ lines.push(`Active shell: ${snapshot.activeShellId}`);
12145
+ }
12146
+ if (snapshot.listenerId) {
12147
+ lines.push(`Tracked listener: ${snapshot.listenerId}`);
12148
+ }
12149
+ return lines.join("\n");
12150
+ }
12151
+
11669
12152
  export {
11670
12153
  ENV_KEYS,
11671
12154
  DEFAULT_MODEL,
@@ -11680,6 +12163,8 @@ export {
11680
12163
  HealthMonitor,
11681
12164
  ZombieHunter,
11682
12165
  getLLMClient,
12166
+ getGlobalRequestCount,
12167
+ getGlobalTokenUsage,
11683
12168
  createContextExtractor,
11684
12169
  parseTurnNumbers,
11685
12170
  rotateTurnRecords,
@@ -11706,5 +12191,7 @@ export {
11706
12191
  formatChallengeAnalysis,
11707
12192
  setCurrentTurn,
11708
12193
  CoreAgent,
12194
+ getShellSupervisorLifecycleSnapshot,
12195
+ buildShellSupervisorLifecycleSection,
11709
12196
  CategorizedToolRegistry
11710
12197
  };