@pentoshi/clai 3.0.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/classic-renderer.js +9 -1
  3. package/dist/agent/classic-renderer.js.map +1 -1
  4. package/dist/agent/context-manager.js +8 -2
  5. package/dist/agent/context-manager.js.map +1 -1
  6. package/dist/agent/events.d.ts +5 -0
  7. package/dist/agent/loop-guard.d.ts +2 -0
  8. package/dist/agent/loop-guard.js +39 -2
  9. package/dist/agent/loop-guard.js.map +1 -1
  10. package/dist/agent/plan-tool.d.ts +22 -0
  11. package/dist/agent/plan-tool.js +434 -49
  12. package/dist/agent/plan-tool.js.map +1 -1
  13. package/dist/agent/project-root.d.ts +33 -0
  14. package/dist/agent/project-root.js +158 -0
  15. package/dist/agent/project-root.js.map +1 -0
  16. package/dist/agent/runner.js +1204 -253
  17. package/dist/agent/runner.js.map +1 -1
  18. package/dist/agent/task-evidence.d.ts +103 -0
  19. package/dist/agent/task-evidence.js +413 -0
  20. package/dist/agent/task-evidence.js.map +1 -0
  21. package/dist/agent/tool-call-parser.d.ts +21 -0
  22. package/dist/agent/tool-call-parser.js +101 -28
  23. package/dist/agent/tool-call-parser.js.map +1 -1
  24. package/dist/agent/tool-history.d.ts +27 -0
  25. package/dist/agent/tool-history.js +150 -0
  26. package/dist/agent/tool-history.js.map +1 -0
  27. package/dist/agent/workspace-orient.d.ts +64 -0
  28. package/dist/agent/workspace-orient.js +418 -0
  29. package/dist/agent/workspace-orient.js.map +1 -0
  30. package/dist/app/adapters/agent-event-adapter.js +5 -0
  31. package/dist/app/adapters/agent-event-adapter.js.map +1 -1
  32. package/dist/app/controllers/session-controller.js +4 -0
  33. package/dist/app/controllers/session-controller.js.map +1 -1
  34. package/dist/app/events/app-event.d.ts +4 -0
  35. package/dist/app/events/app-event.js.map +1 -1
  36. package/dist/commands/doctor.js +14 -1
  37. package/dist/commands/doctor.js.map +1 -1
  38. package/dist/commands/update.js +1 -1
  39. package/dist/index.js +40 -8
  40. package/dist/index.js.map +1 -1
  41. package/dist/llm/adapters/anthropic-tools.d.ts +99 -0
  42. package/dist/llm/adapters/anthropic-tools.js +225 -0
  43. package/dist/llm/adapters/anthropic-tools.js.map +1 -0
  44. package/dist/llm/adapters/gemini-tools.d.ts +54 -0
  45. package/dist/llm/adapters/gemini-tools.js +139 -0
  46. package/dist/llm/adapters/gemini-tools.js.map +1 -0
  47. package/dist/llm/adapters/ollama-tools.d.ts +22 -0
  48. package/dist/llm/adapters/ollama-tools.js +62 -0
  49. package/dist/llm/adapters/ollama-tools.js.map +1 -0
  50. package/dist/llm/adapters/openai-tools.d.ts +39 -0
  51. package/dist/llm/adapters/openai-tools.js +71 -0
  52. package/dist/llm/adapters/openai-tools.js.map +1 -0
  53. package/dist/llm/agentrouter.js +23 -4
  54. package/dist/llm/agentrouter.js.map +1 -1
  55. package/dist/llm/anthropic.js +93 -92
  56. package/dist/llm/anthropic.js.map +1 -1
  57. package/dist/llm/aws-mantle.js +93 -56
  58. package/dist/llm/aws-mantle.js.map +1 -1
  59. package/dist/llm/bynara.js +23 -4
  60. package/dist/llm/bynara.js.map +1 -1
  61. package/dist/llm/capabilities.d.ts +7 -0
  62. package/dist/llm/capabilities.js +74 -0
  63. package/dist/llm/capabilities.js.map +1 -1
  64. package/dist/llm/gemini.js +63 -48
  65. package/dist/llm/gemini.js.map +1 -1
  66. package/dist/llm/groq.js +23 -4
  67. package/dist/llm/groq.js.map +1 -1
  68. package/dist/llm/http.d.ts +22 -17
  69. package/dist/llm/http.js +99 -26
  70. package/dist/llm/http.js.map +1 -1
  71. package/dist/llm/kimchi.js +23 -4
  72. package/dist/llm/kimchi.js.map +1 -1
  73. package/dist/llm/nvidia.js +23 -4
  74. package/dist/llm/nvidia.js.map +1 -1
  75. package/dist/llm/ollama.js +49 -31
  76. package/dist/llm/ollama.js.map +1 -1
  77. package/dist/llm/openai.js +23 -4
  78. package/dist/llm/openai.js.map +1 -1
  79. package/dist/llm/openrouter.js +23 -4
  80. package/dist/llm/openrouter.js.map +1 -1
  81. package/dist/llm/qwen-cloud.js +23 -4
  82. package/dist/llm/qwen-cloud.js.map +1 -1
  83. package/dist/llm/router.js +48 -6
  84. package/dist/llm/router.js.map +1 -1
  85. package/dist/llm/tool-protocol.d.ts +73 -0
  86. package/dist/llm/tool-protocol.js +282 -0
  87. package/dist/llm/tool-protocol.js.map +1 -0
  88. package/dist/modes/ask.js +74 -20
  89. package/dist/modes/ask.js.map +1 -1
  90. package/dist/prompts/index.d.ts +13 -5
  91. package/dist/prompts/index.js +122 -23
  92. package/dist/prompts/index.js.map +1 -1
  93. package/dist/repl.js +7 -0
  94. package/dist/repl.js.map +1 -1
  95. package/dist/store/config.d.ts +7 -0
  96. package/dist/store/config.js +1 -0
  97. package/dist/store/config.js.map +1 -1
  98. package/dist/store/plan.d.ts +2 -0
  99. package/dist/store/plan.js.map +1 -1
  100. package/dist/tools/capabilities.d.ts +2 -0
  101. package/dist/tools/capabilities.js +101 -6
  102. package/dist/tools/capabilities.js.map +1 -1
  103. package/dist/tools/command-intent.d.ts +1 -0
  104. package/dist/tools/command-intent.js +31 -0
  105. package/dist/tools/command-intent.js.map +1 -1
  106. package/dist/tools/definitions.d.ts +20 -0
  107. package/dist/tools/definitions.js +509 -0
  108. package/dist/tools/definitions.js.map +1 -0
  109. package/dist/tools/fs.d.ts +6 -0
  110. package/dist/tools/fs.js +68 -9
  111. package/dist/tools/fs.js.map +1 -1
  112. package/dist/tools/net-ping-sweep.js +69 -16
  113. package/dist/tools/net-ping-sweep.js.map +1 -1
  114. package/dist/tools/nmap-runner.d.ts +8 -7
  115. package/dist/tools/nmap-runner.js +126 -65
  116. package/dist/tools/nmap-runner.js.map +1 -1
  117. package/dist/tools/registry.js +122 -33
  118. package/dist/tools/registry.js.map +1 -1
  119. package/dist/tools/validate.d.ts +11 -0
  120. package/dist/tools/validate.js +53 -0
  121. package/dist/tools/validate.js.map +1 -1
  122. package/dist/tools/web/search.js +48 -4
  123. package/dist/tools/web/search.js.map +1 -1
  124. package/dist/tui/runtime.d.ts +19 -0
  125. package/dist/tui/runtime.js +92 -0
  126. package/dist/tui/runtime.js.map +1 -0
  127. package/dist/tui/state.js +2 -0
  128. package/dist/tui/state.js.map +1 -1
  129. package/dist/tui-v2/app/App.d.ts +3 -3
  130. package/dist/tui-v2/app/App.js +22 -10
  131. package/dist/tui-v2/app/App.js.map +1 -1
  132. package/dist/tui-v2/app/plan-lifecycle.d.ts +6 -0
  133. package/dist/tui-v2/app/plan-lifecycle.js +43 -7
  134. package/dist/tui-v2/app/plan-lifecycle.js.map +1 -1
  135. package/dist/tui-v2/components/modal/confirm-modal.d.ts +5 -6
  136. package/dist/tui-v2/components/modal/confirm-modal.js +60 -21
  137. package/dist/tui-v2/components/modal/confirm-modal.js.map +1 -1
  138. package/dist/tui-v2/components/modal/secret-modal.d.ts +3 -9
  139. package/dist/tui-v2/components/modal/secret-modal.js +17 -17
  140. package/dist/tui-v2/components/modal/secret-modal.js.map +1 -1
  141. package/dist/tui-v2/components/overlay/overlay-host.d.ts +12 -3
  142. package/dist/tui-v2/components/overlay/overlay-host.js +19 -4
  143. package/dist/tui-v2/components/overlay/overlay-host.js.map +1 -1
  144. package/dist/tui-v2/components/plan/plan-view.d.ts +1 -1
  145. package/dist/tui-v2/components/plan/plan-view.js +8 -4
  146. package/dist/tui-v2/components/plan/plan-view.js.map +1 -1
  147. package/dist/tui-v2/components/status/status-line.js +10 -1
  148. package/dist/tui-v2/components/status/status-line.js.map +1 -1
  149. package/dist/tui-v2/components/transcript/assistant-message.js +6 -0
  150. package/dist/tui-v2/components/transcript/assistant-message.js.map +1 -1
  151. package/dist/tui-v2/components/transcript/compacted-row.d.ts +5 -5
  152. package/dist/tui-v2/components/transcript/compacted-row.js +15 -40
  153. package/dist/tui-v2/components/transcript/compacted-row.js.map +1 -1
  154. package/dist/tui-v2/components/transcript/tool-card.js +43 -13
  155. package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
  156. package/dist/tui-v2/components/transcript/transcript-row.js +8 -1
  157. package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
  158. package/dist/tui-v2/rendering/batch-sections.d.ts +12 -0
  159. package/dist/tui-v2/rendering/batch-sections.js +68 -0
  160. package/dist/tui-v2/rendering/batch-sections.js.map +1 -1
  161. package/dist/tui-v2/rendering/strip-tool-surfaces.d.ts +10 -0
  162. package/dist/tui-v2/rendering/strip-tool-surfaces.js +32 -0
  163. package/dist/tui-v2/rendering/strip-tool-surfaces.js.map +1 -0
  164. package/dist/tui-v2/rendering/tool-presenter.d.ts +6 -1
  165. package/dist/tui-v2/rendering/tool-presenter.js +68 -2
  166. package/dist/tui-v2/rendering/tool-presenter.js.map +1 -1
  167. package/dist/tui-v2/state/transcript-hydrate.js +6 -2
  168. package/dist/tui-v2/state/transcript-hydrate.js.map +1 -1
  169. package/dist/tui-v2/state/transcript-reducer.js +74 -16
  170. package/dist/tui-v2/state/transcript-reducer.js.map +1 -1
  171. package/dist/tui-v2/state/transcript-types.d.ts +1 -1
  172. package/dist/types.d.ts +58 -0
  173. package/dist/ui/ansi-box.d.ts +2 -0
  174. package/dist/ui/ansi-box.js +8 -1
  175. package/dist/ui/ansi-box.js.map +1 -1
  176. package/package.json +13 -4
@@ -1,7 +1,7 @@
1
1
  import { platform } from "node:os";
2
2
  import { commandAvailable } from "../os/pkgmgr.js";
3
3
  import { spawnArgv } from "./shell.js";
4
- import { nmapScanNeedsPrivilege, toConnectScanArgv } from "./validate.js";
4
+ import { isNmapSingleHostTarget, looksLikeNmapNoHostsUp, nmapArgvHasPn, nmapScanNeedsPrivilege, toConnectScanArgv, withNmapSkipDiscovery, } from "./validate.js";
5
5
  /**
6
6
  * Pick the OS-appropriate privilege-escalation prefix for a raw-socket scan.
7
7
  * - macOS / Linux → `sudo` (clai forwards stdin so the user types their
@@ -36,24 +36,33 @@ async function elevationPrefix() {
36
36
  function looksLikePrivilegeError(output) {
37
37
  return /(?:requires root privileges|you (?:requested|need) (?:a scan type|root)|operation not permitted|must (?:be|run as) root|raw sockets?|sudo: (?:a (?:password|terminal) is required|no askpass|3 incorrect)|incorrect password|authentication failure|permission denied|requires (?:administrator|elevation))/i.test(output);
38
38
  }
39
+ function canInteractiveSudo() {
40
+ return Boolean(process.stdin.isTTY);
41
+ }
39
42
  /**
40
43
  * Run an nmap scan, transparently obtaining the privileges a stealth/raw
41
44
  * scan needs and falling back to an unprivileged TCP connect scan when those
42
45
  * privileges can't be obtained (no sudo, password declined, etc.).
43
46
  *
44
47
  * Strategy:
45
- * 1. If the scan needs raw sockets and we're not root, wrap it in the
46
- * OS-appropriate elevation helper (sudo / doas / gsudo). stdin is
47
- * inherited so the user can type their password live exactly the
48
- * pattern documented for shell.exec sudo.
49
- * 2. If elevation is unavailable, or the privileged attempt fails in a way
48
+ * 1. Single-host port scans get -Pn so failed host-discovery pings do not
49
+ * report "0 hosts up" while ports are actually open.
50
+ * 2. If the scan needs raw sockets and we're not root, wrap it in the
51
+ * OS-appropriate elevation helper (sudo / doas / gsudo). Prefer the
52
+ * secure requestSecret path (TUI); else interactive TTY sudo.
53
+ * 3. If elevation is unavailable, or the privileged attempt fails in a way
50
54
  * that looks like a permission/privilege error, retry as `-sT` (TCP
51
55
  * connect) which works for any user on every OS.
52
- * This is the "most general approach first, then fall back" behavior the
53
- * scans need so they never dead-end on "you must be root".
56
+ * 4. If a run still looks like "host down / 0 hosts up" without -Pn, retry
57
+ * once with -Pn before returning.
54
58
  */
55
59
  export async function runNmapScan(argv, options) {
56
- const needsPrivilege = nmapScanNeedsPrivilege(argv);
60
+ // Known single hosts: skip discovery by default (ICMP/ARP often blocked or
61
+ // needs root; false "0 hosts up" is a common agent failure mode).
62
+ let scanArgv = isNmapSingleHostTarget(argv) && !nmapArgvHasPn(argv)
63
+ ? withNmapSkipDiscovery(argv)
64
+ : [...argv];
65
+ const needsPrivilege = nmapScanNeedsPrivilege(scanArgv);
57
66
  const prefix = needsPrivilege ? await elevationPrefix() : undefined;
58
67
  const attempts = [];
59
68
  if (needsPrivilege && prefix) {
@@ -61,74 +70,95 @@ export async function runNmapScan(argv, options) {
61
70
  // Authenticate in a short, dedicated process. In the TUI, never inherit
62
71
  // stdin for the long nmap scan: pipe the already-entered password to
63
72
  // sudo so Ink keeps receiving Escape/Ctrl+C while nmap is running.
64
- options?.onOutput?.(options?.requestSecret
65
- ? "\nAdministrator access is required for a stealth scan. Complete the secure password prompt below.\n"
66
- : "\nAdministrator access is required for a stealth scan. Enter your sudo password below; Ctrl+C cancels.\n", "stdout");
67
- let auth;
68
- let sudoPassword;
69
- if (options?.requestSecret) {
70
- const password = await options.requestSecret({
71
- title: "Administrator access",
72
- prompt: "Enter your macOS password for sudo. It is sent only to sudo and is never stored.",
73
- });
74
- if (password === undefined) {
75
- return { ok: false, output: "Administrator authentication cancelled.", exitCode: 130 };
76
- }
77
- sudoPassword = password;
78
- auth = await spawnArgv({
79
- command: "sudo",
80
- argv: ["-S", "-p", "", "-v"],
81
- stdinText: `${password}\n`,
82
- timeoutMs: 30_000,
83
- signal: options.signal,
84
- onOutput: options.onOutput,
85
- noArtifact: true,
86
- interactiveStdin: false,
87
- });
88
- }
89
- else {
90
- // Classic REPL: let sudo read directly from its controlling terminal.
91
- auth = await spawnArgv({
92
- command: "sudo",
93
- argv: [...prefix.argv, "-v"],
94
- timeoutMs: 120_000,
95
- signal: options?.signal,
96
- onOutput: options?.onOutput,
97
- interactiveStdin: true,
98
- noArtifact: true,
99
- });
100
- }
101
- if (options?.signal?.aborted || auth.exitCode === 130)
102
- return auth;
103
- if (auth.ok) {
104
- attempts.push({
105
- command: "sudo",
106
- argv: options?.requestSecret ? ["-S", "-p", "", "nmap", ...argv] : ["-n", "nmap", ...argv],
107
- stdinText: options?.requestSecret ? `${sudoPassword ?? ""}\n` : undefined,
108
- note: "Administrator access confirmed. Starting stealth scan (ESC cancels).",
109
- });
73
+ const useSecret = Boolean(options?.requestSecret);
74
+ const useTty = !useSecret && canInteractiveSudo();
75
+ if (!useSecret && !useTty) {
76
+ // No password UI and no TTY — interactive sudo would hang or fail
77
+ // with "a terminal is required". Skip straight to connect-scan.
78
+ options?.onOutput?.("\nCannot prompt for sudo password here (no secret UI / no TTY). Using an unprivileged TCP connect scan (-sT -Pn).\n", "stderr");
110
79
  }
111
80
  else {
112
- options?.onOutput?.("\nSudo authentication was not completed; using an unprivileged TCP connect scan instead.\n", "stderr");
81
+ options?.onOutput?.(useSecret
82
+ ? "\nAdministrator access is required for a stealth scan. Complete the secure password prompt below.\n"
83
+ : "\nAdministrator access is required for a stealth scan. Enter your sudo password below; Ctrl+C cancels.\n", "stdout");
84
+ let auth;
85
+ let sudoPassword;
86
+ if (useSecret && options?.requestSecret) {
87
+ const password = await options.requestSecret({
88
+ title: "Administrator access",
89
+ prompt: "Enter your password for sudo (nmap raw-socket scan). It is sent only to sudo and is never stored.",
90
+ });
91
+ if (password === undefined) {
92
+ return {
93
+ ok: false,
94
+ output: "Administrator authentication cancelled. Re-run and enter your password, or use profile.scanType \"tcp\" for an unprivileged connect scan.",
95
+ exitCode: 130,
96
+ };
97
+ }
98
+ sudoPassword = password;
99
+ auth = await spawnArgv({
100
+ command: "sudo",
101
+ argv: ["-S", "-p", "", "-v"],
102
+ stdinText: `${password}\n`,
103
+ timeoutMs: 30_000,
104
+ signal: options.signal,
105
+ onOutput: options.onOutput,
106
+ noArtifact: true,
107
+ interactiveStdin: false,
108
+ });
109
+ }
110
+ else {
111
+ // Classic REPL: let sudo read directly from its controlling terminal.
112
+ auth = await spawnArgv({
113
+ command: "sudo",
114
+ argv: [...prefix.argv, "-v"],
115
+ timeoutMs: 120_000,
116
+ signal: options?.signal,
117
+ onOutput: options?.onOutput,
118
+ interactiveStdin: true,
119
+ noArtifact: true,
120
+ });
121
+ }
122
+ if (options?.signal?.aborted || auth.exitCode === 130)
123
+ return auth;
124
+ if (auth.ok) {
125
+ attempts.push({
126
+ command: "sudo",
127
+ // -n after a successful -v uses the cached credential without
128
+ // another password prompt (and without needing another stdin).
129
+ argv: useSecret
130
+ ? ["-S", "-p", "", "nmap", ...scanArgv]
131
+ : ["-n", "nmap", ...scanArgv],
132
+ stdinText: useSecret ? `${sudoPassword ?? ""}\n` : undefined,
133
+ note: "Administrator access confirmed. Starting stealth scan (ESC cancels).",
134
+ });
135
+ }
136
+ else {
137
+ const detail = auth.output?.trim()
138
+ ? `\n${auth.output.trim().slice(0, 400)}`
139
+ : "";
140
+ options?.onOutput?.(`\nSudo authentication failed or was not completed; using an unprivileged TCP connect scan instead.${detail}\n`, "stderr");
141
+ }
113
142
  }
114
143
  }
115
144
  else if (prefix.command) {
116
145
  attempts.push({
117
146
  command: prefix.command,
118
- argv: [...prefix.argv, "nmap", ...argv],
147
+ argv: [...prefix.argv, "nmap", ...scanArgv],
119
148
  interactiveStdin: true,
120
149
  note: `Running a stealth scan with ${prefix.command} (you may be prompted for your password).`,
121
150
  });
122
151
  }
123
152
  else {
124
153
  // Already root.
125
- attempts.push({ command: "nmap", argv });
154
+ attempts.push({ command: "nmap", argv: scanArgv });
126
155
  }
127
156
  // Fallback: unprivileged connect scan if elevation fails/declines.
157
+ const connectArgv = withNmapSkipDiscovery(toConnectScanArgv(scanArgv));
128
158
  attempts.push({
129
159
  command: "nmap",
130
- argv: toConnectScanArgv(argv),
131
- note: "Privileged scan unavailable — falling back to an unprivileged TCP connect scan (-sT).",
160
+ argv: connectArgv,
161
+ note: "Privileged scan unavailable — falling back to an unprivileged TCP connect scan (-sT -Pn).",
132
162
  });
133
163
  }
134
164
  else if (needsPrivilege && !prefix) {
@@ -136,14 +166,14 @@ export async function runNmapScan(argv, options) {
136
166
  // but tell the user why the stealth scan was downgraded.
137
167
  attempts.push({
138
168
  command: "nmap",
139
- argv: toConnectScanArgv(argv),
169
+ argv: withNmapSkipDiscovery(toConnectScanArgv(scanArgv)),
140
170
  note: platform() === "win32"
141
- ? "No elevation helper found (sudo/gsudo). Run from an Administrator terminal with Npcap for a SYN scan; using a TCP connect scan (-sT) for now."
142
- : "No sudo/doas available for a raw-socket SYN scan — using an unprivileged TCP connect scan (-sT) instead.",
171
+ ? "No elevation helper found (sudo/gsudo). Run from an Administrator terminal with Npcap for a SYN scan; using a TCP connect scan (-sT -Pn) for now."
172
+ : "No sudo/doas available for a raw-socket SYN scan — using an unprivileged TCP connect scan (-sT -Pn) instead.",
143
173
  });
144
174
  }
145
175
  else {
146
- attempts.push({ command: "nmap", argv });
176
+ attempts.push({ command: "nmap", argv: scanArgv });
147
177
  }
148
178
  let last;
149
179
  for (let i = 0; i < attempts.length; i += 1) {
@@ -153,7 +183,7 @@ export async function runNmapScan(argv, options) {
153
183
  }
154
184
  if (attempt.note)
155
185
  options?.onOutput?.(`\n${attempt.note}\n`, "stdout");
156
- const result = await spawnArgv({
186
+ let result = await spawnArgv({
157
187
  command: attempt.command,
158
188
  argv: attempt.argv,
159
189
  stdinText: attempt.stdinText,
@@ -164,10 +194,41 @@ export async function runNmapScan(argv, options) {
164
194
  ? { interactiveStdin: attempt.interactiveStdin }
165
195
  : {}),
166
196
  });
197
+ // Host discovery false negative: retry once with -Pn on the same elevation.
198
+ if (result.ok &&
199
+ looksLikeNmapNoHostsUp(result.output) &&
200
+ !nmapArgvHasPn(attempt.argv) &&
201
+ isNmapSingleHostTarget(attempt.argv)) {
202
+ options?.onOutput?.("\nHost discovery reported no hosts up — retrying once with -Pn (treat host as online).\n", "stdout");
203
+ result = await spawnArgv({
204
+ command: attempt.command,
205
+ argv: withNmapSkipDiscovery(attempt.argv),
206
+ stdinText: attempt.stdinText,
207
+ timeoutMs: 300_000,
208
+ signal: options?.signal,
209
+ onOutput: options?.onOutput,
210
+ ...(attempt.interactiveStdin !== undefined
211
+ ? { interactiveStdin: attempt.interactiveStdin }
212
+ : {}),
213
+ });
214
+ }
167
215
  last = result;
168
216
  // Success, or a non-privilege failure we shouldn't paper over → return.
169
217
  const isLastAttempt = i === attempts.length - 1;
170
218
  if (result.ok || isLastAttempt || !looksLikePrivilegeError(result.output)) {
219
+ // Annotate empty single-host results so the model does not loop forever.
220
+ if (result.ok &&
221
+ looksLikeNmapNoHostsUp(result.output) &&
222
+ isNmapSingleHostTarget(attempt.argv)) {
223
+ return {
224
+ ...result,
225
+ output: result.output +
226
+ "\n\nNote: nmap still reported no live host/open ports after -Pn. " +
227
+ "The target may be offline, firewalled, or blocking this host. " +
228
+ "Do not retry the same net.scan args in a loop — try different ports, " +
229
+ "confirm the IP with net.context / net.pingSweep, or use shell.exec for a custom nmap line.",
230
+ };
231
+ }
171
232
  return result;
172
233
  }
173
234
  // Otherwise loop to the next (fallback) attempt.
@@ -1 +1 @@
1
- {"version":3,"file":"nmap-runner.js","sourceRoot":"","sources":["../../src/tools/nmap-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nmap-runner.js","sourceRoot":"","sources":["../../src/tools/nmap-runner.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { safeCwd } from "../os/cwd.js";
4
4
  import { fsEdit, fsDelete, fsList, fsRead, fsSearch, fsWrite, fsWriteMany, fsReplaceLines, fsAppend, } from "./fs.js";
5
5
  import { httpFetch } from "./http.js";
6
6
  import { shellExec, spawnArgv } from "./shell.js";
7
+ import { isLongQuietInstallOrScaffoldCommand, } from "../agent/task-evidence.js";
7
8
  import { imageOcr } from "./image.js";
8
9
  import { pdfRead } from "./pdf.js";
9
10
  import { webFetch } from "./web/fetch.js";
@@ -20,6 +21,7 @@ import { jobManager } from "./jobs.js";
20
21
  import { looksLongRunning } from "./command-intent.js";
21
22
  import { packageBinaryName } from "./package-binary.js";
22
23
  import { runNmapScan } from "./nmap-runner.js";
24
+ import { fromWireName, sanitizeToolName } from "../llm/tool-protocol.js";
23
25
  function requireString(args, key) {
24
26
  const value = args[key];
25
27
  if (typeof value !== "string" || value.length === 0) {
@@ -27,6 +29,14 @@ function requireString(args, key) {
27
29
  }
28
30
  return value;
29
31
  }
32
+ /** Like requireString but allows empty string (e.g. replaceLines delete). */
33
+ function requireStringAllowEmpty(args, key) {
34
+ const value = args[key];
35
+ if (typeof value !== "string") {
36
+ throw new Error(`Tool argument "${key}" must be a string`);
37
+ }
38
+ return value;
39
+ }
30
40
  function optionalString(args, key) {
31
41
  const value = args[key];
32
42
  return typeof value === "string" && value.length > 0 ? value : undefined;
@@ -82,10 +92,15 @@ export const toolRegistry = {
82
92
  }
83
93
  return job;
84
94
  }
95
+ // create-next-app / npm install often exceed the default 3 min shell timeout
96
+ // when the model omits timeoutMs — bump automatically for known long jobs.
97
+ const explicitTimeout = optionalNumber(args, "timeoutMs");
98
+ const timeoutMs = explicitTimeout ??
99
+ (isLongQuietInstallOrScaffoldCommand(command) ? 900_000 : undefined);
85
100
  return shellExec({
86
101
  command,
87
102
  cwd: optionalString(args, "cwd"),
88
- timeoutMs: optionalNumber(args, "timeoutMs"),
103
+ timeoutMs,
89
104
  signal: options?.signal,
90
105
  onOutput: options?.onOutput,
91
106
  });
@@ -297,10 +312,12 @@ export const toolRegistry = {
297
312
  */
298
313
  async "whois.lookup"(args, options) {
299
314
  const host = parseHost(requireString(args, "target"));
315
+ // Keep whois short-lived: many servers hang or buffer until close. A hard
316
+ // 20s cap beats the outer 60s "no output" stall watchdog aborting mid-recon.
300
317
  return spawnArgv({
301
318
  command: "whois",
302
319
  argv: [host.value],
303
- timeoutMs: 60_000,
320
+ timeoutMs: 20_000,
304
321
  signal: options?.signal,
305
322
  onOutput: options?.onOutput,
306
323
  });
@@ -368,28 +385,56 @@ export const toolRegistry = {
368
385
  }
369
386
  const outputs = [];
370
387
  const transcript = [];
388
+ let anyOk = false;
389
+ const userAborted = () => Boolean(options?.signal?.aborted);
371
390
  for (const step of steps) {
372
- if (options?.signal?.aborted)
391
+ if (userAborted())
373
392
  break;
374
393
  const display = `${step.command} ${step.argv.join(" ")}`;
375
394
  transcript.push(`$ ${display}`);
376
395
  // Announce each sub-step so users see progress through long recons.
377
396
  options?.onOutput?.(`\n$ ${display}\n`, "stdout");
378
- const result = step.key === "nmap"
379
- ? // Route the scan through the privilege-aware runner so the
380
- // stealth SYN scan is elevated (sudo/elevation) and falls back
381
- // to a connect scan when privilege isn't available.
382
- await runNmapScan(step.argv, options)
383
- : await spawnArgv({
384
- command: step.command,
385
- argv: step.argv,
386
- timeoutMs: 180_000,
387
- signal: options?.signal,
388
- onOutput: options?.onOutput,
389
- });
390
- outputs.push(result.output);
391
- transcript.push(result.output);
392
- if (options?.signal?.aborted)
397
+ // Heartbeat so the outer 60s stall watchdog doesn't kill a quiet whois.
398
+ const beat = setInterval(() => {
399
+ options?.onOutput?.(`[recon] ${step.key} still running…\n`, "stdout");
400
+ }, 5_000);
401
+ beat.unref?.();
402
+ // Per-step caps: whois often hangs; nmap needs longer; dns is quick.
403
+ const stepTimeoutMs = step.key === "whois" ? 20_000 : step.key === "dns" ? 30_000 : 180_000;
404
+ let result;
405
+ try {
406
+ result =
407
+ step.key === "nmap"
408
+ ? await runNmapScan(step.argv, options)
409
+ : await spawnArgv({
410
+ command: step.command,
411
+ argv: step.argv,
412
+ timeoutMs: stepTimeoutMs,
413
+ signal: options?.signal,
414
+ onOutput: options?.onOutput,
415
+ });
416
+ }
417
+ catch (error) {
418
+ const msg = error instanceof Error ? error.message : String(error);
419
+ result = {
420
+ ok: false,
421
+ output: `${step.key} error: ${msg}`,
422
+ exitCode: 1,
423
+ };
424
+ }
425
+ finally {
426
+ clearInterval(beat);
427
+ }
428
+ // Continue remaining steps even if whois/dns fails — never cancel
429
+ // sibling top-level tools just because whois hung.
430
+ if (result.ok)
431
+ anyOk = true;
432
+ const body = result.ok
433
+ ? result.output
434
+ : `${result.output || `${step.key} failed`}${result.exitCode !== undefined ? ` (exit ${result.exitCode})` : ""}`;
435
+ outputs.push(body);
436
+ transcript.push(body);
437
+ if (userAborted())
393
438
  break;
394
439
  }
395
440
  if (artifactPath) {
@@ -401,12 +446,15 @@ export const toolRegistry = {
401
446
  artifactPath = undefined;
402
447
  }
403
448
  }
449
+ // Soft-success when any step worked and the user didn't cancel. Partial
450
+ // whois failure still returns useful dns/nmap output to the model.
451
+ const aborted = userAborted();
404
452
  return {
405
- ok: !options?.signal?.aborted,
406
- output: options?.signal?.aborted
453
+ ok: aborted ? false : anyOk || outputs.length > 0,
454
+ output: aborted
407
455
  ? `${outputs.join("\n\n")}\n\nCommand aborted.`.trim()
408
456
  : outputs.join("\n\n"),
409
- exitCode: options?.signal?.aborted ? 130 : 0,
457
+ exitCode: aborted ? 130 : anyOk ? 0 : 1,
410
458
  ...(artifactPath ? { outputPath: artifactPath } : {}),
411
459
  };
412
460
  },
@@ -460,11 +508,23 @@ export const toolRegistry = {
460
508
  return fsEdit(requireString(args, "path"), requireString(args, "oldText"), requireString(args, "newText"), optionalNumber(args, "expectedReplacements"), { confirmed: options?.confirmed });
461
509
  },
462
510
  async "fs.replaceLines"(args, options) {
463
- return fsReplaceLines(requireString(args, "path"), requireNumber(args, "startLine"), requireNumber(args, "endLine"), requireString(args, "content"), { confirmed: options?.confirmed });
511
+ // Empty content / delete:true removes the line range (X6).
512
+ let content;
513
+ if (args.delete === true) {
514
+ content = "";
515
+ }
516
+ else if (typeof args.content === "string") {
517
+ content = requireStringAllowEmpty(args, "content");
518
+ }
519
+ else {
520
+ throw new Error('Tool argument "content" must be a string (use "" or delete:true to delete the line range)');
521
+ }
522
+ return fsReplaceLines(requireString(args, "path"), requireNumber(args, "startLine"), requireNumber(args, "endLine"), content, { confirmed: options?.confirmed });
464
523
  },
465
524
  async "fs.append"(args, options) {
466
525
  return fsAppend(requireString(args, "path"), requireString(args, "content"), {
467
526
  position: optionalString(args, "position"),
527
+ expectedPriorBytes: optionalNumber(args, "expectedPriorBytes"),
468
528
  confirmed: options?.confirmed,
469
529
  });
470
530
  },
@@ -535,13 +595,25 @@ function buildShellCommandFromCall(name, args) {
535
595
  * dangerous commands are gated exactly as a hand-written shell.exec would be.
536
596
  */
537
597
  export function normalizeToolCall(call) {
538
- if (toolRegistry[call.name])
539
- return call;
540
- const name = typeof call.name === "string" ? call.name.trim() : "";
598
+ // X1: strip channel/commentary tokens before registry lookup.
599
+ let name = typeof call.name === "string" ? call.name.trim() : "";
600
+ if (name && !toolRegistry[name]) {
601
+ const cleaned = sanitizeToolName(name);
602
+ const mapped = fromWireName(cleaned) ?? fromWireName(name) ?? cleaned;
603
+ if (mapped && toolRegistry[mapped]) {
604
+ return { name: mapped, args: call.args ?? {} };
605
+ }
606
+ if (cleaned && cleaned !== name)
607
+ name = cleaned;
608
+ }
609
+ if (toolRegistry[name]) {
610
+ return name === call.name ? call : { name, args: call.args ?? {} };
611
+ }
541
612
  // Leave genuinely unknown namespaced tools (e.g. a typo'd "fs.reed") to
542
613
  // surface a clear error rather than guessing at a shell command.
543
- if (!name || name.includes(".") || name.includes("/"))
544
- return call;
614
+ if (!name || name.includes(".") || name.includes("/")) {
615
+ return name === call.name ? call : { name, args: call.args ?? {} };
616
+ }
545
617
  const args = call.args ?? {};
546
618
  const command = buildShellCommandFromCall(name, args);
547
619
  if (!command)
@@ -716,10 +788,22 @@ async function runToolBatch(args, options) {
716
788
  }
717
789
  tick(`[batch] #${index + 1} ${spec.name} starting`);
718
790
  try {
719
- const result = await runToolCall({ name: spec.name, args: spec.args },
720
- // Do not fan child stdout into the card (keeps parseable sections
721
- // clean). Progress lines above are enough for the stall watchdog.
722
- { signal: batchAc.signal });
791
+ // Lightweight child heartbeats so silent tools (whois) never trip the
792
+ // outer 60s stall watchdog while still running under the batch.
793
+ const childHeartbeat = setInterval(() => {
794
+ tick(`[batch] #${index + 1} ${spec.name} still running…`);
795
+ }, BATCH_HEARTBEAT_MS);
796
+ childHeartbeat.unref?.();
797
+ let result;
798
+ try {
799
+ result = await runToolCall({ name: spec.name, args: spec.args },
800
+ // Do not fan full child stdout into the card (keeps final sections
801
+ // clean). Progress ticks above drive the live UI + stall watchdog.
802
+ { signal: batchAc.signal });
803
+ }
804
+ finally {
805
+ clearInterval(childHeartbeat);
806
+ }
723
807
  outcomes[index] = {
724
808
  index,
725
809
  name: spec.name,
@@ -770,6 +854,7 @@ async function runToolBatch(args, options) {
770
854
  }
771
855
  const finalOutcomes = outcomes;
772
856
  const allOk = finalOutcomes.every((outcome) => outcome.ok);
857
+ const parentAborted = Boolean(options?.signal?.aborted);
773
858
  const sections = finalOutcomes.map((outcome) => {
774
859
  const status = outcome.ok ? "ok" : "fail";
775
860
  const head = `── #${outcome.index + 1} ${outcome.name} [${status}${outcome.exitCode !== undefined ? ` exit=${outcome.exitCode}` : ""}]`;
@@ -784,10 +869,14 @@ async function runToolBatch(args, options) {
784
869
  `[batch] timed out after ${BATCH_HARD_TIMEOUT_MS / 1000}s — partial results below\n\n` +
785
870
  output;
786
871
  }
872
+ // Soft-success for the agent turn: one failed whois/dns must NOT cancel
873
+ // sibling top-level tools (http.fetch, net.scan, …). Partial failures stay
874
+ // visible in the sectioned body and non-zero exitCode for the model.
875
+ const softOk = !parentAborted && !timedOut;
787
876
  return {
788
- ok: allOk,
877
+ ok: softOk ? true : allOk,
789
878
  output,
790
- exitCode: allOk ? 0 : timedOut ? 124 : 1,
879
+ exitCode: allOk ? 0 : timedOut ? 124 : parentAborted ? 130 : 1,
791
880
  };
792
881
  }
793
882
  //# sourceMappingURL=registry.js.map