pentesting 0.72.8 → 0.72.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -48,20 +48,23 @@ import {
48
48
  WORKSPACE,
49
49
  WORK_DIR,
50
50
  cleanupAllProcesses,
51
- clearCommandEventEmitter,
52
- clearInputHandler,
51
+ clearActiveSessionRuntime,
53
52
  clearWorkspace,
53
+ createSessionRuntime,
54
54
  createTempFile,
55
55
  debugLog,
56
56
  ensureDirExists,
57
57
  flowLog,
58
58
  generateId,
59
59
  generatePrefixedId,
60
+ getActiveSessionRuntime,
60
61
  getApiKey,
62
+ getApprovalMode,
61
63
  getBaseUrl,
62
64
  getErrorMessage,
63
65
  getModel,
64
66
  getProcessOutput,
67
+ getScopeMode,
65
68
  getSearchApiKey,
66
69
  getSearchApiUrl,
67
70
  getThinkingBudget,
@@ -78,14 +81,13 @@ import {
78
81
  runCommand,
79
82
  saveState,
80
83
  sendToProcess,
81
- setCommandEventEmitter,
82
- setInputHandler,
84
+ setActiveSessionRuntime,
83
85
  setTorEnabled,
84
86
  startBackgroundProcess,
85
87
  stopBackgroundProcess,
86
88
  validateRequiredConfig,
87
89
  writeFileContent
88
- } from "./chunk-6YWYFB6E.js";
90
+ } from "./chunk-SLDFXMHL.js";
89
91
  import {
90
92
  DETECTION_PATTERNS,
91
93
  EXIT_CODES,
@@ -101,11 +103,10 @@ import {
101
103
  getProcessEventLog,
102
104
  getPromptBuilderConfig,
103
105
  getPromptSources,
104
- getUserInputQueueConfig,
105
106
  llmNodeCooldownPolicy,
106
107
  llmNodeOutputParsing,
107
108
  llmNodeSystemPrompt
108
- } from "./chunk-74KL4OOU.js";
109
+ } from "./chunk-GHJPYI4S.js";
109
110
 
110
111
  // src/platform/tui/main.tsx
111
112
  import chalk5 from "chalk";
@@ -511,35 +512,35 @@ function prioritizeChains(chains) {
511
512
  );
512
513
  return unique.slice(0, GRAPH_LIMITS.MAX_CHAINS);
513
514
  }
514
- function dfsChains(nodeId, path2, pathEdges, visited, ctx) {
515
+ function dfsChains(nodeId, path4, pathEdges, visited, ctx) {
515
516
  const { nodes, edges, goalTypes, results } = ctx;
516
517
  const node = nodes.get(nodeId);
517
- if (!node || visited.has(nodeId) || path2.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH || node.status === NODE_STATUS.FAILED) {
518
+ if (!node || visited.has(nodeId) || path4.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH || node.status === NODE_STATUS.FAILED) {
518
519
  return;
519
520
  }
520
521
  visited.add(nodeId);
521
- path2.push(node);
522
- if (isGoalReached(node, goalTypes, path2)) {
523
- results.push(buildChain(path2, pathEdges, node));
522
+ path4.push(node);
523
+ if (isGoalReached(node, goalTypes, path4)) {
524
+ results.push(buildChain(path4, pathEdges, node));
524
525
  }
525
526
  const outEdges = edges.filter((e) => e.from === nodeId && e.status !== EDGE_STATUS.FAILED);
526
527
  for (const edge of outEdges) {
527
- dfsChains(edge.to, path2, [...pathEdges, edge], visited, ctx);
528
+ dfsChains(edge.to, path4, [...pathEdges, edge], visited, ctx);
528
529
  }
529
- path2.pop();
530
+ path4.pop();
530
531
  visited.delete(nodeId);
531
532
  }
532
- function isGoalReached(node, goalTypes, path2) {
533
- return goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path2.length > 1;
533
+ function isGoalReached(node, goalTypes, path4) {
534
+ return goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path4.length > 1;
534
535
  }
535
- function buildChain(path2, edges, targetNode) {
536
+ function buildChain(path4, edges, targetNode) {
536
537
  return {
537
- steps: [...path2],
538
+ steps: [...path4],
538
539
  edges: [...edges],
539
- description: path2.map((n) => n.label).join(" \u2192 "),
540
+ description: path4.map((n) => n.label).join(" \u2192 "),
540
541
  probability: edges.reduce((acc, e) => acc * e.confidence, 1),
541
542
  impact: estimateImpact(targetNode),
542
- length: path2.length
543
+ length: path4.length
543
544
  };
544
545
  }
545
546
  function estimateImpact(node) {
@@ -1001,8 +1002,8 @@ var AttackGraphStore = class {
1001
1002
  * Record a failed path with IMP-1 cap.
1002
1003
  * Keeps only the most recent MAX_FAILED_PATHS entries.
1003
1004
  */
1004
- recordFailedPath(path2) {
1005
- this.failedPaths.push(path2);
1005
+ recordFailedPath(path4) {
1006
+ this.failedPaths.push(path4);
1006
1007
  if (this.failedPaths.length > AGENT_LIMITS.MAX_FAILED_PATHS) {
1007
1008
  this.failedPaths = this.failedPaths.slice(-AGENT_LIMITS.MAX_FAILED_PATHS);
1008
1009
  }
@@ -1079,7 +1080,7 @@ var AttackGraph = class {
1079
1080
  */
1080
1081
  markFailed(nodeId, reason) {
1081
1082
  markNodeFailedInGraph(this.store.getNodesMap(), this.store.getEdgesList(), nodeId, reason, {
1082
- onFailedPath: (path2) => this.store.recordFailedPath(path2)
1083
+ onFailedPath: (path4) => this.store.recordFailedPath(path4)
1083
1084
  });
1084
1085
  }
1085
1086
  /**
@@ -1093,7 +1094,7 @@ var AttackGraph = class {
1093
1094
  */
1094
1095
  markEdgeFailed(fromId, toId, reason) {
1095
1096
  markEdgeFailedInList(this.store.getEdgesList(), fromId, toId, reason, {
1096
- onFailedPath: (path2) => this.store.recordFailedPath(path2)
1097
+ onFailedPath: (path4) => this.store.recordFailedPath(path4)
1097
1098
  });
1098
1099
  }
1099
1100
  // ─── Domain-Specific Registration ───────────────────────────
@@ -1233,44 +1234,83 @@ var EPISODIC_EVENT_TYPES = {
1233
1234
  };
1234
1235
 
1235
1236
  // src/shared/utils/agent-memory/fingerprint.ts
1237
+ var WORDLIST_FLAGS = /* @__PURE__ */ new Set(["-w", "-P", "-U", "-L", "--wordlist", "--password", "--passwords", "--username", "--usernames"]);
1238
+ var PORT_FLAGS = /* @__PURE__ */ new Set(["-p", "--port"]);
1239
+ var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["python", "python3", "bash", "sh", "zsh", "node", "ruby", "perl"]);
1240
+ function stripWrappingQuotes(value) {
1241
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1242
+ return value.slice(1, -1);
1243
+ }
1244
+ return value;
1245
+ }
1246
+ function tokenizeCommand(command) {
1247
+ const matches = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
1248
+ return matches.map(stripWrappingQuotes);
1249
+ }
1250
+ function parseOptionTokens(tokens) {
1251
+ const options = [];
1252
+ const positionals = [];
1253
+ for (let i = 1; i < tokens.length; i++) {
1254
+ const token = tokens[i];
1255
+ if (token.startsWith("--")) {
1256
+ const eqIndex = token.indexOf("=");
1257
+ if (eqIndex !== -1) {
1258
+ options.push({ flag: token.slice(0, eqIndex), value: token.slice(eqIndex + 1) });
1259
+ continue;
1260
+ }
1261
+ const next = tokens[i + 1];
1262
+ if (next && !next.startsWith("-")) {
1263
+ options.push({ flag: token, value: next });
1264
+ i++;
1265
+ } else {
1266
+ options.push({ flag: token, value: "" });
1267
+ }
1268
+ continue;
1269
+ }
1270
+ if (token.startsWith("-") && token.length > 1) {
1271
+ const next = tokens[i + 1];
1272
+ const expectsValue = /^-[A-Za-z]$/.test(token) || /^-[a-z]{2,}$/.test(token);
1273
+ const canConsumeNext = expectsValue && next && !next.startsWith("-");
1274
+ if (canConsumeNext) {
1275
+ options.push({ flag: token, value: next });
1276
+ i++;
1277
+ } else {
1278
+ options.push({ flag: token, value: "" });
1279
+ }
1280
+ continue;
1281
+ }
1282
+ positionals.push(token);
1283
+ }
1284
+ return { options, positionals };
1285
+ }
1286
+ function findOptionValue(options, flags) {
1287
+ return options.find((option) => flags.has(option.flag))?.value || "";
1288
+ }
1289
+ function buildOptionSignature(options, positionals, effectiveTool) {
1290
+ const entries = options.filter((option) => !WORDLIST_FLAGS.has(option.flag) && !PORT_FLAGS.has(option.flag)).map((option) => option.value ? `${option.flag}=${option.value}` : option.flag);
1291
+ if (SCRIPT_RUNNERS.has(effectiveTool) && positionals.length > 0) {
1292
+ entries.push(`script=${positionals[0]}`);
1293
+ }
1294
+ return Array.from(new Set(entries)).sort().join(" ");
1295
+ }
1236
1296
  function extractFingerprint(tool, command) {
1237
1297
  const cmd = command || "";
1298
+ const tokens = tokenizeCommand(cmd);
1238
1299
  let effectiveTool = tool.toLowerCase();
1239
1300
  if (effectiveTool === "run_cmd" || effectiveTool === "run_background") {
1240
- const firstWord = cmd.trim().split(/\s+/)[0];
1301
+ const firstWord = tokens[0];
1241
1302
  if (firstWord && !firstWord.startsWith("-")) {
1242
1303
  effectiveTool = firstWord.toLowerCase();
1243
1304
  }
1244
1305
  }
1306
+ const { options, positionals } = parseOptionTokens(tokens);
1245
1307
  const targetMatch = cmd.match(
1246
1308
  /(?::\/\/|@)([\w.\-]+(?::\d+)?)|\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d+)?\b/
1247
1309
  );
1248
1310
  const target = targetMatch?.[1] || targetMatch?.[2] || "";
1249
- let wordlistMatch = cmd.match(/(?:-[wPUL]\s+)([^\s]+)/);
1250
- if (!wordlistMatch) {
1251
- wordlistMatch = cmd.match(/(?:--(?:wordlist|passwords?|usernames?)(?:=|\s+))([^\s]+)/i);
1252
- }
1253
- const wordlist = wordlistMatch?.[1] || "";
1254
- const portMatch = cmd.match(/(?:-p\s+|--port(?:=|\s+))(\S+)/);
1255
- const port = portMatch?.[1] || "";
1256
- const flagPatterns = [
1257
- /--level(?:=|\s+)(\S+)/i,
1258
- // sqlmap
1259
- /--risk(?:=|\s+)(\S+)/i,
1260
- // sqlmap
1261
- /-s([VSTCAUOPMX]+)/,
1262
- // nmap scan type
1263
- /--script(?:=|\s+)(\S+)/i,
1264
- // nmap scripts
1265
- /-[el]\s+(\S+)/i,
1266
- // hydra login/password single
1267
- /--method(?:=|\s+)(\S+)/i
1268
- // HTTP method
1269
- ];
1270
- const flags = flagPatterns.map((p) => {
1271
- const m = cmd.match(p);
1272
- return m?.[0]?.trim() || "";
1273
- }).filter(Boolean).join(" ");
1311
+ const wordlist = findOptionValue(options, WORDLIST_FLAGS);
1312
+ const port = findOptionValue(options, PORT_FLAGS);
1313
+ const flags = buildOptionSignature(options, positionals, effectiveTool);
1274
1314
  return { tool: effectiveTool, target, wordlist, flags, port };
1275
1315
  }
1276
1316
  function fingerprintsMatch(a, b) {
@@ -1978,10 +2018,25 @@ var DynamicTechniqueLibrary = class {
1978
2018
  };
1979
2019
 
1980
2020
  // src/engine/state/models/engagement.ts
2021
+ function safeList(values) {
2022
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
2023
+ }
2024
+ function normalizeScope(scope) {
2025
+ return {
2026
+ allowedCidrs: safeList(scope.allowedCidrs),
2027
+ allowedDomains: safeList(scope.allowedDomains),
2028
+ exclusions: safeList(scope.exclusions),
2029
+ isDOSAllowed: Boolean(scope.isDOSAllowed),
2030
+ isSocialAllowed: Boolean(scope.isSocialAllowed)
2031
+ };
2032
+ }
1981
2033
  var EngagementState = class {
1982
2034
  engagement = null;
1983
2035
  set(engagement) {
1984
- this.engagement = engagement;
2036
+ this.engagement = {
2037
+ ...engagement,
2038
+ scope: normalizeScope(engagement.scope)
2039
+ };
1985
2040
  }
1986
2041
  get() {
1987
2042
  return this.engagement;
@@ -1990,14 +2045,15 @@ var EngagementState = class {
1990
2045
  return this.engagement?.scope || null;
1991
2046
  }
1992
2047
  setScope(scope, currentPhase) {
2048
+ const normalizedScope = normalizeScope(scope);
1993
2049
  if (this.engagement) {
1994
- this.engagement.scope = scope;
2050
+ this.engagement.scope = normalizedScope;
1995
2051
  } else {
1996
2052
  this.engagement = {
1997
2053
  id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
1998
2054
  name: DEFAULTS.ENGAGEMENT_NAME,
1999
2055
  client: DEFAULTS.ENGAGEMENT_CLIENT,
2000
- scope,
2056
+ scope: normalizedScope,
2001
2057
  phase: currentPhase,
2002
2058
  startedAt: Date.now()
2003
2059
  };
@@ -2302,9 +2358,9 @@ var DIRECTIVES = {
2302
2358
  ].join("\n"),
2303
2359
  SPRINT: (ctx) => [
2304
2360
  `\u26A1 SPRINT MODE [${ctx.pctStr} elapsed, ${ctx.remainStr} of ${ctx.totalStr} remaining]:`,
2305
- "- RustScan first for fast port discovery \u2192 then nmap -Pn -sV -sC on found ports",
2306
- "- ALWAYS use nmap -Pn (no exceptions \u2014 firewalls block ICMP)",
2307
- "- Run ALL scans in parallel (rustscan, nmap UDP, web fuzzing, CVE search)",
2361
+ "- Start with the fastest broad discovery method available, then deepen only on confirmed surfaces",
2362
+ "- If host discovery looks filtered, prefer reconnaissance that does not depend on ICMP assumptions",
2363
+ "- Run independent discovery tasks in parallel (network, web, OSINT, version intel)",
2308
2364
  "- Try default/weak credentials on every discovered service IMMEDIATELY",
2309
2365
  "- Check for exposed files (.env, .git, backup.sql, phpinfo)",
2310
2366
  "- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
@@ -2527,7 +2583,7 @@ var SharedState = class {
2527
2583
  /**
2528
2584
  * Last turn's triage memo (Triager ⑧).
2529
2585
  * Structured priority classification of tool results.
2530
- * WHY: Reflector and SummaryRegenerator read this for richer delta analysis.
2586
+ * WHY: Reflector and MemorySynth read this for richer delta analysis.
2531
2587
  * Injected into prompt as <triage-memo> for the main LLM.
2532
2588
  */
2533
2589
  lastTriageMemo = "";
@@ -2822,8 +2878,9 @@ var FILE_EXTENSIONS_TO_SKIP = /* @__PURE__ */ new Set([
2822
2878
  "dec"
2823
2879
  ]);
2824
2880
  var ScopeGuard = class {
2825
- constructor(state) {
2881
+ constructor(state, mode = getScopeMode()) {
2826
2882
  this.state = state;
2883
+ this.mode = mode;
2827
2884
  }
2828
2885
  /**
2829
2886
  * AI-Native: always allow.
@@ -2831,8 +2888,28 @@ var ScopeGuard = class {
2831
2888
  * The agent should attempt everything; humans and infrastructure judge the results.
2832
2889
  * isTargetInScope() exists for advisory reporting — not for blocking.
2833
2890
  */
2834
- check(_toolCall) {
2835
- return { isAllowed: true };
2891
+ check(toolCall) {
2892
+ if (this.mode === "advisory") {
2893
+ return { isAllowed: true };
2894
+ }
2895
+ const scope = this.state.getScope();
2896
+ const serializedInput = JSON.stringify(toolCall.input);
2897
+ const targets = Array.from(this.extractTargets(serializedInput));
2898
+ if (targets.length === 0) return { isAllowed: true };
2899
+ if (!scope) {
2900
+ return {
2901
+ isAllowed: false,
2902
+ reason: "Scope enforcement is enabled but no scope is configured.",
2903
+ violations: targets
2904
+ };
2905
+ }
2906
+ const violations = targets.filter((target) => !this.isTargetInScope(target));
2907
+ if (violations.length === 0) return { isAllowed: true };
2908
+ return {
2909
+ isAllowed: false,
2910
+ reason: `Out-of-scope target(s): ${violations.join(", ")}`,
2911
+ violations
2912
+ };
2836
2913
  }
2837
2914
  /**
2838
2915
  * Advisory check: is a specific target in scope?
@@ -2841,13 +2918,16 @@ var ScopeGuard = class {
2841
2918
  isTargetInScope(target) {
2842
2919
  const scope = this.state.getScope();
2843
2920
  if (!scope) return false;
2844
- if (scope.exclusions.some((e) => this.matchesDomain(target, e))) {
2921
+ const exclusions = Array.isArray(scope.exclusions) ? scope.exclusions : [];
2922
+ const allowedDomains = Array.isArray(scope.allowedDomains) ? scope.allowedDomains : [];
2923
+ const allowedCidrs = Array.isArray(scope.allowedCidrs) ? scope.allowedCidrs : [];
2924
+ if (exclusions.some((e) => this.matchesDomain(target, e))) {
2845
2925
  return false;
2846
2926
  }
2847
- if (scope.allowedDomains.some((d) => this.matchesDomain(target, d))) {
2927
+ if (allowedDomains.some((d) => this.matchesDomain(target, d))) {
2848
2928
  return true;
2849
2929
  }
2850
- if (scope.allowedCidrs.some((c) => this.matchesCidr(target, c))) {
2930
+ if (allowedCidrs.some((c) => this.matchesCidr(target, c))) {
2851
2931
  return true;
2852
2932
  }
2853
2933
  return false;
@@ -2916,7 +2996,7 @@ var ScopeGuard = class {
2916
2996
  const lower = match.toLowerCase();
2917
2997
  const ext = lower.split(".").pop() || "";
2918
2998
  if (FILE_EXTENSIONS_TO_SKIP.has(ext)) continue;
2919
- if (/^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/i.test(match)) continue;
2999
+ if (/^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/i.test(match) && ext.length > 3) continue;
2920
3000
  targets.add(lower);
2921
3001
  }
2922
3002
  return targets;
@@ -2925,8 +3005,9 @@ var ScopeGuard = class {
2925
3005
 
2926
3006
  // src/engine/approval.ts
2927
3007
  var ApprovalGate = class {
2928
- constructor(shouldAutoApprove = false) {
3008
+ constructor(shouldAutoApprove = false, mode = getApprovalMode()) {
2929
3009
  this.shouldAutoApprove = shouldAutoApprove;
3010
+ this.mode = mode;
2930
3011
  }
2931
3012
  /**
2932
3013
  * Set auto-approve mode
@@ -2945,7 +3026,13 @@ var ApprovalGate = class {
2945
3026
  * The agent must have unrestricted freedom to exploit targets.
2946
3027
  * Category/level system retained for audit trail only.
2947
3028
  */
2948
- async request(_toolCall) {
3029
+ async request(toolCall) {
3030
+ if (this.mode === "require_auto_approve" && !this.shouldAutoApprove) {
3031
+ return {
3032
+ isApproved: false,
3033
+ reason: `Approval required for ${toolCall.name} while PENTEST_APPROVAL_MODE=require_auto_approve.`
3034
+ };
3035
+ }
2949
3036
  return { isApproved: true };
2950
3037
  }
2951
3038
  };
@@ -3284,22 +3371,16 @@ var systemTools = [
3284
3371
  ];
3285
3372
 
3286
3373
  // src/engine/tools/agents.ts
3287
- var globalCredentialHandler = null;
3288
- function setCredentialHandler(handler) {
3289
- globalCredentialHandler = handler;
3290
- }
3291
- function clearCredentialHandler() {
3292
- globalCredentialHandler = null;
3293
- }
3294
3374
  async function requestCredential(request) {
3295
- if (!globalCredentialHandler) {
3375
+ const credentialHandler = getActiveSessionRuntime()?.getCredentialHandler();
3376
+ if (!credentialHandler) {
3296
3377
  return {
3297
3378
  success: false,
3298
3379
  output: `No credential handler available. Requested: ${request.type} - ${request.prompt}`
3299
3380
  };
3300
3381
  }
3301
3382
  try {
3302
- const value = await globalCredentialHandler(request);
3383
+ const value = await credentialHandler(request);
3303
3384
  if (value === null) {
3304
3385
  if (request.isOptional) {
3305
3386
  return {
@@ -6762,7 +6843,7 @@ import { existsSync as existsSync3, readdirSync, rmSync } from "fs";
6762
6843
  import { join as join6 } from "path";
6763
6844
  function parseTurnNumbers(turnsDir) {
6764
6845
  if (!existsSync3(turnsDir)) return [];
6765
- return readdirSync(turnsDir).filter((e) => /^\d+\.md$/.test(e)).map((e) => Number(e.replace(".md", "")));
6846
+ return readdirSync(turnsDir).filter((e) => /^\d+-memory\.md$/.test(e)).map((e) => Number(e.replace("-memory.md", "")));
6766
6847
  }
6767
6848
  function rotateTurnRecords() {
6768
6849
  try {
@@ -6773,7 +6854,7 @@ function rotateTurnRecords() {
6773
6854
  const toDel = turns.slice(0, turns.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
6774
6855
  for (const n of toDel) {
6775
6856
  try {
6776
- rmSync(join6(turnsDir, `${n}.md`), { force: true });
6857
+ rmSync(join6(turnsDir, `${n}-memory.md`), { force: true });
6777
6858
  } catch {
6778
6859
  }
6779
6860
  }
@@ -6832,6 +6913,40 @@ function getRecentTurnContents(count) {
6832
6913
 
6833
6914
  // src/shared/utils/journal/summary.ts
6834
6915
  import { writeFileSync as writeFileSync4 } from "fs";
6916
+
6917
+ // src/shared/utils/journal/memory-file.ts
6918
+ function yamlList(values) {
6919
+ if (values.length === 0) return " - none";
6920
+ return values.map((value) => ` - ${value}`).join("\n");
6921
+ }
6922
+ function buildTurnMemoryDocument(input) {
6923
+ const body = input.memoryBody.trim();
6924
+ return [
6925
+ "---",
6926
+ `turn: ${input.turn}`,
6927
+ `phase: ${input.phase}`,
6928
+ "file_kind: turn_memory",
6929
+ `written_by: ${input.writtenBy}`,
6930
+ "input_sources:",
6931
+ yamlList(input.inputSources),
6932
+ "notes_included:",
6933
+ yamlList(input.notesIncluded),
6934
+ `generated_at: ${input.generatedAt}`,
6935
+ "---",
6936
+ "",
6937
+ `# Turn Memory ${input.turn}`,
6938
+ "",
6939
+ "## Provenance",
6940
+ `written_by: ${input.writtenBy}`,
6941
+ `input_sources: ${input.inputSources.join(", ") || "none"}`,
6942
+ `notes_included: ${input.notesIncluded.join(", ") || "none"}`,
6943
+ "",
6944
+ "## Memory",
6945
+ body
6946
+ ].join("\n");
6947
+ }
6948
+
6949
+ // src/shared/utils/journal/summary.ts
6835
6950
  function regenerateJournalSummary() {
6836
6951
  try {
6837
6952
  const contents = getRecentTurnContents(MEMORY_LIMITS.MAX_TURN_ENTRIES);
@@ -6840,10 +6955,19 @@ function regenerateJournalSummary() {
6840
6955
  if (currentTurn < 1) return;
6841
6956
  ensureDirExists(WORKSPACE.TURNS);
6842
6957
  const summary = contents.slice(-5).join("\n\n---\n\n");
6843
- writeFileSync4(WORKSPACE.turnPath(currentTurn), summary, "utf-8");
6958
+ const memoryFile = buildTurnMemoryDocument({
6959
+ turn: currentTurn,
6960
+ phase: "unknown",
6961
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6962
+ writtenBy: "fallback_concat",
6963
+ inputSources: ["recent_turn_memory_files"],
6964
+ notesIncluded: ["fallback_concat"],
6965
+ memoryBody: summary
6966
+ });
6967
+ writeFileSync4(WORKSPACE.turnPath(currentTurn), memoryFile, "utf-8");
6844
6968
  debugLog("general", "Fallback journal summary written", {
6845
6969
  turns: contents.length,
6846
- target: `turns/${currentTurn}.md`
6970
+ target: `turns/${currentTurn}-memory.md`
6847
6971
  });
6848
6972
  } catch (err) {
6849
6973
  debugLog("general", "Failed to regenerate journal summary", { error: String(err) });
@@ -6895,9 +7019,18 @@ var STATUS_ICONS2 = {
6895
7019
  };
6896
7020
 
6897
7021
  // src/shared/utils/journal/formatters/record.ts
7022
+ function safeList2(values) {
7023
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
7024
+ }
6898
7025
  function formatTurnRecord(input) {
6899
7026
  const { turn, timestamp, phase, tools, memo: memo13, reflection } = input;
6900
7027
  const time = timestamp.slice(0, 19).replace("T", " ");
7028
+ const keyFindings = safeList2(memo13.keyFindings);
7029
+ const credentials = safeList2(memo13.credentials);
7030
+ const attackVectors = safeList2(memo13.attackVectors);
7031
+ const failures = safeList2(memo13.failures);
7032
+ const suspicions = safeList2(memo13.suspicions);
7033
+ const nextSteps = safeList2(memo13.nextSteps);
6901
7034
  const sections = [];
6902
7035
  sections.push(`# Turn ${turn} | ${time} | Phase: ${phase}`);
6903
7036
  sections.push("");
@@ -6913,25 +7046,25 @@ function formatTurnRecord(input) {
6913
7046
  }
6914
7047
  sections.push("");
6915
7048
  sections.push(`## ${TURN_SECTIONS.KEY_INSIGHTS}`);
6916
- if (memo13.keyFindings.length > 0) {
6917
- for (const f of memo13.keyFindings) sections.push(`- ${INSIGHT_TAGS.DISCOVERED}: ${f}`);
7049
+ if (keyFindings.length > 0) {
7050
+ for (const f of keyFindings) sections.push(`- ${INSIGHT_TAGS.DISCOVERED}: ${f}`);
6918
7051
  }
6919
- if (memo13.credentials.length > 0) {
6920
- for (const c of memo13.credentials) sections.push(`- ${INSIGHT_TAGS.CREDENTIAL}: ${c}`);
7052
+ if (credentials.length > 0) {
7053
+ for (const c of credentials) sections.push(`- ${INSIGHT_TAGS.CREDENTIAL}: ${c}`);
6921
7054
  }
6922
- if (memo13.attackVectors.length > 0) {
6923
- for (const v of memo13.attackVectors) sections.push(`- ${INSIGHT_TAGS.CONFIRMED}: ${v}`);
7055
+ if (attackVectors.length > 0) {
7056
+ for (const v of attackVectors) sections.push(`- ${INSIGHT_TAGS.CONFIRMED}: ${v}`);
6924
7057
  }
6925
- if (memo13.failures.length > 0) {
6926
- for (const f of memo13.failures) sections.push(`- ${INSIGHT_TAGS.DEAD_END}: ${f}`);
7058
+ if (failures.length > 0) {
7059
+ for (const f of failures) sections.push(`- ${INSIGHT_TAGS.DEAD_END}: ${f}`);
6927
7060
  }
6928
- if (memo13.suspicions.length > 0) {
6929
- for (const s of memo13.suspicions) sections.push(`- ${INSIGHT_TAGS.SUSPICIOUS}: ${s}`);
7061
+ if (suspicions.length > 0) {
7062
+ for (const s of suspicions) sections.push(`- ${INSIGHT_TAGS.SUSPICIOUS}: ${s}`);
6930
7063
  }
6931
- if (memo13.nextSteps.length > 0) {
6932
- for (const n of memo13.nextSteps) sections.push(`- ${INSIGHT_TAGS.NEXT}: ${n}`);
7064
+ if (nextSteps.length > 0) {
7065
+ for (const n of nextSteps) sections.push(`- ${INSIGHT_TAGS.NEXT}: ${n}`);
6933
7066
  }
6934
- if (memo13.keyFindings.length === 0 && memo13.failures.length === 0 && memo13.credentials.length === 0) {
7067
+ if (keyFindings.length === 0 && failures.length === 0 && credentials.length === 0) {
6935
7068
  sections.push(`- ${TURN_EMPTY_MESSAGES.NO_INSIGHTS}`);
6936
7069
  }
6937
7070
  sections.push("");
@@ -6942,6 +7075,9 @@ function formatTurnRecord(input) {
6942
7075
  }
6943
7076
 
6944
7077
  // src/shared/utils/journal/formatters/reflection.ts
7078
+ function safeList3(values) {
7079
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
7080
+ }
6945
7081
  function formatReflectionInput(input) {
6946
7082
  const { tools, memo: memo13, phase, triageMemo } = input;
6947
7083
  const parts = [
@@ -6962,10 +7098,14 @@ function formatReflectionInput(input) {
6962
7098
  parts.push("");
6963
7099
  parts.push("Analyst \uCD94\uCD9C \uBA54\uBAA8:");
6964
7100
  if (memo13) {
6965
- if (memo13.keyFindings.length > 0) parts.push(` \uBC1C\uACAC: ${memo13.keyFindings.join(", ")}`);
6966
- if (memo13.credentials.length > 0) parts.push(` \uD06C\uB808\uB374\uC15C: ${memo13.credentials.join(", ")}`);
6967
- if (memo13.failures.length > 0) parts.push(` \uC2E4\uD328: ${memo13.failures.join(", ")}`);
6968
- if (memo13.suspicions.length > 0) parts.push(` \uC758\uC2EC: ${memo13.suspicions.join(", ")}`);
7101
+ const keyFindings = safeList3(memo13.keyFindings);
7102
+ const credentials = safeList3(memo13.credentials);
7103
+ const failures = safeList3(memo13.failures);
7104
+ const suspicions = safeList3(memo13.suspicions);
7105
+ if (keyFindings.length > 0) parts.push(` \uBC1C\uACAC: ${keyFindings.join(", ")}`);
7106
+ if (credentials.length > 0) parts.push(` \uD06C\uB808\uB374\uC15C: ${credentials.join(", ")}`);
7107
+ if (failures.length > 0) parts.push(` \uC2E4\uD328: ${failures.join(", ")}`);
7108
+ if (suspicions.length > 0) parts.push(` \uC758\uC2EC: ${suspicions.join(", ")}`);
6969
7109
  parts.push(` \uACF5\uACA9 \uAC00\uCE58: ${memo13.attackValue}`);
6970
7110
  } else {
6971
7111
  parts.push(" (\uBD84\uC11D \uACB0\uACFC \uC5C6\uC74C)");
@@ -6986,7 +7126,6 @@ var Reflector = class extends AuxiliaryLLMBase {
6986
7126
  formatInput(input) {
6987
7127
  return formatReflectionInput({
6988
7128
  tools: input.tools,
6989
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6990
7129
  memo: input.memo,
6991
7130
  phase: input.phase,
6992
7131
  triageMemo: input.triageMemo
@@ -7003,14 +7142,14 @@ function createReflector(llm) {
7003
7142
  return new Reflector(llm);
7004
7143
  }
7005
7144
 
7006
- // src/engine/auxiliary-llm/summary-regenerator.ts
7007
- var SummaryRegenerator = class extends AuxiliaryLLMBase {
7145
+ // src/engine/auxiliary-llm/memory-synth.ts
7146
+ var MemorySynth = class extends AuxiliaryLLMBase {
7008
7147
  constructor(llm) {
7009
- super(llm, { systemPrompt: llmNodeSystemPrompt("summary_regenerator"), logErrors: true });
7148
+ super(llm, { systemPrompt: llmNodeSystemPrompt("memory_synth"), logErrors: true });
7010
7149
  }
7011
7150
  formatInput(input) {
7012
7151
  if (input.existingSummary) {
7013
- return `Previous summary:
7152
+ return `Previous memory:
7014
7153
  ${input.existingSummary}
7015
7154
 
7016
7155
  Current turn:
@@ -7026,8 +7165,8 @@ ${input.turnData}`;
7026
7165
  return { summary: "", success: false };
7027
7166
  }
7028
7167
  };
7029
- function createSummaryRegenerator(llm) {
7030
- return new SummaryRegenerator(llm);
7168
+ function createMemorySynth(llm) {
7169
+ return new MemorySynth(llm);
7031
7170
  }
7032
7171
 
7033
7172
  // src/engine/auxiliary-llm/playbook-synthesizer.ts
@@ -7142,6 +7281,45 @@ function createTriager(llm) {
7142
7281
  return new Triager(llm);
7143
7282
  }
7144
7283
 
7284
+ // src/shared/utils/policy/document.ts
7285
+ import fs from "fs";
7286
+ import path from "path";
7287
+ var DEFAULT_POLICY_DOCUMENT = `# Policy Memory
7288
+
7289
+ No persistent user policy has been recorded yet.
7290
+ Store only durable constraints, sensitive handling rules, and reusable engagement guidance here.
7291
+ `;
7292
+ function getPolicyDocumentPath() {
7293
+ return WORKSPACE.POLICY;
7294
+ }
7295
+ function ensurePolicyDocument() {
7296
+ const filePath = getPolicyDocumentPath();
7297
+ const dirPath = path.dirname(filePath);
7298
+ if (!fs.existsSync(dirPath)) {
7299
+ fs.mkdirSync(dirPath, { recursive: true });
7300
+ }
7301
+ if (!fs.existsSync(filePath)) {
7302
+ fs.writeFileSync(filePath, DEFAULT_POLICY_DOCUMENT, "utf-8");
7303
+ }
7304
+ return filePath;
7305
+ }
7306
+ function readPolicyDocument() {
7307
+ try {
7308
+ ensurePolicyDocument();
7309
+ return fs.readFileSync(getPolicyDocumentPath(), "utf-8").trim();
7310
+ } catch {
7311
+ return "";
7312
+ }
7313
+ }
7314
+ function writePolicyDocument(content) {
7315
+ ensurePolicyDocument();
7316
+ fs.writeFileSync(
7317
+ getPolicyDocumentPath(),
7318
+ content.trim() || DEFAULT_POLICY_DOCUMENT,
7319
+ "utf-8"
7320
+ );
7321
+ }
7322
+
7145
7323
  // src/engine/auxiliary-llm/strategist.ts
7146
7324
  var Strategist = class extends AuxiliaryLLMBase {
7147
7325
  lastDirective = null;
@@ -7167,6 +7345,8 @@ var Strategist = class extends AuxiliaryLLMBase {
7167
7345
  if (journalSummary) sections.push("", "## Session Journal (past turns summary)", journalSummary);
7168
7346
  } catch {
7169
7347
  }
7348
+ const policyDocument = readPolicyDocument();
7349
+ if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
7170
7350
  const graph = state.attackGraph.toPrompt();
7171
7351
  if (graph) sections.push("", "## Attack Graph", graph);
7172
7352
  const timeline = state.episodicMemory.toPrompt();
@@ -7300,6 +7480,74 @@ function createStrategist(llm) {
7300
7480
  return new Strategist(llm);
7301
7481
  }
7302
7482
 
7483
+ // src/engine/auxiliary-llm/input-processor.ts
7484
+ function extractTag(content, tag) {
7485
+ const match = content.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "i"));
7486
+ return match?.[1]?.trim() ?? "";
7487
+ }
7488
+ function parseBoolean(value) {
7489
+ return value.trim().toLowerCase() === "true";
7490
+ }
7491
+ var InputProcessor = class extends AuxiliaryLLMBase {
7492
+ constructor(llm) {
7493
+ super(llm, {
7494
+ systemPrompt: llmNodeSystemPrompt("input_processor"),
7495
+ logErrors: true
7496
+ });
7497
+ }
7498
+ formatInput(input) {
7499
+ return [
7500
+ "<input-processor-context>",
7501
+ `<has-active-engagement>${String(input.hasActiveEngagement)}</has-active-engagement>`,
7502
+ `<current-objective>${input.currentObjective || "none"}</current-objective>`,
7503
+ "<existing-policy>",
7504
+ input.existingPolicy || "No existing policy document.",
7505
+ "</existing-policy>",
7506
+ "<raw-user-input>",
7507
+ input.rawInput,
7508
+ "</raw-user-input>",
7509
+ "</input-processor-context>"
7510
+ ].join("\n");
7511
+ }
7512
+ parseOutput(response) {
7513
+ const shouldForwardToMain = parseBoolean(extractTag(response, "should_forward_to_main"));
7514
+ const shouldWritePolicy = parseBoolean(extractTag(response, "should_write_policy"));
7515
+ const forwardedInput = extractTag(response, "forwarded_input");
7516
+ const directResponse = extractTag(response, "direct_response");
7517
+ const policyDocument = extractTag(response, "policy_document_markdown");
7518
+ const policyUpdateSummary = extractTag(response, "policy_update_summary");
7519
+ const insightSummary = extractTag(response, "insight_summary");
7520
+ return {
7521
+ shouldForwardToMain,
7522
+ forwardedInput,
7523
+ directResponse,
7524
+ shouldWritePolicy,
7525
+ policyDocument,
7526
+ policyUpdateSummary,
7527
+ insightSummary,
7528
+ success: true
7529
+ };
7530
+ }
7531
+ createFailureOutput(_reason) {
7532
+ return {
7533
+ shouldForwardToMain: true,
7534
+ forwardedInput: "",
7535
+ directResponse: "",
7536
+ shouldWritePolicy: false,
7537
+ policyDocument: "",
7538
+ policyUpdateSummary: "",
7539
+ insightSummary: "",
7540
+ success: false
7541
+ };
7542
+ }
7543
+ handleEmptyResponse() {
7544
+ return this.createFailureOutput("Empty response from LLM");
7545
+ }
7546
+ };
7547
+ function createInputProcessor(llm) {
7548
+ return new InputProcessor(llm);
7549
+ }
7550
+
7303
7551
  // src/domains/engagement/mission.ts
7304
7552
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
7305
7553
  import { join as join7 } from "path";
@@ -9769,8 +10017,8 @@ Install: apt-get install binwalk` };
9769
10017
  if (tryPassword(String(password))) {
9770
10018
  results.push(`Extracted with password: "${password}"`);
9771
10019
  try {
9772
- const { readFileSync: readFileSync5 } = await import("fs");
9773
- results.push(`Content: ${readFileSync5(outFile, "utf8").slice(0, 1e3)}`);
10020
+ const { readFileSync: readFileSync6 } = await import("fs");
10021
+ results.push(`Content: ${readFileSync6(outFile, "utf8").slice(0, 1e3)}`);
9774
10022
  } catch {
9775
10023
  results.push(`Saved to: ${outFile}`);
9776
10024
  }
@@ -9785,8 +10033,8 @@ Install: apt-get install binwalk` };
9785
10033
  results.push(`Extracted with password: "${pwd}"`);
9786
10034
  found = true;
9787
10035
  try {
9788
- const { readFileSync: readFileSync5 } = await import("fs");
9789
- results.push(`Content: ${readFileSync5(outFile, "utf8").slice(0, 1e3)}`);
10036
+ const { readFileSync: readFileSync6 } = await import("fs");
10037
+ results.push(`Content: ${readFileSync6(outFile, "utf8").slice(0, 1e3)}`);
9790
10038
  } catch {
9791
10039
  results.push(`Saved to: ${outFile}`);
9792
10040
  }
@@ -10460,8 +10708,8 @@ function extractFuzzStructured(output) {
10460
10708
  for (const pattern of pathPatterns) {
10461
10709
  let match;
10462
10710
  while ((match = pattern.exec(output)) !== null) {
10463
- const path2 = match[1] || match[0];
10464
- const cleanPath = path2.replace(/\s.*$/, "").trim();
10711
+ const path4 = match[1] || match[0];
10712
+ const cleanPath = path4.replace(/\s.*$/, "").trim();
10465
10713
  if (cleanPath.startsWith("/") && !paths.includes(cleanPath)) {
10466
10714
  paths.push(cleanPath);
10467
10715
  }
@@ -10696,6 +10944,19 @@ function autoExtractStructured(toolName, output) {
10696
10944
 
10697
10945
  // src/engine/tool-registry/pipeline.ts
10698
10946
  var PRIVATE_IP_RE = /^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|127\.|0\.)/;
10947
+ var RECON_TRIGGER_TOOLS = /* @__PURE__ */ new Set([
10948
+ TOOL_NAMES.RUN_CMD,
10949
+ TOOL_NAMES.BROWSE_URL,
10950
+ TOOL_NAMES.PARSE_NMAP,
10951
+ TOOL_NAMES.WHOIS_LOOKUP,
10952
+ TOOL_NAMES.DNS_RECON,
10953
+ TOOL_NAMES.SUBDOMAIN_ENUM,
10954
+ TOOL_NAMES.HARVESTER,
10955
+ TOOL_NAMES.BINWALK_ANALYZE,
10956
+ TOOL_NAMES.EXIF_DATA,
10957
+ TOOL_NAMES.CHECKSEC,
10958
+ TOOL_NAMES.FILE_INFO
10959
+ ]);
10699
10960
  function handleOSINTIPs(state, ips, sourceTool) {
10700
10961
  const publicIPs = ips.filter((ip) => !PRIVATE_IP_RE.test(ip)).slice(0, 10);
10701
10962
  const registered = [];
@@ -10707,81 +10968,123 @@ function handleOSINTIPs(state, ips, sourceTool) {
10707
10968
  }
10708
10969
  }
10709
10970
  if (registered.length > 0) {
10710
- state.episodicMemory.record("tool_success", `OSINT auto-registered ${registered.length} new target(s): ${registered.slice(0, 3).join(", ")}`);
10971
+ state.episodicMemory.record(
10972
+ "tool_success",
10973
+ `OSINT auto-registered ${registered.length} new target(s): ${registered.slice(0, 3).join(", ")}`
10974
+ );
10711
10975
  }
10712
10976
  return `
10713
10977
  [AUTO-EXTRACTED] ${publicIPs.length} IP(s) discovered: ${publicIPs.slice(0, 5).join(", ")}`;
10714
10978
  }
10715
- var RECON_TRIGGER_TOOLS = /* @__PURE__ */ new Set([
10716
- TOOL_NAMES.RUN_CMD,
10717
- TOOL_NAMES.BROWSE_URL,
10718
- TOOL_NAMES.PARSE_NMAP,
10719
- // IMP-12: OSINT tools produce recon data that can identify challenge type
10720
- TOOL_NAMES.WHOIS_LOOKUP,
10721
- TOOL_NAMES.DNS_RECON,
10722
- TOOL_NAMES.SUBDOMAIN_ENUM,
10723
- TOOL_NAMES.HARVESTER,
10724
- // IMP-5: Forensics analysis output useful for challenge identification
10725
- TOOL_NAMES.BINWALK_ANALYZE,
10726
- TOOL_NAMES.EXIF_DATA,
10727
- // IMP-3: Binary analysis first step
10728
- TOOL_NAMES.CHECKSEC,
10729
- TOOL_NAMES.FILE_INFO
10730
- ]);
10731
- function runPostExecutionPipeline(state, toolCall, result) {
10732
- const command = String(toolCall.input.command || toolCall.input.url || toolCall.input.query || JSON.stringify(toolCall.input));
10733
- if (result.success) {
10734
- state.workingMemory.recordSuccess(toolCall.name, command, result.output || "");
10735
- } else {
10736
- state.workingMemory.recordFailure(toolCall.name, command, result.error || "Unknown error");
10737
- state.dynamicTechniques.recordFailure(command);
10738
- }
10739
- if (result.success && result.output) {
10740
- const structured = autoExtractStructured(toolCall.name, result.output);
10741
- if (structured) {
10742
- const additions = [];
10743
- if (structured.credentials?.length) {
10744
- additions.push(`
10745
- [AUTO-EXTRACTED] ${structured.credentials.length} credential(s) found`);
10746
- }
10747
- if (structured.vulnerabilities?.length) {
10748
- additions.push(`
10749
- [AUTO-EXTRACTED] ${structured.vulnerabilities.length} CVE reference(s) found`);
10750
- }
10751
- if (structured.binaryAnalysisSummary) {
10752
- additions.push(`
10979
+ function buildCommand(toolCall) {
10980
+ return String(
10981
+ toolCall.input.command || toolCall.input.url || toolCall.input.query || JSON.stringify(toolCall.input)
10982
+ );
10983
+ }
10984
+ var recordWorkingMemoryStep = {
10985
+ name: "working_memory_update",
10986
+ run(ctx) {
10987
+ if (ctx.result.success) {
10988
+ ctx.state.workingMemory.recordSuccess(ctx.toolCall.name, ctx.command, ctx.result.output || "");
10989
+ return;
10990
+ }
10991
+ ctx.state.workingMemory.recordFailure(
10992
+ ctx.toolCall.name,
10993
+ ctx.command,
10994
+ ctx.result.error || "Unknown error"
10995
+ );
10996
+ ctx.state.dynamicTechniques.recordFailure(ctx.command);
10997
+ }
10998
+ };
10999
+ var extractStructuredOutputStep = {
11000
+ name: "structured_extraction",
11001
+ run(ctx) {
11002
+ if (!ctx.result.success || !ctx.result.output) return;
11003
+ ctx.structured = autoExtractStructured(ctx.toolCall.name, ctx.result.output);
11004
+ }
11005
+ };
11006
+ var annotateStructuredOutputStep = {
11007
+ name: "output_annotation",
11008
+ run(ctx) {
11009
+ if (!ctx.structured) return;
11010
+ if (ctx.structured.credentials?.length) {
11011
+ ctx.outputAdditions.push(`
11012
+ [AUTO-EXTRACTED] ${ctx.structured.credentials.length} credential(s) found`);
11013
+ }
11014
+ if (ctx.structured.vulnerabilities?.length) {
11015
+ ctx.outputAdditions.push(`
11016
+ [AUTO-EXTRACTED] ${ctx.structured.vulnerabilities.length} CVE reference(s) found`);
11017
+ }
11018
+ if (ctx.structured.binaryAnalysisSummary) {
11019
+ ctx.outputAdditions.push(`
10753
11020
  [AUTO-EXTRACTED] Binary analysis:
10754
- ${structured.binaryAnalysisSummary}`);
10755
- }
10756
- if (structured.discoveredIPs?.length) {
10757
- additions.push(handleOSINTIPs(state, structured.discoveredIPs, toolCall.name));
10758
- }
10759
- if (structured.discoveredSubdomains?.length) {
10760
- for (const sub of structured.discoveredSubdomains.slice(0, 20)) {
10761
- state.attackGraph.addOSINT("subdomain", sub, { source: toolCall.name });
10762
- }
10763
- }
10764
- if (structured.cryptoWeaknesses?.length) {
10765
- additions.push(`
10766
- [AUTO-EXTRACTED] Crypto weaknesses: ${structured.cryptoWeaknesses.join("; ")}`);
10767
- }
10768
- if (structured.stegoHints?.length) {
10769
- additions.push(`
10770
- [AUTO-EXTRACTED] Stego hints: ${structured.stegoHints.join("; ")}`);
10771
- }
10772
- if (additions.length > 0) {
10773
- result.output += additions.join("");
10774
- }
11021
+ ${ctx.structured.binaryAnalysisSummary}`);
11022
+ }
11023
+ if (ctx.structured.cryptoWeaknesses?.length) {
11024
+ ctx.outputAdditions.push(
11025
+ `
11026
+ [AUTO-EXTRACTED] Crypto weaknesses: ${ctx.structured.cryptoWeaknesses.join("; ")}`
11027
+ );
11028
+ }
11029
+ if (ctx.structured.stegoHints?.length) {
11030
+ ctx.outputAdditions.push(`
11031
+ [AUTO-EXTRACTED] Stego hints: ${ctx.structured.stegoHints.join("; ")}`);
10775
11032
  }
10776
11033
  }
10777
- if (result.success && !state.getChallengeAnalysis()) {
10778
- if (RECON_TRIGGER_TOOLS.has(toolCall.name) && result.output && result.output.length > MIN_RECON_OUTPUT_LENGTH) {
10779
- const analysis = analyzeChallenge(result.output);
10780
- if (analysis.confidence > MIN_CHALLENGE_CONFIDENCE) {
10781
- state.setChallengeAnalysis(analysis);
11034
+ };
11035
+ var registerOSINTDiscoveriesStep = {
11036
+ name: "osint_target_registration",
11037
+ run(ctx) {
11038
+ if (!ctx.structured) return;
11039
+ if (ctx.structured.discoveredIPs?.length) {
11040
+ ctx.outputAdditions.push(handleOSINTIPs(ctx.state, ctx.structured.discoveredIPs, ctx.toolCall.name));
11041
+ }
11042
+ if (ctx.structured.discoveredSubdomains?.length) {
11043
+ for (const sub of ctx.structured.discoveredSubdomains.slice(0, 20)) {
11044
+ ctx.state.attackGraph.addOSINT("subdomain", sub, { source: ctx.toolCall.name });
10782
11045
  }
10783
11046
  }
10784
11047
  }
11048
+ };
11049
+ var flushOutputAnnotationsStep = {
11050
+ name: "flush_output_annotation",
11051
+ run(ctx) {
11052
+ if (ctx.outputAdditions.length === 0) return;
11053
+ ctx.result.output += ctx.outputAdditions.join("");
11054
+ }
11055
+ };
11056
+ var detectChallengeStep = {
11057
+ name: "challenge_detection",
11058
+ run(ctx) {
11059
+ if (!ctx.result.success || ctx.state.getChallengeAnalysis()) return;
11060
+ if (!RECON_TRIGGER_TOOLS.has(ctx.toolCall.name)) return;
11061
+ if (!ctx.result.output || ctx.result.output.length <= MIN_RECON_OUTPUT_LENGTH) return;
11062
+ const analysis = analyzeChallenge(ctx.result.output);
11063
+ if (analysis.confidence > MIN_CHALLENGE_CONFIDENCE) {
11064
+ ctx.state.setChallengeAnalysis(analysis);
11065
+ }
11066
+ }
11067
+ };
11068
+ var POST_EXECUTION_STEPS = [
11069
+ recordWorkingMemoryStep,
11070
+ extractStructuredOutputStep,
11071
+ annotateStructuredOutputStep,
11072
+ registerOSINTDiscoveriesStep,
11073
+ flushOutputAnnotationsStep,
11074
+ detectChallengeStep
11075
+ ];
11076
+ function runPostExecutionPipeline(state, toolCall, result) {
11077
+ const ctx = {
11078
+ state,
11079
+ toolCall,
11080
+ result,
11081
+ command: buildCommand(toolCall),
11082
+ structured: null,
11083
+ outputAdditions: []
11084
+ };
11085
+ for (const step of POST_EXECUTION_STEPS) {
11086
+ step.run(ctx);
11087
+ }
10785
11088
  }
10786
11089
 
10787
11090
  // src/engine/tool-registry/core.ts
@@ -11446,12 +11749,12 @@ function trackBlockedPattern(progress, toolName, errorLower) {
11446
11749
  }
11447
11750
  function appendBlockedCommandHints(lines, errorLower) {
11448
11751
  if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
11449
- lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
11752
+ lines.push(`Fix: Use the tool's native output or formatting options instead of shell pipes.`);
11450
11753
  lines.push(`Alternative approaches:`);
11451
11754
  lines.push(` 1. Save output to file: command > ${WORK_DIR}/output.txt, then use read_file("${WORK_DIR}/output.txt")`);
11452
- lines.push(` 2. Use tool-specific flags: nmap -oN ${WORK_DIR}/scan.txt, curl -o ${WORK_DIR}/page.html`);
11453
- lines.push(` 3. Use browse_url for web content instead of curl | grep`);
11454
- lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
11755
+ lines.push(` 2. Discover the tool's own output flag via --help and use that instead of shell chaining`);
11756
+ lines.push(` 3. If the target is web content, prefer a dedicated browser/web tool over shell text filtering`);
11757
+ lines.push(` 4. If filtering output: run the command first, then read_file + parse the result in a second step`);
11455
11758
  } else if (errorLower.includes("redirect")) {
11456
11759
  lines.push(`Fix: Redirect to ${WORK_DIR}/ path.`);
11457
11760
  lines.push(`Example: command > ${WORK_DIR}/output.txt or command 2>&1 > ${WORK_DIR}/errors.txt`);
@@ -11650,14 +11953,23 @@ function extractHypothesizedReason(failureLine) {
11650
11953
  }
11651
11954
 
11652
11955
  // src/shared/utils/context-digest/formatters.ts
11956
+ function safeList4(values) {
11957
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
11958
+ }
11653
11959
  function formatContextForLLM(memo13) {
11654
11960
  const compact = {};
11655
- if (memo13.keyFindings.length > 0) compact.findings = memo13.keyFindings;
11656
- if (memo13.credentials.length > 0) compact.credentials = memo13.credentials;
11657
- if (memo13.attackVectors.length > 0) compact.attackVectors = memo13.attackVectors;
11658
- if (memo13.failures.length > 0) compact.failures = memo13.failures;
11659
- if (memo13.suspicions.length > 0) compact.suspicions = memo13.suspicions;
11660
- if (memo13.nextSteps.length > 0) compact.nextSteps = memo13.nextSteps;
11961
+ const keyFindings = safeList4(memo13.keyFindings);
11962
+ const credentials = safeList4(memo13.credentials);
11963
+ const attackVectors = safeList4(memo13.attackVectors);
11964
+ const failures = safeList4(memo13.failures);
11965
+ const suspicions = safeList4(memo13.suspicions);
11966
+ const nextSteps = safeList4(memo13.nextSteps);
11967
+ if (keyFindings.length > 0) compact.findings = keyFindings;
11968
+ if (credentials.length > 0) compact.credentials = credentials;
11969
+ if (attackVectors.length > 0) compact.attackVectors = attackVectors;
11970
+ if (failures.length > 0) compact.failures = failures;
11971
+ if (suspicions.length > 0) compact.suspicions = suspicions;
11972
+ if (nextSteps.length > 0) compact.nextSteps = nextSteps;
11661
11973
  compact.attackValue = memo13.attackValue;
11662
11974
  if (memo13.reflection) compact.reflection = memo13.reflection;
11663
11975
  return JSON.stringify(compact);
@@ -12315,8 +12627,8 @@ function getTechniquesDir() {
12315
12627
  return _techniquesDir;
12316
12628
  }
12317
12629
  function loadPromptFile(filename) {
12318
- const path2 = join10(getPromptsDir(), filename);
12319
- return existsSync6(path2) ? readFileSync3(path2, PROMPT_CONFIG.ENCODING) : "";
12630
+ const path4 = join10(getPromptsDir(), filename);
12631
+ return existsSync6(path4) ? readFileSync3(path4, PROMPT_CONFIG.ENCODING) : "";
12320
12632
  }
12321
12633
  function loadTechniqueFile(techniqueName) {
12322
12634
  const filename = techniqueName.endsWith(".md") ? techniqueName : `${techniqueName}.md`;
@@ -12330,14 +12642,20 @@ function loadTechniqueFile(techniqueName) {
12330
12642
  }
12331
12643
 
12332
12644
  // src/agents/prompt-builder/fragments/core.ts
12645
+ function safeList5(values) {
12646
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
12647
+ }
12333
12648
  function buildScopeFragment(state) {
12334
12649
  const scope = state.getScope();
12335
12650
  if (!scope) return PROMPT_DEFAULTS.NO_SCOPE;
12651
+ const allowedCidrs = safeList5(scope.allowedCidrs);
12652
+ const allowedDomains = safeList5(scope.allowedDomains);
12653
+ const exclusions = safeList5(scope.exclusions);
12336
12654
  const flags = `DOS Allowed: ${scope.isDOSAllowed} | Social Engineering Allowed: ${scope.isSocialAllowed}`;
12337
12655
  return PROMPT_XML.SCOPE(
12338
- scope.allowedCidrs.join(", ") || "none",
12339
- scope.allowedDomains.join(", ") || "none",
12340
- scope.exclusions.join(", ") || "none",
12656
+ allowedCidrs.join(", ") || "none",
12657
+ allowedDomains.join(", ") || "none",
12658
+ exclusions.join(", ") || "none",
12341
12659
  flags
12342
12660
  );
12343
12661
  }
@@ -12457,7 +12775,7 @@ ${summary}
12457
12775
  </session-journal>`;
12458
12776
  }
12459
12777
  if (olderTurns.length > 0) {
12460
- const indexLines = olderTurns.slice(-10).map((n) => `.pentesting/turns/${n}.md`).join("\n");
12778
+ const indexLines = olderTurns.slice(-10).map((n) => `.pentesting/turns/${n}-memory.md`).join("\n");
12461
12779
  const ellipsis = olderTurns.length > 10 ? `
12462
12780
  ... (${olderTurns.length - 10} earlier turns \u2014 check .pentesting/turns/)` : "";
12463
12781
  output += `
@@ -12477,6 +12795,8 @@ function buildUserContextFragment(userInput) {
12477
12795
  }
12478
12796
 
12479
12797
  // src/agents/prompt-builder/yaml-layer-executor.ts
12798
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
12799
+ import path2 from "path";
12480
12800
  var STATE_FRAGMENT_REGISTRY = {
12481
12801
  "state.scope": (ctx) => buildScopeFragment(ctx.state),
12482
12802
  "state.todo": (ctx) => buildTodoFragment(ctx.state),
@@ -12508,7 +12828,7 @@ async function executeLayer(layer, ctx) {
12508
12828
  case "static":
12509
12829
  return layer.content?.trim() ?? null;
12510
12830
  case "file_read":
12511
- return buildJournalFragment() || null;
12831
+ return executeFileReadLayer(layer) || null;
12512
12832
  case "user_input":
12513
12833
  return buildUserContextFragment(ctx.userInput) || null;
12514
12834
  case "memory": {
@@ -12522,6 +12842,21 @@ async function executeLayer(layer, ctx) {
12522
12842
  return null;
12523
12843
  }
12524
12844
  }
12845
+ function executeFileReadLayer(layer) {
12846
+ const source = layer.source?.trim();
12847
+ if (!source) return null;
12848
+ if (source.includes("{N-1}")) {
12849
+ return buildJournalFragment() || null;
12850
+ }
12851
+ const absolutePath = path2.resolve(process.cwd(), source);
12852
+ if (!existsSync7(absolutePath)) return null;
12853
+ const content = readFileSync4(absolutePath, "utf-8").trim();
12854
+ if (!content) return null;
12855
+ if (layer.wrap) {
12856
+ return layer.wrap.replace("{content}", content);
12857
+ }
12858
+ return content;
12859
+ }
12525
12860
  function executeFileLayer(layer) {
12526
12861
  if (!layer.source) return null;
12527
12862
  const parts = layer.source.split("/");
@@ -12640,6 +12975,9 @@ function isPipelineNode(step) {
12640
12975
  }
12641
12976
 
12642
12977
  // src/engine/pipeline/runner.ts
12978
+ function collectChangedMemoryKeys(base, next) {
12979
+ return Object.keys(next).filter((key) => !Object.is(base[key], next[key]));
12980
+ }
12643
12981
  var PipelineRunner = class {
12644
12982
  /**
12645
12983
  * Recursively execute a pipeline step.
@@ -12677,7 +13015,29 @@ var PipelineRunner = class {
12677
13015
  return;
12678
13016
  }
12679
13017
  if ("parallel" in step) {
12680
- await Promise.all(step.parallel.map((s) => this.execute(s, ctx)));
13018
+ const branchResults = await Promise.all(step.parallel.map(async (s) => {
13019
+ const localMemory = { ...ctx.memory };
13020
+ const branchCtx = { ...ctx, memory: localMemory };
13021
+ await this.execute(s, branchCtx);
13022
+ return localMemory;
13023
+ }));
13024
+ const collisions = /* @__PURE__ */ new Set();
13025
+ const baseMemory = { ...ctx.memory };
13026
+ for (const branchMemory of branchResults) {
13027
+ const changedKeys = collectChangedMemoryKeys(baseMemory, branchMemory);
13028
+ for (const key of changedKeys) {
13029
+ if (Object.prototype.hasOwnProperty.call(ctx.memory, key) && !Object.is(ctx.memory[key], baseMemory[key])) {
13030
+ collisions.add(key);
13031
+ continue;
13032
+ }
13033
+ ctx.memory[key] = branchMemory[key];
13034
+ }
13035
+ }
13036
+ if (collisions.size > 0) {
13037
+ throw new Error(
13038
+ `PipelineRunner parallel memory collision on key(s): ${Array.from(collisions).join(", ")}`
13039
+ );
13040
+ }
12681
13041
  return;
12682
13042
  }
12683
13043
  if ("condition" in step) {
@@ -12732,7 +13092,7 @@ var ActionNode = class {
12732
13092
  var flagsCaptured = (ctx) => ctx.flagsAfter > ctx.flagsBefore;
12733
13093
 
12734
13094
  // src/engine/pipeline/loader.ts
12735
- import { readFileSync as readFileSync4 } from "fs";
13095
+ import { readFileSync as readFileSync5 } from "fs";
12736
13096
  import { join as join11 } from "path";
12737
13097
  import { parse as yamlParse } from "yaml";
12738
13098
 
@@ -12853,32 +13213,45 @@ var makeSynthesizePlaybook = (llm, opts) => async (ctx) => {
12853
13213
  // src/agents/main-agent/turn-recorder.ts
12854
13214
  import { writeFileSync as writeFileSync7 } from "fs";
12855
13215
  async function recordTurn(context) {
12856
- const { turnCounter, toolJournal, summaryRegenerator } = context;
13216
+ const { turnCounter, toolJournal, memorySynth } = context;
12857
13217
  if (toolJournal.length === 0) return turnCounter;
12858
13218
  try {
12859
- await writeTurnInsight(turnCounter, summaryRegenerator, context);
13219
+ await writeTurnInsight(turnCounter, memorySynth, context);
12860
13220
  rotateTurnRecords();
12861
13221
  } catch {
12862
13222
  }
12863
13223
  return turnCounter + 1;
12864
13224
  }
12865
- async function writeTurnInsight(turnCounter, summaryRegenerator, ctx) {
13225
+ async function writeTurnInsight(turnCounter, memorySynth, ctx) {
12866
13226
  const { phase, toolJournal, memo: memo13, reflections } = ctx;
13227
+ const fallbackNextSteps = Array.isArray(memo13.nextSteps) ? memo13.nextSteps : [];
12867
13228
  const turnData = formatTurnRecord({
12868
13229
  turn: turnCounter,
12869
13230
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12870
13231
  phase,
12871
13232
  tools: toolJournal,
12872
13233
  memo: memo13,
12873
- reflection: reflections.length > 0 ? reflections.join(" | ") : memo13.nextSteps.join("; ")
13234
+ reflection: reflections.length > 0 ? reflections.join(" | ") : fallbackNextSteps.join("; ")
12874
13235
  });
12875
13236
  const existingSummary = readJournalSummary();
12876
13237
  try {
12877
- const result = await summaryRegenerator.execute({ existingSummary, turnData });
13238
+ const result = await memorySynth.execute({ existingSummary, turnData });
12878
13239
  if (result.success && result.summary) {
12879
13240
  ensureDirExists(WORKSPACE.TURNS);
12880
- writeFileSync7(WORKSPACE.turnPath(turnCounter), result.summary, "utf-8");
12881
- flowLog("\u2465SummaryRegen", "\u2192", `turns/${turnCounter}.md`, `${result.summary.length}B`);
13241
+ const memoryFile = buildTurnMemoryDocument({
13242
+ turn: turnCounter,
13243
+ phase,
13244
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13245
+ writtenBy: "memory_synth",
13246
+ inputSources: [
13247
+ "previous_turn_memory",
13248
+ "current_turn_record"
13249
+ ],
13250
+ notesIncluded: reflections.length > 0 ? ["analyst_memo", "turn_reviews"] : ["analyst_memo"],
13251
+ memoryBody: result.summary
13252
+ });
13253
+ writeFileSync7(WORKSPACE.turnPath(turnCounter), memoryFile, "utf-8");
13254
+ flowLog("\u2465MemorySynth", "\u2192", `turns/${turnCounter}-memory.md`, `${memoryFile.length}B`);
12882
13255
  } else {
12883
13256
  regenerateJournalSummary();
12884
13257
  }
@@ -12887,27 +13260,158 @@ async function writeTurnInsight(turnCounter, summaryRegenerator, ctx) {
12887
13260
  }
12888
13261
  }
12889
13262
 
13263
+ // src/engine/verifier/turn-verifier.ts
13264
+ function asStringArray(values) {
13265
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
13266
+ }
13267
+ function uniq(values) {
13268
+ const safeValues = asStringArray(values);
13269
+ if (safeValues.length === 0) return [];
13270
+ return [...new Set(safeValues.map((v) => v.trim()).filter(Boolean))];
13271
+ }
13272
+ function parseInputSummary(inputSummary) {
13273
+ try {
13274
+ const parsed = JSON.parse(inputSummary);
13275
+ return parsed && typeof parsed === "object" ? parsed : null;
13276
+ } catch {
13277
+ return null;
13278
+ }
13279
+ }
13280
+ function verifyStateConsistency(ctx) {
13281
+ const issues = [];
13282
+ const lootDetails = ctx.state.getLoot().map((item) => item.detail.toLowerCase());
13283
+ const findingTexts = ctx.state.getFindings().map((item) => `${item.title}
13284
+ ${item.description}`.toLowerCase());
13285
+ for (const credential of uniq(ctx.memo?.credentials)) {
13286
+ if (!lootDetails.some((detail) => detail.includes(credential.toLowerCase()))) {
13287
+ issues.push({
13288
+ severity: "error",
13289
+ code: "state_consistency",
13290
+ message: `Analyst extracted credential "${credential}" but state.loot does not contain it.`
13291
+ });
13292
+ }
13293
+ }
13294
+ if (ctx.memo?.attackValue === "HIGH" || ctx.memo?.attackValue === "MED") {
13295
+ for (const vector of uniq(ctx.memo?.attackVectors)) {
13296
+ const needle = vector.toLowerCase().slice(0, 80);
13297
+ if (!findingTexts.some((text) => text.includes(needle))) {
13298
+ issues.push({
13299
+ severity: "warning",
13300
+ code: "state_consistency",
13301
+ message: `Analyst marked attack vector "${vector}" but no matching finding was recorded.`
13302
+ });
13303
+ }
13304
+ }
13305
+ }
13306
+ return issues;
13307
+ }
13308
+ function verifyScopeConsistency(ctx) {
13309
+ if (getScopeMode() !== "enforce") return [];
13310
+ const issues = [];
13311
+ const guard = new ScopeGuard(ctx.state, "enforce");
13312
+ for (const tool of ctx.journal) {
13313
+ if (!tool.success) continue;
13314
+ const parsedInput = parseInputSummary(tool.inputSummary);
13315
+ if (!parsedInput) continue;
13316
+ const serialized = JSON.stringify(parsedInput);
13317
+ const targets = Array.from(guard.extractTargets(serialized));
13318
+ const violations = targets.filter((target) => !guard.isTargetInScope(target));
13319
+ if (violations.length === 0) continue;
13320
+ issues.push({
13321
+ severity: "error",
13322
+ code: "scope_violation",
13323
+ message: `Successful tool ${tool.name} referenced out-of-scope target(s): ${violations.join(", ")}`
13324
+ });
13325
+ }
13326
+ return issues;
13327
+ }
13328
+ function verifyAskUserUsage(ctx) {
13329
+ const issues = [];
13330
+ const askUserCalls = ctx.journal.filter((tool) => tool.name === TOOL_NAMES.ASK_USER && tool.success);
13331
+ const nonSensitive = [];
13332
+ for (const call of askUserCalls) {
13333
+ const parsed = parseInputSummary(call.inputSummary);
13334
+ const inputType = String(parsed?.input_type || "text");
13335
+ const question = String(parsed?.question || "");
13336
+ if (!SENSITIVE_INPUT_TYPES.includes(inputType)) {
13337
+ nonSensitive.push({ inputType, question });
13338
+ }
13339
+ }
13340
+ if (nonSensitive.length > 0) {
13341
+ issues.push({
13342
+ severity: "warning",
13343
+ code: "ask_user_overuse",
13344
+ message: `Non-sensitive ask_user used ${nonSensitive.length} time(s): ${nonSensitive.map((item) => `${item.inputType}:${item.question.slice(0, 80)}`).join(" | ")}`
13345
+ });
13346
+ }
13347
+ if (askUserCalls.length > 1) {
13348
+ issues.push({
13349
+ severity: "warning",
13350
+ code: "ask_user_overuse",
13351
+ message: `ask_user was called ${askUserCalls.length} times in one turn.`
13352
+ });
13353
+ }
13354
+ return issues;
13355
+ }
13356
+ function formatSummary(issues) {
13357
+ if (issues.length === 0) return "[VERIFIER] OK - no rule violations detected";
13358
+ const errorCount = issues.filter((issue) => issue.severity === "error").length;
13359
+ const warningCount = issues.filter((issue) => issue.severity === "warning").length;
13360
+ const lines = [`[VERIFIER] ${errorCount} error(s), ${warningCount} warning(s)`];
13361
+ for (const issue of issues.slice(0, 5)) {
13362
+ lines.push(`- ${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`);
13363
+ }
13364
+ return lines.join("\n");
13365
+ }
13366
+ function verifyTurnContext(ctx) {
13367
+ const issues = [
13368
+ ...verifyStateConsistency(ctx),
13369
+ ...verifyScopeConsistency(ctx),
13370
+ ...verifyAskUserUsage(ctx)
13371
+ ];
13372
+ return {
13373
+ ok: !issues.some((issue) => issue.severity === "error"),
13374
+ issues,
13375
+ summary: formatSummary(issues)
13376
+ };
13377
+ }
13378
+
12890
13379
  // src/engine/pipeline/handlers/turn-mgmt.ts
13380
+ function resolveCurrentTurnCounter(memory) {
13381
+ return typeof memory.turnCounter === "number" ? memory.turnCounter : Math.max(1, (getNextTurnNumber() || 2) - 1);
13382
+ }
13383
+ function collectTurnReflections(ctx) {
13384
+ return [
13385
+ ...ctx.state.lastReflection ? [ctx.state.lastReflection] : [],
13386
+ ...typeof ctx.memory.turn_verifier_summary === "string" ? [ctx.memory.turn_verifier_summary] : []
13387
+ ];
13388
+ }
12891
13389
  var makeWriteInsight = (llm, _opts) => async (ctx) => {
12892
13390
  if (!llm) return { success: false };
12893
- const turnCounter = typeof ctx.memory.turnCounter === "number" ? ctx.memory.turnCounter : Math.max(1, (getNextTurnNumber() || 2) - 1);
13391
+ const turnCounter = resolveCurrentTurnCounter(ctx.memory);
12894
13392
  await recordTurn({
12895
13393
  turnCounter,
12896
13394
  phase: ctx.state.getPhase(),
12897
13395
  toolJournal: ctx.journal,
12898
13396
  memo: ctx.memo,
12899
- reflections: ctx.state.lastReflection ? [ctx.state.lastReflection] : [],
12900
- summaryRegenerator: createSummaryRegenerator(llm)
13397
+ reflections: collectTurnReflections(ctx),
13398
+ memorySynth: createMemorySynth(llm)
12901
13399
  });
12902
13400
  return { success: true };
12903
13401
  };
13402
+ var makeVerifyTurn = (_llm, _opts) => async (ctx) => {
13403
+ const report = verifyTurnContext(ctx);
13404
+ ctx.memory.turn_verifier_report = report;
13405
+ ctx.memory.turn_verifier_summary = report.summary;
13406
+ return { success: report.ok, output: report };
13407
+ };
12904
13408
  var makeRotateTurns = (_llm, _opts) => async (_ctx) => {
12905
13409
  rotateTurnRecords();
12906
13410
  return { success: true };
12907
13411
  };
12908
13412
  var makeSaveSession = (_llm, _opts) => async (ctx) => {
12909
13413
  try {
12910
- const { saveState: saveState2 } = await import("./persistence-RDC7AENL.js");
13414
+ const { saveState: saveState2 } = await import("./persistence-7FTYXIZY.js");
12911
13415
  saveState2(ctx.state);
12912
13416
  } catch {
12913
13417
  }
@@ -12915,9 +13419,26 @@ var makeSaveSession = (_llm, _opts) => async (ctx) => {
12915
13419
  };
12916
13420
 
12917
13421
  // src/engine/pipeline/handlers/core-loop.ts
12918
- var makeDrainUserInput = (_llm, _opts) => async (ctx) => {
13422
+ var makeProcessUserInput = (_llm, _opts) => async (ctx) => {
12919
13423
  if (!ctx.controller) return { success: true };
12920
- ctx.controller.drainUserInput(ctx.messages);
13424
+ const result = await ctx.controller.processUserInputTurn();
13425
+ const shouldForward = result?.shouldForwardToMain !== false;
13426
+ ctx.memory.should_forward_to_main = shouldForward;
13427
+ if (!shouldForward) {
13428
+ const directResponse = typeof result?.directResponse === "string" ? result.directResponse : "";
13429
+ ctx.memory.output = directResponse;
13430
+ ctx.memory.short_circuit_response = ctx.memory.output;
13431
+ ctx.memory.has_tool_calls = false;
13432
+ ctx.memory.isCompleted = true;
13433
+ if (directResponse.trim() && ctx.events) {
13434
+ const phase = ctx.state.getPhase();
13435
+ ctx.events.emit({
13436
+ type: EVENT_TYPES.AI_RESPONSE,
13437
+ timestamp: Date.now(),
13438
+ data: { content: directResponse.trim(), phase }
13439
+ });
13440
+ }
13441
+ }
12921
13442
  return { success: true };
12922
13443
  };
12923
13444
  var makeBuildSystemPrompt = (_llm, _opts) => async (ctx) => {
@@ -12928,6 +13449,12 @@ var makeBuildSystemPrompt = (_llm, _opts) => async (ctx) => {
12928
13449
  };
12929
13450
  var makeCoreInference = (_llm, _opts) => async (ctx) => {
12930
13451
  if (!ctx.controller) throw new Error("AgentController missing in context");
13452
+ if (ctx.memory.short_circuit_response) {
13453
+ ctx.memory.output = ctx.memory.short_circuit_response;
13454
+ ctx.memory.has_tool_calls = false;
13455
+ ctx.memory.isCompleted = true;
13456
+ return { success: true };
13457
+ }
12931
13458
  const prompt = ctx.memory.system_prompt;
12932
13459
  if (!prompt) throw new Error("core_inference: system_prompt missing \u2014 build_system_prompt must run before core_inference");
12933
13460
  const { output, toolCalls, hadReasoningEnd } = await ctx.controller.runLLMInference(ctx, prompt);
@@ -12959,11 +13486,12 @@ var ACTION_HANDLER_REGISTRY = {
12959
13486
  replace_messages_from_memory: makeReplaceMessagesFromMemory,
12960
13487
  synthesize_playbook: makeSynthesizePlaybook,
12961
13488
  // ── Turn lifecycle handlers ─────────────────────────────────────────────
13489
+ verify_turn: makeVerifyTurn,
12962
13490
  write_insight: makeWriteInsight,
12963
13491
  rotate_turns: makeRotateTurns,
12964
13492
  save_session: makeSaveSession,
12965
13493
  // ── Core agent loop handlers ────────────────────────────────────────────
12966
- drain_user_input: makeDrainUserInput,
13494
+ process_user_input: makeProcessUserInput,
12967
13495
  build_system_prompt: makeBuildSystemPrompt,
12968
13496
  core_inference: makeCoreInference,
12969
13497
  core_tool_execution: makeCoreToolExecution
@@ -13031,11 +13559,6 @@ function buildStepV2(raw, nodeMap, conditions) {
13031
13559
  throw new Error(`Invalid pipeline step: ${JSON.stringify(raw)}`);
13032
13560
  }
13033
13561
  const r = raw;
13034
- if ("node" in r && typeof r["node"] === "string") {
13035
- const node = nodeMap.get(r["node"]);
13036
- if (!node) throw new Error(`Unknown node reference: "${r["node"]}". Declare it in nodes: section.`);
13037
- return node;
13038
- }
13039
13562
  if ("serial" in r && Array.isArray(r["serial"])) {
13040
13563
  return { serial: r["serial"].map((s) => buildStepV2(s, nodeMap, conditions)) };
13041
13564
  }
@@ -13055,18 +13578,16 @@ function buildStepV2(raw, nodeMap, conditions) {
13055
13578
  }
13056
13579
  throw new Error(`Unrecognized step shape: ${JSON.stringify(raw)}`);
13057
13580
  }
13058
- function loadPipeline(yamlPath, registryOrOptions, legacyKey) {
13059
- const content = readFileSync4(yamlPath, "utf-8");
13581
+ function loadPipeline(yamlPath, options) {
13582
+ const content = readFileSync5(yamlPath, "utf-8");
13060
13583
  const config = yamlParse(content);
13061
- const key = legacyKey ?? registryOrOptions.key ?? "post_step";
13584
+ const key = options.key ?? "post_step";
13062
13585
  const raw = config[key];
13063
13586
  if (!raw) throw new Error(`Pipeline key "${key}" not found in ${yamlPath}`);
13064
- if ("llm" in registryOrOptions && config.nodes) {
13065
- return loadV2(config, raw, registryOrOptions);
13066
- }
13067
- return buildStepV1(raw, registryOrOptions);
13587
+ if (!config.nodes) throw new Error(`Pipeline config in ${yamlPath} must declare a nodes: section.`);
13588
+ return loadCurrent(config, raw, options);
13068
13589
  }
13069
- function loadV2(config, pipelineRaw, opts) {
13590
+ function loadCurrent(config, pipelineRaw, opts) {
13070
13591
  const llm = opts.llm;
13071
13592
  const conditions = opts.conditions ?? {};
13072
13593
  const nodeDefs = config.nodes ?? {};
@@ -13082,33 +13603,6 @@ function loadV2(config, pipelineRaw, opts) {
13082
13603
  }
13083
13604
  return buildStepV2(pipelineRaw, nodeMap, conditions);
13084
13605
  }
13085
- function buildStepV1(raw, registry) {
13086
- if (!raw || typeof raw !== "object") {
13087
- throw new Error(`Invalid pipeline step: ${JSON.stringify(raw)}`);
13088
- }
13089
- const r = raw;
13090
- if ("node" in r) {
13091
- const id = r["node"];
13092
- const factory = registry.nodes[id];
13093
- if (!factory) throw new Error(`Unknown node: "${id}". Register it in node-registry.ts.`);
13094
- return factory(id);
13095
- }
13096
- if ("serial" in r && Array.isArray(r["serial"])) {
13097
- return { serial: r["serial"].map((s) => buildStepV1(s, registry)) };
13098
- }
13099
- if ("parallel" in r && Array.isArray(r["parallel"])) {
13100
- return { parallel: r["parallel"].map((s) => buildStepV1(s, registry)) };
13101
- }
13102
- if ("when" in r) {
13103
- const condName = r["when"];
13104
- const cond = registry.conditions[condName];
13105
- if (!cond) throw new Error(`Unknown condition: "${condName}".`);
13106
- const then = buildStepV1(r["then"], registry);
13107
- const otherwise = "else" in r ? buildStepV1(r["else"], registry) : void 0;
13108
- return { condition: cond, then, else: otherwise };
13109
- }
13110
- throw new Error(`Unrecognized step shape: ${JSON.stringify(raw)}`);
13111
- }
13112
13606
  function resolvePipelineYaml(relativePath) {
13113
13607
  return join11(process.cwd(), relativePath);
13114
13608
  }
@@ -13129,181 +13623,15 @@ function getConditionRegistry() {
13129
13623
  // Fires when ≥ 2 SUCCEEDED attack graph nodes exist (works without flags)
13130
13624
  has_exploit_chain: hasExploitChain,
13131
13625
  // Fires when the LLM returned tool calls to execute
13132
- has_tool_calls: (ctx) => ctx.memory.has_tool_calls === true
13626
+ has_tool_calls: (ctx) => ctx.memory.has_tool_calls === true,
13627
+ // Fires when input processing decided the main agent should continue.
13628
+ should_forward_to_main: (ctx) => ctx.memory.should_forward_to_main !== false
13133
13629
  };
13134
13630
  }
13135
13631
 
13136
- // src/agents/user-input/constants.ts
13137
- var RECENT_MESSAGE_THRESHOLD_SECONDS = 5;
13138
-
13139
- // src/agents/user-input/intent/header.ts
13140
- var INTENT_HEADER = `
13141
- <user-message priority="INTERRUPT">
13142
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13143
- \u26A1 USER MESSAGE \u2014 STOP. READ THIS FIRST. THEN ACT.
13144
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13145
-
13146
- The user sent the following message(s) WHILE you are actively working.
13147
- This message takes PRECEDENCE over your current plan. Process it NOW.
13148
-
13149
- IMPORTANT RULES:
13150
- - If the user is repeating themselves, it means you MISSED or IGNORED their previous message.
13151
- - Check your Working Memory for the correct value before proceeding.
13152
- - NEVER continue with an incorrect value after a correction, even if it appears in a SUCCESS log.
13153
- - Working Memory success logs are NOT ground truth \u2014 user corrections override them.
13154
-
13155
- <<USER_MESSAGES>>
13156
- `;
13157
-
13158
- // src/agents/user-input/intent/classification.ts
13159
- var INTENT_CLASSIFICATION = `
13160
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13161
- \xA71. INTENT CLASSIFICATION (Chain-of-Thought \u2014 MANDATORY)
13162
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13163
-
13164
- You MUST internally reason through this decision tree IN ORDER.
13165
- Stop at the FIRST matching category \u2014 do NOT skip ahead.
13166
-
13167
- \u250C\u2500 STEP 1: Is the user telling you to STOP or ABORT?
13168
- \u2502 Signals: "stop", "abort", "cancel", "enough", "halt", "wait"
13169
- \u2502 \u2192 CATEGORY: ABORT
13170
- \u2502 \u2192 ACTION: Immediately stop current tool execution.
13171
- \u2502 Use \`ask_user\` to confirm: "Understood, stopping. What would you like me to do next?"
13172
- \u2502
13173
- \u251C\u2500 STEP 2: Is the user CORRECTING you or saying you're WRONG?
13174
- \u2502 Signals: "that's wrong", "don't do that", "stop doing X", "you already tried that",
13175
- \u2502 "not that way", "I said X not Y", negative feedback on your actions,
13176
- \u2502 spelling corrections ("you wrote X, it should be Y"),
13177
- \u2502 name corrections ("that's not the right username/target/domain")
13178
- \u2502 \u2192 CATEGORY: CORRECTION
13179
- \u2502 \u2192 ACTION: 1. Acknowledge the error in ONE sentence (do NOT apologize excessively).
13180
- \u2502 2. IMMEDIATELY fix the mistake and resume work with the correct value.
13181
- \u2502 3. Do NOT use \`ask_user\` for confirmation \u2014 fix it and continue.
13182
- \u2502 4. NEVER repeat the same wrong value after a correction.
13183
- \u2502
13184
- \u251C\u2500 STEP 3: Is the user providing ACTIONABLE INFORMATION?
13185
- \u2502 Signals: credentials, passwords, usernames, file paths, endpoints, API keys,
13186
- \u2502 ports, version numbers, IP addresses, hints about the target
13187
- \u2502 \u2192 CATEGORY: INFORMATION
13188
- \u2502 \u2192 ACTION: 1. Store with \`add_loot\` (if credentials) or remember contextually.
13189
- \u2502 2. USE this information immediately in your next tool call.
13190
- \u2502 3. Do NOT ask "should I use this?" \u2014 just use it.
13191
- \u2502 Example: User says "password is admin123"
13192
- \u2502 \u2192 Immediately try those credentials on all discovered login surfaces.
13193
- \u2502
13194
- \u251C\u2500 STEP 4: Is the user giving a DIRECT COMMAND to execute something?
13195
- \u2502 Signals: imperative verb + specific action: "run X", "scan Y", "exploit Z",
13196
- \u2502 "try X on Y", "use sqlmap", "brute force SSH"
13197
- \u2502 \u2192 CATEGORY: COMMAND
13198
- \u2502 \u2192 ACTION: Execute EXACTLY what the user asked. No more, no less.
13199
- \u2502 Do NOT add extra scans or "while we're at it" actions.
13200
- \u2502 Do NOT ask for confirmation \u2014 the user already decided.
13201
- \u2502
13202
- \u251C\u2500 STEP 5: Is the user changing the TARGET or SCOPE?
13203
- \u2502 Signals: new IP/domain, "switch to", "add target", "remove target",
13204
- \u2502 "also attack X", "change scope"
13205
- \u2502 \u2192 CATEGORY: TARGET_CHANGE
13206
- \u2502 \u2192 ACTION: 1. Call \`add_target\` with the new target.
13207
- \u2502 2. Confirm briefly with \`ask_user\`: "Added [target]. Starting reconnaissance."
13208
- \u2502 3. Begin testing the new target.
13209
- \u2502
13210
- \u251C\u2500 STEP 6: Is the user providing STRATEGIC GUIDANCE?
13211
- \u2502 Signals: "focus on X", "prioritize Y", "skip Z", "try X approach",
13212
- \u2502 "what about X?", "have you considered X?", tactical suggestions
13213
- \u2502 \u2192 CATEGORY: GUIDANCE
13214
- \u2502 \u2192 ACTION: 1. Acknowledge the guidance briefly via \`ask_user\`:
13215
- \u2502 "Understood \u2014 adjusting strategy to focus on [X]."
13216
- \u2502 2. Immediately adjust your approach and continue working.
13217
- \u2502 3. The acknowledgment and next action should be in the SAME turn.
13218
- \u2502
13219
- \u251C\u2500 STEP 7: Is the user asking about PROGRESS or STATUS?
13220
- \u2502 Signals: "what did you find?", "any progress?", "status?", "what are you doing?",
13221
- \u2502 "show me", "report", "findings so far", "how's it going?"
13222
- \u2502 \u2192 CATEGORY: STATUS_QUERY
13223
- \u2502 \u2192 ACTION: Use \`ask_user\` to provide a structured status report:
13224
- \u2502 FORMAT:
13225
- \u2502 "\u{1F4CA} Status Report:
13226
- \u2502 \u2022 Phase: [current phase]
13227
- \u2502 \u2022 Targets: [count] ([list key ones])
13228
- \u2502 \u2022 Key Findings: [count] ([summarize top findings])
13229
- \u2502 \u2022 Current Action: [what you were doing]
13230
- \u2502 \u2022 Next Steps: [what you plan to do next]"
13231
- \u2502 Then RESUME your previous work \u2014 do NOT stop after reporting.
13232
- \u2502
13233
- \u2514\u2500 STEP 8: Everything else \u2192 CONVERSATION
13234
- Signals: greetings, questions, discussions, explanations, opinions,
13235
- casual talk, "hello", "how does X work?", "explain Y"
13236
- \u2192 CATEGORY: CONVERSATION
13237
- \u2192 ACTION: Use \`ask_user\` to respond naturally and conversationally.
13238
- Answer questions with your knowledge.
13239
- Then ask if they want you to continue with the current task.
13240
- Do NOT start any scans or attacks.
13241
- `;
13242
-
13243
- // src/agents/user-input/intent/resolution.ts
13244
- var INTENT_RESOLUTION = `
13245
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13246
- \xA72. MULTI-MESSAGE RESOLUTION
13247
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13248
-
13249
- If multiple user messages are queued, process them as follows:
13250
- 1. Read ALL messages first to understand the full context.
13251
- 2. Later messages may OVERRIDE or CLARIFY earlier ones.
13252
- Example: [1] "try brute force" \u2192 [2] "actually, skip brute force, try SQLi"
13253
- \u2192 Only execute the SQLi instruction (message 2 overrides message 1).
13254
- 3. If messages are independent, process the HIGHEST PRIORITY category first
13255
- (ABORT > CORRECTION > INFORMATION > COMMAND > TARGET > GUIDANCE > STATUS > CONVERSATION).
13256
- 4. Acknowledge all messages but act on the most recent directive.
13257
-
13258
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13259
- \xA73. WORK RESUMPTION RULES
13260
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13261
-
13262
- After handling the user's message, you MUST resume productive work:
13263
-
13264
- \u251C\u2500 ABORT/CONVERSATION \u2192 Wait for next user instruction. Do NOT auto-resume.
13265
- \u251C\u2500 STATUS_QUERY \u2192 Report status, then RESUME previous work in the same turn.
13266
- \u251C\u2500 GUIDANCE/CORRECTION \u2192 Adjust plan, then CONTINUE with modified approach.
13267
- \u251C\u2500 INFORMATION \u2192 USE the information immediately in your next action.
13268
- \u251C\u2500 COMMAND \u2192 Execute the command. After completion, resume prior work.
13269
- \u251C\u2500 TARGET_CHANGE \u2192 Switch to new target, begin fresh workflow.
13270
-
13271
- KEY PRINCIPLE: Never leave a turn empty-handed.
13272
- If you used \`ask_user\` to respond, you may ALSO call other tools in the same turn
13273
- (except for ABORT and CONVERSATION categories).
13274
- `;
13275
-
13276
- // src/agents/user-input/intent/antipatterns.ts
13277
- var INTENT_ANTIPATTERNS = `
13278
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13279
- \xA74. ANTI-PATTERNS \u2014 NEVER DO THESE
13280
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
13281
-
13282
- \u251C\u2500 \u274C Ignore the user's message and continue your previous plan
13283
- \u251C\u2500 \u274C Start scanning/attacking after a greeting
13284
- \u251C\u2500 \u274C Ask "should I use this?" when the user provides credentials \u2192 JUST USE THEM
13285
- \u251C\u2500 \u274C Respond with only text (no tool call) \u2014 always use \`ask_user\` for responses
13286
- \u251C\u2500 \u274C Stop all work after answering a status query \u2014 RESUME immediately
13287
- \u251C\u2500 \u274C Add extra actions the user didn't ask for when handling a COMMAND
13288
- \u251C\u2500 \u274C Repeat the same failed approach after a CORRECTION
13289
- \u251C\u2500 \u274C Treat every input as an attack command \u2014 MOST inputs are collaborative
13290
- \u251C\u2500 \u274C Apologize more than once for the same mistake \u2014 acknowledge once and fix it
13291
- \u251C\u2500 \u274C Use the wrong value (typo/wrong target) AFTER the user corrected it
13292
- \u2514\u2500 \u274C Ignore repeating corrections \u2014 if user says it twice, you missed it the first time
13293
- </user-message>`;
13294
-
13295
- // src/agents/user-input/intent/index.ts
13296
- var USER_INPUT_INTENT_PROMPT = [
13297
- INTENT_HEADER,
13298
- INTENT_CLASSIFICATION,
13299
- INTENT_RESOLUTION,
13300
- INTENT_ANTIPATTERNS
13301
- ].join("\n");
13302
-
13303
13632
  // src/agents/user-input/queue.ts
13304
13633
  var MAX_QUEUE_ITEMS = 20;
13305
13634
  var MAX_ITEM_CHARS = 500;
13306
- var MAX_DRAIN_CHARS = 4e3;
13307
13635
  function truncateText(text, max) {
13308
13636
  if (text.length <= max) return text;
13309
13637
  if (max <= 1) return text.slice(0, max);
@@ -13337,23 +13665,13 @@ var UserInputQueue = class {
13337
13665
  return this.queue.length;
13338
13666
  }
13339
13667
  /**
13340
- * Drain all queued inputs and format them with intent analysis prompt.
13341
- * Returns null if queue is empty.
13342
- * Clears the queue after draining.
13668
+ * Drain all queued inputs as raw records.
13343
13669
  */
13344
- drainAndFormat() {
13345
- if (this.queue.length === 0) return null;
13670
+ drain() {
13671
+ if (this.queue.length === 0) return [];
13346
13672
  const messages = [...this.queue];
13347
13673
  this.queue = [];
13348
- const formattedMessages = messages.map((m, i) => {
13349
- const timeAgo = Math.round((Date.now() - m.timestamp) / 1e3);
13350
- const timeLabel = timeAgo < RECENT_MESSAGE_THRESHOLD_SECONDS ? "just now" : `${timeAgo}s ago`;
13351
- return `[${i + 1}] (${timeLabel}) "${truncateText(m.text, MAX_ITEM_CHARS)}"`;
13352
- }).join("\n");
13353
- return USER_INPUT_INTENT_PROMPT.replace(
13354
- "<<USER_MESSAGES>>",
13355
- truncateText(formattedMessages, MAX_DRAIN_CHARS)
13356
- );
13674
+ return messages;
13357
13675
  }
13358
13676
  /**
13359
13677
  * Peek at the queue without draining.
@@ -13431,32 +13749,36 @@ var MainAgent = class extends CoreAgent {
13431
13749
  userInput = "";
13432
13750
  turnCounter = 0;
13433
13751
  userInputQueue = new UserInputQueue();
13752
+ pendingInitialUserInput = null;
13434
13753
  pipelineRunner;
13435
13754
  turnCyclePipeline;
13755
+ inputProcessor;
13756
+ sessionRuntime;
13436
13757
  constructor(state, events, toolRegistry, approvalGate, scopeGuard) {
13437
13758
  super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
13438
13759
  this.approvalGate = approvalGate;
13439
13760
  this.scopeGuard = scopeGuard;
13440
13761
  this.promptBuilder = new PromptBuilder(state);
13441
13762
  this.pipelineRunner = new PipelineRunner();
13763
+ this.inputProcessor = createInputProcessor(this.llm);
13764
+ this.sessionRuntime = createSessionRuntime();
13765
+ setActiveSessionRuntime(this.sessionRuntime);
13442
13766
  const pipelineYaml = resolvePipelineYaml("pipeline.yaml");
13443
13767
  this.turnCyclePipeline = loadPipeline(pipelineYaml, {
13444
13768
  llm: this.llm,
13445
- conditions: getConditionRegistry()
13446
- }, "turn_cycle");
13769
+ conditions: getConditionRegistry(),
13770
+ key: "turn_cycle"
13771
+ });
13447
13772
  }
13448
13773
  // ─── Lifecycle ─────────────────────────────────────────────────────────────
13449
13774
  async execute(userInput) {
13450
- this.userInput = userInput;
13451
- this.drainUserInput([]);
13452
- if (!this.state.hasActiveEngagement() && this.userInput.trim().length > 0) {
13453
- this.state.currentObjective = this.userInput.trim();
13454
- }
13455
- emitStart(this.events, this.userInput, this.state);
13775
+ this.userInput = "";
13776
+ this.pendingInitialUserInput = userInput;
13777
+ emitStart(this.events, userInput, this.state);
13456
13778
  initializeTask(this.state);
13457
13779
  try {
13458
13780
  const initialPrompt = await this.buildDynamicPrompt({});
13459
- const result = await this.run(this.userInput, initialPrompt);
13781
+ const result = await this.run(userInput, initialPrompt);
13460
13782
  return result.output;
13461
13783
  } finally {
13462
13784
  try {
@@ -13494,34 +13816,66 @@ var MainAgent = class extends CoreAgent {
13494
13816
  isCompleted: !!ctx.memory.isCompleted
13495
13817
  };
13496
13818
  }
13497
- /**
13498
- * Injects any pending user input into the conversation.
13499
- *
13500
- * Behavior is fully controlled by pipeline.yaml `user_input_queue:` section:
13501
- * format: 'raw' | 'tagged' — whether to wrap with XML tag
13502
- * tag: string — XML tag name (default: user-input)
13503
- * inject_position: 'messages_end' | 'messages_start'
13504
- *
13505
- * WHY: Zero hardcoded formatting — RL agent can mutate this in pipeline.yaml.
13506
- * Called by the `drain_user_input` pipeline node at turn_cycle start.
13507
- */
13508
- drainUserInput(messages) {
13509
- if (!this.userInputQueue.hasPending()) return;
13510
- const queued = this.userInputQueue.drainAndFormat();
13511
- if (!queued) return;
13512
- const cfg = getUserInputQueueConfig();
13513
- const tag = cfg.tag ?? "user-input";
13514
- const formatted = cfg.format === "tagged" ? `<${tag}>
13515
- ${queued}
13516
- </${tag}>` : queued;
13517
- if (this.userInput.trim() === "") {
13518
- this.userInput = formatted;
13519
- } else {
13520
- this.userInput = `${this.userInput}
13521
-
13522
- ${formatted}`;
13819
+ async processUserInputTurn() {
13820
+ const messages = this.collectPendingUserInputs();
13821
+ this.pendingInitialUserInput = null;
13822
+ if (messages.length === 0) {
13823
+ return { shouldForwardToMain: true };
13824
+ }
13825
+ const rawInput = messages.map((text, index) => `[${index + 1}] ${text}`).join("\n");
13826
+ const result = await this.inputProcessor.execute({
13827
+ rawInput,
13828
+ existingPolicy: readPolicyDocument(),
13829
+ hasActiveEngagement: this.state.hasActiveEngagement(),
13830
+ currentObjective: this.state.currentObjective || ""
13831
+ });
13832
+ const normalized = this.normalizeInputProcessorResult(result, rawInput);
13833
+ if (normalized.shouldWritePolicy && normalized.policyDocument.trim()) {
13834
+ writePolicyDocument(normalized.policyDocument);
13835
+ }
13836
+ if (normalized.shouldForwardToMain && normalized.forwardedInput.trim()) {
13837
+ this.userInput = this.appendUserInput(this.userInput, normalized.forwardedInput);
13838
+ if (!this.state.hasActiveEngagement()) {
13839
+ this.state.currentObjective = normalized.forwardedInput;
13840
+ }
13523
13841
  }
13524
13842
  this.events.emit({ type: EVENT_TYPES.QUEUE_DRAINED, timestamp: Date.now() });
13843
+ this.emitQueueUpdated();
13844
+ return normalized;
13845
+ }
13846
+ normalizeInputProcessorResult(result, fallbackInput) {
13847
+ if (!result.success) {
13848
+ return {
13849
+ shouldForwardToMain: true,
13850
+ forwardedInput: fallbackInput,
13851
+ directResponse: "",
13852
+ shouldWritePolicy: false,
13853
+ policyDocument: "",
13854
+ policyUpdateSummary: "",
13855
+ insightSummary: "Input processor fallback: forwarded raw input.",
13856
+ success: false
13857
+ };
13858
+ }
13859
+ return {
13860
+ ...result,
13861
+ forwardedInput: result.forwardedInput.trim() || fallbackInput,
13862
+ directResponse: result.directResponse.trim(),
13863
+ policyDocument: result.policyDocument.trim(),
13864
+ policyUpdateSummary: result.policyUpdateSummary.trim(),
13865
+ insightSummary: result.insightSummary.trim()
13866
+ };
13867
+ }
13868
+ collectPendingUserInputs() {
13869
+ const drained = this.userInputQueue.drain();
13870
+ return [
13871
+ ...this.pendingInitialUserInput ? [this.pendingInitialUserInput] : [],
13872
+ ...drained.map((item) => item.text)
13873
+ ].map((text) => text.trim()).filter(Boolean);
13874
+ }
13875
+ appendUserInput(existing, next) {
13876
+ return existing.trim() === "" ? next : `${existing}
13877
+
13878
+ ${next}`;
13525
13879
  }
13526
13880
  async buildDynamicPrompt(memory) {
13527
13881
  return this.promptBuilder.build(this.userInput, this.state.getPhase() || PHASES.RECON, memory);
@@ -13550,6 +13904,9 @@ ${formatted}`;
13550
13904
  getEventEmitter() {
13551
13905
  return this.events;
13552
13906
  }
13907
+ getSessionRuntime() {
13908
+ return this.sessionRuntime;
13909
+ }
13553
13910
  setScope(allowed, exclusions = []) {
13554
13911
  this.state.setScope({
13555
13912
  allowedCidrs: allowed.filter((a) => a.includes("/")),
@@ -13565,18 +13922,37 @@ ${formatted}`;
13565
13922
  }
13566
13923
  enqueueUserInput(text) {
13567
13924
  this.userInputQueue.enqueue(text);
13925
+ this.emitQueueUpdated();
13568
13926
  }
13569
13927
  dequeueLastUserInput() {
13570
- return this.userInputQueue.dequeueLast();
13928
+ const value = this.userInputQueue.dequeueLast();
13929
+ this.emitQueueUpdated();
13930
+ return value;
13571
13931
  }
13572
13932
  hasPendingUserInput() {
13573
13933
  return this.userInputQueue.hasPending();
13574
13934
  }
13935
+ getPendingUserInputCount() {
13936
+ return this.userInputQueue.pendingCount();
13937
+ }
13938
+ getPendingUserInputPreview() {
13939
+ return this.userInputQueue.peek().map((item) => item.text);
13940
+ }
13941
+ emitQueueUpdated() {
13942
+ this.events.emit({
13943
+ type: EVENT_TYPES.QUEUE_UPDATED,
13944
+ timestamp: Date.now(),
13945
+ data: {
13946
+ count: this.getPendingUserInputCount(),
13947
+ preview: this.getPendingUserInputPreview()
13948
+ }
13949
+ });
13950
+ }
13575
13951
  };
13576
13952
 
13577
13953
  // src/engine/yaml-runtime.ts
13578
- import fs from "fs";
13579
- import path from "path";
13954
+ import fs2 from "fs";
13955
+ import path3 from "path";
13580
13956
  var YamlRuntime = class _YamlRuntime {
13581
13957
  static build(state, events, toolRegistry, approvalGate, scopeGuard) {
13582
13958
  const cfg = getPipelineConfig();
@@ -13584,14 +13960,15 @@ var YamlRuntime = class _YamlRuntime {
13584
13960
  return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
13585
13961
  }
13586
13962
  static setupWorkspace(workspaceCfg) {
13587
- const directories = workspaceCfg?.directories || {};
13963
+ const directories = workspaceCfg?.directories ?? {};
13588
13964
  for (const [key, dirPath] of Object.entries(directories)) {
13589
13965
  if (dirPath === "DEPRECATED") continue;
13590
- const resolved = path.resolve(process.cwd(), dirPath);
13591
- if (!fs.existsSync(resolved)) {
13592
- fs.mkdirSync(resolved, { recursive: true });
13966
+ const resolved = path3.resolve(process.cwd(), dirPath);
13967
+ if (!fs2.existsSync(resolved)) {
13968
+ fs2.mkdirSync(resolved, { recursive: true });
13593
13969
  }
13594
13970
  }
13971
+ ensurePolicyDocument();
13595
13972
  }
13596
13973
  };
13597
13974
 
@@ -13751,6 +14128,55 @@ var formatInlineStatus = () => {
13751
14128
  return JSON.stringify(statusData);
13752
14129
  };
13753
14130
 
14131
+ // src/platform/tui/utils/input-request-broker.ts
14132
+ function createInputRequestBrokerState() {
14133
+ return {
14134
+ active: { status: "inactive" },
14135
+ queue: []
14136
+ };
14137
+ }
14138
+ function sameRequest(a, b) {
14139
+ return a.prompt === b.prompt && a.inputType === b.inputType && a.context === b.context && a.source === b.source;
14140
+ }
14141
+ function sortQueue(queue) {
14142
+ return [...queue].sort((a, b) => b.priority - a.priority);
14143
+ }
14144
+ function enqueueInputRequest(state, request) {
14145
+ if (state.active.status === "inactive") {
14146
+ return {
14147
+ active: { status: "active", ...request },
14148
+ queue: []
14149
+ };
14150
+ }
14151
+ const existingIndex = state.queue.findIndex((item) => sameRequest(item, request));
14152
+ const nextQueue = existingIndex >= 0 ? state.queue.map((item, index) => index === existingIndex ? request : item) : [...state.queue, request];
14153
+ return {
14154
+ active: state.active,
14155
+ queue: sortQueue(nextQueue)
14156
+ };
14157
+ }
14158
+ function resolveActiveInputRequest(state) {
14159
+ const resolved = state.active;
14160
+ if (state.queue.length === 0) {
14161
+ return {
14162
+ resolved,
14163
+ nextState: { active: { status: "inactive" }, queue: [] }
14164
+ };
14165
+ }
14166
+ const [nextActive, ...rest] = sortQueue(state.queue);
14167
+ return {
14168
+ resolved,
14169
+ nextState: {
14170
+ active: { status: "active", ...nextActive },
14171
+ queue: rest
14172
+ }
14173
+ };
14174
+ }
14175
+ function clearAllInputRequests(state) {
14176
+ const pending = state.active.status === "active" ? [state.active, ...state.queue] : [...state.queue];
14177
+ return pending;
14178
+ }
14179
+
13754
14180
  // src/platform/tui/hooks/useAgentState.ts
13755
14181
  function sanitizeLiveProgress(progress) {
13756
14182
  return {
@@ -13769,11 +14195,15 @@ var useAgentState = (isTyping = false) => {
13769
14195
  const [elapsedTime, setElapsedTime] = useState(0);
13770
14196
  const [retryState, setRetryState] = useState({ status: "idle" });
13771
14197
  const [currentTokens, setCurrentTokens] = useState(0);
13772
- const [inputRequest, setInputRequest] = useState({ status: "inactive" });
14198
+ const [inputBroker, setInputBroker] = useState(
14199
+ createInputRequestBrokerState()
14200
+ );
13773
14201
  const [stats, setStats] = useState({ phase: DEFAULTS.INIT_PHASE, targets: 0, findings: 0, todo: 0, targetLabel: "" });
13774
14202
  const [turnCount, setTurnCount] = useState(0);
13775
14203
  const [liveProgressState, setLiveProgressState] = useState(DEFAULT_LIVE_PROGRESS);
13776
14204
  const startTimeRef = useRef(0);
14205
+ const inputBrokerRef = useRef(createInputRequestBrokerState());
14206
+ inputBrokerRef.current = inputBroker;
13777
14207
  const retryCountdownRef = useRef(null);
13778
14208
  const retryCountRef = useRef(0);
13779
14209
  const tokenAccumRef = useRef(0);
@@ -13824,6 +14254,21 @@ var useAgentState = (isTyping = false) => {
13824
14254
  retryCountdownRef.current = null;
13825
14255
  }
13826
14256
  }, []);
14257
+ const enqueueInputRequest2 = useCallback((request) => {
14258
+ setInputBroker((prev) => enqueueInputRequest(prev, request));
14259
+ }, []);
14260
+ const settleActiveInputRequest = useCallback((value) => {
14261
+ const current = inputBrokerRef.current;
14262
+ if (current.active.status !== "active") return;
14263
+ current.active.resolve(value);
14264
+ setInputBroker(resolveActiveInputRequest(current).nextState);
14265
+ }, []);
14266
+ const cancelAllInputRequests = useCallback(() => {
14267
+ const current = inputBrokerRef.current;
14268
+ const pending = clearAllInputRequests(current);
14269
+ for (const request of pending) request.resolve(null);
14270
+ setInputBroker(createInputRequestBrokerState());
14271
+ }, []);
13827
14272
  return {
13828
14273
  // State
13829
14274
  messages,
@@ -13837,8 +14282,8 @@ var useAgentState = (isTyping = false) => {
13837
14282
  setRetryState,
13838
14283
  currentTokens,
13839
14284
  setCurrentTokens,
13840
- inputRequest,
13841
- setInputRequest,
14285
+ inputRequest: inputBroker.active,
14286
+ pendingInputRequests: inputBroker.queue.length,
13842
14287
  stats,
13843
14288
  setStats,
13844
14289
  turnCount,
@@ -13857,7 +14302,10 @@ var useAgentState = (isTyping = false) => {
13857
14302
  addMessage,
13858
14303
  resetCumulativeCounters,
13859
14304
  manageTimer,
13860
- clearAllTimers
14305
+ clearAllTimers,
14306
+ enqueueInputRequest: enqueueInputRequest2,
14307
+ settleActiveInputRequest,
14308
+ cancelAllInputRequests
13861
14309
  };
13862
14310
  };
13863
14311
 
@@ -14220,7 +14668,7 @@ var AUXILIARY_STATUS_MAP = {
14220
14668
  post_step: UI_STATUS_MESSAGES.AUXILIARY_POST_STEP,
14221
14669
  turn_archive: UI_STATUS_MESSAGES.AUXILIARY_TURN_ARCHIVE,
14222
14670
  turn_cycle: UI_STATUS_MESSAGES.AUXILIARY_TURN_CYCLE,
14223
- drain_user_input: UI_STATUS_MESSAGES.AUXILIARY_DRAIN_INPUT,
14671
+ process_user_input: UI_STATUS_MESSAGES.AUXILIARY_DRAIN_INPUT,
14224
14672
  strategist: UI_STATUS_MESSAGES.AUXILIARY_STRATEGIST,
14225
14673
  build_system_prompt: UI_STATUS_MESSAGES.AUXILIARY_BUILD_PROMPT,
14226
14674
  core_inference: UI_STATUS_MESSAGES.AUXILIARY_CORE_INFERENCE,
@@ -14421,50 +14869,63 @@ function createReasoningHandlers(state, reasoningBufferRef, isTyping = false) {
14421
14869
  }
14422
14870
 
14423
14871
  // src/platform/tui/hooks/useAgentEvents/handlers/input.ts
14424
- function setupInputHandlers(setInputRequest, addMessage, setCurrentStatus) {
14425
- setInputHandler((p) => {
14872
+ var INPUT_REQUEST_PRIORITIES = {
14873
+ input_processor: 10,
14874
+ tool_prompt: 50,
14875
+ credential: 100
14876
+ };
14877
+ function buildRequestPayload(source, prompt, resolve, extra) {
14878
+ return {
14879
+ source,
14880
+ prompt,
14881
+ resolve,
14882
+ priority: INPUT_REQUEST_PRIORITIES[source],
14883
+ isPassword: extra.isPassword,
14884
+ inputType: extra.inputType,
14885
+ context: extra.context,
14886
+ optional: extra.optional,
14887
+ options: extra.options,
14888
+ placeholder: extra.placeholder
14889
+ };
14890
+ }
14891
+ function setupInputHandlers(runtime, enqueueInputRequest2, addMessage, setCurrentStatus) {
14892
+ runtime.setInputHandler((p) => {
14426
14893
  return new Promise((resolve) => {
14427
14894
  const isPassword = /password|passphrase/i.test(p);
14428
14895
  const inputType = /sudo/i.test(p) ? INPUT_TYPES.SUDO_PASSWORD : isPassword ? INPUT_TYPES.PASSWORD : INPUT_TYPES.TEXT;
14429
14896
  if (setCurrentStatus) setCurrentStatus("");
14430
14897
  addMessage("ai", `${UI_ICONS.INPUT_LOCK} ${p.trim()}`);
14431
- setInputRequest({
14432
- status: "active",
14433
- prompt: p.trim(),
14898
+ enqueueInputRequest2(buildRequestPayload("tool_prompt", p.trim(), resolve, {
14434
14899
  isPassword,
14435
- inputType,
14436
- resolve
14437
- });
14900
+ inputType
14901
+ }));
14438
14902
  });
14439
14903
  });
14440
- setCredentialHandler((request) => {
14904
+ runtime.setCredentialHandler((request) => {
14441
14905
  return new Promise((resolve) => {
14442
14906
  const isPassword = SENSITIVE_INPUT_TYPES.includes(request.type);
14443
14907
  const displayPrompt = buildCredentialPrompt(request);
14444
14908
  if (setCurrentStatus) setCurrentStatus("");
14445
14909
  addMessage("ai", `${UI_ICONS.INPUT_LOCK} ${displayPrompt}`);
14446
- setInputRequest({
14447
- status: "active",
14448
- prompt: displayPrompt,
14910
+ enqueueInputRequest2(buildRequestPayload("credential", displayPrompt, resolve, {
14449
14911
  isPassword,
14450
14912
  inputType: request.type,
14451
14913
  context: request.context,
14452
14914
  optional: request.isOptional,
14453
- options: request.options,
14454
- resolve
14455
- });
14915
+ options: request.options
14916
+ }));
14456
14917
  });
14457
14918
  });
14458
14919
  return () => {
14459
- clearInputHandler();
14460
- clearCredentialHandler();
14920
+ runtime.clearInputHandler();
14921
+ runtime.clearCredentialHandler();
14461
14922
  };
14462
14923
  }
14463
14924
 
14464
14925
  // src/platform/tui/hooks/useAgentEvents/handlers/command.ts
14465
- function setupCommandHandlers(addMessage, setCurrentStatus) {
14926
+ function setupCommandHandlers(runtime, addMessage, setCurrentStatus) {
14466
14927
  let lastStatusBase = "";
14467
- setCommandEventEmitter((event) => {
14928
+ runtime.setCommandEventEmitter((event) => {
14468
14929
  if (event.type === COMMAND_EVENT_TYPES.COMMAND_START) {
14469
14930
  lastStatusBase = event.message;
14470
14931
  setCurrentStatus(event.message);
@@ -14490,7 +14951,7 @@ ${UI_ICONS.LIVE_PREFIX}${cleanLine}`);
14490
14951
  addMessage("system", msg);
14491
14952
  });
14492
14953
  return () => {
14493
- clearCommandEventEmitter();
14954
+ runtime.clearCommandEventEmitter();
14494
14955
  };
14495
14956
  }
14496
14957
 
@@ -14500,7 +14961,7 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14500
14961
  addMessage,
14501
14962
  setCurrentStatus,
14502
14963
  setRetryState,
14503
- setInputRequest,
14964
+ enqueueInputRequest: enqueueInputRequest2,
14504
14965
  setStats,
14505
14966
  setLiveProgress,
14506
14967
  clearAllTimers,
@@ -14518,6 +14979,8 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14518
14979
  };
14519
14980
  useEffect(() => {
14520
14981
  const events = eventsRef.current;
14982
+ const runtime = agent.getSessionRuntime();
14983
+ setActiveSessionRuntime(runtime);
14521
14984
  const toolHandlers = createToolHandlers({ addMessage, setCurrentStatus, setLiveProgress, toolStartedAtRef });
14522
14985
  const lifecycleHandlers = createLifecycleHandlers(agent, {
14523
14986
  addMessage,
@@ -14536,8 +14999,8 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14536
14999
  reasoningBufferRef,
14537
15000
  isTyping
14538
15001
  );
14539
- const cleanupInput = setupInputHandlers(setInputRequest, addMessage, setCurrentStatus);
14540
- const cleanupCommand = setupCommandHandlers(addMessage, setCurrentStatus);
15002
+ const cleanupInput = setupInputHandlers(runtime, enqueueInputRequest2, addMessage, setCurrentStatus);
15003
+ const cleanupCommand = setupCommandHandlers(runtime, addMessage, setCurrentStatus);
14541
15004
  const updateStats = () => {
14542
15005
  const s = agent.getState();
14543
15006
  setStats({
@@ -14589,6 +15052,7 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14589
15052
  clearAllTimers();
14590
15053
  cleanupInput();
14591
15054
  cleanupCommand();
15055
+ clearActiveSessionRuntime(runtime);
14592
15056
  };
14593
15057
  }, [
14594
15058
  agent,
@@ -14596,7 +15060,7 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14596
15060
  setCurrentStatus,
14597
15061
  setRetryState,
14598
15062
  setCurrentTokens,
14599
- setInputRequest,
15063
+ enqueueInputRequest2,
14600
15064
  setStats,
14601
15065
  setLiveProgress,
14602
15066
  retryCountdownRef,
@@ -14611,6 +15075,11 @@ var useAgentEvents = (agent, eventsRef, state, isTyping = false) => {
14611
15075
  };
14612
15076
 
14613
15077
  // src/platform/tui/hooks/useAgent.ts
15078
+ function trimQueuedMessages(queue, maxLength, maxItems) {
15079
+ const trimmed = queue.map((text) => truncate(text, maxLength));
15080
+ if (trimmed.length <= maxItems) return trimmed;
15081
+ return trimmed.slice(trimmed.length - maxItems);
15082
+ }
14614
15083
  var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14615
15084
  const [agent] = useState2(() => createMainAgent(shouldAutoApprove));
14616
15085
  const eventsRef = useRef3(agent.getEventEmitter());
@@ -14626,7 +15095,7 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14626
15095
  retryState,
14627
15096
  currentTokens,
14628
15097
  inputRequest,
14629
- setInputRequest,
15098
+ pendingInputRequests,
14630
15099
  stats,
14631
15100
  setStats,
14632
15101
  addMessage,
@@ -14634,17 +15103,25 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14634
15103
  resetCumulativeCounters,
14635
15104
  turnCount,
14636
15105
  setTurnCount,
14637
- liveProgress
15106
+ liveProgress,
15107
+ settleActiveInputRequest,
15108
+ cancelAllInputRequests
14638
15109
  } = state;
14639
15110
  const [messageQueue, setMessageQueue] = useState2([]);
15111
+ const messageQueueLengthRef = useRef3(0);
14640
15112
  const setMessageQueueSafe = useCallback2((value) => {
14641
15113
  setMessageQueue((prev) => {
14642
15114
  const next = typeof value === "function" ? value(prev) : value;
14643
- const trimmed = next.map((t) => truncate(t, TUI_DISPLAY_LIMITS.QUEUED_MESSAGE_TEXT_MAX));
14644
- if (trimmed.length <= TUI_DISPLAY_LIMITS.MAX_QUEUED_MESSAGES) return trimmed;
14645
- return trimmed.slice(trimmed.length - TUI_DISPLAY_LIMITS.MAX_QUEUED_MESSAGES);
15115
+ return trimQueuedMessages(
15116
+ next,
15117
+ TUI_DISPLAY_LIMITS.QUEUED_MESSAGE_TEXT_MAX,
15118
+ TUI_DISPLAY_LIMITS.MAX_QUEUED_MESSAGES
15119
+ );
14646
15120
  });
14647
15121
  }, []);
15122
+ useEffect2(() => {
15123
+ messageQueueLengthRef.current = messageQueue.length;
15124
+ }, [messageQueue.length]);
14648
15125
  useEffect2(() => {
14649
15126
  if (target) {
14650
15127
  agent.addTarget(target);
@@ -14652,19 +15129,22 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14652
15129
  }
14653
15130
  }, [agent, target]);
14654
15131
  useEffect2(() => {
15132
+ const handleQueueUpdated = (event) => {
15133
+ setMessageQueueSafe(event.data.preview);
15134
+ };
14655
15135
  const handleQueueDrained = () => {
14656
- setMessageQueueSafe((prev) => {
14657
- if (prev.length > 0) {
14658
- addMessage("system", `Queued input applied (${prev.length})`);
14659
- }
14660
- return [];
14661
- });
15136
+ if (messageQueueLengthRef.current > 0) {
15137
+ addMessage("system", `Queued input applied (${messageQueueLengthRef.current})`);
15138
+ }
14662
15139
  };
15140
+ setMessageQueueSafe(agent.getPendingUserInputPreview());
15141
+ agent.getEventEmitter().on(EVENT_TYPES.QUEUE_UPDATED, handleQueueUpdated);
14663
15142
  agent.getEventEmitter().on(EVENT_TYPES.QUEUE_DRAINED, handleQueueDrained);
14664
15143
  return () => {
15144
+ agent.getEventEmitter().off(EVENT_TYPES.QUEUE_UPDATED, handleQueueUpdated);
14665
15145
  agent.getEventEmitter().off(EVENT_TYPES.QUEUE_DRAINED, handleQueueDrained);
14666
15146
  };
14667
- }, [agent, setMessageQueueSafe, addMessage]);
15147
+ }, [agent, addMessage, setMessageQueueSafe]);
14668
15148
  useAgentEvents(agent, eventsRef, state, isTyping);
14669
15149
  const abortedRef = useRef3(false);
14670
15150
  const executeTask = useCallback2(async (task) => {
@@ -14689,7 +15169,6 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14689
15169
  }
14690
15170
  if (agent.hasPendingUserInput()) {
14691
15171
  currentTask = "";
14692
- setMessageQueueSafe([]);
14693
15172
  continuing = true;
14694
15173
  continue;
14695
15174
  }
@@ -14721,24 +15200,21 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14721
15200
  manageTimer("stop");
14722
15201
  setCurrentStatus("");
14723
15202
  addMessage("system", UI_MESSAGES.INTERRUPTED);
14724
- setMessageQueueSafe([]);
14725
- }, [agent, addMessage, manageTimer, setIsProcessing, setCurrentStatus, setMessageQueueSafe]);
15203
+ }, [agent, addMessage, manageTimer, setIsProcessing, setCurrentStatus]);
14726
15204
  const recallQueuedInput = useCallback2(() => {
14727
15205
  const recalled = agent.dequeueLastUserInput();
14728
15206
  if (!recalled) return null;
14729
- setMessageQueueSafe((prev) => prev.length > 0 ? prev.slice(0, -1) : prev);
14730
15207
  return recalled;
14731
- }, [agent, setMessageQueueSafe]);
15208
+ }, [agent]);
14732
15209
  const inputRequestRef = useRef3(inputRequest);
14733
15210
  inputRequestRef.current = inputRequest;
14734
15211
  const cancelInputRequest = useCallback2(() => {
14735
15212
  const ir = inputRequestRef.current;
14736
15213
  if (ir.status === "active") {
14737
- ir.resolve(null);
14738
- setInputRequest({ status: "inactive" });
15214
+ settleActiveInputRequest(null);
14739
15215
  addMessage("system", UI_MESSAGES.INPUT_CANCELLED);
14740
15216
  }
14741
- }, [setInputRequest, addMessage]);
15217
+ }, [settleActiveInputRequest, addMessage]);
14742
15218
  const refreshStats = useCallback2(() => {
14743
15219
  const s = agent.getState();
14744
15220
  setStats({
@@ -14761,15 +15237,16 @@ var useAgent = (shouldAutoApprove, target, isTyping = false) => {
14761
15237
  currentTokens,
14762
15238
  liveProgress,
14763
15239
  inputRequest,
14764
- setInputRequest,
15240
+ pendingInputRequests,
14765
15241
  stats,
14766
15242
  turnCount,
14767
15243
  messageQueue,
14768
- setMessageQueue: setMessageQueueSafe,
14769
15244
  recallQueuedInput,
14770
15245
  executeTask,
14771
15246
  abort,
14772
15247
  cancelInputRequest,
15248
+ settleActiveInputRequest,
15249
+ cancelAllInputRequests,
14773
15250
  addMessage,
14774
15251
  refreshStats
14775
15252
  };
@@ -15556,15 +16033,16 @@ var useAppLogic = ({ autoApprove = false, target }) => {
15556
16033
  currentTokens,
15557
16034
  liveProgress = DEFAULT_LIVE_PROGRESS,
15558
16035
  inputRequest,
15559
- setInputRequest,
16036
+ pendingInputRequests,
15560
16037
  stats,
15561
16038
  turnCount,
15562
16039
  messageQueue,
15563
- setMessageQueue,
15564
16040
  recallQueuedInput,
15565
16041
  executeTask,
15566
16042
  abort,
15567
16043
  cancelInputRequest,
16044
+ settleActiveInputRequest,
16045
+ cancelAllInputRequests,
15568
16046
  addMessage,
15569
16047
  refreshStats
15570
16048
  } = useAgent(autoApproveMode, target, isTyping);
@@ -15597,16 +16075,12 @@ var useAppLogic = ({ autoApprove = false, target }) => {
15597
16075
  });
15598
16076
  }, [terminalHeight]);
15599
16077
  const handleExit = useCallback7(() => {
15600
- const ir = inputRequestRef.current;
15601
- if (ir.status === "active") {
15602
- ir.resolve(null);
15603
- setInputRequest({ status: "inactive" });
15604
- }
16078
+ cancelAllInputRequests();
15605
16079
  cleanupAllProcesses().catch(() => {
15606
16080
  });
15607
16081
  exit();
15608
16082
  setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
15609
- }, [exit, setInputRequest]);
16083
+ }, [exit, cancelAllInputRequests]);
15610
16084
  const { handleCommand } = useCommands({
15611
16085
  agent,
15612
16086
  addMessage,
@@ -15630,10 +16104,9 @@ var useAppLogic = ({ autoApprove = false, target }) => {
15630
16104
  const sanitized = sanitizeInput(value).slice(0, TUI_DISPLAY_LIMITS.MAX_INPUT_CHARS);
15631
16105
  const displayText = ir.isPassword ? "\u2022".repeat(Math.min(sanitized.length, TUI_DISPLAY_LIMITS.PASSWORD_MASK_MAX)) : sanitized;
15632
16106
  addMessage("user", displayText);
15633
- ir.resolve(sanitized);
15634
- setInputRequest({ status: "inactive" });
16107
+ settleActiveInputRequest(sanitized);
15635
16108
  setSecretInput("");
15636
- }, [addMessage, setInputRequest]);
16109
+ }, [addMessage, settleActiveInputRequest]);
15637
16110
  const handleSubmit = useCallback7(async (value) => {
15638
16111
  const trimmed = sanitizeInput(value);
15639
16112
  if (!trimmed) return;
@@ -15650,10 +16123,8 @@ var useAppLogic = ({ autoApprove = false, target }) => {
15650
16123
  handleSecretSubmit(safeInput);
15651
16124
  } else if (isProcessingRef.current) {
15652
16125
  agent.enqueueUserInput(safeInput);
15653
- setMessageQueue((prev) => [...prev, safeInput]);
15654
16126
  } else {
15655
16127
  addMessage("user", safeInput);
15656
- setMessageQueue([]);
15657
16128
  await executeTask(safeInput);
15658
16129
  }
15659
16130
  }, [agent, addMessage, executeTask, handleCommand, handleSecretSubmit]);
@@ -15690,6 +16161,7 @@ var useAppLogic = ({ autoApprove = false, target }) => {
15690
16161
  currentTokens,
15691
16162
  liveProgress,
15692
16163
  inputRequest,
16164
+ pendingInputRequests,
15693
16165
  stats,
15694
16166
  turnCount,
15695
16167
  messageQueue,
@@ -16327,11 +16799,115 @@ import { Box as Box8, Text as Text8 } from "ink";
16327
16799
  // src/platform/tui/components/BlinkingCircle.tsx
16328
16800
  import { memo as memo7 } from "react";
16329
16801
  import { Text as Text7 } from "ink";
16802
+
16803
+ // src/platform/tui/utils/animation-style.ts
16804
+ var DOCKER_BLUE = "#0db7ed";
16805
+ var DOCKER_BLUE_DARK = "#2496ed";
16806
+ var BLINKING_CIRCLE_PHASE_LENGTH = 8;
16807
+ var BLINKING_CIRCLE_SOLID_PHASE = 4;
16808
+ var SPLASH_PULSE_PERIOD_DIVISOR = 10;
16809
+ var SPLASH_PULSE_BOLD_THRESHOLD = 0.55;
16810
+ var SPLASH_PULSE_DIM_THRESHOLD = 0.3;
16811
+ var SPLASH_PULSE_BRIGHT_THRESHOLD = 0.65;
16812
+ var SPLASH_PULSE_MID_THRESHOLD = 0.4;
16813
+ var SHIMMER_COLOR_STEPS = 64;
16814
+ var COIN_FRAME_FIXED_WIDTH = 40;
16815
+ function getBlinkingCircleFrame(tick, activeColor = THEME.primary) {
16816
+ const phase = tick % BLINKING_CIRCLE_PHASE_LENGTH;
16817
+ const isSolid = phase < BLINKING_CIRCLE_SOLID_PHASE;
16818
+ return {
16819
+ glyph: isSolid ? "\u25CF" : "\u25CC",
16820
+ color: isSolid ? activeColor : THEME.cyan,
16821
+ bold: isSolid,
16822
+ dimColor: !isSolid
16823
+ };
16824
+ }
16825
+ function normalizeFrameLines(frame) {
16826
+ const width = COIN_FRAME_FIXED_WIDTH;
16827
+ return {
16828
+ width,
16829
+ // Center each line within the fixed width
16830
+ lines: frame.map((line) => {
16831
+ const trimmed = line.trimEnd();
16832
+ const padding = Math.floor((width - trimmed.length) / 2);
16833
+ const leftPad = " ".repeat(Math.max(0, padding));
16834
+ const rightPad = " ".repeat(Math.max(0, width - trimmed.length - padding));
16835
+ return leftPad + trimmed + rightPad;
16836
+ })
16837
+ };
16838
+ }
16839
+ function hslToHex(h, s, l) {
16840
+ const a = s * Math.min(l, 1 - l);
16841
+ const f = (n) => {
16842
+ const k = (n + h / 30) % 12;
16843
+ const c = l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
16844
+ return Math.round(255 * c).toString(16).padStart(2, "0");
16845
+ };
16846
+ return `#${f(0)}${f(8)}${f(4)}`;
16847
+ }
16848
+ function coinHue(tick) {
16849
+ const t = (Math.sin(tick * (Math.PI / 960)) + 1) / 2;
16850
+ return hslToHex(197 + t * 15, 0.85, 0.45 + t * 0.15);
16851
+ }
16852
+ function getSplashPulseTone(tick) {
16853
+ const pulse = (Math.sin(tick * (Math.PI / SPLASH_PULSE_PERIOD_DIVISOR)) + 1) / 2;
16854
+ return {
16855
+ bold: pulse > SPLASH_PULSE_BOLD_THRESHOLD,
16856
+ dimColor: pulse < SPLASH_PULSE_DIM_THRESHOLD,
16857
+ color: pulse > SPLASH_PULSE_BRIGHT_THRESHOLD ? "#ffffff" : pulse > SPLASH_PULSE_MID_THRESHOLD ? DOCKER_BLUE : DOCKER_BLUE_DARK
16858
+ // #2496ed
16859
+ };
16860
+ }
16861
+ function lerp(a, b, t) {
16862
+ return Math.round(a + (b - a) * t);
16863
+ }
16864
+ function hexToRgb(hex) {
16865
+ return [
16866
+ parseInt(hex.slice(1, 3), 16),
16867
+ parseInt(hex.slice(3, 5), 16),
16868
+ parseInt(hex.slice(5, 7), 16)
16869
+ ];
16870
+ }
16871
+ function rgbToHex([r, g, b]) {
16872
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
16873
+ }
16874
+ function buildNeutralShimmerPalette() {
16875
+ return Array.from({ length: SHIMMER_COLOR_STEPS }, (_, i) => {
16876
+ const t = i / (SHIMMER_COLOR_STEPS - 1);
16877
+ const v = Math.round(88 + t * (255 - 88));
16878
+ const h = v.toString(16).padStart(2, "0");
16879
+ return `#${h}${h}${h}`;
16880
+ });
16881
+ }
16882
+ function buildAccentShimmerPalette() {
16883
+ const accentStart = hexToRgb(DOCKER_BLUE_DARK);
16884
+ const accentMid = hexToRgb(DOCKER_BLUE);
16885
+ const accentPeak = hexToRgb("#ffffff");
16886
+ return Array.from({ length: SHIMMER_COLOR_STEPS }, (_, i) => {
16887
+ const t = i / (SHIMMER_COLOR_STEPS - 1);
16888
+ if (t < 0.6) {
16889
+ const local2 = t / 0.6;
16890
+ return rgbToHex([
16891
+ lerp(accentStart[0], accentMid[0], local2),
16892
+ lerp(accentStart[1], accentMid[1], local2),
16893
+ lerp(accentStart[2], accentMid[2], local2)
16894
+ ]);
16895
+ }
16896
+ const local = (t - 0.6) / 0.4;
16897
+ return rgbToHex([
16898
+ lerp(accentMid[0], accentPeak[0], local),
16899
+ lerp(accentMid[1], accentPeak[1], local),
16900
+ lerp(accentMid[2], accentPeak[2], local)
16901
+ ]);
16902
+ });
16903
+ }
16904
+
16905
+ // src/platform/tui/components/BlinkingCircle.tsx
16330
16906
  import { jsx as jsx9 } from "react/jsx-runtime";
16331
16907
  var BlinkingCircle = memo7(({ color }) => {
16332
16908
  const tick = useAnimationTick();
16333
- const isVisible = tick % 10 < 5;
16334
- return /* @__PURE__ */ jsx9(Text7, { color: color || THEME.primary, children: isVisible ? "\u25CF" : " " });
16909
+ const frame = getBlinkingCircleFrame(tick, color);
16910
+ return /* @__PURE__ */ jsx9(Text7, { color: frame.color, bold: frame.bold, dimColor: frame.dimColor, children: frame.glyph });
16335
16911
  });
16336
16912
 
16337
16913
  // src/platform/tui/components/status/RetryView.tsx
@@ -16356,22 +16932,23 @@ import { memo as memo8, useMemo as useMemo2 } from "react";
16356
16932
  import { Text as Text9 } from "ink";
16357
16933
  import { jsx as jsx11 } from "react/jsx-runtime";
16358
16934
  var SPOT_WIDTH = 6;
16359
- var SWEEP_SPEED = 0.6;
16935
+ var SWEEP_SPEED = 1.5;
16360
16936
  var LOOP_PAD = SPOT_WIDTH * 2;
16361
16937
  var TICK_WRAP = 1e6;
16362
- var COLOR_STEPS = 64;
16363
- var PALETTE = Array.from({ length: COLOR_STEPS }, (_, i) => {
16364
- const t = i / (COLOR_STEPS - 1);
16365
- const v = Math.round(88 + t * (255 - 88));
16366
- const h = v.toString(16).padStart(2, "0");
16367
- return `#${h}${h}${h}`;
16368
- });
16938
+ var NEUTRAL_PALETTE = buildNeutralShimmerPalette();
16939
+ var ACCENT_PALETTE = buildAccentShimmerPalette();
16369
16940
  function gaussian(x, sigma) {
16370
16941
  return Math.exp(-(x * x) / (2 * sigma * sigma));
16371
16942
  }
16372
- var ShimmerText = memo8(({ children, bold, phase = 0 }) => {
16943
+ var ShimmerText = memo8(({
16944
+ children,
16945
+ bold,
16946
+ phase = 0,
16947
+ variant = "neutral"
16948
+ }) => {
16373
16949
  const tick = useAnimationTick() % TICK_WRAP;
16374
16950
  const len = children.length;
16951
+ const palette = variant === "accent" ? ACCENT_PALETTE : NEUTRAL_PALETTE;
16375
16952
  const chars = useMemo2(() => Array.from(children), [children]);
16376
16953
  const loopLen = len + LOOP_PAD;
16377
16954
  const rawPos = (tick * SWEEP_SPEED + phase * loopLen) % loopLen;
@@ -16380,8 +16957,8 @@ var ShimmerText = memo8(({ children, bold, phase = 0 }) => {
16380
16957
  return /* @__PURE__ */ jsx11(Text9, { bold, children: chars.map((char, i) => {
16381
16958
  const dist = Math.abs(i - spotPos);
16382
16959
  const brightness = gaussian(dist, sigma);
16383
- const idx = Math.min(COLOR_STEPS - 1, Math.floor(brightness * COLOR_STEPS));
16384
- const color = PALETTE[idx];
16960
+ const idx = Math.min(SHIMMER_COLOR_STEPS - 1, Math.floor(brightness * SHIMMER_COLOR_STEPS));
16961
+ const color = palette[idx];
16385
16962
  return /* @__PURE__ */ jsx11(Text9, { color, children: char }, i);
16386
16963
  }) });
16387
16964
  });
@@ -16420,7 +16997,7 @@ var ProcessingView = ({
16420
16997
  return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
16421
16998
  /* @__PURE__ */ jsxs8(Box9, { children: [
16422
16999
  /* @__PURE__ */ jsx12(Box9, { width: 2, flexShrink: 0, children: isActive ? /* @__PURE__ */ jsx12(BlinkingCircle, { color }) : /* @__PURE__ */ jsx12(Text10, { color: THEME.dimGray, children: "\u2022" }) }),
16423
- isActive ? /* @__PURE__ */ jsx12(ShimmerText, { bold: true, children: stageText }) : /* @__PURE__ */ jsx12(Text10, { bold: true, color: stageColor, children: stageText }),
17000
+ isActive ? /* @__PURE__ */ jsx12(ShimmerText, { bold: true, variant: "accent", children: stageText }) : /* @__PURE__ */ jsx12(Text10, { bold: true, color: stageColor, children: stageText }),
16424
17001
  /* @__PURE__ */ jsxs8(Text10, { color: THEME.dimGray, dimColor: true, wrap: "truncate", children: [
16425
17002
  " ",
16426
17003
  metaSuffix
@@ -16665,6 +17242,7 @@ var SimpleTextInput = ({
16665
17242
  import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
16666
17243
  var SecretInputArea = ({
16667
17244
  inputRequest,
17245
+ pendingCount = 0,
16668
17246
  secretInput,
16669
17247
  setSecretInput,
16670
17248
  onSecretSubmit
@@ -16677,7 +17255,8 @@ var SecretInputArea = ({
16677
17255
  children: [
16678
17256
  /* @__PURE__ */ jsxs11(Box12, { children: [
16679
17257
  /* @__PURE__ */ jsx16(Text13, { color: THEME.yellow, children: "secure input" }),
16680
- /* @__PURE__ */ jsx16(Text13, { color: THEME.gray, dimColor: true, children: " hidden in transcript" })
17258
+ /* @__PURE__ */ jsx16(Text13, { color: THEME.gray, dimColor: true, children: " hidden in transcript" }),
17259
+ pendingCount > 0 && /* @__PURE__ */ jsx16(Text13, { color: THEME.gray, dimColor: true, children: ` ${pendingCount} waiting` })
16681
17260
  ] }),
16682
17261
  /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", children: [
16683
17262
  /* @__PURE__ */ jsx16(Box12, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx16(Text13, { color: THEME.yellow, children: "!" }) }),
@@ -16749,6 +17328,7 @@ var ChatInput = memo10(({
16749
17328
  queuedPreview = "",
16750
17329
  placeholder,
16751
17330
  inputRequest,
17331
+ pendingInputCount = 0,
16752
17332
  secretInput,
16753
17333
  setSecretInput,
16754
17334
  onSecretSubmit
@@ -16879,6 +17459,7 @@ var ChatInput = memo10(({
16879
17459
  SecretInputArea,
16880
17460
  {
16881
17461
  inputRequest,
17462
+ pendingCount: pendingInputCount,
16882
17463
  secretInput,
16883
17464
  setSecretInput: handleSecretChange,
16884
17465
  onSecretSubmit: wrappedSecretSubmit
@@ -17081,6 +17662,7 @@ var BottomRegion = ({
17081
17662
  recallQueuedInput,
17082
17663
  handleSubmit,
17083
17664
  inputRequest,
17665
+ pendingInputRequests,
17084
17666
  secretInput,
17085
17667
  setSecretInput,
17086
17668
  handleSecretSubmit,
@@ -17121,11 +17703,13 @@ var BottomRegion = ({
17121
17703
  queuedPreview,
17122
17704
  placeholder: "Describe the target or type /help",
17123
17705
  inputRequest,
17706
+ pendingInputCount: pendingInputRequests,
17124
17707
  secretInput,
17125
17708
  setSecretInput,
17126
17709
  onSecretSubmit: handleSecretSubmit
17127
17710
  }
17128
17711
  ) }),
17712
+ /* @__PURE__ */ jsx21(Box17, { width: "100%", children: /* @__PURE__ */ jsx21(Text18, { dimColor: true, color: THEME.dimGray, children: "\u2500".repeat(Math.max(1, columns - 2)) }) }),
17129
17713
  /* @__PURE__ */ jsx21(Box17, { width: "100%", children: /* @__PURE__ */ jsx21(
17130
17714
  footer_default,
17131
17715
  {
@@ -17210,6 +17794,7 @@ var App = ({ autoApprove = false, target }) => {
17210
17794
  currentTokens,
17211
17795
  liveProgress,
17212
17796
  inputRequest,
17797
+ pendingInputRequests,
17213
17798
  stats,
17214
17799
  turnCount,
17215
17800
  messageQueue,
@@ -17250,6 +17835,7 @@ var App = ({ autoApprove = false, target }) => {
17250
17835
  recallQueuedInput: handleRecallQueuedInput,
17251
17836
  handleSubmit,
17252
17837
  inputRequest,
17838
+ pendingInputRequests,
17253
17839
  secretInput,
17254
17840
  setSecretInput,
17255
17841
  handleSecretSubmit,
@@ -17964,19 +18550,6 @@ var COIN_FRAMES = [
17964
18550
 
17965
18551
  // src/platform/tui/components/SplashScreen.tsx
17966
18552
  import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
17967
- function hslToHex(h, s, l) {
17968
- const a = s * Math.min(l, 1 - l);
17969
- const f = (n) => {
17970
- const k = (n + h / 30) % 12;
17971
- const c = l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
17972
- return Math.round(255 * c).toString(16).padStart(2, "0");
17973
- };
17974
- return `#${f(0)}${f(8)}${f(4)}`;
17975
- }
17976
- function coinHue(tick) {
17977
- const t = (Math.sin(tick * (Math.PI / 960)) + 1) / 2;
17978
- return hslToHex(210 * (1 - t), 0.85, 0.55);
17979
- }
17980
18553
  var SplashScreen = ({
17981
18554
  onComplete,
17982
18555
  durationMs = 3e3
@@ -17998,7 +18571,9 @@ var SplashScreen = ({
17998
18571
  return () => clearInterval(interval);
17999
18572
  }, [durationMs, onComplete]);
18000
18573
  const frame = COIN_FRAMES[tick % COIN_FRAMES.length];
18574
+ const normalizedFrame = normalizeFrameLines(frame);
18001
18575
  const coinColor = coinHue(tick);
18576
+ const titleTone = getSplashPulseTone(tick);
18002
18577
  const isFading = elapsed > durationMs - 500;
18003
18578
  return /* @__PURE__ */ jsx23(
18004
18579
  Box19,
@@ -18009,10 +18584,19 @@ var SplashScreen = ({
18009
18584
  alignItems: "center",
18010
18585
  justifyContent: "center",
18011
18586
  children: /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", alignItems: "center", flexShrink: 0, children: [
18012
- /* @__PURE__ */ jsx23(Box19, { flexDirection: "column", alignItems: "center", children: frame.map((line, i) => {
18013
- return /* @__PURE__ */ jsx23(Box19, { flexShrink: 0, height: 1, children: /* @__PURE__ */ jsx23(Text19, { color: coinColor, bold: !isFading, dimColor: isFading, wrap: "truncate-end", children: line }) }, i);
18587
+ /* @__PURE__ */ jsx23(Box19, { flexDirection: "column", alignItems: "center", children: normalizedFrame.lines.map((line, i) => {
18588
+ return /* @__PURE__ */ jsx23(Box19, { flexShrink: 0, height: 1, width: normalizedFrame.width, children: /* @__PURE__ */ jsx23(Text19, { color: coinColor, bold: !isFading, dimColor: isFading, wrap: "truncate-end", children: line }) }, i);
18014
18589
  }) }),
18015
- /* @__PURE__ */ jsx23(Box19, { marginTop: 2, flexShrink: 0, children: /* @__PURE__ */ jsx23(Text19, { bold: true, dimColor: isFading, children: "Pentesting Agent Starting..." }) })
18590
+ /* @__PURE__ */ jsx23(Box19, { marginTop: 2, flexShrink: 0, children: isFading ? /* @__PURE__ */ jsx23(Text19, { bold: true, dimColor: true, children: "Pentesting Agent Starting..." }) : /* @__PURE__ */ jsx23(ShimmerText, { bold: true, variant: "accent", children: "Pentesting Agent Starting..." }) }),
18591
+ /* @__PURE__ */ jsx23(Box19, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx23(
18592
+ Text19,
18593
+ {
18594
+ color: titleTone.color,
18595
+ bold: titleTone.bold && !isFading,
18596
+ dimColor: isFading || titleTone.dimColor,
18597
+ children: "Initializing autonomous workflow"
18598
+ }
18599
+ ) })
18016
18600
  ] })
18017
18601
  }
18018
18602
  );
@@ -18075,14 +18659,14 @@ async function runAction(objective, options) {
18075
18659
  console.log(chalk2.hex(HEX.gray)("\n[+] Assessment complete!\n"));
18076
18660
  console.log(result);
18077
18661
  if (options.output) {
18078
- const fs2 = await import("fs/promises");
18662
+ const fs3 = await import("fs/promises");
18079
18663
  const { dirname: dirname2 } = await import("path");
18080
18664
  const outputDir = dirname2(options.output);
18081
18665
  if (outputDir && outputDir !== ".") {
18082
- await fs2.mkdir(outputDir, { recursive: true }).catch(() => {
18666
+ await fs3.mkdir(outputDir, { recursive: true }).catch(() => {
18083
18667
  });
18084
18668
  }
18085
- await fs2.writeFile(options.output, JSON.stringify({ result }, null, 2));
18669
+ await fs3.writeFile(options.output, JSON.stringify({ result }, null, 2));
18086
18670
  console.log(chalk2.hex(HEX.primary)(`
18087
18671
  [+] Report saved to: ${options.output}`));
18088
18672
  }